skills-over-mcp-demo
Serves Agent Skills over MCP using the SEP-2640 Skills Extension, enabling skills discovery, retrieval, and resource reading via MCP.
README
skills-over-mcp-demo
Demo MCP server that serves Agent Skills over MCP per SEP-2640 v1 (Skills Extension), built on the v2 TypeScript SDK (@modelcontextprotocol/server) and the experimental skills extension SDK (@olaservo/ext-skills).
Quickstart
npm install
npm run smoke # build + end-to-end client checks over stdio
npm run smoke:http # same checks over streamable HTTP (the transport OpenAI's importer uses)
Both smoke tests connect with versionNegotiation: { mode: "auto" } and assert the connection lands on the latest protocol revision (2026-07-28) and that skills/list carries the SEP-2549 caching attributes, which the SEP scopes to that revision.
Or run the server directly for use from a host:
npm run build
node dist/index.js # stdio, serves the bundled skills/ directory
node dist/index.js path/to/dir # stdio, different skills directory
node dist/http.js --port 3000 # streamable HTTP at http://127.0.0.1:3000/mcp
Claude Desktop / generic stdio host config:
{
"mcpServers": {
"skills-over-mcp-demo": {
"command": "node",
"args": ["<absolute path>/dist/index.js"]
}
}
}
What it demonstrates
| SEP-2640 surface | Where |
|---|---|
Capability declaration capabilities.extensions["io.modelcontextprotocol/skills"] |
registerSkillResources() declares it before connect |
skills/list — entries with verbatim frontmatter + complete {uri, digest} manifest |
3 listed skills |
skills/get — entry retrieval by URI, listed or not; -32602 for non-skills |
the unlisted secret-menu skill |
Partial listings + server instructions as a skill pointer |
secret-menu is served but never enumerated; instructions point to it |
resources/read — every skill file individually addressable |
skill://… resources + catch-all template |
resources/directory/read (optional, gated on directoryRead) |
declared and implemented |
SEP-2549 list-caching attributes on skills/list (2026-07-28+) |
ttlMs: 60_000, cacheScope: "public" |
| Multi-segment skill paths | skill://dice-roller/tabletop-dice/SKILL.md |
Opt-in tools bridge (--tools-bridge) |
list_skills + read_skill (read-only, annotations.readOnlyHint) |
Interop finding: ChatGPT connector creation fails against a tools-less MCP server — the "New Plugin" dialog errors out generically when tools/list returns -32601, even though the server is a valid SEP-2640 skills/resources server. The --tools-bridge flag exists for that case. It is off by default and not part of SEP-2640: the SEP defines no server-side tools (its reader-tool guidance is for host-provided tools), so the default surface stays pure-SEP. The flag mirrors that host-side reader tool server-side so tool-only hosts can consume the skills at all; the HF Space deployment enables it.
Skills served:
skill://dice-roller/tabletop-dice/SKILL.md multi-segment path, references/ supporting file
skill://mcp-glossary/SKILL.md single-file skill
skill://release-notes-writer/SKILL.md templates/ supporting file
skill://secret-menu/SKILL.md UNLISTED — reachable via skills/get + instructions pointer only
The smoke test (src/smoke.ts) exercises the client side of the SEP: capability gating, enumeration, unlisted-skill retrieval, digest + frontmatter verification on reads, unlisted-file rejection semantics, directory walking, and discoverAndBuildCatalog() producing a system-prompt catalog that merges the instructions-pointer skill with the listing.
Public endpoint (Hugging Face Space)
The server is deployed as a public Docker Space at olaservo/skills-over-mcp-demo:
https://olaservo-skills-over-mcp-demo.hf.space/mcp
Deployment lives in hf-space/: assemble.ps1 stages a self-contained bundle (source + skills + Dockerfile + Space README) into hf-space/.staging, then hf upload olaservo/skills-over-mcp-demo hf-space/.staging . --repo-type space pushes it. Verify the live endpoint with tsx src/smoke-http.ts https://olaservo-skills-over-mcp-demo.hf.space/mcp. Free CPU Spaces sleep after inactivity; the first request after a sleep is slow.
Deployment note: the first push got auto-flagged by HF's abuse handler (rule "Cloudflare" — tunnel-domain strings in a source docstring). Removing the trycloudflare.com references and recreating the Space cleared it; keep tunnel hostnames out of Space content.
Testing the OpenAI skills import (Scan Tools)
OpenAI imports skills server-side: during plugin submission, the developer portal's Scan Tools connects to your MCP server over streamable HTTP, runs skills/list / skills/get / resources/read, verifies digests, and snapshots the skills into the plugin draft. Codex and ChatGPT then receive skills from OpenAI's plugin backend, not from your server. The test ladder:
-
Local validation with MCP Inspector (OpenAI's prescribed pre-submission check):
npm run start:http # terminal 1 npx @modelcontextprotocol/inspector # terminal 2 → Streamable HTTP → http://127.0.0.1:3000/mcp # or non-interactive: npx @modelcontextprotocol/inspector --cli http://127.0.0.1:3000/mcp --transport http --method resources/list -
Expose publicly — the deployed Hugging Face Space above is the stable public endpoint. (Alternative: a quick tunnel — start one against
http://localhost:3000, then restart the server with--allow-host <tunnel-hostname>.) -
Run Scan Tools in the OpenAI developer portal against
https://olaservo-skills-over-mcp-demo.hf.space/mcpduring plugin creation, then check the imported draft.
Result (2026-08-03): confirmed. Scan Tools imported all 3 listed skills with "Passed" validation; unlisted secret-menu did not import (the importer consumes only skills/list). Full findings — including the tools-surface requirement for ChatGPT connectors and the explicit-annotations validation — in docs/openai-plugin-submission.md. Expected: the 3 listed skills import with digests verified; secret-menu does not (their importer consumes only skills/list). Then exercise the plugin in ChatGPT developer mode.
The demo intentionally fits OpenAI's import limits (≤5 uniquely named skills, ≤100 files/skill, SKILL.md ≤256 KiB, files ≤1 MiB).
OpenAI skills import vs SEP-2640
OpenAI's MCP server plugin docs support importing skills from an MCP server, and their mechanism is recognizably SEP-2640 v1. Checked against the current SEP text (PR #2640, seps/2640-skills-extension.md):
Matches the SEP:
- Capability declared at
capabilities.extensions["io.modelcontextprotocol/skills"]— OpenAI's docs even call out that the earlierexperimentallocation is wrong. skills/list(paginated vianextCursor) returninguri, verbatimfrontmatter(with requirednameanddescription), and a completeresourcesmanifest;skills/getfor single-entry retrieval; content fetched viaresources/read.- Digest format
sha256:+ 64 lowercase hex chars; text resources hashed over the UTF-8 bytes ofcontent.text, blob resources hashed over the base64-decoded bytes — exactly the SEP's raw-bytes rule. - Directory-name-equals-skill-name (the SEP's "final
<skill-path>segment MUST equalfrontmatter.name"). - Import-time verification that fetched content matches digests and that
SKILL.mdfrontmatter matches the catalog entry exactly — both host-side MUSTs in the SEP. - Rejecting skills whose resources aren't all fetchable/digestible — the SEP says hosts MAY decline skills without a
resourcesmanifest, and OpenAI does.
Differences (host policy or stricter-than-SEP):
skill://treated as required. OpenAI says "use theskill://URI convention." The SEP says servers SHOULD useskill://but MAY serve skills under any scheme, and hosts MUST NOT infer skill-ness from the scheme. If OpenAI's scanner actually rejects non-skill://URIs, that's stricter than the SEP — a conformantgithub://…skills server would fail import. Practical takeaway: stick toskill://(as this demo does) for maximum compatibility.- Import limits are OpenAI policy, not SEP. ≤5 uniquely named skills per scan, ≤10 catalog pages, ≤100 files per skill,
SKILL.md≤256 KiB, supporting files ≤1 MiB, ≤5 MiB per skill — none of these appear in the SEP. A conformant server can exceed them and simply won't import fully. - Archives are OpenAI-internal. Their "generated archives per scan ≤8 MiB" limit refers to post-import packaging on their side. The SEP explicitly removed archive distribution from the wire protocol (see its Deferred Features appendix); no conflict, but worth knowing the archive never crosses the MCP connection.
- Snapshot-only, listing-only. OpenAI imports at scan time and requires re-scanning to pick up changes — consistent with the SEP's point-in-time entries. But an import pipeline only consumes
skills/list, so SEP features aimed at live hosts don't apply: unlisted skills (like this demo'ssecret-menu), the instructions pointer, andresources/directory/readare invisible to OpenAI's importer. Servers targeting OpenAI import should list everything they want imported.
Layout
src/server.ts shared factory: loadSkills() + registerSkillResources()
src/index.ts stdio entry point (serveStdio)
src/http.ts streamable HTTP entry point (createMcpHandler + @modelcontextprotocol/node)
src/checks.ts shared SEP-2640 client-side checks
src/smoke.ts stdio smoke test
src/smoke-http.ts streamable HTTP smoke test
skills/ the demo skills (Agent Skills format: SKILL.md + supporting files)
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。