Authorise operations for a caller¶
mcp.New requires a policy. AllowRegistered() is the deliberate choice for a
local stdio host serving one person's own commands; a service with more than
one caller writes its own.
The two callbacks¶
policy := mcp.Policy{
Authorize: func(ctx context.Context, req mcp.AccessRequest) error {
principal, ok := identityFrom(ctx)
if !ok {
return mcp.NewFailure(mcp.FailureUnavailable, "Sign in to use this tool", nil)
}
switch req.Kind {
case mcp.Discover:
if !principal.MaySee(req.Operation) {
return mcp.NewFailure(mcp.FailureUnavailable, "Operation not available", nil)
}
case mcp.Execute:
if !principal.MayRun(req.Operation, req.Arguments) {
return mcp.NewFailure(mcp.FailureUnavailable, "Operation not available", nil)
}
}
return nil
},
ScopeKey: func(ctx context.Context) (string, error) {
principal, ok := identityFrom(ctx)
if !ok {
return "", errors.New("no identity")
}
return principal.ID + "/" + principal.PolicyRevision, nil
},
}
Authorize is asked twice for a call that was discovered first: once with
Discover when the operation is searched or inspected, and again with
Execute, carrying the validated canonical arguments, when it is invoked. The
second answer does not depend on the first; a name a caller may inspect is not
a name they may run, and a known name may be invoked without any search.
Deny with a FailureUnavailable. Unknown names and denied names produce the
same safe answer, so a caller cannot probe the catalogue. Any other error, or a
panic, fails the request rather than authorising it.
What ScopeKey is for¶
ScopeKey names the caller's effective authorisation: an opaque, credential-free
string that changes when their permissions change. Search cursors, resource
list cursors and confirmation continuations are bound to it, so a token issued
to one principal cannot be replayed by another, and one issued under an old
policy stops working when the policy moves. Include a policy revision in it
when permissions can change under a live session.
Resource policy¶
mcp.NewResources takes its own ResourcePolicy with four callbacks:
Discover for registered descriptions, Visible for whether a planned item is
in this caller's selection, Read for body access, and ScopeKey as above.
The gateway asks Visible and Read for every item before reading any body,
and asks again before delivering, so an item revoked mid-read is removed. A
hidden item produces no warning; a visible item denied a read produces one.
Rules the module enforces for you¶
- Both callbacks are required and must be safe to call concurrently.
- Policy runs before application code, on detached arguments, so a handler cannot see a value the policy did not.
- Every search page and every resume rechecks policy. Nothing caches an authorised catalogue between callers.
- Hints (
readOnly,destructiveand the rest) describe an operation; they never grant or withhold access. Presentation hints likewise.
The registry reference states the exact contracts.