Skip to content

Shared MCP servers cannot authenticate users individually (per-user credentials) #4869

Description

@ajayr

Problem

Remote MCP services (mail, calendars, issue trackers, docs platforms) authenticate people, not deployments. DeerFlow's MCP configuration is instance-global: one server list in extensions_config.json, with one set of static headers per server.

On a multi-user DeerFlow instance this forces a bad choice:

  1. Share one credential across all users — everyone acts on the remote service as the same identity, and every user can read/act on data belonging to whoever owns the token.
  2. Register N near-identical server entries (one per user, each with that user's token) — tool-list bloat, and still no isolation: any user can invoke any entry's tools, i.e. any user can reach any other user's account on the remote service.

The existing per-(user, thread) MCP session-pool scoping isolates state between users, but every user still connects with the same credentials.

Proposal

Let a single HTTP/SSE server entry carry a per-user credential map, resolved per tool call from the authenticated runtime user, following the same per-call header-override mechanism the built-in OAuth interceptor already uses:

{
  "mcpServers": {
    "shared-service": {
      "type": "http",
      "url": "https://api.example.com/mcp",
      "headers": { "Authorization": "$SERVICE_DISCOVERY_TOKEN" },
      "user_auth": {
        "header": "Authorization",
        "users": {
          "alice-user-id": "$SERVICE_TOKEN_ALICE",
          "bob-user-id": "$SERVICE_TOKEN_BOB"
        }
      }
    }
  }
}

Key behaviors:

  • Fail-closed by default: an unmapped user (or a credential env var that is unset) gets a clear error, never another user's credential and never the discovery token; per-server on_missing: "passthrough" opt-out for servers where the static headers are an acceptable fallback.
  • Static headers remain for startup tool discovery only.
  • Gateway API masks credential values in GET responses and preserves stored values on masked PUT round-trips, matching the existing env/headers/OAuth secret contract.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageAwaiting maintainer triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions