Skip to content

ACL: mcp acl check CLI and enriched audit log entries #56

@avelino

Description

@avelino

Once the new ACL (#55) is in place, operators need two things to run it in production with confidence:

  1. 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.
  2. 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.

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 check CLI

mcp acl check --subject <name> --server <alias> --tool <name> [--access read|write]
  • Loads the current servers.json exactly the way mcp serve does, including ACL config and tool classifications (from ACL: automatic tool read/write classifier with manual override #54).
  • 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).
  • classification_source: override | annotation | classifier | fallback.
  • classification_confidence: float in 0.0–1.0.
  • classification_kind: read | write | ambiguous.

Constraints:

  • 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.

Technical pointers

  • The evaluator from ACL: new role-based schema, union evaluation, and read/write enforcement #55 should already return a structured Decision value 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 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    aclAccess control lists and authorizationcliCLI commands, flags, and UXenhancementNew feature or improvementobservabilityAudit logs, monitoring, debuggingsecuritySecurity, authorization, and hardening

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions