auth: refresh_token() always sends `scope`, so refresh fails against Salesforce ("scope parameter not supported")
Mantenedores costumam responder em até 3 dias
@gocamille já está trabalhando nisso.
Desde 29/10/2026.
Avaliação
Esta issue ainda não foi avaliada.
Descrição
Summary
AuthorizationManager::refresh_token() always adds the stored granted_scopes (plus offline_access when advertised) to the refresh request as scope. Salesforce's token endpoint rejects any refresh request that includes scope:
400 {"error":"invalid_request","error_description":"scope parameter not supported"}
So every refresh fails against a Salesforce-hosted MCP server, and the user has to go through the full authorization flow again each time the access token expires. Clients built on rmcp, including Codex, can't do anything about it, because there's no way to leave scope out of the refresh request.
Where it happens
crates/rmcp/src/transport/auth.rs on main (9e0783d):
let mut refresh_scopes = stored_credentials.granted_scopes.clone();
self.add_offline_access_if_supported(&mut refresh_scopes);
let requested_scopes = refresh_scopes.clone();
for scope in refresh_scopes {
refresh_request = refresh_request.add_scope(Scope::new(scope));
}
Clearing the stored granted_scopes doesn't help either. Salesforce returns scope in every token response ("scope": "refresh_token mcp_api"), and resolve_granted_scopes stores it again, so the next refresh sends scope once more.
Reproduction
Server: a Salesforce Hosted MCP server (https://api.salesforce.com/platform/mcp/v1/...) with a pre-registered public client (PKCE, no secret). The External Client App has "Require secret for Refresh Token Flow" turned off.
Direct calls to the org's /services/oauth2/token with the same refresh token:
| Request | Result |
|---|---|
grant_type=refresh_token&client_id=…&refresh_token=…&scope=mcp_api refresh_token |
400 invalid_request: scope parameter not supported |
grant_type=refresh_token&client_id=…&refresh_token=… |
200, new access token; response includes "scope":"refresh_token mcp_api" |
The same failure through Codex CLI 0.161.0 (rmcp 3.5.1, rev 98a5478), with RUST_LOG=codex_rmcp_client=debug,rmcp=debug:
refresh_transaction{... refresh_reason="expiry"}: rmcp::transport::auth: refresh token present, attempting refresh
WARN ... MCP OAuth provider refresh failed error=OAuth token refresh failed: Server returned error response: invalid_request: scope parameter not supported
WARN ... MCP OAuth access token is expired and refresh failed; reauthorization required
For comparison, the MCP TypeScript SDK's refreshAuthorization sends no scope, only resource, and refreshes silently against the same Salesforce server and client. That also shows Salesforce accepts the RFC 8707 resource parameter that rmcp now adds to refresh requests. Only scope is the problem.
Why this should be configurable
- RFC 6749 §6 makes
scopeOPTIONAL on refresh. If it's omitted, the server treats the request as asking for the originally granted scope. - Salesforce's OAuth 2.0 Refresh Token Flow documents
grant_type,refresh_token,client_id,client_secret,client_assertion,client_assertion_typeandformatas the request parameters.scopeisn't one of them. - Some providers do need
scopeon refresh. Microsoft Entra is one (see mcp-remote#357). So "never sendscope" isn't a safe default.
Proposed fix
Either of these would work. They aren't mutually exclusive:
- Retry without
scope: if the refresh fails withinvalid_request(orinvalid_scope) andscopewas sent, retry once without it. This works without any client changes and doesn't change behavior for providers that acceptscope. - Opt-out: a setting on
AuthorizationManager/OAuthState, for exampleomit_scope_on_refresh: bool, that clients can expose in their config.
ToolHive hit the same Salesforce error and fixed it with an opt-out: stacklok/toolhive#6830 (PR #6832).
Related
- #1330 / #1331: #1330 is about refresh requesting
offline_accessthat was never granted. PR #1331 refreshes with the granted scopes, so it still sendsscopeand won't fix Salesforce. A fix for this issue should cover the case where the server returnsscopein the token response but won't accept it on refresh. - openai/codex#58103: the downstream Codex issue.
- Linguagem predominante
- Rust
- Estrelas
- 4.1k
- Forks
- 694
- Merge médio
- 4d 16h
- PRs com merge (30d)
- 43
Preparar o ambiente
Inicia o contêiner de desenvolvimento do projeto no navegador, com a sua própria conta do GitHub.
- Sem Dockerfile nem arquivo Docker Compose
- Sem modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de modelcontextprotocol/rust-sdk
-
Return MergedList<T> from list_all_* and drop the _with_cache_hint variantsTalvez já em andamento @DaleSeo assumiu há 1 dia. Abertaenhancement P3 ready for work T-service v4
modelcontextprotocol/rust-sdk#1430 · 1 responsável ·
Mantenedores costumam responder em até 3 dias
-
Server logs a fabricated 'initialize' (with synthesized rmcp/3.8.0 peer identity) for no-initialize SEP-2567 requests, ignoring clientInfo from _metaTalvez já em andamento @jamadeo assumiu há 2 dias. Abertabug P2 ready for work T-service T-transport
modelcontextprotocol/rust-sdk#1425 · 2 comentários · 1 responsável ·
Mantenedores costumam responder em até 3 dias
-
OAuth discovery drops explicit default ports from advertised issuer identifiersTalvez já em andamento @gocamille assumiu há 4 dias. Abertabug P1 ready for work T-security T-transport
modelcontextprotocol/rust-sdk#1420 · 2 comentários · 1 responsável ·
Mantenedores costumam responder em até 3 dias
-
`register_client` drops `client_secret_expires_at` from the registration responseTalvez já em andamento @gocamille assumiu há 5 dias. Abertabug P2 ready for work T-security
modelcontextprotocol/rust-sdk#1416 · 1 comentário · 1 responsável ·
Mantenedores costumam responder em até 3 dias
-
Typed client calls drop the parse error when a reply misses a required fieldTalvez já em andamento @gocamille assumiu há 5 dias. Abertaenhancement P2 ready for work T-core T-service
modelcontextprotocol/rust-sdk#1415 · 1 comentário · 1 responsável ·
Mantenedores costumam responder em até 3 dias
Todas as issues de modelcontextprotocol/rust-sdk
Issues semelhantes
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
gitbutlerapp/gitbutler#16982 ·
Mantenedores costumam responder em até 1 dia
-
priority: high status: new topic: ga4gh-sdk type: docs type: maintenance workload: minutes
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
-
C-bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
rust-lang/rust-clippy#17976 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 88/100
aaif-goose/goose#13327 ·
Mantenedores costumam responder em até 1 dia
-
llm translation
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 1 dia