Skip to content

feat(mcp): per-tool ACL — govern MCP tools by the caller's API key - #668

Merged
moonming merged 2 commits into
mainfrom
feat/mcp-per-tool-acl
Jun 29, 2026
Merged

feat(mcp): per-tool ACL — govern MCP tools by the caller's API key#668
moonming merged 2 commits into
mainfrom
feat/mcp-per-tool-acl

Conversation

@moonming

Copy link
Copy Markdown
Member

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 /mcp endpoint (#667) and the snapshot-sourced gateway (#665).

How

  • aisix-coreApiKey.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 (consistent with allowed_models).
  • aisix-mcpToolAcl (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 (per request; the handler already holds the resolved key, so no rmcp request-context plumbing is needed).
  • aisix-admin — the self-hosted apikeys API (StandaloneApiKeyBody + PublicApiKey) round-trips allowed_tools; api_key.schema.json regenerated (so the loader + admin validation accept it).

Test plan

  • can_access_tool semantics (none/empty → deny, ["*"] → all, exact names).
  • ToolAcl::from_allowed mapping.
  • end-to-end (real upstreams): a key permitting only alpha__echobeta__echo is hidden from tools/list and its call is rejected; alpha__echo lists and calls. (tool_acl_filters_list_and_rejects_calls)
  • apikeys etcd round-trip carries allowed_tools.
  • fmt, clippy --workspace --all-targets -D warnings, cargo test (core/mcp/proxy/admin) green.

Scope notes

Refs AISIX-Cloud#894

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
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@moonming, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 99b27339-88af-4687-9e4d-704b32b26797

📥 Commits

Reviewing files that changed from the base of the PR and between 0eba11c and 4b08da6.

📒 Files selected for processing (8)
  • crates/aisix-admin/src/apikeys_handlers.rs
  • crates/aisix-admin/tests/etcd_integration.rs
  • crates/aisix-core/src/models/apikey.rs
  • crates/aisix-mcp/src/gateway.rs
  • crates/aisix-mcp/src/lib.rs
  • crates/aisix-mcp/tests/gateway_aggregation.rs
  • crates/aisix-proxy/src/mcp.rs
  • schemas/resources/api_key.schema.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mcp-per-tool-acl

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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
@moonming

Copy link
Copy Markdown
Member Author

Independent audit (CLAUDE.md §8): APPROVE

Auditor ran clippy/test/build/dump-schema in an isolated worktree — all green, zero schema drift. Verified: deny-by-default holds; ACL filters tools/list on namespaced names and gates tools/call before routing to any upstream (no upstream contact for a disallowed call); neutral rejection (no existence leak); no bypass (__-in-name, *-literal vs AllowAll, case/trailing — all deny, never looser); no regression to LLM auth/routing; admin round-trips allowed_tools.

Five LOW findings, none blocking. Two folded in (commit pushed):

  • Neutral-message contract was untested → e2e now asserts the rejection says "not available" and does not contain "permitted"/"forbidden"/"exists".
  • McpGateway::new AllowAll default could fail-open for a future caller → documented that new/from_snapshot are unrestricted until with_tool_acl, and that the proxy mount is the single enforcement point (always scopes).

Acknowledged deferrals (LOW): OpenAPI doc for allowed_tools → folds into #663; a proxy-level ACL e2e (logic is e2e-tested at the aisix-mcp layer; proxy is one-line wiring); asserting the stored allowed_tools value (rides the same serde path as allowed_models, acceptance is tested).

Merging on CI green.

@moonming
moonming merged commit d6adc8a into main Jun 29, 2026
12 checks passed
@moonming
moonming deleted the feat/mcp-per-tool-acl branch June 29, 2026 09:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant