diff --git a/package.json b/package.json index 410f80cf..ff9a9b35 100644 --- a/package.json +++ b/package.json @@ -33,12 +33,12 @@ "lint:fix:all": "pnpm -r lint:fix", "check:all": "pnpm -r typecheck && pnpm -r lint", "test:all": "pnpm -r test", - "test:conformance:client": "conformance client --command 'npx tsx src/conformance/everything-client.ts'", - "test:conformance:client:all": "conformance client --command 'npx tsx src/conformance/everything-client.ts' --suite all", - "test:conformance:client:run": "npx tsx src/conformance/everything-client.ts", - "test:conformance:server": "scripts/run-server-conformance.sh", - "test:conformance:server:all": "scripts/run-server-conformance.sh --suite all", - "test:conformance:server:run": "npx tsx src/conformance/everything-server.ts", + "test:conformance:client": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:client", + "test:conformance:client:all": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:client:all", + "test:conformance:client:run": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:client:run", + "test:conformance:server": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server", + "test:conformance:server:all": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:all", + "test:conformance:server:run": "pnpm --filter @modelcontextprotocol/test-conformance run test:conformance:server:run", "test:conformance:all": "pnpm run test:conformance:client:all && pnpm run test:conformance:server:all" }, "devDependencies": { @@ -47,7 +47,6 @@ "@changesets/cli": "^2.29.8", "@eslint/js": "catalog:devTools", "@modelcontextprotocol/client": "workspace:^", - "@modelcontextprotocol/conformance": "0.1.10", "@modelcontextprotocol/server": "workspace:^", "@modelcontextprotocol/node": "workspace:^", "@types/content-type": "catalog:devTools", @@ -60,12 +59,10 @@ "@types/supertest": "catalog:devTools", "@types/ws": "catalog:devTools", "@typescript/native-preview": "catalog:devTools", - "cors": "catalog:runtimeServerOnly", "eslint": "catalog:devTools", "eslint-config-prettier": "catalog:devTools", "eslint-plugin-n": "catalog:devTools", "fast-glob": "^3.3.3", - "express": "catalog:runtimeServerOnly", "prettier": "catalog:devTools", "supertest": "catalog:devTools", "tsdown": "catalog:devTools", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf05bc33..583bbdbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,9 +146,6 @@ importers: '@modelcontextprotocol/client': specifier: workspace:^ version: link:packages/client - '@modelcontextprotocol/conformance': - specifier: 0.1.10 - version: 0.1.10(@cfworker/json-schema@4.1.1)(hono@4.11.4) '@modelcontextprotocol/node': specifier: workspace:^ version: link:packages/middleware/node @@ -185,9 +182,6 @@ importers: '@typescript/native-preview': specifier: catalog:devTools version: 7.0.0-dev.20260105.1 - cors: - specifier: catalog:runtimeServerOnly - version: 2.8.5 eslint: specifier: catalog:devTools version: 9.39.2 @@ -197,9 +191,6 @@ importers: eslint-plugin-n: specifier: catalog:devTools version: 17.23.1(eslint@9.39.2)(typescript@5.9.3) - express: - specifier: catalog:runtimeServerOnly - version: 5.2.1 fast-glob: specifier: ^3.3.3 version: 3.3.3 @@ -845,6 +836,48 @@ importers: specifier: catalog:devTools version: 4.0.16(@types/node@24.10.4)(tsx@4.21.0)(yaml@2.8.2) + test/conformance: + devDependencies: + '@modelcontextprotocol/client': + specifier: workspace:^ + version: link:../../packages/client + '@modelcontextprotocol/conformance': + specifier: 0.1.10 + version: 0.1.10(@cfworker/json-schema@4.1.1)(hono@4.11.4) + '@modelcontextprotocol/core': + specifier: workspace:^ + version: link:../../packages/core + '@modelcontextprotocol/eslint-config': + specifier: workspace:^ + version: link:../../common/eslint-config + '@modelcontextprotocol/express': + specifier: workspace:^ + version: link:../../packages/middleware/express + '@modelcontextprotocol/node': + specifier: workspace:^ + version: link:../../packages/middleware/node + '@modelcontextprotocol/server': + specifier: workspace:^ + version: link:../../packages/server + '@modelcontextprotocol/test-helpers': + specifier: workspace:^ + version: link:../helpers + '@modelcontextprotocol/tsconfig': + specifier: workspace:^ + version: link:../../common/tsconfig + '@modelcontextprotocol/vitest-config': + specifier: workspace:^ + version: link:../../common/vitest-config + cors: + specifier: catalog:runtimeServerOnly + version: 2.8.5 + express: + specifier: catalog:runtimeServerOnly + version: 5.2.1 + zod: + specifier: catalog:runtimeShared + version: 4.3.5 + test/helpers: devDependencies: '@modelcontextprotocol/core': diff --git a/src/conformance/README.md b/test/conformance/README.md similarity index 100% rename from src/conformance/README.md rename to test/conformance/README.md diff --git a/test/conformance/eslint.config.mjs b/test/conformance/eslint.config.mjs new file mode 100644 index 00000000..951c9f3a --- /dev/null +++ b/test/conformance/eslint.config.mjs @@ -0,0 +1,5 @@ +// @ts-check + +import baseConfig from '@modelcontextprotocol/eslint-config'; + +export default baseConfig; diff --git a/test/conformance/package.json b/test/conformance/package.json new file mode 100644 index 00000000..253afd9a --- /dev/null +++ b/test/conformance/package.json @@ -0,0 +1,54 @@ +{ + "name": "@modelcontextprotocol/test-conformance", + "private": true, + "version": "2.0.0-alpha.0", + "description": "Model Context Protocol implementation for TypeScript", + "license": "MIT", + "author": "Anthropic, PBC (https://anthropic.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/typescript-sdk/issues", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/modelcontextprotocol/typescript-sdk.git" + }, + "engines": { + "node": ">=20", + "pnpm": ">=10.24.0" + }, + "packageManager": "pnpm@10.24.0", + "keywords": [ + "modelcontextprotocol", + "mcp" + ], + "scripts": { + "lint": "eslint src/ && prettier --ignore-path ../../.prettierignore --check .", + "lint:fix": "eslint src/ --fix && prettier --ignore-path ../../.prettierignore --write .", + "check": "npm run typecheck && npm run lint", + "start": "npm run server", + "server": "tsx watch --clear-screen=false scripts/cli.ts server", + "client": "tsx scripts/cli.ts client", + "test:conformance:client": "conformance client --command 'npx tsx ./src/everythingClient.ts'", + "test:conformance:client:all": "conformance client --command 'npx tsx ./src/everythingClient.ts' --suite all", + "test:conformance:client:run": "npx tsx ./src/everythingClient.ts", + "test:conformance:server": "scripts/run-server-conformance.sh", + "test:conformance:server:all": "scripts/run-server-conformance.sh --suite all", + "test:conformance:server:run": "npx tsx ./src/everythingServer.ts", + "test:conformance:all": "pnpm run test:conformance:client:all && pnpm run test:conformance:server:all" + }, + "devDependencies": { + "@modelcontextprotocol/conformance": "0.1.10", + "@modelcontextprotocol/client": "workspace:^", + "@modelcontextprotocol/server": "workspace:^", + "@modelcontextprotocol/core": "workspace:^", + "@modelcontextprotocol/express": "workspace:^", + "@modelcontextprotocol/node": "workspace:^", + "@modelcontextprotocol/tsconfig": "workspace:^", + "@modelcontextprotocol/vitest-config": "workspace:^", + "@modelcontextprotocol/eslint-config": "workspace:^", + "@modelcontextprotocol/test-helpers": "workspace:^", + "cors": "catalog:runtimeServerOnly", + "express": "catalog:runtimeServerOnly", + "zod": "catalog:runtimeShared" + } +} diff --git a/scripts/run-server-conformance.sh b/test/conformance/scripts/run-server-conformance.sh similarity index 96% rename from scripts/run-server-conformance.sh rename to test/conformance/scripts/run-server-conformance.sh index 4de4a1ae..203a0145 100755 --- a/scripts/run-server-conformance.sh +++ b/test/conformance/scripts/run-server-conformance.sh @@ -13,7 +13,7 @@ cd "$SCRIPT_DIR/.." # Start the server in the background echo "Starting conformance test server on port ${PORT}..." -npx tsx src/conformance/everything-server.ts & +npx tsx ./src/everythingServer.ts & SERVER_PID=$! # Function to cleanup on exit diff --git a/src/conformance/everything-client.ts b/test/conformance/src/everythingClient.ts similarity index 96% rename from src/conformance/everything-client.ts rename to test/conformance/src/everythingClient.ts index 19ba5d64..be08a1c7 100644 --- a/src/conformance/everything-client.ts +++ b/test/conformance/src/everythingClient.ts @@ -12,10 +12,11 @@ * consolidating all the individual test clients into one. */ -import { Client, StreamableHTTPClientTransport, ClientCredentialsProvider, PrivateKeyJwtProvider } from '@modelcontextprotocol/client'; +import { Client, ClientCredentialsProvider, PrivateKeyJwtProvider, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; import { z } from 'zod'; -import { withOAuthRetry, handle401 } from './helpers/withOAuthRetry.js'; + import { logger } from './helpers/logger.js'; +import { handle401, withOAuthRetry } from './helpers/withOAuthRetry.js'; /** * Fixed client metadata URL for CIMD conformance tests. @@ -353,7 +354,7 @@ async function main(): Promise { logger.error('Usage: MCP_CONFORMANCE_SCENARIO= everything-client '); logger.error('\nThe MCP_CONFORMANCE_SCENARIO env var is set automatically by the conformance runner.'); logger.error('\nAvailable scenarios:'); - for (const name of Object.keys(scenarioHandlers).sort()) { + for (const name of Object.keys(scenarioHandlers).toSorted()) { logger.error(` - ${name}`); } process.exit(1); @@ -363,7 +364,7 @@ async function main(): Promise { if (!handler) { logger.error(`Unknown scenario: ${scenarioName}`); logger.error('\nAvailable scenarios:'); - for (const name of Object.keys(scenarioHandlers).sort()) { + for (const name of Object.keys(scenarioHandlers).toSorted()) { logger.error(` - ${name}`); } process.exit(1); @@ -378,7 +379,9 @@ async function main(): Promise { } } -main().catch(error => { - logger.error('Unhandled error:', error); +try { + await main(); +} catch (error) { + logger.error('Error:', error); process.exit(1); -}); +} diff --git a/src/conformance/everything-server.ts b/test/conformance/src/everythingServer.ts similarity index 99% rename from src/conformance/everything-server.ts rename to test/conformance/src/everythingServer.ts index 926c6a1b..05c83237 100644 --- a/src/conformance/everything-server.ts +++ b/test/conformance/src/everythingServer.ts @@ -9,11 +9,11 @@ import { randomUUID } from 'node:crypto'; -import type { CallToolResult, GetPromptResult, ReadResourceResult, EventId, EventStore, StreamId } from '@modelcontextprotocol/server'; -import { ElicitResultSchema, isInitializeRequest, McpServer, ResourceTemplate } from '@modelcontextprotocol/server'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; -import type { Request, Response } from 'express'; +import type { CallToolResult, EventId, EventStore, GetPromptResult, ReadResourceResult, StreamId } from '@modelcontextprotocol/server'; +import { ElicitResultSchema, isInitializeRequest, McpServer, ResourceTemplate } from '@modelcontextprotocol/server'; import cors from 'cors'; +import type { Request, Response } from 'express'; import express from 'express'; import * as z from 'zod/v4'; @@ -64,7 +64,7 @@ const TEST_IMAGE_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQ const TEST_AUDIO_BASE64 = 'UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAA='; // Function to create a new MCP server instance (one per session) -function createMcpServer(sessionId?: string) { +function createMcpServer() { const mcpServer = new McpServer( { name: 'mcp-conformance-test-server', @@ -941,7 +941,7 @@ app.post('/mcp', async (req: Request, res: Response) => { res.status(400).json({ jsonrpc: '2.0', error: { - code: -32000, + code: -32_000, message: 'Invalid or missing session ID' }, id: null @@ -956,7 +956,7 @@ app.post('/mcp', async (req: Request, res: Response) => { res.status(500).json({ jsonrpc: '2.0', error: { - code: -32603, + code: -32_603, message: 'Internal server error' }, id: null diff --git a/src/conformance/helpers/ConformanceOAuthProvider.ts b/test/conformance/src/helpers/conformanceOAuthProvider.ts similarity index 100% rename from src/conformance/helpers/ConformanceOAuthProvider.ts rename to test/conformance/src/helpers/conformanceOAuthProvider.ts index 3ae21e8b..1643afab 100644 --- a/src/conformance/helpers/ConformanceOAuthProvider.ts +++ b/test/conformance/src/helpers/conformanceOAuthProvider.ts @@ -1,8 +1,8 @@ import type { - OAuthClientProvider, OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, + OAuthClientProvider, OAuthTokens } from '@modelcontextprotocol/client'; diff --git a/src/conformance/helpers/logger.ts b/test/conformance/src/helpers/logger.ts similarity index 100% rename from src/conformance/helpers/logger.ts rename to test/conformance/src/helpers/logger.ts diff --git a/src/conformance/helpers/withOAuthRetry.ts b/test/conformance/src/helpers/withOAuthRetry.ts similarity index 98% rename from src/conformance/helpers/withOAuthRetry.ts rename to test/conformance/src/helpers/withOAuthRetry.ts index eca89f44..187b9374 100644 --- a/src/conformance/helpers/withOAuthRetry.ts +++ b/test/conformance/src/helpers/withOAuthRetry.ts @@ -1,6 +1,7 @@ -import { auth, extractWWWAuthenticateParams, UnauthorizedError } from '@modelcontextprotocol/client'; import type { FetchLike, Middleware } from '@modelcontextprotocol/client'; -import { ConformanceOAuthProvider } from './ConformanceOAuthProvider.js'; +import { auth, extractWWWAuthenticateParams, UnauthorizedError } from '@modelcontextprotocol/client'; + +import { ConformanceOAuthProvider } from './conformanceOAuthProvider.js'; export const handle401 = async ( response: Response, diff --git a/test/conformance/tsconfig.json b/test/conformance/tsconfig.json new file mode 100644 index 00000000..5c22ec1b --- /dev/null +++ b/test/conformance/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "@modelcontextprotocol/tsconfig", + "include": ["./"], + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "paths": { + "*": ["./*"], + "@modelcontextprotocol/core": ["./node_modules/@modelcontextprotocol/core/src/index.ts"], + "@modelcontextprotocol/client": ["./node_modules/@modelcontextprotocol/client/src/index.ts"], + "@modelcontextprotocol/server": ["./node_modules/@modelcontextprotocol/server/src/index.ts"], + "@modelcontextprotocol/express": ["./node_modules/@modelcontextprotocol/express/src/index.ts"], + "@modelcontextprotocol/node": ["./node_modules/@modelcontextprotocol/node/src/index.ts"], + "@modelcontextprotocol/vitest-config": ["./node_modules/@modelcontextprotocol/vitest-config/tsconfig.json"], + "@modelcontextprotocol/test-helpers": ["./node_modules/@modelcontextprotocol/test-helpers/src/index.ts"] + } + } +} diff --git a/test/conformance/vitest.config.js b/test/conformance/vitest.config.js new file mode 100644 index 00000000..38f030fd --- /dev/null +++ b/test/conformance/vitest.config.js @@ -0,0 +1,3 @@ +import baseConfig from '../../common/vitest-config/vitest.config.js'; + +export default baseConfig;