This commit is contained in:
parent
f60dff0674
commit
f4137630c0
6 changed files with 933 additions and 207 deletions
|
|
@ -28,10 +28,17 @@ client: []
|
|||
# --- Same gaps as the 2025 baseline (fail identically when forced to 2026-07-28) ---
|
||||
# (empty: SEP-2468/2352/2350/837 burned by the auth bundle; SEP-2106 burned earlier)
|
||||
|
||||
server: []
|
||||
server:
|
||||
# --- Carried-forward scenarios (also run by the 2025 legs) ---
|
||||
# (empty: json-schema-2020-12 burned by the SEP-2106 fixture;
|
||||
# (json-schema-2020-12 burned by the SEP-2106 fixture;
|
||||
# sep-2164-resource-not-found burned by the spec#2907 error-code renumber +
|
||||
# alpha.5 referee; server-stateless burned by conformance#376 at alpha.9 —
|
||||
# the referee's `requiredCapabilities` assertion now matches the schema's
|
||||
# `ClientCapabilities` object, which is what this SDK emits.)
|
||||
# alpha.5 referee.)
|
||||
#
|
||||
# --- spec PR #3002 — referee pinned at alpha.9 asserts the OLD shape ---
|
||||
# Same three failing checks as the 2025-leg baseline entry
|
||||
# (sep-2575-request-meta-invalid-missing-client-info, the
|
||||
# missing-client-info iteration of sep-2575-http-server-meta-invalid-400,
|
||||
# and sep-2575-server-implements-discover which requires body serverInfo):
|
||||
# this SDK follows the final revision. Remove when the pin bumps to a
|
||||
# conformance release that incorporates #3002 (alpha.10+).
|
||||
- server-stateless
|
||||
|
|
|
|||
|
|
@ -23,6 +23,17 @@ client: []
|
|||
# last referee-side gap — conformance#361 callback-iss — closed at alpha.6)
|
||||
|
||||
server:
|
||||
# --- spec PR #3002 — referee pinned at alpha.9 asserts the OLD shape ---
|
||||
# The alpha.9 `server-stateless` scenario still enforces the pre-#3002
|
||||
# spec: clientInfo required in the envelope, serverInfo a DiscoverResult
|
||||
# body field. This SDK follows the final revision (clientInfo optional;
|
||||
# identity in the result _meta), so exactly three checks fail:
|
||||
# - sep-2575-request-meta-invalid-missing-client-info
|
||||
# - sep-2575-http-server-meta-invalid-400 (the missing-client-info iteration)
|
||||
# - sep-2575-server-implements-discover (requires body serverInfo)
|
||||
# Remove this entry when the pin bumps to a conformance release that
|
||||
# incorporates #3002 (alpha.10+).
|
||||
- server-stateless
|
||||
# --- SEP-2663 (io.modelcontextprotocol/tasks) — server SDK does not implement the tasks extension ---
|
||||
# Extension-tagged scenarios; selected only by `--suite all` (the alpha.9 referee
|
||||
# has no server-side `--suite extensions`). The active/draft/2026 legs never select
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ function withLocalDiscoverResponse(serverInfo: { name: string; version: string }
|
|||
// list/read/get calls reach the real mock; callers that
|
||||
// only use tools are unaffected by the extra entries.
|
||||
capabilities: { tools: { listChanged: true }, resources: {}, prompts: {} },
|
||||
serverInfo
|
||||
_meta: { 'io.modelcontextprotocol/serverInfo': serverInfo }
|
||||
}
|
||||
},
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
|
|
|
|||
|
|
@ -1365,7 +1365,24 @@ export const REQUIREMENTS: Record<string, Requirement> = {
|
|||
},
|
||||
'protocol:meta:result-to-client': {
|
||||
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic#_meta',
|
||||
behavior: "_meta returned in a handler's result is delivered intact to the requesting client."
|
||||
behavior:
|
||||
"_meta returned in a handler's result is delivered intact to the requesting client. On a 2026-07-28 connection the delivered _meta additionally carries the SDK-stamped io.modelcontextprotocol/serverInfo key (spec PR #3002: servers SHOULD identify themselves on every response); on 2025-era connections nothing is ever added."
|
||||
},
|
||||
'protocol:meta:server-identity': {
|
||||
source: 'https://modelcontextprotocol.io/specification/draft/server/discover',
|
||||
behavior:
|
||||
"On the 2026-07-28 revision the server identifies itself via _meta['io.modelcontextprotocol/serverInfo'] on its responses (spec PR #3002), and the client resolves getServerVersion() from the discover result's _meta.",
|
||||
transports: ['entryModern'],
|
||||
addedInSpecVersion: '2026-07-28',
|
||||
note: 'entryModern is the only arm that negotiates a real 2026-07-28 connection (the other arms run the legacy handshake), and the body also POSTs a raw discover via wired.fetch to assert the wire-level _meta stamp.'
|
||||
},
|
||||
'protocol:envelope:client-info-optional': {
|
||||
source: 'https://modelcontextprotocol.io/specification/draft/basic#meta',
|
||||
behavior:
|
||||
'A 2026-07-28 request whose per-request _meta envelope omits io.modelcontextprotocol/clientInfo is served normally — clientInfo is a SHOULD (spec PR #3002), never a validation requirement.',
|
||||
transports: ['entryModern'],
|
||||
addedInSpecVersion: '2026-07-28',
|
||||
note: "The wired SDK client always sends clientInfo (the spec SHOULD), so the omission can only be put on the wire as a raw POST through wired.fetch — which needs the entry arm's in-process endpoint."
|
||||
},
|
||||
'protocol:request-id:unique': {
|
||||
entryExclusions: [{ arm: 'entryModern', reason: 'method-not-in-modern-registry' }],
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import {
|
|||
import { expect, vi } from 'vitest';
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
import { tapWire, wire } from '../helpers/index';
|
||||
import { modernEnvelopeMeta, tapWire, wire } from '../helpers/index';
|
||||
import { verifies } from '../helpers/verifies';
|
||||
import type { TestArgs } from '../types';
|
||||
|
||||
|
|
@ -1128,8 +1128,71 @@ verifies('protocol:meta:result-to-client', async ({ transport }: TestArgs) => {
|
|||
const result = await client.callTool({ name: 'metered_call', arguments: {} });
|
||||
|
||||
expect(result.content).toEqual([{ type: 'text', text: 'metered' }]);
|
||||
// The _meta the handler attached to its result reaches the requesting client unchanged.
|
||||
expect(result._meta).toEqual(resultMeta);
|
||||
// The _meta the handler attached to its result reaches the requesting
|
||||
// client unchanged. On a 2026-era connection (the entryModern arm) the
|
||||
// encode seam additionally stamps the server's identity (spec PR #3002:
|
||||
// `_meta` serverInfo SHOULD on every response); on 2025-era connections
|
||||
// nothing is ever added (never-stamp).
|
||||
const expectedMeta =
|
||||
client.getNegotiatedProtocolVersion() === '2026-07-28'
|
||||
? { ...resultMeta, 'io.modelcontextprotocol/serverInfo': { name: 's', version: '0' } }
|
||||
: resultMeta;
|
||||
expect(result._meta).toEqual(expectedMeta);
|
||||
});
|
||||
|
||||
verifies('protocol:meta:server-identity', async ({ transport }: TestArgs) => {
|
||||
const makeServer = () => {
|
||||
const s = new Server({ name: 'identity-server', version: '4.2.0' }, { capabilities: { tools: {} } });
|
||||
s.setRequestHandler('tools/list', () => ({ tools: [] }));
|
||||
return s;
|
||||
};
|
||||
const client = newClient();
|
||||
await using wired = await wire(transport, makeServer, client);
|
||||
|
||||
// Client-side resolution: getServerVersion() reads the discover _meta.
|
||||
expect(client.getServerVersion()).toEqual({ name: 'identity-server', version: '4.2.0' });
|
||||
|
||||
// Wire-side: a raw discover response carries the _meta stamp.
|
||||
const response = await wired.fetch!(wired.url!, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
accept: 'application/json, text/event-stream',
|
||||
'mcp-protocol-version': '2026-07-28',
|
||||
'mcp-method': 'server/discover'
|
||||
},
|
||||
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'server/discover', params: { _meta: modernEnvelopeMeta() } })
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
const body = (await response.json()) as { result: { _meta?: Record<string, unknown> } };
|
||||
expect(body.result._meta?.['io.modelcontextprotocol/serverInfo']).toEqual({ name: 'identity-server', version: '4.2.0' });
|
||||
});
|
||||
|
||||
verifies('protocol:envelope:client-info-optional', async ({ transport }: TestArgs) => {
|
||||
const makeServer = () => {
|
||||
const s = new Server({ name: 's', version: '0' }, { capabilities: { tools: {} } });
|
||||
s.setRequestHandler('tools/list', () => ({ tools: [] }));
|
||||
return s;
|
||||
};
|
||||
const client = newClient();
|
||||
await using wired = await wire(transport, makeServer, client);
|
||||
|
||||
const meta = modernEnvelopeMeta();
|
||||
delete meta['io.modelcontextprotocol/clientInfo'];
|
||||
const response = await wired.fetch!(wired.url!, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
accept: 'application/json, text/event-stream',
|
||||
'mcp-protocol-version': '2026-07-28',
|
||||
'mcp-method': 'tools/list'
|
||||
},
|
||||
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: meta } })
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
const body = (await response.json()) as { result?: { tools?: unknown[] }; error?: unknown };
|
||||
expect(body.error).toBeUndefined();
|
||||
expect(body.result?.tools).toEqual([]);
|
||||
});
|
||||
|
||||
verifies('protocol:request-id:unique', async ({ transport }: TestArgs) => {
|
||||
|
|
@ -1611,7 +1674,7 @@ class LoopbackTransport implements Transport {
|
|||
this.respond(message.id, {
|
||||
supportedVersions: [this.serverProtocolVersion],
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: { name: 'loopback-server', version: '3.1.4' }
|
||||
_meta: { 'io.modelcontextprotocol/serverInfo': { name: 'loopback-server', version: '3.1.4' } }
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ function makeResponder(toolCallBody: unknown) {
|
|||
return {
|
||||
supportedVersions: ['2026-07-28'],
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: { name: 'raw-input-required-server', version: '0' }
|
||||
_meta: { 'io.modelcontextprotocol/serverInfo': { name: 'raw-input-required-server', version: '0' } }
|
||||
};
|
||||
}
|
||||
if (request.method === 'tools/call') return toolCallBody;
|
||||
|
|
|
|||
Loading…
Reference in a new issue