[v2] Incompatibility with refresh_tokens from Azure when client_id=resource_id
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 72/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- typescript
調査の方向性
提供された repro.mjs の例にある refreshAuthorization 呼び出しから始め、その refresh リクエストがどのように組み立てられるかを追跡します。Azure に対して再現を実行し、初期リクエストと refresh リクエストのスコープを比較し、client_id と resource_id が一致する場合に更新されたアクセストークンが正常に使用できることを確認します。
索引モデルが issue の本文から書いたものです。
説明
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
- 主要言語
- TypeScript
- スター
- 13.6k
- フォーク
- 2.3k
- 平均マージ
- 1日 5時間
- マージ済み PR(30日)
- 52
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
modelcontextprotocol/typescript-sdk のほかの issue
-
Stateless 405 response omits the Allow header対応中かも @Hugo-DDT が 4 日前に担当しました。 オープンv2
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
modelcontextprotocol/typescript-sdk#2970 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
[v2] @modelcontextprotocol/server inlines fast-uri 3.1.0, which has 9 published advisories対応中かも @Andiii208 が 6 日前に担当しました。 オープンv2
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
modelcontextprotocol/typescript-sdk#2966 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
v1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
modelcontextprotocol/typescript-sdk#2946 · コメント 4 件 ·
メンテナーはふだん 1 日以内に返信
-
[v2] URI template reserved expansions encode existing %HH sequences again対応中かも @takagibit18 が 13 日前に担当しました。 オープンv1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
modelcontextprotocol/typescript-sdk#2920 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[v2] URI template strict expansions leave !'()* unencoded対応中かも @takagibit18 が 13 日前に担当しました。 オープンv1 v2
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
modelcontextprotocol/typescript-sdk#2919 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
modelcontextprotocol/typescript-sdk の issue をすべて見る
似ている issue
-
documentation good first issue
難易度 2/5 半日 初心者へのやさしさ 72/100
sujithputta02/Esapay#13 ·
-
Share dialog: the address book's contact count reads "1 contacts" (no singular form in any language)対応中かも @chibenwa が今日担当しました。 オープンai-driven-qa bug claude Cosmetic
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
linagora/twake-contacts-frontend#271 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
Koenkk/zigbee2mqtt#33402 ·
メンテナーはふだん 1 日以内に返信
-
dx P2 platform Stellar Wave Wave 10
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
winsznx/routedock#579 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
area:ui effort:S enhancement priority:P2
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信