feat(mcp): per-tool ACL — govern MCP tools by the caller's API key - #668
Conversation
Scopes the MCP gateway to the tools the caller's AISIX API key permits, so MCP tool access is governed by the same key object as LLM access. - aisix-core: `ApiKey.allowed_tools: Option<Vec<String>>` (namespaced `<server>__<tool>` names, `"*"` = all) + `can_access_tool`, mirroring `allowed_models`/`can_access`. Deny-by-default: a key with no `allowed_tools` may call no MCP tools — access is granted explicitly. - aisix-mcp: `ToolAcl` (AllowAll / Allow(set), built from a key's allowed_tools) + `McpGateway::with_tool_acl`. `tools/list` exposes only permitted tools; `tools/call` rejects the rest with a neutral message (defense-in-depth; doesn't reveal whether the tool exists upstream). - aisix-proxy: the `/mcp` handler builds the gateway scoped to `auth.key().allowed_tools`. - aisix-admin: the self-hosted apikeys API (`StandaloneApiKeyBody` + `PublicApiKey`) round-trips `allowed_tools`; api_key schema regenerated. Tested: can_access_tool semantics; ToolAcl mapping; an end-to-end run where the key permits only one upstream's tool — the other is hidden from tools/list and its call is rejected, the permitted one lists and calls. Refs AISIX-Cloud#894
|
Warning Review limit reached
Next review available in: 27 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Audit (CLAUDE.md §8) on #668 returned APPROVE with LOW suggestions; folded in the two cheap ones: - The e2e now asserts the non-permitted tools/call rejection carries the neutral "not available" message and does NOT leak existence/permission detail ("permitted"/"forbidden"/"exists") — the contract was untested. - Documented that McpGateway::new is unrestricted (AllowAll) until scoped via with_tool_acl, and that the proxy /mcp mount is the single enforcement point — guarding against a future caller fail-open. Refs AISIX-Cloud#894
Independent audit (CLAUDE.md §8): APPROVEAuditor ran clippy/test/build/dump-schema in an isolated worktree — all green, zero schema drift. Verified: deny-by-default holds; ACL filters Five LOW findings, none blocking. Two folded in (
Acknowledged deferrals (LOW): OpenAPI doc for Merging on CI green. |
What
DP-4 slice 3. Scopes the MCP gateway to the tools the caller's AISIX API key permits — so MCP tool access is governed by the same key object as LLM access (the "single-pipeline same-governance" differentiation). Builds on the mounted, authenticated
/mcpendpoint (#667) and the snapshot-sourced gateway (#665).How
ApiKey.allowed_tools: Option<Vec<String>>(namespaced<server>__<tool>names;"*"= all) +can_access_tool, mirroringallowed_models/can_access. Deny-by-default: a key with noallowed_toolsmay call no MCP tools — access is granted explicitly (consistent withallowed_models).ToolAcl(AllowAll/Allow(set), built from a key'sallowed_tools) +McpGateway::with_tool_acl.tools/listexposes only permitted tools;tools/callrejects the rest with a neutral message (defense-in-depth — doesn't reveal whether the tool exists upstream)./mcphandler builds the gateway scoped toauth.key().allowed_tools(per request; the handler already holds the resolved key, so no rmcp request-context plumbing is needed).StandaloneApiKeyBody+PublicApiKey) round-tripsallowed_tools;api_key.schema.jsonregenerated (so the loader + admin validation accept it).Test plan
can_access_toolsemantics (none/empty → deny,["*"]→ all, exact names).ToolAcl::from_allowedmapping.alpha__echo→beta__echois hidden fromtools/listand its call is rejected;alpha__echolists and calls. (tool_acl_filters_list_and_rejects_calls)allowed_tools.fmt,clippy --workspace --all-targets -D warnings,cargo test(core/mcp/proxy/admin) green.Scope notes
aisix-mcpand is e2e-tested there (the real-upstream harness); the proxy is one-line wiring (ToolAcl::from_allowed(key.allowed_tools)), covered by compile + the existing/mcpauth tests.allowed_toolsrequest/response field is not added (consistent with how the resource OpenAPI was handled in Document /admin/v1/mcp_servers in the Admin API OpenAPI #663/feat(mcp): McpServer admin resource (registration + CRUD) #664; the field IS accepted by the admin API via the regenerated schema). Can fold into the Document /admin/v1/mcp_servers in the Admin API OpenAPI #663 OpenAPI follow-up.Refs AISIX-Cloud#894