Align 2026-07-28 wire with spec PR #3002: serverInfo in result _meta, clientInfo optional (#2513)

This commit is contained in:
Felix Weinberger 2026-07-20 16:09:55 +01:00 • committed by GitHub
commit f4137630c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 933 additions and 207 deletions

View file

@ -128,9 +128,12 @@ describe('createMcpHandler — modern path', () => {
postRequest({ jsonrpc: '2.0', id: 5, method: 'server/discover', params: { _meta: ENVELOPE } })
);
expect(response.status).toBe(200);
const body = (await response.json()) as { result: { supportedVersions: string[]; serverInfo: { name: string } } };
const body = (await response.json()) as {
result: { supportedVersions: string[]; _meta: Record<string, { name: string }> };
};
expect(body.result.supportedVersions).toEqual([MODERN_REVISION]);
expect(body.result.serverInfo.name).toBe('entry-test-server');
// #3002: identity in the result `_meta`, never the body.
expect(body.result._meta['io.modelcontextprotocol/serverInfo']!.name).toBe('entry-test-server');
});
it('backfills the deprecated accessors and the negotiated revision from the validated envelope (per-request instance state)', async () => {
@ -309,7 +312,10 @@ describe('createMcpHandler — modern path', () => {
expect(response.status).toBe(400);
const body = (await response.json()) as JSONRPCErrorBody;
expect(body.error.code).toBe(-32_602);
expect(JSON.stringify(body.error.data)).toContain('clientInfo');
// clientCapabilities is the missing REQUIRED key; clientInfo is a
// SHOULD since spec PR #3002 and its absence is never an error.
expect(JSON.stringify(body.error.data)).toContain('clientCapabilities');
expect(JSON.stringify(body.error.data)).not.toContain('clientInfo');
expect(body.id).toBe(1);
expect(state.contexts).toHaveLength(0);
});

View file

@ -208,7 +208,16 @@ describe('createMcpHandler — subscriptions/listen', () => {
expect(resultFrame).toEqual({
jsonrpc: '2.0',
id: 1,
result: { resultType: 'complete', _meta: { 'io.modelcontextprotocol/subscriptionId': 1 } }
result: {
resultType: 'complete',
_meta: {
'io.modelcontextprotocol/subscriptionId': 1,
// #3002: the close result carries the serving instance's
// identity like every other result (SubscriptionsListenResultMeta
// extends ResultMetaObject).
'io.modelcontextprotocol/serverInfo': { name: 'listen-test-server', version: '1.0.0' }
}
}
});
});

View file

@ -30,6 +30,7 @@ import {
isJSONRPCResultResponse,
LATEST_PROTOCOL_VERSION,
PROTOCOL_VERSION_META_KEY,
SERVER_INFO_META_KEY,
setNegotiatedProtocolVersion,
SUPPORTED_PROTOCOL_VERSIONS
} from '@modelcontextprotocol/core-internal';
@ -99,12 +100,39 @@ describe('server/discover handler gating', () => {
if (isJSONRPCResultResponse(response)) {
const result = DiscoverResultSchema.parse(response.result);
expect(result.supportedVersions).toEqual([MODERN]);
expect(result.serverInfo).toEqual({ name: 'modern-server', version: '2.0.0' });
// #3002: identity travels in the result `_meta`, never the body.
expect(result._meta?.[SERVER_INFO_META_KEY]).toEqual({ name: 'modern-server', version: '2.0.0' });
expect('serverInfo' in (response.result as Record<string, unknown>)).toBe(false);
expect(result.instructions).toBe('hello');
}
await server.close();
});
it('serves discover for a client that omits clientInfo (SHOULD since spec PR #3002)', async () => {
const server = new Server(
{ name: 'modern-server', version: '2.0.0' },
{ capabilities: { tools: {} }, supportedProtocolVersions: DUAL_ERA_VERSIONS }
);
const withoutClientInfo: JSONRPCRequest = {
jsonrpc: '2.0',
id: 1,
method: 'server/discover',
params: {
_meta: {
[PROTOCOL_VERSION_META_KEY]: MODERN,
[CLIENT_CAPABILITIES_META_KEY]: {}
}
}
};
const response = await sendRaw(server, withoutClientInfo, { markModern: true });
expect(isJSONRPCResultResponse(response)).toBe(true);
if (isJSONRPCResultResponse(response)) {
const result = DiscoverResultSchema.parse(response.result);
expect(result.supportedVersions).toEqual([MODERN]);
}
await server.close();
});
it('a modern-era instance whose supported list carries no modern revision still answers -32601 (handler not installed)', async () => {
const server = new Server({ name: 'test', version: '1.0.0' }, { capabilities: {} });
const response = await sendRaw(server, discoverRequest, { markModern: true });

View file

@ -121,9 +121,12 @@ describe('serveStdio — subscriptions/listen', () => {
// the wire closes. No notifications/cancelled (the pre-#2953 path).
const results = inbound.filter(m => 'result' in m) as { id: unknown; result: unknown }[];
expect(results.map(m => m.id)).toEqual(['s1', 's2']);
// #3002: each close result carries the serving instance's identity
// (SubscriptionsListenResultMeta extends ResultMetaObject).
const serverInfo = { name: 's', version: '1' };
expect(results.map(m => m.result)).toEqual([
{ resultType: 'complete', _meta: { [SUBSCRIPTION_ID_META_KEY]: 's1' } },
{ resultType: 'complete', _meta: { [SUBSCRIPTION_ID_META_KEY]: 's2' } }
{ resultType: 'complete', _meta: { [SUBSCRIPTION_ID_META_KEY]: 's1', 'io.modelcontextprotocol/serverInfo': serverInfo } },
{ resultType: 'complete', _meta: { [SUBSCRIPTION_ID_META_KEY]: 's2', 'io.modelcontextprotocol/serverInfo': serverInfo } }
]);
expect(inbound.some(m => (m as JSONRPCNotification).method === 'notifications/cancelled')).toBe(false);
});