From c8663c1dad7ca0735d1d74333410f3fa7888af65 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 16:21:48 +0000 Subject: [PATCH 1/9] Add client credentials support to everything-client - Add ConformanceContextSchema with discriminated union for auth contexts - Add runClientCredentialsJwt and runClientCredentialsBasic handlers - Update runner to include scenario name in context for discriminated union parsing - Update test helpers to set env vars for inline client runners - Fix JWT audience validation to match SDK behavior (no trailing slash) - Remove client credentials scenarios from skip list --- .../clients/typescript/everything-client.ts | 107 +++++++++++++++++- src/runner/client.ts | 6 +- .../client/auth/client-credentials.ts | 5 +- src/scenarios/client/auth/index.test.ts | 20 +++- .../client/auth/test_helpers/testClient.ts | 14 +++ src/schemas/context.ts | 23 ++++ 6 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 src/schemas/context.ts diff --git a/examples/clients/typescript/everything-client.ts b/examples/clients/typescript/everything-client.ts index 464eb7a..6618426 100644 --- a/examples/clients/typescript/everything-client.ts +++ b/examples/clients/typescript/everything-client.ts @@ -12,9 +12,15 @@ * consolidating all the individual test clients into one. */ +import { fileURLToPath } from 'url'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { + ClientCredentialsProvider, + PrivateKeyJwtProvider +} from '@modelcontextprotocol/sdk/client/auth-extensions.js'; import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import { ConformanceContextSchema } from '../../../src/schemas/context.js'; import { withOAuthRetry } from './helpers/withOAuthRetry.js'; import { logger } from './helpers/logger.js'; @@ -175,6 +181,96 @@ async function runElicitationDefaultsClient(serverUrl: string): Promise { registerScenario('elicitation-defaults', runElicitationDefaultsClient); +// ============================================================================ +// Client Credentials scenarios +// ============================================================================ + +/** + * Parse the conformance context from MCP_CONFORMANCE_CONTEXT env var. + */ +function parseContext() { + const raw = process.env.MCP_CONFORMANCE_CONTEXT; + if (!raw) { + throw new Error('MCP_CONFORMANCE_CONTEXT not set'); + } + return ConformanceContextSchema.parse(JSON.parse(raw)); +} + +/** + * Client credentials with private_key_jwt authentication. + */ +export async function runClientCredentialsJwt( + serverUrl: string +): Promise { + const ctx = parseContext(); + if (ctx.name !== 'auth/client-credentials-jwt') { + throw new Error(`Expected jwt context, got ${ctx.name}`); + } + + const provider = new PrivateKeyJwtProvider({ + clientId: ctx.client_id, + privateKey: ctx.private_key_pem, + algorithm: ctx.signing_algorithm || 'ES256' + }); + + const client = new Client( + { name: 'conformance-client-credentials-jwt', version: '1.0.0' }, + { capabilities: {} } + ); + + const transport = new StreamableHTTPClientTransport(new URL(serverUrl), { + authProvider: provider + }); + + await client.connect(transport); + logger.debug('Successfully connected with private_key_jwt auth'); + + await client.listTools(); + logger.debug('Successfully listed tools'); + + await transport.close(); + logger.debug('Connection closed successfully'); +} + +registerScenario('auth/client-credentials-jwt', runClientCredentialsJwt); + +/** + * Client credentials with client_secret_basic authentication. + */ +export async function runClientCredentialsBasic( + serverUrl: string +): Promise { + const ctx = parseContext(); + if (ctx.name !== 'auth/client-credentials-basic') { + throw new Error(`Expected basic context, got ${ctx.name}`); + } + + const provider = new ClientCredentialsProvider({ + clientId: ctx.client_id, + clientSecret: ctx.client_secret + }); + + const client = new Client( + { name: 'conformance-client-credentials-basic', version: '1.0.0' }, + { capabilities: {} } + ); + + const transport = new StreamableHTTPClientTransport(new URL(serverUrl), { + authProvider: provider + }); + + await client.connect(transport); + logger.debug('Successfully connected with client_secret_basic auth'); + + await client.listTools(); + logger.debug('Successfully listed tools'); + + await transport.close(); + logger.debug('Connection closed successfully'); +} + +registerScenario('auth/client-credentials-basic', runClientCredentialsBasic); + // ============================================================================ // Main entry point // ============================================================================ @@ -216,7 +312,10 @@ async function main(): Promise { } } -main().catch((error) => { - console.error('Unhandled error:', error); - process.exit(1); -}); +// Only run main when this file is executed directly, not when imported as a module +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch((error) => { + console.error('Unhandled error:', error); + process.exit(1); + }); +} diff --git a/src/runner/client.ts b/src/runner/client.ts index dcb005f..7db5461 100644 --- a/src/runner/client.ts +++ b/src/runner/client.ts @@ -35,7 +35,11 @@ async function executeClient( const env = { ...process.env }; env.MCP_CONFORMANCE_SCENARIO = scenarioName; if (context) { - env.MCP_CONFORMANCE_CONTEXT = JSON.stringify(context); + // Include scenario name in context for discriminated union parsing + env.MCP_CONFORMANCE_CONTEXT = JSON.stringify({ + name: scenarioName, + ...context + }); } return new Promise((resolve) => { diff --git a/src/scenarios/client/auth/client-credentials.ts b/src/scenarios/client/auth/client-credentials.ts index df13384..ca14307 100644 --- a/src/scenarios/client/auth/client-credentials.ts +++ b/src/scenarios/client/auth/client-credentials.ts @@ -51,9 +51,8 @@ export class ClientCredentialsJwtScenario implements Scenario { tokenEndpointAuthSigningAlgValuesSupported: ['ES256'], onTokenRequest: async ({ grantType, body, timestamp, authBaseUrl }) => { // Per RFC 7523bis, the audience MUST be the issuer identifier - const issuerUrl = authBaseUrl.endsWith('/') - ? authBaseUrl - : `${authBaseUrl}/`; + // The SDK uses metadata.issuer as audience, which matches authBaseUrl + const issuerUrl = authBaseUrl; if (grantType !== 'client_credentials') { this.checks.push({ id: 'client-credentials-grant-type', diff --git a/src/scenarios/client/auth/index.test.ts b/src/scenarios/client/auth/index.test.ts index 803ecf7..cd920b2 100644 --- a/src/scenarios/client/auth/index.test.ts +++ b/src/scenarios/client/auth/index.test.ts @@ -10,6 +10,10 @@ import { runClient as ignoreScopeClient } from '../../../../examples/clients/typ import { runClient as partialScopesClient } from '../../../../examples/clients/typescript/auth-test-partial-scopes'; import { runClient as ignore403Client } from '../../../../examples/clients/typescript/auth-test-ignore-403'; import { runClient as noRetryLimitClient } from '../../../../examples/clients/typescript/auth-test-no-retry-limit'; +import { + runClientCredentialsJwt, + runClientCredentialsBasic +} from '../../../../examples/clients/typescript/everything-client'; import { setLogLevel } from '../../../../examples/clients/typescript/helpers/logger'; beforeAll(() => { @@ -17,10 +21,7 @@ beforeAll(() => { }); const skipScenarios = new Set([ - // Client credentials scenarios require SDK support for client_credentials grant - // Pending typescript-sdk implementation - 'auth/client-credentials-jwt', - 'auth/client-credentials-basic' + // Add scenarios that should be skipped here ]); const allowClientErrorScenarios = new Set([ @@ -28,6 +29,13 @@ const allowClientErrorScenarios = new Set([ 'auth/scope-retry-limit' ]); +// Map of scenario names to their specific client handlers +const scenarioClientMap: Record Promise> = + { + 'auth/client-credentials-jwt': runClientCredentialsJwt, + 'auth/client-credentials-basic': runClientCredentialsBasic + }; + describe('Client Auth Scenarios', () => { // Generate individual test for each auth scenario for (const scenario of authScenariosList) { @@ -36,7 +44,9 @@ describe('Client Auth Scenarios', () => { // TODO: skip in a native way? return; } - const runner = new InlineClientRunner(goodClient); + // Use scenario-specific client if available, otherwise use goodClient + const clientFn = scenarioClientMap[scenario.name] ?? goodClient; + const runner = new InlineClientRunner(clientFn); await runClientAgainstScenario(runner, scenario.name, { allowClientError: allowClientErrorScenarios.has(scenario.name) }); diff --git a/src/scenarios/client/auth/test_helpers/testClient.ts b/src/scenarios/client/auth/test_helpers/testClient.ts index 45f4e6d..0dbc8d8 100644 --- a/src/scenarios/client/auth/test_helpers/testClient.ts +++ b/src/scenarios/client/auth/test_helpers/testClient.ts @@ -107,6 +107,16 @@ export async function runClientAgainstScenario( const serverUrl = urls.serverUrl; try { + // Set environment variables for inline clients + // These mirror what src/runner/client.ts does for spawned processes + process.env.MCP_CONFORMANCE_SCENARIO = scenarioName; + if (urls.context) { + process.env.MCP_CONFORMANCE_CONTEXT = JSON.stringify({ + name: scenarioName, + ...urls.context + }); + } + // Run the client try { await runner.run(serverUrl); @@ -166,6 +176,10 @@ export async function runClientAgainstScenario( } } } finally { + // Clean up environment variables + delete process.env.MCP_CONFORMANCE_SCENARIO; + delete process.env.MCP_CONFORMANCE_CONTEXT; + // Stop the scenario server await scenario.stop(); } diff --git a/src/schemas/context.ts b/src/schemas/context.ts new file mode 100644 index 0000000..71e76fd --- /dev/null +++ b/src/schemas/context.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; + +/** + * Schema for conformance test context passed via MCP_CONFORMANCE_CONTEXT. + * + * Each variant includes a `name` field matching the scenario name to enable + * discriminated union parsing and type-safe access to scenario-specific fields. + */ +export const ConformanceContextSchema = z.discriminatedUnion('name', [ + z.object({ + name: z.literal('auth/client-credentials-jwt'), + client_id: z.string(), + private_key_pem: z.string(), + signing_algorithm: z.string().optional() + }), + z.object({ + name: z.literal('auth/client-credentials-basic'), + client_id: z.string(), + client_secret: z.string() + }) +]); + +export type ConformanceContext = z.infer; From a3d6e5beb58fffb0af8ea7d5410179445a204bd8 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 16:40:39 +0000 Subject: [PATCH 2/9] Accept both trailing slash forms for JWT audience per RFC 3986 Per RFC 3986, URLs with and without trailing slash are equivalent. The MCP spec recommends (SHOULD) the form without trailing slash, but since it's not a MUST, the conformance test should accept both forms for interoperability with clients like Pydantic that normalize URLs. --- .vscode/settings.json | 3 +++ src/scenarios/client/auth/client-credentials.ts | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2ffce44 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "workbench.colorTheme": "Solarized Dark" +} diff --git a/src/scenarios/client/auth/client-credentials.ts b/src/scenarios/client/auth/client-credentials.ts index ca14307..76b2720 100644 --- a/src/scenarios/client/auth/client-credentials.ts +++ b/src/scenarios/client/auth/client-credentials.ts @@ -97,8 +97,15 @@ export class ClientCredentialsJwtScenario implements Scenario { // Verify JWT signature and claims using the generated public key try { // Per RFC 7523bis, audience MUST be the issuer identifier + // Per RFC 3986, URLs with and without trailing slash are equivalent, + // so we normalize by removing trailing slashes for comparison + const normalizedIssuerUrl = issuerUrl.replace(/\/+$/, ''); const { payload } = await jose.jwtVerify(clientAssertion, publicKey, { - audience: issuerUrl, + audience: [ + issuerUrl, + normalizedIssuerUrl, + `${normalizedIssuerUrl}/` + ], clockTolerance: 30 }); From 0bd8fc5530b0aae92980e4295873a296fb307e5d Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 16:48:36 +0000 Subject: [PATCH 3/9] Remove .vscode from tracking and add to gitignore --- .gitignore | 4 +++- .vscode/settings.json | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 4a1642b..263abe7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules results/ lefthook-local.yml -dist \ No newline at end of file +dist.vscode/ +.vscode/ +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 2ffce44..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "workbench.colorTheme": "Solarized Dark" -} From a6e86b2e18ae4d709e64cf8258e365bcb2ab3cdd Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 16:51:35 +0000 Subject: [PATCH 4/9] Address review feedback - Remove unused issuerUrl variable and comment - Simplify audience array to exactly 2 values with clearer naming - Add comment explaining strip-then-add-back logic - Rename ConformanceContextSchema to ClientConformanceContextSchema --- dist/index.js | 6583 +++++++++++++++++ .../clients/typescript/everything-client.ts | 4 +- .../client/auth/client-credentials.ts | 19 +- src/schemas/context.ts | 8 +- 4 files changed, 6598 insertions(+), 16 deletions(-) create mode 100755 dist/index.js diff --git a/dist/index.js b/dist/index.js new file mode 100755 index 0000000..cf03c8e --- /dev/null +++ b/dist/index.js @@ -0,0 +1,6583 @@ +#!/usr/bin/env node +import { Command as e } from 'commander'; +import { ZodError as t, z as n } from 'zod'; +import { spawn as r } from 'child_process'; +import { promises as i } from 'fs'; +import a from 'path'; +import o from 'http'; +import { Server as s } from '@modelcontextprotocol/sdk/server/index.js'; +import { StreamableHTTPServerTransport as c } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { + CallToolRequestSchema as l, + CallToolResultSchema as u, + CreateMessageRequestSchema as d, + ElicitRequestSchema as f, + ElicitResultSchema as p, + ErrorCode as m, + ListToolsRequestSchema as h, + LoggingMessageNotificationSchema as g, + McpError as _, + ProgressNotificationSchema as v +} from '@modelcontextprotocol/sdk/types.js'; +import y from 'express'; +import { randomUUID as ee } from 'crypto'; +import { Client as b } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport as x } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { EventSourceParserStream as S } from 'eventsource-parser/stream'; +import { requireBearerAuth as te } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js'; +import * as C from 'jose'; +var ne = Object.defineProperty, + re = ((e) => { + let t = {}; + for (var n in e) ne(t, n, { get: e[n], enumerable: !0 }); + return t; + })({ + createClientInitializationCheck: () => ae, + createServerInfoCheck: () => ie + }); +function ie(e) { + return { + id: `server-info`, + name: `ServerInfo`, + description: `Test server info returned to client`, + status: `INFO`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `MCP-Lifecycle`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle` + } + ], + details: { serverName: e.name, serverVersion: e.version } + }; +} +const w = [`2025-06-18`, `2025-11-25`]; +function ae(e, t = `2025-11-25`) { + let n = e?.protocolVersion, + r = (w.includes(t) ? w : [...w, t]).includes(n), + i = []; + return ( + n || i.push(`Protocol version not provided`), + r || i.push(`Version mismatch: expected ${t}, got ${n}`), + e?.clientInfo?.name || i.push(`Client name missing`), + e?.clientInfo?.version || i.push(`Client version missing`), + { + id: `mcp-client-initialization`, + name: `MCPClientInitialization`, + description: `Validates that MCP client properly initializes with server`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `MCP-Lifecycle`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle` + } + ], + details: { + protocolVersionSent: n, + expectedSpecVersion: t, + versionMatch: r, + clientName: e?.clientInfo?.name, + clientVersion: e?.clientInfo?.version + }, + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + logs: i.length > 0 ? i : void 0 + } + ); +} +const T = re; +var oe = class { + constructor() { + ((this.name = `initialize`), + (this.description = `Tests MCP client initialization handshake`), + (this.server = null), + (this.checks = []), + (this.port = 0)); + } + async start() { + return new Promise((e, t) => { + ((this.server = o.createServer((e, t) => { + this.handleRequest(e, t); + })), + this.server.on(`error`, t), + this.server.listen(0, () => { + let n = this.server.address(); + n && typeof n == `object` + ? ((this.port = n.port), + e({ serverUrl: `http://localhost:${this.port}` })) + : t(Error(`Failed to get server address`)); + })); + }); + } + async stop() { + return new Promise((e, t) => { + this.server + ? this.server.close((n) => { + n ? t(n) : ((this.server = null), e()); + }) + : e(); + }); + } + getChecks() { + return this.checks; + } + handleRequest(e, t) { + let n = ``; + (e.on(`data`, (e) => { + n += e.toString(); + }), + e.on(`end`, () => { + try { + let e = JSON.parse(n); + e.method === `initialize` + ? this.handleInitialize(e, t) + : e.method === `tools/list` + ? this.handleToolsList(e, t) + : (t.writeHead(200, { 'Content-Type': `application/json` }), + t.end( + JSON.stringify({ jsonrpc: `2.0`, id: e.id, result: {} }) + )); + } catch (e) { + (t.writeHead(400, { 'Content-Type': `application/json` }), + t.end( + JSON.stringify({ + jsonrpc: `2.0`, + error: { code: -32700, message: `Parse error ${e}` } + }) + )); + } + })); + } + handleInitialize(e, t) { + let n = e.params, + r = T.createClientInitializationCheck(n); + this.checks.push(r); + let i = { name: `test-server`, version: `1.0.0` }; + this.checks.push(T.createServerInfoCheck(i)); + let a = [`2025-06-18`, `2025-11-25`], + o = n?.protocolVersion, + s = a.includes(o) ? o : `2025-11-25`, + c = { + jsonrpc: `2.0`, + id: e.id, + result: { protocolVersion: s, serverInfo: i, capabilities: {} } + }; + (t.writeHead(200, { 'Content-Type': `application/json` }), + t.end(JSON.stringify(c))); + } + handleToolsList(e, t) { + let n = { jsonrpc: `2.0`, id: e.id, result: { tools: [] } }; + (t.writeHead(200, { 'Content-Type': `application/json` }), + t.end(JSON.stringify(n))); + } +}; +function E(e, t) { + return (n, r, i) => { + let a = `Received ${n.method} request for ${n.path}`, + o = { method: n.method, path: n.path, body: n.body }; + if ( + (Object.keys(n.query).length > 0 && (o.query = n.query), + t.mcpRoute && + n.path === t.mcpRoute && + n.get(`content-type`)?.includes(`application/json`) && + n.body && + typeof n.body == `object` && + n.body.method) + ) { + let e = n.body.method; + ((a += ` (method: ${e})`), (o.mcpMethod = e)); + } + e.push({ + id: t.incomingId, + name: t.incomingId.charAt(0).toUpperCase() + t.incomingId.slice(1), + description: a, + status: `INFO`, + timestamp: new Date().toISOString(), + details: o + }); + let s = r.write.bind(r), + c = r.end.bind(r), + l = []; + ((r.write = function (e, ...t) { + return (l.push(e), s(e, ...t)); + }), + (r.end = function (i, ...a) { + i && l.push(i); + let s = l.map((e) => (typeof e == `string` ? Buffer.from(e) : e)), + u = Buffer.concat(s).toString(`utf8`), + d = `Sent ${r.statusCode} response for ${n.method} ${n.path}`, + f = { method: n.method, path: n.path, statusCode: r.statusCode }; + o.mcpMethod && + ((d += ` (method: ${o.mcpMethod})`), (f.mcpMethod = o.mcpMethod)); + let p = r.getHeaders(); + if ((Object.keys(p).length > 0 && (f.headers = p), u)) + try { + f.body = JSON.parse(u); + } catch { + f.body = u; + } + return ( + e.push({ + id: t.outgoingId, + name: t.outgoingId.charAt(0).toUpperCase() + t.outgoingId.slice(1), + description: d, + status: `INFO`, + timestamp: new Date().toISOString(), + details: f + }), + c(i, ...a) + ); + }), + i()); + }; +} +function se(e) { + let t = new s( + { name: `add-numbers-server`, version: `1.0.0` }, + { capabilities: { tools: {} } } + ); + (t.setRequestHandler(h, async () => ({ + tools: [ + { + name: `add_numbers`, + description: `Add two numbers together`, + inputSchema: { + type: `object`, + properties: { + a: { type: `number`, description: `First number` }, + b: { type: `number`, description: `Second number` } + }, + required: [`a`, `b`] + } + } + ] + })), + t.setRequestHandler(l, async (t) => { + if (t.params.name === `add_numbers`) { + let { a: n, b: r } = t.params.arguments, + i = n + r; + return ( + e.push({ + id: `tool-add-numbers`, + name: `ToolAddNumbers`, + description: `Validates that the add_numbers tool works correctly`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `MCP-Tools`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ], + details: { a: n, b: r, result: i } + }), + { + content: [ + { type: `text`, text: `The sum of ${n} and ${r} is ${i}` } + ] + } + ); + } + throw Error(`Unknown tool: ${t.params.name}`); + })); + let n = y(); + return ( + n.use(y.json()), + n.use( + E(e, { + incomingId: `incoming-request`, + outgoingId: `outgoing-response`, + mcpRoute: `/mcp` + }) + ), + n.post(`/mcp`, async (e, n) => { + let r = new c({ sessionIdGenerator: void 0 }); + (await t.connect(r), await r.handleRequest(e, n, e.body)); + }), + n + ); +} +var ce = class { + constructor() { + ((this.name = `tools_call`), + (this.description = `Tests calling tools with various parameter types`), + (this.app = null), + (this.httpServer = null), + (this.checks = [])); + } + async start() { + return ( + (this.checks = []), + (this.app = se(this.checks)), + (this.httpServer = this.app.listen(0)), + { serverUrl: `http://localhost:${this.httpServer.address().port}/mcp` } + ); + } + async stop() { + ((this.httpServer &&= + (await new Promise((e) => this.httpServer.close(e)), null)), + (this.app = null)); + } + getChecks() { + for (let e of [`tool-add-numbers`]) + this.checks.find((t) => t.id === e) || + this.checks.push({ + id: e, + name: `ToolAddNumbers`, + description: `Validates that the add_numbers tool works correctly`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + details: { message: `Tool was not called by client` }, + specReferences: [ + { + id: `MCP-Tools`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ] + }); + return this.checks; + } +}; +function le(e) { + return !!( + typeof e == `object` && + e && + `method` in e && + e.method === `initialize` + ); +} +function ue(e) { + let t = {}, + n = {}, + r = () => { + let t = new s( + { name: `elicitation-defaults-test-server`, version: `1.0.0` }, + { capabilities: { tools: {} } } + ); + return ( + t.setRequestHandler(h, async () => ({ + tools: [ + { + name: `test_client_elicitation_defaults`, + description: `Tests that client applies defaults for omitted elicitation fields`, + inputSchema: { type: `object`, properties: {}, required: [] } + } + ] + })), + t.setRequestHandler(l, async (n) => { + if (n.params.name === `test_client_elicitation_defaults`) + try { + let n = await t.request( + { + method: `elicitation/create`, + params: { + message: `Test client default value handling - please accept with defaults`, + requestedSchema: { + type: `object`, + properties: { + name: { + type: `string`, + description: `User name`, + default: `John Doe` + }, + age: { + type: `integer`, + description: `User age`, + default: 30 + }, + score: { + type: `number`, + description: `User score`, + default: 95.5 + }, + status: { + type: `string`, + description: `User status`, + enum: [`active`, `inactive`, `pending`], + default: `active` + }, + verified: { + type: `boolean`, + description: `Verification status`, + default: !0 + } + }, + required: [] + } + } + }, + p + ); + if (n.action !== `accept`) + return ( + e.push({ + id: `client-elicitation-sep1034-general`, + name: `ClientElicitationSEP1034General`, + description: `Client accepts elicitation request`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Expected action 'accept', got '${n.action}'`, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ] + }), + { + content: [ + { type: `text`, text: `Elicitation was not accepted` } + ] + } + ); + let r = n.content || {}, + i = []; + (`name` in r + ? typeof r.name != `string` && + i.push(`Expected string for "name", got ${typeof r.name}`) + : i.push( + `Field "name" missing - should have default "John Doe"` + ), + e.push({ + id: `client-elicitation-sep1034-string-default`, + name: `ClientElicitationSEP1034StringDefault`, + description: `Client applies string default value for elicitation`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { + field: `name`, + expectedDefault: `John Doe`, + receivedValue: r.name + } + })); + let a = []; + (`age` in r + ? typeof r.age != `number` && + a.push(`Expected number for "age", got ${typeof r.age}`) + : a.push(`Field "age" missing - should have default 30`), + e.push({ + id: `client-elicitation-sep1034-integer-default`, + name: `ClientElicitationSEP1034IntegerDefault`, + description: `Client applies integer default value for elicitation`, + status: a.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: a.length > 0 ? a.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { + field: `age`, + expectedDefault: 30, + receivedValue: r.age + } + })); + let o = []; + (`score` in r + ? typeof r.score != `number` && + o.push(`Expected number for "score", got ${typeof r.score}`) + : o.push(`Field "score" missing - should have default 95.5`), + e.push({ + id: `client-elicitation-sep1034-number-default`, + name: `ClientElicitationSEP1034NumberDefault`, + description: `Client applies number default value for elicitation`, + status: o.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: o.length > 0 ? o.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { + field: `score`, + expectedDefault: 95.5, + receivedValue: r.score + } + })); + let s = []; + (`status` in r + ? typeof r.status == `string` + ? [`active`, `inactive`, `pending`].includes(r.status) || + s.push(`Value "${r.status}" is not a valid enum member`) + : s.push( + `Expected string for "status", got ${typeof r.status}` + ) + : s.push( + `Field "status" missing - should have default "active"` + ), + e.push({ + id: `client-elicitation-sep1034-enum-default`, + name: `ClientElicitationSEP1034EnumDefault`, + description: `Client applies enum default value for elicitation`, + status: s.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: s.length > 0 ? s.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { + field: `status`, + expectedDefault: `active`, + receivedValue: r.status + } + })); + let c = []; + return ( + `verified` in r + ? typeof r.verified != `boolean` && + c.push( + `Expected boolean for "verified", got ${typeof r.verified}` + ) + : c.push( + `Field "verified" missing - should have default true` + ), + e.push({ + id: `client-elicitation-sep1034-boolean-default`, + name: `ClientElicitationSEP1034BooleanDefault`, + description: `Client applies boolean default value for elicitation`, + status: c.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: c.length > 0 ? c.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { + field: `verified`, + expectedDefault: !0, + receivedValue: r.verified + } + }), + { + content: [ + { + type: `text`, + text: `Elicitation completed: ${JSON.stringify(r)}` + } + ] + } + ); + } catch (t) { + return ( + e.push({ + id: `client-elicitation-sep1034-general`, + name: `ClientElicitationSEP1034General`, + description: `Client handles elicitation with defaults`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Elicitation failed: ${t.message}`, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ] + }), + { + content: [ + { type: `text`, text: `Elicitation error: ${t.message}` } + ] + } + ); + } + throw Error(`Unknown tool: ${n.params.name}`); + }), + t + ); + }, + i = y(); + return ( + i.use(y.json()), + i.use( + E(e, { + incomingId: `incoming-request`, + outgoingId: `outgoing-response`, + mcpRoute: `/mcp` + }) + ), + i.post(`/mcp`, async (e, i) => { + let a = e.headers[`mcp-session-id`]; + try { + let o; + if (a && t[a]) o = t[a]; + else if (!a && le(e.body)) { + let a = r(); + ((o = new c({ + sessionIdGenerator: () => ee(), + onsessioninitialized: (e) => { + ((t[e] = o), (n[e] = a)); + } + })), + (o.onclose = () => { + let e = o.sessionId; + e && t[e] && (delete t[e], n[e] && (n[e].close(), delete n[e])); + }), + await a.connect(o), + await o.handleRequest(e, i, e.body)); + return; + } else { + i.status(400).json({ + jsonrpc: `2.0`, + error: { code: -32e3, message: `Invalid or missing session ID` }, + id: null + }); + return; + } + await o.handleRequest(e, i, e.body); + } catch { + i.headersSent || + i.status(500).json({ + jsonrpc: `2.0`, + error: { code: -32603, message: `Internal server error` }, + id: null + }); + } + }), + i.get(`/mcp`, async (e, n) => { + let r = e.headers[`mcp-session-id`]; + if (!r || !t[r]) { + n.status(400).send(`Invalid or missing session ID`); + return; + } + try { + await t[r].handleRequest(e, n); + } catch { + n.headersSent || n.status(500).send(`Error establishing SSE stream`); + } + }), + i.delete(`/mcp`, async (e, n) => { + let r = e.headers[`mcp-session-id`]; + if (!r || !t[r]) { + n.status(400).send(`Invalid or missing session ID`); + return; + } + try { + await t[r].handleRequest(e, n); + } catch { + n.headersSent || n.status(500).send(`Error handling termination`); + } + }), + { + app: i, + cleanup: () => { + for (let e of Object.keys(t)) n[e] && n[e].close(); + } + } + ); +} +var de = class { + constructor() { + ((this.name = `elicitation-sep1034-client-defaults`), + (this.description = `Tests client applies default values for omitted elicitation fields (SEP-1034)`), + (this.app = null), + (this.httpServer = null), + (this.checks = []), + (this.cleanup = null)); + } + async start() { + this.checks = []; + let { app: e, cleanup: t } = ue(this.checks); + return ( + (this.app = e), + (this.cleanup = t), + (this.httpServer = this.app.listen(0)), + { serverUrl: `http://localhost:${this.httpServer.address().port}/mcp` } + ); + } + async stop() { + ((this.cleanup &&= (this.cleanup(), null)), + (this.httpServer &&= + (await new Promise((e) => this.httpServer.close(e)), null)), + (this.app = null)); + } + getChecks() { + for (let e of [ + `client-elicitation-sep1034-string-default`, + `client-elicitation-sep1034-integer-default`, + `client-elicitation-sep1034-number-default`, + `client-elicitation-sep1034-enum-default`, + `client-elicitation-sep1034-boolean-default` + ]) + this.checks.find((t) => t.id === e) || + this.checks.push({ + id: e, + name: e.replace(/-/g, ``), + description: `Server applies ${e.split(`-`)[4]} default for elicitation`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + details: { message: `Tool was not called by client` }, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ] + }); + return this.checks; + } + }, + fe = class { + constructor() { + ((this.name = `sse-retry`), + (this.description = `Tests that client respects SSE retry field timing and reconnects properly (SEP-1699)`), + (this.server = null), + (this.checks = []), + (this.port = 0), + (this.toolStreamCloseTime = null), + (this.getReconnectionTime = null), + (this.getConnectionCount = 0), + (this.lastEventIds = []), + (this.retryValue = 500), + (this.eventIdCounter = 0), + (this.sessionId = `session-${Date.now()}`), + (this.pendingToolCallId = null), + (this.getResponseStream = null), + (this.EARLY_TOLERANCE = 50), + (this.LATE_TOLERANCE = 200), + (this.VERY_LATE_MULTIPLIER = 2)); + } + async start() { + return new Promise((e, t) => { + ((this.server = o.createServer((e, t) => { + this.handleRequest(e, t); + })), + this.server.on(`error`, t), + this.server.listen(0, () => { + let n = this.server.address(); + n && typeof n == `object` + ? ((this.port = n.port), + e({ serverUrl: `http://localhost:${this.port}` })) + : t(Error(`Failed to get server address`)); + })); + }); + } + async stop() { + return new Promise((e, t) => { + this.server + ? this.server.close((n) => { + n ? t(n) : ((this.server = null), e()); + }) + : e(); + }); + } + getChecks() { + return (this.generateChecks(), this.checks); + } + handleRequest(e, t) { + if (e.method === `GET`) { + (this.getConnectionCount++, + (this.getReconnectionTime = performance.now())); + let n = e.headers[`last-event-id`], + r = n + ? `Received GET request for ${e.url} (Last-Event-ID: ${n})` + : `Received GET request for ${e.url}`; + (this.checks.push({ + id: `incoming-request`, + name: `IncomingRequest`, + description: r, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + method: `GET`, + url: e.url, + headers: e.headers, + connectionCount: this.getConnectionCount + } + }), + n && this.lastEventIds.push(n), + this.handleGetSSEStream(e, t)); + } else + e.method === `POST` + ? this.handlePostRequest(e, t) + : (t.writeHead(405), t.end(`Method Not Allowed`)); + } + handleGetSSEStream(e, t) { + (t.writeHead(200, { + 'Content-Type': `text/event-stream`, + 'Cache-Control': `no-cache`, + Connection: `keep-alive`, + 'mcp-session-id': this.sessionId + }), + this.eventIdCounter++); + let n = `event-${this.eventIdCounter}`, + r = `id: ${n}\nretry: ${this.retryValue}\ndata: \n\n`; + if ( + (t.write(r), + this.checks.push({ + id: `outgoing-sse-event`, + name: `OutgoingSseEvent`, + description: `Sent SSE priming event on GET stream (id: ${n}, retry: ${this.retryValue}ms)`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + eventId: n, + retryMs: this.retryValue, + eventType: `priming`, + raw: r + } + }), + (this.getResponseStream = t), + this.pendingToolCallId !== null) + ) { + let e = { + jsonrpc: `2.0`, + id: this.pendingToolCallId, + result: { + content: [ + { + type: `text`, + text: `Reconnection test completed successfully` + } + ] + } + }, + n = `event-${++this.eventIdCounter}`, + r = `event: message\nid: ${n}\ndata: ${JSON.stringify(e)}\n\n`; + (t.write(r), + this.checks.push({ + id: `outgoing-sse-event`, + name: `OutgoingSseEvent`, + description: `Sent tool response on GET stream after reconnection (id: ${n})`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + eventId: n, + eventType: `message`, + jsonrpcId: this.pendingToolCallId, + body: e, + raw: r + } + }), + (this.pendingToolCallId = null)); + } + } + handlePostRequest(e, t) { + let n = ``; + (e.on(`data`, (e) => { + n += e.toString(); + }), + e.on(`end`, () => { + try { + let r = JSON.parse(n); + (this.checks.push({ + id: `incoming-request`, + name: `IncomingRequest`, + description: `Received POST request for ${e.url} (method: ${r.method})`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + method: `POST`, + url: e.url, + jsonrpcMethod: r.method, + jsonrpcId: r.id + } + }), + r.method === `initialize` + ? this.handleInitialize(e, t, r) + : r.method === `tools/list` + ? this.handleToolsList(t, r) + : r.method === `tools/call` + ? this.handleToolsCall(t, r) + : r.id === void 0 + ? (t.writeHead(202), t.end()) + : (t.writeHead(200, { + 'Content-Type': `application/json`, + 'mcp-session-id': this.sessionId + }), + t.end( + JSON.stringify({ + jsonrpc: `2.0`, + id: r.id, + result: {} + }) + ))); + } catch (e) { + (t.writeHead(400, { 'Content-Type': `application/json` }), + t.end( + JSON.stringify({ + jsonrpc: `2.0`, + error: { code: -32700, message: `Parse error: ${e}` } + }) + )); + } + })); + } + handleInitialize(e, t, n) { + t.writeHead(200, { + 'Content-Type': `application/json`, + 'mcp-session-id': this.sessionId + }); + let r = { + jsonrpc: `2.0`, + id: n.id, + result: { + protocolVersion: `2025-03-26`, + serverInfo: { name: `sse-retry-test-server`, version: `1.0.0` }, + capabilities: { tools: {} } + } + }; + (t.end(JSON.stringify(r)), + this.checks.push({ + id: `outgoing-response`, + name: `OutgoingResponse`, + description: `Sent initialize response`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { jsonrpcId: n.id, body: r } + })); + } + handleToolsList(e, t) { + e.writeHead(200, { + 'Content-Type': `application/json`, + 'mcp-session-id': this.sessionId + }); + let n = { + jsonrpc: `2.0`, + id: t.id, + result: { + tools: [ + { + name: `test_reconnection`, + description: `A tool that triggers SSE stream closure to test client reconnection behavior`, + inputSchema: { type: `object`, properties: {}, required: [] } + } + ] + } + }; + (e.end(JSON.stringify(n)), + this.checks.push({ + id: `outgoing-response`, + name: `OutgoingResponse`, + description: `Sent tools/list response`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { jsonrpcId: t.id, body: n } + })); + } + handleToolsCall(e, t) { + ((this.pendingToolCallId = t.id), + e.writeHead(200, { + 'Content-Type': `text/event-stream`, + 'Cache-Control': `no-cache`, + Connection: `keep-alive`, + 'mcp-session-id': this.sessionId + }), + this.eventIdCounter++); + let n = `event-${this.eventIdCounter}`, + r = `id: ${n}\nretry: ${this.retryValue}\ndata: \n\n`; + (e.write(r), + this.checks.push({ + id: `outgoing-sse-event`, + name: `OutgoingSseEvent`, + description: `Sent SSE priming event for tools/call (id: ${n}, retry: ${this.retryValue}ms)`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + eventId: n, + retryMs: this.retryValue, + eventType: `priming`, + raw: r + } + }), + setTimeout(() => { + ((this.toolStreamCloseTime = performance.now()), + this.checks.push({ + id: `outgoing-stream-close`, + name: `OutgoingStreamClose`, + description: `Closed tools/call SSE stream to trigger client reconnection`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + retryMs: this.retryValue, + pendingToolCallId: this.pendingToolCallId + } + }), + e.end()); + }, 50)); + } + generateChecks() { + if (this.getConnectionCount < 1) { + this.checks.push({ + id: `client-sse-graceful-reconnect`, + name: `ClientGracefulReconnect`, + description: `Client reconnects via GET after SSE stream is closed gracefully`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Client did not attempt GET reconnection after stream closure. Client should treat graceful stream close as reconnectable.`, + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + getConnectionCount: this.getConnectionCount, + toolStreamCloseTime: this.toolStreamCloseTime, + retryValue: this.retryValue + } + }); + return; + } + if ( + (this.checks.push({ + id: `client-sse-graceful-reconnect`, + name: `ClientGracefulReconnect`, + description: `Client reconnects via GET after SSE stream is closed gracefully`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { getConnectionCount: this.getConnectionCount } + }), + this.toolStreamCloseTime !== null && this.getReconnectionTime !== null) + ) { + let e = this.getReconnectionTime - this.toolStreamCloseTime, + t = this.retryValue - this.EARLY_TOLERANCE, + n = this.retryValue + this.LATE_TOLERANCE, + r = e < t, + i = e > n, + a = e > this.retryValue * this.VERY_LATE_MULTIPLIER, + o = !r && !i, + s = `SUCCESS`, + c; + (r + ? ((s = `FAILURE`), + (c = `Client reconnected too early (${e.toFixed(0)}ms instead of ${this.retryValue}ms). Client MUST respect the retry field and wait the specified time.`)) + : a + ? ((s = `FAILURE`), + (c = `Client reconnected very late (${e.toFixed(0)}ms instead of ${this.retryValue}ms). Client appears to be ignoring the retry field and using its own backoff strategy.`)) + : i && + ((s = `WARNING`), + (c = `Client reconnected slightly late (${e.toFixed(0)}ms instead of ${this.retryValue}ms). This is acceptable but may indicate network delays.`)), + this.checks.push({ + id: `client-sse-retry-timing`, + name: `ClientRespectsRetryField`, + description: `Client MUST respect the retry field, waiting the given number of milliseconds before attempting to reconnect`, + status: s, + timestamp: new Date().toISOString(), + errorMessage: c, + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + expectedRetryMs: this.retryValue, + actualDelayMs: Math.round(e), + minAcceptableMs: t, + maxAcceptableMs: n, + veryLateThresholdMs: this.retryValue * this.VERY_LATE_MULTIPLIER, + earlyToleranceMs: this.EARLY_TOLERANCE, + lateToleranceMs: this.LATE_TOLERANCE, + withinTolerance: o, + tooEarly: r, + slightlyLate: i, + veryLate: a, + getConnectionCount: this.getConnectionCount + } + })); + } else + this.checks.push({ + id: `client-sse-retry-timing`, + name: `ClientRespectsRetryField`, + description: `Client MUST respect the retry field timing`, + status: `WARNING`, + timestamp: new Date().toISOString(), + errorMessage: `Could not measure timing - tool stream close time or GET reconnection time not recorded`, + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + toolStreamCloseTime: this.toolStreamCloseTime, + getReconnectionTime: this.getReconnectionTime + } + }); + let e = this.lastEventIds.length > 0 && this.lastEventIds[0] !== void 0; + this.checks.push({ + id: `client-sse-last-event-id`, + name: `ClientSendsLastEventId`, + description: `Client SHOULD send Last-Event-ID header on reconnection for resumability`, + status: e ? `SUCCESS` : `WARNING`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + hasLastEventId: e, + lastEventIds: this.lastEventIds, + getConnectionCount: this.getConnectionCount + }, + errorMessage: e + ? void 0 + : `Client did not send Last-Event-ID header on reconnection. This is a SHOULD requirement for resumability.` + }); + } + }; +async function D(e) { + let t = new b( + { name: `conformance-test-client`, version: `1.0.0` }, + { capabilities: { sampling: {}, elicitation: {} } } + ), + n = new x(new URL(e)); + return ( + await t.connect(n), + { + client: t, + close: async () => { + await t.close(); + } + } + ); +} +var pe = class { + constructor(e) { + ((this.loggingNotifications = []), + (this.progressNotifications = []), + e.setNotificationHandler(g, (e) => { + this.loggingNotifications.push(e); + }), + e.setNotificationHandler(v, (e) => { + this.progressNotifications.push(e); + })); + } + getLoggingNotifications() { + return this.loggingNotifications; + } + getProgressNotifications() { + return this.progressNotifications; + } + getNotifications() { + return this.loggingNotifications; + } + }, + me = class { + constructor() { + ((this.name = `server-initialize`), + (this.description = `Test basic server initialization handshake. + +**Server Implementation Requirements:** + +**Endpoint**: \`initialize\` + +**Requirements**: +- Accept \`initialize\` request with client info and capabilities +- Return valid initialize response with server info, protocol version, and capabilities +- Accept \`initialized\` notification from client after handshake + +This test verifies the server can complete the two-phase initialization handshake successfully.`)); + } + async run(e) { + let t = []; + try { + let n = await D(e); + (t.push({ + id: `server-initialize`, + name: `ServerInitialize`, + description: `Server responds to initialize request with valid structure`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `MCP-Initialize`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization` + } + ], + details: { serverUrl: e, connected: !0 } + }), + await n.close()); + } catch (e) { + t.push({ + id: `server-initialize`, + name: `ServerInitialize`, + description: `Server responds to initialize request with valid structure`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed to initialize: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Initialize`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization` + } + ] + }); + } + return t; + } + }, + he = class { + constructor() { + ((this.name = `logging-set-level`), + (this.description = + 'Test setting logging level.\n\n**Server Implementation Requirements:**\n\n**Endpoint**: `logging/setLevel`\n\n**Requirements**:\n- Accept log level setting\n- Filter subsequent log notifications based on level\n- Return empty object `{}`\n\n**Log Levels** (in order of severity):\n- `debug`\n- `info`\n- `notice`\n- `warning`\n- `error`\n- `critical`\n- `alert`\n- `emergency`')); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.setLoggingLevel(`info`), + i = []; + (r && + Object.keys(r).length > 0 && + i.push(`Expected empty object {} response`), + t.push({ + id: `logging-set-level`, + name: `LoggingSetLevel`, + description: `Server accepts logging level setting`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Logging`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging` + } + ], + details: { result: r } + }), + await n.close()); + } catch (e) { + t.push({ + id: `logging-set-level`, + name: `LoggingSetLevel`, + description: `Server accepts logging level setting`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Logging`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging` + } + ] + }); + } + return t; + } + }, + ge = class { + constructor() { + ((this.name = `ping`), + (this.description = `Test ping utility for connection health check. + +**Server Implementation Requirements:** + +**Endpoint**: \`ping\` + +**Requirements**: +- Accept ping request with no parameters +- Respond promptly with empty object \`{}\` + +**Request Format**: + +\`\`\`json +{ + "jsonrpc": "2.0", + "id": "123", + "method": "ping" +} +\`\`\` + +**Response Format**: + +\`\`\`json +{ + "jsonrpc": "2.0", + "id": "123", + "result": {} +} +\`\`\` + +**Implementation Note**: The ping utility allows either party to verify that their counterpart is still responsive and the connection is alive.`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.ping(), + i = []; + (r && + Object.keys(r).length > 0 && + i.push(`Expected empty object {} response`), + t.push({ + id: `ping`, + name: `Ping`, + description: `Server responds to ping requests`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Ping`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping` + } + ], + details: { result: r } + }), + await n.close()); + } catch (e) { + t.push({ + id: `ping`, + name: `Ping`, + description: `Server responds to ping requests`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Ping`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping` + } + ] + }); + } + return t; + } + }, + _e = class { + constructor() { + ((this.name = `completion-complete`), + (this.description = `Test completion endpoint. + +**Server Implementation Requirements:** + +**Endpoint**: \`completion/complete\` + +**Requirements**: +- Accept completion requests for prompt or resource template arguments +- Provide contextual suggestions based on partial input +- Return array of completion values ranked by relevance + +**Request Format**: + +\`\`\`json +{ + "method": "completion/complete", + "params": { + "ref": { + "type": "ref/prompt", + "name": "test_prompt_with_arguments" + }, + "argument": { + "name": "arg1", + "value": "par" + } + } +} +\`\`\` + +**Response Format**: + +\`\`\`json +{ + "completion": { + "values": ["paris", "park", "party"], + "total": 150, + "hasMore": false + } +} +\`\`\` + +**Implementation Note**: For conformance testing, completion support can be minimal or return empty arrays. The capability just needs to be declared and the endpoint must respond correctly.`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.complete({ + ref: { type: `ref/prompt`, name: `test_prompt_with_arguments` }, + argument: { name: `arg1`, value: `test` } + }), + i = []; + (r.completion + ? (r.completion.values || + i.push(`Missing values array in completion`), + Array.isArray(r.completion.values) || + i.push(`completion.values is not an array`)) + : i.push(`Missing completion field`), + t.push({ + id: `completion-complete`, + name: `CompletionComplete`, + description: `Server responds to completion requests`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Completion`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion` + } + ], + details: { result: r } + }), + await n.close()); + } catch (e) { + t.push({ + id: `completion-complete`, + name: `CompletionComplete`, + description: `Server responds to completion requests`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Completion`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion` + } + ] + }); + } + return t; + } + }, + ve = class { + constructor() { + ((this.name = `tools-list`), + (this.description = `Test listing available tools. + +**Server Implementation Requirements:** + +**Endpoint**: \`tools/list\` + +**Requirements**: +- Return array of all available tools +- Each tool MUST have: + - \`name\` (string) + - \`description\` (string) + - \`inputSchema\` (valid JSON Schema object)`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.listTools(), + i = []; + (r.tools + ? (Array.isArray(r.tools) || i.push(`tools is not an array`), + r.tools.forEach((e, t) => { + (e.name || i.push(`Tool ${t}: missing name`), + e.description || i.push(`Tool ${t}: missing description`), + e.inputSchema || i.push(`Tool ${t}: missing inputSchema`)); + })) + : i.push(`Missing tools array`), + t.push({ + id: `tools-list`, + name: `ToolsList`, + description: `Server lists available tools with valid structure`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Tools-List`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools` + } + ], + details: { + toolCount: r.tools?.length || 0, + tools: r.tools?.map((e) => e.name) + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-list`, + name: `ToolsList`, + description: `Server lists available tools with valid structure`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Tools-List`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools` + } + ] + }); + } + return t; + } + }, + ye = class { + constructor() { + ((this.name = `tools-call-simple-text`), + (this.description = `Test calling a tool that returns simple text. + +**Server Implementation Requirements:** + +Implement tool \`test_simple_text\` with no arguments that returns: + +\`\`\`json +{ + "content": [ + { + "type": "text", + "text": "This is a simple text response for testing." + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.callTool({ name: `test_simple_text` }), + i = [], + a = r.content; + (a || i.push(`Missing content array`), + Array.isArray(a) || i.push(`content is not an array`), + a && a.length === 0 && i.push(`content array is empty`)); + let o = a && a.find((e) => e.type === `text`); + (o || i.push(`No text content found`), + o && !o.text && i.push(`Text content missing text field`), + t.push({ + id: `tools-call-simple-text`, + name: `ToolsCallSimpleText`, + description: `Tool returns simple text content`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ], + details: { result: r } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-simple-text`, + name: `ToolsCallSimpleText`, + description: `Tool returns simple text content`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ] + }); + } + return t; + } + }, + be = class { + constructor() { + ((this.name = `tools-call-image`), + (this.description = `Test calling a tool that returns image content. + +**Server Implementation Requirements:** + +Implement tool \`test_image_content\` with no arguments that returns: + +\`\`\`json +{ + "content": [ + { + "type": "image", + "data": "", + "mimeType": "image/png" + } + ] +} +\`\`\` + +**Implementation Note**: Use a minimal test image (e.g., 1x1 red pixel PNG)`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.callTool({ + name: `test_image_content`, + arguments: {} + }), + i = [], + a = r.content; + a || i.push(`Missing content array`); + let o = a && a.find((e) => e.type === `image`); + (o || i.push(`No image content found`), + o && !o.data && i.push(`Image content missing data field`), + o && !o.mimeType && i.push(`Image content missing mimeType`), + t.push({ + id: `tools-call-image`, + name: `ToolsCallImage`, + description: `Tool returns image content`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ], + details: { mimeType: o?.mimeType, hasData: !!o?.data } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-image`, + name: `ToolsCallImage`, + description: `Tool returns image content`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ] + }); + } + return t; + } + }, + xe = class { + constructor() { + ((this.name = `tools-call-mixed-content`), + (this.description = `Test tool returning multiple content types. + +**Server Implementation Requirements:** + +Implement tool \`test_multiple_content_types\` with no arguments that returns: + +\`\`\`json +{ + "content": [ + { + "type": "text", + "text": "Multiple content types test:" + }, + { + "type": "image", + "data": "", + "mimeType": "image/png" + }, + { + "type": "resource", + "resource": { + "uri": "test://mixed-content-resource", + "mimeType": "application/json", + "text": "{"test":"data","value":123}" + } + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.callTool({ + name: `test_multiple_content_types`, + arguments: {} + }), + i = [], + a = r.content; + (a || i.push(`Missing content array`), + a && a.length < 2 && i.push(`Expected multiple content items`)); + let o = a && a.some((e) => e.type === `text`), + s = a && a.some((e) => e.type === `image`), + c = a && a.some((e) => e.type === `resource`); + (o || i.push(`Missing text content`), + s || i.push(`Missing image content`), + c || i.push(`Missing resource content`), + t.push({ + id: `tools-call-mixed-content`, + name: `ToolsCallMixedContent`, + description: `Tool returns multiple content types`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ], + details: { + contentCount: a ? a.length : 0, + contentTypes: a ? a.map((e) => e.type) : [] + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-mixed-content`, + name: `ToolsCallMixedContent`, + description: `Tool returns multiple content types`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ] + }); + } + return t; + } + }, + Se = class { + constructor() { + ((this.name = `tools-call-with-logging`), + (this.description = `Test tool that sends log messages during execution. + +**Server Implementation Requirements:** + +Implement tool \`test_tool_with_logging\` with no arguments. + +**Behavior**: During execution, send 3 log notifications at info level: +1. "Tool execution started" +2. "Tool processing data" (after ~50ms delay) +3. "Tool execution completed" (after another ~50ms delay) + +**Returns**: Text content confirming execution + +**Implementation Note**: The delays are important to test that clients can receive multiple log notifications during tool execution`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = new pe(n.client); + (await n.client.setLoggingLevel(`debug`), + await n.client.callTool({ + name: `test_tool_with_logging`, + arguments: {} + }), + await new Promise((e) => setTimeout(e, 200))); + let i = r.getNotifications(), + a = []; + (i.length === 0 + ? a.push(`No log notifications received`) + : i.length < 3 && + a.push(`Expected at least 3 log messages, got ${i.length}`), + t.push({ + id: `tools-call-with-logging`, + name: `ToolsCallWithLogging`, + description: `Tool sends log messages during execution`, + status: a.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: a.length > 0 ? a.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Logging`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging` + } + ], + details: { logCount: i.length, logs: i.map((e) => e.params) } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-with-logging`, + name: `ToolsCallWithLogging`, + description: `Tool sends log messages during execution`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Logging`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging` + } + ] + }); + } + return t; + } + }, + Ce = class { + constructor() { + ((this.name = `tools-call-error`), + (this.description = `Test tool error reporting. + +**Server Implementation Requirements:** + +Implement tool \`test_error_handling\` with no arguments. + +**Behavior**: Always throw an error + +**Returns**: JSON-RPC response with \`isError: true\` + +\`\`\`json +{ + "isError": true, + "content": [ + { + "type": "text", + "text": "This tool intentionally returns an error for testing" + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.callTool({ + name: `test_error_handling`, + arguments: {} + }), + i = r.isError === !0, + a = r.content && r.content.length > 0 && r.content[0].text; + (t.push({ + id: `tools-call-error`, + name: `ToolsCallError`, + description: `Tool returns error correctly`, + status: i && a ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i + ? a + ? void 0 + : `Error result missing error message` + : `Tool did not return isError: true`, + specReferences: [ + { + id: `MCP-Error-Handling`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle` + } + ], + details: { result: r } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-error`, + name: `ToolsCallError`, + description: `Tool returns error correctly`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Error-Handling`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle` + } + ] + }); + } + return t; + } + }, + we = class { + constructor() { + ((this.name = `tools-call-with-progress`), + (this.description = `Test tool that reports progress notifications. + +**Server Implementation Requirements:** + +Implement tool \`test_tool_with_progress\` with no arguments. + +**Behavior**: If \`_meta.progressToken\` is provided in request: +- Send progress notification: \`0/100\` +- Wait ~50ms +- Send progress notification: \`50/100\` +- Wait ~50ms +- Send progress notification: \`100/100\` + +If no progress token provided, just execute with delays. + +**Returns**: Text content confirming execution + +**Progress Notification Format**: + +\`\`\`json +{ + "method": "notifications/progress", + "params": { + "progressToken": "", + "progress": 50, + "total": 100 + } +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = [], + i = await n.client.request( + { + method: `tools/call`, + params: { + name: `test_tool_with_progress`, + arguments: {}, + _meta: { progressToken: `progress-test-1` } + } + }, + u, + { + onprogress: (e) => { + r.push(e); + } + } + ), + a = []; + if ( + (r.length === 0 + ? a.push(`No progress notifications received`) + : r.length < 3 && + a.push( + `Expected at least 3 progress notifications, got ${r.length}` + ), + r.length >= 3) + ) { + let e = r[0].progress, + t = r[1].progress, + n = r[2].progress; + (e <= t && t <= n) || a.push(`Progress values should be increasing`); + } + (t.push({ + id: `tools-call-with-progress`, + name: `ToolsCallWithProgress`, + description: `Tool reports progress notifications`, + status: a.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: a.length > 0 ? a.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Progress`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress` + } + ], + details: { + progressCount: r.length, + progressNotifications: r.map((e) => e), + result: i + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-with-progress`, + name: `ToolsCallWithProgress`, + description: `Tool reports progress notifications`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Progress`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress` + } + ] + }); + } + return t; + } + }, + Te = class { + constructor() { + ((this.name = `tools-call-sampling`), + (this.description = `Test tool that requests LLM sampling from client. + +**Server Implementation Requirements:** + +Implement tool \`test_sampling\` with argument: +- \`prompt\` (string, required) - The prompt to send to the LLM + +**Behavior**: Request LLM sampling from the client using \`sampling/createMessage\` + +**Sampling Request**: + +\`\`\`json +{ + "method": "sampling/createMessage", + "params": { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "" + } + } + ], + "maxTokens": 100 + } +} +\`\`\` + +**Returns**: Text content with the LLM's response + +\`\`\`json +{ + "content": [ + { + "type": "text", + "text": "LLM response: " + } + ] +} +\`\`\` + +**Implementation Note**: If the client doesn't support sampling (no \`sampling\` capability), return an error.`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = !1; + n.client.setRequestHandler( + d, + async (e) => ( + (r = !0), + { + role: `assistant`, + content: { + type: `text`, + text: `This is a test response from the client` + }, + model: `test-model`, + stopReason: `endTurn` + } + ) + ); + let i = await n.client.callTool({ + name: `test_sampling`, + arguments: { prompt: `Test prompt for sampling` } + }), + a = []; + r || a.push(`Server did not request sampling from client`); + let o = i.content; + ((!o || o.length === 0) && a.push(`Tool did not return content`), + t.push({ + id: `tools-call-sampling`, + name: `ToolsCallSampling`, + description: `Tool requests LLM sampling from client`, + status: a.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: a.length > 0 ? a.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Sampling`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling` + } + ], + details: { samplingRequested: r, result: i } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-sampling`, + name: `ToolsCallSampling`, + description: `Tool requests LLM sampling from client`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Sampling`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling` + } + ] + }); + } + return t; + } + }, + O = class { + constructor() { + ((this.name = `tools-call-elicitation`), + (this.description = `Test tool that requests user input (elicitation) from client. + +**Server Implementation Requirements:** + +Implement tool \`test_elicitation\` with argument: +- \`message\` (string, required) - The message to show the user + +**Behavior**: Request user input from the client using \`elicitation/create\` + +**Elicitation Request**: + +\`\`\`json +{ + "method": "elicitation/create", + "params": { + "message": "", + "requestedSchema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "User's response" + }, + "email": { + "type": "string", + "description": "User's email address" + } + }, + "required": ["username", "email"] + } + } +} +\`\`\` + +**Returns**: Text content with the user's response + +\`\`\`json +{ + "content": [ + { + "type": "text", + "text": "User response: " + } + ] +} +\`\`\` + +**Implementation Note**: If the client doesn't support elicitation (no \`elicitation\` capability), return an error.`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = !1; + n.client.setRequestHandler( + f, + async (e) => ( + (r = !0), + { + action: `accept`, + content: { username: `testuser`, email: `test@example.com` } + } + ) + ); + let i = await n.client.callTool({ + name: `test_elicitation`, + arguments: { message: `Please provide your information` } + }), + a = []; + r || a.push(`Server did not request elicitation from client`); + let o = i.content; + ((!o || o.length === 0) && a.push(`Tool did not return content`), + t.push({ + id: `tools-call-elicitation`, + name: `ToolsCallElicitation`, + description: `Tool requests user input from client`, + status: a.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: a.length > 0 ? a.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Elicitation`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation` + } + ], + details: { elicitationRequested: r, result: i } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-elicitation`, + name: `ToolsCallElicitation`, + description: `Tool requests user input from client`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Elicitation`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation` + } + ] + }); + } + return t; + } + }, + Ee = class { + constructor() { + ((this.name = `tools-call-audio`), + (this.description = `Test calling a tool that returns audio content. + +**Server Implementation Requirements:** + +Implement tool \`test_audio_content\` with no arguments that returns: + +\`\`\`json +{ + "content": [ + { + "type": "audio", + "data": "", + "mimeType": "audio/wav" + } + ] +} +\`\`\` + +**Implementation Note**: Use a minimal test audio file`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.callTool({ + name: `test_audio_content`, + arguments: {} + }), + i = [], + a = r.content; + (a || i.push(`Missing content array`), + Array.isArray(a) || i.push(`content is not an array`), + a && a.length === 0 && i.push(`content array is empty`)); + let o = a && a.find((e) => e.type === `audio`); + (o || i.push(`No audio content found`), + o && !o.data && i.push(`Audio content missing data field`), + o && !o.mimeType && i.push(`Audio content missing mimeType field`), + o && + o.mimeType !== `audio/wav` && + i.push(`Expected mimeType 'audio/wav', got '${o.mimeType}'`), + t.push({ + id: `tools-call-audio`, + name: `ToolsCallAudio`, + description: `Tool returns audio content`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ], + details: { + hasAudioContent: !!o, + audioDataLength: o?.data?.length || 0 + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-audio`, + name: `ToolsCallAudio`, + description: `Tool returns audio content`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ] + }); + } + return t; + } + }, + De = class { + constructor() { + ((this.name = `tools-call-embedded-resource`), + (this.description = `Test calling a tool that returns embedded resource content. + +**Server Implementation Requirements:** + +Implement tool \`test_embedded_resource\` with no arguments that returns: + +\`\`\`json +{ + "content": [ + { + "type": "resource", + "resource": { + "uri": "test://embedded-resource", + "mimeType": "text/plain", + "text": "This is an embedded resource content." + } + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.callTool({ + name: `test_embedded_resource`, + arguments: {} + }), + i = [], + a = r.content; + (a || i.push(`Missing content array`), + Array.isArray(a) || i.push(`content is not an array`), + a && a.length === 0 && i.push(`content array is empty`)); + let o = a && a.find((e) => e.type === `resource`); + (o || i.push(`No resource content found`), + o && !o.resource && i.push(`Resource content missing resource field`), + o?.resource && + (o.resource.uri || i.push(`Resource missing uri field`), + o.resource.mimeType || i.push(`Resource missing mimeType field`), + !o.resource.text && + !o.resource.blob && + i.push(`Resource missing both text and blob fields`)), + t.push({ + id: `tools-call-embedded-resource`, + name: `ToolsCallEmbeddedResource`, + description: `Tool returns embedded resource content`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ], + details: { hasResourceContent: !!o, resourceUri: o?.resource?.uri } + }), + await n.close()); + } catch (e) { + t.push({ + id: `tools-call-embedded-resource`, + name: `ToolsCallEmbeddedResource`, + description: `Tool returns embedded resource content`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Tools-Call`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` + } + ] + }); + } + return t; + } + }; +const k = `json_schema_2020_12_tool`, + A = `https://json-schema.org/draft/2020-12/schema`; +var j = class { + constructor() { + ((this.name = `json-schema-2020-12`), + (this.description = `Validates JSON Schema 2020-12 keyword preservation (SEP-1613). + +**Server Implementation Requirements:** + +Implement tool \`${k}\` with inputSchema containing JSON Schema 2020-12 features: + +\`\`\`json +{ + "name": "${k}", + "description": "Tool with JSON Schema 2020-12 features", + "inputSchema": { + "$schema": "${A}", + "type": "object", + "$defs": { + "address": { + "type": "object", + "properties": { + "street": { "type": "string" }, + "city": { "type": "string" } + } + } + }, + "properties": { + "name": { "type": "string" }, + "address": { "$ref": "#/$defs/address" } + }, + "additionalProperties": false + } +} +\`\`\` + +**Verification**: The test verifies that \`$schema\`, \`$defs\`, and \`additionalProperties\` are preserved in the tool listing response.`)); + } + async run(e) { + let t = [], + n = [ + { + id: `SEP-1613`, + url: `https://github.com/modelcontextprotocol/specification/pull/655` + } + ]; + try { + let r = await D(e), + i = await r.client.listTools(), + a = i.tools?.find((e) => e.name === k); + if ( + (t.push({ + id: `json-schema-2020-12-tool-found`, + name: `JsonSchema2020_12ToolFound`, + description: `Server advertises tool '${k}'`, + status: a ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: a + ? void 0 + : `Tool '${k}' not found. Available tools: ${i.tools?.map((e) => e.name).join(`, `) || `none`}`, + specReferences: n, + details: { + toolFound: !!a, + availableTools: i.tools?.map((e) => e.name) || [] + } + }), + !a) + ) + return (await r.close(), t); + let o = a.inputSchema, + s = `$schema` in o, + c = o.$schema, + l = c === A; + t.push({ + id: `json-schema-2020-12-$schema`, + name: `JsonSchema2020_12$Schema`, + description: `inputSchema.$schema field preserved with value '${A}'`, + status: s && l ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: s + ? l + ? void 0 + : `$schema has unexpected value: ${JSON.stringify(c)}` + : `$schema field missing from inputSchema - field was likely stripped`, + specReferences: n, + details: { hasSchema: s, schemaValue: c, expected: A } + }); + let u = `$defs` in o, + d = o.$defs, + f = d && `address` in d; + t.push({ + id: `json-schema-2020-12-$defs`, + name: `JsonSchema2020_12$Defs`, + description: `inputSchema.$defs field preserved with expected structure`, + status: u && f ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: u + ? f + ? void 0 + : `$defs exists but missing expected "address" definition` + : `$defs field missing from inputSchema - field was likely stripped`, + specReferences: n, + details: { + hasDefs: u, + defsKeys: d ? Object.keys(d) : [], + defsValue: d + } + }); + let p = `additionalProperties` in o, + m = o.additionalProperties, + h = m === !1; + (t.push({ + id: `json-schema-2020-12-additionalProperties`, + name: `JsonSchema2020_12AdditionalProperties`, + description: `inputSchema.additionalProperties field preserved`, + status: p && h ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: p + ? h + ? void 0 + : `additionalProperties has unexpected value: ${JSON.stringify(m)}, expected: false` + : `additionalProperties field missing from inputSchema - field was likely stripped`, + specReferences: n, + details: { + hasAdditionalProps: p, + additionalPropsValue: m, + expected: !1 + } + }), + await r.close()); + } catch (e) { + t.push({ + id: `json-schema-2020-12-error`, + name: `JsonSchema2020_12Error`, + description: `JSON Schema 2020-12 conformance test`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: n + }); + } + return t; + } + }, + M = class { + constructor() { + ((this.name = `elicitation-sep1034-defaults`), + (this.description = `Test elicitation with default values for all primitive types (SEP-1034). + +**Server Implementation Requirements:** + +Implement a tool named \`test_elicitation_sep1034_defaults\` (no arguments) that requests \`elicitation/create\` from the client with a schema containing default values for all primitive types: +- \`name\` (string): default "John Doe" +- \`age\` (integer): default 30 +- \`score\` (number): default 95.5 +- \`status\` (string enum: ["active", "inactive", "pending"]): default "active" +- \`verified\` (boolean): default true + +**Returns**: Text content with the elicitation result + +\`\`\`json +{ + "content": [ + { + "type": "text", + "text": "Elicitation completed: action=, content={...}" + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = null; + if ( + (n.client.setRequestHandler( + f, + async (e) => ( + (r = e), + { + action: `accept`, + content: { + name: `Jane Smith`, + age: 25, + score: 88, + status: `inactive`, + verified: !1 + } + } + ) + ), + await n.client.callTool({ + name: `test_elicitation_sep1034_defaults`, + arguments: {} + }), + !r) + ) + return ( + t.push({ + id: `elicitation-sep1034-general`, + name: `ElicitationSEP1034General`, + description: `Server requests elicitation with default values`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Server did not request elicitation from client`, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ] + }), + await n.close(), + t + ); + let i = r.params?.requestedSchema?.properties, + a = []; + (i?.name + ? (i.name.type !== `string` && + a.push(`Expected type "string", got "${i.name.type}"`), + `default` in i.name + ? i.name.default !== `John Doe` && + a.push(`Expected default "John Doe", got "${i.name.default}"`) + : a.push(`Missing default field`)) + : a.push(`Missing string field "name"`), + t.push({ + id: `elicitation-sep1034-string-default`, + name: `ElicitationSEP1034StringDefault`, + description: `String schema includes default value`, + status: a.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: a.length > 0 ? a.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { field: `name`, schema: i?.name } + })); + let o = []; + (i?.age + ? (i.age.type !== `integer` && + o.push(`Expected type "integer", got "${i.age.type}"`), + `default` in i.age + ? i.age.default !== 30 && + o.push(`Expected default 30, got ${i.age.default}`) + : o.push(`Missing default field`)) + : o.push(`Missing integer field "age"`), + t.push({ + id: `elicitation-sep1034-integer-default`, + name: `ElicitationSEP1034IntegerDefault`, + description: `Integer schema includes default value`, + status: o.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: o.length > 0 ? o.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { field: `age`, schema: i?.age } + })); + let s = []; + (i?.score + ? (i.score.type !== `number` && + s.push(`Expected type "number", got "${i.score.type}"`), + `default` in i.score + ? i.score.default !== 95.5 && + s.push(`Expected default 95.5, got ${i.score.default}`) + : s.push(`Missing default field`)) + : s.push(`Missing number field "score"`), + t.push({ + id: `elicitation-sep1034-number-default`, + name: `ElicitationSEP1034NumberDefault`, + description: `Number schema includes default value`, + status: s.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: s.length > 0 ? s.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { field: `score`, schema: i?.score } + })); + let c = []; + (i?.status + ? (i.status.type !== `string` && + c.push(`Expected type "string", got "${i.status.type}"`), + (!i.status.enum || !Array.isArray(i.status.enum)) && + c.push(`Missing or invalid enum array`), + `default` in i.status + ? (i.status.default !== `active` && + c.push( + `Expected default "active", got "${i.status.default}"` + ), + i.status.enum && + !i.status.enum.includes(i.status.default) && + c.push( + `Default value "${i.status.default}" is not a valid enum member` + )) + : c.push(`Missing default field`)) + : c.push(`Missing enum field "status"`), + t.push({ + id: `elicitation-sep1034-enum-default`, + name: `ElicitationSEP1034EnumDefault`, + description: `Enum schema includes valid default value`, + status: c.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: c.length > 0 ? c.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { field: `status`, schema: i?.status } + })); + let l = []; + (i?.verified + ? (i.verified.type !== `boolean` && + l.push(`Expected type "boolean", got "${i.verified.type}"`), + `default` in i.verified + ? i.verified.default !== !0 && + l.push(`Expected default true, got ${i.verified.default}`) + : l.push(`Missing default field`)) + : l.push(`Missing boolean field "verified"`), + t.push({ + id: `elicitation-sep1034-boolean-default`, + name: `ElicitationSEP1034BooleanDefault`, + description: `Boolean schema includes default value`, + status: l.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: l.length > 0 ? l.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ], + details: { field: `verified`, schema: i?.verified } + }), + await n.close()); + } catch (e) { + t.push({ + id: `elicitation-sep1034-general`, + name: `ElicitationSEP1034General`, + description: `Server requests elicitation with default values`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `SEP-1034`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` + } + ] + }); + } + return t; + } + }, + N = class { + constructor() { + ((this.name = `elicitation-sep1330-enums`), + (this.description = `Test elicitation with enum schema improvements (SEP-1330). + +**Server Implementation Requirements:** + +Implement a tool named \`test_elicitation_sep1330_enums\` (no arguments) that requests \`elicitation/create\` from the client with a schema containing all 5 enum variants: + +1. **Untitled single-select**: \`{ type: "string", enum: ["option1", "option2", "option3"] }\` +2. **Titled single-select**: \`{ type: "string", oneOf: [{ const: "value1", title: "First Option" }, ...] }\` +3. **Legacy titled (deprecated)**: \`{ type: "string", enum: ["opt1", "opt2", "opt3"], enumNames: ["Option One", "Option Two", "Option Three"] }\` +4. **Untitled multi-select**: \`{ type: "array", items: { type: "string", enum: ["option1", "option2", "option3"] } }\` +5. **Titled multi-select**: \`{ type: "array", items: { anyOf: [{ const: "value1", title: "First Choice" }, ...] } }\` + +**Returns**: Text content with the elicitation result + +\`\`\`json +{ + "content": [ + { + "type": "text", + "text": "Elicitation completed: action=, content={...}" + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = null; + if ( + (n.client.setRequestHandler( + f, + async (e) => ( + (r = e), + { + action: `accept`, + content: { + untitledSingle: `option1`, + titledSingle: `value1`, + legacyEnum: `opt1`, + untitledMulti: [`option1`, `option2`], + titledMulti: [`value1`, `value2`] + } + } + ) + ), + await n.client.callTool({ + name: `test_elicitation_sep1330_enums`, + arguments: {} + }), + !r) + ) + return ( + t.push({ + id: `elicitation-sep1330-general`, + name: `ElicitationSEP1330General`, + description: `Server requests elicitation with enum schemas`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Server did not request elicitation from client`, + specReferences: [ + { + id: `SEP-1330`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` + } + ] + }), + await n.close(), + t + ); + let i = r.params?.requestedSchema?.properties, + a = []; + (i?.untitledSingle + ? (i.untitledSingle.type !== `string` && + a.push(`Expected type "string", got "${i.untitledSingle.type}"`), + (!i.untitledSingle.enum || !Array.isArray(i.untitledSingle.enum)) && + a.push(`Missing or invalid enum array`), + i.untitledSingle.oneOf && + a.push(`Untitled enum should not have oneOf property`), + i.untitledSingle.enumNames && + a.push(`Untitled enum should not have enumNames property`)) + : a.push( + `Missing untitled single-select enum field "untitledSingle"` + ), + t.push({ + id: `elicitation-sep1330-untitled-single`, + name: `ElicitationSEP1330UntitledSingle`, + description: `Untitled single-select enum schema uses enum array`, + status: a.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: a.length > 0 ? a.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1330`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` + } + ], + details: { field: `untitledSingle`, schema: i?.untitledSingle } + })); + let o = []; + (i?.titledSingle + ? (i.titledSingle.type !== `string` && + o.push(`Expected type "string", got "${i.titledSingle.type}"`), + !i.titledSingle.oneOf || !Array.isArray(i.titledSingle.oneOf) + ? o.push(`Missing or invalid oneOf array for titled enum`) + : i.titledSingle.oneOf.filter( + (e) => + typeof e.const != `string` || typeof e.title != `string` + ).length > 0 && + o.push( + `oneOf items must have "const" (string) and "title" (string) properties` + ), + i.titledSingle.enum && + o.push(`Titled enum should use oneOf instead of enum array`)) + : o.push(`Missing titled single-select enum field "titledSingle"`), + t.push({ + id: `elicitation-sep1330-titled-single`, + name: `ElicitationSEP1330TitledSingle`, + description: `Titled single-select enum schema uses oneOf with const/title`, + status: o.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: o.length > 0 ? o.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1330`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` + } + ], + details: { field: `titledSingle`, schema: i?.titledSingle } + })); + let s = []; + (i?.legacyEnum + ? (i.legacyEnum.type !== `string` && + s.push(`Expected type "string", got "${i.legacyEnum.type}"`), + (!i.legacyEnum.enum || !Array.isArray(i.legacyEnum.enum)) && + s.push(`Missing or invalid enum array`), + !i.legacyEnum.enumNames || !Array.isArray(i.legacyEnum.enumNames) + ? s.push( + `Missing or invalid enumNames array for legacy titled enum` + ) + : i.legacyEnum.enum && + i.legacyEnum.enumNames.length !== i.legacyEnum.enum.length && + s.push( + `enumNames length (${i.legacyEnum.enumNames.length}) must match enum length (${i.legacyEnum.enum.length})` + )) + : s.push(`Missing legacy titled enum field "legacyEnum"`), + t.push({ + id: `elicitation-sep1330-legacy-enumnames`, + name: `ElicitationSEP1330LegacyEnumNames`, + description: `Legacy titled enum schema uses enumNames (deprecated)`, + status: s.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: s.length > 0 ? s.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1330`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` + } + ], + details: { field: `legacyEnum`, schema: i?.legacyEnum } + })); + let c = []; + (i?.untitledMulti + ? (i.untitledMulti.type !== `array` && + c.push(`Expected type "array", got "${i.untitledMulti.type}"`), + i.untitledMulti.items + ? (i.untitledMulti.items.type !== `string` && + c.push( + `Expected items.type "string", got "${i.untitledMulti.items.type}"` + ), + (!i.untitledMulti.items.enum || + !Array.isArray(i.untitledMulti.items.enum)) && + c.push(`Missing or invalid items.enum array`), + i.untitledMulti.items.anyOf && + c.push( + `Untitled multi-select should use items.enum, not items.anyOf` + )) + : c.push(`Missing items property for array type`)) + : c.push(`Missing untitled multi-select enum field "untitledMulti"`), + t.push({ + id: `elicitation-sep1330-untitled-multi`, + name: `ElicitationSEP1330UntitledMulti`, + description: `Untitled multi-select enum schema uses array with items.enum`, + status: c.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: c.length > 0 ? c.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1330`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` + } + ], + details: { field: `untitledMulti`, schema: i?.untitledMulti } + })); + let l = []; + (i?.titledMulti + ? (i.titledMulti.type !== `array` && + l.push(`Expected type "array", got "${i.titledMulti.type}"`), + i.titledMulti.items + ? (!i.titledMulti.items.anyOf || + !Array.isArray(i.titledMulti.items.anyOf) + ? l.push( + `Missing or invalid items.anyOf array for titled multi-select` + ) + : i.titledMulti.items.anyOf.filter( + (e) => + typeof e.const != `string` || typeof e.title != `string` + ).length > 0 && + l.push( + `items.anyOf entries must have "const" (string) and "title" (string) properties` + ), + i.titledMulti.items.enum && + l.push( + `Titled multi-select should use items.anyOf, not items.enum` + )) + : l.push(`Missing items property for array type`)) + : l.push(`Missing titled multi-select enum field "titledMulti"`), + t.push({ + id: `elicitation-sep1330-titled-multi`, + name: `ElicitationSEP1330TitledMulti`, + description: `Titled multi-select enum schema uses array with items.anyOf`, + status: l.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: l.length > 0 ? l.join(`; `) : void 0, + specReferences: [ + { + id: `SEP-1330`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` + } + ], + details: { field: `titledMulti`, schema: i?.titledMulti } + }), + await n.close()); + } catch (e) { + t.push({ + id: `elicitation-sep1330-general`, + name: `ElicitationSEP1330General`, + description: `Server requests elicitation with enum schemas`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `SEP-1330`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` + } + ] + }); + } + return t; + } + }; +function Oe(e) { + return async (t, n) => { + let r = n.method || `GET`, + i = `Sending ${r} request`; + if (n.body) + try { + let e = JSON.parse(n.body); + e.method && (i = `Sending ${r} ${e.method}`); + } catch {} + e.push({ + id: `outgoing-request`, + name: `OutgoingRequest`, + description: i, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + method: r, + url: t, + headers: n.headers, + body: n.body ? JSON.parse(n.body) : void 0 + } + }); + let a = await fetch(t, n), + o = {}; + return ( + a.headers.forEach((e, t) => { + o[t] = e; + }), + e.push({ + id: `incoming-response`, + name: `IncomingResponse`, + description: `Received ${a.status} response for ${r}`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { statusCode: a.status, headers: o } + }), + a + ); + }; +} +var P = class { + constructor() { + ((this.name = `server-sse-polling`), + (this.description = `Test server SSE polling via test_reconnection tool that closes stream mid-call (SEP-1699)`)); + } + async run(e) { + let t = [], + n, + r, + i; + try { + ((r = new b( + { name: `conformance-test-client`, version: `1.0.0` }, + { capabilities: { sampling: {}, elicitation: {} } } + )), + (i = new x(new URL(e))), + await r.connect(i), + (n = i.sessionId), + n || + t.push({ + id: `server-sse-polling-session`, + name: `ServerSSEPollingSession`, + description: `Server provides session ID for SSE polling tests`, + status: `WARNING`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + message: `Server did not provide session ID - SSE polling tests may not work correctly` + } + })); + let a = Oe(t), + o = await a(e, { + method: `POST`, + headers: { + 'Content-Type': `application/json`, + Accept: `text/event-stream, application/json`, + ...(n && { 'mcp-session-id': n }), + 'mcp-protocol-version': `2025-03-26` + }, + body: JSON.stringify({ + jsonrpc: `2.0`, + id: 1, + method: `tools/call`, + params: { name: `test_reconnection`, arguments: {} } + }) + }); + if (!o.ok) + return o.status === 400 || o.status === 404 + ? (t.push({ + id: `server-sse-test-reconnection-tool`, + name: `ServerTestReconnectionTool`, + description: `Server implements test_reconnection tool for SSE polling tests`, + status: `WARNING`, + timestamp: new Date().toISOString(), + errorMessage: `Server does not implement test_reconnection tool (HTTP ${o.status}). This tool is recommended for testing SSE polling behavior.`, + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ] + }), + t) + : (t.push({ + id: `server-sse-post-request`, + name: `ServerSSEPostRequest`, + description: `Server accepts POST request with SSE stream response`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Server returned HTTP ${o.status}`, + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ] + }), + t); + let s = o.headers.get(`content-type`); + if (!s?.includes(`text/event-stream`)) + return ( + t.push({ + id: `server-sse-content-type`, + name: `ServerSSEContentType`, + description: `Server returns text/event-stream for POST request`, + status: `INFO`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + contentType: s, + message: `Server returned JSON instead of SSE stream - priming event tests not applicable` + } + }), + t + ); + let c = !1, + l = !1, + u = !1, + d = !1, + f, + p, + m = 0, + h = !1; + if (!o.body) + return ( + t.push({ + id: `server-sse-polling-stream`, + name: `ServerSSEPollingStream`, + description: `Server provides SSE response body`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Response body is null`, + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ] + }), + t + ); + let g = o.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough( + new S({ + onRetry: (e) => { + ((d = !0), (f = e)); + } + }) + ) + .getReader(), + _ = setTimeout(() => { + g.cancel(); + }, 1e4); + try { + for (;;) { + let { value: e, done: n } = await g.read(); + if (n) break; + if ((m++, e.id)) { + ((c = !0), (p = e.id)); + let n = e.data === `` || e.data === `{}` || e.data.trim() === ``; + (n && ((l = !0), m === 1 && (u = !0)), + t.push({ + id: `incoming-sse-event`, + name: `IncomingSseEvent`, + description: n + ? `Received SSE priming event (id: ${e.id})` + : `Received SSE event (id: ${e.id})`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + eventId: e.id, + eventType: e.event || `message`, + isPriming: n, + hasRetryField: d, + retryValue: f, + data: e.data + } + })); + } + if (e.data) + try { + let n = JSON.parse(e.data); + if (n.id === 1 && n.result) { + h = !0; + let r = n.result?.isError === !0; + t.push({ + id: `incoming-sse-event`, + name: `IncomingSseEvent`, + description: `Received tool response on POST stream`, + status: r ? `FAILURE` : `INFO`, + timestamp: new Date().toISOString(), + details: { eventId: e.id, body: n }, + ...(r && { errorMessage: `Tool call failed` }) + }); + } + } catch {} + } + } finally { + clearTimeout(_); + } + t.push({ + id: `stream-closed`, + name: `StreamClosed`, + description: `POST SSE stream closed after ${m} event(s)`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { eventCount: m, lastEventId: p, receivedToolResponse: h } + }); + let v = `SUCCESS`, + y; + if ( + (l + ? u || + ((v = `WARNING`), + (y = `Priming event was not sent first. It should be sent immediately when the SSE stream is established.`)) + : ((v = `WARNING`), + (y = `Server did not send priming event with id and empty data on POST SSE stream. This is recommended for resumability.`)), + t.push({ + id: `server-sse-priming-event`, + name: `ServerSendsPrimingEvent`, + description: `Server SHOULD send priming event with id and empty data on POST SSE streams`, + status: v, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + hasPrimingEvent: l, + primingEventIsFirst: u, + hasEventId: c, + lastEventId: p, + eventCount: m + }, + errorMessage: y + }), + t.push({ + id: `server-sse-retry-field`, + name: `ServerSendsRetryField`, + description: `Server SHOULD send retry field to control client reconnection timing`, + status: d ? `SUCCESS` : `WARNING`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { hasRetryField: d, retryValue: f }, + errorMessage: d + ? void 0 + : `Server did not send retry field. This is recommended for controlling client reconnection timing.` + }), + !h && p && n) + ) { + let r = await a(e, { + method: `GET`, + headers: { + Accept: `text/event-stream`, + 'mcp-session-id': n, + 'mcp-protocol-version': `2025-03-26`, + 'last-event-id': p + } + }); + if (r.ok && r.body) { + let e = r.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new S()) + .getReader(), + n = setTimeout(() => { + e.cancel(); + }, 5e3); + try { + for (;;) { + let { value: n, done: r } = await e.read(); + if (r) break; + if ( + (t.push({ + id: `incoming-sse-event`, + name: `IncomingSseEvent`, + description: `Received SSE event on GET reconnection stream (id: ${n.id || `none`})`, + status: `INFO`, + timestamp: new Date().toISOString(), + details: { + eventId: n.id, + eventType: n.event || `message`, + data: n.data + } + }), + n.data) + ) + try { + let e = JSON.parse(n.data); + if (e.id === 1 && e.result) { + h = !0; + break; + } + } catch {} + } + } finally { + clearTimeout(n); + } + t.push({ + id: `server-sse-disconnect-resume`, + name: `ServerDisconnectResume`, + description: `Server closes SSE stream mid-call and resumes after client reconnects with Last-Event-ID`, + status: h ? `SUCCESS` : `WARNING`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + lastEventIdUsed: p, + receivedToolResponse: h, + message: h + ? `Successfully received tool response after reconnection` + : `Tool response not received after reconnection` + }, + errorMessage: h + ? void 0 + : `Server did not send tool response after client reconnected with Last-Event-ID` + }); + } else + r.status === 405 + ? t.push({ + id: `server-sse-disconnect-resume`, + name: `ServerDisconnectResume`, + description: `Server supports GET reconnection with Last-Event-ID`, + status: `INFO`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + statusCode: r.status, + message: `Server does not support standalone GET SSE endpoint (405 Method Not Allowed)` + } + }) + : t.push({ + id: `server-sse-disconnect-resume`, + name: `ServerDisconnectResume`, + description: `Server supports GET reconnection with Last-Event-ID`, + status: `WARNING`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + statusCode: r.status, + lastEventIdUsed: p, + message: `Server returned ${r.status} for GET request with Last-Event-ID` + }, + errorMessage: `Server did not accept reconnection with Last-Event-ID (HTTP ${r.status})` + }); + } else + h + ? t.push({ + id: `server-sse-disconnect-resume`, + name: `ServerDisconnectResume`, + description: `Server closes SSE stream mid-call and resumes after reconnection`, + status: `INFO`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + receivedToolResponse: !0, + message: `Tool response received on initial POST stream - server did not disconnect mid-call. The test_reconnection tool should close the stream before sending the result.` + } + }) + : t.push({ + id: `server-sse-disconnect-resume`, + name: `ServerDisconnectResume`, + description: `Server closes SSE stream mid-call and resumes after reconnection`, + status: `INFO`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + lastEventId: p, + sessionId: n, + message: `Could not test disconnect/resume - no last event ID or session ID available` + } + }); + } catch (e) { + t.push({ + id: `server-sse-polling-error`, + name: `ServerSSEPollingTest`, + description: `Test server SSE polling behavior`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Error: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ] + }); + } finally { + if (r) + try { + await r.close(); + } catch {} + } + return t; + } + }, + ke = class { + constructor() { + ((this.name = `server-sse-multiple-streams`), + (this.description = `Test server supports multiple concurrent POST SSE streams (SEP-1699)`)); + } + async run(e) { + let t = [], + n, + r, + i; + try { + if ( + ((r = new b( + { name: `conformance-test-client`, version: `1.0.0` }, + { capabilities: { sampling: {}, elicitation: {} } } + )), + (i = new x(new URL(e))), + await r.connect(i), + (n = i.sessionId), + !n) + ) + return ( + t.push({ + id: `server-sse-multiple-streams-session`, + name: `ServerSSEMultipleStreamsSession`, + description: `Server provides session ID for multiple streams test`, + status: `WARNING`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + message: `Server did not provide session ID - multiple streams test may not work correctly` + } + }), + t + ); + let a = [], + o = []; + for (let t = 0; t < 3; t++) { + let r = fetch(e, { + method: `POST`, + headers: { + 'Content-Type': `application/json`, + Accept: `text/event-stream, application/json`, + 'mcp-session-id': n, + 'mcp-protocol-version': `2025-03-26` + }, + body: JSON.stringify({ + jsonrpc: `2.0`, + id: 1e3 + t, + method: `tools/list`, + params: {} + }) + }); + o.push(r); + } + let s = await Promise.all(o); + a.push(...s); + let c = a.every((e) => e.ok), + l = a.map((e) => e.status), + u = a.map((e) => e.headers.get(`content-type`)), + d = u.filter((e) => e?.includes(`text/event-stream`)).length; + t.push({ + id: `server-accepts-multiple-post-streams`, + name: `ServerAcceptsMultiplePostStreams`, + description: `Server allows multiple concurrent POST requests (each may return SSE or JSON)`, + status: c ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + numStreamsAttempted: 3, + numStreamsAccepted: l.filter((e) => e === 200).length, + numSseStreams: d, + statuses: l, + contentTypes: u + }, + errorMessage: c + ? void 0 + : `Server rejected some requests. Statuses: ${l.join(`, `)}` + }); + let f = await Promise.all( + a.map(async (e, t) => { + if (!e.headers.get(`content-type`)?.includes(`text/event-stream`)) + return { index: t, type: `json`, skipped: !0 }; + if (!e.ok || !e.body) + return { index: t, type: `sse`, error: `Stream not available` }; + try { + let n = e.body + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new S()) + .getReader(), + r = new Promise((e) => setTimeout(() => e(null), 2e3)), + i = n.read().then(({ value: e }) => e), + a = await Promise.race([i, r]); + return (await n.cancel(), { index: t, type: `sse`, event: a }); + } catch (e) { + return { + index: t, + type: `sse`, + error: e instanceof Error ? e.message : String(e) + }; + } + }) + ), + p = f + .filter((e) => e.type === `sse`) + .filter((e) => !(`error` in e)).length; + d > 0 + ? t.push({ + id: `server-sse-streams-functional`, + name: `ServerSSEStreamsFunctional`, + description: `Multiple POST SSE streams should be functional`, + status: p === d ? `SUCCESS` : p > 0 ? `WARNING` : `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + numSseStreams: d, + functionalSseStreams: p, + results: f + }, + errorMessage: + p < d ? `Only ${p}/${d} SSE streams were functional` : void 0 + }) + : t.push({ + id: `server-sse-streams-functional`, + name: `ServerSSEStreamsFunctional`, + description: `Server returned JSON responses (SSE streams optional)`, + status: `INFO`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ], + details: { + numSseStreams: 0, + message: `Server returned JSON for all requests - SSE streaming is optional`, + results: f + } + }); + } catch (e) { + t.push({ + id: `server-sse-multiple-streams-error`, + name: `ServerSSEMultipleStreamsTest`, + description: `Test server multiple SSE streams behavior`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Error: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `SEP-1699`, + url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` + } + ] + }); + } finally { + if (r) + try { + await r.close(); + } catch {} + } + return t; + } + }, + Ae = class { + constructor() { + ((this.name = `resources-list`), + (this.description = `Test listing available resources. + +**Server Implementation Requirements:** + +**Endpoint**: \`resources/list\` + +**Requirements**: +- Return array of all available **direct resources** (not templates) +- Each resource MUST have: + - \`uri\` (string) + - \`name\` (string) + - \`description\` (string) + - \`mimeType\` (string, optional)`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.listResources(), + i = []; + (r.resources + ? (Array.isArray(r.resources) || i.push(`resources is not an array`), + r.resources.forEach((e, t) => { + (e.uri || i.push(`Resource ${t}: missing uri`), + e.name || i.push(`Resource ${t}: missing name`)); + })) + : i.push(`Missing resources array`), + t.push({ + id: `resources-list`, + name: `ResourcesList`, + description: `Server lists available resources with valid structure`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Resources-List`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources` + } + ], + details: { + resourceCount: r.resources?.length || 0, + resources: r.resources?.map((e) => e.uri) + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `resources-list`, + name: `ResourcesList`, + description: `Server lists available resources with valid structure`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Resources-List`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources` + } + ] + }); + } + return t; + } + }, + je = class { + constructor() { + ((this.name = `resources-read-text`), + (this.description = `Test reading text resource. + +**Server Implementation Requirements:** + +Implement resource \`test://static-text\` that returns: + +\`\`\`json +{ + "contents": [ + { + "uri": "test://static-text", + "mimeType": "text/plain", + "text": "This is the content of the static text resource." + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.readResource({ uri: `test://static-text` }), + i = []; + (r.contents || i.push(`Missing contents array`), + Array.isArray(r.contents) || i.push(`contents is not an array`), + r.contents.length === 0 && i.push(`contents array is empty`)); + let a = r.contents[0]; + (a && + (a.uri || i.push(`Content missing uri`), + a.mimeType || i.push(`Content missing mimeType`), + a.text || i.push(`Content missing text field`)), + t.push({ + id: `resources-read-text`, + name: `ResourcesReadText`, + description: `Read text resource successfully`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Resources-Read`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources` + } + ], + details: { uri: a?.uri, mimeType: a?.mimeType, hasText: !!a?.text } + }), + await n.close()); + } catch (e) { + t.push({ + id: `resources-read-text`, + name: `ResourcesReadText`, + description: `Read text resource successfully`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Resources-Read`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources` + } + ] + }); + } + return t; + } + }, + Me = class { + constructor() { + ((this.name = `resources-read-binary`), + (this.description = `Test reading binary resource. + +**Server Implementation Requirements:** + +Implement resource \`test://static-binary\` that returns: + +\`\`\`json +{ + "contents": [ + { + "uri": "test://static-binary", + "mimeType": "image/png", + "blob": "" + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.readResource({ uri: `test://static-binary` }), + i = []; + (r.contents || i.push(`Missing contents array`), + r.contents.length === 0 && i.push(`contents array is empty`)); + let a = r.contents[0]; + (a && + (a.uri || i.push(`Content missing uri`), + a.mimeType || i.push(`Content missing mimeType`), + a.blob || i.push(`Content missing blob field`)), + t.push({ + id: `resources-read-binary`, + name: `ResourcesReadBinary`, + description: `Read binary resource successfully`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Resources-Read`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources` + } + ], + details: { uri: a?.uri, mimeType: a?.mimeType, hasBlob: !!a?.blob } + }), + await n.close()); + } catch (e) { + t.push({ + id: `resources-read-binary`, + name: `ResourcesReadBinary`, + description: `Read binary resource successfully`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Resources-Read`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources` + } + ] + }); + } + return t; + } + }, + Ne = class { + constructor() { + ((this.name = `resources-templates-read`), + (this.description = `Test reading resource from template. + +**Server Implementation Requirements:** + +Implement resource template \`test://template/{id}/data\` that substitutes parameters. + +**Behavior**: When client requests \`test://template/123/data\`, substitute \`{id}\` with \`123\` + +Returns (for \`uri: "test://template/123/data"\`): + +\`\`\`json +{ + "contents": [ + { + "uri": "test://template/123/data", + "mimeType": "application/json", + "text": "{"id":"123","templateTest":true,"data":"Data for ID: 123"}" + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.readResource({ uri: `test://template/123/data` }), + i = []; + (r.contents || i.push(`Missing contents array`), + r.contents.length === 0 && i.push(`contents array is empty`)); + let a = r.contents[0]; + if (a) { + a.uri || i.push(`Content missing uri`); + let e = `text` in a, + t = `blob` in a; + !e && !t && i.push(`Content missing text or blob`); + let n = e ? a.text : t ? `[binary]` : ``; + typeof n == `string` && + !n.includes(`123`) && + i.push(`Parameter substitution not reflected in content`); + } + (t.push({ + id: `resources-templates-read`, + name: `ResourcesTemplateRead`, + description: `Read resource from template with parameter substitution`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Resources-Templates`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates` + } + ], + details: { + uri: a?.uri, + content: a ? (`text` in a ? a.text : a.blob) : void 0 + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `resources-templates-read`, + name: `ResourcesTemplateRead`, + description: `Read resource from template with parameter substitution`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Resources-Templates`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates` + } + ] + }); + } + return t; + } + }, + Pe = class { + constructor() { + ((this.name = `resources-subscribe`), + (this.description = `Test subscribing to resource updates. + +**Server Implementation Requirements:** + +**Endpoint**: \`resources/subscribe\` + +**Requirements**: +- Accept subscription request with URI +- Track subscribed URIs +- Return empty object \`{}\` + +Example request: + +\`\`\`json +{ + "method": "resources/subscribe", + "params": { + "uri": "test://watched-resource" + } +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e); + (await n.client.subscribeResource({ uri: `test://watched-resource` }), + t.push({ + id: `resources-subscribe`, + name: `ResourcesSubscribe`, + description: `Subscribe to resource successfully`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `MCP-Resources-Subscribe`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions` + } + ] + }), + await n.close()); + } catch (e) { + t.push({ + id: `resources-subscribe`, + name: `ResourcesSubscribe`, + description: `Subscribe to resource successfully`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Resources-Subscribe`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions` + } + ] + }); + } + return t; + } + }, + Fe = class { + constructor() { + ((this.name = `resources-unsubscribe`), + (this.description = `Test unsubscribing from resource. + +**Server Implementation Requirements:** + +**Endpoint**: \`resources/unsubscribe\` + +**Requirements**: +- Accept unsubscribe request with URI +- Remove URI from subscriptions +- Stop sending update notifications for that URI +- Return empty object \`{}\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e); + (await n.client.subscribeResource({ uri: `test://watched-resource` }), + await n.client.unsubscribeResource({ + uri: `test://watched-resource` + }), + t.push({ + id: `resources-unsubscribe`, + name: `ResourcesUnsubscribe`, + description: `Unsubscribe from resource successfully`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [ + { + id: `MCP-Resources-Subscribe`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/schema#unsubscriberequest` + } + ] + }), + await n.close()); + } catch (e) { + t.push({ + id: `resources-unsubscribe`, + name: `ResourcesUnsubscribe`, + description: `Unsubscribe from resource successfully`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Resources-Subscribe`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions` + } + ] + }); + } + return t; + } + }, + Ie = class { + constructor() { + ((this.name = `prompts-list`), + (this.description = `Test listing available prompts. + +**Server Implementation Requirements:** + +**Endpoint**: \`prompts/list\` + +**Requirements**: +- Return array of all available prompts +- Each prompt MUST have: + - \`name\` (string) + - \`description\` (string) + - \`arguments\` (array, optional) - list of required arguments`)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.listPrompts(), + i = []; + (r.prompts + ? (Array.isArray(r.prompts) || i.push(`prompts is not an array`), + r.prompts.forEach((e, t) => { + (e.name || i.push(`Prompt ${t}: missing name`), + e.description || i.push(`Prompt ${t}: missing description`)); + })) + : i.push(`Missing prompts array`), + t.push({ + id: `prompts-list`, + name: `PromptsList`, + description: `Server lists available prompts with valid structure`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Prompts-List`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts` + } + ], + details: { + promptCount: r.prompts?.length || 0, + prompts: r.prompts?.map((e) => e.name) + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `prompts-list`, + name: `PromptsList`, + description: `Server lists available prompts with valid structure`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Prompts-List`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts` + } + ] + }); + } + return t; + } + }, + Le = class { + constructor() { + ((this.name = `prompts-get-simple`), + (this.description = `Test getting a simple prompt without arguments. + +**Server Implementation Requirements:** + +Implement a prompt named \`test_simple_prompt\` with no arguments that returns: + +\`\`\`json +{ + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "This is a simple prompt for testing." + } + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.getPrompt({ name: `test_simple_prompt` }), + i = []; + (r.messages || i.push(`Missing messages array`), + Array.isArray(r.messages) || i.push(`messages is not an array`), + r.messages.length === 0 && i.push(`messages array is empty`), + r.messages.forEach((e, t) => { + (e.role || i.push(`Message ${t}: missing role`), + e.content || i.push(`Message ${t}: missing content`)); + }), + t.push({ + id: `prompts-get-simple`, + name: `PromptsGetSimple`, + description: `Get simple prompt successfully`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Prompts-Get`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts` + } + ], + details: { messageCount: r.messages?.length || 0 } + }), + await n.close()); + } catch (e) { + t.push({ + id: `prompts-get-simple`, + name: `PromptsGetSimple`, + description: `Get simple prompt successfully`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Prompts-Get`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts` + } + ] + }); + } + return t; + } + }, + Re = class { + constructor() { + ((this.name = `prompts-get-with-args`), + (this.description = `Test parameterized prompt. + +**Server Implementation Requirements:** + +Implement a prompt named \`test_prompt_with_arguments\` with arguments: +- \`arg1\` (string, required) - First test argument +- \`arg2\` (string, required) - Second test argument + +Returns (with args \`{arg1: "hello", arg2: "world"}\`): + +\`\`\`json +{ + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "Prompt with arguments: arg1='hello', arg2='world'" + } + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.getPrompt({ + name: `test_prompt_with_arguments`, + arguments: { arg1: `testValue1`, arg2: `testValue2` } + }), + i = []; + (r.messages || i.push(`Missing messages array`), + r.messages.length === 0 && i.push(`messages array is empty`)); + let a = JSON.stringify(r.messages); + (a.includes(`testValue1`) || i.push(`arg1 not substituted in prompt`), + a.includes(`testValue2`) || i.push(`arg2 not substituted in prompt`), + t.push({ + id: `prompts-get-with-args`, + name: `PromptsGetWithArgs`, + description: `Get parameterized prompt with argument substitution`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Prompts-Get`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts` + } + ], + details: { + messageCount: r.messages?.length || 0, + messages: r.messages + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `prompts-get-with-args`, + name: `PromptsGetWithArgs`, + description: `Get parameterized prompt with argument substitution`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Prompts-Get`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts` + } + ] + }); + } + return t; + } + }, + ze = class { + constructor() { + ((this.name = `prompts-get-embedded-resource`), + (this.description = `Test prompt with embedded resource content. + +**Server Implementation Requirements:** + +Implement a prompt named \`test_prompt_with_embedded_resource\` with argument: +- \`resourceUri\` (string, required) - URI of the resource to embed + +Returns: + +\`\`\`json +{ + "messages": [ + { + "role": "user", + "content": { + "type": "resource", + "resource": { + "uri": "", + "mimeType": "text/plain", + "text": "Embedded resource content for testing." + } + } + }, + { + "role": "user", + "content": { + "type": "text", + "text": "Please process the embedded resource above." + } + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.getPrompt({ + name: `test_prompt_with_embedded_resource`, + arguments: { resourceUri: `test://example-resource` } + }), + i = []; + (r.messages || i.push(`Missing messages array`), + r.messages.some( + (e) => + e.content?.type === `resource` || e.content?.resource !== void 0 + ) || i.push(`No embedded resource found in prompt`), + t.push({ + id: `prompts-get-embedded-resource`, + name: `PromptsGetEmbeddedResource`, + description: `Get prompt with embedded resource`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Prompts-Embedded-Resources`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources` + } + ], + details: { + messageCount: r.messages?.length || 0, + messages: r.messages + } + }), + await n.close()); + } catch (e) { + t.push({ + id: `prompts-get-embedded-resource`, + name: `PromptsGetEmbeddedResource`, + description: `Get prompt with embedded resource`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Prompts-Embedded-Resources`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources` + } + ] + }); + } + return t; + } + }, + Be = class { + constructor() { + ((this.name = `prompts-get-with-image`), + (this.description = `Test prompt with image content. + +**Server Implementation Requirements:** + +Implement a prompt named \`test_prompt_with_image\` with no arguments that returns: + +\`\`\`json +{ + "messages": [ + { + "role": "user", + "content": { + "type": "image", + "data": "", + "mimeType": "image/png" + } + }, + { + "role": "user", + "content": { + "type": "text", + "text": "Please analyze the image above." + } + } + ] +} +\`\`\``)); + } + async run(e) { + let t = []; + try { + let n = await D(e), + r = await n.client.getPrompt({ name: `test_prompt_with_image` }), + i = []; + (r.messages || i.push(`Missing messages array`), + r.messages.some( + (e) => + e.content?.type === `image` && + e.content?.data && + e.content?.mimeType + ) || i.push(`No image content found in prompt`), + t.push({ + id: `prompts-get-with-image`, + name: `PromptsGetWithImage`, + description: `Get prompt with image content`, + status: i.length === 0 ? `SUCCESS` : `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: i.length > 0 ? i.join(`; `) : void 0, + specReferences: [ + { + id: `MCP-Prompts-Image`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content` + } + ], + details: { messageCount: r.messages?.length || 0 } + }), + await n.close()); + } catch (e) { + t.push({ + id: `prompts-get-with-image`, + name: `PromptsGetWithImage`, + description: `Get prompt with image content`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, + specReferences: [ + { + id: `MCP-Prompts-Image`, + url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content` + } + ] + }); + } + return t; + } + }; +const F = { + RFC_PRM_DISCOVERY: { + id: `RFC-9728`, + url: `https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1` + }, + RFC_AUTH_SERVER_METADATA_REQUEST: { + id: `RFC-8414-metadata-request`, + url: `https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1` + }, + LEGACY_2025_03_26_AUTH_DISCOVERY: { + id: `MCP-2025-03-26-Authorization-metadata-discovery`, + url: `https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#server-metadata-discovery` + }, + LEGACY_2025_03_26_AUTH_URL_FALLBACK: { + id: `MCP-2025-03-26-Authorization-metadata-url-fallback`, + url: `https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#fallbacks-for-servers-without-metadata-discovery` + }, + MCP_PRM_DISCOVERY: { + id: `MCP-2025-06-18-PRM-discovery`, + url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#protected-resource-metadata-discovery-requirements` + }, + MCP_AUTH_DISCOVERY: { + id: `MCP-Authorization-metadata-discovery`, + url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#authorization-server-metadata-discovery` + }, + MCP_DCR: { + id: `MCP-Dynamic-client-registration`, + url: `https://modelcontextprotocol.io/specification/draft/basic/client#dynamic-client-registration` + }, + OAUTH_2_1_AUTHORIZATION_ENDPOINT: { + id: `OAUTH-2.1-authorization-endpoint`, + url: `https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-authorization-endpoint` + }, + OAUTH_2_1_TOKEN: { + id: `OAUTH-2.1-token-request`, + url: `https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request` + }, + MCP_ACCESS_TOKEN_USAGE: { + id: `MCP-Access-token-usage`, + url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#access-token-usage` + }, + MCP_SCOPE_SELECTION_STRATEGY: { + id: `MCP-Scope-selection-strategy`, + url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-selection-strategy` + }, + MCP_SCOPE_CHALLENGE_HANDLING: { + id: `MCP-Scope-challenge-handling`, + url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-challenge-handling` + }, + MCP_AUTH_ERROR_HANDLING: { + id: `MCP-Auth-error-handling`, + url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#error-handling` + }, + MCP_CLIENT_ID_METADATA_DOCUMENTS: { + id: `MCP-Client-ID-Metadata-Documents`, + url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#client-id-metadata-documents` + }, + IETF_CIMD: { + id: `IETF-OAuth-Client-ID-Metadata-Document`, + url: `https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00` + }, + RFC_JWT_CLIENT_AUTH: { + id: `RFC-7523-JWT-Client-Auth`, + url: `https://datatracker.ietf.org/doc/html/rfc7523#section-2.2` + }, + OAUTH_2_1_CLIENT_CREDENTIALS: { + id: `OAUTH-2.1-client-credentials-grant`, + url: `https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#section-4.2` + }, + SEP_1046_CLIENT_CREDENTIALS: { + id: `SEP-1046-Client-Credentials`, + url: `https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx` + } +}; +function I(e, t, n = {}) { + let { + metadataPath: r = `/.well-known/oauth-authorization-server`, + isOpenIdConfiguration: i = !1, + loggingEnabled: a = !0, + routePrefix: o = ``, + scopesSupported: s, + grantTypesSupported: c = [`authorization_code`, `refresh_token`], + tokenEndpointAuthMethodsSupported: l = [`none`], + tokenEndpointAuthSigningAlgValuesSupported: u, + clientIdMetadataDocumentSupported: d, + tokenVerifier: f, + onTokenRequest: p, + onAuthorizationRequest: m, + onRegistrationRequest: h + } = n, + g = [], + _ = { + authorization_endpoint: `${o}/authorize`, + token_endpoint: `${o}/token`, + registration_endpoint: `${o}/register` + }, + v = y(); + return ( + v.use(y.json()), + v.use(y.urlencoded({ extended: !0 })), + a && + v.use( + E(e, { + incomingId: `incoming-auth-request`, + outgoingId: `outgoing-auth-response` + }) + ), + v.get(r, (n, r) => { + e.push({ + id: `authorization-server-metadata`, + name: `AuthorizationServerMetadata`, + description: `Client requested authorization server metadata`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [ + F.RFC_AUTH_SERVER_METADATA_REQUEST, + F.MCP_AUTH_DISCOVERY + ], + details: { url: n.url, path: n.path } + }); + let a = { + issuer: t(), + authorization_endpoint: `${t()}${_.authorization_endpoint}`, + token_endpoint: `${t()}${_.token_endpoint}`, + registration_endpoint: `${t()}${_.registration_endpoint}`, + response_types_supported: [`code`], + grant_types_supported: c, + code_challenge_methods_supported: [`S256`], + token_endpoint_auth_methods_supported: l, + ...(u && { token_endpoint_auth_signing_alg_values_supported: u }) + }; + (s !== void 0 && (a.scopes_supported = s), + d !== void 0 && (a.client_id_metadata_document_supported = d), + i && + ((a.jwks_uri = `${t()}/.well-known/jwks.json`), + (a.subject_types_supported = [`public`]), + (a.id_token_signing_alg_values_supported = [`RS256`])), + r.json(a)); + }), + v.get(_.authorization_endpoint, (t, n) => { + let r = new Date().toISOString(); + e.push({ + id: `authorization-request`, + name: `AuthorizationRequest`, + description: `Client made authorization request`, + status: `SUCCESS`, + timestamp: r, + specReferences: [F.OAUTH_2_1_AUTHORIZATION_ENDPOINT], + details: { query: t.query } + }); + let i = t.query.scope; + ((g = i ? i.split(` `) : []), + m && m({ clientId: t.query.client_id, scope: i, timestamp: r })); + let a = t.query.redirect_uri, + o = t.query.state, + s = new URL(a); + (s.searchParams.set(`code`, `test-auth-code`), + o && s.searchParams.set(`state`, o), + n.redirect(s.toString())); + }), + v.post(_.token_endpoint, async (n, r) => { + let i = new Date().toISOString(), + a = n.body.scope, + o = n.body.grant_type; + e.push({ + id: `token-request`, + name: `TokenRequest`, + description: `Client requested access token`, + status: `SUCCESS`, + timestamp: i, + specReferences: [F.OAUTH_2_1_TOKEN], + details: { endpoint: `/token`, grantType: o } + }); + let s = `test-token-${Date.now()}`, + c = g; + if (p) { + let e = await p({ + scope: a, + grantType: o, + timestamp: i, + body: n.body, + authBaseUrl: t(), + tokenEndpoint: `${t()}${_.token_endpoint}`, + authorizationHeader: n.headers.authorization + }); + if (`error` in e) { + r.status(e.statusCode || 400).json({ + error: e.error, + error_description: e.errorDescription + }); + return; + } + ((s = e.token), (c = e.scopes)); + } + (f && f.registerToken(s, c), + r.json({ + access_token: s, + token_type: `Bearer`, + expires_in: 3600, + ...(c.length > 0 && { scope: c.join(` `) }) + })); + }), + v.post(_.registration_endpoint, (t, n) => { + let r = `test-client-id`, + i = `test-client-secret`, + a; + if (h) { + let e = h(t); + ((r = e.clientId), + (i = e.clientSecret), + (a = e.tokenEndpointAuthMethod)); + } + (e.push({ + id: `client-registration`, + name: `ClientRegistration`, + description: `Client registered with authorization server`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_DCR], + details: { + endpoint: `/register`, + clientName: t.body.client_name, + ...(a && { tokenEndpointAuthMethod: a }) + } + }), + n.status(201).json({ + client_id: r, + ...(i && { client_secret: i }), + client_name: t.body.client_name || `test-client`, + redirect_uris: t.body.redirect_uris || [], + ...(a && { token_endpoint_auth_method: a }) + })); + }), + v + ); +} +var L = class { + constructor(e, t = []) { + ((this.checks = e), + (this.expectedScopes = t), + (this.tokenScopes = new Map())); + } + registerToken(e, t) { + this.tokenScopes.set(e, t); + } + async verifyAccessToken(e) { + if (e.startsWith(`test-token`) || e.startsWith(`cc-token`)) { + let t = this.tokenScopes.get(e) || []; + return ( + this.checks.push({ + id: `valid-bearer-token`, + name: `ValidBearerToken`, + description: `Client provided valid bearer token`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_ACCESS_TOKEN_USAGE], + details: { token: e.substring(0, 15) + `...`, scopes: t } + }), + { + token: e, + clientId: `test-client`, + scopes: t, + expiresAt: Math.floor(Date.now() / 1e3) + 3600 + } + ); + } + throw ( + this.checks.push({ + id: `invalid-bearer-token`, + name: `InvalidBearerToken`, + description: `Client provided invalid bearer token`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_ACCESS_TOKEN_USAGE], + details: { + message: `Token verification failed`, + token: e ? e.substring(0, 10) + `...` : `missing` + } + }), + Error(`Invalid token`) + ); + } +}; +function R(e, t, n, r = {}) { + let { + prmPath: i = `/.well-known/oauth-protected-resource/mcp`, + requiredScopes: a = [], + scopesSupported: o, + includePrmInWwwAuth: u = !0, + includeScopeInWwwAuth: d = !1, + tokenVerifier: f + } = r, + p = new s( + { name: `auth-prm-pathbased-server`, version: `1.0.0` }, + { capabilities: { tools: {} } } + ); + (p.setRequestHandler(h, async () => ({ + tools: [{ name: `test-tool`, inputSchema: { type: `object` } }] + })), + p.setRequestHandler(l, async (e) => { + if (e.params.name === `test-tool`) + return { content: [{ type: `text`, text: `test` }] }; + throw new _(m.InvalidParams, `Tool ${e.params.name} not found`); + })); + let g = y(); + return ( + g.use(y.json()), + g.use( + E(e, { + incomingId: `incoming-request`, + outgoingId: `outgoing-response`, + mcpRoute: `/mcp` + }) + ), + i !== null && + g.get(i, (r, a) => { + e.push({ + id: `prm-pathbased-requested`, + name: `PRMPathBasedRequested`, + description: `Client requested PRM metadata at path-based location`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [F.RFC_PRM_DISCOVERY, F.MCP_PRM_DISCOVERY], + details: { url: r.url, path: r.path } + }); + let s = { + resource: + i === `/.well-known/oauth-protected-resource` ? t() : `${t()}/mcp`, + authorization_servers: [n()] + }; + (o !== void 0 && (s.scopes_supported = o), a.json(s)); + }), + g.post(`/mcp`, async (n, o, s) => { + let l = f || new L(e, a); + ( + r.authMiddleware ?? + te({ + verifier: l, + requiredScopes: d ? a : [], + ...(u && i !== null && { resourceMetadataUrl: `${t()}${i}` }) + }) + )(n, o, async (e) => { + if (e) return s(e); + let t = new c({ sessionIdGenerator: void 0 }); + try { + (await p.connect(t), + await t.handleRequest(n, o, n.body), + o.on(`close`, () => { + (t.close(), p.close()); + })); + } catch (e) { + (console.error(`Error handling MCP request:`, e), + o.headersSent || + o.status(500).json({ + jsonrpc: `2.0`, + error: { code: -32603, message: `Internal server error` }, + id: null + })); + } + }); + }), + g + ); +} +var z = class { + constructor() { + ((this.app = null), + (this.httpServer = null), + (this.baseUrl = ``), + (this.getUrl = () => this.baseUrl)); + } + async start(e) { + return ( + (this.app = e), + (this.httpServer = this.app.listen(0)), + (this.baseUrl = `http://localhost:${this.httpServer.address().port}`), + this.baseUrl + ); + } + async stop() { + ((this.httpServer &&= + (await new Promise((e) => { + (this.httpServer.closeAllConnections?.(), + this.httpServer.close(() => e())); + }), + null)), + (this.app = null)); + } +}; +const B = [ + { + name: `metadata-default`, + prmLocation: `/.well-known/oauth-protected-resource/mcp`, + inWwwAuth: !0, + oauthMetadataLocation: `/.well-known/oauth-authorization-server`, + trapRootPrm: !0 + }, + { + name: `metadata-var1`, + prmLocation: `/.well-known/oauth-protected-resource/mcp`, + inWwwAuth: !1, + oauthMetadataLocation: `/.well-known/openid-configuration` + }, + { + name: `metadata-var2`, + prmLocation: `/.well-known/oauth-protected-resource`, + inWwwAuth: !1, + oauthMetadataLocation: `/.well-known/oauth-authorization-server/tenant1`, + authRoutePrefix: `/tenant1` + }, + { + name: `metadata-var3`, + prmLocation: `/custom/metadata/location.json`, + inWwwAuth: !0, + oauthMetadataLocation: `/tenant1/.well-known/openid-configuration`, + authRoutePrefix: `/tenant1` + } +]; +function V(e) { + let t = new z(), + n = new z(), + r = [], + i = e.authRoutePrefix || ``, + a = e.oauthMetadataLocation.includes(`openid-configuration`), + o = e.prmLocation === `/.well-known/oauth-protected-resource/mcp`; + return { + name: `auth/${e.name}`, + description: `Tests Basic OAuth metadata discovery flow. + +**PRM:** ${e.prmLocation}${e.inWwwAuth ? `` : ` (not in WWW-Authenticate)`} +**OAuth metadata:** ${e.oauthMetadataLocation} +`, + async start() { + r = []; + let o = I(r, t.getUrl, { + metadataPath: e.oauthMetadataLocation, + isOpenIdConfiguration: a, + ...(i && { routePrefix: i }) + }); + (i && + o.get(`/.well-known/oauth-authorization-server`, (e, t) => { + (r.push({ + id: `authorization-server-metadata-wrong-path`, + name: `AuthorizationServerMetadataWrongPath`, + description: `Client requested authorization server at the root path when the AS URL has a path-based location`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [ + F.RFC_AUTH_SERVER_METADATA_REQUEST, + F.MCP_AUTH_DISCOVERY + ], + details: { url: e.url } + }), + t.status(404).send(`Not Found`)); + }), + await t.start(o)); + let s = i ? () => `${t.getUrl()}${i}` : t.getUrl, + c = R(r, n.getUrl, s, { + prmPath: e.prmLocation, + includePrmInWwwAuth: e.inWwwAuth + }); + return ( + e.trapRootPrm && + c.get(`/.well-known/oauth-protected-resource`, (e, t) => { + (r.push({ + id: `prm-priority-order`, + name: `PRM Priority Order`, + description: `Client requested PRM metadata at root location on a server with path-based PRM`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.RFC_PRM_DISCOVERY, F.MCP_PRM_DISCOVERY], + details: { url: e.url, path: e.path } + }), + t.status(404).json({ + error: `not_found`, + error_description: `PRM metadata not available at root location` + })); + }), + await n.start(c), + { serverUrl: `${n.getUrl()}/mcp` } + ); + }, + async stop() { + (await t.stop(), await n.stop()); + }, + getChecks() { + let e = [ + ...(o ? [`prm-pathbased-requested`] : []), + `authorization-server-metadata`, + `client-registration`, + `authorization-request`, + `token-request` + ]; + for (let t of e) + r.find((e) => e.id === t) || + r.push({ + id: t, + name: `Expected Check Missing: ${t}`, + description: `Expected Check Missing: ${t}`, + status: `FAILURE`, + timestamp: new Date().toISOString() + }); + return r; + } + }; +} +(V(B[0]), V(B[1]), V(B[2]), V(B[3])); +const Ve = B.map(V); +function He() { + return Ve.map((e) => e.name); +} +const H = `https://conformance-test.local/client-metadata.json`; +var Ue = class { + constructor() { + ((this.name = `auth/basic-cimd`), + (this.description = `Tests OAuth flow with Client ID Metadata Documents (SEP-991/URL-based client IDs). Server advertises client_id_metadata_document_supported=true and client should use URL as client_id instead of DCR.`), + (this.authServer = new z()), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = I(this.checks, this.authServer.getUrl, { + clientIdMetadataDocumentSupported: !0, + onAuthorizationRequest: (e) => { + let t = e.clientId === H; + this.checks.push({ + id: `cimd-client-id-used`, + name: `Client ID Metadata Document Usage`, + description: t + ? `Client correctly used URL-based client ID when server supports client_id_metadata_document_supported` + : `Client SHOULD use URL-based client ID when server advertises client_id_metadata_document_supported=true`, + status: t ? `SUCCESS` : `WARNING`, + timestamp: e.timestamp, + specReferences: [F.MCP_CLIENT_ID_METADATA_DOCUMENTS, F.IETF_CIMD], + details: { + expectedClientId: H, + actualClientId: e.clientId || `none` + } + }); + } + }); + await this.authServer.start(e); + let t = R(this.checks, this.server.getUrl, this.authServer.getUrl); + return ( + await this.server.start(t), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + return ( + this.checks.some((e) => e.id === `cimd-client-id-used`) || + this.checks.push({ + id: `cimd-client-id-used`, + name: `Client ID Metadata Document Usage`, + description: `Client did not make an authorization request to test CIMD support`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_CLIENT_ID_METADATA_DOCUMENTS, F.IETF_CIMD] + }), + this.checks + ); + } + }, + We = class { + constructor() { + ((this.name = `auth/2025-03-26-oauth-metadata-backcompat`), + (this.description = `Tests 2025-03-26 spec OAuth flow: no PRM (Protected Resource Metadata), OAuth metadata at root location`), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = I(this.checks, this.server.getUrl, { + loggingEnabled: !1, + routePrefix: `/oauth` + }), + t = R(this.checks, this.server.getUrl, this.server.getUrl, { + prmPath: null + }); + return ( + t.use(e), + await this.server.start(t), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + await this.server.stop(); + } + getChecks() { + for (let e of [ + `authorization-server-metadata`, + `client-registration`, + `authorization-request`, + `token-request` + ]) + this.checks.find((t) => t.id === e) || + this.checks.push({ + id: e, + name: `Expected Check Missing: ${e}`, + description: `Expected Check Missing: ${e}`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.LEGACY_2025_03_26_AUTH_DISCOVERY] + }); + return this.checks; + } + }, + Ge = class { + constructor() { + ((this.name = `auth/2025-03-26-oauth-endpoint-fallback`), + (this.description = `Tests OAuth flow with no metadata endpoints, relying on fallback to standard OAuth endpoints at server root (2025-03-26 spec behavior)`), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = R(this.checks, this.server.getUrl, this.server.getUrl, { + prmPath: null + }); + return ( + e.use(y.urlencoded({ extended: !0 })), + e.get(`/authorize`, (e, t) => { + this.checks.push({ + id: `authorization-request`, + name: `AuthorizationRequest`, + description: `Client made authorization request to fallback endpoint`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [F.LEGACY_2025_03_26_AUTH_URL_FALLBACK], + details: { + response_type: e.query.response_type, + client_id: e.query.client_id, + redirect_uri: e.query.redirect_uri, + state: e.query.state, + code_challenge: e.query.code_challenge ? `present` : `missing`, + code_challenge_method: e.query.code_challenge_method + } + }); + let n = e.query.redirect_uri, + r = e.query.state, + i = new URL(n); + (i.searchParams.set(`code`, `test-auth-code`), + r && i.searchParams.set(`state`, r), + t.redirect(i.toString())); + }), + e.post(`/token`, (e, t) => { + (this.checks.push({ + id: `token-request`, + name: `TokenRequest`, + description: `Client requested access token from fallback endpoint`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [F.LEGACY_2025_03_26_AUTH_URL_FALLBACK], + details: { endpoint: `/token`, grantType: e.body.grant_type } + }), + t.json({ + access_token: `test-token`, + token_type: `Bearer`, + expires_in: 3600 + })); + }), + e.post(`/register`, (e, t) => { + (this.checks.push({ + id: `client-registration`, + name: `ClientRegistration`, + description: `Client registered with authorization server at fallback endpoint`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [F.LEGACY_2025_03_26_AUTH_URL_FALLBACK], + details: { endpoint: `/register`, clientName: e.body.client_name } + }), + t.status(201).json({ + client_id: `test-client-id`, + client_secret: `test-client-secret`, + client_name: e.body.client_name || `test-client`, + redirect_uris: e.body.redirect_uris || [] + })); + }), + await this.server.start(e), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + await this.server.stop(); + } + getChecks() { + for (let e of [ + `client-registration`, + `authorization-request`, + `token-request` + ]) + this.checks.find((t) => t.id === e) || + this.checks.push({ + id: e, + name: `Expected Check Missing: ${e}`, + description: `Expected Check Missing: ${e}`, + status: `FAILURE`, + timestamp: new Date().toISOString() + }); + return this.checks; + } + }, + Ke = class { + constructor() { + ((this.name = `auth/scope-from-www-authenticate`), + (this.description = `Tests that client uses scope parameter from WWW-Authenticate header when provided`), + (this.authServer = new z()), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = `mcp:basic`, + t = new L(this.checks, [e]), + n = I(this.checks, this.authServer.getUrl, { + tokenVerifier: t, + onAuthorizationRequest: (t) => { + let n = (t.scope ? t.scope.split(` `) : []).includes(e); + this.checks.push({ + id: `scope-from-www-authenticate`, + name: `Client scope selection from WWW-Authenticate header`, + description: n + ? `Client correctly used the scope parameter from the WWW-Authenticate header` + : `Client SHOULD use the scope parameter from the WWW-Authenticate header when provided`, + status: n ? `SUCCESS` : `WARNING`, + timestamp: t.timestamp, + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], + details: { expectedScope: e, requestedScope: t.scope || `none` } + }); + } + }); + await this.authServer.start(n); + let r = R(this.checks, this.server.getUrl, this.authServer.getUrl, { + prmPath: `/.well-known/oauth-protected-resource/mcp`, + requiredScopes: [e], + includeScopeInWwwAuth: !0, + tokenVerifier: t + }); + return ( + await this.server.start(r), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + return ( + this.checks.some((e) => e.id === `scope-from-www-authenticate`) || + this.checks.push({ + id: `scope-from-www-authenticate`, + name: `Client scope selection from WWW-Authenticate header`, + description: `Client did not complete authorization flow - scope check could not be performed`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] + }), + this.checks + ); + } + }, + qe = class { + constructor() { + ((this.name = `auth/scope-from-scopes-supported`), + (this.description = `Tests that client uses all scopes from scopes_supported when scope not in WWW-Authenticate header`), + (this.authServer = new z()), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = [`mcp:basic`, `mcp:read`, `mcp:write`], + t = new L(this.checks, e), + n = I(this.checks, this.authServer.getUrl, { + tokenVerifier: t, + onAuthorizationRequest: (t) => { + let n = t.scope ? t.scope.split(` `) : [], + r = e.every((e) => n.includes(e)); + this.checks.push({ + id: `scope-from-scopes-supported`, + name: `Client scope selection from scopes_supported`, + description: r + ? `Client correctly used all scopes from scopes_supported in PRM when scope not in WWW-Authenticate` + : `Client SHOULD use all scopes from scopes_supported when scope not available in WWW-Authenticate header`, + status: r ? `SUCCESS` : `WARNING`, + timestamp: t.timestamp, + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], + details: { + scopesSupported: e.join(` `), + requestedScope: t.scope || `none`, + ...(r + ? {} + : { + missingScopes: e.filter((e) => !n.includes(e)).join(` `) + }) + } + }); + } + }); + await this.authServer.start(n); + let r = R(this.checks, this.server.getUrl, this.authServer.getUrl, { + prmPath: `/.well-known/oauth-protected-resource/mcp`, + requiredScopes: e, + scopesSupported: e, + includeScopeInWwwAuth: !1, + tokenVerifier: t + }); + return ( + await this.server.start(r), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + return ( + this.checks.some((e) => e.id === `scope-from-scopes-supported`) || + this.checks.push({ + id: `scope-from-scopes-supported`, + name: `Client scope selection from scopes_supported`, + description: `Client did not complete authorization flow - scope check could not be performed`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] + }), + this.checks + ); + } + }, + Je = class { + constructor() { + ((this.name = `auth/scope-omitted-when-undefined`), + (this.description = `Tests that client omits scope parameter when scopes_supported is undefined`), + (this.authServer = new z()), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = new L(this.checks, []), + t = I(this.checks, this.authServer.getUrl, { + tokenVerifier: e, + onAuthorizationRequest: (e) => { + let t = !e.scope || e.scope.trim() === ``; + this.checks.push({ + id: `scope-omitted-when-undefined`, + name: `Client scope omission when scopes_supported undefined`, + description: t + ? `Client correctly omitted scope parameter when scopes_supported is undefined` + : `Client SHOULD omit scope parameter when scopes_supported is undefined and scope not in WWW-Authenticate`, + status: t ? `SUCCESS` : `WARNING`, + timestamp: e.timestamp, + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], + details: { scopeParameter: t ? `omitted` : e.scope } + }); + } + }); + await this.authServer.start(t); + let n = R(this.checks, this.server.getUrl, this.authServer.getUrl, { + prmPath: `/.well-known/oauth-protected-resource/mcp`, + requiredScopes: [], + scopesSupported: void 0, + includeScopeInWwwAuth: !1, + tokenVerifier: e + }); + return ( + await this.server.start(n), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + return ( + this.checks.some((e) => e.id === `scope-omitted-when-undefined`) || + this.checks.push({ + id: `scope-omitted-when-undefined`, + name: `Client scope omission when scopes_supported undefined`, + description: `Client did not complete authorization flow - scope check could not be performed`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] + }), + this.checks + ); + } + }, + Ye = class { + constructor() { + ((this.name = `auth/scope-step-up`), + (this.description = `Tests that client handles step-up authentication with different scope requirements per operation`), + (this.authServer = new z()), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = `mcp:basic`, + t = [`mcp:basic`, `mcp:write`], + n = new L(this.checks, t), + r = 0, + i = I(this.checks, this.authServer.getUrl, { + tokenVerifier: n, + onAuthorizationRequest: (n) => { + r++; + let i = n.scope ? n.scope.split(` `) : []; + if (r === 1) { + let t = i.includes(e); + this.checks.push({ + id: `scope-step-up-initial`, + name: `Client initial scope selection for step-up auth`, + description: t + ? `Client correctly used scope from WWW-Authenticate header for initial auth` + : `Client SHOULD use the scope parameter from the WWW-Authenticate header`, + status: t ? `SUCCESS` : `WARNING`, + timestamp: n.timestamp, + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], + details: { expectedScope: e, requestedScope: n.scope || `none` } + }); + } else if (r === 2) { + let e = t.every((e) => i.includes(e)); + this.checks.push({ + id: `scope-step-up-escalation`, + name: `Client scope escalation for step-up auth`, + description: e + ? `Client correctly escalated scopes for step-up authentication` + : `Client SHOULD request additional scopes when receiving 403 with new scope requirements`, + status: e ? `SUCCESS` : `WARNING`, + timestamp: n.timestamp, + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], + details: { + expectedScopes: t.join(` `), + requestedScope: n.scope || `none` + } + }); + } + } + }); + await this.authServer.start(i); + let a = () => + `${this.server.getUrl()}/.well-known/oauth-protected-resource/mcp`, + o = R(this.checks, this.server.getUrl, this.authServer.getUrl, { + prmPath: `/.well-known/oauth-protected-resource/mcp`, + requiredScopes: t, + scopesSupported: t, + includeScopeInWwwAuth: !0, + authMiddleware: async (r, i, o) => { + let s = r.body; + typeof s == `string` && (s = JSON.parse(s)); + let c = s?.method; + if (c === `initialize` || c?.startsWith(`notifications/`)) + return o(); + let l = r.headers.authorization; + if (!l || !l.startsWith(`Bearer `)) + return i + .status(401) + .set( + `WWW-Authenticate`, + `Bearer scope="${e}", resource_metadata="${a()}"` + ) + .json({ + error: `invalid_token`, + error_description: `Missing Authorization header` + }); + let u = l.substring(7), + d = (await n.verifyAccessToken(u)).scopes || [], + f = c === `tools/call` ? t : [e]; + if (!f.every((e) => d.includes(e))) + return i + .status(403) + .set( + `WWW-Authenticate`, + `Bearer scope="${f.join(` `)}", resource_metadata="${a()}", error="insufficient_scope"` + ) + .json({ + error: `insufficient_scope`, + error_description: `Token has insufficient scope` + }); + o(); + }, + tokenVerifier: n + }); + return ( + await this.server.start(o), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + let e = this.checks.some((e) => e.id === `scope-step-up-initial`), + t = this.checks.some((e) => e.id === `scope-step-up-escalation`); + return ( + e || + this.checks.push({ + id: `scope-step-up-initial`, + name: `Client initial scope selection for step-up auth`, + description: `Client did not make an initial authorization request`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] + }), + t || + this.checks.push({ + id: `scope-step-up-escalation`, + name: `Client scope escalation for step-up auth`, + description: `Client did not make a second authorization request for scope escalation`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] + }), + this.checks + ); + } + }, + Xe = class { + constructor() { + ((this.name = `auth/scope-retry-limit`), + (this.description = `Tests that client implements retry limits to prevent infinite authorization loops on repeated 403 responses`), + (this.authServer = new z()), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = `mcp:admin`, + t = new L(this.checks, []), + n = 0, + r = I(this.checks, this.authServer.getUrl, { + tokenVerifier: t, + onAuthorizationRequest: (e) => { + (n++, + this.checks.push({ + id: `scope-retry-auth-attempt`, + name: `Authorization attempt ${n}`, + description: `Client made authorization request attempt ${n}`, + status: `INFO`, + timestamp: e.timestamp, + specReferences: [F.MCP_SCOPE_CHALLENGE_HANDLING], + details: { attemptNumber: n, requestedScope: e.scope || `none` } + })); + } + }); + await this.authServer.start(r); + let i = () => + `${this.server.getUrl()}/.well-known/oauth-protected-resource/mcp`, + a = 0, + o = R(this.checks, this.server.getUrl, this.authServer.getUrl, { + prmPath: `/.well-known/oauth-protected-resource/mcp`, + requiredScopes: [e], + scopesSupported: [e], + includeScopeInWwwAuth: !0, + authMiddleware: async (t, n, r) => { + let o = t.body; + typeof o == `string` && (o = JSON.parse(o)); + let s = o?.method; + if (s === `initialize` || s?.startsWith(`notifications/`)) + return r(); + let c = t.headers.authorization; + return !c || !c.startsWith(`Bearer `) + ? n + .status(401) + .set( + `WWW-Authenticate`, + `Bearer scope="${e}", resource_metadata="${i()}"` + ) + .json({ + error: `invalid_token`, + error_description: `Missing Authorization header` + }) + : (a++, + a > 3 + ? n.status(410).json({ + error: `test_complete`, + error_description: `Test is over - client exceeded maximum retry attempts` + }) + : n + .status(403) + .set( + `WWW-Authenticate`, + `Bearer error="insufficient_scope", scope="${e}", resource_metadata="${i()}", error_description="Scope upgrade will never succeed"` + ) + .json({ + error: `insufficient_scope`, + error_description: `Scope upgrade will never succeed` + })); + }, + tokenVerifier: t + }); + return ( + await this.server.start(o), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + let e = this.checks.filter( + (e) => e.id === `scope-retry-auth-attempt` + ).length; + return ( + e === 0 + ? this.checks.push({ + id: `scope-retry-limit`, + name: `Client retry limit for scope escalation`, + description: `Client did not make any authorization requests`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_SCOPE_CHALLENGE_HANDLING] + }) + : e <= 3 + ? this.checks.push({ + id: `scope-retry-limit`, + name: `Client retry limit for scope escalation`, + description: `Client correctly limited retry attempts to ${e} (3 or fewer)`, + status: `SUCCESS`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_SCOPE_CHALLENGE_HANDLING], + details: { authorizationAttempts: e, maxAllowed: 3 } + }) + : this.checks.push({ + id: `scope-retry-limit`, + name: `Client retry limit for scope escalation`, + description: `Client made ${e} authorization attempts (more than 3). Clients SHOULD implement retry limits to avoid infinite loops.`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.MCP_SCOPE_CHALLENGE_HANDLING], + details: { authorizationAttempts: e, maxAllowed: 3 } + }), + this.checks + ); + } + }; +function Ze(e, t) { + return e?.startsWith(`Basic `) + ? `client_secret_basic` + : t + ? `client_secret_post` + : `none`; +} +function Qe(e) { + let t = e.substring(6); + try { + return Buffer.from(t, `base64`).toString(`utf-8`).includes(`:`) + ? { valid: !0 } + : { valid: !1, error: `missing colon separator` }; + } catch { + return { valid: !1, error: `base64 decoding failed` }; + } +} +const $e = { + client_secret_basic: `HTTP Basic authentication (client_secret_basic)`, + client_secret_post: `client_secret_post`, + none: `no authentication (public client)` +}; +var U = class { + constructor(e) { + ((this.authServer = new z()), + (this.server = new z()), + (this.checks = []), + (this.expectedAuthMethod = e), + (this.name = `auth/token-endpoint-auth-${e === `client_secret_basic` ? `basic` : e === `client_secret_post` ? `post` : `none`}`), + (this.description = `Tests that client uses ${$e[e]} when server only supports ${e}`)); + } + async start() { + this.checks = []; + let e = new L(this.checks, []), + t = I(this.checks, this.authServer.getUrl, { + tokenVerifier: e, + tokenEndpointAuthMethodsSupported: [this.expectedAuthMethod], + onTokenRequest: ({ + authorizationHeader: e, + body: t, + timestamp: n + }) => { + let r = t.client_secret, + i = Ze(e, r), + a = i === this.expectedAuthMethod, + o; + if (i === `client_secret_basic` && e) { + let t = Qe(e); + t.valid || (o = t.error); + } + let s = a && !o ? `SUCCESS` : `FAILURE`, + c; + return ( + (c = o + ? `Client sent Basic auth header but ${o}` + : a + ? `Client correctly used ${$e[this.expectedAuthMethod]} for token endpoint` + : `Client used ${i} but server only supports ${this.expectedAuthMethod}`), + this.checks.push({ + id: `token-endpoint-auth-method`, + name: `Token endpoint authentication method`, + description: c, + status: s, + timestamp: n, + specReferences: [F.OAUTH_2_1_TOKEN], + details: { + expectedAuthMethod: this.expectedAuthMethod, + actualAuthMethod: i, + hasAuthorizationHeader: !!e, + hasBodyClientSecret: !!r, + ...(o && { formatError: o }) + } + }), + { token: `test-token-${Date.now()}`, scopes: [] } + ); + }, + onRegistrationRequest: () => ({ + clientId: `test-client-${Date.now()}`, + clientSecret: + this.expectedAuthMethod === `none` + ? void 0 + : `test-secret-${Date.now()}`, + tokenEndpointAuthMethod: this.expectedAuthMethod + }) + }); + await this.authServer.start(t); + let n = R(this.checks, this.server.getUrl, this.authServer.getUrl, { + prmPath: `/.well-known/oauth-protected-resource/mcp`, + requiredScopes: [], + tokenVerifier: e + }); + return ( + await this.server.start(n), + { serverUrl: `${this.server.getUrl()}/mcp` } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + return ( + this.checks.some((e) => e.id === `token-endpoint-auth-method`) || + this.checks.push({ + id: `token-endpoint-auth-method`, + name: `Token endpoint authentication method`, + description: `Client did not make a token request`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [F.OAUTH_2_1_TOKEN] + }), + this.checks + ); + } + }, + et = class extends U { + constructor() { + super(`client_secret_basic`); + } + }, + tt = class extends U { + constructor() { + super(`client_secret_post`); + } + }, + nt = class extends U { + constructor() { + super(`none`); + } + }; +const W = `conformance-test-client`, + rt = `conformance-test-secret`; +async function it() { + let { publicKey: e, privateKey: t } = await C.generateKeyPair(`ES256`, { + extractable: !0 + }); + return { publicKey: e, privateKeyPem: await C.exportPKCS8(t) }; +} +var at = class { + constructor() { + ((this.name = `auth/client-credentials-jwt`), + (this.description = `Tests OAuth client_credentials flow with private_key_jwt authentication (SEP-1046)`), + (this.authServer = new z()), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let { publicKey: e, privateKeyPem: t } = await it(), + n = I(this.checks, this.authServer.getUrl, { + grantTypesSupported: [`client_credentials`], + tokenEndpointAuthMethodsSupported: [`private_key_jwt`], + tokenEndpointAuthSigningAlgValuesSupported: [`ES256`], + onTokenRequest: async ({ + grantType: t, + body: n, + timestamp: r, + authBaseUrl: i + }) => { + let a = i; + if (t !== `client_credentials`) + return ( + this.checks.push({ + id: `client-credentials-grant-type`, + name: `ClientCredentialsGrantType`, + description: `Expected grant_type=client_credentials, got ${t}`, + status: `FAILURE`, + timestamp: r, + specReferences: [ + F.OAUTH_2_1_CLIENT_CREDENTIALS, + F.SEP_1046_CLIENT_CREDENTIALS + ] + }), + { + error: `unsupported_grant_type`, + errorDescription: `Only client_credentials grant is supported` + } + ); + let o = n.client_assertion, + s = n.client_assertion_type; + if (s !== `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`) + return ( + this.checks.push({ + id: `client-credentials-assertion-type`, + name: `ClientCredentialsAssertionType`, + description: `Invalid client_assertion_type: ${s}`, + status: `FAILURE`, + timestamp: r, + specReferences: [F.RFC_JWT_CLIENT_AUTH] + }), + { + error: `invalid_client`, + errorDescription: `Invalid client_assertion_type`, + statusCode: 401 + } + ); + try { + let t = a.replace(/\/+$/, ``), + { payload: i } = await C.jwtVerify(o, e, { + audience: [a, t, `${t}/`], + clockTolerance: 30 + }); + if (i.iss !== W) + return ( + this.checks.push({ + id: `client-credentials-jwt-iss`, + name: `ClientCredentialsJwtIss`, + description: `JWT iss claim '${i.iss}' does not match expected client_id '${W}'`, + status: `FAILURE`, + timestamp: r, + specReferences: [F.RFC_JWT_CLIENT_AUTH], + details: { expected: W, actual: i.iss } + }), + { + error: `invalid_client`, + errorDescription: `JWT iss claim does not match expected client_id`, + statusCode: 401 + } + ); + if (i.sub !== W) + return ( + this.checks.push({ + id: `client-credentials-jwt-sub`, + name: `ClientCredentialsJwtSub`, + description: `JWT sub claim '${i.sub}' does not match expected client_id '${W}'`, + status: `FAILURE`, + timestamp: r, + specReferences: [F.RFC_JWT_CLIENT_AUTH], + details: { expected: W, actual: i.sub } + }), + { + error: `invalid_client`, + errorDescription: `JWT sub claim does not match expected client_id`, + statusCode: 401 + } + ); + this.checks.push({ + id: `client-credentials-jwt-verified`, + name: `ClientCredentialsJwtVerified`, + description: `Client successfully authenticated with signed JWT assertion`, + status: `SUCCESS`, + timestamp: r, + specReferences: [ + F.OAUTH_2_1_CLIENT_CREDENTIALS, + F.SEP_1046_CLIENT_CREDENTIALS, + F.RFC_JWT_CLIENT_AUTH + ], + details: { iss: i.iss, sub: i.sub, aud: i.aud } + }); + let s = n.scope ? n.scope.split(` `) : []; + return { token: `cc-token-${Date.now()}`, scopes: s }; + } catch (e) { + let t = e instanceof Error ? e.message : String(e); + return ( + this.checks.push({ + id: `client-credentials-jwt-verified`, + name: `ClientCredentialsJwtVerified`, + description: `JWT verification failed: ${t}`, + status: `FAILURE`, + timestamp: r, + specReferences: [F.RFC_JWT_CLIENT_AUTH], + details: { error: t } + }), + { + error: `invalid_client`, + errorDescription: `JWT verification failed: ${t}`, + statusCode: 401 + } + ); + } + } + }); + await this.authServer.start(n); + let r = R(this.checks, this.server.getUrl, this.authServer.getUrl); + return ( + await this.server.start(r), + { + serverUrl: `${this.server.getUrl()}/mcp`, + context: { + client_id: W, + private_key_pem: t, + signing_algorithm: `ES256` + } + } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + return ( + this.checks.some((e) => e.id === `client-credentials-jwt-verified`) || + this.checks.push({ + id: `client-credentials-jwt-verified`, + name: `ClientCredentialsJwtVerified`, + description: `Client did not make a client_credentials token request`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [ + F.OAUTH_2_1_CLIENT_CREDENTIALS, + F.SEP_1046_CLIENT_CREDENTIALS + ] + }), + this.checks + ); + } + }, + ot = class { + constructor() { + ((this.name = `auth/client-credentials-basic`), + (this.description = `Tests OAuth client_credentials flow with client_secret_basic authentication`), + (this.authServer = new z()), + (this.server = new z()), + (this.checks = [])); + } + async start() { + this.checks = []; + let e = I(this.checks, this.authServer.getUrl, { + grantTypesSupported: [`client_credentials`], + tokenEndpointAuthMethodsSupported: [`client_secret_basic`], + onTokenRequest: async ({ + grantType: e, + body: t, + timestamp: n, + authorizationHeader: r + }) => { + if (e !== `client_credentials`) + return ( + this.checks.push({ + id: `client-credentials-grant-type`, + name: `ClientCredentialsGrantType`, + description: `Expected grant_type=client_credentials, got ${e}`, + status: `FAILURE`, + timestamp: n, + specReferences: [ + F.OAUTH_2_1_CLIENT_CREDENTIALS, + F.SEP_1046_CLIENT_CREDENTIALS + ] + }), + { + error: `unsupported_grant_type`, + errorDescription: `Only client_credentials grant is supported` + } + ); + let i = r; + if (!i || !i.startsWith(`Basic `)) + return ( + this.checks.push({ + id: `client-credentials-basic-auth`, + name: `ClientCredentialsBasicAuth`, + description: `Missing or invalid Authorization header for Basic auth`, + status: `FAILURE`, + timestamp: n, + specReferences: [F.SEP_1046_CLIENT_CREDENTIALS] + }), + { + error: `invalid_client`, + errorDescription: `Missing or invalid Authorization header`, + statusCode: 401 + } + ); + let a = i.slice(6), + [o, s] = Buffer.from(a, `base64`).toString(`utf-8`).split(`:`); + if (o !== W || s !== rt) + return ( + this.checks.push({ + id: `client-credentials-basic-auth`, + name: `ClientCredentialsBasicAuth`, + description: `Invalid client credentials`, + status: `FAILURE`, + timestamp: n, + specReferences: [F.SEP_1046_CLIENT_CREDENTIALS], + details: { clientId: o } + }), + { + error: `invalid_client`, + errorDescription: `Invalid client credentials`, + statusCode: 401 + } + ); + this.checks.push({ + id: `client-credentials-basic-auth`, + name: `ClientCredentialsBasicAuth`, + description: `Client successfully authenticated with client_secret_basic`, + status: `SUCCESS`, + timestamp: n, + specReferences: [ + F.OAUTH_2_1_CLIENT_CREDENTIALS, + F.SEP_1046_CLIENT_CREDENTIALS + ], + details: { clientId: o } + }); + let c = t.scope ? t.scope.split(` `) : []; + return { token: `cc-token-${Date.now()}`, scopes: c }; + } + }); + await this.authServer.start(e); + let t = R(this.checks, this.server.getUrl, this.authServer.getUrl); + return ( + await this.server.start(t), + { + serverUrl: `${this.server.getUrl()}/mcp`, + context: { client_id: W, client_secret: rt } + } + ); + } + async stop() { + (await this.authServer.stop(), await this.server.stop()); + } + getChecks() { + return ( + this.checks.some((e) => e.id === `client-credentials-basic-auth`) || + this.checks.push({ + id: `client-credentials-basic-auth`, + name: `ClientCredentialsBasicAuth`, + description: `Client did not make a client_credentials token request`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + specReferences: [ + F.OAUTH_2_1_CLIENT_CREDENTIALS, + F.SEP_1046_CLIENT_CREDENTIALS + ] + }), + this.checks + ); + } + }; +const st = [ + ...Ve, + new Ue(), + new We(), + new Ge(), + new Ke(), + new qe(), + new Je(), + new Ye(), + new Xe(), + new et(), + new tt(), + new nt(), + new at(), + new ot() + ], + ct = [new N(), new j(), new O(), new M(), new P()], + lt = [ + new me(), + new he(), + new ge(), + new _e(), + new ve(), + new ye(), + new be(), + new Ee(), + new De(), + new xe(), + new Se(), + new Ce(), + new we(), + new Te(), + new O(), + new j(), + new M(), + new P(), + new ke(), + new N(), + new Ae(), + new je(), + new Me(), + new Ne(), + new Pe(), + new Fe(), + new Ie(), + new Le(), + new Re(), + new ze(), + new Be() + ], + ut = lt.filter((e) => !ct.some((t) => t.name === e.name)), + dt = new Map(lt.map((e) => [e.name, e])), + ft = [new oe(), new ce(), new de(), new fe(), ...st], + pt = new Map(ft.map((e) => [e.name, e])); +function G(e) { + return pt.get(e); +} +function mt(e) { + return dt.get(e); +} +function K() { + return Array.from(pt.keys()); +} +function q() { + return Array.from(dt.keys()); +} +function ht() { + return ut.map((e) => e.name); +} +function gt() { + return ct.map((e) => e.name); +} +function _t() { + return st.map((e) => e.name); +} +const J = { + RESET: `\x1B[0m`, + GRAY: `\x1B[90m`, + GREEN: `\x1B[32m`, + YELLOW: `\x1B[33m`, + RED: `\x1B[31m`, + BLUE: `\x1B[36m` +}; +function vt(e) { + switch (e) { + case `SUCCESS`: + return J.GREEN; + case `FAILURE`: + return J.RED; + case `WARNING`: + return J.YELLOW; + case `INFO`: + return J.BLUE; + default: + return J.RESET; + } +} +function Y(e) { + let t = Math.max(...e.map((e) => e.id.length)), + n = Math.max(...e.map((e) => e.status.length)); + return e.map( + (e) => + `${`${J.GRAY}${e.timestamp}${J.RESET}`} [${e.id.padEnd(t)}] ${`${vt(e.status)}${e.status.padEnd(n)}${J.RESET}`} ${e.description}` + + (e.id.includes(`outgoing`) && e.id.includes(`response`) + ? ` +` + : ``) + ).join(` +`); +} +async function X() { + let e = a.join(process.cwd(), `results`); + return (await i.mkdir(e, { recursive: !0 }), e); +} +function Z(e, t = ``) { + let n = new Date().toISOString().replace(/[:.]/g, `-`), + r = t ? `${t}-${e}` : e; + return a.join(`results`, `${r}-${n}`); +} +async function yt(e, t, n, i = 3e4, a) { + let o = e.split(` `), + s = o[0], + c = [...o.slice(1), n], + l = ``, + u = ``, + d = !1, + f = { ...process.env }; + return ( + (f.MCP_CONFORMANCE_SCENARIO = t), + a && (f.MCP_CONFORMANCE_CONTEXT = JSON.stringify({ name: t, ...a })), + new Promise((e) => { + let t = r(s, c, { shell: !0, stdio: `pipe`, env: f }), + n = setTimeout(() => { + ((d = !0), t.kill()); + }, i); + (t.stdout && + t.stdout.on(`data`, (e) => { + l += e.toString(); + }), + t.stderr && + t.stderr.on(`data`, (e) => { + u += e.toString(); + }), + t.on(`close`, (t) => { + (clearTimeout(n), + e({ exitCode: t || 0, stdout: l, stderr: u, timedOut: d })); + }), + t.on(`error`, (t) => { + (clearTimeout(n), + e({ + exitCode: -1, + stdout: l, + stderr: u + `\nProcess error: ${t.message}`, + timedOut: d + })); + })); + }) + ); +} +async function bt(e, t, n = 3e4) { + await X(); + let r = Z(t); + await i.mkdir(r, { recursive: !0 }); + let o = G(t); + console.error(`Starting scenario: ${t}`); + let s = await o.start(); + (console.error(`Executing client: ${e} ${s.serverUrl}`), + s.context && console.error(`With context: ${JSON.stringify(s.context)}`)); + try { + let c = await yt(e, t, s.serverUrl, n, s.context); + (c.exitCode !== 0 && + (console.error(`\nClient exited with code ${c.exitCode}`), + c.stdout && console.error(`\nStdout:\n${c.stdout}`), + c.stderr && console.error(`\nStderr:\n${c.stderr}`)), + c.timedOut && console.error(`\nClient timed out after ${n}ms`)); + let l = o.getChecks(); + return ( + await i.writeFile(a.join(r, `checks.json`), JSON.stringify(l, null, 2)), + await i.writeFile(a.join(r, `stdout.txt`), c.stdout), + await i.writeFile(a.join(r, `stderr.txt`), c.stderr), + console.error(`Results saved to ${r}`), + { checks: l, clientOutput: c, resultDir: r } + ); + } finally { + await o.stop(); + } +} +function xt(e, t = !1, n) { + let r = e.filter( + (e) => e.status === `SUCCESS` || e.status === `FAILURE` + ).length, + i = e.filter((e) => e.status === `SUCCESS`).length, + a = e.filter((e) => e.status === `FAILURE`).length, + o = e.filter((e) => e.status === `WARNING`).length, + s = n?.timedOut ?? !1, + c = n ? n.exitCode !== 0 : !1, + l = a > 0 || o > 0 || s || c; + return ( + t + ? console.log(JSON.stringify(e, null, 2)) + : console.error(`Checks:\n${Y(e)}`), + console.error(` +Test Results:`), + console.error(`Passed: ${i}/${r}, ${a} failed, ${o} warnings`), + s && + console.error(` +⚠️ CLIENT TIMED OUT - Test incomplete`), + c && + !s && + console.error( + `\n⚠️ CLIENT EXITED WITH ERROR (code ${n?.exitCode}) - Test may be incomplete` + ), + a > 0 && + (console.error(` +Failed Checks:`), + e + .filter((e) => e.status === `FAILURE`) + .forEach((e) => { + (console.error(` - ${e.name}: ${e.description}`), + e.errorMessage && console.error(` Error: ${e.errorMessage}`)); + })), + o > 0 && + (console.error(` +Warning Checks:`), + e + .filter((e) => e.status === `WARNING`) + .forEach((e) => { + (console.error(` - ${e.name}: ${e.description}`), + e.errorMessage && console.error(` Warning: ${e.errorMessage}`)); + })), + l + ? console.error(` +❌ OVERALL: FAILED`) + : console.error(` +✅ OVERALL: PASSED`), + { passed: i, failed: a, denominator: r, warnings: o, overallFailure: l } + ); +} +async function St(e, t = !1) { + await X(); + let n = Z(e); + await i.mkdir(n, { recursive: !0 }); + let r = G(e); + console.log(`Starting scenario: ${e}`); + let o = await r.start(); + (console.log(`Server URL: ${o.serverUrl}`), + console.log(`Press Ctrl+C to stop and save checks...`)); + let s = async () => { + console.log(` +Shutting down...`); + let e = r.getChecks(); + (await i.writeFile(a.join(n, `checks.json`), JSON.stringify(e, null, 2)), + t + ? console.log(`\nChecks:\n${JSON.stringify(e, null, 2)}`) + : console.log(`\nChecks:\n${Y(e)}`), + console.log(`\nChecks saved to ${n}/checks.json`), + await r.stop(), + process.exit(0)); + }; + (process.on(`SIGINT`, s), + process.on(`SIGTERM`, s), + await new Promise(() => {})); +} +function Ct(e) { + return e + .replace(/\*\*([^*]+)\*\*/g, `\x1B[1m$1\x1B[0m`) + .replace(/`([^`]+)`/g, `\x1B[2m$1\x1B[0m`); +} +async function Q(e, t) { + await X(); + let n = Z(t, `server`); + await i.mkdir(n, { recursive: !0 }); + let r = mt(t); + console.log(`Running client scenario '${t}' against server: ${e}`); + let o = await r.run(e); + return ( + await i.writeFile(a.join(n, `checks.json`), JSON.stringify(o, null, 2)), + console.log(`Results saved to ${n}`), + { checks: o, resultDir: n, scenarioDescription: r.description } + ); +} +function wt(e, t, n = !1) { + let r = e.filter( + (e) => e.status === `SUCCESS` || e.status === `FAILURE` + ).length, + i = e.filter((e) => e.status === `SUCCESS`).length, + a = e.filter((e) => e.status === `FAILURE`).length, + o = e.filter((e) => e.status === `WARNING`).length; + return ( + n + ? console.log(JSON.stringify(e, null, 2)) + : console.log(`Checks:\n${Y(e)}`), + console.log(` +Test Results:`), + console.log(`Passed: ${i}/${r}, ${a} failed, ${o} warnings`), + a > 0 && + (console.log(` +=== Failed Checks ===`), + e + .filter((e) => e.status === `FAILURE`) + .forEach((e) => { + (console.log(`\n - ${e.name}: ${e.description}`), + e.errorMessage && console.log(` Error: ${e.errorMessage}`), + console.log(`\n${Ct(t)}`)); + })), + { passed: i, failed: a, denominator: r, warnings: o } + ); +} +function Tt(e) { + console.log(` + +=== SUMMARY ===`); + let t = 0, + n = 0; + for (let r of e) { + let e = r.checks.filter((e) => e.status === `SUCCESS`).length, + i = r.checks.filter((e) => e.status === `FAILURE`).length; + ((t += e), (n += i)); + let a = i === 0 ? `✓` : `✗`; + console.log(`${a} ${r.scenario}: ${e} passed, ${i} failed`); + } + return ( + console.log(`\nTotal: ${t} passed, ${n} failed`), + { totalPassed: t, totalFailed: n } + ); +} +const Et = n.object({ + command: n.string().min(1, `Command cannot be empty`).optional(), + scenario: n + .string() + .min(1, `Scenario cannot be empty`) + .refine( + (e) => G(e) !== void 0, + (e) => ({ message: `Unknown scenario '${e}'` }) + ), + timeout: n + .string() + .transform((e) => parseInt(e, 10)) + .pipe( + n + .number() + .positive(`Timeout must be a positive number`) + .int(`Timeout must be an integer`) + ) + .optional(), + verbose: n.boolean().optional() + }), + Dt = n.object({ + url: n.string().url(`Invalid server URL`), + scenario: n + .string() + .refine( + (e) => mt(e) !== void 0, + (e) => ({ message: `Unknown scenario '${e}'` }) + ) + .optional() + }); +n.object({ + scenario: n + .string() + .min(1, `Scenario cannot be empty`) + .refine( + (e) => G(e) !== void 0, + (e) => ({ message: `Unknown scenario '${e}'` }) + ) +}); +var Ot = `0.1.8`; +const $ = new e(); +($.name(`conformance`).description(`MCP Conformance Test Suite`).version(Ot), + $.command(`client`) + .description( + `Run conformance tests against a client implementation or start interactive mode` + ) + .option(`--command `, `Command to run the client`) + .option(`--scenario `, `Scenario to test`) + .option( + `--suite `, + `Run a suite of tests in parallel (e.g., "auth")` + ) + .option(`--timeout `, `Timeout in milliseconds`, `30000`) + .option(`--verbose`, `Show verbose output`) + .action(async (e) => { + try { + let t = parseInt(e.timeout, 10), + n = e.verbose ?? !1; + if (e.suite) { + e.command || + (console.error(`--command is required when using --suite`), + process.exit(1)); + let r = { + auth: _t, + metadata: He, + 'sep-835': () => _t().filter((e) => e.startsWith(`auth/scope-`)) + }, + i = e.suite.toLowerCase(); + r[i] || + (console.error(`Unknown suite: ${i}`), + console.error(`Available suites: ${Object.keys(r).join(`, `)}`), + process.exit(1)); + let a = r[i](); + console.log( + `Running ${i} suite (${a.length} scenarios) in parallel...\n` + ); + let o = await Promise.all( + a.map(async (n) => { + try { + return { + scenario: n, + checks: (await bt(e.command, n, t)).checks, + error: null + }; + } catch (e) { + return { + scenario: n, + checks: [ + { + id: n, + name: n, + description: `Failed to run scenario`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: e instanceof Error ? e.message : String(e) + } + ], + error: e + }; + } + }) + ); + console.log(` +=== SUITE SUMMARY === +`); + let s = 0, + c = 0, + l = 0; + for (let e of o) { + let t = e.checks.filter((e) => e.status === `SUCCESS`).length, + r = e.checks.filter((e) => e.status === `FAILURE`).length, + i = e.checks.filter((e) => e.status === `WARNING`).length; + ((s += t), (c += r), (l += i)); + let a = r === 0 && i === 0 ? `✓` : `✗`, + o = i > 0 ? `, ${i} warnings` : ``; + (console.log(`${a} ${e.scenario}: ${t} passed, ${r} failed${o}`), + n && + r > 0 && + e.checks + .filter((e) => e.status === `FAILURE`) + .forEach((e) => { + console.log( + ` - ${e.name}: ${e.errorMessage || e.description}` + ); + })); + } + (console.log(`\nTotal: ${s} passed, ${c} failed, ${l} warnings`), + process.exit(c > 0 || l > 0 ? 1 : 0)); + } + e.scenario || + (console.error(`Either --scenario or --suite is required`), + console.error(` +Available client scenarios:`), + K().forEach((e) => console.error(` - ${e}`)), + console.error(` +Available suites: auth, metadata, sep-835`), + process.exit(1)); + let r = Et.parse(e); + r.command || (await St(r.scenario, n), process.exit(0)); + let i = await bt(r.command, r.scenario, t), + { overallFailure: a } = xt(i.checks, n, i.clientOutput); + process.exit(a ? 1 : 0); + } catch (e) { + (e instanceof t && + (console.error(`Validation error:`), + e.errors.forEach((e) => { + console.error(` ${e.path.join(`.`)}: ${e.message}`); + }), + console.error(` +Available client scenarios:`), + K().forEach((e) => console.error(` - ${e}`)), + process.exit(1)), + console.error(`Client test error:`, e), + process.exit(1)); + } + }), + $.command(`server`) + .description(`Run conformance tests against a server implementation`) + .requiredOption(`--url `, `URL of the server to test`) + .option( + `--scenario `, + `Scenario to test (defaults to active suite if not specified)` + ) + .option( + `--suite `, + `Suite to run: "active" (default, excludes pending), "all", or "pending"`, + `active` + ) + .option(`--verbose`, `Show verbose output (JSON instead of pretty print)`) + .action(async (e) => { + try { + let t = Dt.parse(e), + n = e.verbose ?? !1; + if (t.scenario) { + let e = await Q(t.url, t.scenario), + { failed: r } = wt(e.checks, e.scenarioDescription, n); + process.exit(r > 0 ? 1 : 0); + } else { + let n = e.suite?.toLowerCase() || `active`, + r; + (n === `all` + ? (r = q()) + : n === `active` + ? (r = ht()) + : n === `pending` + ? (r = gt()) + : (console.error(`Unknown suite: ${n}`), + console.error(`Available suites: active, all, pending`), + process.exit(1)), + console.log( + `Running ${n} suite (${r.length} scenarios) against ${t.url}\n` + )); + let i = []; + for (let e of r) { + console.log(`\n=== Running scenario: ${e} ===`); + try { + let n = await Q(t.url, e); + i.push({ scenario: e, checks: n.checks }); + } catch (t) { + (console.error(`Failed to run scenario ${e}:`, t), + i.push({ + scenario: e, + checks: [ + { + id: e, + name: e, + description: `Failed to run scenario`, + status: `FAILURE`, + timestamp: new Date().toISOString(), + errorMessage: t instanceof Error ? t.message : String(t) + } + ] + })); + } + } + let { totalFailed: a } = Tt(i); + process.exit(a > 0 ? 1 : 0); + } + } catch (e) { + (e instanceof t && + (console.error(`Validation error:`), + e.errors.forEach((e) => { + console.error(` ${e.path.join(`.`)}: ${e.message}`); + }), + console.error(` +Available server scenarios:`), + q().forEach((e) => console.error(` - ${e}`)), + process.exit(1)), + console.error(`Server test error:`, e), + process.exit(1)); + } + }), + $.command(`list`) + .description(`List available test scenarios`) + .option(`--client`, `List client scenarios`) + .option(`--server`, `List server scenarios`) + .action((e) => { + ((e.server || (!e.client && !e.server)) && + (console.log(`Server scenarios (test against a server):`), + q().forEach((e) => console.log(` - ${e}`))), + (e.client || (!e.client && !e.server)) && + ((e.server || (!e.client && !e.server)) && console.log(``), + console.log(`Client scenarios (test against a client):`), + K().forEach((e) => console.log(` - ${e}`)))); + }), + $.parse()); +export {}; diff --git a/examples/clients/typescript/everything-client.ts b/examples/clients/typescript/everything-client.ts index 6618426..ac5ea5b 100644 --- a/examples/clients/typescript/everything-client.ts +++ b/examples/clients/typescript/everything-client.ts @@ -20,7 +20,7 @@ import { PrivateKeyJwtProvider } from '@modelcontextprotocol/sdk/client/auth-extensions.js'; import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import { ConformanceContextSchema } from '../../../src/schemas/context.js'; +import { ClientConformanceContextSchema } from '../../../src/schemas/context.js'; import { withOAuthRetry } from './helpers/withOAuthRetry.js'; import { logger } from './helpers/logger.js'; @@ -193,7 +193,7 @@ function parseContext() { if (!raw) { throw new Error('MCP_CONFORMANCE_CONTEXT not set'); } - return ConformanceContextSchema.parse(JSON.parse(raw)); + return ClientConformanceContextSchema.parse(JSON.parse(raw)); } /** diff --git a/src/scenarios/client/auth/client-credentials.ts b/src/scenarios/client/auth/client-credentials.ts index 76b2720..b82b5e2 100644 --- a/src/scenarios/client/auth/client-credentials.ts +++ b/src/scenarios/client/auth/client-credentials.ts @@ -50,9 +50,6 @@ export class ClientCredentialsJwtScenario implements Scenario { tokenEndpointAuthMethodsSupported: ['private_key_jwt'], tokenEndpointAuthSigningAlgValuesSupported: ['ES256'], onTokenRequest: async ({ grantType, body, timestamp, authBaseUrl }) => { - // Per RFC 7523bis, the audience MUST be the issuer identifier - // The SDK uses metadata.issuer as audience, which matches authBaseUrl - const issuerUrl = authBaseUrl; if (grantType !== 'client_credentials') { this.checks.push({ id: 'client-credentials-grant-type', @@ -96,16 +93,16 @@ export class ClientCredentialsJwtScenario implements Scenario { // Verify JWT signature and claims using the generated public key try { - // Per RFC 7523bis, audience MUST be the issuer identifier + // Per RFC 7523bis, audience MUST be the issuer identifier. // Per RFC 3986, URLs with and without trailing slash are equivalent, - // so we normalize by removing trailing slashes for comparison - const normalizedIssuerUrl = issuerUrl.replace(/\/+$/, ''); + // so we accept both forms for interoperability (e.g. Pydantic normalizes + // URLs by adding trailing slashes). + // Strip any trailing slashes first, then accept both the bare form + // and the form with exactly one trailing slash. + const withoutSlash = authBaseUrl.replace(/\/+$/, ''); + const withSlash = `${withoutSlash}/`; const { payload } = await jose.jwtVerify(clientAssertion, publicKey, { - audience: [ - issuerUrl, - normalizedIssuerUrl, - `${normalizedIssuerUrl}/` - ], + audience: [withoutSlash, withSlash], clockTolerance: 30 }); diff --git a/src/schemas/context.ts b/src/schemas/context.ts index 71e76fd..d7ea2a2 100644 --- a/src/schemas/context.ts +++ b/src/schemas/context.ts @@ -1,12 +1,12 @@ import { z } from 'zod'; /** - * Schema for conformance test context passed via MCP_CONFORMANCE_CONTEXT. + * Schema for client conformance test context passed via MCP_CONFORMANCE_CONTEXT. * * Each variant includes a `name` field matching the scenario name to enable * discriminated union parsing and type-safe access to scenario-specific fields. */ -export const ConformanceContextSchema = z.discriminatedUnion('name', [ +export const ClientConformanceContextSchema = z.discriminatedUnion('name', [ z.object({ name: z.literal('auth/client-credentials-jwt'), client_id: z.string(), @@ -20,4 +20,6 @@ export const ConformanceContextSchema = z.discriminatedUnion('name', [ }) ]); -export type ConformanceContext = z.infer; +export type ClientConformanceContext = z.infer< + typeof ClientConformanceContextSchema +>; From 32226324dea9328680251c4a33f617ae753f285f Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 16:51:47 +0000 Subject: [PATCH 5/9] Remove dist from tracking and add to gitignore --- .gitignore | 1 + dist/index.js | 6583 ------------------------------------------------- 2 files changed, 1 insertion(+), 6583 deletions(-) delete mode 100755 dist/index.js diff --git a/.gitignore b/.gitignore index 263abe7..8255b55 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ lefthook-local.yml dist.vscode/ .vscode/ .vscode/ +dist/ diff --git a/dist/index.js b/dist/index.js deleted file mode 100755 index cf03c8e..0000000 --- a/dist/index.js +++ /dev/null @@ -1,6583 +0,0 @@ -#!/usr/bin/env node -import { Command as e } from 'commander'; -import { ZodError as t, z as n } from 'zod'; -import { spawn as r } from 'child_process'; -import { promises as i } from 'fs'; -import a from 'path'; -import o from 'http'; -import { Server as s } from '@modelcontextprotocol/sdk/server/index.js'; -import { StreamableHTTPServerTransport as c } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { - CallToolRequestSchema as l, - CallToolResultSchema as u, - CreateMessageRequestSchema as d, - ElicitRequestSchema as f, - ElicitResultSchema as p, - ErrorCode as m, - ListToolsRequestSchema as h, - LoggingMessageNotificationSchema as g, - McpError as _, - ProgressNotificationSchema as v -} from '@modelcontextprotocol/sdk/types.js'; -import y from 'express'; -import { randomUUID as ee } from 'crypto'; -import { Client as b } from '@modelcontextprotocol/sdk/client/index.js'; -import { StreamableHTTPClientTransport as x } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; -import { EventSourceParserStream as S } from 'eventsource-parser/stream'; -import { requireBearerAuth as te } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js'; -import * as C from 'jose'; -var ne = Object.defineProperty, - re = ((e) => { - let t = {}; - for (var n in e) ne(t, n, { get: e[n], enumerable: !0 }); - return t; - })({ - createClientInitializationCheck: () => ae, - createServerInfoCheck: () => ie - }); -function ie(e) { - return { - id: `server-info`, - name: `ServerInfo`, - description: `Test server info returned to client`, - status: `INFO`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `MCP-Lifecycle`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle` - } - ], - details: { serverName: e.name, serverVersion: e.version } - }; -} -const w = [`2025-06-18`, `2025-11-25`]; -function ae(e, t = `2025-11-25`) { - let n = e?.protocolVersion, - r = (w.includes(t) ? w : [...w, t]).includes(n), - i = []; - return ( - n || i.push(`Protocol version not provided`), - r || i.push(`Version mismatch: expected ${t}, got ${n}`), - e?.clientInfo?.name || i.push(`Client name missing`), - e?.clientInfo?.version || i.push(`Client version missing`), - { - id: `mcp-client-initialization`, - name: `MCPClientInitialization`, - description: `Validates that MCP client properly initializes with server`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `MCP-Lifecycle`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle` - } - ], - details: { - protocolVersionSent: n, - expectedSpecVersion: t, - versionMatch: r, - clientName: e?.clientInfo?.name, - clientVersion: e?.clientInfo?.version - }, - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - logs: i.length > 0 ? i : void 0 - } - ); -} -const T = re; -var oe = class { - constructor() { - ((this.name = `initialize`), - (this.description = `Tests MCP client initialization handshake`), - (this.server = null), - (this.checks = []), - (this.port = 0)); - } - async start() { - return new Promise((e, t) => { - ((this.server = o.createServer((e, t) => { - this.handleRequest(e, t); - })), - this.server.on(`error`, t), - this.server.listen(0, () => { - let n = this.server.address(); - n && typeof n == `object` - ? ((this.port = n.port), - e({ serverUrl: `http://localhost:${this.port}` })) - : t(Error(`Failed to get server address`)); - })); - }); - } - async stop() { - return new Promise((e, t) => { - this.server - ? this.server.close((n) => { - n ? t(n) : ((this.server = null), e()); - }) - : e(); - }); - } - getChecks() { - return this.checks; - } - handleRequest(e, t) { - let n = ``; - (e.on(`data`, (e) => { - n += e.toString(); - }), - e.on(`end`, () => { - try { - let e = JSON.parse(n); - e.method === `initialize` - ? this.handleInitialize(e, t) - : e.method === `tools/list` - ? this.handleToolsList(e, t) - : (t.writeHead(200, { 'Content-Type': `application/json` }), - t.end( - JSON.stringify({ jsonrpc: `2.0`, id: e.id, result: {} }) - )); - } catch (e) { - (t.writeHead(400, { 'Content-Type': `application/json` }), - t.end( - JSON.stringify({ - jsonrpc: `2.0`, - error: { code: -32700, message: `Parse error ${e}` } - }) - )); - } - })); - } - handleInitialize(e, t) { - let n = e.params, - r = T.createClientInitializationCheck(n); - this.checks.push(r); - let i = { name: `test-server`, version: `1.0.0` }; - this.checks.push(T.createServerInfoCheck(i)); - let a = [`2025-06-18`, `2025-11-25`], - o = n?.protocolVersion, - s = a.includes(o) ? o : `2025-11-25`, - c = { - jsonrpc: `2.0`, - id: e.id, - result: { protocolVersion: s, serverInfo: i, capabilities: {} } - }; - (t.writeHead(200, { 'Content-Type': `application/json` }), - t.end(JSON.stringify(c))); - } - handleToolsList(e, t) { - let n = { jsonrpc: `2.0`, id: e.id, result: { tools: [] } }; - (t.writeHead(200, { 'Content-Type': `application/json` }), - t.end(JSON.stringify(n))); - } -}; -function E(e, t) { - return (n, r, i) => { - let a = `Received ${n.method} request for ${n.path}`, - o = { method: n.method, path: n.path, body: n.body }; - if ( - (Object.keys(n.query).length > 0 && (o.query = n.query), - t.mcpRoute && - n.path === t.mcpRoute && - n.get(`content-type`)?.includes(`application/json`) && - n.body && - typeof n.body == `object` && - n.body.method) - ) { - let e = n.body.method; - ((a += ` (method: ${e})`), (o.mcpMethod = e)); - } - e.push({ - id: t.incomingId, - name: t.incomingId.charAt(0).toUpperCase() + t.incomingId.slice(1), - description: a, - status: `INFO`, - timestamp: new Date().toISOString(), - details: o - }); - let s = r.write.bind(r), - c = r.end.bind(r), - l = []; - ((r.write = function (e, ...t) { - return (l.push(e), s(e, ...t)); - }), - (r.end = function (i, ...a) { - i && l.push(i); - let s = l.map((e) => (typeof e == `string` ? Buffer.from(e) : e)), - u = Buffer.concat(s).toString(`utf8`), - d = `Sent ${r.statusCode} response for ${n.method} ${n.path}`, - f = { method: n.method, path: n.path, statusCode: r.statusCode }; - o.mcpMethod && - ((d += ` (method: ${o.mcpMethod})`), (f.mcpMethod = o.mcpMethod)); - let p = r.getHeaders(); - if ((Object.keys(p).length > 0 && (f.headers = p), u)) - try { - f.body = JSON.parse(u); - } catch { - f.body = u; - } - return ( - e.push({ - id: t.outgoingId, - name: t.outgoingId.charAt(0).toUpperCase() + t.outgoingId.slice(1), - description: d, - status: `INFO`, - timestamp: new Date().toISOString(), - details: f - }), - c(i, ...a) - ); - }), - i()); - }; -} -function se(e) { - let t = new s( - { name: `add-numbers-server`, version: `1.0.0` }, - { capabilities: { tools: {} } } - ); - (t.setRequestHandler(h, async () => ({ - tools: [ - { - name: `add_numbers`, - description: `Add two numbers together`, - inputSchema: { - type: `object`, - properties: { - a: { type: `number`, description: `First number` }, - b: { type: `number`, description: `Second number` } - }, - required: [`a`, `b`] - } - } - ] - })), - t.setRequestHandler(l, async (t) => { - if (t.params.name === `add_numbers`) { - let { a: n, b: r } = t.params.arguments, - i = n + r; - return ( - e.push({ - id: `tool-add-numbers`, - name: `ToolAddNumbers`, - description: `Validates that the add_numbers tool works correctly`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `MCP-Tools`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ], - details: { a: n, b: r, result: i } - }), - { - content: [ - { type: `text`, text: `The sum of ${n} and ${r} is ${i}` } - ] - } - ); - } - throw Error(`Unknown tool: ${t.params.name}`); - })); - let n = y(); - return ( - n.use(y.json()), - n.use( - E(e, { - incomingId: `incoming-request`, - outgoingId: `outgoing-response`, - mcpRoute: `/mcp` - }) - ), - n.post(`/mcp`, async (e, n) => { - let r = new c({ sessionIdGenerator: void 0 }); - (await t.connect(r), await r.handleRequest(e, n, e.body)); - }), - n - ); -} -var ce = class { - constructor() { - ((this.name = `tools_call`), - (this.description = `Tests calling tools with various parameter types`), - (this.app = null), - (this.httpServer = null), - (this.checks = [])); - } - async start() { - return ( - (this.checks = []), - (this.app = se(this.checks)), - (this.httpServer = this.app.listen(0)), - { serverUrl: `http://localhost:${this.httpServer.address().port}/mcp` } - ); - } - async stop() { - ((this.httpServer &&= - (await new Promise((e) => this.httpServer.close(e)), null)), - (this.app = null)); - } - getChecks() { - for (let e of [`tool-add-numbers`]) - this.checks.find((t) => t.id === e) || - this.checks.push({ - id: e, - name: `ToolAddNumbers`, - description: `Validates that the add_numbers tool works correctly`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - details: { message: `Tool was not called by client` }, - specReferences: [ - { - id: `MCP-Tools`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ] - }); - return this.checks; - } -}; -function le(e) { - return !!( - typeof e == `object` && - e && - `method` in e && - e.method === `initialize` - ); -} -function ue(e) { - let t = {}, - n = {}, - r = () => { - let t = new s( - { name: `elicitation-defaults-test-server`, version: `1.0.0` }, - { capabilities: { tools: {} } } - ); - return ( - t.setRequestHandler(h, async () => ({ - tools: [ - { - name: `test_client_elicitation_defaults`, - description: `Tests that client applies defaults for omitted elicitation fields`, - inputSchema: { type: `object`, properties: {}, required: [] } - } - ] - })), - t.setRequestHandler(l, async (n) => { - if (n.params.name === `test_client_elicitation_defaults`) - try { - let n = await t.request( - { - method: `elicitation/create`, - params: { - message: `Test client default value handling - please accept with defaults`, - requestedSchema: { - type: `object`, - properties: { - name: { - type: `string`, - description: `User name`, - default: `John Doe` - }, - age: { - type: `integer`, - description: `User age`, - default: 30 - }, - score: { - type: `number`, - description: `User score`, - default: 95.5 - }, - status: { - type: `string`, - description: `User status`, - enum: [`active`, `inactive`, `pending`], - default: `active` - }, - verified: { - type: `boolean`, - description: `Verification status`, - default: !0 - } - }, - required: [] - } - } - }, - p - ); - if (n.action !== `accept`) - return ( - e.push({ - id: `client-elicitation-sep1034-general`, - name: `ClientElicitationSEP1034General`, - description: `Client accepts elicitation request`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Expected action 'accept', got '${n.action}'`, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ] - }), - { - content: [ - { type: `text`, text: `Elicitation was not accepted` } - ] - } - ); - let r = n.content || {}, - i = []; - (`name` in r - ? typeof r.name != `string` && - i.push(`Expected string for "name", got ${typeof r.name}`) - : i.push( - `Field "name" missing - should have default "John Doe"` - ), - e.push({ - id: `client-elicitation-sep1034-string-default`, - name: `ClientElicitationSEP1034StringDefault`, - description: `Client applies string default value for elicitation`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { - field: `name`, - expectedDefault: `John Doe`, - receivedValue: r.name - } - })); - let a = []; - (`age` in r - ? typeof r.age != `number` && - a.push(`Expected number for "age", got ${typeof r.age}`) - : a.push(`Field "age" missing - should have default 30`), - e.push({ - id: `client-elicitation-sep1034-integer-default`, - name: `ClientElicitationSEP1034IntegerDefault`, - description: `Client applies integer default value for elicitation`, - status: a.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: a.length > 0 ? a.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { - field: `age`, - expectedDefault: 30, - receivedValue: r.age - } - })); - let o = []; - (`score` in r - ? typeof r.score != `number` && - o.push(`Expected number for "score", got ${typeof r.score}`) - : o.push(`Field "score" missing - should have default 95.5`), - e.push({ - id: `client-elicitation-sep1034-number-default`, - name: `ClientElicitationSEP1034NumberDefault`, - description: `Client applies number default value for elicitation`, - status: o.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: o.length > 0 ? o.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { - field: `score`, - expectedDefault: 95.5, - receivedValue: r.score - } - })); - let s = []; - (`status` in r - ? typeof r.status == `string` - ? [`active`, `inactive`, `pending`].includes(r.status) || - s.push(`Value "${r.status}" is not a valid enum member`) - : s.push( - `Expected string for "status", got ${typeof r.status}` - ) - : s.push( - `Field "status" missing - should have default "active"` - ), - e.push({ - id: `client-elicitation-sep1034-enum-default`, - name: `ClientElicitationSEP1034EnumDefault`, - description: `Client applies enum default value for elicitation`, - status: s.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: s.length > 0 ? s.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { - field: `status`, - expectedDefault: `active`, - receivedValue: r.status - } - })); - let c = []; - return ( - `verified` in r - ? typeof r.verified != `boolean` && - c.push( - `Expected boolean for "verified", got ${typeof r.verified}` - ) - : c.push( - `Field "verified" missing - should have default true` - ), - e.push({ - id: `client-elicitation-sep1034-boolean-default`, - name: `ClientElicitationSEP1034BooleanDefault`, - description: `Client applies boolean default value for elicitation`, - status: c.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: c.length > 0 ? c.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { - field: `verified`, - expectedDefault: !0, - receivedValue: r.verified - } - }), - { - content: [ - { - type: `text`, - text: `Elicitation completed: ${JSON.stringify(r)}` - } - ] - } - ); - } catch (t) { - return ( - e.push({ - id: `client-elicitation-sep1034-general`, - name: `ClientElicitationSEP1034General`, - description: `Client handles elicitation with defaults`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Elicitation failed: ${t.message}`, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ] - }), - { - content: [ - { type: `text`, text: `Elicitation error: ${t.message}` } - ] - } - ); - } - throw Error(`Unknown tool: ${n.params.name}`); - }), - t - ); - }, - i = y(); - return ( - i.use(y.json()), - i.use( - E(e, { - incomingId: `incoming-request`, - outgoingId: `outgoing-response`, - mcpRoute: `/mcp` - }) - ), - i.post(`/mcp`, async (e, i) => { - let a = e.headers[`mcp-session-id`]; - try { - let o; - if (a && t[a]) o = t[a]; - else if (!a && le(e.body)) { - let a = r(); - ((o = new c({ - sessionIdGenerator: () => ee(), - onsessioninitialized: (e) => { - ((t[e] = o), (n[e] = a)); - } - })), - (o.onclose = () => { - let e = o.sessionId; - e && t[e] && (delete t[e], n[e] && (n[e].close(), delete n[e])); - }), - await a.connect(o), - await o.handleRequest(e, i, e.body)); - return; - } else { - i.status(400).json({ - jsonrpc: `2.0`, - error: { code: -32e3, message: `Invalid or missing session ID` }, - id: null - }); - return; - } - await o.handleRequest(e, i, e.body); - } catch { - i.headersSent || - i.status(500).json({ - jsonrpc: `2.0`, - error: { code: -32603, message: `Internal server error` }, - id: null - }); - } - }), - i.get(`/mcp`, async (e, n) => { - let r = e.headers[`mcp-session-id`]; - if (!r || !t[r]) { - n.status(400).send(`Invalid or missing session ID`); - return; - } - try { - await t[r].handleRequest(e, n); - } catch { - n.headersSent || n.status(500).send(`Error establishing SSE stream`); - } - }), - i.delete(`/mcp`, async (e, n) => { - let r = e.headers[`mcp-session-id`]; - if (!r || !t[r]) { - n.status(400).send(`Invalid or missing session ID`); - return; - } - try { - await t[r].handleRequest(e, n); - } catch { - n.headersSent || n.status(500).send(`Error handling termination`); - } - }), - { - app: i, - cleanup: () => { - for (let e of Object.keys(t)) n[e] && n[e].close(); - } - } - ); -} -var de = class { - constructor() { - ((this.name = `elicitation-sep1034-client-defaults`), - (this.description = `Tests client applies default values for omitted elicitation fields (SEP-1034)`), - (this.app = null), - (this.httpServer = null), - (this.checks = []), - (this.cleanup = null)); - } - async start() { - this.checks = []; - let { app: e, cleanup: t } = ue(this.checks); - return ( - (this.app = e), - (this.cleanup = t), - (this.httpServer = this.app.listen(0)), - { serverUrl: `http://localhost:${this.httpServer.address().port}/mcp` } - ); - } - async stop() { - ((this.cleanup &&= (this.cleanup(), null)), - (this.httpServer &&= - (await new Promise((e) => this.httpServer.close(e)), null)), - (this.app = null)); - } - getChecks() { - for (let e of [ - `client-elicitation-sep1034-string-default`, - `client-elicitation-sep1034-integer-default`, - `client-elicitation-sep1034-number-default`, - `client-elicitation-sep1034-enum-default`, - `client-elicitation-sep1034-boolean-default` - ]) - this.checks.find((t) => t.id === e) || - this.checks.push({ - id: e, - name: e.replace(/-/g, ``), - description: `Server applies ${e.split(`-`)[4]} default for elicitation`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - details: { message: `Tool was not called by client` }, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ] - }); - return this.checks; - } - }, - fe = class { - constructor() { - ((this.name = `sse-retry`), - (this.description = `Tests that client respects SSE retry field timing and reconnects properly (SEP-1699)`), - (this.server = null), - (this.checks = []), - (this.port = 0), - (this.toolStreamCloseTime = null), - (this.getReconnectionTime = null), - (this.getConnectionCount = 0), - (this.lastEventIds = []), - (this.retryValue = 500), - (this.eventIdCounter = 0), - (this.sessionId = `session-${Date.now()}`), - (this.pendingToolCallId = null), - (this.getResponseStream = null), - (this.EARLY_TOLERANCE = 50), - (this.LATE_TOLERANCE = 200), - (this.VERY_LATE_MULTIPLIER = 2)); - } - async start() { - return new Promise((e, t) => { - ((this.server = o.createServer((e, t) => { - this.handleRequest(e, t); - })), - this.server.on(`error`, t), - this.server.listen(0, () => { - let n = this.server.address(); - n && typeof n == `object` - ? ((this.port = n.port), - e({ serverUrl: `http://localhost:${this.port}` })) - : t(Error(`Failed to get server address`)); - })); - }); - } - async stop() { - return new Promise((e, t) => { - this.server - ? this.server.close((n) => { - n ? t(n) : ((this.server = null), e()); - }) - : e(); - }); - } - getChecks() { - return (this.generateChecks(), this.checks); - } - handleRequest(e, t) { - if (e.method === `GET`) { - (this.getConnectionCount++, - (this.getReconnectionTime = performance.now())); - let n = e.headers[`last-event-id`], - r = n - ? `Received GET request for ${e.url} (Last-Event-ID: ${n})` - : `Received GET request for ${e.url}`; - (this.checks.push({ - id: `incoming-request`, - name: `IncomingRequest`, - description: r, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - method: `GET`, - url: e.url, - headers: e.headers, - connectionCount: this.getConnectionCount - } - }), - n && this.lastEventIds.push(n), - this.handleGetSSEStream(e, t)); - } else - e.method === `POST` - ? this.handlePostRequest(e, t) - : (t.writeHead(405), t.end(`Method Not Allowed`)); - } - handleGetSSEStream(e, t) { - (t.writeHead(200, { - 'Content-Type': `text/event-stream`, - 'Cache-Control': `no-cache`, - Connection: `keep-alive`, - 'mcp-session-id': this.sessionId - }), - this.eventIdCounter++); - let n = `event-${this.eventIdCounter}`, - r = `id: ${n}\nretry: ${this.retryValue}\ndata: \n\n`; - if ( - (t.write(r), - this.checks.push({ - id: `outgoing-sse-event`, - name: `OutgoingSseEvent`, - description: `Sent SSE priming event on GET stream (id: ${n}, retry: ${this.retryValue}ms)`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - eventId: n, - retryMs: this.retryValue, - eventType: `priming`, - raw: r - } - }), - (this.getResponseStream = t), - this.pendingToolCallId !== null) - ) { - let e = { - jsonrpc: `2.0`, - id: this.pendingToolCallId, - result: { - content: [ - { - type: `text`, - text: `Reconnection test completed successfully` - } - ] - } - }, - n = `event-${++this.eventIdCounter}`, - r = `event: message\nid: ${n}\ndata: ${JSON.stringify(e)}\n\n`; - (t.write(r), - this.checks.push({ - id: `outgoing-sse-event`, - name: `OutgoingSseEvent`, - description: `Sent tool response on GET stream after reconnection (id: ${n})`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - eventId: n, - eventType: `message`, - jsonrpcId: this.pendingToolCallId, - body: e, - raw: r - } - }), - (this.pendingToolCallId = null)); - } - } - handlePostRequest(e, t) { - let n = ``; - (e.on(`data`, (e) => { - n += e.toString(); - }), - e.on(`end`, () => { - try { - let r = JSON.parse(n); - (this.checks.push({ - id: `incoming-request`, - name: `IncomingRequest`, - description: `Received POST request for ${e.url} (method: ${r.method})`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - method: `POST`, - url: e.url, - jsonrpcMethod: r.method, - jsonrpcId: r.id - } - }), - r.method === `initialize` - ? this.handleInitialize(e, t, r) - : r.method === `tools/list` - ? this.handleToolsList(t, r) - : r.method === `tools/call` - ? this.handleToolsCall(t, r) - : r.id === void 0 - ? (t.writeHead(202), t.end()) - : (t.writeHead(200, { - 'Content-Type': `application/json`, - 'mcp-session-id': this.sessionId - }), - t.end( - JSON.stringify({ - jsonrpc: `2.0`, - id: r.id, - result: {} - }) - ))); - } catch (e) { - (t.writeHead(400, { 'Content-Type': `application/json` }), - t.end( - JSON.stringify({ - jsonrpc: `2.0`, - error: { code: -32700, message: `Parse error: ${e}` } - }) - )); - } - })); - } - handleInitialize(e, t, n) { - t.writeHead(200, { - 'Content-Type': `application/json`, - 'mcp-session-id': this.sessionId - }); - let r = { - jsonrpc: `2.0`, - id: n.id, - result: { - protocolVersion: `2025-03-26`, - serverInfo: { name: `sse-retry-test-server`, version: `1.0.0` }, - capabilities: { tools: {} } - } - }; - (t.end(JSON.stringify(r)), - this.checks.push({ - id: `outgoing-response`, - name: `OutgoingResponse`, - description: `Sent initialize response`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { jsonrpcId: n.id, body: r } - })); - } - handleToolsList(e, t) { - e.writeHead(200, { - 'Content-Type': `application/json`, - 'mcp-session-id': this.sessionId - }); - let n = { - jsonrpc: `2.0`, - id: t.id, - result: { - tools: [ - { - name: `test_reconnection`, - description: `A tool that triggers SSE stream closure to test client reconnection behavior`, - inputSchema: { type: `object`, properties: {}, required: [] } - } - ] - } - }; - (e.end(JSON.stringify(n)), - this.checks.push({ - id: `outgoing-response`, - name: `OutgoingResponse`, - description: `Sent tools/list response`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { jsonrpcId: t.id, body: n } - })); - } - handleToolsCall(e, t) { - ((this.pendingToolCallId = t.id), - e.writeHead(200, { - 'Content-Type': `text/event-stream`, - 'Cache-Control': `no-cache`, - Connection: `keep-alive`, - 'mcp-session-id': this.sessionId - }), - this.eventIdCounter++); - let n = `event-${this.eventIdCounter}`, - r = `id: ${n}\nretry: ${this.retryValue}\ndata: \n\n`; - (e.write(r), - this.checks.push({ - id: `outgoing-sse-event`, - name: `OutgoingSseEvent`, - description: `Sent SSE priming event for tools/call (id: ${n}, retry: ${this.retryValue}ms)`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - eventId: n, - retryMs: this.retryValue, - eventType: `priming`, - raw: r - } - }), - setTimeout(() => { - ((this.toolStreamCloseTime = performance.now()), - this.checks.push({ - id: `outgoing-stream-close`, - name: `OutgoingStreamClose`, - description: `Closed tools/call SSE stream to trigger client reconnection`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - retryMs: this.retryValue, - pendingToolCallId: this.pendingToolCallId - } - }), - e.end()); - }, 50)); - } - generateChecks() { - if (this.getConnectionCount < 1) { - this.checks.push({ - id: `client-sse-graceful-reconnect`, - name: `ClientGracefulReconnect`, - description: `Client reconnects via GET after SSE stream is closed gracefully`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Client did not attempt GET reconnection after stream closure. Client should treat graceful stream close as reconnectable.`, - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - getConnectionCount: this.getConnectionCount, - toolStreamCloseTime: this.toolStreamCloseTime, - retryValue: this.retryValue - } - }); - return; - } - if ( - (this.checks.push({ - id: `client-sse-graceful-reconnect`, - name: `ClientGracefulReconnect`, - description: `Client reconnects via GET after SSE stream is closed gracefully`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { getConnectionCount: this.getConnectionCount } - }), - this.toolStreamCloseTime !== null && this.getReconnectionTime !== null) - ) { - let e = this.getReconnectionTime - this.toolStreamCloseTime, - t = this.retryValue - this.EARLY_TOLERANCE, - n = this.retryValue + this.LATE_TOLERANCE, - r = e < t, - i = e > n, - a = e > this.retryValue * this.VERY_LATE_MULTIPLIER, - o = !r && !i, - s = `SUCCESS`, - c; - (r - ? ((s = `FAILURE`), - (c = `Client reconnected too early (${e.toFixed(0)}ms instead of ${this.retryValue}ms). Client MUST respect the retry field and wait the specified time.`)) - : a - ? ((s = `FAILURE`), - (c = `Client reconnected very late (${e.toFixed(0)}ms instead of ${this.retryValue}ms). Client appears to be ignoring the retry field and using its own backoff strategy.`)) - : i && - ((s = `WARNING`), - (c = `Client reconnected slightly late (${e.toFixed(0)}ms instead of ${this.retryValue}ms). This is acceptable but may indicate network delays.`)), - this.checks.push({ - id: `client-sse-retry-timing`, - name: `ClientRespectsRetryField`, - description: `Client MUST respect the retry field, waiting the given number of milliseconds before attempting to reconnect`, - status: s, - timestamp: new Date().toISOString(), - errorMessage: c, - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - expectedRetryMs: this.retryValue, - actualDelayMs: Math.round(e), - minAcceptableMs: t, - maxAcceptableMs: n, - veryLateThresholdMs: this.retryValue * this.VERY_LATE_MULTIPLIER, - earlyToleranceMs: this.EARLY_TOLERANCE, - lateToleranceMs: this.LATE_TOLERANCE, - withinTolerance: o, - tooEarly: r, - slightlyLate: i, - veryLate: a, - getConnectionCount: this.getConnectionCount - } - })); - } else - this.checks.push({ - id: `client-sse-retry-timing`, - name: `ClientRespectsRetryField`, - description: `Client MUST respect the retry field timing`, - status: `WARNING`, - timestamp: new Date().toISOString(), - errorMessage: `Could not measure timing - tool stream close time or GET reconnection time not recorded`, - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - toolStreamCloseTime: this.toolStreamCloseTime, - getReconnectionTime: this.getReconnectionTime - } - }); - let e = this.lastEventIds.length > 0 && this.lastEventIds[0] !== void 0; - this.checks.push({ - id: `client-sse-last-event-id`, - name: `ClientSendsLastEventId`, - description: `Client SHOULD send Last-Event-ID header on reconnection for resumability`, - status: e ? `SUCCESS` : `WARNING`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - hasLastEventId: e, - lastEventIds: this.lastEventIds, - getConnectionCount: this.getConnectionCount - }, - errorMessage: e - ? void 0 - : `Client did not send Last-Event-ID header on reconnection. This is a SHOULD requirement for resumability.` - }); - } - }; -async function D(e) { - let t = new b( - { name: `conformance-test-client`, version: `1.0.0` }, - { capabilities: { sampling: {}, elicitation: {} } } - ), - n = new x(new URL(e)); - return ( - await t.connect(n), - { - client: t, - close: async () => { - await t.close(); - } - } - ); -} -var pe = class { - constructor(e) { - ((this.loggingNotifications = []), - (this.progressNotifications = []), - e.setNotificationHandler(g, (e) => { - this.loggingNotifications.push(e); - }), - e.setNotificationHandler(v, (e) => { - this.progressNotifications.push(e); - })); - } - getLoggingNotifications() { - return this.loggingNotifications; - } - getProgressNotifications() { - return this.progressNotifications; - } - getNotifications() { - return this.loggingNotifications; - } - }, - me = class { - constructor() { - ((this.name = `server-initialize`), - (this.description = `Test basic server initialization handshake. - -**Server Implementation Requirements:** - -**Endpoint**: \`initialize\` - -**Requirements**: -- Accept \`initialize\` request with client info and capabilities -- Return valid initialize response with server info, protocol version, and capabilities -- Accept \`initialized\` notification from client after handshake - -This test verifies the server can complete the two-phase initialization handshake successfully.`)); - } - async run(e) { - let t = []; - try { - let n = await D(e); - (t.push({ - id: `server-initialize`, - name: `ServerInitialize`, - description: `Server responds to initialize request with valid structure`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `MCP-Initialize`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization` - } - ], - details: { serverUrl: e, connected: !0 } - }), - await n.close()); - } catch (e) { - t.push({ - id: `server-initialize`, - name: `ServerInitialize`, - description: `Server responds to initialize request with valid structure`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed to initialize: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Initialize`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#initialization` - } - ] - }); - } - return t; - } - }, - he = class { - constructor() { - ((this.name = `logging-set-level`), - (this.description = - 'Test setting logging level.\n\n**Server Implementation Requirements:**\n\n**Endpoint**: `logging/setLevel`\n\n**Requirements**:\n- Accept log level setting\n- Filter subsequent log notifications based on level\n- Return empty object `{}`\n\n**Log Levels** (in order of severity):\n- `debug`\n- `info`\n- `notice`\n- `warning`\n- `error`\n- `critical`\n- `alert`\n- `emergency`')); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.setLoggingLevel(`info`), - i = []; - (r && - Object.keys(r).length > 0 && - i.push(`Expected empty object {} response`), - t.push({ - id: `logging-set-level`, - name: `LoggingSetLevel`, - description: `Server accepts logging level setting`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Logging`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging` - } - ], - details: { result: r } - }), - await n.close()); - } catch (e) { - t.push({ - id: `logging-set-level`, - name: `LoggingSetLevel`, - description: `Server accepts logging level setting`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Logging`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging` - } - ] - }); - } - return t; - } - }, - ge = class { - constructor() { - ((this.name = `ping`), - (this.description = `Test ping utility for connection health check. - -**Server Implementation Requirements:** - -**Endpoint**: \`ping\` - -**Requirements**: -- Accept ping request with no parameters -- Respond promptly with empty object \`{}\` - -**Request Format**: - -\`\`\`json -{ - "jsonrpc": "2.0", - "id": "123", - "method": "ping" -} -\`\`\` - -**Response Format**: - -\`\`\`json -{ - "jsonrpc": "2.0", - "id": "123", - "result": {} -} -\`\`\` - -**Implementation Note**: The ping utility allows either party to verify that their counterpart is still responsive and the connection is alive.`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.ping(), - i = []; - (r && - Object.keys(r).length > 0 && - i.push(`Expected empty object {} response`), - t.push({ - id: `ping`, - name: `Ping`, - description: `Server responds to ping requests`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Ping`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping` - } - ], - details: { result: r } - }), - await n.close()); - } catch (e) { - t.push({ - id: `ping`, - name: `Ping`, - description: `Server responds to ping requests`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Ping`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping` - } - ] - }); - } - return t; - } - }, - _e = class { - constructor() { - ((this.name = `completion-complete`), - (this.description = `Test completion endpoint. - -**Server Implementation Requirements:** - -**Endpoint**: \`completion/complete\` - -**Requirements**: -- Accept completion requests for prompt or resource template arguments -- Provide contextual suggestions based on partial input -- Return array of completion values ranked by relevance - -**Request Format**: - -\`\`\`json -{ - "method": "completion/complete", - "params": { - "ref": { - "type": "ref/prompt", - "name": "test_prompt_with_arguments" - }, - "argument": { - "name": "arg1", - "value": "par" - } - } -} -\`\`\` - -**Response Format**: - -\`\`\`json -{ - "completion": { - "values": ["paris", "park", "party"], - "total": 150, - "hasMore": false - } -} -\`\`\` - -**Implementation Note**: For conformance testing, completion support can be minimal or return empty arrays. The capability just needs to be declared and the endpoint must respond correctly.`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.complete({ - ref: { type: `ref/prompt`, name: `test_prompt_with_arguments` }, - argument: { name: `arg1`, value: `test` } - }), - i = []; - (r.completion - ? (r.completion.values || - i.push(`Missing values array in completion`), - Array.isArray(r.completion.values) || - i.push(`completion.values is not an array`)) - : i.push(`Missing completion field`), - t.push({ - id: `completion-complete`, - name: `CompletionComplete`, - description: `Server responds to completion requests`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Completion`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion` - } - ], - details: { result: r } - }), - await n.close()); - } catch (e) { - t.push({ - id: `completion-complete`, - name: `CompletionComplete`, - description: `Server responds to completion requests`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Completion`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/completion` - } - ] - }); - } - return t; - } - }, - ve = class { - constructor() { - ((this.name = `tools-list`), - (this.description = `Test listing available tools. - -**Server Implementation Requirements:** - -**Endpoint**: \`tools/list\` - -**Requirements**: -- Return array of all available tools -- Each tool MUST have: - - \`name\` (string) - - \`description\` (string) - - \`inputSchema\` (valid JSON Schema object)`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.listTools(), - i = []; - (r.tools - ? (Array.isArray(r.tools) || i.push(`tools is not an array`), - r.tools.forEach((e, t) => { - (e.name || i.push(`Tool ${t}: missing name`), - e.description || i.push(`Tool ${t}: missing description`), - e.inputSchema || i.push(`Tool ${t}: missing inputSchema`)); - })) - : i.push(`Missing tools array`), - t.push({ - id: `tools-list`, - name: `ToolsList`, - description: `Server lists available tools with valid structure`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Tools-List`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools` - } - ], - details: { - toolCount: r.tools?.length || 0, - tools: r.tools?.map((e) => e.name) - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-list`, - name: `ToolsList`, - description: `Server lists available tools with valid structure`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Tools-List`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#listing-tools` - } - ] - }); - } - return t; - } - }, - ye = class { - constructor() { - ((this.name = `tools-call-simple-text`), - (this.description = `Test calling a tool that returns simple text. - -**Server Implementation Requirements:** - -Implement tool \`test_simple_text\` with no arguments that returns: - -\`\`\`json -{ - "content": [ - { - "type": "text", - "text": "This is a simple text response for testing." - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.callTool({ name: `test_simple_text` }), - i = [], - a = r.content; - (a || i.push(`Missing content array`), - Array.isArray(a) || i.push(`content is not an array`), - a && a.length === 0 && i.push(`content array is empty`)); - let o = a && a.find((e) => e.type === `text`); - (o || i.push(`No text content found`), - o && !o.text && i.push(`Text content missing text field`), - t.push({ - id: `tools-call-simple-text`, - name: `ToolsCallSimpleText`, - description: `Tool returns simple text content`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ], - details: { result: r } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-simple-text`, - name: `ToolsCallSimpleText`, - description: `Tool returns simple text content`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ] - }); - } - return t; - } - }, - be = class { - constructor() { - ((this.name = `tools-call-image`), - (this.description = `Test calling a tool that returns image content. - -**Server Implementation Requirements:** - -Implement tool \`test_image_content\` with no arguments that returns: - -\`\`\`json -{ - "content": [ - { - "type": "image", - "data": "", - "mimeType": "image/png" - } - ] -} -\`\`\` - -**Implementation Note**: Use a minimal test image (e.g., 1x1 red pixel PNG)`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.callTool({ - name: `test_image_content`, - arguments: {} - }), - i = [], - a = r.content; - a || i.push(`Missing content array`); - let o = a && a.find((e) => e.type === `image`); - (o || i.push(`No image content found`), - o && !o.data && i.push(`Image content missing data field`), - o && !o.mimeType && i.push(`Image content missing mimeType`), - t.push({ - id: `tools-call-image`, - name: `ToolsCallImage`, - description: `Tool returns image content`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ], - details: { mimeType: o?.mimeType, hasData: !!o?.data } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-image`, - name: `ToolsCallImage`, - description: `Tool returns image content`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ] - }); - } - return t; - } - }, - xe = class { - constructor() { - ((this.name = `tools-call-mixed-content`), - (this.description = `Test tool returning multiple content types. - -**Server Implementation Requirements:** - -Implement tool \`test_multiple_content_types\` with no arguments that returns: - -\`\`\`json -{ - "content": [ - { - "type": "text", - "text": "Multiple content types test:" - }, - { - "type": "image", - "data": "", - "mimeType": "image/png" - }, - { - "type": "resource", - "resource": { - "uri": "test://mixed-content-resource", - "mimeType": "application/json", - "text": "{"test":"data","value":123}" - } - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.callTool({ - name: `test_multiple_content_types`, - arguments: {} - }), - i = [], - a = r.content; - (a || i.push(`Missing content array`), - a && a.length < 2 && i.push(`Expected multiple content items`)); - let o = a && a.some((e) => e.type === `text`), - s = a && a.some((e) => e.type === `image`), - c = a && a.some((e) => e.type === `resource`); - (o || i.push(`Missing text content`), - s || i.push(`Missing image content`), - c || i.push(`Missing resource content`), - t.push({ - id: `tools-call-mixed-content`, - name: `ToolsCallMixedContent`, - description: `Tool returns multiple content types`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ], - details: { - contentCount: a ? a.length : 0, - contentTypes: a ? a.map((e) => e.type) : [] - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-mixed-content`, - name: `ToolsCallMixedContent`, - description: `Tool returns multiple content types`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ] - }); - } - return t; - } - }, - Se = class { - constructor() { - ((this.name = `tools-call-with-logging`), - (this.description = `Test tool that sends log messages during execution. - -**Server Implementation Requirements:** - -Implement tool \`test_tool_with_logging\` with no arguments. - -**Behavior**: During execution, send 3 log notifications at info level: -1. "Tool execution started" -2. "Tool processing data" (after ~50ms delay) -3. "Tool execution completed" (after another ~50ms delay) - -**Returns**: Text content confirming execution - -**Implementation Note**: The delays are important to test that clients can receive multiple log notifications during tool execution`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = new pe(n.client); - (await n.client.setLoggingLevel(`debug`), - await n.client.callTool({ - name: `test_tool_with_logging`, - arguments: {} - }), - await new Promise((e) => setTimeout(e, 200))); - let i = r.getNotifications(), - a = []; - (i.length === 0 - ? a.push(`No log notifications received`) - : i.length < 3 && - a.push(`Expected at least 3 log messages, got ${i.length}`), - t.push({ - id: `tools-call-with-logging`, - name: `ToolsCallWithLogging`, - description: `Tool sends log messages during execution`, - status: a.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: a.length > 0 ? a.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Logging`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging` - } - ], - details: { logCount: i.length, logs: i.map((e) => e.params) } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-with-logging`, - name: `ToolsCallWithLogging`, - description: `Tool sends log messages during execution`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Logging`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging` - } - ] - }); - } - return t; - } - }, - Ce = class { - constructor() { - ((this.name = `tools-call-error`), - (this.description = `Test tool error reporting. - -**Server Implementation Requirements:** - -Implement tool \`test_error_handling\` with no arguments. - -**Behavior**: Always throw an error - -**Returns**: JSON-RPC response with \`isError: true\` - -\`\`\`json -{ - "isError": true, - "content": [ - { - "type": "text", - "text": "This tool intentionally returns an error for testing" - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.callTool({ - name: `test_error_handling`, - arguments: {} - }), - i = r.isError === !0, - a = r.content && r.content.length > 0 && r.content[0].text; - (t.push({ - id: `tools-call-error`, - name: `ToolsCallError`, - description: `Tool returns error correctly`, - status: i && a ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i - ? a - ? void 0 - : `Error result missing error message` - : `Tool did not return isError: true`, - specReferences: [ - { - id: `MCP-Error-Handling`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle` - } - ], - details: { result: r } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-error`, - name: `ToolsCallError`, - description: `Tool returns error correctly`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Error-Handling`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle` - } - ] - }); - } - return t; - } - }, - we = class { - constructor() { - ((this.name = `tools-call-with-progress`), - (this.description = `Test tool that reports progress notifications. - -**Server Implementation Requirements:** - -Implement tool \`test_tool_with_progress\` with no arguments. - -**Behavior**: If \`_meta.progressToken\` is provided in request: -- Send progress notification: \`0/100\` -- Wait ~50ms -- Send progress notification: \`50/100\` -- Wait ~50ms -- Send progress notification: \`100/100\` - -If no progress token provided, just execute with delays. - -**Returns**: Text content confirming execution - -**Progress Notification Format**: - -\`\`\`json -{ - "method": "notifications/progress", - "params": { - "progressToken": "", - "progress": 50, - "total": 100 - } -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = [], - i = await n.client.request( - { - method: `tools/call`, - params: { - name: `test_tool_with_progress`, - arguments: {}, - _meta: { progressToken: `progress-test-1` } - } - }, - u, - { - onprogress: (e) => { - r.push(e); - } - } - ), - a = []; - if ( - (r.length === 0 - ? a.push(`No progress notifications received`) - : r.length < 3 && - a.push( - `Expected at least 3 progress notifications, got ${r.length}` - ), - r.length >= 3) - ) { - let e = r[0].progress, - t = r[1].progress, - n = r[2].progress; - (e <= t && t <= n) || a.push(`Progress values should be increasing`); - } - (t.push({ - id: `tools-call-with-progress`, - name: `ToolsCallWithProgress`, - description: `Tool reports progress notifications`, - status: a.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: a.length > 0 ? a.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Progress`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress` - } - ], - details: { - progressCount: r.length, - progressNotifications: r.map((e) => e), - result: i - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-with-progress`, - name: `ToolsCallWithProgress`, - description: `Tool reports progress notifications`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Progress`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/progress` - } - ] - }); - } - return t; - } - }, - Te = class { - constructor() { - ((this.name = `tools-call-sampling`), - (this.description = `Test tool that requests LLM sampling from client. - -**Server Implementation Requirements:** - -Implement tool \`test_sampling\` with argument: -- \`prompt\` (string, required) - The prompt to send to the LLM - -**Behavior**: Request LLM sampling from the client using \`sampling/createMessage\` - -**Sampling Request**: - -\`\`\`json -{ - "method": "sampling/createMessage", - "params": { - "messages": [ - { - "role": "user", - "content": { - "type": "text", - "text": "" - } - } - ], - "maxTokens": 100 - } -} -\`\`\` - -**Returns**: Text content with the LLM's response - -\`\`\`json -{ - "content": [ - { - "type": "text", - "text": "LLM response: " - } - ] -} -\`\`\` - -**Implementation Note**: If the client doesn't support sampling (no \`sampling\` capability), return an error.`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = !1; - n.client.setRequestHandler( - d, - async (e) => ( - (r = !0), - { - role: `assistant`, - content: { - type: `text`, - text: `This is a test response from the client` - }, - model: `test-model`, - stopReason: `endTurn` - } - ) - ); - let i = await n.client.callTool({ - name: `test_sampling`, - arguments: { prompt: `Test prompt for sampling` } - }), - a = []; - r || a.push(`Server did not request sampling from client`); - let o = i.content; - ((!o || o.length === 0) && a.push(`Tool did not return content`), - t.push({ - id: `tools-call-sampling`, - name: `ToolsCallSampling`, - description: `Tool requests LLM sampling from client`, - status: a.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: a.length > 0 ? a.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Sampling`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling` - } - ], - details: { samplingRequested: r, result: i } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-sampling`, - name: `ToolsCallSampling`, - description: `Tool requests LLM sampling from client`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Sampling`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/sampling` - } - ] - }); - } - return t; - } - }, - O = class { - constructor() { - ((this.name = `tools-call-elicitation`), - (this.description = `Test tool that requests user input (elicitation) from client. - -**Server Implementation Requirements:** - -Implement tool \`test_elicitation\` with argument: -- \`message\` (string, required) - The message to show the user - -**Behavior**: Request user input from the client using \`elicitation/create\` - -**Elicitation Request**: - -\`\`\`json -{ - "method": "elicitation/create", - "params": { - "message": "", - "requestedSchema": { - "type": "object", - "properties": { - "username": { - "type": "string", - "description": "User's response" - }, - "email": { - "type": "string", - "description": "User's email address" - } - }, - "required": ["username", "email"] - } - } -} -\`\`\` - -**Returns**: Text content with the user's response - -\`\`\`json -{ - "content": [ - { - "type": "text", - "text": "User response: " - } - ] -} -\`\`\` - -**Implementation Note**: If the client doesn't support elicitation (no \`elicitation\` capability), return an error.`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = !1; - n.client.setRequestHandler( - f, - async (e) => ( - (r = !0), - { - action: `accept`, - content: { username: `testuser`, email: `test@example.com` } - } - ) - ); - let i = await n.client.callTool({ - name: `test_elicitation`, - arguments: { message: `Please provide your information` } - }), - a = []; - r || a.push(`Server did not request elicitation from client`); - let o = i.content; - ((!o || o.length === 0) && a.push(`Tool did not return content`), - t.push({ - id: `tools-call-elicitation`, - name: `ToolsCallElicitation`, - description: `Tool requests user input from client`, - status: a.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: a.length > 0 ? a.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Elicitation`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation` - } - ], - details: { elicitationRequested: r, result: i } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-elicitation`, - name: `ToolsCallElicitation`, - description: `Tool requests user input from client`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Elicitation`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/elicitation` - } - ] - }); - } - return t; - } - }, - Ee = class { - constructor() { - ((this.name = `tools-call-audio`), - (this.description = `Test calling a tool that returns audio content. - -**Server Implementation Requirements:** - -Implement tool \`test_audio_content\` with no arguments that returns: - -\`\`\`json -{ - "content": [ - { - "type": "audio", - "data": "", - "mimeType": "audio/wav" - } - ] -} -\`\`\` - -**Implementation Note**: Use a minimal test audio file`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.callTool({ - name: `test_audio_content`, - arguments: {} - }), - i = [], - a = r.content; - (a || i.push(`Missing content array`), - Array.isArray(a) || i.push(`content is not an array`), - a && a.length === 0 && i.push(`content array is empty`)); - let o = a && a.find((e) => e.type === `audio`); - (o || i.push(`No audio content found`), - o && !o.data && i.push(`Audio content missing data field`), - o && !o.mimeType && i.push(`Audio content missing mimeType field`), - o && - o.mimeType !== `audio/wav` && - i.push(`Expected mimeType 'audio/wav', got '${o.mimeType}'`), - t.push({ - id: `tools-call-audio`, - name: `ToolsCallAudio`, - description: `Tool returns audio content`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ], - details: { - hasAudioContent: !!o, - audioDataLength: o?.data?.length || 0 - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-audio`, - name: `ToolsCallAudio`, - description: `Tool returns audio content`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ] - }); - } - return t; - } - }, - De = class { - constructor() { - ((this.name = `tools-call-embedded-resource`), - (this.description = `Test calling a tool that returns embedded resource content. - -**Server Implementation Requirements:** - -Implement tool \`test_embedded_resource\` with no arguments that returns: - -\`\`\`json -{ - "content": [ - { - "type": "resource", - "resource": { - "uri": "test://embedded-resource", - "mimeType": "text/plain", - "text": "This is an embedded resource content." - } - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.callTool({ - name: `test_embedded_resource`, - arguments: {} - }), - i = [], - a = r.content; - (a || i.push(`Missing content array`), - Array.isArray(a) || i.push(`content is not an array`), - a && a.length === 0 && i.push(`content array is empty`)); - let o = a && a.find((e) => e.type === `resource`); - (o || i.push(`No resource content found`), - o && !o.resource && i.push(`Resource content missing resource field`), - o?.resource && - (o.resource.uri || i.push(`Resource missing uri field`), - o.resource.mimeType || i.push(`Resource missing mimeType field`), - !o.resource.text && - !o.resource.blob && - i.push(`Resource missing both text and blob fields`)), - t.push({ - id: `tools-call-embedded-resource`, - name: `ToolsCallEmbeddedResource`, - description: `Tool returns embedded resource content`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ], - details: { hasResourceContent: !!o, resourceUri: o?.resource?.uri } - }), - await n.close()); - } catch (e) { - t.push({ - id: `tools-call-embedded-resource`, - name: `ToolsCallEmbeddedResource`, - description: `Tool returns embedded resource content`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Tools-Call`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/tools#calling-tools` - } - ] - }); - } - return t; - } - }; -const k = `json_schema_2020_12_tool`, - A = `https://json-schema.org/draft/2020-12/schema`; -var j = class { - constructor() { - ((this.name = `json-schema-2020-12`), - (this.description = `Validates JSON Schema 2020-12 keyword preservation (SEP-1613). - -**Server Implementation Requirements:** - -Implement tool \`${k}\` with inputSchema containing JSON Schema 2020-12 features: - -\`\`\`json -{ - "name": "${k}", - "description": "Tool with JSON Schema 2020-12 features", - "inputSchema": { - "$schema": "${A}", - "type": "object", - "$defs": { - "address": { - "type": "object", - "properties": { - "street": { "type": "string" }, - "city": { "type": "string" } - } - } - }, - "properties": { - "name": { "type": "string" }, - "address": { "$ref": "#/$defs/address" } - }, - "additionalProperties": false - } -} -\`\`\` - -**Verification**: The test verifies that \`$schema\`, \`$defs\`, and \`additionalProperties\` are preserved in the tool listing response.`)); - } - async run(e) { - let t = [], - n = [ - { - id: `SEP-1613`, - url: `https://github.com/modelcontextprotocol/specification/pull/655` - } - ]; - try { - let r = await D(e), - i = await r.client.listTools(), - a = i.tools?.find((e) => e.name === k); - if ( - (t.push({ - id: `json-schema-2020-12-tool-found`, - name: `JsonSchema2020_12ToolFound`, - description: `Server advertises tool '${k}'`, - status: a ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: a - ? void 0 - : `Tool '${k}' not found. Available tools: ${i.tools?.map((e) => e.name).join(`, `) || `none`}`, - specReferences: n, - details: { - toolFound: !!a, - availableTools: i.tools?.map((e) => e.name) || [] - } - }), - !a) - ) - return (await r.close(), t); - let o = a.inputSchema, - s = `$schema` in o, - c = o.$schema, - l = c === A; - t.push({ - id: `json-schema-2020-12-$schema`, - name: `JsonSchema2020_12$Schema`, - description: `inputSchema.$schema field preserved with value '${A}'`, - status: s && l ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: s - ? l - ? void 0 - : `$schema has unexpected value: ${JSON.stringify(c)}` - : `$schema field missing from inputSchema - field was likely stripped`, - specReferences: n, - details: { hasSchema: s, schemaValue: c, expected: A } - }); - let u = `$defs` in o, - d = o.$defs, - f = d && `address` in d; - t.push({ - id: `json-schema-2020-12-$defs`, - name: `JsonSchema2020_12$Defs`, - description: `inputSchema.$defs field preserved with expected structure`, - status: u && f ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: u - ? f - ? void 0 - : `$defs exists but missing expected "address" definition` - : `$defs field missing from inputSchema - field was likely stripped`, - specReferences: n, - details: { - hasDefs: u, - defsKeys: d ? Object.keys(d) : [], - defsValue: d - } - }); - let p = `additionalProperties` in o, - m = o.additionalProperties, - h = m === !1; - (t.push({ - id: `json-schema-2020-12-additionalProperties`, - name: `JsonSchema2020_12AdditionalProperties`, - description: `inputSchema.additionalProperties field preserved`, - status: p && h ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: p - ? h - ? void 0 - : `additionalProperties has unexpected value: ${JSON.stringify(m)}, expected: false` - : `additionalProperties field missing from inputSchema - field was likely stripped`, - specReferences: n, - details: { - hasAdditionalProps: p, - additionalPropsValue: m, - expected: !1 - } - }), - await r.close()); - } catch (e) { - t.push({ - id: `json-schema-2020-12-error`, - name: `JsonSchema2020_12Error`, - description: `JSON Schema 2020-12 conformance test`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: n - }); - } - return t; - } - }, - M = class { - constructor() { - ((this.name = `elicitation-sep1034-defaults`), - (this.description = `Test elicitation with default values for all primitive types (SEP-1034). - -**Server Implementation Requirements:** - -Implement a tool named \`test_elicitation_sep1034_defaults\` (no arguments) that requests \`elicitation/create\` from the client with a schema containing default values for all primitive types: -- \`name\` (string): default "John Doe" -- \`age\` (integer): default 30 -- \`score\` (number): default 95.5 -- \`status\` (string enum: ["active", "inactive", "pending"]): default "active" -- \`verified\` (boolean): default true - -**Returns**: Text content with the elicitation result - -\`\`\`json -{ - "content": [ - { - "type": "text", - "text": "Elicitation completed: action=, content={...}" - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = null; - if ( - (n.client.setRequestHandler( - f, - async (e) => ( - (r = e), - { - action: `accept`, - content: { - name: `Jane Smith`, - age: 25, - score: 88, - status: `inactive`, - verified: !1 - } - } - ) - ), - await n.client.callTool({ - name: `test_elicitation_sep1034_defaults`, - arguments: {} - }), - !r) - ) - return ( - t.push({ - id: `elicitation-sep1034-general`, - name: `ElicitationSEP1034General`, - description: `Server requests elicitation with default values`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Server did not request elicitation from client`, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ] - }), - await n.close(), - t - ); - let i = r.params?.requestedSchema?.properties, - a = []; - (i?.name - ? (i.name.type !== `string` && - a.push(`Expected type "string", got "${i.name.type}"`), - `default` in i.name - ? i.name.default !== `John Doe` && - a.push(`Expected default "John Doe", got "${i.name.default}"`) - : a.push(`Missing default field`)) - : a.push(`Missing string field "name"`), - t.push({ - id: `elicitation-sep1034-string-default`, - name: `ElicitationSEP1034StringDefault`, - description: `String schema includes default value`, - status: a.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: a.length > 0 ? a.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { field: `name`, schema: i?.name } - })); - let o = []; - (i?.age - ? (i.age.type !== `integer` && - o.push(`Expected type "integer", got "${i.age.type}"`), - `default` in i.age - ? i.age.default !== 30 && - o.push(`Expected default 30, got ${i.age.default}`) - : o.push(`Missing default field`)) - : o.push(`Missing integer field "age"`), - t.push({ - id: `elicitation-sep1034-integer-default`, - name: `ElicitationSEP1034IntegerDefault`, - description: `Integer schema includes default value`, - status: o.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: o.length > 0 ? o.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { field: `age`, schema: i?.age } - })); - let s = []; - (i?.score - ? (i.score.type !== `number` && - s.push(`Expected type "number", got "${i.score.type}"`), - `default` in i.score - ? i.score.default !== 95.5 && - s.push(`Expected default 95.5, got ${i.score.default}`) - : s.push(`Missing default field`)) - : s.push(`Missing number field "score"`), - t.push({ - id: `elicitation-sep1034-number-default`, - name: `ElicitationSEP1034NumberDefault`, - description: `Number schema includes default value`, - status: s.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: s.length > 0 ? s.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { field: `score`, schema: i?.score } - })); - let c = []; - (i?.status - ? (i.status.type !== `string` && - c.push(`Expected type "string", got "${i.status.type}"`), - (!i.status.enum || !Array.isArray(i.status.enum)) && - c.push(`Missing or invalid enum array`), - `default` in i.status - ? (i.status.default !== `active` && - c.push( - `Expected default "active", got "${i.status.default}"` - ), - i.status.enum && - !i.status.enum.includes(i.status.default) && - c.push( - `Default value "${i.status.default}" is not a valid enum member` - )) - : c.push(`Missing default field`)) - : c.push(`Missing enum field "status"`), - t.push({ - id: `elicitation-sep1034-enum-default`, - name: `ElicitationSEP1034EnumDefault`, - description: `Enum schema includes valid default value`, - status: c.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: c.length > 0 ? c.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { field: `status`, schema: i?.status } - })); - let l = []; - (i?.verified - ? (i.verified.type !== `boolean` && - l.push(`Expected type "boolean", got "${i.verified.type}"`), - `default` in i.verified - ? i.verified.default !== !0 && - l.push(`Expected default true, got ${i.verified.default}`) - : l.push(`Missing default field`)) - : l.push(`Missing boolean field "verified"`), - t.push({ - id: `elicitation-sep1034-boolean-default`, - name: `ElicitationSEP1034BooleanDefault`, - description: `Boolean schema includes default value`, - status: l.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: l.length > 0 ? l.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ], - details: { field: `verified`, schema: i?.verified } - }), - await n.close()); - } catch (e) { - t.push({ - id: `elicitation-sep1034-general`, - name: `ElicitationSEP1034General`, - description: `Server requests elicitation with default values`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `SEP-1034`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034` - } - ] - }); - } - return t; - } - }, - N = class { - constructor() { - ((this.name = `elicitation-sep1330-enums`), - (this.description = `Test elicitation with enum schema improvements (SEP-1330). - -**Server Implementation Requirements:** - -Implement a tool named \`test_elicitation_sep1330_enums\` (no arguments) that requests \`elicitation/create\` from the client with a schema containing all 5 enum variants: - -1. **Untitled single-select**: \`{ type: "string", enum: ["option1", "option2", "option3"] }\` -2. **Titled single-select**: \`{ type: "string", oneOf: [{ const: "value1", title: "First Option" }, ...] }\` -3. **Legacy titled (deprecated)**: \`{ type: "string", enum: ["opt1", "opt2", "opt3"], enumNames: ["Option One", "Option Two", "Option Three"] }\` -4. **Untitled multi-select**: \`{ type: "array", items: { type: "string", enum: ["option1", "option2", "option3"] } }\` -5. **Titled multi-select**: \`{ type: "array", items: { anyOf: [{ const: "value1", title: "First Choice" }, ...] } }\` - -**Returns**: Text content with the elicitation result - -\`\`\`json -{ - "content": [ - { - "type": "text", - "text": "Elicitation completed: action=, content={...}" - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = null; - if ( - (n.client.setRequestHandler( - f, - async (e) => ( - (r = e), - { - action: `accept`, - content: { - untitledSingle: `option1`, - titledSingle: `value1`, - legacyEnum: `opt1`, - untitledMulti: [`option1`, `option2`], - titledMulti: [`value1`, `value2`] - } - } - ) - ), - await n.client.callTool({ - name: `test_elicitation_sep1330_enums`, - arguments: {} - }), - !r) - ) - return ( - t.push({ - id: `elicitation-sep1330-general`, - name: `ElicitationSEP1330General`, - description: `Server requests elicitation with enum schemas`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Server did not request elicitation from client`, - specReferences: [ - { - id: `SEP-1330`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` - } - ] - }), - await n.close(), - t - ); - let i = r.params?.requestedSchema?.properties, - a = []; - (i?.untitledSingle - ? (i.untitledSingle.type !== `string` && - a.push(`Expected type "string", got "${i.untitledSingle.type}"`), - (!i.untitledSingle.enum || !Array.isArray(i.untitledSingle.enum)) && - a.push(`Missing or invalid enum array`), - i.untitledSingle.oneOf && - a.push(`Untitled enum should not have oneOf property`), - i.untitledSingle.enumNames && - a.push(`Untitled enum should not have enumNames property`)) - : a.push( - `Missing untitled single-select enum field "untitledSingle"` - ), - t.push({ - id: `elicitation-sep1330-untitled-single`, - name: `ElicitationSEP1330UntitledSingle`, - description: `Untitled single-select enum schema uses enum array`, - status: a.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: a.length > 0 ? a.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1330`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` - } - ], - details: { field: `untitledSingle`, schema: i?.untitledSingle } - })); - let o = []; - (i?.titledSingle - ? (i.titledSingle.type !== `string` && - o.push(`Expected type "string", got "${i.titledSingle.type}"`), - !i.titledSingle.oneOf || !Array.isArray(i.titledSingle.oneOf) - ? o.push(`Missing or invalid oneOf array for titled enum`) - : i.titledSingle.oneOf.filter( - (e) => - typeof e.const != `string` || typeof e.title != `string` - ).length > 0 && - o.push( - `oneOf items must have "const" (string) and "title" (string) properties` - ), - i.titledSingle.enum && - o.push(`Titled enum should use oneOf instead of enum array`)) - : o.push(`Missing titled single-select enum field "titledSingle"`), - t.push({ - id: `elicitation-sep1330-titled-single`, - name: `ElicitationSEP1330TitledSingle`, - description: `Titled single-select enum schema uses oneOf with const/title`, - status: o.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: o.length > 0 ? o.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1330`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` - } - ], - details: { field: `titledSingle`, schema: i?.titledSingle } - })); - let s = []; - (i?.legacyEnum - ? (i.legacyEnum.type !== `string` && - s.push(`Expected type "string", got "${i.legacyEnum.type}"`), - (!i.legacyEnum.enum || !Array.isArray(i.legacyEnum.enum)) && - s.push(`Missing or invalid enum array`), - !i.legacyEnum.enumNames || !Array.isArray(i.legacyEnum.enumNames) - ? s.push( - `Missing or invalid enumNames array for legacy titled enum` - ) - : i.legacyEnum.enum && - i.legacyEnum.enumNames.length !== i.legacyEnum.enum.length && - s.push( - `enumNames length (${i.legacyEnum.enumNames.length}) must match enum length (${i.legacyEnum.enum.length})` - )) - : s.push(`Missing legacy titled enum field "legacyEnum"`), - t.push({ - id: `elicitation-sep1330-legacy-enumnames`, - name: `ElicitationSEP1330LegacyEnumNames`, - description: `Legacy titled enum schema uses enumNames (deprecated)`, - status: s.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: s.length > 0 ? s.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1330`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` - } - ], - details: { field: `legacyEnum`, schema: i?.legacyEnum } - })); - let c = []; - (i?.untitledMulti - ? (i.untitledMulti.type !== `array` && - c.push(`Expected type "array", got "${i.untitledMulti.type}"`), - i.untitledMulti.items - ? (i.untitledMulti.items.type !== `string` && - c.push( - `Expected items.type "string", got "${i.untitledMulti.items.type}"` - ), - (!i.untitledMulti.items.enum || - !Array.isArray(i.untitledMulti.items.enum)) && - c.push(`Missing or invalid items.enum array`), - i.untitledMulti.items.anyOf && - c.push( - `Untitled multi-select should use items.enum, not items.anyOf` - )) - : c.push(`Missing items property for array type`)) - : c.push(`Missing untitled multi-select enum field "untitledMulti"`), - t.push({ - id: `elicitation-sep1330-untitled-multi`, - name: `ElicitationSEP1330UntitledMulti`, - description: `Untitled multi-select enum schema uses array with items.enum`, - status: c.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: c.length > 0 ? c.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1330`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` - } - ], - details: { field: `untitledMulti`, schema: i?.untitledMulti } - })); - let l = []; - (i?.titledMulti - ? (i.titledMulti.type !== `array` && - l.push(`Expected type "array", got "${i.titledMulti.type}"`), - i.titledMulti.items - ? (!i.titledMulti.items.anyOf || - !Array.isArray(i.titledMulti.items.anyOf) - ? l.push( - `Missing or invalid items.anyOf array for titled multi-select` - ) - : i.titledMulti.items.anyOf.filter( - (e) => - typeof e.const != `string` || typeof e.title != `string` - ).length > 0 && - l.push( - `items.anyOf entries must have "const" (string) and "title" (string) properties` - ), - i.titledMulti.items.enum && - l.push( - `Titled multi-select should use items.anyOf, not items.enum` - )) - : l.push(`Missing items property for array type`)) - : l.push(`Missing titled multi-select enum field "titledMulti"`), - t.push({ - id: `elicitation-sep1330-titled-multi`, - name: `ElicitationSEP1330TitledMulti`, - description: `Titled multi-select enum schema uses array with items.anyOf`, - status: l.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: l.length > 0 ? l.join(`; `) : void 0, - specReferences: [ - { - id: `SEP-1330`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` - } - ], - details: { field: `titledMulti`, schema: i?.titledMulti } - }), - await n.close()); - } catch (e) { - t.push({ - id: `elicitation-sep1330-general`, - name: `ElicitationSEP1330General`, - description: `Server requests elicitation with enum schemas`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `SEP-1330`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330` - } - ] - }); - } - return t; - } - }; -function Oe(e) { - return async (t, n) => { - let r = n.method || `GET`, - i = `Sending ${r} request`; - if (n.body) - try { - let e = JSON.parse(n.body); - e.method && (i = `Sending ${r} ${e.method}`); - } catch {} - e.push({ - id: `outgoing-request`, - name: `OutgoingRequest`, - description: i, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - method: r, - url: t, - headers: n.headers, - body: n.body ? JSON.parse(n.body) : void 0 - } - }); - let a = await fetch(t, n), - o = {}; - return ( - a.headers.forEach((e, t) => { - o[t] = e; - }), - e.push({ - id: `incoming-response`, - name: `IncomingResponse`, - description: `Received ${a.status} response for ${r}`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { statusCode: a.status, headers: o } - }), - a - ); - }; -} -var P = class { - constructor() { - ((this.name = `server-sse-polling`), - (this.description = `Test server SSE polling via test_reconnection tool that closes stream mid-call (SEP-1699)`)); - } - async run(e) { - let t = [], - n, - r, - i; - try { - ((r = new b( - { name: `conformance-test-client`, version: `1.0.0` }, - { capabilities: { sampling: {}, elicitation: {} } } - )), - (i = new x(new URL(e))), - await r.connect(i), - (n = i.sessionId), - n || - t.push({ - id: `server-sse-polling-session`, - name: `ServerSSEPollingSession`, - description: `Server provides session ID for SSE polling tests`, - status: `WARNING`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - message: `Server did not provide session ID - SSE polling tests may not work correctly` - } - })); - let a = Oe(t), - o = await a(e, { - method: `POST`, - headers: { - 'Content-Type': `application/json`, - Accept: `text/event-stream, application/json`, - ...(n && { 'mcp-session-id': n }), - 'mcp-protocol-version': `2025-03-26` - }, - body: JSON.stringify({ - jsonrpc: `2.0`, - id: 1, - method: `tools/call`, - params: { name: `test_reconnection`, arguments: {} } - }) - }); - if (!o.ok) - return o.status === 400 || o.status === 404 - ? (t.push({ - id: `server-sse-test-reconnection-tool`, - name: `ServerTestReconnectionTool`, - description: `Server implements test_reconnection tool for SSE polling tests`, - status: `WARNING`, - timestamp: new Date().toISOString(), - errorMessage: `Server does not implement test_reconnection tool (HTTP ${o.status}). This tool is recommended for testing SSE polling behavior.`, - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ] - }), - t) - : (t.push({ - id: `server-sse-post-request`, - name: `ServerSSEPostRequest`, - description: `Server accepts POST request with SSE stream response`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Server returned HTTP ${o.status}`, - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ] - }), - t); - let s = o.headers.get(`content-type`); - if (!s?.includes(`text/event-stream`)) - return ( - t.push({ - id: `server-sse-content-type`, - name: `ServerSSEContentType`, - description: `Server returns text/event-stream for POST request`, - status: `INFO`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - contentType: s, - message: `Server returned JSON instead of SSE stream - priming event tests not applicable` - } - }), - t - ); - let c = !1, - l = !1, - u = !1, - d = !1, - f, - p, - m = 0, - h = !1; - if (!o.body) - return ( - t.push({ - id: `server-sse-polling-stream`, - name: `ServerSSEPollingStream`, - description: `Server provides SSE response body`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Response body is null`, - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ] - }), - t - ); - let g = o.body - .pipeThrough(new TextDecoderStream()) - .pipeThrough( - new S({ - onRetry: (e) => { - ((d = !0), (f = e)); - } - }) - ) - .getReader(), - _ = setTimeout(() => { - g.cancel(); - }, 1e4); - try { - for (;;) { - let { value: e, done: n } = await g.read(); - if (n) break; - if ((m++, e.id)) { - ((c = !0), (p = e.id)); - let n = e.data === `` || e.data === `{}` || e.data.trim() === ``; - (n && ((l = !0), m === 1 && (u = !0)), - t.push({ - id: `incoming-sse-event`, - name: `IncomingSseEvent`, - description: n - ? `Received SSE priming event (id: ${e.id})` - : `Received SSE event (id: ${e.id})`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - eventId: e.id, - eventType: e.event || `message`, - isPriming: n, - hasRetryField: d, - retryValue: f, - data: e.data - } - })); - } - if (e.data) - try { - let n = JSON.parse(e.data); - if (n.id === 1 && n.result) { - h = !0; - let r = n.result?.isError === !0; - t.push({ - id: `incoming-sse-event`, - name: `IncomingSseEvent`, - description: `Received tool response on POST stream`, - status: r ? `FAILURE` : `INFO`, - timestamp: new Date().toISOString(), - details: { eventId: e.id, body: n }, - ...(r && { errorMessage: `Tool call failed` }) - }); - } - } catch {} - } - } finally { - clearTimeout(_); - } - t.push({ - id: `stream-closed`, - name: `StreamClosed`, - description: `POST SSE stream closed after ${m} event(s)`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { eventCount: m, lastEventId: p, receivedToolResponse: h } - }); - let v = `SUCCESS`, - y; - if ( - (l - ? u || - ((v = `WARNING`), - (y = `Priming event was not sent first. It should be sent immediately when the SSE stream is established.`)) - : ((v = `WARNING`), - (y = `Server did not send priming event with id and empty data on POST SSE stream. This is recommended for resumability.`)), - t.push({ - id: `server-sse-priming-event`, - name: `ServerSendsPrimingEvent`, - description: `Server SHOULD send priming event with id and empty data on POST SSE streams`, - status: v, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - hasPrimingEvent: l, - primingEventIsFirst: u, - hasEventId: c, - lastEventId: p, - eventCount: m - }, - errorMessage: y - }), - t.push({ - id: `server-sse-retry-field`, - name: `ServerSendsRetryField`, - description: `Server SHOULD send retry field to control client reconnection timing`, - status: d ? `SUCCESS` : `WARNING`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { hasRetryField: d, retryValue: f }, - errorMessage: d - ? void 0 - : `Server did not send retry field. This is recommended for controlling client reconnection timing.` - }), - !h && p && n) - ) { - let r = await a(e, { - method: `GET`, - headers: { - Accept: `text/event-stream`, - 'mcp-session-id': n, - 'mcp-protocol-version': `2025-03-26`, - 'last-event-id': p - } - }); - if (r.ok && r.body) { - let e = r.body - .pipeThrough(new TextDecoderStream()) - .pipeThrough(new S()) - .getReader(), - n = setTimeout(() => { - e.cancel(); - }, 5e3); - try { - for (;;) { - let { value: n, done: r } = await e.read(); - if (r) break; - if ( - (t.push({ - id: `incoming-sse-event`, - name: `IncomingSseEvent`, - description: `Received SSE event on GET reconnection stream (id: ${n.id || `none`})`, - status: `INFO`, - timestamp: new Date().toISOString(), - details: { - eventId: n.id, - eventType: n.event || `message`, - data: n.data - } - }), - n.data) - ) - try { - let e = JSON.parse(n.data); - if (e.id === 1 && e.result) { - h = !0; - break; - } - } catch {} - } - } finally { - clearTimeout(n); - } - t.push({ - id: `server-sse-disconnect-resume`, - name: `ServerDisconnectResume`, - description: `Server closes SSE stream mid-call and resumes after client reconnects with Last-Event-ID`, - status: h ? `SUCCESS` : `WARNING`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - lastEventIdUsed: p, - receivedToolResponse: h, - message: h - ? `Successfully received tool response after reconnection` - : `Tool response not received after reconnection` - }, - errorMessage: h - ? void 0 - : `Server did not send tool response after client reconnected with Last-Event-ID` - }); - } else - r.status === 405 - ? t.push({ - id: `server-sse-disconnect-resume`, - name: `ServerDisconnectResume`, - description: `Server supports GET reconnection with Last-Event-ID`, - status: `INFO`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - statusCode: r.status, - message: `Server does not support standalone GET SSE endpoint (405 Method Not Allowed)` - } - }) - : t.push({ - id: `server-sse-disconnect-resume`, - name: `ServerDisconnectResume`, - description: `Server supports GET reconnection with Last-Event-ID`, - status: `WARNING`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - statusCode: r.status, - lastEventIdUsed: p, - message: `Server returned ${r.status} for GET request with Last-Event-ID` - }, - errorMessage: `Server did not accept reconnection with Last-Event-ID (HTTP ${r.status})` - }); - } else - h - ? t.push({ - id: `server-sse-disconnect-resume`, - name: `ServerDisconnectResume`, - description: `Server closes SSE stream mid-call and resumes after reconnection`, - status: `INFO`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - receivedToolResponse: !0, - message: `Tool response received on initial POST stream - server did not disconnect mid-call. The test_reconnection tool should close the stream before sending the result.` - } - }) - : t.push({ - id: `server-sse-disconnect-resume`, - name: `ServerDisconnectResume`, - description: `Server closes SSE stream mid-call and resumes after reconnection`, - status: `INFO`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - lastEventId: p, - sessionId: n, - message: `Could not test disconnect/resume - no last event ID or session ID available` - } - }); - } catch (e) { - t.push({ - id: `server-sse-polling-error`, - name: `ServerSSEPollingTest`, - description: `Test server SSE polling behavior`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Error: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ] - }); - } finally { - if (r) - try { - await r.close(); - } catch {} - } - return t; - } - }, - ke = class { - constructor() { - ((this.name = `server-sse-multiple-streams`), - (this.description = `Test server supports multiple concurrent POST SSE streams (SEP-1699)`)); - } - async run(e) { - let t = [], - n, - r, - i; - try { - if ( - ((r = new b( - { name: `conformance-test-client`, version: `1.0.0` }, - { capabilities: { sampling: {}, elicitation: {} } } - )), - (i = new x(new URL(e))), - await r.connect(i), - (n = i.sessionId), - !n) - ) - return ( - t.push({ - id: `server-sse-multiple-streams-session`, - name: `ServerSSEMultipleStreamsSession`, - description: `Server provides session ID for multiple streams test`, - status: `WARNING`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - message: `Server did not provide session ID - multiple streams test may not work correctly` - } - }), - t - ); - let a = [], - o = []; - for (let t = 0; t < 3; t++) { - let r = fetch(e, { - method: `POST`, - headers: { - 'Content-Type': `application/json`, - Accept: `text/event-stream, application/json`, - 'mcp-session-id': n, - 'mcp-protocol-version': `2025-03-26` - }, - body: JSON.stringify({ - jsonrpc: `2.0`, - id: 1e3 + t, - method: `tools/list`, - params: {} - }) - }); - o.push(r); - } - let s = await Promise.all(o); - a.push(...s); - let c = a.every((e) => e.ok), - l = a.map((e) => e.status), - u = a.map((e) => e.headers.get(`content-type`)), - d = u.filter((e) => e?.includes(`text/event-stream`)).length; - t.push({ - id: `server-accepts-multiple-post-streams`, - name: `ServerAcceptsMultiplePostStreams`, - description: `Server allows multiple concurrent POST requests (each may return SSE or JSON)`, - status: c ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - numStreamsAttempted: 3, - numStreamsAccepted: l.filter((e) => e === 200).length, - numSseStreams: d, - statuses: l, - contentTypes: u - }, - errorMessage: c - ? void 0 - : `Server rejected some requests. Statuses: ${l.join(`, `)}` - }); - let f = await Promise.all( - a.map(async (e, t) => { - if (!e.headers.get(`content-type`)?.includes(`text/event-stream`)) - return { index: t, type: `json`, skipped: !0 }; - if (!e.ok || !e.body) - return { index: t, type: `sse`, error: `Stream not available` }; - try { - let n = e.body - .pipeThrough(new TextDecoderStream()) - .pipeThrough(new S()) - .getReader(), - r = new Promise((e) => setTimeout(() => e(null), 2e3)), - i = n.read().then(({ value: e }) => e), - a = await Promise.race([i, r]); - return (await n.cancel(), { index: t, type: `sse`, event: a }); - } catch (e) { - return { - index: t, - type: `sse`, - error: e instanceof Error ? e.message : String(e) - }; - } - }) - ), - p = f - .filter((e) => e.type === `sse`) - .filter((e) => !(`error` in e)).length; - d > 0 - ? t.push({ - id: `server-sse-streams-functional`, - name: `ServerSSEStreamsFunctional`, - description: `Multiple POST SSE streams should be functional`, - status: p === d ? `SUCCESS` : p > 0 ? `WARNING` : `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - numSseStreams: d, - functionalSseStreams: p, - results: f - }, - errorMessage: - p < d ? `Only ${p}/${d} SSE streams were functional` : void 0 - }) - : t.push({ - id: `server-sse-streams-functional`, - name: `ServerSSEStreamsFunctional`, - description: `Server returned JSON responses (SSE streams optional)`, - status: `INFO`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ], - details: { - numSseStreams: 0, - message: `Server returned JSON for all requests - SSE streaming is optional`, - results: f - } - }); - } catch (e) { - t.push({ - id: `server-sse-multiple-streams-error`, - name: `ServerSSEMultipleStreamsTest`, - description: `Test server multiple SSE streams behavior`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Error: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `SEP-1699`, - url: `https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699` - } - ] - }); - } finally { - if (r) - try { - await r.close(); - } catch {} - } - return t; - } - }, - Ae = class { - constructor() { - ((this.name = `resources-list`), - (this.description = `Test listing available resources. - -**Server Implementation Requirements:** - -**Endpoint**: \`resources/list\` - -**Requirements**: -- Return array of all available **direct resources** (not templates) -- Each resource MUST have: - - \`uri\` (string) - - \`name\` (string) - - \`description\` (string) - - \`mimeType\` (string, optional)`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.listResources(), - i = []; - (r.resources - ? (Array.isArray(r.resources) || i.push(`resources is not an array`), - r.resources.forEach((e, t) => { - (e.uri || i.push(`Resource ${t}: missing uri`), - e.name || i.push(`Resource ${t}: missing name`)); - })) - : i.push(`Missing resources array`), - t.push({ - id: `resources-list`, - name: `ResourcesList`, - description: `Server lists available resources with valid structure`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Resources-List`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources` - } - ], - details: { - resourceCount: r.resources?.length || 0, - resources: r.resources?.map((e) => e.uri) - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `resources-list`, - name: `ResourcesList`, - description: `Server lists available resources with valid structure`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Resources-List`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#listing-resources` - } - ] - }); - } - return t; - } - }, - je = class { - constructor() { - ((this.name = `resources-read-text`), - (this.description = `Test reading text resource. - -**Server Implementation Requirements:** - -Implement resource \`test://static-text\` that returns: - -\`\`\`json -{ - "contents": [ - { - "uri": "test://static-text", - "mimeType": "text/plain", - "text": "This is the content of the static text resource." - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.readResource({ uri: `test://static-text` }), - i = []; - (r.contents || i.push(`Missing contents array`), - Array.isArray(r.contents) || i.push(`contents is not an array`), - r.contents.length === 0 && i.push(`contents array is empty`)); - let a = r.contents[0]; - (a && - (a.uri || i.push(`Content missing uri`), - a.mimeType || i.push(`Content missing mimeType`), - a.text || i.push(`Content missing text field`)), - t.push({ - id: `resources-read-text`, - name: `ResourcesReadText`, - description: `Read text resource successfully`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Resources-Read`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources` - } - ], - details: { uri: a?.uri, mimeType: a?.mimeType, hasText: !!a?.text } - }), - await n.close()); - } catch (e) { - t.push({ - id: `resources-read-text`, - name: `ResourcesReadText`, - description: `Read text resource successfully`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Resources-Read`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources` - } - ] - }); - } - return t; - } - }, - Me = class { - constructor() { - ((this.name = `resources-read-binary`), - (this.description = `Test reading binary resource. - -**Server Implementation Requirements:** - -Implement resource \`test://static-binary\` that returns: - -\`\`\`json -{ - "contents": [ - { - "uri": "test://static-binary", - "mimeType": "image/png", - "blob": "" - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.readResource({ uri: `test://static-binary` }), - i = []; - (r.contents || i.push(`Missing contents array`), - r.contents.length === 0 && i.push(`contents array is empty`)); - let a = r.contents[0]; - (a && - (a.uri || i.push(`Content missing uri`), - a.mimeType || i.push(`Content missing mimeType`), - a.blob || i.push(`Content missing blob field`)), - t.push({ - id: `resources-read-binary`, - name: `ResourcesReadBinary`, - description: `Read binary resource successfully`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Resources-Read`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources` - } - ], - details: { uri: a?.uri, mimeType: a?.mimeType, hasBlob: !!a?.blob } - }), - await n.close()); - } catch (e) { - t.push({ - id: `resources-read-binary`, - name: `ResourcesReadBinary`, - description: `Read binary resource successfully`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Resources-Read`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#reading-resources` - } - ] - }); - } - return t; - } - }, - Ne = class { - constructor() { - ((this.name = `resources-templates-read`), - (this.description = `Test reading resource from template. - -**Server Implementation Requirements:** - -Implement resource template \`test://template/{id}/data\` that substitutes parameters. - -**Behavior**: When client requests \`test://template/123/data\`, substitute \`{id}\` with \`123\` - -Returns (for \`uri: "test://template/123/data"\`): - -\`\`\`json -{ - "contents": [ - { - "uri": "test://template/123/data", - "mimeType": "application/json", - "text": "{"id":"123","templateTest":true,"data":"Data for ID: 123"}" - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.readResource({ uri: `test://template/123/data` }), - i = []; - (r.contents || i.push(`Missing contents array`), - r.contents.length === 0 && i.push(`contents array is empty`)); - let a = r.contents[0]; - if (a) { - a.uri || i.push(`Content missing uri`); - let e = `text` in a, - t = `blob` in a; - !e && !t && i.push(`Content missing text or blob`); - let n = e ? a.text : t ? `[binary]` : ``; - typeof n == `string` && - !n.includes(`123`) && - i.push(`Parameter substitution not reflected in content`); - } - (t.push({ - id: `resources-templates-read`, - name: `ResourcesTemplateRead`, - description: `Read resource from template with parameter substitution`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Resources-Templates`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates` - } - ], - details: { - uri: a?.uri, - content: a ? (`text` in a ? a.text : a.blob) : void 0 - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `resources-templates-read`, - name: `ResourcesTemplateRead`, - description: `Read resource from template with parameter substitution`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Resources-Templates`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-templates` - } - ] - }); - } - return t; - } - }, - Pe = class { - constructor() { - ((this.name = `resources-subscribe`), - (this.description = `Test subscribing to resource updates. - -**Server Implementation Requirements:** - -**Endpoint**: \`resources/subscribe\` - -**Requirements**: -- Accept subscription request with URI -- Track subscribed URIs -- Return empty object \`{}\` - -Example request: - -\`\`\`json -{ - "method": "resources/subscribe", - "params": { - "uri": "test://watched-resource" - } -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e); - (await n.client.subscribeResource({ uri: `test://watched-resource` }), - t.push({ - id: `resources-subscribe`, - name: `ResourcesSubscribe`, - description: `Subscribe to resource successfully`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `MCP-Resources-Subscribe`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions` - } - ] - }), - await n.close()); - } catch (e) { - t.push({ - id: `resources-subscribe`, - name: `ResourcesSubscribe`, - description: `Subscribe to resource successfully`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Resources-Subscribe`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions` - } - ] - }); - } - return t; - } - }, - Fe = class { - constructor() { - ((this.name = `resources-unsubscribe`), - (this.description = `Test unsubscribing from resource. - -**Server Implementation Requirements:** - -**Endpoint**: \`resources/unsubscribe\` - -**Requirements**: -- Accept unsubscribe request with URI -- Remove URI from subscriptions -- Stop sending update notifications for that URI -- Return empty object \`{}\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e); - (await n.client.subscribeResource({ uri: `test://watched-resource` }), - await n.client.unsubscribeResource({ - uri: `test://watched-resource` - }), - t.push({ - id: `resources-unsubscribe`, - name: `ResourcesUnsubscribe`, - description: `Unsubscribe from resource successfully`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [ - { - id: `MCP-Resources-Subscribe`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/schema#unsubscriberequest` - } - ] - }), - await n.close()); - } catch (e) { - t.push({ - id: `resources-unsubscribe`, - name: `ResourcesUnsubscribe`, - description: `Unsubscribe from resource successfully`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Resources-Subscribe`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/resources#resource-subscriptions` - } - ] - }); - } - return t; - } - }, - Ie = class { - constructor() { - ((this.name = `prompts-list`), - (this.description = `Test listing available prompts. - -**Server Implementation Requirements:** - -**Endpoint**: \`prompts/list\` - -**Requirements**: -- Return array of all available prompts -- Each prompt MUST have: - - \`name\` (string) - - \`description\` (string) - - \`arguments\` (array, optional) - list of required arguments`)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.listPrompts(), - i = []; - (r.prompts - ? (Array.isArray(r.prompts) || i.push(`prompts is not an array`), - r.prompts.forEach((e, t) => { - (e.name || i.push(`Prompt ${t}: missing name`), - e.description || i.push(`Prompt ${t}: missing description`)); - })) - : i.push(`Missing prompts array`), - t.push({ - id: `prompts-list`, - name: `PromptsList`, - description: `Server lists available prompts with valid structure`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Prompts-List`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts` - } - ], - details: { - promptCount: r.prompts?.length || 0, - prompts: r.prompts?.map((e) => e.name) - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `prompts-list`, - name: `PromptsList`, - description: `Server lists available prompts with valid structure`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Prompts-List`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#listing-prompts` - } - ] - }); - } - return t; - } - }, - Le = class { - constructor() { - ((this.name = `prompts-get-simple`), - (this.description = `Test getting a simple prompt without arguments. - -**Server Implementation Requirements:** - -Implement a prompt named \`test_simple_prompt\` with no arguments that returns: - -\`\`\`json -{ - "messages": [ - { - "role": "user", - "content": { - "type": "text", - "text": "This is a simple prompt for testing." - } - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.getPrompt({ name: `test_simple_prompt` }), - i = []; - (r.messages || i.push(`Missing messages array`), - Array.isArray(r.messages) || i.push(`messages is not an array`), - r.messages.length === 0 && i.push(`messages array is empty`), - r.messages.forEach((e, t) => { - (e.role || i.push(`Message ${t}: missing role`), - e.content || i.push(`Message ${t}: missing content`)); - }), - t.push({ - id: `prompts-get-simple`, - name: `PromptsGetSimple`, - description: `Get simple prompt successfully`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Prompts-Get`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts` - } - ], - details: { messageCount: r.messages?.length || 0 } - }), - await n.close()); - } catch (e) { - t.push({ - id: `prompts-get-simple`, - name: `PromptsGetSimple`, - description: `Get simple prompt successfully`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Prompts-Get`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts` - } - ] - }); - } - return t; - } - }, - Re = class { - constructor() { - ((this.name = `prompts-get-with-args`), - (this.description = `Test parameterized prompt. - -**Server Implementation Requirements:** - -Implement a prompt named \`test_prompt_with_arguments\` with arguments: -- \`arg1\` (string, required) - First test argument -- \`arg2\` (string, required) - Second test argument - -Returns (with args \`{arg1: "hello", arg2: "world"}\`): - -\`\`\`json -{ - "messages": [ - { - "role": "user", - "content": { - "type": "text", - "text": "Prompt with arguments: arg1='hello', arg2='world'" - } - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.getPrompt({ - name: `test_prompt_with_arguments`, - arguments: { arg1: `testValue1`, arg2: `testValue2` } - }), - i = []; - (r.messages || i.push(`Missing messages array`), - r.messages.length === 0 && i.push(`messages array is empty`)); - let a = JSON.stringify(r.messages); - (a.includes(`testValue1`) || i.push(`arg1 not substituted in prompt`), - a.includes(`testValue2`) || i.push(`arg2 not substituted in prompt`), - t.push({ - id: `prompts-get-with-args`, - name: `PromptsGetWithArgs`, - description: `Get parameterized prompt with argument substitution`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Prompts-Get`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts` - } - ], - details: { - messageCount: r.messages?.length || 0, - messages: r.messages - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `prompts-get-with-args`, - name: `PromptsGetWithArgs`, - description: `Get parameterized prompt with argument substitution`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Prompts-Get`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#getting-prompts` - } - ] - }); - } - return t; - } - }, - ze = class { - constructor() { - ((this.name = `prompts-get-embedded-resource`), - (this.description = `Test prompt with embedded resource content. - -**Server Implementation Requirements:** - -Implement a prompt named \`test_prompt_with_embedded_resource\` with argument: -- \`resourceUri\` (string, required) - URI of the resource to embed - -Returns: - -\`\`\`json -{ - "messages": [ - { - "role": "user", - "content": { - "type": "resource", - "resource": { - "uri": "", - "mimeType": "text/plain", - "text": "Embedded resource content for testing." - } - } - }, - { - "role": "user", - "content": { - "type": "text", - "text": "Please process the embedded resource above." - } - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.getPrompt({ - name: `test_prompt_with_embedded_resource`, - arguments: { resourceUri: `test://example-resource` } - }), - i = []; - (r.messages || i.push(`Missing messages array`), - r.messages.some( - (e) => - e.content?.type === `resource` || e.content?.resource !== void 0 - ) || i.push(`No embedded resource found in prompt`), - t.push({ - id: `prompts-get-embedded-resource`, - name: `PromptsGetEmbeddedResource`, - description: `Get prompt with embedded resource`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Prompts-Embedded-Resources`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources` - } - ], - details: { - messageCount: r.messages?.length || 0, - messages: r.messages - } - }), - await n.close()); - } catch (e) { - t.push({ - id: `prompts-get-embedded-resource`, - name: `PromptsGetEmbeddedResource`, - description: `Get prompt with embedded resource`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Prompts-Embedded-Resources`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#embedded-resources` - } - ] - }); - } - return t; - } - }, - Be = class { - constructor() { - ((this.name = `prompts-get-with-image`), - (this.description = `Test prompt with image content. - -**Server Implementation Requirements:** - -Implement a prompt named \`test_prompt_with_image\` with no arguments that returns: - -\`\`\`json -{ - "messages": [ - { - "role": "user", - "content": { - "type": "image", - "data": "", - "mimeType": "image/png" - } - }, - { - "role": "user", - "content": { - "type": "text", - "text": "Please analyze the image above." - } - } - ] -} -\`\`\``)); - } - async run(e) { - let t = []; - try { - let n = await D(e), - r = await n.client.getPrompt({ name: `test_prompt_with_image` }), - i = []; - (r.messages || i.push(`Missing messages array`), - r.messages.some( - (e) => - e.content?.type === `image` && - e.content?.data && - e.content?.mimeType - ) || i.push(`No image content found in prompt`), - t.push({ - id: `prompts-get-with-image`, - name: `PromptsGetWithImage`, - description: `Get prompt with image content`, - status: i.length === 0 ? `SUCCESS` : `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: i.length > 0 ? i.join(`; `) : void 0, - specReferences: [ - { - id: `MCP-Prompts-Image`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content` - } - ], - details: { messageCount: r.messages?.length || 0 } - }), - await n.close()); - } catch (e) { - t.push({ - id: `prompts-get-with-image`, - name: `PromptsGetWithImage`, - description: `Get prompt with image content`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: `Failed: ${e instanceof Error ? e.message : String(e)}`, - specReferences: [ - { - id: `MCP-Prompts-Image`, - url: `https://modelcontextprotocol.io/specification/2025-06-18/server/prompts#image-content` - } - ] - }); - } - return t; - } - }; -const F = { - RFC_PRM_DISCOVERY: { - id: `RFC-9728`, - url: `https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1` - }, - RFC_AUTH_SERVER_METADATA_REQUEST: { - id: `RFC-8414-metadata-request`, - url: `https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1` - }, - LEGACY_2025_03_26_AUTH_DISCOVERY: { - id: `MCP-2025-03-26-Authorization-metadata-discovery`, - url: `https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#server-metadata-discovery` - }, - LEGACY_2025_03_26_AUTH_URL_FALLBACK: { - id: `MCP-2025-03-26-Authorization-metadata-url-fallback`, - url: `https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#fallbacks-for-servers-without-metadata-discovery` - }, - MCP_PRM_DISCOVERY: { - id: `MCP-2025-06-18-PRM-discovery`, - url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#protected-resource-metadata-discovery-requirements` - }, - MCP_AUTH_DISCOVERY: { - id: `MCP-Authorization-metadata-discovery`, - url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#authorization-server-metadata-discovery` - }, - MCP_DCR: { - id: `MCP-Dynamic-client-registration`, - url: `https://modelcontextprotocol.io/specification/draft/basic/client#dynamic-client-registration` - }, - OAUTH_2_1_AUTHORIZATION_ENDPOINT: { - id: `OAUTH-2.1-authorization-endpoint`, - url: `https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-authorization-endpoint` - }, - OAUTH_2_1_TOKEN: { - id: `OAUTH-2.1-token-request`, - url: `https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request` - }, - MCP_ACCESS_TOKEN_USAGE: { - id: `MCP-Access-token-usage`, - url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#access-token-usage` - }, - MCP_SCOPE_SELECTION_STRATEGY: { - id: `MCP-Scope-selection-strategy`, - url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-selection-strategy` - }, - MCP_SCOPE_CHALLENGE_HANDLING: { - id: `MCP-Scope-challenge-handling`, - url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#scope-challenge-handling` - }, - MCP_AUTH_ERROR_HANDLING: { - id: `MCP-Auth-error-handling`, - url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#error-handling` - }, - MCP_CLIENT_ID_METADATA_DOCUMENTS: { - id: `MCP-Client-ID-Metadata-Documents`, - url: `https://modelcontextprotocol.io/specification/draft/basic/authorization#client-id-metadata-documents` - }, - IETF_CIMD: { - id: `IETF-OAuth-Client-ID-Metadata-Document`, - url: `https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00` - }, - RFC_JWT_CLIENT_AUTH: { - id: `RFC-7523-JWT-Client-Auth`, - url: `https://datatracker.ietf.org/doc/html/rfc7523#section-2.2` - }, - OAUTH_2_1_CLIENT_CREDENTIALS: { - id: `OAUTH-2.1-client-credentials-grant`, - url: `https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#section-4.2` - }, - SEP_1046_CLIENT_CREDENTIALS: { - id: `SEP-1046-Client-Credentials`, - url: `https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx` - } -}; -function I(e, t, n = {}) { - let { - metadataPath: r = `/.well-known/oauth-authorization-server`, - isOpenIdConfiguration: i = !1, - loggingEnabled: a = !0, - routePrefix: o = ``, - scopesSupported: s, - grantTypesSupported: c = [`authorization_code`, `refresh_token`], - tokenEndpointAuthMethodsSupported: l = [`none`], - tokenEndpointAuthSigningAlgValuesSupported: u, - clientIdMetadataDocumentSupported: d, - tokenVerifier: f, - onTokenRequest: p, - onAuthorizationRequest: m, - onRegistrationRequest: h - } = n, - g = [], - _ = { - authorization_endpoint: `${o}/authorize`, - token_endpoint: `${o}/token`, - registration_endpoint: `${o}/register` - }, - v = y(); - return ( - v.use(y.json()), - v.use(y.urlencoded({ extended: !0 })), - a && - v.use( - E(e, { - incomingId: `incoming-auth-request`, - outgoingId: `outgoing-auth-response` - }) - ), - v.get(r, (n, r) => { - e.push({ - id: `authorization-server-metadata`, - name: `AuthorizationServerMetadata`, - description: `Client requested authorization server metadata`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [ - F.RFC_AUTH_SERVER_METADATA_REQUEST, - F.MCP_AUTH_DISCOVERY - ], - details: { url: n.url, path: n.path } - }); - let a = { - issuer: t(), - authorization_endpoint: `${t()}${_.authorization_endpoint}`, - token_endpoint: `${t()}${_.token_endpoint}`, - registration_endpoint: `${t()}${_.registration_endpoint}`, - response_types_supported: [`code`], - grant_types_supported: c, - code_challenge_methods_supported: [`S256`], - token_endpoint_auth_methods_supported: l, - ...(u && { token_endpoint_auth_signing_alg_values_supported: u }) - }; - (s !== void 0 && (a.scopes_supported = s), - d !== void 0 && (a.client_id_metadata_document_supported = d), - i && - ((a.jwks_uri = `${t()}/.well-known/jwks.json`), - (a.subject_types_supported = [`public`]), - (a.id_token_signing_alg_values_supported = [`RS256`])), - r.json(a)); - }), - v.get(_.authorization_endpoint, (t, n) => { - let r = new Date().toISOString(); - e.push({ - id: `authorization-request`, - name: `AuthorizationRequest`, - description: `Client made authorization request`, - status: `SUCCESS`, - timestamp: r, - specReferences: [F.OAUTH_2_1_AUTHORIZATION_ENDPOINT], - details: { query: t.query } - }); - let i = t.query.scope; - ((g = i ? i.split(` `) : []), - m && m({ clientId: t.query.client_id, scope: i, timestamp: r })); - let a = t.query.redirect_uri, - o = t.query.state, - s = new URL(a); - (s.searchParams.set(`code`, `test-auth-code`), - o && s.searchParams.set(`state`, o), - n.redirect(s.toString())); - }), - v.post(_.token_endpoint, async (n, r) => { - let i = new Date().toISOString(), - a = n.body.scope, - o = n.body.grant_type; - e.push({ - id: `token-request`, - name: `TokenRequest`, - description: `Client requested access token`, - status: `SUCCESS`, - timestamp: i, - specReferences: [F.OAUTH_2_1_TOKEN], - details: { endpoint: `/token`, grantType: o } - }); - let s = `test-token-${Date.now()}`, - c = g; - if (p) { - let e = await p({ - scope: a, - grantType: o, - timestamp: i, - body: n.body, - authBaseUrl: t(), - tokenEndpoint: `${t()}${_.token_endpoint}`, - authorizationHeader: n.headers.authorization - }); - if (`error` in e) { - r.status(e.statusCode || 400).json({ - error: e.error, - error_description: e.errorDescription - }); - return; - } - ((s = e.token), (c = e.scopes)); - } - (f && f.registerToken(s, c), - r.json({ - access_token: s, - token_type: `Bearer`, - expires_in: 3600, - ...(c.length > 0 && { scope: c.join(` `) }) - })); - }), - v.post(_.registration_endpoint, (t, n) => { - let r = `test-client-id`, - i = `test-client-secret`, - a; - if (h) { - let e = h(t); - ((r = e.clientId), - (i = e.clientSecret), - (a = e.tokenEndpointAuthMethod)); - } - (e.push({ - id: `client-registration`, - name: `ClientRegistration`, - description: `Client registered with authorization server`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_DCR], - details: { - endpoint: `/register`, - clientName: t.body.client_name, - ...(a && { tokenEndpointAuthMethod: a }) - } - }), - n.status(201).json({ - client_id: r, - ...(i && { client_secret: i }), - client_name: t.body.client_name || `test-client`, - redirect_uris: t.body.redirect_uris || [], - ...(a && { token_endpoint_auth_method: a }) - })); - }), - v - ); -} -var L = class { - constructor(e, t = []) { - ((this.checks = e), - (this.expectedScopes = t), - (this.tokenScopes = new Map())); - } - registerToken(e, t) { - this.tokenScopes.set(e, t); - } - async verifyAccessToken(e) { - if (e.startsWith(`test-token`) || e.startsWith(`cc-token`)) { - let t = this.tokenScopes.get(e) || []; - return ( - this.checks.push({ - id: `valid-bearer-token`, - name: `ValidBearerToken`, - description: `Client provided valid bearer token`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_ACCESS_TOKEN_USAGE], - details: { token: e.substring(0, 15) + `...`, scopes: t } - }), - { - token: e, - clientId: `test-client`, - scopes: t, - expiresAt: Math.floor(Date.now() / 1e3) + 3600 - } - ); - } - throw ( - this.checks.push({ - id: `invalid-bearer-token`, - name: `InvalidBearerToken`, - description: `Client provided invalid bearer token`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_ACCESS_TOKEN_USAGE], - details: { - message: `Token verification failed`, - token: e ? e.substring(0, 10) + `...` : `missing` - } - }), - Error(`Invalid token`) - ); - } -}; -function R(e, t, n, r = {}) { - let { - prmPath: i = `/.well-known/oauth-protected-resource/mcp`, - requiredScopes: a = [], - scopesSupported: o, - includePrmInWwwAuth: u = !0, - includeScopeInWwwAuth: d = !1, - tokenVerifier: f - } = r, - p = new s( - { name: `auth-prm-pathbased-server`, version: `1.0.0` }, - { capabilities: { tools: {} } } - ); - (p.setRequestHandler(h, async () => ({ - tools: [{ name: `test-tool`, inputSchema: { type: `object` } }] - })), - p.setRequestHandler(l, async (e) => { - if (e.params.name === `test-tool`) - return { content: [{ type: `text`, text: `test` }] }; - throw new _(m.InvalidParams, `Tool ${e.params.name} not found`); - })); - let g = y(); - return ( - g.use(y.json()), - g.use( - E(e, { - incomingId: `incoming-request`, - outgoingId: `outgoing-response`, - mcpRoute: `/mcp` - }) - ), - i !== null && - g.get(i, (r, a) => { - e.push({ - id: `prm-pathbased-requested`, - name: `PRMPathBasedRequested`, - description: `Client requested PRM metadata at path-based location`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [F.RFC_PRM_DISCOVERY, F.MCP_PRM_DISCOVERY], - details: { url: r.url, path: r.path } - }); - let s = { - resource: - i === `/.well-known/oauth-protected-resource` ? t() : `${t()}/mcp`, - authorization_servers: [n()] - }; - (o !== void 0 && (s.scopes_supported = o), a.json(s)); - }), - g.post(`/mcp`, async (n, o, s) => { - let l = f || new L(e, a); - ( - r.authMiddleware ?? - te({ - verifier: l, - requiredScopes: d ? a : [], - ...(u && i !== null && { resourceMetadataUrl: `${t()}${i}` }) - }) - )(n, o, async (e) => { - if (e) return s(e); - let t = new c({ sessionIdGenerator: void 0 }); - try { - (await p.connect(t), - await t.handleRequest(n, o, n.body), - o.on(`close`, () => { - (t.close(), p.close()); - })); - } catch (e) { - (console.error(`Error handling MCP request:`, e), - o.headersSent || - o.status(500).json({ - jsonrpc: `2.0`, - error: { code: -32603, message: `Internal server error` }, - id: null - })); - } - }); - }), - g - ); -} -var z = class { - constructor() { - ((this.app = null), - (this.httpServer = null), - (this.baseUrl = ``), - (this.getUrl = () => this.baseUrl)); - } - async start(e) { - return ( - (this.app = e), - (this.httpServer = this.app.listen(0)), - (this.baseUrl = `http://localhost:${this.httpServer.address().port}`), - this.baseUrl - ); - } - async stop() { - ((this.httpServer &&= - (await new Promise((e) => { - (this.httpServer.closeAllConnections?.(), - this.httpServer.close(() => e())); - }), - null)), - (this.app = null)); - } -}; -const B = [ - { - name: `metadata-default`, - prmLocation: `/.well-known/oauth-protected-resource/mcp`, - inWwwAuth: !0, - oauthMetadataLocation: `/.well-known/oauth-authorization-server`, - trapRootPrm: !0 - }, - { - name: `metadata-var1`, - prmLocation: `/.well-known/oauth-protected-resource/mcp`, - inWwwAuth: !1, - oauthMetadataLocation: `/.well-known/openid-configuration` - }, - { - name: `metadata-var2`, - prmLocation: `/.well-known/oauth-protected-resource`, - inWwwAuth: !1, - oauthMetadataLocation: `/.well-known/oauth-authorization-server/tenant1`, - authRoutePrefix: `/tenant1` - }, - { - name: `metadata-var3`, - prmLocation: `/custom/metadata/location.json`, - inWwwAuth: !0, - oauthMetadataLocation: `/tenant1/.well-known/openid-configuration`, - authRoutePrefix: `/tenant1` - } -]; -function V(e) { - let t = new z(), - n = new z(), - r = [], - i = e.authRoutePrefix || ``, - a = e.oauthMetadataLocation.includes(`openid-configuration`), - o = e.prmLocation === `/.well-known/oauth-protected-resource/mcp`; - return { - name: `auth/${e.name}`, - description: `Tests Basic OAuth metadata discovery flow. - -**PRM:** ${e.prmLocation}${e.inWwwAuth ? `` : ` (not in WWW-Authenticate)`} -**OAuth metadata:** ${e.oauthMetadataLocation} -`, - async start() { - r = []; - let o = I(r, t.getUrl, { - metadataPath: e.oauthMetadataLocation, - isOpenIdConfiguration: a, - ...(i && { routePrefix: i }) - }); - (i && - o.get(`/.well-known/oauth-authorization-server`, (e, t) => { - (r.push({ - id: `authorization-server-metadata-wrong-path`, - name: `AuthorizationServerMetadataWrongPath`, - description: `Client requested authorization server at the root path when the AS URL has a path-based location`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [ - F.RFC_AUTH_SERVER_METADATA_REQUEST, - F.MCP_AUTH_DISCOVERY - ], - details: { url: e.url } - }), - t.status(404).send(`Not Found`)); - }), - await t.start(o)); - let s = i ? () => `${t.getUrl()}${i}` : t.getUrl, - c = R(r, n.getUrl, s, { - prmPath: e.prmLocation, - includePrmInWwwAuth: e.inWwwAuth - }); - return ( - e.trapRootPrm && - c.get(`/.well-known/oauth-protected-resource`, (e, t) => { - (r.push({ - id: `prm-priority-order`, - name: `PRM Priority Order`, - description: `Client requested PRM metadata at root location on a server with path-based PRM`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.RFC_PRM_DISCOVERY, F.MCP_PRM_DISCOVERY], - details: { url: e.url, path: e.path } - }), - t.status(404).json({ - error: `not_found`, - error_description: `PRM metadata not available at root location` - })); - }), - await n.start(c), - { serverUrl: `${n.getUrl()}/mcp` } - ); - }, - async stop() { - (await t.stop(), await n.stop()); - }, - getChecks() { - let e = [ - ...(o ? [`prm-pathbased-requested`] : []), - `authorization-server-metadata`, - `client-registration`, - `authorization-request`, - `token-request` - ]; - for (let t of e) - r.find((e) => e.id === t) || - r.push({ - id: t, - name: `Expected Check Missing: ${t}`, - description: `Expected Check Missing: ${t}`, - status: `FAILURE`, - timestamp: new Date().toISOString() - }); - return r; - } - }; -} -(V(B[0]), V(B[1]), V(B[2]), V(B[3])); -const Ve = B.map(V); -function He() { - return Ve.map((e) => e.name); -} -const H = `https://conformance-test.local/client-metadata.json`; -var Ue = class { - constructor() { - ((this.name = `auth/basic-cimd`), - (this.description = `Tests OAuth flow with Client ID Metadata Documents (SEP-991/URL-based client IDs). Server advertises client_id_metadata_document_supported=true and client should use URL as client_id instead of DCR.`), - (this.authServer = new z()), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = I(this.checks, this.authServer.getUrl, { - clientIdMetadataDocumentSupported: !0, - onAuthorizationRequest: (e) => { - let t = e.clientId === H; - this.checks.push({ - id: `cimd-client-id-used`, - name: `Client ID Metadata Document Usage`, - description: t - ? `Client correctly used URL-based client ID when server supports client_id_metadata_document_supported` - : `Client SHOULD use URL-based client ID when server advertises client_id_metadata_document_supported=true`, - status: t ? `SUCCESS` : `WARNING`, - timestamp: e.timestamp, - specReferences: [F.MCP_CLIENT_ID_METADATA_DOCUMENTS, F.IETF_CIMD], - details: { - expectedClientId: H, - actualClientId: e.clientId || `none` - } - }); - } - }); - await this.authServer.start(e); - let t = R(this.checks, this.server.getUrl, this.authServer.getUrl); - return ( - await this.server.start(t), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - return ( - this.checks.some((e) => e.id === `cimd-client-id-used`) || - this.checks.push({ - id: `cimd-client-id-used`, - name: `Client ID Metadata Document Usage`, - description: `Client did not make an authorization request to test CIMD support`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_CLIENT_ID_METADATA_DOCUMENTS, F.IETF_CIMD] - }), - this.checks - ); - } - }, - We = class { - constructor() { - ((this.name = `auth/2025-03-26-oauth-metadata-backcompat`), - (this.description = `Tests 2025-03-26 spec OAuth flow: no PRM (Protected Resource Metadata), OAuth metadata at root location`), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = I(this.checks, this.server.getUrl, { - loggingEnabled: !1, - routePrefix: `/oauth` - }), - t = R(this.checks, this.server.getUrl, this.server.getUrl, { - prmPath: null - }); - return ( - t.use(e), - await this.server.start(t), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - await this.server.stop(); - } - getChecks() { - for (let e of [ - `authorization-server-metadata`, - `client-registration`, - `authorization-request`, - `token-request` - ]) - this.checks.find((t) => t.id === e) || - this.checks.push({ - id: e, - name: `Expected Check Missing: ${e}`, - description: `Expected Check Missing: ${e}`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.LEGACY_2025_03_26_AUTH_DISCOVERY] - }); - return this.checks; - } - }, - Ge = class { - constructor() { - ((this.name = `auth/2025-03-26-oauth-endpoint-fallback`), - (this.description = `Tests OAuth flow with no metadata endpoints, relying on fallback to standard OAuth endpoints at server root (2025-03-26 spec behavior)`), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = R(this.checks, this.server.getUrl, this.server.getUrl, { - prmPath: null - }); - return ( - e.use(y.urlencoded({ extended: !0 })), - e.get(`/authorize`, (e, t) => { - this.checks.push({ - id: `authorization-request`, - name: `AuthorizationRequest`, - description: `Client made authorization request to fallback endpoint`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [F.LEGACY_2025_03_26_AUTH_URL_FALLBACK], - details: { - response_type: e.query.response_type, - client_id: e.query.client_id, - redirect_uri: e.query.redirect_uri, - state: e.query.state, - code_challenge: e.query.code_challenge ? `present` : `missing`, - code_challenge_method: e.query.code_challenge_method - } - }); - let n = e.query.redirect_uri, - r = e.query.state, - i = new URL(n); - (i.searchParams.set(`code`, `test-auth-code`), - r && i.searchParams.set(`state`, r), - t.redirect(i.toString())); - }), - e.post(`/token`, (e, t) => { - (this.checks.push({ - id: `token-request`, - name: `TokenRequest`, - description: `Client requested access token from fallback endpoint`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [F.LEGACY_2025_03_26_AUTH_URL_FALLBACK], - details: { endpoint: `/token`, grantType: e.body.grant_type } - }), - t.json({ - access_token: `test-token`, - token_type: `Bearer`, - expires_in: 3600 - })); - }), - e.post(`/register`, (e, t) => { - (this.checks.push({ - id: `client-registration`, - name: `ClientRegistration`, - description: `Client registered with authorization server at fallback endpoint`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [F.LEGACY_2025_03_26_AUTH_URL_FALLBACK], - details: { endpoint: `/register`, clientName: e.body.client_name } - }), - t.status(201).json({ - client_id: `test-client-id`, - client_secret: `test-client-secret`, - client_name: e.body.client_name || `test-client`, - redirect_uris: e.body.redirect_uris || [] - })); - }), - await this.server.start(e), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - await this.server.stop(); - } - getChecks() { - for (let e of [ - `client-registration`, - `authorization-request`, - `token-request` - ]) - this.checks.find((t) => t.id === e) || - this.checks.push({ - id: e, - name: `Expected Check Missing: ${e}`, - description: `Expected Check Missing: ${e}`, - status: `FAILURE`, - timestamp: new Date().toISOString() - }); - return this.checks; - } - }, - Ke = class { - constructor() { - ((this.name = `auth/scope-from-www-authenticate`), - (this.description = `Tests that client uses scope parameter from WWW-Authenticate header when provided`), - (this.authServer = new z()), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = `mcp:basic`, - t = new L(this.checks, [e]), - n = I(this.checks, this.authServer.getUrl, { - tokenVerifier: t, - onAuthorizationRequest: (t) => { - let n = (t.scope ? t.scope.split(` `) : []).includes(e); - this.checks.push({ - id: `scope-from-www-authenticate`, - name: `Client scope selection from WWW-Authenticate header`, - description: n - ? `Client correctly used the scope parameter from the WWW-Authenticate header` - : `Client SHOULD use the scope parameter from the WWW-Authenticate header when provided`, - status: n ? `SUCCESS` : `WARNING`, - timestamp: t.timestamp, - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], - details: { expectedScope: e, requestedScope: t.scope || `none` } - }); - } - }); - await this.authServer.start(n); - let r = R(this.checks, this.server.getUrl, this.authServer.getUrl, { - prmPath: `/.well-known/oauth-protected-resource/mcp`, - requiredScopes: [e], - includeScopeInWwwAuth: !0, - tokenVerifier: t - }); - return ( - await this.server.start(r), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - return ( - this.checks.some((e) => e.id === `scope-from-www-authenticate`) || - this.checks.push({ - id: `scope-from-www-authenticate`, - name: `Client scope selection from WWW-Authenticate header`, - description: `Client did not complete authorization flow - scope check could not be performed`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] - }), - this.checks - ); - } - }, - qe = class { - constructor() { - ((this.name = `auth/scope-from-scopes-supported`), - (this.description = `Tests that client uses all scopes from scopes_supported when scope not in WWW-Authenticate header`), - (this.authServer = new z()), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = [`mcp:basic`, `mcp:read`, `mcp:write`], - t = new L(this.checks, e), - n = I(this.checks, this.authServer.getUrl, { - tokenVerifier: t, - onAuthorizationRequest: (t) => { - let n = t.scope ? t.scope.split(` `) : [], - r = e.every((e) => n.includes(e)); - this.checks.push({ - id: `scope-from-scopes-supported`, - name: `Client scope selection from scopes_supported`, - description: r - ? `Client correctly used all scopes from scopes_supported in PRM when scope not in WWW-Authenticate` - : `Client SHOULD use all scopes from scopes_supported when scope not available in WWW-Authenticate header`, - status: r ? `SUCCESS` : `WARNING`, - timestamp: t.timestamp, - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], - details: { - scopesSupported: e.join(` `), - requestedScope: t.scope || `none`, - ...(r - ? {} - : { - missingScopes: e.filter((e) => !n.includes(e)).join(` `) - }) - } - }); - } - }); - await this.authServer.start(n); - let r = R(this.checks, this.server.getUrl, this.authServer.getUrl, { - prmPath: `/.well-known/oauth-protected-resource/mcp`, - requiredScopes: e, - scopesSupported: e, - includeScopeInWwwAuth: !1, - tokenVerifier: t - }); - return ( - await this.server.start(r), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - return ( - this.checks.some((e) => e.id === `scope-from-scopes-supported`) || - this.checks.push({ - id: `scope-from-scopes-supported`, - name: `Client scope selection from scopes_supported`, - description: `Client did not complete authorization flow - scope check could not be performed`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] - }), - this.checks - ); - } - }, - Je = class { - constructor() { - ((this.name = `auth/scope-omitted-when-undefined`), - (this.description = `Tests that client omits scope parameter when scopes_supported is undefined`), - (this.authServer = new z()), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = new L(this.checks, []), - t = I(this.checks, this.authServer.getUrl, { - tokenVerifier: e, - onAuthorizationRequest: (e) => { - let t = !e.scope || e.scope.trim() === ``; - this.checks.push({ - id: `scope-omitted-when-undefined`, - name: `Client scope omission when scopes_supported undefined`, - description: t - ? `Client correctly omitted scope parameter when scopes_supported is undefined` - : `Client SHOULD omit scope parameter when scopes_supported is undefined and scope not in WWW-Authenticate`, - status: t ? `SUCCESS` : `WARNING`, - timestamp: e.timestamp, - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], - details: { scopeParameter: t ? `omitted` : e.scope } - }); - } - }); - await this.authServer.start(t); - let n = R(this.checks, this.server.getUrl, this.authServer.getUrl, { - prmPath: `/.well-known/oauth-protected-resource/mcp`, - requiredScopes: [], - scopesSupported: void 0, - includeScopeInWwwAuth: !1, - tokenVerifier: e - }); - return ( - await this.server.start(n), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - return ( - this.checks.some((e) => e.id === `scope-omitted-when-undefined`) || - this.checks.push({ - id: `scope-omitted-when-undefined`, - name: `Client scope omission when scopes_supported undefined`, - description: `Client did not complete authorization flow - scope check could not be performed`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] - }), - this.checks - ); - } - }, - Ye = class { - constructor() { - ((this.name = `auth/scope-step-up`), - (this.description = `Tests that client handles step-up authentication with different scope requirements per operation`), - (this.authServer = new z()), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = `mcp:basic`, - t = [`mcp:basic`, `mcp:write`], - n = new L(this.checks, t), - r = 0, - i = I(this.checks, this.authServer.getUrl, { - tokenVerifier: n, - onAuthorizationRequest: (n) => { - r++; - let i = n.scope ? n.scope.split(` `) : []; - if (r === 1) { - let t = i.includes(e); - this.checks.push({ - id: `scope-step-up-initial`, - name: `Client initial scope selection for step-up auth`, - description: t - ? `Client correctly used scope from WWW-Authenticate header for initial auth` - : `Client SHOULD use the scope parameter from the WWW-Authenticate header`, - status: t ? `SUCCESS` : `WARNING`, - timestamp: n.timestamp, - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], - details: { expectedScope: e, requestedScope: n.scope || `none` } - }); - } else if (r === 2) { - let e = t.every((e) => i.includes(e)); - this.checks.push({ - id: `scope-step-up-escalation`, - name: `Client scope escalation for step-up auth`, - description: e - ? `Client correctly escalated scopes for step-up authentication` - : `Client SHOULD request additional scopes when receiving 403 with new scope requirements`, - status: e ? `SUCCESS` : `WARNING`, - timestamp: n.timestamp, - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY], - details: { - expectedScopes: t.join(` `), - requestedScope: n.scope || `none` - } - }); - } - } - }); - await this.authServer.start(i); - let a = () => - `${this.server.getUrl()}/.well-known/oauth-protected-resource/mcp`, - o = R(this.checks, this.server.getUrl, this.authServer.getUrl, { - prmPath: `/.well-known/oauth-protected-resource/mcp`, - requiredScopes: t, - scopesSupported: t, - includeScopeInWwwAuth: !0, - authMiddleware: async (r, i, o) => { - let s = r.body; - typeof s == `string` && (s = JSON.parse(s)); - let c = s?.method; - if (c === `initialize` || c?.startsWith(`notifications/`)) - return o(); - let l = r.headers.authorization; - if (!l || !l.startsWith(`Bearer `)) - return i - .status(401) - .set( - `WWW-Authenticate`, - `Bearer scope="${e}", resource_metadata="${a()}"` - ) - .json({ - error: `invalid_token`, - error_description: `Missing Authorization header` - }); - let u = l.substring(7), - d = (await n.verifyAccessToken(u)).scopes || [], - f = c === `tools/call` ? t : [e]; - if (!f.every((e) => d.includes(e))) - return i - .status(403) - .set( - `WWW-Authenticate`, - `Bearer scope="${f.join(` `)}", resource_metadata="${a()}", error="insufficient_scope"` - ) - .json({ - error: `insufficient_scope`, - error_description: `Token has insufficient scope` - }); - o(); - }, - tokenVerifier: n - }); - return ( - await this.server.start(o), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - let e = this.checks.some((e) => e.id === `scope-step-up-initial`), - t = this.checks.some((e) => e.id === `scope-step-up-escalation`); - return ( - e || - this.checks.push({ - id: `scope-step-up-initial`, - name: `Client initial scope selection for step-up auth`, - description: `Client did not make an initial authorization request`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] - }), - t || - this.checks.push({ - id: `scope-step-up-escalation`, - name: `Client scope escalation for step-up auth`, - description: `Client did not make a second authorization request for scope escalation`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_SCOPE_SELECTION_STRATEGY] - }), - this.checks - ); - } - }, - Xe = class { - constructor() { - ((this.name = `auth/scope-retry-limit`), - (this.description = `Tests that client implements retry limits to prevent infinite authorization loops on repeated 403 responses`), - (this.authServer = new z()), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = `mcp:admin`, - t = new L(this.checks, []), - n = 0, - r = I(this.checks, this.authServer.getUrl, { - tokenVerifier: t, - onAuthorizationRequest: (e) => { - (n++, - this.checks.push({ - id: `scope-retry-auth-attempt`, - name: `Authorization attempt ${n}`, - description: `Client made authorization request attempt ${n}`, - status: `INFO`, - timestamp: e.timestamp, - specReferences: [F.MCP_SCOPE_CHALLENGE_HANDLING], - details: { attemptNumber: n, requestedScope: e.scope || `none` } - })); - } - }); - await this.authServer.start(r); - let i = () => - `${this.server.getUrl()}/.well-known/oauth-protected-resource/mcp`, - a = 0, - o = R(this.checks, this.server.getUrl, this.authServer.getUrl, { - prmPath: `/.well-known/oauth-protected-resource/mcp`, - requiredScopes: [e], - scopesSupported: [e], - includeScopeInWwwAuth: !0, - authMiddleware: async (t, n, r) => { - let o = t.body; - typeof o == `string` && (o = JSON.parse(o)); - let s = o?.method; - if (s === `initialize` || s?.startsWith(`notifications/`)) - return r(); - let c = t.headers.authorization; - return !c || !c.startsWith(`Bearer `) - ? n - .status(401) - .set( - `WWW-Authenticate`, - `Bearer scope="${e}", resource_metadata="${i()}"` - ) - .json({ - error: `invalid_token`, - error_description: `Missing Authorization header` - }) - : (a++, - a > 3 - ? n.status(410).json({ - error: `test_complete`, - error_description: `Test is over - client exceeded maximum retry attempts` - }) - : n - .status(403) - .set( - `WWW-Authenticate`, - `Bearer error="insufficient_scope", scope="${e}", resource_metadata="${i()}", error_description="Scope upgrade will never succeed"` - ) - .json({ - error: `insufficient_scope`, - error_description: `Scope upgrade will never succeed` - })); - }, - tokenVerifier: t - }); - return ( - await this.server.start(o), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - let e = this.checks.filter( - (e) => e.id === `scope-retry-auth-attempt` - ).length; - return ( - e === 0 - ? this.checks.push({ - id: `scope-retry-limit`, - name: `Client retry limit for scope escalation`, - description: `Client did not make any authorization requests`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_SCOPE_CHALLENGE_HANDLING] - }) - : e <= 3 - ? this.checks.push({ - id: `scope-retry-limit`, - name: `Client retry limit for scope escalation`, - description: `Client correctly limited retry attempts to ${e} (3 or fewer)`, - status: `SUCCESS`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_SCOPE_CHALLENGE_HANDLING], - details: { authorizationAttempts: e, maxAllowed: 3 } - }) - : this.checks.push({ - id: `scope-retry-limit`, - name: `Client retry limit for scope escalation`, - description: `Client made ${e} authorization attempts (more than 3). Clients SHOULD implement retry limits to avoid infinite loops.`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.MCP_SCOPE_CHALLENGE_HANDLING], - details: { authorizationAttempts: e, maxAllowed: 3 } - }), - this.checks - ); - } - }; -function Ze(e, t) { - return e?.startsWith(`Basic `) - ? `client_secret_basic` - : t - ? `client_secret_post` - : `none`; -} -function Qe(e) { - let t = e.substring(6); - try { - return Buffer.from(t, `base64`).toString(`utf-8`).includes(`:`) - ? { valid: !0 } - : { valid: !1, error: `missing colon separator` }; - } catch { - return { valid: !1, error: `base64 decoding failed` }; - } -} -const $e = { - client_secret_basic: `HTTP Basic authentication (client_secret_basic)`, - client_secret_post: `client_secret_post`, - none: `no authentication (public client)` -}; -var U = class { - constructor(e) { - ((this.authServer = new z()), - (this.server = new z()), - (this.checks = []), - (this.expectedAuthMethod = e), - (this.name = `auth/token-endpoint-auth-${e === `client_secret_basic` ? `basic` : e === `client_secret_post` ? `post` : `none`}`), - (this.description = `Tests that client uses ${$e[e]} when server only supports ${e}`)); - } - async start() { - this.checks = []; - let e = new L(this.checks, []), - t = I(this.checks, this.authServer.getUrl, { - tokenVerifier: e, - tokenEndpointAuthMethodsSupported: [this.expectedAuthMethod], - onTokenRequest: ({ - authorizationHeader: e, - body: t, - timestamp: n - }) => { - let r = t.client_secret, - i = Ze(e, r), - a = i === this.expectedAuthMethod, - o; - if (i === `client_secret_basic` && e) { - let t = Qe(e); - t.valid || (o = t.error); - } - let s = a && !o ? `SUCCESS` : `FAILURE`, - c; - return ( - (c = o - ? `Client sent Basic auth header but ${o}` - : a - ? `Client correctly used ${$e[this.expectedAuthMethod]} for token endpoint` - : `Client used ${i} but server only supports ${this.expectedAuthMethod}`), - this.checks.push({ - id: `token-endpoint-auth-method`, - name: `Token endpoint authentication method`, - description: c, - status: s, - timestamp: n, - specReferences: [F.OAUTH_2_1_TOKEN], - details: { - expectedAuthMethod: this.expectedAuthMethod, - actualAuthMethod: i, - hasAuthorizationHeader: !!e, - hasBodyClientSecret: !!r, - ...(o && { formatError: o }) - } - }), - { token: `test-token-${Date.now()}`, scopes: [] } - ); - }, - onRegistrationRequest: () => ({ - clientId: `test-client-${Date.now()}`, - clientSecret: - this.expectedAuthMethod === `none` - ? void 0 - : `test-secret-${Date.now()}`, - tokenEndpointAuthMethod: this.expectedAuthMethod - }) - }); - await this.authServer.start(t); - let n = R(this.checks, this.server.getUrl, this.authServer.getUrl, { - prmPath: `/.well-known/oauth-protected-resource/mcp`, - requiredScopes: [], - tokenVerifier: e - }); - return ( - await this.server.start(n), - { serverUrl: `${this.server.getUrl()}/mcp` } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - return ( - this.checks.some((e) => e.id === `token-endpoint-auth-method`) || - this.checks.push({ - id: `token-endpoint-auth-method`, - name: `Token endpoint authentication method`, - description: `Client did not make a token request`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [F.OAUTH_2_1_TOKEN] - }), - this.checks - ); - } - }, - et = class extends U { - constructor() { - super(`client_secret_basic`); - } - }, - tt = class extends U { - constructor() { - super(`client_secret_post`); - } - }, - nt = class extends U { - constructor() { - super(`none`); - } - }; -const W = `conformance-test-client`, - rt = `conformance-test-secret`; -async function it() { - let { publicKey: e, privateKey: t } = await C.generateKeyPair(`ES256`, { - extractable: !0 - }); - return { publicKey: e, privateKeyPem: await C.exportPKCS8(t) }; -} -var at = class { - constructor() { - ((this.name = `auth/client-credentials-jwt`), - (this.description = `Tests OAuth client_credentials flow with private_key_jwt authentication (SEP-1046)`), - (this.authServer = new z()), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let { publicKey: e, privateKeyPem: t } = await it(), - n = I(this.checks, this.authServer.getUrl, { - grantTypesSupported: [`client_credentials`], - tokenEndpointAuthMethodsSupported: [`private_key_jwt`], - tokenEndpointAuthSigningAlgValuesSupported: [`ES256`], - onTokenRequest: async ({ - grantType: t, - body: n, - timestamp: r, - authBaseUrl: i - }) => { - let a = i; - if (t !== `client_credentials`) - return ( - this.checks.push({ - id: `client-credentials-grant-type`, - name: `ClientCredentialsGrantType`, - description: `Expected grant_type=client_credentials, got ${t}`, - status: `FAILURE`, - timestamp: r, - specReferences: [ - F.OAUTH_2_1_CLIENT_CREDENTIALS, - F.SEP_1046_CLIENT_CREDENTIALS - ] - }), - { - error: `unsupported_grant_type`, - errorDescription: `Only client_credentials grant is supported` - } - ); - let o = n.client_assertion, - s = n.client_assertion_type; - if (s !== `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`) - return ( - this.checks.push({ - id: `client-credentials-assertion-type`, - name: `ClientCredentialsAssertionType`, - description: `Invalid client_assertion_type: ${s}`, - status: `FAILURE`, - timestamp: r, - specReferences: [F.RFC_JWT_CLIENT_AUTH] - }), - { - error: `invalid_client`, - errorDescription: `Invalid client_assertion_type`, - statusCode: 401 - } - ); - try { - let t = a.replace(/\/+$/, ``), - { payload: i } = await C.jwtVerify(o, e, { - audience: [a, t, `${t}/`], - clockTolerance: 30 - }); - if (i.iss !== W) - return ( - this.checks.push({ - id: `client-credentials-jwt-iss`, - name: `ClientCredentialsJwtIss`, - description: `JWT iss claim '${i.iss}' does not match expected client_id '${W}'`, - status: `FAILURE`, - timestamp: r, - specReferences: [F.RFC_JWT_CLIENT_AUTH], - details: { expected: W, actual: i.iss } - }), - { - error: `invalid_client`, - errorDescription: `JWT iss claim does not match expected client_id`, - statusCode: 401 - } - ); - if (i.sub !== W) - return ( - this.checks.push({ - id: `client-credentials-jwt-sub`, - name: `ClientCredentialsJwtSub`, - description: `JWT sub claim '${i.sub}' does not match expected client_id '${W}'`, - status: `FAILURE`, - timestamp: r, - specReferences: [F.RFC_JWT_CLIENT_AUTH], - details: { expected: W, actual: i.sub } - }), - { - error: `invalid_client`, - errorDescription: `JWT sub claim does not match expected client_id`, - statusCode: 401 - } - ); - this.checks.push({ - id: `client-credentials-jwt-verified`, - name: `ClientCredentialsJwtVerified`, - description: `Client successfully authenticated with signed JWT assertion`, - status: `SUCCESS`, - timestamp: r, - specReferences: [ - F.OAUTH_2_1_CLIENT_CREDENTIALS, - F.SEP_1046_CLIENT_CREDENTIALS, - F.RFC_JWT_CLIENT_AUTH - ], - details: { iss: i.iss, sub: i.sub, aud: i.aud } - }); - let s = n.scope ? n.scope.split(` `) : []; - return { token: `cc-token-${Date.now()}`, scopes: s }; - } catch (e) { - let t = e instanceof Error ? e.message : String(e); - return ( - this.checks.push({ - id: `client-credentials-jwt-verified`, - name: `ClientCredentialsJwtVerified`, - description: `JWT verification failed: ${t}`, - status: `FAILURE`, - timestamp: r, - specReferences: [F.RFC_JWT_CLIENT_AUTH], - details: { error: t } - }), - { - error: `invalid_client`, - errorDescription: `JWT verification failed: ${t}`, - statusCode: 401 - } - ); - } - } - }); - await this.authServer.start(n); - let r = R(this.checks, this.server.getUrl, this.authServer.getUrl); - return ( - await this.server.start(r), - { - serverUrl: `${this.server.getUrl()}/mcp`, - context: { - client_id: W, - private_key_pem: t, - signing_algorithm: `ES256` - } - } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - return ( - this.checks.some((e) => e.id === `client-credentials-jwt-verified`) || - this.checks.push({ - id: `client-credentials-jwt-verified`, - name: `ClientCredentialsJwtVerified`, - description: `Client did not make a client_credentials token request`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [ - F.OAUTH_2_1_CLIENT_CREDENTIALS, - F.SEP_1046_CLIENT_CREDENTIALS - ] - }), - this.checks - ); - } - }, - ot = class { - constructor() { - ((this.name = `auth/client-credentials-basic`), - (this.description = `Tests OAuth client_credentials flow with client_secret_basic authentication`), - (this.authServer = new z()), - (this.server = new z()), - (this.checks = [])); - } - async start() { - this.checks = []; - let e = I(this.checks, this.authServer.getUrl, { - grantTypesSupported: [`client_credentials`], - tokenEndpointAuthMethodsSupported: [`client_secret_basic`], - onTokenRequest: async ({ - grantType: e, - body: t, - timestamp: n, - authorizationHeader: r - }) => { - if (e !== `client_credentials`) - return ( - this.checks.push({ - id: `client-credentials-grant-type`, - name: `ClientCredentialsGrantType`, - description: `Expected grant_type=client_credentials, got ${e}`, - status: `FAILURE`, - timestamp: n, - specReferences: [ - F.OAUTH_2_1_CLIENT_CREDENTIALS, - F.SEP_1046_CLIENT_CREDENTIALS - ] - }), - { - error: `unsupported_grant_type`, - errorDescription: `Only client_credentials grant is supported` - } - ); - let i = r; - if (!i || !i.startsWith(`Basic `)) - return ( - this.checks.push({ - id: `client-credentials-basic-auth`, - name: `ClientCredentialsBasicAuth`, - description: `Missing or invalid Authorization header for Basic auth`, - status: `FAILURE`, - timestamp: n, - specReferences: [F.SEP_1046_CLIENT_CREDENTIALS] - }), - { - error: `invalid_client`, - errorDescription: `Missing or invalid Authorization header`, - statusCode: 401 - } - ); - let a = i.slice(6), - [o, s] = Buffer.from(a, `base64`).toString(`utf-8`).split(`:`); - if (o !== W || s !== rt) - return ( - this.checks.push({ - id: `client-credentials-basic-auth`, - name: `ClientCredentialsBasicAuth`, - description: `Invalid client credentials`, - status: `FAILURE`, - timestamp: n, - specReferences: [F.SEP_1046_CLIENT_CREDENTIALS], - details: { clientId: o } - }), - { - error: `invalid_client`, - errorDescription: `Invalid client credentials`, - statusCode: 401 - } - ); - this.checks.push({ - id: `client-credentials-basic-auth`, - name: `ClientCredentialsBasicAuth`, - description: `Client successfully authenticated with client_secret_basic`, - status: `SUCCESS`, - timestamp: n, - specReferences: [ - F.OAUTH_2_1_CLIENT_CREDENTIALS, - F.SEP_1046_CLIENT_CREDENTIALS - ], - details: { clientId: o } - }); - let c = t.scope ? t.scope.split(` `) : []; - return { token: `cc-token-${Date.now()}`, scopes: c }; - } - }); - await this.authServer.start(e); - let t = R(this.checks, this.server.getUrl, this.authServer.getUrl); - return ( - await this.server.start(t), - { - serverUrl: `${this.server.getUrl()}/mcp`, - context: { client_id: W, client_secret: rt } - } - ); - } - async stop() { - (await this.authServer.stop(), await this.server.stop()); - } - getChecks() { - return ( - this.checks.some((e) => e.id === `client-credentials-basic-auth`) || - this.checks.push({ - id: `client-credentials-basic-auth`, - name: `ClientCredentialsBasicAuth`, - description: `Client did not make a client_credentials token request`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - specReferences: [ - F.OAUTH_2_1_CLIENT_CREDENTIALS, - F.SEP_1046_CLIENT_CREDENTIALS - ] - }), - this.checks - ); - } - }; -const st = [ - ...Ve, - new Ue(), - new We(), - new Ge(), - new Ke(), - new qe(), - new Je(), - new Ye(), - new Xe(), - new et(), - new tt(), - new nt(), - new at(), - new ot() - ], - ct = [new N(), new j(), new O(), new M(), new P()], - lt = [ - new me(), - new he(), - new ge(), - new _e(), - new ve(), - new ye(), - new be(), - new Ee(), - new De(), - new xe(), - new Se(), - new Ce(), - new we(), - new Te(), - new O(), - new j(), - new M(), - new P(), - new ke(), - new N(), - new Ae(), - new je(), - new Me(), - new Ne(), - new Pe(), - new Fe(), - new Ie(), - new Le(), - new Re(), - new ze(), - new Be() - ], - ut = lt.filter((e) => !ct.some((t) => t.name === e.name)), - dt = new Map(lt.map((e) => [e.name, e])), - ft = [new oe(), new ce(), new de(), new fe(), ...st], - pt = new Map(ft.map((e) => [e.name, e])); -function G(e) { - return pt.get(e); -} -function mt(e) { - return dt.get(e); -} -function K() { - return Array.from(pt.keys()); -} -function q() { - return Array.from(dt.keys()); -} -function ht() { - return ut.map((e) => e.name); -} -function gt() { - return ct.map((e) => e.name); -} -function _t() { - return st.map((e) => e.name); -} -const J = { - RESET: `\x1B[0m`, - GRAY: `\x1B[90m`, - GREEN: `\x1B[32m`, - YELLOW: `\x1B[33m`, - RED: `\x1B[31m`, - BLUE: `\x1B[36m` -}; -function vt(e) { - switch (e) { - case `SUCCESS`: - return J.GREEN; - case `FAILURE`: - return J.RED; - case `WARNING`: - return J.YELLOW; - case `INFO`: - return J.BLUE; - default: - return J.RESET; - } -} -function Y(e) { - let t = Math.max(...e.map((e) => e.id.length)), - n = Math.max(...e.map((e) => e.status.length)); - return e.map( - (e) => - `${`${J.GRAY}${e.timestamp}${J.RESET}`} [${e.id.padEnd(t)}] ${`${vt(e.status)}${e.status.padEnd(n)}${J.RESET}`} ${e.description}` + - (e.id.includes(`outgoing`) && e.id.includes(`response`) - ? ` -` - : ``) - ).join(` -`); -} -async function X() { - let e = a.join(process.cwd(), `results`); - return (await i.mkdir(e, { recursive: !0 }), e); -} -function Z(e, t = ``) { - let n = new Date().toISOString().replace(/[:.]/g, `-`), - r = t ? `${t}-${e}` : e; - return a.join(`results`, `${r}-${n}`); -} -async function yt(e, t, n, i = 3e4, a) { - let o = e.split(` `), - s = o[0], - c = [...o.slice(1), n], - l = ``, - u = ``, - d = !1, - f = { ...process.env }; - return ( - (f.MCP_CONFORMANCE_SCENARIO = t), - a && (f.MCP_CONFORMANCE_CONTEXT = JSON.stringify({ name: t, ...a })), - new Promise((e) => { - let t = r(s, c, { shell: !0, stdio: `pipe`, env: f }), - n = setTimeout(() => { - ((d = !0), t.kill()); - }, i); - (t.stdout && - t.stdout.on(`data`, (e) => { - l += e.toString(); - }), - t.stderr && - t.stderr.on(`data`, (e) => { - u += e.toString(); - }), - t.on(`close`, (t) => { - (clearTimeout(n), - e({ exitCode: t || 0, stdout: l, stderr: u, timedOut: d })); - }), - t.on(`error`, (t) => { - (clearTimeout(n), - e({ - exitCode: -1, - stdout: l, - stderr: u + `\nProcess error: ${t.message}`, - timedOut: d - })); - })); - }) - ); -} -async function bt(e, t, n = 3e4) { - await X(); - let r = Z(t); - await i.mkdir(r, { recursive: !0 }); - let o = G(t); - console.error(`Starting scenario: ${t}`); - let s = await o.start(); - (console.error(`Executing client: ${e} ${s.serverUrl}`), - s.context && console.error(`With context: ${JSON.stringify(s.context)}`)); - try { - let c = await yt(e, t, s.serverUrl, n, s.context); - (c.exitCode !== 0 && - (console.error(`\nClient exited with code ${c.exitCode}`), - c.stdout && console.error(`\nStdout:\n${c.stdout}`), - c.stderr && console.error(`\nStderr:\n${c.stderr}`)), - c.timedOut && console.error(`\nClient timed out after ${n}ms`)); - let l = o.getChecks(); - return ( - await i.writeFile(a.join(r, `checks.json`), JSON.stringify(l, null, 2)), - await i.writeFile(a.join(r, `stdout.txt`), c.stdout), - await i.writeFile(a.join(r, `stderr.txt`), c.stderr), - console.error(`Results saved to ${r}`), - { checks: l, clientOutput: c, resultDir: r } - ); - } finally { - await o.stop(); - } -} -function xt(e, t = !1, n) { - let r = e.filter( - (e) => e.status === `SUCCESS` || e.status === `FAILURE` - ).length, - i = e.filter((e) => e.status === `SUCCESS`).length, - a = e.filter((e) => e.status === `FAILURE`).length, - o = e.filter((e) => e.status === `WARNING`).length, - s = n?.timedOut ?? !1, - c = n ? n.exitCode !== 0 : !1, - l = a > 0 || o > 0 || s || c; - return ( - t - ? console.log(JSON.stringify(e, null, 2)) - : console.error(`Checks:\n${Y(e)}`), - console.error(` -Test Results:`), - console.error(`Passed: ${i}/${r}, ${a} failed, ${o} warnings`), - s && - console.error(` -⚠️ CLIENT TIMED OUT - Test incomplete`), - c && - !s && - console.error( - `\n⚠️ CLIENT EXITED WITH ERROR (code ${n?.exitCode}) - Test may be incomplete` - ), - a > 0 && - (console.error(` -Failed Checks:`), - e - .filter((e) => e.status === `FAILURE`) - .forEach((e) => { - (console.error(` - ${e.name}: ${e.description}`), - e.errorMessage && console.error(` Error: ${e.errorMessage}`)); - })), - o > 0 && - (console.error(` -Warning Checks:`), - e - .filter((e) => e.status === `WARNING`) - .forEach((e) => { - (console.error(` - ${e.name}: ${e.description}`), - e.errorMessage && console.error(` Warning: ${e.errorMessage}`)); - })), - l - ? console.error(` -❌ OVERALL: FAILED`) - : console.error(` -✅ OVERALL: PASSED`), - { passed: i, failed: a, denominator: r, warnings: o, overallFailure: l } - ); -} -async function St(e, t = !1) { - await X(); - let n = Z(e); - await i.mkdir(n, { recursive: !0 }); - let r = G(e); - console.log(`Starting scenario: ${e}`); - let o = await r.start(); - (console.log(`Server URL: ${o.serverUrl}`), - console.log(`Press Ctrl+C to stop and save checks...`)); - let s = async () => { - console.log(` -Shutting down...`); - let e = r.getChecks(); - (await i.writeFile(a.join(n, `checks.json`), JSON.stringify(e, null, 2)), - t - ? console.log(`\nChecks:\n${JSON.stringify(e, null, 2)}`) - : console.log(`\nChecks:\n${Y(e)}`), - console.log(`\nChecks saved to ${n}/checks.json`), - await r.stop(), - process.exit(0)); - }; - (process.on(`SIGINT`, s), - process.on(`SIGTERM`, s), - await new Promise(() => {})); -} -function Ct(e) { - return e - .replace(/\*\*([^*]+)\*\*/g, `\x1B[1m$1\x1B[0m`) - .replace(/`([^`]+)`/g, `\x1B[2m$1\x1B[0m`); -} -async function Q(e, t) { - await X(); - let n = Z(t, `server`); - await i.mkdir(n, { recursive: !0 }); - let r = mt(t); - console.log(`Running client scenario '${t}' against server: ${e}`); - let o = await r.run(e); - return ( - await i.writeFile(a.join(n, `checks.json`), JSON.stringify(o, null, 2)), - console.log(`Results saved to ${n}`), - { checks: o, resultDir: n, scenarioDescription: r.description } - ); -} -function wt(e, t, n = !1) { - let r = e.filter( - (e) => e.status === `SUCCESS` || e.status === `FAILURE` - ).length, - i = e.filter((e) => e.status === `SUCCESS`).length, - a = e.filter((e) => e.status === `FAILURE`).length, - o = e.filter((e) => e.status === `WARNING`).length; - return ( - n - ? console.log(JSON.stringify(e, null, 2)) - : console.log(`Checks:\n${Y(e)}`), - console.log(` -Test Results:`), - console.log(`Passed: ${i}/${r}, ${a} failed, ${o} warnings`), - a > 0 && - (console.log(` -=== Failed Checks ===`), - e - .filter((e) => e.status === `FAILURE`) - .forEach((e) => { - (console.log(`\n - ${e.name}: ${e.description}`), - e.errorMessage && console.log(` Error: ${e.errorMessage}`), - console.log(`\n${Ct(t)}`)); - })), - { passed: i, failed: a, denominator: r, warnings: o } - ); -} -function Tt(e) { - console.log(` - -=== SUMMARY ===`); - let t = 0, - n = 0; - for (let r of e) { - let e = r.checks.filter((e) => e.status === `SUCCESS`).length, - i = r.checks.filter((e) => e.status === `FAILURE`).length; - ((t += e), (n += i)); - let a = i === 0 ? `✓` : `✗`; - console.log(`${a} ${r.scenario}: ${e} passed, ${i} failed`); - } - return ( - console.log(`\nTotal: ${t} passed, ${n} failed`), - { totalPassed: t, totalFailed: n } - ); -} -const Et = n.object({ - command: n.string().min(1, `Command cannot be empty`).optional(), - scenario: n - .string() - .min(1, `Scenario cannot be empty`) - .refine( - (e) => G(e) !== void 0, - (e) => ({ message: `Unknown scenario '${e}'` }) - ), - timeout: n - .string() - .transform((e) => parseInt(e, 10)) - .pipe( - n - .number() - .positive(`Timeout must be a positive number`) - .int(`Timeout must be an integer`) - ) - .optional(), - verbose: n.boolean().optional() - }), - Dt = n.object({ - url: n.string().url(`Invalid server URL`), - scenario: n - .string() - .refine( - (e) => mt(e) !== void 0, - (e) => ({ message: `Unknown scenario '${e}'` }) - ) - .optional() - }); -n.object({ - scenario: n - .string() - .min(1, `Scenario cannot be empty`) - .refine( - (e) => G(e) !== void 0, - (e) => ({ message: `Unknown scenario '${e}'` }) - ) -}); -var Ot = `0.1.8`; -const $ = new e(); -($.name(`conformance`).description(`MCP Conformance Test Suite`).version(Ot), - $.command(`client`) - .description( - `Run conformance tests against a client implementation or start interactive mode` - ) - .option(`--command `, `Command to run the client`) - .option(`--scenario `, `Scenario to test`) - .option( - `--suite `, - `Run a suite of tests in parallel (e.g., "auth")` - ) - .option(`--timeout `, `Timeout in milliseconds`, `30000`) - .option(`--verbose`, `Show verbose output`) - .action(async (e) => { - try { - let t = parseInt(e.timeout, 10), - n = e.verbose ?? !1; - if (e.suite) { - e.command || - (console.error(`--command is required when using --suite`), - process.exit(1)); - let r = { - auth: _t, - metadata: He, - 'sep-835': () => _t().filter((e) => e.startsWith(`auth/scope-`)) - }, - i = e.suite.toLowerCase(); - r[i] || - (console.error(`Unknown suite: ${i}`), - console.error(`Available suites: ${Object.keys(r).join(`, `)}`), - process.exit(1)); - let a = r[i](); - console.log( - `Running ${i} suite (${a.length} scenarios) in parallel...\n` - ); - let o = await Promise.all( - a.map(async (n) => { - try { - return { - scenario: n, - checks: (await bt(e.command, n, t)).checks, - error: null - }; - } catch (e) { - return { - scenario: n, - checks: [ - { - id: n, - name: n, - description: `Failed to run scenario`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: e instanceof Error ? e.message : String(e) - } - ], - error: e - }; - } - }) - ); - console.log(` -=== SUITE SUMMARY === -`); - let s = 0, - c = 0, - l = 0; - for (let e of o) { - let t = e.checks.filter((e) => e.status === `SUCCESS`).length, - r = e.checks.filter((e) => e.status === `FAILURE`).length, - i = e.checks.filter((e) => e.status === `WARNING`).length; - ((s += t), (c += r), (l += i)); - let a = r === 0 && i === 0 ? `✓` : `✗`, - o = i > 0 ? `, ${i} warnings` : ``; - (console.log(`${a} ${e.scenario}: ${t} passed, ${r} failed${o}`), - n && - r > 0 && - e.checks - .filter((e) => e.status === `FAILURE`) - .forEach((e) => { - console.log( - ` - ${e.name}: ${e.errorMessage || e.description}` - ); - })); - } - (console.log(`\nTotal: ${s} passed, ${c} failed, ${l} warnings`), - process.exit(c > 0 || l > 0 ? 1 : 0)); - } - e.scenario || - (console.error(`Either --scenario or --suite is required`), - console.error(` -Available client scenarios:`), - K().forEach((e) => console.error(` - ${e}`)), - console.error(` -Available suites: auth, metadata, sep-835`), - process.exit(1)); - let r = Et.parse(e); - r.command || (await St(r.scenario, n), process.exit(0)); - let i = await bt(r.command, r.scenario, t), - { overallFailure: a } = xt(i.checks, n, i.clientOutput); - process.exit(a ? 1 : 0); - } catch (e) { - (e instanceof t && - (console.error(`Validation error:`), - e.errors.forEach((e) => { - console.error(` ${e.path.join(`.`)}: ${e.message}`); - }), - console.error(` -Available client scenarios:`), - K().forEach((e) => console.error(` - ${e}`)), - process.exit(1)), - console.error(`Client test error:`, e), - process.exit(1)); - } - }), - $.command(`server`) - .description(`Run conformance tests against a server implementation`) - .requiredOption(`--url `, `URL of the server to test`) - .option( - `--scenario `, - `Scenario to test (defaults to active suite if not specified)` - ) - .option( - `--suite `, - `Suite to run: "active" (default, excludes pending), "all", or "pending"`, - `active` - ) - .option(`--verbose`, `Show verbose output (JSON instead of pretty print)`) - .action(async (e) => { - try { - let t = Dt.parse(e), - n = e.verbose ?? !1; - if (t.scenario) { - let e = await Q(t.url, t.scenario), - { failed: r } = wt(e.checks, e.scenarioDescription, n); - process.exit(r > 0 ? 1 : 0); - } else { - let n = e.suite?.toLowerCase() || `active`, - r; - (n === `all` - ? (r = q()) - : n === `active` - ? (r = ht()) - : n === `pending` - ? (r = gt()) - : (console.error(`Unknown suite: ${n}`), - console.error(`Available suites: active, all, pending`), - process.exit(1)), - console.log( - `Running ${n} suite (${r.length} scenarios) against ${t.url}\n` - )); - let i = []; - for (let e of r) { - console.log(`\n=== Running scenario: ${e} ===`); - try { - let n = await Q(t.url, e); - i.push({ scenario: e, checks: n.checks }); - } catch (t) { - (console.error(`Failed to run scenario ${e}:`, t), - i.push({ - scenario: e, - checks: [ - { - id: e, - name: e, - description: `Failed to run scenario`, - status: `FAILURE`, - timestamp: new Date().toISOString(), - errorMessage: t instanceof Error ? t.message : String(t) - } - ] - })); - } - } - let { totalFailed: a } = Tt(i); - process.exit(a > 0 ? 1 : 0); - } - } catch (e) { - (e instanceof t && - (console.error(`Validation error:`), - e.errors.forEach((e) => { - console.error(` ${e.path.join(`.`)}: ${e.message}`); - }), - console.error(` -Available server scenarios:`), - q().forEach((e) => console.error(` - ${e}`)), - process.exit(1)), - console.error(`Server test error:`, e), - process.exit(1)); - } - }), - $.command(`list`) - .description(`List available test scenarios`) - .option(`--client`, `List client scenarios`) - .option(`--server`, `List server scenarios`) - .action((e) => { - ((e.server || (!e.client && !e.server)) && - (console.log(`Server scenarios (test against a server):`), - q().forEach((e) => console.log(` - ${e}`))), - (e.client || (!e.client && !e.server)) && - ((e.server || (!e.client && !e.server)) && console.log(``), - console.log(`Client scenarios (test against a client):`), - K().forEach((e) => console.log(` - ${e}`)))); - }), - $.parse()); -export {}; From 20b98a169282973459f1683ce028e38bfea15700 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 16:52:46 +0000 Subject: [PATCH 6/9] Fix .gitignore formatting --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8255b55..5c745fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ node_modules results/ lefthook-local.yml -dist.vscode/ -.vscode/ -.vscode/ dist/ +.vscode/ From 98d6592bb7faf0cf5e5c22c763a2c7b0d9752e38 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 16:55:17 +0000 Subject: [PATCH 7/9] Add getHandler to everything-client for cleaner test imports Instead of importing individual handlers, tests can now import getHandler and look up handlers by scenario name. --- examples/clients/typescript/everything-client.ts | 8 ++++++++ src/scenarios/client/auth/index.test.ts | 16 +++------------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/examples/clients/typescript/everything-client.ts b/examples/clients/typescript/everything-client.ts index ac5ea5b..edb64cc 100644 --- a/examples/clients/typescript/everything-client.ts +++ b/examples/clients/typescript/everything-client.ts @@ -42,6 +42,14 @@ function registerScenarios(names: string[], handler: ScenarioHandler): void { } } +/** + * Get a scenario handler by name. + * Returns undefined if no handler is registered for the scenario. + */ +export function getHandler(scenarioName: string): ScenarioHandler | undefined { + return scenarioHandlers[scenarioName]; +} + // ============================================================================ // Basic scenarios (initialize, tools-call) // ============================================================================ diff --git a/src/scenarios/client/auth/index.test.ts b/src/scenarios/client/auth/index.test.ts index cd920b2..13d7fa4 100644 --- a/src/scenarios/client/auth/index.test.ts +++ b/src/scenarios/client/auth/index.test.ts @@ -10,10 +10,7 @@ import { runClient as ignoreScopeClient } from '../../../../examples/clients/typ import { runClient as partialScopesClient } from '../../../../examples/clients/typescript/auth-test-partial-scopes'; import { runClient as ignore403Client } from '../../../../examples/clients/typescript/auth-test-ignore-403'; import { runClient as noRetryLimitClient } from '../../../../examples/clients/typescript/auth-test-no-retry-limit'; -import { - runClientCredentialsJwt, - runClientCredentialsBasic -} from '../../../../examples/clients/typescript/everything-client'; +import { getHandler } from '../../../../examples/clients/typescript/everything-client'; import { setLogLevel } from '../../../../examples/clients/typescript/helpers/logger'; beforeAll(() => { @@ -29,13 +26,6 @@ const allowClientErrorScenarios = new Set([ 'auth/scope-retry-limit' ]); -// Map of scenario names to their specific client handlers -const scenarioClientMap: Record Promise> = - { - 'auth/client-credentials-jwt': runClientCredentialsJwt, - 'auth/client-credentials-basic': runClientCredentialsBasic - }; - describe('Client Auth Scenarios', () => { // Generate individual test for each auth scenario for (const scenario of authScenariosList) { @@ -44,8 +34,8 @@ describe('Client Auth Scenarios', () => { // TODO: skip in a native way? return; } - // Use scenario-specific client if available, otherwise use goodClient - const clientFn = scenarioClientMap[scenario.name] ?? goodClient; + // Use everything-client handler if available, otherwise use goodClient + const clientFn = getHandler(scenario.name) ?? goodClient; const runner = new InlineClientRunner(clientFn); await runClientAgainstScenario(runner, scenario.name, { allowClientError: allowClientErrorScenarios.has(scenario.name) From de21ffa7a78e8ec1aac159352dc8a510b889e744 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 17:00:27 +0000 Subject: [PATCH 8/9] Use getHandler exclusively, remove goodClient import - Add CIMD support to runAuthClient in everything-client - Add auth fallback to getHandler for unregistered auth/* scenarios - Remove goodClient import from test file --- .../clients/typescript/everything-client.ts | 34 ++++++++++++++++--- src/scenarios/client/auth/index.test.ts | 7 ++-- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/examples/clients/typescript/everything-client.ts b/examples/clients/typescript/everything-client.ts index edb64cc..6131ab0 100644 --- a/examples/clients/typescript/everything-client.ts +++ b/examples/clients/typescript/everything-client.ts @@ -21,9 +21,17 @@ import { } from '@modelcontextprotocol/sdk/client/auth-extensions.js'; import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { ClientConformanceContextSchema } from '../../../src/schemas/context.js'; -import { withOAuthRetry } from './helpers/withOAuthRetry.js'; +import { withOAuthRetry, handle401 } from './helpers/withOAuthRetry.js'; import { logger } from './helpers/logger.js'; +/** + * Fixed client metadata URL for CIMD conformance tests. + * When server supports client_id_metadata_document_supported, this URL + * will be used as the client_id instead of doing dynamic registration. + */ +const CIMD_CLIENT_METADATA_URL = + 'https://conformance-test.local/client-metadata.json'; + // Scenario handler type type ScenarioHandler = (serverUrl: string) => Promise; @@ -44,10 +52,20 @@ function registerScenarios(names: string[], handler: ScenarioHandler): void { /** * Get a scenario handler by name. - * Returns undefined if no handler is registered for the scenario. + * For auth/* scenarios, falls back to the standard OAuth auth client if no + * specific handler is registered. + * Returns undefined only if no handler matches. */ export function getHandler(scenarioName: string): ScenarioHandler | undefined { - return scenarioHandlers[scenarioName]; + const handler = scenarioHandlers[scenarioName]; + if (handler) return handler; + + // Fall back to auth client for unregistered auth/* scenarios + if (scenarioName.startsWith('auth/')) { + return runAuthClient; + } + + return undefined; } // ============================================================================ @@ -86,7 +104,9 @@ async function runAuthClient(serverUrl: string): Promise { const oauthFetch = withOAuthRetry( 'test-auth-client', - new URL(serverUrl) + new URL(serverUrl), + handle401, + CIMD_CLIENT_METADATA_URL )(fetch); const transport = new StreamableHTTPClientTransport(new URL(serverUrl), { @@ -107,8 +127,11 @@ async function runAuthClient(serverUrl: string): Promise { } // Register all auth scenarios that should use the well-behaved auth client +// Note: getHandler() also falls back to runAuthClient for any auth/* scenario, +// so this explicit registration is mainly for documentation purposes registerScenarios( [ + 'auth/basic-cimd', 'auth/basic-dcr', 'auth/basic-metadata-var1', 'auth/basic-metadata-var2', @@ -118,7 +141,8 @@ registerScenarios( 'auth/scope-from-www-authenticate', 'auth/scope-from-scopes-supported', 'auth/scope-omitted-when-undefined', - 'auth/scope-step-up' + 'auth/scope-step-up', + 'auth/scope-retry-limit' ], runAuthClient ); diff --git a/src/scenarios/client/auth/index.test.ts b/src/scenarios/client/auth/index.test.ts index 13d7fa4..6dcc020 100644 --- a/src/scenarios/client/auth/index.test.ts +++ b/src/scenarios/client/auth/index.test.ts @@ -3,7 +3,6 @@ import { runClientAgainstScenario, InlineClientRunner } from './test_helpers/testClient'; -import { runClient as goodClient } from '../../../../examples/clients/typescript/auth-test'; import { runClient as badPrmClient } from '../../../../examples/clients/typescript/auth-test-bad-prm'; import { runClient as noCimdClient } from '../../../../examples/clients/typescript/auth-test-no-cimd'; import { runClient as ignoreScopeClient } from '../../../../examples/clients/typescript/auth-test-ignore-scope'; @@ -34,8 +33,10 @@ describe('Client Auth Scenarios', () => { // TODO: skip in a native way? return; } - // Use everything-client handler if available, otherwise use goodClient - const clientFn = getHandler(scenario.name) ?? goodClient; + const clientFn = getHandler(scenario.name); + if (!clientFn) { + throw new Error(`No handler registered for scenario: ${scenario.name}`); + } const runner = new InlineClientRunner(clientFn); await runClientAgainstScenario(runner, scenario.name, { allowClientError: allowClientErrorScenarios.has(scenario.name) From 788db5812d7473cc82add242e2172655a155a7e6 Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Mon, 5 Jan 2026 17:28:34 +0000 Subject: [PATCH 9/9] Remove runAuthClient fallback, register all auth scenarios explicitly --- .../clients/typescript/everything-client.ts | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/examples/clients/typescript/everything-client.ts b/examples/clients/typescript/everything-client.ts index 6131ab0..4491fe6 100644 --- a/examples/clients/typescript/everything-client.ts +++ b/examples/clients/typescript/everything-client.ts @@ -52,20 +52,10 @@ function registerScenarios(names: string[], handler: ScenarioHandler): void { /** * Get a scenario handler by name. - * For auth/* scenarios, falls back to the standard OAuth auth client if no - * specific handler is registered. - * Returns undefined only if no handler matches. + * Returns undefined if no handler is registered for the scenario. */ export function getHandler(scenarioName: string): ScenarioHandler | undefined { - const handler = scenarioHandlers[scenarioName]; - if (handler) return handler; - - // Fall back to auth client for unregistered auth/* scenarios - if (scenarioName.startsWith('auth/')) { - return runAuthClient; - } - - return undefined; + return scenarioHandlers[scenarioName]; } // ============================================================================ @@ -126,23 +116,30 @@ async function runAuthClient(serverUrl: string): Promise { logger.debug('Connection closed successfully'); } -// Register all auth scenarios that should use the well-behaved auth client -// Note: getHandler() also falls back to runAuthClient for any auth/* scenario, -// so this explicit registration is mainly for documentation purposes +// Register all auth scenarios that use the well-behaved OAuth auth client registerScenarios( [ + // Basic auth scenarios 'auth/basic-cimd', 'auth/basic-dcr', - 'auth/basic-metadata-var1', - 'auth/basic-metadata-var2', - 'auth/basic-metadata-var3', + // Metadata discovery scenarios + 'auth/metadata-default', + 'auth/metadata-var1', + 'auth/metadata-var2', + 'auth/metadata-var3', + // Backcompat scenarios 'auth/2025-03-26-oauth-metadata-backcompat', 'auth/2025-03-26-oauth-endpoint-fallback', + // Scope handling scenarios 'auth/scope-from-www-authenticate', 'auth/scope-from-scopes-supported', 'auth/scope-omitted-when-undefined', 'auth/scope-step-up', - 'auth/scope-retry-limit' + 'auth/scope-retry-limit', + // Token endpoint auth method scenarios + 'auth/token-endpoint-auth-basic', + 'auth/token-endpoint-auth-post', + 'auth/token-endpoint-auth-none' ], runAuthClient );