[v2] Incompatibility with refresh_tokens from Azure when client_id=resource_id
Los mantenedores suelen responder en 1 día
@claude ya está trabajando en esto.
Desde el 26/8/2026.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 72/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- typescript
- Área
- authentication
Línea de trabajo
Comienza con la llamada a refreshAuthorization en el ejemplo repro.mjs proporcionado y sigue cómo se construye su solicitud de refresh. Ejecuta la reproducción contra Azure, compara los scopes de la solicitud inicial y de la solicitud de refresh, y verifica que el token de acceso actualizado funciona correctamente cuando client_id y resource_id coinciden.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
What happened?
I have some MCP servers which require access_tokens from login.microsoftonline.com, and for which the OAuth client ID and the OAuth resource ID are the same Azure app.
All AI agent harnesses can successfully authenticate to these MCP servers initially, but several fail to fetch new access_tokens when they expire using the refresh_token.
This is because some MCP client libraries (including this one) don't handle the scope parameter when using a refresh_token in the way that Azure requires. This leads to the following misleading error message:
$ node repro.mjs "${my_tenantid}" "${my_clientid}" "api://${my_clientid}/access_as_user"
...
InvalidRequestError: AADSTS90009: Application 'REDACTED'(REDACTED) is requesting a token for it
self. This scenario is supported only if resource is specified using the GUID based App Identifier. Trace ID: REDACTED Correlation ID: REDACTED Timestamp: 2026-08-26 01:06:39Z
One way to fix this error actually has nothing to do with whether an app is requesting a token for itself or not - it is to pass along the same scope parameter with the refresh request as was used in the original request.
How's the support for other harnesses?
opencodeandpidepend on this library, so they don't workcodexworks well- The rust version of this SDK, upon which codex depends, does add scopes to the refresh requests: https://github.com/modelcontextprotocol/rust-sdk/blob/rmcp-v3.1.2/crates/rmcp/src/transport/auth.rs#L2184
claudedoesn't work- they made some related changes just last week but it still doesn't fix the refresh flow - https://github.com/anthropics/claude-code/issues/4540#issuecomment-5346014700
- However, they do have a workaround by way of
headersHelper- https://code.claude.com/docs/en/mcp#use-dynamic-headers-for-custom-authentication
Other related issues:
What did you expect?
No response
Code to reproduce
import { randomUUID } from "node:crypto";
import http from "node:http";
import { exchangeAuthorization, refreshAuthorization, startAuthorization } from "@modelcontextprotocol/client";
const [tenant, clientId, apiScope] = process.argv.slice(2);
if (!tenant || !clientId || !apiScope) {
console.error('Usage: node repro.mjs TENANT_ID CLIENT_ID "API_SCOPE"');
process.exit(2);
}
const root = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0`;
const redirectUri = "http://localhost:53682/callback";
const scope = `openid profile offline_access ${apiScope}`;
const clientInformation = { client_id: clientId };
const metadata = {
authorization_endpoint: `${root}/authorize`,
token_endpoint: `${root}/token`,
response_types_supported: ["code"],
code_challenge_methods_supported: ["S256"],
};
const state = randomUUID();
const { authorizationUrl, codeVerifier } = await startAuthorization(root, {
metadata, clientInformation, redirectUrl: redirectUri, scope, state,
});
const authorizationCode = await new Promise((resolve, reject) => {
const server = http.createServer((request, response) => {
const url = new URL(request.url, redirectUri);
if (url.pathname !== "/callback") return response.writeHead(404).end();
const code = url.searchParams.get("code");
const error = url.searchParams.get("error");
const valid = url.searchParams.get("state") === state;
response.end(valid && code ? "Authorization complete." : "Authorization failed.");
server.close();
if (error) reject(new Error(error));
else if (!valid || !code) reject(new Error("Invalid OAuth callback"));
else resolve(code);
});
server.once("error", reject);
server.listen(53682, () => console.log(`Open this URL:\n\n${authorizationUrl}\n`));
});
const trace = (label) => async (url, init) => {
const body = new URLSearchParams(init.body.toString());
console.log(`${label}:`, { grant_type: body.get("grant_type"), scope: body.get("scope") ?? "<redacted>" });
return fetch(url, init);
};
const initial = await exchangeAuthorization(root, {
metadata, clientInformation, authorizationCode, codeVerifier, redirectUri,
addClientAuthentication: async (_headers, body) => {
body.set("client_id", clientId);
body.set("scope", scope);
},
fetchFn: trace("initial request"),
});
if (!initial.refresh_token) throw new Error("No refresh token returned");
console.log("Initial access and refresh tokens received.");
const refreshed = await refreshAuthorization(root, {
metadata, clientInformation, refreshToken: initial.refresh_token,
fetchFn: trace("refresh request"),
});
console.log("Refreshed access token received:", !!refreshed.access_token);
SDK version
1.29.0 and also 2.0.0
Area
Auth
- Lenguaje dominante
- TypeScript
- Estrellas
- 13.6k
- Forks
- 2.3k
- Merge medio
- 1 d 2 h
- PR fusionados (30 d)
- 51
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de modelcontextprotocol/typescript-sdk
-
Stateless 405 response omits the Allow headerPosiblemente ocupada @Hugo-DDT la tomó hace 5 días. Abiertov2
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
modelcontextprotocol/typescript-sdk#2970 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
[v2] @modelcontextprotocol/server inlines fast-uri 3.1.0, which has 9 published advisoriesPosiblemente ocupada @Andiii208 la tomó hace 7 días. Abiertov2
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
modelcontextprotocol/typescript-sdk#2966 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
v1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
modelcontextprotocol/typescript-sdk#2946 · 4 comentarios ·
Los mantenedores suelen responder en 1 día
-
[v2] URI template reserved expansions encode existing %HH sequences againPosiblemente ocupada @takagibit18 la tomó hace 14 días. Abiertov1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
modelcontextprotocol/typescript-sdk#2920 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
[v2] URI template strict expansions leave !'()* unencodedPosiblemente ocupada @takagibit18 la tomó hace 14 días. Abiertov1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
modelcontextprotocol/typescript-sdk#2919 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de modelcontextprotocol/typescript-sdk
Issues similares
-
Dificultad 1/5 1-3 horas Aptitud para principiantes 75/100
siyuan-note/siyuan#20629 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Volume eject does not refuse system, root or /home mountsPosiblemente ocupada @bernardopg la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
spacedriveapp/spacedrive#3159 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
melgarafael/DeskcommCRM#3000 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
callstackincubator/simlock#639 ·
Los mantenedores suelen responder en 1 día