Skip to content

feat: serve skills over the MCP Skills extension (SEP-2640) - #295

Open
mattpodwysocki wants to merge 7 commits into
mainfrom
feat/skills-extension
Open

mattpodwysocki wants to merge 7 commits into
mainfrom
feat/skills-extension

Conversation

@mattpodwysocki

@mattpodwysocki mattpodwysocki commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Part of #259

What

The server now supports the MCP Skills extension (SEP-2640, Final, io.modelcontextprotocol/skills). A client can list our skills and load one when a task calls for it, so the model doesn't have to work the steps out from tool descriptions.

Four skills, all from mapbox/mapbox-agent-skills:

  • mapbox-map-display (new, added in Add mapbox-map-display and mapbox-reachability-analysis skills mapbox-agent-skills#94): passing refs to render_map_tool, several results on one map, hand-placed markers with mapbox_id for the results panel, base map restyling, and when a static image is right instead.
  • mapbox-reachability-analysis (new, same PR): isochrone, then bbox_tool, then category search in that box, then points_within_polygon_tool to drop places in the box's corners, then one map with the area and only the places inside it. This goes further than the show-reachable-areas prompt, which maps the area only.
  • mapbox-location-grounding: answering location questions from live data with citations instead of training data.
  • mapbox-search-patterns: choosing the right search tool and parameters.

Where the skills come from

The skills repo is the only copy; nothing is duplicated here.

  • agent-skills.json pins a commit (full SHA) and maps each served skill to the tools it needs.
  • scripts/fetch-agent-skills.mjs downloads that commit and generates the gitignored src/skills/agentSkills.generated.ts. It runs from prepare, so npm ci and npm run build both run it, and it skips the download when the generated file already matches agent-skills.json.
  • The skills compile into dist and ship in the npm package (ESM and CommonJS), so nothing is fetched at runtime and hosted gets them through the package.
  • evals/ and AGENTS.md aren't served; SKILL.md and references/ are.
  • To change a skill: edit it in mapbox-agent-skills, then bump ref here.
  • mapbox-agent-skills#94 also fixes tool names those skills had wrong for this server (point_in_polygon_tool, the DevKit's bounding_box_tool, static maps instead of render_map_tool). ref currently pins that PR's head commit; I'll bump it to the merge commit once Add 8 offline geospatial tools using Turf.js #94 merges, before this one does.

How

  • SDK support: served by the SDK's own Skills extension (@modelcontextprotocol/server/ext/skills, typescript-sdk#3047), which shipped in 2.4.0 (chore: bump MCP TypeScript SDK to 2.4.0 #303). It declares the capability under capabilities.extensions (where ChatGPT reads it), answers skills/list and skills/get, and serves the skill:// files with their sha256 digests and byte sizes. Our code just hands it each skill's files with memorySkillFiles.
  • Strict loading: a skill that breaks the format's rules (name, frontmatter, limits) fails at startup instead of being quietly skipped.
  • Frontmatter: values are written as JSON strings, which YAML reads as double-quoted scalars, so a colon in a description can't break it.
  • Caching: skills are static per server version, so the cache hint is 1 hour, public (sent on 2026-07-28 connections).
  • Only usable skills: a skill is served only when every tool it uses is enabled. --disable-tools isochrone_tool drops the reachability skill, and with no skills left the extension isn't declared at all.
  • Turning skills off: --disable-skills or DISABLE_SKILLS=true serves none, for a plugin that already ships its own copies of the same skills. Hosted doesn't attach skills yet either; that waits until we can confirm how the ChatGPT plugin handles skills from both places.
  • Export: @mapbox/mcp-server/skills exports the skills, installSkillsTo(server, skills) and loadSkills(skills). Hosted builds a server per request, so it should loadSkills once and attach() the catalog to each server.

Updated after #296 and #303: this first hand-rolled skills/list and skills/get on SDK v1, with the two skills inline in TypeScript. It's now on SDK v2 with the SDK's implementation, and the skills come from mapbox-agent-skills.

Client support

  • ChatGPT: imports skills when an app is submitted (Scan Tools), not live. Up to 5 skills, which is why the list stays short.
  • fast-agent and MCP Inspector: can read them.
  • Claude: doesn't support the extension yet, so nothing changes in Claude Desktop or Claude Code for now.

Testing

  • New tests cover:
    • Listing, getting and reading every file through the SDK's SkillsClient, which checks sizes, digests and frontmatter the way hosts do (including a multibyte character and a colon and quotes in the description).
    • The unknown-skill error, one catalog attached to several servers, and an invalid skill name failing to load.
    • The generated file matches agent-skills.json (catches a forgotten npm run fetch-skills).
    • Every served skill loads under the SDK's strict rules, at most 5 skills (ChatGPT's limit), SKILL.md under 256 KiB.
    • No file in any served skill names a tool this server doesn't have. This is what caught the wrong tool names in the skills repo.
    • Tool filtering.
  • Typecheck clean, 1099 tests pass, lint 0 errors.
  • Fresh clone: npm ci generates the skills, then typecheck, build and the full suite pass. npm pack includes the compiled skills in both builds.
  • SkillsClient over stdio against the built dist: all four skills read back verified. With isochrone_tool disabled, reachability and location-grounding drop out. With render_map_tool, isochrone_tool and search_and_geocode_tool disabled, none are served and the extension isn't declared.
  • Over stdio, initialize declares io.modelcontextprotocol/skills by default and no extension with --disable-skills or DISABLE_SKILLS=true.
  • mcp-inspector --cli node dist/esm/index.js --method skills/list --verify --require-digests (Inspector 2.11.0): "Verified 4 skills and 7 files: no conformance errors."
  • Ran the reachability steps with the live tools, for a 10 minute walk from the Ferry Building. Of 25 cafés, 24 were inside the walk area and 1 was dropped. That hit the 25-result cap, so the skill's "there are probably more" guidance is what applies there.

Not done yet

  • Not tried in ChatGPT, which needs a Scan Tools run during app submission.
  • directoryRead isn't declared. Supporting files like mapbox-search-patterns' references are listed in the skill's entry, so clients still reach them.

🤖 Generated with Claude Code

Adds two skills, mapbox-map-display and mapbox-reachability-analysis,
served through skills/list, skills/get and skill:// resources, with
manifest digests computed from the served bytes. A skill is only served
when all its tools are enabled. Also exported from
@mapbox/mcp-server/skills for the hosted server.

Part of #259

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mattpodwysocki mattpodwysocki added the enhancement New feature or request label Oct 21, 2026
@mattpodwysocki
mattpodwysocki requested a review from a team as a code owner October 21, 2026 13:09
@mattpodwysocki mattpodwysocki added the enhancement New feature or request label Oct 21, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mattpodwysocki and others added 2 commits October 26, 2026 11:49
SDK 2.4.0 ships SEP-2640 support, so the hand-rolled skills/list and
skills/get handlers go away. loadSkills() builds a SkillCatalog from our
Skill objects and installSkillsTo() attaches it, which also declares the
capability. Tests read every skill through the SDK's SkillsClient, which
checks sizes, digests and frontmatter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mattpodwysocki and others added 2 commits October 26, 2026 12:21
The skills now live only in mapbox-agent-skills (mapbox-agent-skills#94
adds the two this PR introduced). agent-skills.json pins a commit and maps
each served skill to the tools it needs; scripts/fetch-agent-skills.mjs,
run by prepare (npm ci, npm run build), generates the gitignored
src/skills/agentSkills.generated.ts, so the skills ship in the package and
nothing is fetched at runtime. Also serves mapbox-location-grounding and
mapbox-search-patterns. Tests check the generated file matches the pin,
every skill loads strictly, and no skill names a tool this server lacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For plugins that already ship their own copies of the same skills.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ctufts
ctufts previously approved these changes Oct 28, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants