chore(deps): bump conformance to 0.1.15, port auth/pre-registration from v1.x (#1650)

This commit is contained in:
Felix Weinberger 2026-03-13 14:49:30 +00:00 • committed by GitHub
commit 54051729c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 234 additions and 31 deletions

View file

@ -0,0 +1,5 @@
# Conformance scenarios not yet implemented in the v2 TypeScript SDK.
# CI exits 0 if only these fail, exits 1 on unexpected failures or stale entries.
client:
- auth/cross-app-access-complete-flow

View file

@ -28,16 +28,16 @@
"start": "npm run server",
"server": "tsx watch --clear-screen=false scripts/cli.ts server",
"client": "tsx scripts/cli.ts client",
"test:conformance:client": "conformance client --command 'npx tsx ./src/everythingClient.ts'",
"test:conformance:client:all": "conformance client --command 'npx tsx ./src/everythingClient.ts' --suite all",
"test:conformance:client": "conformance client --command 'npx tsx ./src/everythingClient.ts' --expected-failures ./expected-failures.yaml",
"test:conformance:client:all": "conformance client --command 'npx tsx ./src/everythingClient.ts' --suite all --expected-failures ./expected-failures.yaml",
"test:conformance:client:run": "npx tsx ./src/everythingClient.ts",
"test:conformance:server": "scripts/run-server-conformance.sh",
"test:conformance:server:all": "scripts/run-server-conformance.sh --suite all",
"test:conformance:server": "scripts/run-server-conformance.sh --expected-failures ./expected-failures.yaml",
"test:conformance:server:all": "scripts/run-server-conformance.sh --suite all --expected-failures ./expected-failures.yaml",
"test:conformance:server:run": "npx tsx ./src/everythingServer.ts",
"test:conformance:all": "pnpm run test:conformance:client:all && pnpm run test:conformance:server:all"
},
"devDependencies": {
"@modelcontextprotocol/conformance": "0.1.10",
"@modelcontextprotocol/conformance": "0.1.15",
"@modelcontextprotocol/client": "workspace:^",
"@modelcontextprotocol/server": "workspace:^",
"@modelcontextprotocol/core": "workspace:^",

View file

@ -15,6 +15,7 @@
import { Client, ClientCredentialsProvider, PrivateKeyJwtProvider, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
import * as z from 'zod/v4';
import { ConformanceOAuthProvider } from './helpers/conformanceOAuthProvider.js';
import { logger } from './helpers/logger.js';
import { handle401, withOAuthRetry } from './helpers/withOAuthRetry.js';
@ -42,6 +43,11 @@ const ClientConformanceContextSchema = z.discriminatedUnion('name', [
name: z.literal('auth/client-credentials-basic'),
client_id: z.string(),
client_secret: z.string()
}),
z.object({
name: z.literal('auth/pre-registration'),
client_id: z.string(),
client_secret: z.string()
})
]);
@ -240,6 +246,43 @@ async function runClientCredentialsBasic(serverUrl: string): Promise<void> {
registerScenario('auth/client-credentials-basic', runClientCredentialsBasic);
// ============================================================================
// Pre-registration scenario (no dynamic client registration)
// ============================================================================
async function runPreRegistrationClient(serverUrl: string): Promise<void> {
const ctx = parseContext();
if (ctx.name !== 'auth/pre-registration') {
throw new Error(`Expected pre-registration context, got ${ctx.name}`);
}
// Create a provider pre-populated with registered credentials,
// so the SDK skips dynamic client registration.
const provider = new ConformanceOAuthProvider('http://localhost:3000/callback', {
client_name: 'conformance-pre-registration',
redirect_uris: ['http://localhost:3000/callback']
});
provider.saveClientInformation({
client_id: ctx.client_id,
client_secret: ctx.client_secret,
redirect_uris: ['http://localhost:3000/callback']
});
const oauthFetch = withOAuthRetry('conformance-pre-registration', new URL(serverUrl), handle401, undefined, provider)(fetch);
const client = new Client({ name: 'conformance-pre-registration', version: '1.0.0' }, { capabilities: {} });
const transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
fetch: oauthFetch
});
await client.connect(transport);
await client.listTools();
await client.callTool({ name: 'test-tool', arguments: {} });
await transport.close();
}
registerScenario('auth/pre-registration', runPreRegistrationClient);
// ============================================================================
// Elicitation defaults scenario
// ============================================================================

View file

@ -9,6 +9,7 @@
import { randomUUID } from 'node:crypto';
import { localhostHostValidation } from '@modelcontextprotocol/express';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import type { CallToolResult, EventId, EventStore, GetPromptResult, ReadResourceResult, StreamId } from '@modelcontextprotocol/server';
import { isInitializeRequest, McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
@ -876,6 +877,9 @@ function createMcpServer() {
const app = express();
app.use(express.json());
// DNS rebinding protection: reject non-localhost Host headers
app.use(localhostHostValidation());
// Configure CORS to expose Mcp-Session-Id header for browser-based clients
app.use(
cors({

View file

@ -55,16 +55,19 @@ export const withOAuthRetry = (
clientName: string,
baseUrl?: string | URL,
handle401Fn: typeof handle401 = handle401,
clientMetadataUrl?: string
clientMetadataUrl?: string,
existingProvider?: ConformanceOAuthProvider
): Middleware => {
const provider = new ConformanceOAuthProvider(
'http://localhost:3000/callback',
{
client_name: clientName,
redirect_uris: ['http://localhost:3000/callback']
},
clientMetadataUrl
);
const provider =
existingProvider ??
new ConformanceOAuthProvider(
'http://localhost:3000/callback',
{
client_name: clientName,
redirect_uris: ['http://localhost:3000/callback']
},
clientMetadataUrl
);
return (next: FetchLike) => {
return async (input: string | URL, init?: RequestInit): Promise<Response> => {
const makeRequest = async (): Promise<Response> => {