'use strict'; const assert = require('node:assert/strict'); const http = require('node:http'); const test = require('node:test'); const { MCP_PROTOCOL_VERSION, McpServer, SUPPORTED_PROTOCOL_VERSIONS, } = require('../lib/server'); function createServer(toolRegistry = {}, config = {}) { return new McpServer({ config: { host: '127.0.0.1', port: 8765, ...config }, toolRegistry: { listTools: () => [], callTool: async () => 'ok', ...toolRegistry, }, resourceProvider: { listResources: () => [], listResourceTemplates: () => [], readResource: async () => ({ contents: [] }), }, promptProvider: { listPrompts: () => [], getPrompt: () => ({ messages: [] }), }, interactionLog: { add() {} }, runtimeLog: { add() {} }, serverName: 'test-server', serverVersion: '0.0.0-test', }); } function httpJson(port, payload, headers = {}) { const body = payload === undefined ? '' : JSON.stringify(payload); return new Promise((resolve, reject) => { const request = http.request( { host: '127.0.0.1', port, method: 'POST', path: '/', headers: { Accept: 'application/json, text/event-stream', 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), ...headers, }, }, (response) => { const chunks = []; response.on('data', (chunk) => chunks.push(chunk)); response.on('end', () => { resolve({ statusCode: response.statusCode, headers: response.headers, body: Buffer.concat(chunks).toString('utf8'), }); }); } ); request.on('error', reject); request.end(body); }); } test('initialize negotiates the current MCP protocol version by default', async () => { const server = createServer(); const response = await server.handleRpcRequest({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {}, }); assert.equal(response.result.protocolVersion, MCP_PROTOCOL_VERSION); assert.equal(response.result.serverInfo.name, 'test-server'); }); test('initialize can negotiate an older supported MCP protocol version', async () => { const server = createServer(); const olderVersion = SUPPORTED_PROTOCOL_VERSIONS[SUPPORTED_PROTOCOL_VERSIONS.length - 1]; const response = await server.handleRpcRequest({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: olderVersion }, }); assert.equal(response.result.protocolVersion, olderVersion); }); test('tool execution failures are returned as MCP tool errors', async () => { const server = createServer({ callTool: async () => { throw new Error('bad arguments'); }, }); const response = await server.handleRpcRequest({ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'example', arguments: {} }, }); assert.equal(response.error, undefined); assert.equal(response.result.isError, true); assert.deepEqual(response.result.content, [{ type: 'text', text: 'bad arguments' }]); }); test('tool object results include structuredContent', async () => { const value = { ok: true, count: 2 }; const server = createServer({ callToolDetailed: async () => ({ value, text: JSON.stringify(value, null, 2), }), }); const response = await server.handleRpcRequest({ jsonrpc: '2.0', id: 4, method: 'tools/call', params: { name: 'example', arguments: {} }, }); assert.deepEqual(response.result.structuredContent, value); assert.equal(response.result.content[0].type, 'text'); }); test('structuredContent sanitizes circular values', async () => { const value = { ok: true }; value.self = value; const server = createServer({ callToolDetailed: async () => ({ value, text: '{ "ok": true, "self": "[Circular]" }', }), }); const response = await server.handleRpcRequest({ jsonrpc: '2.0', id: 5, method: 'tools/call', params: { name: 'example', arguments: {} }, }); assert.deepEqual(response.result.structuredContent, { ok: true, self: '[Circular]' }); }); test('unsupported protocol version headers are rejected when present after initialize', () => { const server = createServer(); const response = server.validateProtocolVersionHeader( { headers: { 'mcp-protocol-version': '1999-01-01' } }, { jsonrpc: '2.0', id: 3, method: 'tools/list' } ); assert.equal(response.error.code, -32600); assert.match(response.error.message, /Unsupported MCP protocol version/); }); test('streamable HTTP Accept headers must allow json and event-stream', () => { const server = createServer(); assert.equal(server.validateAcceptHeader({ headers: { accept: 'application/json, text/event-stream' } }), null); assert.equal(server.validateAcceptHeader({ headers: { accept: '*/*' } }), null); const response = server.validateAcceptHeader({ headers: { accept: 'application/json' } }); assert.equal(response.error.code, -32600); assert.match(response.error.message, /Accept header/); }); test('JSON-RPC responses and notifications are classified for 202 handling', () => { const server = createServer(); assert.equal(server.classifyJsonRpcMessage({ jsonrpc: '2.0', id: 1, result: {} }), 'response'); assert.equal(server.classifyJsonRpcMessage({ jsonrpc: '2.0', method: 'notifications/initialized' }), 'notification'); assert.equal(server.classifyJsonRpcMessage({ jsonrpc: '2.0', id: 1, method: 'tools/list' }), 'request'); }); test('session validation requires a known session when enabled', () => { const server = createServer({}, { enableSessions: true }); server.sessions.add('abc123'); assert.equal( server.validateSession( { headers: { 'mcp-session-id': 'abc123' } }, { jsonrpc: '2.0', id: 1, method: 'tools/list' } ), null ); const missing = server.validateSession( { headers: {} }, { jsonrpc: '2.0', id: 1, method: 'tools/list' } ); assert.equal(missing.statusCode, 400); const unknown = server.validateSession( { headers: { 'mcp-session-id': 'nope' } }, { jsonrpc: '2.0', id: 1, method: 'tools/list' } ); assert.equal(unknown.statusCode, 404); }); test('HTTP notifications return 202 Accepted with no body', async () => { const server = createServer({}, { port: 0 }); await server.start(); try { const response = await httpJson(server.getPort(), { jsonrpc: '2.0', method: 'notifications/initialized', }); assert.equal(response.statusCode, 202); assert.equal(response.body, ''); } finally { await server.stop(); } }); test('HTTP initialize can return an optional session id when sessions are enabled', async () => { const server = createServer({}, { port: 0, enableSessions: true }); await server.start(); try { const response = await httpJson(server.getPort(), { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: MCP_PROTOCOL_VERSION }, }); assert.equal(response.statusCode, 200); assert.match(response.headers['mcp-session-id'], /^[\x21-\x7e]+$/); } finally { await server.stop(); } });