Skip to content

fix(httpapi): hold profile-pinned agent tokens to their pin on GET /api/v1/servers - #1684

Merged
Dumbris merged 1 commit into
smart-mcp-proxy:mainfrom
james-crowley:fix/profile-token-rest-server-scope
Oct 20, 2026
Merged

Dumbris merged 1 commit into
smart-mcp-proxy:mainfrom
james-crowley:fix/profile-token-rest-server-scope

Conversation

@james-crowley

Copy link
Copy Markdown
Contributor

Description

Fixes #1682.

A token created with --profile takes its scope from the profile, so its AllowedServers is usually "*". The #1166 gate (visibleServers -> CanEnumerateServer) only consults AllowedServers, so a bare GET /api/v1/servers listed every configured server to a profile-pinned token, while /tools, preflight and the MCP surface were already held to the pin.

This applies the caller's own pin to a bare GET /servers (no ?profile=): it is now equivalent to GET /servers?profile=<the caller's own pin>. Rows outside the pin disappear, tool_count counts only tools the pin admits, and stats narrow with the list (no count oracle). Admins, the tray socket and legacy --servers tokens are unchanged, since callerHasProfilePin is false for them.

The change is limitServersToCallerPin in view_as.go (reusing limitServersToProfile with a pass-through evaluator) and one call in each of the two branches of handleGetServers (management service and legacy fallback).

Scope note: I only changed /api/v1/servers. Other REST reads gate on canSeeServer (AllowedServers), so a pinned token may still see out-of-pin servers there (for example per-server subtree routes). I left those alone because the scope comments in scope_subtree.go describe the pin as ergonomics rather than the boundary, and that looks like a maintainer decision. Happy to follow up if you want the pin applied to canSeeServer itself.

Testing

  • I have tested these changes locally
  • I have added/updated tests that prove my fix is effective or my feature works
  • All existing tests pass

Verification:

  • go test ./internal/httpapi/ ./internal/auth/ passes.
  • New TestServersBareListHeldToCallerPin runs both handleGetServers branches and fails on main ({"filesystem":1,"github":2,"notion":2} instead of {"github":1}).
  • End to end with a built binary (2 stdio servers a, b; profile prof = a): admin lists ["a","b"], the --profile prof token lists ["a"], and a legacy --servers a token lists ["a"]. On v0.71.1 the profile token listed both.

A token created with --profile takes its scope from the profile, so its AllowedServers is usually "*" and the smart-mcp-proxy#1166 filter (visibleServers) listed every configured server to it, while /tools, preflight and the MCP surface were already limited to the pin.

Apply the caller's own pin to a bare GET /servers, the same way GET /servers?profile=<own pin> does: rows outside the pin disappear, tool_count counts only tools the pin admits, and stats narrow with the list. Admins and legacy-scoped tokens are unchanged.

Fixes smart-mcp-proxy#1682
@Dumbris

Dumbris commented Oct 20, 2026

Copy link
Copy Markdown
Member

Thanks a lot, @james-crowley! This is a clean, well-scoped fix. Reusing limitServersToProfile means a bare GET /servers now matches ?profile=<own pin> and /tools row for row, including the stats. The test covers both handleGetServers branches and fails on main exactly as you described.

Your scope note was right, too. Other read paths (/status, /events, /review, /attention, /profiles, /annotations/coverage) still gate only on AllowedServers, and the doc comment in scope_subtree.go treats the pin as ergonomics. Before anyone patches more endpoints, we need to decide whether the pin is a real visibility boundary, so I opened #1686 to track that decision and the shared-predicate fix. If you want to pick it up, you're very welcome. Merging this now 🙏

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Dumbris
Dumbris merged commit d25e92b into smart-mcp-proxy:main Oct 20, 2026
42 of 46 checks passed
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.

[Bug]: GET /api/v1/servers lists every server to a profile-pinned agent token (#1166 filter only covers legacy allowed_servers)

3 participants