[SECURITY] Claude Teams: Custom MCP connectors expose shared credentials to all team members — no per-user scoping

Status Closed — not planned
Maintainer reply None cached
Activity 9 comments · opened Apr 8, 2026 · closed Jun 15, 2026

What is the expected behavior?

When an admin adds a custom MCP connector on Claude Teams, they should be able to assign it to specific team members. Only assigned members should see and be able to connect to that connector. Members not assigned should not see the connector URL or be able to authenticate with it.

Steps to reproduce

  1. On a Claude Teams plan, go to Organization Settings → Connectors
  2. Add a custom MCP connector with a URL (e.g., a Cloudflare Worker at https://my-worker.example.com/mcp)
  3. Log in as a different team member
  4. Go to Customize → Connectors
  5. Observe: the custom connector is visible with its full URL
  6. Click Connect — authentication completes via auto-approve OAuth with no identity verification
  7. The team member now has full access to whatever the connector provides

Expected: Only assigned team members can see and connect to the connector.
Actual: Every team member can see the URL and connect with zero identity verification.

Why this is a security issue

The connector URL IS the credential. Auto-approve OAuth means anyone who clicks Connect gets a valid bearer token. There is no per-user scoping, no identity binding, and no way for the MCP server to distinguish which team member is connecting.

Real-world impact: We built a custom Gmail MCP connector (Cloudflare Worker with Google Service Account delegation) for multi-account email access. We cannot deploy it for personal inboxes because any team member could connect and access another person's email. The same risk applies to CRM, financial, and task management connectors.

What we tried that doesn't work:

  • Cloudflare Access (SSO gate): Claude's MCP client cannot complete browser-based SSO flows or send custom auth headers like CF-Access-Client-Id
  • URL-embedded secrets: The full URL (including any secrets) is visible in Teams connector settings to all members
  • Per-subdomain routing: Tokens from auto-approve OAuth are bearer credentials — possession = access

Requested features (any would help)

  1. Per-user connector scoping — admins assign specific connectors to specific team members
  2. User-provided secret during Connect flow — a prompt for a key/passphrase when clicking Connect, validated by the MCP server
  3. Custom header support — allow the MCP client to send configurable headers (e.g., for external auth layers like Cloudflare Access)
  4. Per-user connector visibility — team members only see connectors assigned to them
  5. Identity passthrough — pass the authenticated Teams user's identity (email) to the MCP server so it can enforce per-user access control server-side

MCP Server

Custom Cloudflare Worker implementing MCP protocol (JSON-RPC 2.0, protocol version 2025-03-26) with OAuth 2.1 + PKCE (S256) auto-approve flow. Architecture documented and reviewed through 4 rounds of security review before discovering this Teams-level limitation.

Environment

  • Claude Teams plan
  • Custom MCP connector via Cloudflare Workers (self-hosted)
  • macOS, Claude Desktop + Claude Code
  • MCP protocol version 2025-03-26

View original on GitHub ↗

9 Comments

bman5410-ui · 4 months ago

I just ran into this also. similar setup. I am using context forge gateway with an oauth bridge. i have permissions set for each auth credential but when the admin sets up the mcp connection in teams, all team members get the access he used. Not sure how to get around it. On the single person plans it worked well... everyone just used their assigned credentials.

GBR-Rise · 4 months ago

We hit the exact same issue with an internal enterprise tool

Our MCP server uses user-scoped auth — each user authenticates with their own
credentials, and the API enforces row-level data visibility based on their
permissions. That's the correct security model for a multi-tenant internal tool.

With the current org connector model on claude.ai / claude cide, this is completely broken:
whoever re-authenticates last sets the shared token for the entire org. If a
user with limited access re-auths, they silently break data scoping for everyone.

We've filed a related feature request at #46207 focusing on the per-user auth
angle. The two issues are the same underlying gap — the connector model was
designed for shared service accounts, not delegated user identity.

stevenlafl · 4 months ago

So, adding a non org-scoped MCP is still visible for me on Claude.ai. I would almost -expect- this behavior at the org-level, so we can, like with GitHub auth flows for external CI/CD, set it up once and allow all tools to be used inside the org.

So, this seems to be poorly thought out and executed, and the gaps in documentation and features are significant. If added at the org level, we should be able to at LEAST control which users get access to it within Anthropic because of precisely what you said.

ajhpw · 3 months ago

We created a custom MCP connector to our Salesforce instance. Each user has to connect their own Claude account via the Customize > Connectors screen and then log in and authorize their Salesforce account. This seems to be scoped per-user, which we can verify by asking Claude the details of current user from Salesforce. It always responds with information from Salesforce about the correct user they authenticated to Salesforce with.

Am I misunderstanding something here or is what is asked for in this request already possible?

github-actions[bot] · 2 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

REFoqus · 2 months ago

@ajhpw the problem is with tools that just have one API token without oauth. Admin wants to add this and assign to specific teams/members only.

jacrawford2000 · 2 months ago

Sharing the workaround we've run in production for ~6 months, since native per-user scoping still isn't here. It solves the access-control half of this completely, and — for OAuth-capable upstreams — the per-user-identity half too.

Pattern: self-host each connector as a small serverless worker that puts your own identity gate in front of the upstream API. The worker speaks MCP OAuth to the client but delegates login to an IdP (we use Google / Workspace SSO):

  1. Client connects → worker runs standard MCP OAuth (PKCE + dynamic client registration).
  2. Instead of trusting the connection, the worker bounces the user to Google to authenticate.
  3. On callback, verify the identity, check the email against an allowlist, and only then mint an MCP token bound to that verified email.
  4. Re-check the allowlist on every request, not just at mint — otherwise a removed teammate keeps access until token expiry.

That alone kills the "admin adds one connector and the whole org inherits one credential" problem: the upstream API key lives only in the worker's secret store and is never handed to clients; access to the worker is gated per-identity and revoked instantly by editing the allowlist.

For the single-API-token tools @REFoqus mentioned (no upstream OAuth): this is the fix. The shared token stays server-side; you gate who can reach the worker per-user. You get "assign to specific members" without the upstream supporting OAuth at all.

For upstreams that DO support OAuth and you need true per-user identity (row-level scoping, "act as me"): have the worker store a per-user refresh token keyed by verified email (encrypt them — envelope/AES-GCM), and exchange per-user at call time. Each user then acts as themselves against the upstream, and whoever-authed-last can't clobber a shared token — the exact failure @GBR-Rise described.

Stack we use: Cloudflare Workers + KV for token storage, Google as the IdP, an ALLOWED_EMAILS secret as the allowlist — roughly one worker per upstream. Happy to share a skeleton if it's useful.

None of this should be necessary — native per-user connector scoping (and per-user secret entry during the Connect flow) is still the right ask, and this still feels like it merits staying open rather than auto-closed as "not planned." But if you're blocked today, this works.

dan-sotnik · 2 months ago

Want to raise a failure mode with the SSO + allowlist approach - our team hit this not long ago.
Users can connect the MCP to a personal account and re-use it via org identity.

So verifying SSO + an email allowlist gates who the user is, but it doesn't bind which account the connection runs in.

Steps to reproduce:

  1. In the same browser, sign into a personal Claude account (e.g. you@gmail.com).
  2. Make sure you also have an active Google Workspace session for you@yourorg.com open in that browser (normal if you're signed into Gmail/Workspace for work).
  3. Add the org's MCP connector to the personal account.
  4. The client runs OAuth -> your server redirects to Google.
  5. The existing Workspace session silently satisfies it - no login page, no account picker.
  6. Server verifies the returned token: email_verified, hd, allowlist all pass, because it genuinely is a real org user.
  7. Token is minted. The personal account is now talking to the MCP as you@yourorg.com.

We can't cover it only on MCP app Server Side. We need gate right during the Connection to MCP.

github-actions[bot] · 16 hours ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.