forcedotcom / forcedotcom/mcp-hosted
Experience Cloud (portal) users cannot use hosted MCP servers — gateway returns 404 "Session not found" for site-issued tokens whose `aud` includes `api.salesforce.com`
- Dominant language
- No language data
- Stars
- 135
- Forks
- 20
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
Tokens with the `mcp_api` scope issued to an **Experience Cloud (partner community) user** through the site's OAuth endpoints are rejected by the hosted MCP gateway with `404 {"error":{"code":404,"message":"Session not found"}}` — while the identical flow for an internal user (token minted at My Domain) works end to end. Notably, the site-issued JWT **already carries `https://api.salesforce.com` in its `aud` claim**, which suggests portal-user support is intended by the token service but not implemented by the gateway.
### Setup
- Partner Enterprise trial org (Summer '26, API v67.0), Digital Experiences enabled, Partner Central site published and active
- External Client App: authorization code + PKCE, "Issue JWT-based access tokens for named users" enabled, scopes `mcp_api`, `refresh_token`
- Custom MCP server (`McpServerDefinition`) exposing one Apex invocable action, activated via Tooling `McpServerAccess`
- Portal user: Partner Community license, member of the site, API Enabled, Apex class access granted
### Repro
1. As the **portal user**, complete authorization code + PKCE against the site's endpoints: `https://.my.site.com//services/oauth2/authorize` → consent for "Access Salesforce hosted MCP servers" is shown and approved → exchange at `.../services/oauth2/token` succeeds. The resulting JWT:
- `iss`: `https://.my.site.com/`
- `aud`: `["https://.my.site.com/", "https://api.salesforce.com"]`
- `sub`: `b2c:`
- `scp`: `refresh_token mcp_api`
2. POST `initialize` to `https://api.salesforce.com/platform/mcp/v1/custom/` with that bearer token.
3. **Actual**: `404 {"error":{"code":404,"message":"Session not found"}}` (reproducible). **Expected**: the MCP handshake, as happens for internal users.
Control: the same request with a My Domain-issued token (`iss: https://.my.salesforce.com`, `sub: uid:...`) for an internal user returns 200 and `tools/call` executes the action correctly.
Also tried: `https://api.salesforce.com/platform/mcp/v1/d//custom/` → `404 "Server definition not found"` (the `d/{mydomain}` form doesn't appear to support custom servers).
### Why this matters
Partner-facing use cases (PRM / Partner Cloud) are a natural fit for hosted MCP servers: a channel partner registering deals or checking pipeline/MDF from their own MCP client, with the org's sharing model scoping everything automatically. Everything works today up to (and including) minting the `mcp_api` token for the portal user — only the gateway session resolution blocks it. Additionally, the gateway's RFC 9728 discovery metadata advertises `login.salesforce.com` as the only authorization server, which portal users cannot use either — so real MCP clients (e.g. Claude) can't even reach the site-token scenario without manual flows.
### Questions
1. Is portal/Experience Cloud user support on the roadmap for hosted MCP servers?
2. Is the `aud` containing `api.salesforce.com` on site-issued tokens intentional (i.e., is the gateway expected to accept them eventually)?
3. Any recommended interim pattern besides a custom remote MCP server proxying the REST Actions API with site-issued tokens?
Contributor guide
Research direction
Start by reproducing the hosted MCP initialize request with the site-issued portal-user JWT, then compare its issuer, audience, subject, and scopes with the working My Domain token. Trace how the gateway resolves sessions for each token; done means a portal-user token completes the MCP handshake and supports the described hosted-server flow.
Written by the indexing model from the issue text.
Assessment
- Domain
- api, authentication
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100