Skip to content

Add MCP Skills extension support to McpServer and McpClient - #10082

Merged
tim-smart merged 33 commits into
Effect-TS:mainfrom
typedrat:typedrat/mcp-server-skills
Oct 30, 2026
Merged

tim-smart merged 33 commits into
Effect-TS:mainfrom
typedrat:typedrat/mcp-server-skills

Conversation

@typedrat

@typedrat typedrat commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Effect's MCP server and client had no support for the Skills extension (SEP-2640). A server could register a SKILL.md as a plain resource, but it couldn't answer skills/list, skills/get or resources/directory/read, so it couldn't publish skills that hosts discover and verify. This adds both sides, following the maintained specification.

const RefundsSkill = McpServer.skill({
  uri: "skill://billing/refunds/SKILL.md",
  files: {
    "SKILL.md": "---\nname: refunds\ndescription: Process refunds using the billing tools\n---\n\nRead references/policy.md before issuing a refund.\n",
    "references/policy.md": "Refunds above CHF 500 require manager approval.\n"
  }
})

Changes

Server (McpServer.skill / registerSkill)

  • A skill is its SKILL.md URI plus its files, SKILL.md included. Files are served verbatim as resources, copied at registration, and the listed frontmatter is parsed from SKILL.md, so the two cannot differ.
  • The server answers skills/list, skills/get and resources/directory/read. Manifests carry the SHA-256 digest and size of the bytes served. Directory reads list files with the same metadata as resources/list, and prefix directories shared by several skills are merged.
  • Frontmatter must be valid McpSchema.SkillFrontmatter (the Agent Skills fields; others are kept) and plain JSON.
  • Registration fails with IllegalArgumentError on a name that doesn't match the URI, a path that leaves the skill, more than 512 files or 16 MiB, a skill directory that contains or lies inside another skill's, or a file URI already registered as a resource. Registering a resource at a skill file's URI afterwards is a defect. Nested skills are not supported.
  • The io.modelcontextprotocol/skills extension, with directoryRead, is declared once a skill is registered, on every protocol revision. v2026_07_28 results carry ttlMs/cacheScope from the new skills cache hint. Without skills, the methods answer Method not found.
  • McpServer.visibility hides skills through the new skills/list, skills/get and resources/directory/read middleware methods, with the SKILL.md URI as target.
  • MIME types come from Mime.getType, with .py, .sh and .ts mapped to script types.

Client (McpClient, opt-in with extensions: { skills: true })

  • listSkills, getSkill, readResourceDirectory and readSkillResource. Without the option, the client declares no extension and these fail with ConfigurationError. They run only against a server that declares the extension.
  • Invalid entries fail with ValidationError, or are skipped and reported when onInvalidSkill is given.
  • listSkills and readResourceDirectory share the discovery timeout and limits of listTools.
  • readSkillResource reads only files in the manifest and checks their digest and size, and checks a SKILL.md's frontmatter against the listing. For a "dynamic" skill it reads only files inside the skill directory, without digests, and still checks SKILL.md.
  • McpSchema.Skill, McpSchema.SkillResource (with digest and size checks) and McpSchema.SkillFrontmatter.

Fix found along the way

  • On v2026_07_28, resources/list and resources/templates/list failed with -32603 when a resource set an optional field to undefined. Resources are now transcoded through the public schemas, as tools and prompts already were. Own commit and changeset.

Coordination with #8631

#8631 (Tasks) adds a top-level tasks option to McpClient and builds its own client capabilities object. This PR uses extensions: { skills }, meant to grow to extensions: { skills, tasks }. The two need to agree on that shape before either merges, or whichever lands second drops the other's extensions key. Both also add methods to the 2026-07-28 adapter and schema maps.

Tests

  • test/ai/McpServer/Skills.test.ts: discovery; manifests matching the served bytes; copied bytes; skills/get; directory reads with resource metadata; -32602/-32601 errors; cache hints; 2025-11-25 and 2024-11-05; visibility; registration rules, overlap and resource collisions.
  • test/ai/McpClient.test.ts: round trip against McpServer on 2025-11-25 and 2026-07-28; opt-in; invalid entries; tampered bytes, wrong size and changed frontmatter; unlisted files; dynamic skills.
  • test/ai/McpSchema.test.ts: SkillResource digest and size checks.
  • test/ai/McpServer/v2026_07_28.test.ts: optional fields set to undefined (fails without the fix).

The official conformance runner (@modelcontextprotocol/conformance@0.2.0-alpha.12) passed against the original head, before the redesign. It has not been re-run against this head.

Closes #10016
Closes EFF-3092

@changeset-bot

changeset-bot Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8838fef

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 33 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-cloudflare Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/ai-typesafe Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/graphql-generator Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@effect-janitor effect-janitor Bot added the 4.0 label Oct 28, 2026
@effect-bot
effect-bot changed the base branch from main to v4/next-minor October 28, 2026 23:02
@effect-janitor effect-janitor Bot removed the 4.0 label Oct 28, 2026
@typedrat
typedrat force-pushed the typedrat/mcp-server-skills branch from 9cb43cf to 23ff4c0 Compare October 28, 2026 23:03
@typedrat
typedrat marked this pull request as ready for review October 28, 2026 23:06
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
arbitrary-combinators.ts 39.39 KB 39.38 KB +0.01 KB (+0.03%)
basic.ts 7.23 KB 7.21 KB +0.02 KB (+0.22%)
batching.ts 11.05 KB 11.03 KB +0.02 KB (+0.20%)
brand.ts 6.85 KB 6.84 KB +0.01 KB (+0.16%)
cache.ts 11.54 KB 11.53 KB +0.01 KB (+0.08%)
config.ts 22.82 KB 22.80 KB +0.02 KB (+0.09%)
differ.ts 21.97 KB 21.96 KB +0.02 KB (+0.07%)
http-client.ts 26.92 KB 26.90 KB +0.02 KB (+0.08%)
http-router.ts 36.89 KB 36.88 KB +0.00 KB (+0.01%)
logger.ts 12.01 KB 11.99 KB +0.01 KB (+0.13%)
metric.ts 9.47 KB 9.46 KB +0.01 KB (+0.13%)
optic.ts 7.07 KB 7.05 KB +0.02 KB (+0.31%)
pubsub.ts 16.03 KB 16.00 KB +0.03 KB (+0.16%)
queue.ts 12.82 KB 12.80 KB +0.01 KB (+0.12%)
schedule.ts 11.80 KB 11.78 KB +0.02 KB (+0.19%)
schema-bigdecimal.ts 14.07 KB 14.06 KB +0.01 KB (+0.07%)
schema-binary.ts 41.22 KB 41.22 KB -0.00 KB (-0.00%)
schema-class.ts 21.71 KB 21.68 KB +0.03 KB (+0.13%)
schema-fromJsonSchemaDocument.ts 29.56 KB 29.55 KB +0.01 KB (+0.04%)
schema-representation-roundtrip.ts 27.97 KB 27.94 KB +0.03 KB (+0.10%)
schema-string-transformation.ts 15.32 KB 15.31 KB +0.01 KB (+0.06%)
schema-string.ts 12.31 KB 12.29 KB +0.01 KB (+0.11%)
schema-template-literal.ts 16.25 KB 16.23 KB +0.03 KB (+0.16%)
schema-toArbitrary.ts 38.91 KB 38.90 KB +0.01 KB (+0.03%)
schema-toCodeDocument.ts 26.13 KB 26.13 KB -0.01 KB (-0.02%)
schema-toCodecJson.ts 20.88 KB 20.86 KB +0.02 KB (+0.09%)
schema-toEquivalence.ts 21.02 KB 21.00 KB +0.02 KB (+0.09%)
schema-toFormatter.ts 21.12 KB 21.10 KB +0.02 KB (+0.10%)
schema-toJsonSchemaDocument.ts 26.14 KB 26.14 KB +0.01 KB (+0.02%)
schema-toRepresentation.ts 21.21 KB 21.19 KB +0.02 KB (+0.11%)
schema.ts 20.80 KB 20.79 KB +0.01 KB (+0.05%)
stm.ts 14.20 KB 14.19 KB +0.01 KB (+0.10%)
stream.ts 10.38 KB 10.36 KB +0.02 KB (+0.19%)

@effect-bot
effect-bot force-pushed the typedrat/mcp-server-skills branch 4 times, most recently from 10b6d56 to 454054e Compare October 29, 2026 00:35
@effect-bot
effect-bot force-pushed the v4/next-minor branch 6 times, most recently from a2ce78a to 78475e1 Compare October 29, 2026 03:16
@tim-smart
tim-smart deleted the branch Effect-TS:v4/next-minor October 29, 2026 03:31
@tim-smart tim-smart closed this Oct 29, 2026
@typedrat

Copy link
Copy Markdown
Contributor Author

Why was this closed, @tim-smart?

@tim-smart tim-smart reopened this Oct 29, 2026
@tim-smart

Copy link
Copy Markdown
Contributor

It's a GitHub thing when the base branch gets removed.

@lloydrichards lloydrichards left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API looks like what I had in mind 👍 great work.

Could we reuse the same file metadata for resource listings and directory listings? Currently, SKILL.md has the skill’s name, description and size in resources/list, but the directory listing rebuilds the entry and loses some of that information.

I’ve left a few inline comments about keeping the published files and their manifests consistent as well as some suggestions for reusing Mime and Schema where possible.

Comment thread packages/effect/src/ai/McpServer.ts Resolved
Comment thread packages/effect/src/ai/McpServer.ts Resolved
Comment thread packages/effect/src/ai/McpServer.ts Resolved
@effect-bot
effect-bot changed the base branch from v4/next-minor to main October 29, 2026 23:29
@effect-janitor effect-janitor Bot added the 4.0 label Oct 29, 2026
…l fields

`resources/list` and `resources/templates/list` built each entry with the wire
schema's `make` and then decoded the result as wire JSON. `make` keeps a key
explicitly set to `undefined`, which the wire decoder rejects, so registering
a resource with, say, `description: undefined` turned both lists into a
`-32603` internal error. Transcode through the public schemas instead, as
tools and prompts already do, so `undefined` keys are dropped on encode.
Add `McpServer.skill` and `McpServer.registerSkill`, which publish an Agent
Skill under the Skills extension (SEP-2640). Each file of the skill is served
as a resource, and the server answers `skills/list`, `skills/get` and
`resources/directory/read`. Manifests carry the SHA-256 digest and size of the
exact bytes served. `SKILL.md` is either rendered from the frontmatter and
instructions, or given as a file and served verbatim. Registration fails on
skills that break the extension's rules.

A skill served over MCP must conform to the Agent Skills specification, so
its frontmatter is checked against the new `McpSchema.SkillFrontmatter`
schema of that specification, which keeps fields it does not define. The
frontmatter of a verbatim `SKILL.md` is read with `Yaml.parse`: it becomes the
listed frontmatter when none is given, and otherwise must equal the given
one, since hosts reject a skill whose two descriptions differ.

The extension is declared once a skill is registered, and served on every
protocol revision, as the official SDKs do. `v2026_07_28` results also carry
`resultType` and the `ttlMs`/`cacheScope` caching attributes, set by the new
`skills` cache hint. Without registered skills, the methods answer
`Method not found`.

The core registry stores a skill as its entry plus a directory lookup, so a
skill whose `resources` is `"dynamic"` can be added later without changing it.
`McpServer.visibility` already hid a skill's files, which are resources, but
the skill itself stayed in `skills/list` and directory reads and answered
`skills/get`. Add `skills/list`, `skills/get` and `resources/directory/read`
as middleware methods, with the skill's `SKILL.md` URI as `target`. A hidden
skill is left out of listings and directory reads, and `skills/get` fails as
if it was not registered. A directory read checks only the skills that have
the directory, so a shared prefix lists the visible skills under it.
toSkillSourceFile first reduces the three input forms to content and MIME
type, then encodes or copies once, instead of repeating the encode and copy
in each branch.

This branch is waiting to be deployed

1 waiting deployment
fork — 8838fefa Waiting Oct 30, 2026 by effect-bot via approval-gate #24270
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add first-class support for skills extension in McpServer

4 participants