-
Notifications
You must be signed in to change notification settings - Fork 6
Open
Description
Description
OAuth token refresh fails for MCP servers where the OAuth authorization server metadata is hosted at the root domain rather than at the MCP server path.
Affected MCP Server
Notion MCP: https://mcp.notion.com/mcp
Root Cause
The discoverTokenEndpoint() function in oauth-utils.js only tries path-based discovery:
const discoveryUrls = [
`${serverUrl}/.well-known/oauth-authorization-server`,
`${serverUrl}/.well-known/openid-configuration`,
];For Notion:
- mcpc tries:
https://mcp.notion.com/mcp/.well-known/oauth-authorization-server❌ (returns 401) - Notion has:
https://mcp.notion.com/.well-known/oauth-authorization-server✅ (returns valid metadata)
Steps to Reproduce
mcpc https://mcp.notion.com/mcp login- succeeds, OAuth flow works- Wait for access token to expire (~1 hour for Notion)
- Run any mcpc command:
mcpc @notion tools-list - Error:
Could not find OAuth token endpoint for https://mcp.notion.com/mcp. Please re-authenticate
Logs
[DEBUG] [oauth-utils] Trying OAuth discovery at: https://mcp.notion.com/mcp/.well-known/oauth-authorization-server
[DEBUG] [oauth-utils] Trying OAuth discovery at: https://mcp.notion.com/mcp/.well-known/openid-configuration
[ERROR] Could not find OAuth token endpoint for https://mcp.notion.com/mcp
Why Initial Auth Works
Initial OAuth flow uses OAuthUtils.discoverAuthorizationServerMetadata() which does try root-based discovery:
// With issuer URLs with path components, try the following well-known endpoints in order:
if (authServerUrlObj.pathname !== '/') {
endpointsToTry.push(new URL(`/.well-known/oauth-authorization-server${authServerUrlObj.pathname}`, base).toString());
// 2. OpenID Connect Discovery 1.0 with path insertion
// ... more endpoints including root-based
}But discoverTokenEndpoint() (used for refresh) doesn't have this fallback logic.
Suggested Fix
Update discoverTokenEndpoint() to also try root-based discovery as a fallback:
export async function discoverTokenEndpoint(serverUrl) {
const serverUrlObj = new URL(serverUrl);
const base = `${serverUrlObj.protocol}//${serverUrlObj.host}`;
const discoveryUrls = [
// Path-based (current behavior)
`${serverUrl}/.well-known/oauth-authorization-server`,
`${serverUrl}/.well-known/openid-configuration`,
// Root-based fallback (needed for Notion, etc.)
`${base}/.well-known/oauth-authorization-server`,
`${base}/.well-known/openid-configuration`,
];
// ... rest of function
}Workaround
Use profiles.json oauthIssuer field? Currently this field exists but doesn't appear to be used by OAuthTokenManager during token refresh.
Environment
- mcpc version: 0.1.7
- macOS Darwin 25.2.0
- MCP Server: Notion MCP 1.0.1
Additional Context
- Notion's OAuth metadata at root returns valid JSON with
token_endpoint: "https://mcp.notion.com/token" - Other MCP servers (Linear, Amplitude) work because their tokens have longer expiry (~7 days), so refresh is rarely triggered during normal use
- The
oauthIssuerfield inprofiles.jsoncould potentially be used to specify the correct discovery base URL, but it's currently not utilized during token refresh
Metadata
Metadata
Assignees
Labels
No labels