diff --git a/app/mcp/route.ts b/app/mcp/route.ts index f7fb1d5..48b847d 100644 --- a/app/mcp/route.ts +++ b/app/mcp/route.ts @@ -6,7 +6,7 @@ export const runtime = 'nodejs' const DEFAULT_ALLOWED_ORIGINS = ['https://ellaentity.ai', 'https://www.ellaentity.ai', 'https://mcp.ellaentity.ai'] const ALLOW_METHODS = 'POST, OPTIONS' const ALLOW_HEADERS = 'Content-Type, Accept, Mcp-Session-Id, MCP-Protocol-Version' -const REQUIRED_ACCEPT_TYPES = ['application/json', 'text/event-stream'] as const +const ACCEPTED_RESPONSE_TYPES = ['application/json', 'text/event-stream'] as const function allowedOrigins() { return process.env.MCP_ALLOWED_ORIGINS?.split(',').map((origin) => origin.trim()).filter(Boolean) ?? DEFAULT_ALLOWED_ORIGINS @@ -62,14 +62,18 @@ function hasJsonContentType(request: Request) { return request.headers.get('content-type')?.toLowerCase().includes('application/json') ?? false } -function hasRequiredAcceptTypes(request: Request) { - const accept = request.headers.get('accept')?.toLowerCase() +function hasCompatibleAcceptType(request: Request) { + const accept = request.headers.get('accept') - if (!accept) { - return false + if (!accept?.trim()) { + return true } - return REQUIRED_ACCEPT_TYPES.every((type) => accept.includes(type)) + const acceptedTypes = accept.toLowerCase().split(',').map((type) => type.trim()) + + return acceptedTypes.some( + (type) => type === '*/*' || ACCEPTED_RESPONSE_TYPES.some((acceptedType) => type.startsWith(acceptedType)), + ) } export function OPTIONS(request: Request) { @@ -108,8 +112,8 @@ export async function POST(request: Request) { return jsonRpcError(request, 415, -32600, 'Content-Type must be application/json') } - if (!hasRequiredAcceptTypes(request)) { - return jsonRpcError(request, 406, -32600, 'Accept must include application/json and text/event-stream') + if (!hasCompatibleAcceptType(request)) { + return jsonRpcError(request, 406, -32600, 'Accept must include application/json, text/event-stream, or */*') } try { diff --git a/tests/mcp.test.ts b/tests/mcp.test.ts index b8f6f34..3399690 100644 --- a/tests/mcp.test.ts +++ b/tests/mcp.test.ts @@ -64,9 +64,10 @@ test('GET and OPTIONS implement method and CORS behavior', async () => { test('POST validates content negotiation and origins', async () => { const body = { jsonrpc: '2.0', id: 1, method: 'ping', params: {} } - assert.equal((await POST(request('POST', body, { accept: '' }))).status, 406) - assert.equal((await POST(request('POST', body, { accept: 'application/json' }))).status, 406) - assert.equal((await POST(request('POST', body, { accept: 'text/event-stream' }))).status, 406) + assert.notEqual((await POST(request('POST', body, { accept: '' }))).status, 406) + assert.notEqual((await POST(request('POST', body, { accept: 'application/json' }))).status, 406) + assert.notEqual((await POST(request('POST', body, { accept: 'text/event-stream' }))).status, 406) + assert.notEqual((await POST(request('POST', body, { accept: '*/*' }))).status, 406) assert.equal((await POST(request('POST', body, { accept: 'text/plain' }))).status, 406) assert.equal((await POST(request('POST', body, { 'content-type': 'text/plain' }))).status, 415) assert.equal((await POST(request('POST', body, { origin: 'https://evil.example' }))).status, 403)