Grants

NAME

MCP::Client::Policy::Grants - the session grant book a fleet of agents share

SYNOPSIS

use MCP::Client::Policy;
use MCP::Client::Policy::Grants;

# One store for the whole session, seeded from what the last session remembered.
my $grants = MCP::Client::Policy::Grants.new(grants => $session.grants);

my $parent = MCP::Client::Policy.new(:$provider, :&on-ask, grants-store => $grants);
my $child  = MCP::Client::Policy.new(:$provider, :&on-ask, grants-store => $grants);

# The human says "always allow" on the parent's question ...
$grants.list.elems;      # 1

# ... and the child never asks that question at all.

DESCRIPTION

A session grant — the rule an always-allow or always-deny answer leaves behind — belongs to the human, not to the object that happened to ask the question. A single agent cannot tell the difference, because there is only one policy. A fleet can: ten children, each with its own MCP::Client::Policy, will ask the same question ten times if each of them remembers the answer privately.

This is the shared answer to that: a small, lock-protected list of rules that any number of policies consult and any of them can add to. Give the same store to every policy in a session and the doctrine becomes one session, one grant set — the human says "always" once, and every agent alive at that moment, plus every agent started afterwards, is bound by it.

What it does not change

A store is a place to keep grants, not a new kind of permission. Everything about how a grant is used stays where it was, in the policy:

  • grants are consulted only after the static rules have said ask, so a grant can never overrule a deny rule, and never overrules the danger floor;

  • among the rules that do get consulted, the same precedence applies — deny > ask > allow — so a deny grant beats an allow grant whichever policy made either of them;

  • a rule is validated before it is kept, by exactly the validator the policy's own rules go through.

Live, not snapshotted

Policies read the store on every decision rather than copying it at construction, so a grant made a millisecond ago through one policy is in force for the next call through another. This is the whole point: a fleet unblocks the moment the human answers, not at the next agent's next start-up.

Thread safety

One lock, held only across the list. Everything that comes out is a deep plain-data copy, so a caller may keep and edit a snapshot — persist it, render it, diff it — without the store moving underneath it or the caller's edits reaching back in.

EXAMPLES

Seeding a resumed session, then persisting whatever it learns:

my $grants = MCP::Client::Policy::Grants.new(grants => from-json(slurp 'grants.json'));

my $policy = MCP::Client::Policy.new(
    :$provider, :&on-ask,
    grants-store => $grants,
    on-grant     => -> @all { spurt 'grants.json', to-json(@all) },
);

Adding a grant nobody was asked about — a preset that starts a session already trusting a directory, say. add takes a list, validates every rule in it, and appends them in order:

$grants.add([
    { tool => 'fs_write', decision => 'allow', under => '/srv/scratch' },
    { tool => 'fs_edit',  decision => 'allow', under => '/srv/scratch' },
]);

A rule that will not validate throws, and nothing is added — a half-applied list of grants would leave a session trusting something the caller never described:

$grants.add([{ tool => 'fs_write', decision => 'allow', under => '/a/../b' }]);
# X::MCP::Client: invalid under '/a/../b' in a session grant for 'fs_write' ...
say $grants.list.elems;   # unchanged

SEE ALSO

MCP::Client::Policy — the grants-store option, and what a grant means once it is in the book.

MCP::Client v0.5.0

talk to an MCP server, in either protocol era

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

MCP::Server:ver<0.6.0+>:auth<zef:apogee>JSON::Fast:ver<0.19+>:auth<cpan:TIMOTIMO>Cro::HTTP:ver<0.8.11+>:auth<zef:cro>:api<0>MIME::Base64:ver<1.2.5+>:auth<zef:raku-community-modules>

Test Dependencies

Provides

  • MCP::Client
  • MCP::Client::Cache
  • MCP::Client::Correlator
  • MCP::Client::Exceptions
  • MCP::Client::Leases
  • MCP::Client::Leases::Table
  • MCP::Client::Policy
  • MCP::Client::Policy::Commands
  • MCP::Client::Policy::Floor
  • MCP::Client::Policy::Grants
  • MCP::Client::Policy::Rules
  • MCP::Client::Protocol
  • MCP::Client::Reasons
  • MCP::Client::Registry
  • MCP::Client::SSE
  • MCP::Client::Transport
  • MCP::Client::Transport::HTTP
  • MCP::Client::Transport::Stdio
  • MCP::Client::UnknownKeys

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite — the markup and publishing tools behind this site.