You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Once the new ACL (#55) is in place, operators need two things to run it in production with confidence:
A way to ask the ACL "would this request be allowed?" without actually issuing the request. Today the only way to test a rule change is to hit the proxy and see what happens.
Audit logs that explain the decision. The current AuditEntry records that something was allowed or denied but not why — which role, which grant, what the tool was classified as, how confident the classifier was.
This issue delivers both, because they hit exactly the same integration point in the evaluator: the moment a decision is made, we already have everything the debug CLI and the audit enrichment need.
Make ACL decisions inspectable from the command line without running the proxy, and make audit log entries carry enough structured context to answer operational questions ("who tried to write to prod?", "which tool was blocked because the classifier was unsure?") with a single grep.
Synthesizes an AuthIdentity for the given subject using the roles that subject would have according to subjects.<name>.roles in the config. The subject does not need to have a real token — this is a pure policy check.
Runs the ACL evaluator against (subject, server, tool, access) and prints:
Decision: ALLOW or DENY.
Reason: which role and which grant index matched (or default if nothing matched). For denies caused by an explicit deny: true grant, say so clearly.
Classification context: the tool's classified kind, source (override / annotation / classifier / fallback), and confidence.
Effective access: what access the request was evaluated against (if --access was omitted, the CLI uses the tool's classification to pick read or write automatically and reports which it chose).
Optional flags worth considering: --format json for scripting, --role <name> to check a hypothetical role without needing a subject, --all-tools to enumerate every tool on a server and show the decision for each (useful for auditing what a role can actually reach).
Exit code: 0 for allow, non-zero for deny. Enables scripting in CI or pre-deploy checks.
Examples (illustrative output, exact format is an implementation choice):
$ mcp acl check --subject alice --server grafana --tool query_prometheus
ALLOW via role=dev, grant[0] access=read classification=classifier:Read (confidence 0.72)
$ mcp acl check --subject bob --server databricks --tool execute_sql
DENY default deny; bob has no matching grant for databricks/write
classification=classifier:Write (confidence 0.81)
Audit log enrichment
Extend AuditEntry (used today in mcp serve) with the following structured fields:
server: upstream alias the request targeted.
access_kind: read | write | * — the effective access the request was evaluated against.
matched_rule: stable identifier for the grant that produced the decision. Recommended shape: "<role>[<grant_index>]" (e.g., "dev[1]") for role grants, "<subject>.extra[<index>]" for per-subject extras, "default" when nothing matched, and "legacy[<index>]" when the legacy schema was used.
decision: allow | deny (explicit field, even if already encoded elsewhere, to make log queries trivial).
Existing audit fields must not change shape. New fields are additive.
When the legacy schema is in use, classification fields are still populated (the classifier runs regardless), and matched_rule uses the legacy[<index>] form.
JSON output of the audit log should include the new fields at the top level of each entry (not nested) so that jq/grep stays simple.
Documentation
A short section in the project docs (README or a dedicated docs/acl.md) showing:
How to use mcp acl check to validate a policy change before rolling it out.
The new audit fields and example queries (jq 'select(.decision=="deny" and .access_kind=="write")' kind of snippets).
Out of scope
New metrics / Prometheus counters for ACL decisions. Can be a follow-up.
Interactive REPL mode for mcp acl check. CLI-only is enough.
Re-designing the audit log format or storage backend — just adding fields.
mcp acl check belongs next to the existing ACL subcommands. It should reuse the config loader from mcp serve rather than reimplementing it, so behavior matches exactly.
Audit emission sites live in src/serve.rs around the tools/call and tools/list handlers. The decision object should be threaded through so the audit record can consume it without re-evaluating.
Unit tests: synthesize a few policies, run check programmatically (integration test against the CLI binary), assert decisions and exit codes. Audit tests can assert the extended JSON shape.
Success criteria
An operator can run mcp acl check --subject bob --server grafana --tool update_dashboard and get a clear allow/deny with the reason, without starting the proxy.
mcp acl check --subject alice --server github --all-tools enumerates every tool and shows decisions — useful for reviewing what a role actually has access to.
Audit log entries for denied requests contain enough information to answer "which rule denied this?" and "was the tool classified confidently?" without cross-referencing the config.
Running the whole test suite still passes with the extended audit schema.
See the full redesign plan at docs/acl-redesign-plan.md.
Once the new ACL (#55) is in place, operators need two things to run it in production with confidence:
AuditEntryrecords that something was allowed or denied but not why — which role, which grant, what the tool was classified as, how confident the classifier was.This issue delivers both, because they hit exactly the same integration point in the evaluator: the moment a decision is made, we already have everything the debug CLI and the audit enrichment need.
Depends on: #55.
Goal
Make ACL decisions inspectable from the command line without running the proxy, and make audit log entries carry enough structured context to answer operational questions ("who tried to write to prod?", "which tool was blocked because the classifier was unsure?") with a single
grep.Expected behavior
mcp acl checkCLIservers.jsonexactly the waymcp servedoes, including ACL config and tool classifications (from ACL: automatic tool read/write classifier with manual override #54).AuthIdentityfor the given subject using the roles that subject would have according tosubjects.<name>.rolesin the config. The subject does not need to have a real token — this is a pure policy check.(subject, server, tool, access)and prints:ALLOWorDENY.defaultif nothing matched). For denies caused by an explicitdeny: truegrant, say so clearly.override/annotation/classifier/fallback), and confidence.accessthe request was evaluated against (if--accesswas omitted, the CLI uses the tool's classification to pickreadorwriteautomatically and reports which it chose).--format jsonfor scripting,--role <name>to check a hypothetical role without needing a subject,--all-toolsto enumerate every tool on a server and show the decision for each (useful for auditing what a role can actually reach).0for allow, non-zero for deny. Enables scripting in CI or pre-deploy checks.Examples (illustrative output, exact format is an implementation choice):
Audit log enrichment
Extend
AuditEntry(used today inmcp serve) with the following structured fields:server: upstream alias the request targeted.access_kind:read|write|*— the effective access the request was evaluated against.matched_rule: stable identifier for the grant that produced the decision. Recommended shape:"<role>[<grant_index>]"(e.g.,"dev[1]") for role grants,"<subject>.extra[<index>]"for per-subject extras,"default"when nothing matched, and"legacy[<index>]"when the legacy schema was used.decision:allow|deny(explicit field, even if already encoded elsewhere, to make log queries trivial).classification_source:override|annotation|classifier|fallback.classification_confidence: float in 0.0–1.0.classification_kind:read|write|ambiguous.Constraints:
matched_ruleuses thelegacy[<index>]form.jq/grepstays simple.Documentation
docs/acl.md) showing:mcp acl checkto validate a policy change before rolling it out.jq 'select(.decision=="deny" and .access_kind=="write")'kind of snippets).Out of scope
mcp acl check. CLI-only is enough.Technical pointers
Decisionvalue with everything needed (matched grant, classification context). If it does not yet, extend it here — both the CLI and audit consumer need the same data.mcp acl checkbelongs next to the existing ACL subcommands. It should reuse the config loader frommcp serverather than reimplementing it, so behavior matches exactly.src/serve.rsaround thetools/callandtools/listhandlers. The decision object should be threaded through so the audit record can consume it without re-evaluating.checkprogrammatically (integration test against the CLI binary), assert decisions and exit codes. Audit tests can assert the extended JSON shape.Success criteria
mcp acl check --subject bob --server grafana --tool update_dashboardand get a clear allow/deny with the reason, without starting the proxy.mcp acl check --subject alice --server github --all-toolsenumerates every tool and shows decisions — useful for reviewing what a role actually has access to.See the full redesign plan at
docs/acl-redesign-plan.md.