[v2] Decouple server from express and hono - http framework-agnostic MCP server (#1326)
This commit is contained in:
parent
5ce4b5ef08
commit
f495077e08
7 changed files with 8193 additions and 15757 deletions
|
|
@ -9,7 +9,8 @@
|
|||
import { Client } from '@modelcontextprotocol/client';
|
||||
import { InMemoryTransport, ListPromptsResultSchema } from '@modelcontextprotocol/core';
|
||||
import { McpServer } from '@modelcontextprotocol/server';
|
||||
import { type ZodMatrixEntry, zodTestMatrix } from '@modelcontextprotocol/test-helpers';
|
||||
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
|
||||
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
|
||||
|
||||
describe.each(zodTestMatrix)('Issue #1277: $zodVersionLabel', (entry: ZodMatrixEntry) => {
|
||||
const { z } = entry;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ import {
|
|||
SetLevelRequestSchema,
|
||||
SUPPORTED_PROTOCOL_VERSIONS
|
||||
} from '@modelcontextprotocol/core';
|
||||
import { createMcpExpressApp, InMemoryTaskMessageQueue, InMemoryTaskStore, McpServer, Server } from '@modelcontextprotocol/server';
|
||||
import { createMcpExpressApp } from '@modelcontextprotocol/express';
|
||||
import { InMemoryTaskStore, McpServer, Server } from '@modelcontextprotocol/server';
|
||||
import type { Request, Response } from 'express';
|
||||
import supertest from 'supertest';
|
||||
import * as z3 from 'zod/v3';
|
||||
import * as z4 from 'zod/v4';
|
||||
|
|
@ -2066,7 +2068,7 @@ describe('createMcpExpressApp', () => {
|
|||
|
||||
test('should parse JSON bodies', async () => {
|
||||
const app = createMcpExpressApp({ host: '0.0.0.0' }); // Disable host validation for this test
|
||||
app.post('/test', (req, res) => {
|
||||
app.post('/test', (req: Request, res: Response) => {
|
||||
res.json({ received: req.body });
|
||||
});
|
||||
|
||||
|
|
@ -2078,7 +2080,7 @@ describe('createMcpExpressApp', () => {
|
|||
|
||||
test('should reject requests with invalid Host header by default', async () => {
|
||||
const app = createMcpExpressApp();
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
@ -2097,7 +2099,7 @@ describe('createMcpExpressApp', () => {
|
|||
|
||||
test('should allow requests with localhost Host header', async () => {
|
||||
const app = createMcpExpressApp();
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
@ -2109,7 +2111,7 @@ describe('createMcpExpressApp', () => {
|
|||
|
||||
test('should allow requests with 127.0.0.1 Host header', async () => {
|
||||
const app = createMcpExpressApp();
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
@ -2121,7 +2123,7 @@ describe('createMcpExpressApp', () => {
|
|||
|
||||
test('should not apply host validation when host is 0.0.0.0', async () => {
|
||||
const app = createMcpExpressApp({ host: '0.0.0.0' });
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
@ -2134,7 +2136,7 @@ describe('createMcpExpressApp', () => {
|
|||
|
||||
test('should apply host validation when host is explicitly localhost', async () => {
|
||||
const app = createMcpExpressApp({ host: 'localhost' });
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
@ -2146,7 +2148,7 @@ describe('createMcpExpressApp', () => {
|
|||
|
||||
test('should allow requests with IPv6 localhost Host header', async () => {
|
||||
const app = createMcpExpressApp();
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
@ -2158,7 +2160,7 @@ describe('createMcpExpressApp', () => {
|
|||
|
||||
test('should apply host validation when host is ::1 (IPv6 localhost)', async () => {
|
||||
const app = createMcpExpressApp({ host: '::1' });
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
@ -2185,7 +2187,7 @@ describe('createMcpExpressApp', () => {
|
|||
test('should use custom allowedHosts when provided', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const app = createMcpExpressApp({ host: '0.0.0.0', allowedHosts: ['myapp.local', 'localhost'] });
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
@ -2205,7 +2207,7 @@ describe('createMcpExpressApp', () => {
|
|||
test('should override default localhost validation when allowedHosts is provided', async () => {
|
||||
// Even though host is localhost, we're using custom allowedHosts
|
||||
const app = createMcpExpressApp({ host: 'localhost', allowedHosts: ['custom.local'] });
|
||||
app.post('/test', (_req, res) => {
|
||||
app.post('/test', (_req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
import { Client } from '@modelcontextprotocol/client';
|
||||
import { getDisplayName, InMemoryTaskStore, InMemoryTransport, UriTemplate } from '@modelcontextprotocol/core';
|
||||
import type { CallToolResult, Notification, TextContent } from '@modelcontextprotocol/core';
|
||||
import {
|
||||
type CallToolResult,
|
||||
CallToolResultSchema,
|
||||
CompleteResultSchema,
|
||||
ElicitRequestSchema,
|
||||
ErrorCode,
|
||||
getDisplayName,
|
||||
GetPromptResultSchema,
|
||||
InMemoryTaskStore,
|
||||
InMemoryTransport,
|
||||
ListPromptsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ListToolsResultSchema,
|
||||
LoggingMessageNotificationSchema,
|
||||
type Notification,
|
||||
ReadResourceResultSchema,
|
||||
type TextContent,
|
||||
UriTemplate,
|
||||
UrlElicitationRequiredError
|
||||
} from '@modelcontextprotocol/core';
|
||||
|
||||
import { completable } from '../../../../packages/server/src/server/completable.js';
|
||||
import { McpServer, ResourceTemplate } from '../../../../packages/server/src/server/mcp.js';
|
||||
import { type ZodMatrixEntry, zodTestMatrix } from '../../../../packages/server/test/server/__fixtures__/zodTestMatrix.js';
|
||||
import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
|
||||
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
|
||||
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
||||
|
||||
function createLatch() {
|
||||
let latch = false;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { randomUUID } from 'node:crypto';
|
||||
import { createServer, type Server } from 'node:http';
|
||||
import type { Server } from 'node:http';
|
||||
import { createServer } from 'node:http';
|
||||
|
||||
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
LATEST_PROTOCOL_VERSION,
|
||||
ListPromptsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListToolsResultSchema,
|
||||
McpServer,
|
||||
StreamableHTTPServerTransport
|
||||
McpServer
|
||||
} from '@modelcontextprotocol/server';
|
||||
import { listenOnRandomPort } from '@modelcontextprotocol/test-helpers';
|
||||
import { type ZodMatrixEntry, zodTestMatrix } from '@modelcontextprotocol/test-helpers';
|
||||
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
|
||||
import { listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers';
|
||||
|
||||
describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
|
||||
const { z } = entry;
|
||||
|
|
@ -68,7 +69,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
|
|||
);
|
||||
|
||||
// Create transport with or without session management
|
||||
const serverTransport = new StreamableHTTPServerTransport({
|
||||
const serverTransport = new NodeStreamableHTTPServerTransport({
|
||||
sessionIdGenerator: withSessionManagement
|
||||
? () => randomUUID() // With session management, generate UUID
|
||||
: undefined // Without session management, return undefined
|
||||
|
|
@ -89,7 +90,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
|
|||
describe('Stateless Mode', () => {
|
||||
let server: Server;
|
||||
let mcpServer: McpServer;
|
||||
let serverTransport: StreamableHTTPServerTransport;
|
||||
let serverTransport: NodeStreamableHTTPServerTransport;
|
||||
let baseUrl: URL;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -253,7 +254,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
|
|||
describe('Stateful Mode', () => {
|
||||
let server: Server;
|
||||
let mcpServer: McpServer;
|
||||
let serverTransport: StreamableHTTPServerTransport;
|
||||
let serverTransport: NodeStreamableHTTPServerTransport;
|
||||
let baseUrl: URL;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { randomUUID } from 'node:crypto';
|
||||
import { createServer, type Server } from 'node:http';
|
||||
import type { Server } from 'node:http';
|
||||
import { createServer } from 'node:http';
|
||||
|
||||
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
|
||||
import type { TaskRequestOptions } from '@modelcontextprotocol/server';
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
|
|
@ -14,7 +16,6 @@ import {
|
|||
McpError,
|
||||
McpServer,
|
||||
RELATED_TASK_META_KEY,
|
||||
StreamableHTTPServerTransport,
|
||||
TaskSchema
|
||||
} from '@modelcontextprotocol/server';
|
||||
import { listenOnRandomPort, waitForTaskStatus } from '@modelcontextprotocol/test-helpers';
|
||||
|
|
@ -23,7 +24,7 @@ import { z } from 'zod';
|
|||
describe('Task Lifecycle Integration Tests', () => {
|
||||
let server: Server;
|
||||
let mcpServer: McpServer;
|
||||
let serverTransport: StreamableHTTPServerTransport;
|
||||
let serverTransport: NodeStreamableHTTPServerTransport;
|
||||
let baseUrl: URL;
|
||||
let taskStore: InMemoryTaskStore;
|
||||
|
||||
|
|
@ -188,7 +189,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
);
|
||||
|
||||
// Create transport
|
||||
serverTransport = new StreamableHTTPServerTransport({
|
||||
serverTransport = new NodeStreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID()
|
||||
});
|
||||
|
||||
|
|
@ -1040,7 +1041,7 @@ describe('Task Lifecycle Integration Tests', () => {
|
|||
method: 'tasks/cancel',
|
||||
params: { taskId }
|
||||
},
|
||||
z.object({ _meta: z.record(z.unknown()).optional() })
|
||||
z.object({ _meta: z.record(z.string(), z.unknown()).optional() })
|
||||
);
|
||||
|
||||
// Verify task is cancelled
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import { randomUUID } from 'node:crypto';
|
||||
import { createServer, type Server } from 'node:http';
|
||||
import type { Server } from 'node:http';
|
||||
import { createServer } from 'node:http';
|
||||
|
||||
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
LoggingMessageNotificationSchema,
|
||||
McpServer,
|
||||
StreamableHTTPServerTransport
|
||||
} from '@modelcontextprotocol/server';
|
||||
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
|
||||
import type { EventStore, JSONRPCMessage } from '@modelcontextprotocol/server';
|
||||
import { CallToolResultSchema, LoggingMessageNotificationSchema, McpServer } from '@modelcontextprotocol/server';
|
||||
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
|
||||
import { listenOnRandomPort, zodTestMatrix } from '@modelcontextprotocol/test-helpers';
|
||||
|
||||
|
|
@ -51,7 +48,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
|
|||
describe('Transport resumability', () => {
|
||||
let server: Server;
|
||||
let mcpServer: McpServer;
|
||||
let serverTransport: StreamableHTTPServerTransport;
|
||||
let serverTransport: NodeStreamableHTTPServerTransport;
|
||||
let baseUrl: URL;
|
||||
let eventStore: InMemoryEventStore;
|
||||
|
||||
|
|
@ -117,7 +114,7 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
|
|||
);
|
||||
|
||||
// Create a transport with the event store
|
||||
serverTransport = new StreamableHTTPServerTransport({
|
||||
serverTransport = new NodeStreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
eventStore
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { Client } from '@modelcontextprotocol/client';
|
||||
import { InMemoryTransport } from '@modelcontextprotocol/core';
|
||||
import { McpServer, ResourceTemplate, Server } from '@modelcontextprotocol/server';
|
||||
import { type ZodMatrixEntry, zodTestMatrix } from '@modelcontextprotocol/test-helpers';
|
||||
import type { ZodMatrixEntry } from '@modelcontextprotocol/test-helpers';
|
||||
import { zodTestMatrix } from '@modelcontextprotocol/test-helpers';
|
||||
|
||||
describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
|
||||
const { z } = entry;
|
||||
|
|
|
|||
Loading…
Reference in a new issue