Skip to content

MCP TypeScript SDK v2 can't OAuth to Hosted MCP: /oauth2/authorize returns an iss that doesn't match the discovered authorization server #453

Description

@marcrasi

The MCP TypeScript SDK v2 adds additional OAuth checks typescript-sdk#2344, and Monday's hosted MCP fails these checks, so the SDK v2 cannot connect.

Specifically, /oauth2/authorize stamps an iss "https://auth.monday.com", which does not match the authorization server "https://auth.monday.com/mcp" advertised by the well-known oauth-protected-resource.

Reproduction

Put this file in a dir and run npm i @modelcontextprotocol/client@2.0.0 && node repro.js.

It'll print out an auth url to the terminal. Visit it and auth. The client will throw:

IssuerMismatchError: Issuer mismatch in authorization response (RFC 9207): expected "https://auth.monday.com/mcp", received "https://auth.monday.com"

# repro.js
import { Client, StreamableHTTPClientTransport, UnauthorizedError } from '@modelcontextprotocol/client';
import { createServer } from 'node:http';

const SERVER_URL = 'https://mcp.monday.com/mcp';
const REDIRECT_URL = 'http://localhost:8899/callback';

let resolveCallback;
const callback = new Promise(resolve => {
    resolveCallback = resolve;
});

const server = createServer((req, res) => {
    const url = new URL(req.url, REDIRECT_URL);
    if (url.pathname !== '/callback') {
        res.writeHead(404).end();
        return;
    }
    res.end('Return to the terminal.');
    resolveCallback(url.searchParams);
}).listen(8899);

let clientInformation;
let tokens;
let codeVerifier;
let discoveryState;

const authProvider = {
    redirectUrl: REDIRECT_URL,
    clientMetadata: {
        client_name: 'iss-mismatch-repro',
        redirect_uris: [REDIRECT_URL],
        grant_types: ['authorization_code', 'refresh_token'],
        response_types: ['code'],
        token_endpoint_auth_method: 'client_secret_post'
    },
    clientInformation: () => clientInformation,
    saveClientInformation: info => void (clientInformation = info),
    tokens: () => tokens,
    saveTokens: t => void (tokens = t),
    codeVerifier: () => codeVerifier,
    saveCodeVerifier: v => void (codeVerifier = v),
    discoveryState: () => discoveryState,
    saveDiscoveryState: s => void (discoveryState = s),
    redirectToAuthorization: url => console.log('\nOpen this URL to authorize:\n\n' + url.href + '\n')
};

const connect = () => new StreamableHTTPClientTransport(new URL(SERVER_URL), { authProvider });
const client = new Client({ name: 'iss-mismatch-repro', version: '0.0.0' });
const transport = connect();

try {
    await client.connect(transport);
} catch (error) {
    if (!UnauthorizedError.isInstance(error)) throw error;
}

await transport.finishAuth(await callback);
server.close();
await client.connect(connect());

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions