fix: declare the skills extension in the Inspector's client capabilities (#2373)

SEP-2133 negotiates an extension from both sides, and a strict server
refuses skills/* to a client that did not declare
io.modelcontextprotocol/skills. The Inspector called those methods
without ever declaring it. Add it to ADVERTISABLE_EXTENSIONS, on by
default, which also gives it a Server Settings toggle.

Give the composable test server skillsRequireClientExtension and two
per-era strict showcase configs, so a strict server is reproducible here.

Writing the modern-era test exposed two defects the existing suite hid:
its "modern" cases set protocolEra on the transport config, which
InspectorClient does not read, so they connected on legacy; and once
they really ran modern, every skills result failed, because the modern
schemas required resultType, which the SDK codec checks and then lifts
off before a caller schema runs. Connect with versionNegotiation and
assert the era, and drop the resultType requirement (and the two modern
schema variants it was the only difference in).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3RsrgCxVoFPokLB4vAkxi
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-14 08:36:37 -04:00
commit 1d765b8af6

View file

@ -150,8 +150,6 @@ import {
DirectoryReadResultSchema,
GetSkillEnvelopeSchema,
ListSkillsResultSchema,
ModernGetSkillEnvelopeSchema,
ModernDirectoryReadResultSchema,
ModernListSkillsResultSchema,
RESOURCES_DIRECTORY_READ_METHOD,
SKILLS_EXTENSION_KEY,
@ -5796,21 +5794,16 @@ export class InspectorClient extends InspectorClientEventTarget {
uri,
...(effectiveMeta ? { _meta: effectiveMeta } : {}),
};
// Era-aware for the same reason `skills/list` is: the method is
// consumer-owned, so no SDK codec stamps or checks its envelope. The modern
// variant requires `resultType` — a base-protocol member SEP-2322 puts on
// every modern result — and still not the caching attributes, which
// SEP-2640 leaves open. The envelope is returned whole; `getSkill`
// unwraps.
const resultSchema = this.isModernEra()
? ModernGetSkillEnvelopeSchema
: GetSkillEnvelopeSchema;
// One schema for both eras (#2373): on a modern connection the SDK codec
// has already enforced `resultType` and lifted it off, and the caching
// attributes are left open by SEP-2640, so there is nothing era-specific
// left to require. The envelope is returned whole; `getSkill` unwraps.
try {
return await this.invokeMcpClient(
() =>
this.client!.request(
{ method: SKILLS_GET_METHOD, params },
resultSchema,
GetSkillEnvelopeSchema,
this.getRequestOptions(this.progressTokenOf(metadata)),
),
{ method: SKILLS_GET_METHOD },
@ -5872,19 +5865,16 @@ export class InspectorClient extends InspectorClientEventTarget {
// for page one.
...(cursor !== undefined ? { cursor } : {}),
};
// Era-aware for the same reason `skills/list` is — the method is
// consumer-owned, so no SDK codec stamps or checks its envelope. The modern
// variant requires only `resultType`; see the schema for why it stops
// short of the caching attributes that `skills/list` requires.
const resultSchema = this.isModernEra()
? ModernDirectoryReadResultSchema
: DirectoryReadResultSchema;
// One schema for both eras (#2373): the SDK codec enforces and lifts
// `resultType` on a modern connection, and SEP-2640 requires no caching
// attributes of this method — see `skillsSchemas.ts` for why that differs
// from `skills/list`.
try {
return await this.invokeMcpClient(
() =>
this.client!.request(
{ method: RESOURCES_DIRECTORY_READ_METHOD, params },
resultSchema,
DirectoryReadResultSchema,
this.getRequestOptions(this.progressTokenOf(metadata)),
),
{ method: RESOURCES_DIRECTORY_READ_METHOD },