Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion examples/clients/typescript/everything-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,12 @@ async function runRequestMetadataClient(serverUrl: string): Promise<void> {
const clone = response.clone();
try {
const errorResult = await clone.json();
if (errorResult.error?.code === -32001) {
// -32004 is UnsupportedProtocolVersionError in the draft schema;
// -32001 is tolerated for servers that predate the dedicated code.
if (
errorResult.error?.code === -32004 ||
errorResult.error?.code === -32001
) {
logger.debug(
'Received UnsupportedProtocolVersionError, starting negotiation...'
);
Expand Down
81 changes: 76 additions & 5 deletions examples/servers/typescript/everything-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1227,13 +1227,13 @@ app.post('/mcp', async (req, res) => {
});
}

// Protocol Version Negotiation Matrix (-32602, HTTP 400)
// Protocol Version Negotiation Matrix (-32004, HTTP 400)
if (metaVersion !== 'DRAFT-2026-v1') {
return res.status(400).json({
jsonrpc: '2.0',
id,
error: {
code: -32602,
code: -32004,
message: 'UnsupportedProtocolVersionError',
data: { supported: ['DRAFT-2026-v1'] }
}
Expand Down Expand Up @@ -1296,7 +1296,10 @@ app.post('/mcp', async (req, res) => {
supportedVersions: ['DRAFT-2026-v1'],
capabilities: {
tools: { listChanged: true }, // Explicitly announce dynamic capabilities matching Section 7 expectations
prompts: { listChanged: true }
prompts: { listChanged: true },
// resources/list, resources/templates/list and resources/read are
// served on this path, so the capability must be declared too.
resources: {}
},
serverInfo: { name: 'everything-stateless-server', version: '1.0.0' }
}
Expand Down Expand Up @@ -1371,7 +1374,10 @@ app.post('/mcp', async (req, res) => {
description: 'Diagnostic logging validator tool',
inputSchema: { type: 'object', properties: {} }
}
]
],
// SEP-2549 caching hints are required on cacheable list results.
ttlMs: 300000,
cacheScope: 'public'
}
});
}
Expand All @@ -1387,7 +1393,9 @@ app.post('/mcp', async (req, res) => {
name: 'test_input_required_result_prompt',
description: 'MRTR: prompt that requires elicitation input'
}
]
],
ttlMs: 300000,
cacheScope: 'public'
}
});
}
Expand Down Expand Up @@ -1442,6 +1450,69 @@ app.post('/mcp', async (req, res) => {
}
}

// Resources on the stateless path (SEP-2575): SEP-2549 hints + SEP-2164 errors.
if (method === 'resources/list') {
return res.json({
jsonrpc: '2.0',
id,
result: {
resources: [
{
uri: 'test://stateless-static-text',
name: 'Stateless Static Text',
description: 'A static text resource served on the draft path',
mimeType: 'text/plain'
}
],
ttlMs: 300000,
cacheScope: 'public'
}
});
}

if (method === 'resources/templates/list') {
return res.json({
jsonrpc: '2.0',
id,
result: {
resourceTemplates: [],
ttlMs: 300000,
cacheScope: 'public'
}
});
}

if (method === 'resources/read') {
const uri = params.uri as string | undefined;
if (uri === 'test://stateless-static-text') {
return res.json({
jsonrpc: '2.0',
id,
result: {
contents: [
{
uri,
mimeType: 'text/plain',
text: 'Static text content from the stateless draft path.'
}
],
ttlMs: 300000,
cacheScope: 'private'
}
});
}
// SEP-2164: unknown resources get -32602 with the requested uri in data.
return res.status(200).json({
jsonrpc: '2.0',
id,
error: {
code: -32602,
message: 'Resource not found',
data: { uri }
}
});
}

if (method === 'tools/call') {
const name = params.name;
const inputResponses = params.inputResponses as
Expand Down
72 changes: 29 additions & 43 deletions examples/servers/typescript/sep-2164-empty-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,44 @@
/**
* SEP-2164 Negative Test Server
*
* Returns an empty contents array for any resources/read request, violating
* the SEP-2164 MUST NOT. The sep-2164-resource-not-found scenario should
* emit FAILURE for sep-2164-no-empty-contents against this server.
* Speaks the stateless wire protocol (SEP-2575) but returns an empty
* contents array for any resources/read request, violating the SEP-2164 MUST
* NOT. The sep-2164-resource-not-found scenario should emit FAILURE for
* sep-2164-no-empty-contents against this server.
*/

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import {
ListResourcesRequestSchema,
ReadResourceRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import express from 'express';

function createServer() {
const server = new Server(
{ name: 'sep-2164-empty-contents', version: '1.0.0' },
{ capabilities: { resources: {} } }
);

server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: []
}));

server.setRequestHandler(ReadResourceRequestSchema, async () => ({
contents: []
}));

return server;
}

const app = express();
app.use(express.json());

app.post('/mcp', async (req, res) => {
try {
const server = createServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
if (!res.headersSent) {
res.status(500).json({
app.post('/mcp', (req, res) => {
const body = req.body || {};
const id = body.id ?? null;
const method = body.method;

switch (method) {
case 'server/discover':
return res.json({
jsonrpc: '2.0',
id,
result: {
supportedVersions: ['DRAFT-2026-v1'],
capabilities: { resources: {} },
serverInfo: { name: 'sep-2164-empty-contents', version: '1.0.0' }
}
});
case 'resources/list':
return res.json({ jsonrpc: '2.0', id, result: { resources: [] } });
case 'resources/read':
// Deliberately return an empty contents array instead of an error.
return res.json({ jsonrpc: '2.0', id, result: { contents: [] } });
default:
return res.status(404).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: `Internal error: ${error instanceof Error ? error.message : String(error)}`
},
id: null
id,
error: { code: -32601, message: 'Method not found' }
});
}
}
});

Expand Down
Loading
Loading