Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ jobs:
- run: pnpm run build:all
- run: pnpm run test:conformance:server
- run: pnpm run test:conformance:server:draft
- run: pnpm run test:conformance:server:extensions
- run: pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:2026
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"test:conformance:client:run": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:client:run",
"test:conformance:server": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server",
"test:conformance:server:draft": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:draft",
"test:conformance:server:extensions": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:extensions",
"test:conformance:server:all": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:all",
"test:conformance:server:run": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:run",
"test:conformance:all": "pnpm run test:conformance:client:all && pnpm run test:conformance:server:all"
Expand Down
30 changes: 29 additions & 1 deletion test/conformance/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,32 @@

import baseConfig from '@modelcontextprotocol/eslint-config';

export default baseConfig;
export default [
...baseConfig,
{
files: ['**/*.{ts,tsx,js,jsx,mts,cts}'],
rules: {
// Conformance fixtures MUST use only what a consumer would `npm install` and import:
// public package entry points. Anything reaching into core or package internals is banned.
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@modelcontextprotocol/core', '@modelcontextprotocol/core/*'],
message: 'Conformance fixtures must import from @modelcontextprotocol/{server,client}, not core.'
},
{
group: ['@modelcontextprotocol/*/src/*'],
message: 'Conformance fixtures must import only public package entry points.'
},
{
group: ['@modelcontextprotocol/*/dist/*'],
message: 'Conformance fixtures must import only public package entry points.'
}
]
}
]
}
}
];
25 changes: 22 additions & 3 deletions test/conformance/expected-failures.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ client: []
# (empty: SEP-2468/2352/2350/837/2207/990 burned by the auth bundle; the
# last referee-side gap — conformance#361 callback-iss — closed at alpha.6)

# No server entries — the spec#2907 renumber-mismatch cells burned at the
# alpha.5 referee.
server: []
server:
# --- SEP-2663 (io.modelcontextprotocol/tasks) — server SDK does not implement the tasks extension ---
# Extension-tagged scenarios; selected only by `--suite all` (the alpha.6 referee
# has no server-side `--suite extensions`). The active/draft/2026 legs never select
# them, so they cannot flag these entries as stale. `tasks-status-notifications` is
# intentionally absent: the referee SKIPs it unconditionally (harness rewrite pending
# against the SEP-2575 subscriptions/listen channel), so a baseline entry would be
# flagged stale.
- tasks-lifecycle
- tasks-capability-negotiation
- tasks-wire-fields
- tasks-request-state-removal
- tasks-mrtr-input
- tasks-request-headers
- tasks-dispatch-and-envelope
- tasks-required-task-error
- tasks-mrtr-composition
# --- conformance#256 server-sse-polling — referee-side pending scenario ---
# SHOULD-level checks emit WARNING (priming event + retry field) which the baseline
# checker treats as failure. Selected only by `--suite all`; on hold pending
# conformance#366 (referee sends 2025-03-26 while testing a 2025-11-25 feature; SDK correctly version-gates priming).
- server-sse-polling
1 change: 1 addition & 0 deletions test/conformance/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"test:conformance:client:run": "node --import tsx ./src/everythingClient.ts",
"test:conformance:server": "scripts/run-server-conformance.sh --expected-failures ./expected-failures.yaml",
"test:conformance:server:draft": "scripts/run-server-conformance.sh --suite draft --expected-failures ./expected-failures.yaml",
"test:conformance:server:extensions": "scripts/run-server-conformance.sh --suite all --expected-failures ./expected-failures.yaml",
"test:conformance:server:all": "scripts/run-server-conformance.sh --suite all --expected-failures ./expected-failures.yaml",
"test:conformance:server:2026": "scripts/run-server-conformance.sh --suite all --spec-version 2026-07-28 --expected-failures ./expected-failures.2026-07-28.yaml",
"test:conformance:server:run": "node --import tsx ./src/everythingServer.ts",
Expand Down
25 changes: 8 additions & 17 deletions test/conformance/src/authTestServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,10 @@ const ADMIN_SCOPE = 'admin';

// Function to create a new MCP server instance (one per session)
function createMcpServer(): McpServer {
const mcpServer = new McpServer(
{
name: 'mcp-auth-test-server',
version: '1.0.0'
},
{
capabilities: {
tools: {}
}
}
);
const mcpServer = new McpServer({
name: 'mcp-auth-test-server',
version: '1.0.0'
});

// Simple echo tool for testing authenticated calls
mcpServer.registerTool(
Expand All @@ -72,8 +65,7 @@ function createMcpServer(): McpServer {
message: z.string().optional().describe('The message to echo back')
})
},
async (args: { message?: string }) => {
const message = args.message || 'No message provided';
async ({ message = 'No message provided' }) => {
return {
content: [{ type: 'text', text: `Echo: ${message}` }]
};
Expand Down Expand Up @@ -102,8 +94,7 @@ function createMcpServer(): McpServer {
action: z.string().optional().describe('The admin action to perform')
})
},
async (args: { action?: string }) => {
const action = args.action || 'default-admin-action';
async ({ action = 'default-admin-action' }) => {
return {
content: [{ type: 'text', text: `Admin action performed: ${action}` }]
};
Expand Down Expand Up @@ -274,8 +265,8 @@ async function startServer() {
app.use(
cors({
origin: '*',
exposedHeaders: ['Mcp-Session-Id'],
allowedHeaders: ['Content-Type', 'mcp-session-id', 'last-event-id', 'Authorization']
exposedHeaders: ['Mcp-Session-Id', 'WWW-Authenticate'],
allowedHeaders: ['Content-Type', 'mcp-session-id', 'last-event-id', 'Authorization', 'mcp-protocol-version']
})
);

Expand Down
67 changes: 56 additions & 11 deletions test/conformance/src/everythingClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ async function runToolsCallClient(serverUrl: string): Promise<void> {
logger.debug('Successfully listed tools');

// Call the add_numbers tool
const addTool = tools.tools.find(t => t.name === 'add_numbers');
if (addTool) {
if (tools.tools.some(t => t.name === 'add_numbers')) {
const result = await client.callTool({
name: 'add_numbers',
arguments: { a: 5, b: 3 }
Expand All @@ -188,8 +187,7 @@ async function runToolsCallModernClient(serverUrl: string): Promise<void> {
logger.debug('Successfully listed tools');

// Call the add_numbers tool
const addTool = tools.tools.find(t => t.name === 'add_numbers');
if (addTool) {
if (tools.tools.some(t => t.name === 'add_numbers')) {
const result = await client.callTool({
name: 'add_numbers',
arguments: { a: 5, b: 3 }
Expand Down Expand Up @@ -226,6 +224,51 @@ registerScenario('initialize', runBasicClient);
registerScenario('tools_call', runToolsCallClient);
registerScenario('request-metadata', runRequestMetadataClient);

// ============================================================================
// SEP-2243 standard-header client scenario (Mcp-Method / Mcp-Name)
// ============================================================================

// http-standard-headers: the referee mock answers initialize, tools/list,
// tools/call, resources/list, resources/read, prompts/list, prompts/get and
// asserts that each POST carried the correct Mcp-Method header (and Mcp-Name
// for the call/read/get verbs). The SDK emits both headers on the modern
// streamableHttp path, so the fixture just needs to drive each method once.
// The mock has no server/discover handler and its 2025-shaped initialize
// response doesn't satisfy the v2 client — same connect-time gap as the other
// SEP-2243 mocks — so connect via the withLocalDiscoverResponse shim. The
// initialize / notifications/initialized checks are intentionally left
// SKIPPED; the legacy initialize path's missing Mcp-Method is tracked as a
// baseline bug. The mock advertises its own surface (test_headers /
// file:///path/to/file%20name.txt / test_prompt) — the fixture lists first
// and uses whatever the mock returned so it stays referee-version-agnostic.
async function runHttpStandardHeadersClient(serverUrl: string): Promise<void> {
const client = await connectModernHeaderClient(serverUrl);
logger.debug('Successfully connected to MCP server');

const { tools } = await client.listTools();
const tool = tools[0];
if (tool) {
await client.callTool({ name: tool.name, arguments: {} });
}

const { resources } = await client.listResources();
const resource = resources[0];
if (resource) {
await client.readResource({ uri: resource.uri });
}

const { prompts } = await client.listPrompts();
const prompt = prompts[0];
if (prompt) {
await client.getPrompt({ name: prompt.name, arguments: {} });
}

await client.close();
logger.debug('Connection closed successfully');
}

registerScenario('http-standard-headers', runHttpStandardHeadersClient);

// ============================================================================
// SEP-2243 custom-header client scenarios (protocol revision 2026-07-28)
// ============================================================================
Expand Down Expand Up @@ -319,7 +362,10 @@ function withLocalDiscoverResponse(serverInfo: { name: string; version: string }
id: message.id,
result: {
supportedVersions: ['2026-07-28'],
capabilities: { tools: { listChanged: true } },
// Advertise the full read surface so capability-gated
// list/read/get calls reach the real mock; callers that
// only use tools are unaffected by the extra entries.
capabilities: { tools: { listChanged: true }, resources: {}, prompts: {} },
serverInfo
}
},
Expand Down Expand Up @@ -425,6 +471,10 @@ registerScenarios(
'auth/metadata-var3',
'auth/2025-03-26-oauth-metadata-backcompat',
'auth/2025-03-26-oauth-endpoint-fallback',
// RFC 8707 resource-indicator binding: the referee serves a PRM whose
// `resource` does not match the MCP server URL; the SDK's discovery path
// must reject before token exchange (the referee sets `allowClientError`).
'auth/resource-mismatch',
'auth/scope-from-www-authenticate',
'auth/scope-from-scopes-supported',
'auth/scope-omitted-when-undefined',
Expand Down Expand Up @@ -789,9 +839,4 @@ async function main(): Promise<void> {
}
}

try {
await main();
} catch (error) {
logger.error('Error:', error);
process.exit(1);
}
await main();
Loading
Loading