@ssheleg/make-skill 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.10.0 — 2026-08-06
4
+
5
+ ### Added
6
+ - **`references/mcp-ship.md`** — the half of MCP work that starts after the
7
+ server compiles. Mounting into an existing FastAPI/Starlette app and the
8
+ **double-path 404** that follows (FastMCP defaults `streamable_http_path` to
9
+ `/mcp/`; mount that at `/mcp` and the real endpoint is `/mcp/mcp`, so every
10
+ client 404s and the SSE fallback 404s too); ASGI auth middleware and why
11
+ handler-level auth is too late; a health endpoint outside the mount, which is
12
+ what separates "app down" from "MCP path wrong" — the two produce identical
13
+ client errors. Then the 404/307/401 bisect, both client config shapes, and
14
+ publication: `server.json` for remote and package servers, the three registry
15
+ name claims (GitHub, DNS, HTTP) with Ed25519 key handling, `mcp-publisher`,
16
+ CI automation, and versioning.
17
+
18
+ This is a deliberate delta, not a duplicate. Anthropic's `mcp-builder` skill
19
+ covers authoring and evaluation well and carries **nothing** on the registry,
20
+ on mounting, or on why a client sees 404 — checked against it on 2026-08-06.
21
+ The reference says so in its own opening so the boundary survives contact
22
+ with whoever reads it next.
23
+
24
+ ### Notes
25
+ - The reference is reachable from the merged MCP row in the reference table.
26
+ `SKILL.md`'s body budget was already at ~4981 of its 5000-token ceiling, so a
27
+ new row did not fit — the validator caught that on the first attempt, which is
28
+ the budget working as designed rather than an obstacle to route around.
29
+
3
30
  ## v0.9.1 — 2026-08-05
4
31
 
5
32
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssheleg/make-skill",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Create, retrofit, audit, and ship agent skills & Claude Code plugins the proven ssheleg way \u2014 conformance to the Agent Skills open standard AND Anthropic's platform rules (front-matter limits, disclosure budgets, per-surface runtime limits, the Skills API, evals) plus the Claude Code plugin reference (manifest schemas, component layout, claude plugin validate --strict), marketplace repo layout, version sync, validator + CI, multi-channel distribution (plugin, vercel skills CLI, npx, Cursor), npm gotchas, the review checklist for third-party skills, and MCP / A2A rules for protocol-connected skills. This package is the installer CLI.",
5
5
  "keywords": [
6
6
  "skill",
@@ -3,7 +3,7 @@
3
3
  "name": "make-skill",
4
4
  "displayName": "Make Skill",
5
5
  "description": "Create, retrofit, audit, and ship agent skills & Claude Code plugins the proven ssheleg way: conformance to the Agent Skills open standard, Anthropic's platform rules (surfaces, Skills API, evals) and the Claude Code plugin reference, marketplace repo layout, version sync, validator + CI, multi-channel distribution (plugin, vercel skills CLI, npx, Cursor), npm gotchas, end-to-end first publish, the review checklist for third-party skills, plus MCP / A2A references for protocol-connected skills.",
6
- "version": "0.9.1",
6
+ "version": "0.10.0",
7
7
  "author": {
8
8
  "name": "ssheleg",
9
9
  "url": "https://x.com/sshlg93"
@@ -5,7 +5,7 @@ license: MIT
5
5
  compatibility: Authoring works on any agent. The bundled scripts/ need python3. Publishing steps need git, gh, node and npm; the plugin gates need the claude CLI. Not usable on the Claude API surface, which has no network and no runtime package install.
6
6
  metadata:
7
7
  author: ssheleg
8
- version: "0.9.1"
8
+ version: "0.10.0"
9
9
  homepage: https://github.com/ssheleg/make-skill
10
10
  ---
11
11
 
@@ -28,7 +28,7 @@ Copy from a working repo (usually `~/DATA/<name>`): **`ssheleg/super-ux`**
28
28
  | `references/host-capabilities.md` | shipping a **hook, subagent, command, script or MCP dependency** — what each buys and costs, hook events/exit codes, and the degradation clauses that keep the skill working without them |
29
29
  | `references/claude-code-plugin.md` | anything shipping as a **Claude Code plugin/marketplace** — manifest schemas, component layout, LSP/monitors, path variables, `validate` failures |
30
30
  | `references/distribution.md` | the repo layout, publishing, channels, releases |
31
- | `references/mcp.md` | the skill calls/wraps/documents an **MCP** server, or you're choosing skill vs server |
31
+ | `references/mcp.md`, `references/mcp-ship.md` | skill vs **MCP** server, wrapping one — then shipping it: mounting, its 404, `server.json`, the registry |
32
32
  | `references/a2a.md` | the skill spans two autonomous agents (**A2A**): Agent Cards, task lifecycle, delegation |
33
33
 
34
34
  Missing from this copy? Raw fallback:
@@ -0,0 +1,275 @@
1
+ # Shipping an MCP server — mount, debug, publish
2
+
3
+ **Load this when:** the server is written and now has to reach someone —
4
+ mounting it inside an existing web app, fixing a client that cannot connect, or
5
+ publishing it to the official MCP Registry.
6
+
7
+ `references/mcp.md` is the author's model of the protocol: primitives,
8
+ lifecycle, transports, security. This file starts where that one stops. If you
9
+ are still designing tools and their schemas, Anthropic's `mcp-builder` skill
10
+ covers authoring and evaluation in depth and this file deliberately does not
11
+ repeat it — **it has nothing on the registry, on mounting, or on why a client
12
+ sees 404, which is exactly what is here.**
13
+
14
+ Registry schema URL and CLI flags move. Read them from
15
+ <https://modelcontextprotocol.io/registry/> and the
16
+ [registry repo](https://github.com/modelcontextprotocol/registry) rather than
17
+ from a copy — the pinned example below was current on **2026-08-06** and the
18
+ schema is dated by design.
19
+
20
+ ## Contents
21
+
22
+ - [Mounting into an existing web app](#mounting-into-an-existing-web-app)
23
+ - [Auth middleware and a health endpoint](#auth-middleware-and-a-health-endpoint)
24
+ - [Client configuration](#client-configuration)
25
+ - [Debugging a client that will not connect](#debugging-a-client-that-will-not-connect)
26
+ - [Publishing to the MCP Registry](#publishing-to-the-mcp-registry)
27
+ - [Registry name formats and who may claim them](#registry-name-formats-and-who-may-claim-them)
28
+ - [Automating publication](#automating-publication)
29
+ - [Versioning](#versioning)
30
+
31
+ ---
32
+
33
+ ## Mounting into an existing web app
34
+
35
+ The common production shape: you already run a FastAPI/Starlette app, and the
36
+ MCP server should live at `/mcp` on the same host. It is also where the single
37
+ most common bug lives.
38
+
39
+ **The double-path pitfall.** FastMCP defaults its internal
40
+ `streamable_http_path` to `/mcp/`. Mount that app at `/mcp` and the real
41
+ endpoint becomes `/mcp/mcp` — so every client pointed at `/mcp` gets 404, and
42
+ the SSE fallback 404s too, which reads like the server is down.
43
+
44
+ ```python
45
+ mcp = FastMCP(
46
+ "my_server",
47
+ streamable_http_path="/", # REQUIRED when mounting as a sub-app
48
+ stateless_http=True,
49
+ json_response=True,
50
+ )
51
+
52
+ app.mount("/mcp", mcp.streamable_http_app())
53
+ ```
54
+
55
+ Set `streamable_http_path="/"` whenever the app is mounted rather than served
56
+ standalone. Standalone servers keep the default.
57
+
58
+ ---
59
+
60
+ ## Auth middleware and a health endpoint
61
+
62
+ Wrap the mounted ASGI app rather than adding auth inside tool handlers — the
63
+ transport handshake happens before any tool runs, so handler-level auth leaves
64
+ the protocol surface open.
65
+
66
+ ```python
67
+ class MCPAuthMiddleware:
68
+ _PUBLIC_PATHS = {"/health"}
69
+
70
+ def __init__(self, app, *, store):
71
+ self._app, self._store = app, store
72
+
73
+ async def __call__(self, scope, receive, send):
74
+ if scope["type"] not in ("http", "websocket"):
75
+ return await self._app(scope, receive, send)
76
+ if scope.get("path", "") in self._PUBLIC_PATHS:
77
+ return await self._app(scope, receive, send)
78
+
79
+ headers = dict(scope.get("headers", []))
80
+ auth = headers.get(b"authorization", b"").decode()
81
+ if not auth.startswith("Bearer "):
82
+ return await self._send_401(send, "Missing Bearer token")
83
+ if not await self._store.validate_api_key(auth[7:]):
84
+ return await self._send_401(send, "Invalid API key")
85
+ return await self._app(scope, receive, send)
86
+
87
+ app.mount("/mcp", MCPAuthMiddleware(mcp.streamable_http_app(), store=key_store))
88
+ ```
89
+
90
+ Keep a health endpoint **outside** the mount and exempt from auth. It is what
91
+ tells you "the app is up, the MCP path is wrong" apart from "the app is down" —
92
+ the two produce identical client errors.
93
+
94
+ ```python
95
+ @app.get("/mcp/health")
96
+ async def mcp_health():
97
+ return {"status": "ok", "server": "my_server", "transport": "streamable-http"}
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Client configuration
103
+
104
+ Remote (streamable HTTP):
105
+
106
+ ```json
107
+ {
108
+ "mcpServers": {
109
+ "my-server": {
110
+ "url": "https://example.com/mcp",
111
+ "headers": { "Authorization": "Bearer ${MY_SERVER_KEY}" }
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ Local (stdio):
118
+
119
+ ```json
120
+ {
121
+ "mcpServers": {
122
+ "my-server": {
123
+ "command": "uv",
124
+ "args": ["--directory", "/path/to/project", "run", "python", "-m", "my_server"],
125
+ "env": { "API_KEY": "..." }
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ Ship both forms in your README. A user who has to derive the config from prose
132
+ files an issue instead.
133
+
134
+ ---
135
+
136
+ ## Debugging a client that will not connect
137
+
138
+ **404 — by far the most common.** Symptom: `Error POSTing to endpoint: Not
139
+ Found`, then the SSE fallback 404s as well. Bisect the path directly:
140
+
141
+ ```bash
142
+ curl -X POST https://your-host/mcp/ -H 'Content-Type: application/json' -d '{}'
143
+ curl -X POST https://your-host/mcp -H 'Content-Type: application/json' -d '{}'
144
+ curl -X POST https://your-host/mcp/mcp -H 'Content-Type: application/json' -d '{}'
145
+ ```
146
+
147
+ If `/mcp/mcp` answers (even 401) while `/mcp/` 404s, it is the double path.
148
+ A 401 is a **success** for this test: it proves routing reached the server.
149
+
150
+ **307.** Starlette redirects `/mcp` → `/mcp/`. Most clients follow it on POST;
151
+ some do not, and the failure looks like a hang. Either document the trailing
152
+ slash or add an explicit no-slash route.
153
+
154
+ **401.** Check the Bearer prefix and the key, then check that the client is
155
+ actually sending headers — several clients drop custom headers on the SSE
156
+ fallback specifically, so the initial POST authenticates and the stream does not.
157
+
158
+ ---
159
+
160
+ ## Publishing to the MCP Registry
161
+
162
+ The registry stores **metadata pointing at your server**, not the server. You
163
+ publish a `server.json`.
164
+
165
+ Remote server:
166
+
167
+ ```json
168
+ {
169
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
170
+ "name": "com.example/my-mcp",
171
+ "title": "My MCP Server",
172
+ "description": "What it does (max 100 chars).",
173
+ "version": "1.0.0",
174
+ "remotes": [
175
+ {
176
+ "type": "streamable-http",
177
+ "url": "https://example.com/mcp",
178
+ "headers": [
179
+ { "name": "Authorization", "description": "Bearer API key",
180
+ "isRequired": true, "isSecret": true }
181
+ ]
182
+ }
183
+ ]
184
+ }
185
+ ```
186
+
187
+ Local package (npm shown; `pypi` takes the same shape with `registryType`
188
+ changed):
189
+
190
+ ```json
191
+ {
192
+ "packages": [
193
+ {
194
+ "registryType": "npm",
195
+ "identifier": "@username/my-mcp-server",
196
+ "version": "1.0.0",
197
+ "transport": { "type": "stdio" }
198
+ }
199
+ ]
200
+ }
201
+ ```
202
+
203
+ PyPI additionally requires `<!-- mcp-name: your-server-name -->` in the package
204
+ README, as proof the package and the registry entry belong to the same author.
205
+
206
+ Validate before you publish — `description` is capped at **100 characters** and
207
+ a version may be published only once:
208
+
209
+ ```bash
210
+ mcp-publisher validate
211
+ mcp-publisher publish
212
+ curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=my-mcp"
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Registry name formats and who may claim them
218
+
219
+ The name is not free-form: it encodes how you proved ownership.
220
+
221
+ | Auth method | Name format | Example |
222
+ |---|---|---|
223
+ | GitHub | `io.github.<user>/*` | `io.github.alice/weather` |
224
+ | DNS | reverse-DNS of the domain | `com.example/my-server` |
225
+ | HTTP | reverse-DNS of the domain | `com.example/my-server` |
226
+
227
+ **GitHub** is the shortest path — `mcp-publisher login github`, OAuth device
228
+ flow, done. The name must start with your username.
229
+
230
+ **DNS** and **HTTP** claim a domain with an Ed25519 key. DNS puts the public key
231
+ in a TXT record; HTTP serves it at
232
+ `https://<domain>/.well-known/mcp-registry-auth`. Use HTTP when you control the
233
+ web root but not the zone.
234
+
235
+ ```bash
236
+ openssl genpkey -algorithm Ed25519 -out mcp-registry-key.pem
237
+ PUBLIC_KEY="$(openssl pkey -in mcp-registry-key.pem -pubout -outform DER | tail -c 32 | base64)"
238
+ # DNS: <domain>. IN TXT "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}"
239
+ # HTTP: echo "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}" > mcp-registry-auth
240
+ ```
241
+
242
+ Treat `mcp-registry-key.pem` as a publishing credential: it is the thing that
243
+ lets someone replace your server entry. It belongs in a secret store, not the
244
+ repository, and the CI job that uses it should be the only consumer.
245
+
246
+ ---
247
+
248
+ ## Automating publication
249
+
250
+ Same shape as the skill releases in `references/distribution.md`: a `v*` tag
251
+ triggers validate → publish. Two rules carry over unchanged.
252
+
253
+ - **Gate the publish on the validator**, in the same job. A registry entry
254
+ pointing at a broken build is worse than no entry, because clients cache it.
255
+ - **Fail on a version already published** rather than skipping quietly. A silent
256
+ skip is how a tag ends up green while the registry still serves the previous
257
+ version.
258
+
259
+ Install the CLI in CI from the released binary:
260
+
261
+ ```bash
262
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Versioning
268
+
269
+ The registry version is the **server's** version, and it is what clients compare
270
+ against. Keep it equal to the package version — an npm package at 1.2.0 whose
271
+ registry entry says 1.0.0 makes every bug report unactionable.
272
+
273
+ The `$schema` date and your version are unrelated: the schema is the registry's
274
+ contract and moves on the registry's schedule, so re-read it before each publish
275
+ rather than carrying the pinned URL forward on faith.