feat(aauth): MCP tool-level grant authorization + enforcement proxy (#26)#42
Open
markmhendrickson wants to merge 1 commit into
Open
feat(aauth): MCP tool-level grant authorization + enforcement proxy (#26)#42markmhendrickson wants to merge 1 commit into
markmhendrickson wants to merge 1 commit into
Conversation
) Extend agent_grant to gate arbitrary MCP tool calls, not just Neotoma ops. grant_checker.py: - Fix _parse to match the live agent_grant schema: identity via match_sub (was incorrectly filtering on aauth_sub), capabilities as an object-array (was assuming strings). Normalises into `ops` (set) + `tool_grants` (map). - AgentGrant.tool_constraints(server, tool): resolve "<server>:<tool>" grant with server/global wildcard support. - GrantChecker.check_tool(server, tool): returns (allowed, constraints) with permissive fallback when no grant declares any tool capability (un-migrated agents keep working; migrated agents get hard enforcement). - check_param_constraints(constraints, params): enforces tables, max_amount_sats, to_allowlist, max_<field>, allowed_<field>; unknown keys ignored (forward-compatible). - 13 unit tests (test_grant_checker.py), all passing. mcp_tool_grant_proxy/proxy.py: - Generic stdio MCP interceptor between `claude --print` and a downstream MCP server. Forwards all JSON-RPC except tools/call, which it gates against the agent_grant. Denied calls get a synthesized isError result and never reach the downstream tool. Emits tool_call_observation (allowed|denied) for a unified cross-MCP audit trail. - README + 3 smoke tests (advisory passthrough, non-tool passthrough, deny path), all passing. docs/aauth.md: - Promote tool-level authz from "planned" to implemented; add the "Tool-level authorization (issue #26)" section (grant shape, enforcement, permissive-fallback semantics). Grant op format: "tool:<server>:<tool>" with optional param_constraints. Absent = denied; {} = unconstrained; tool:<server>:* and tool:* wildcards. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #26.
What
Extends
agent_grantto gate arbitrary MCP tool calls — across any server, not just Neotoma entity operations.grant_checker.py_parsenow matches the liveagent_grantschema — identity viamatch_sub(was filtering on a nonexistentaauth_subsnapshot field) andcapabilitiesas an object-array (was assuming strings). Without this,GrantCheckerwould never have matched a real grant.ops(set of op strings) +tool_grants(map of<server>:<tool>→ constraint dict).AgentGrant.tool_constraints(server, tool)withtool:<server>:*andtool:*wildcard support.GrantChecker.check_tool(server, tool)→(allowed, constraints), permissive fallback when no grant declares any tool capability (un-migrated agents keep working; migrated agents get hard enforcement).check_param_constraints(constraints, params)— enforcestables,max_amount_sats,to_allowlist,max_<field>,allowed_<field>; unknown keys ignored (forward-compatible).mcp_tool_grant_proxy/proxy.pyclaude --printand a downstream MCP server.tools/call, which it gates against the grant. Denied calls get a synthesizedisErrorresult and never reach the downstream tool.tool_call_observation(result: allowed | denied) to Neotoma for a unified cross-MCP audit trail.docs/aauth.mdGrant op format
{ "op": "tool:<server>:<tool>", "param_constraints": { "max_amount_sats": 500000 } }Absent = denied.
{}= unconstrained.tool:<server>:*/tool:*wildcards.Verification
lib/daemon_runtime/test_grant_checker.py— 13/13 passing (parsing, tool lookup, wildcards, all constraint types, suspended-grant deny).execution/mcp/mcp_tool_grant_proxy/test_proxy_smoke.py— 3/3 passing (advisory passthrough, non-tool passthrough, in-process deny path incl. constraint violation + deny-response shape).ast.parse. HEAD commit GPG-signed.Follow-ups (from the issue's task list, not in this PR)
tool_call_observationschema in Neotoma (proxy writes it best-effort today; schema registration formalizes it).ATELES_AGENT_SUB/ATELES_AGENT_GRANT_IDthrough dispatch subprocess env.parquetMCP server with option-A enforcement for defence-in-depth.capabilities.toolsfromallowed_toolsfor the 12 product-panel agents.🤖 Generated with Claude Code