@ssheleg/make-skill 0.9.0 → 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 +40 -0
- package/package.json +1 -1
- package/plugins/make-skill/.claude-plugin/plugin.json +1 -1
- package/plugins/make-skill/skills/make-skill/SKILL.md +2 -2
- package/plugins/make-skill/skills/make-skill/references/distribution.md +6 -2
- package/plugins/make-skill/skills/make-skill/references/mcp-ship.md +275 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
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
|
+
|
|
30
|
+
## v0.9.1 — 2026-08-05
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- **The release checklist told agents to publish npm by hand.** Step 5 of the
|
|
34
|
+
recurring-release checklist in `distribution.md` said "npm publish if
|
|
35
|
+
applicable (human 2FA the first time)" — contradicting the launch checklist's
|
|
36
|
+
own step 9, which arms CI publishing precisely so that is the *last* manual
|
|
37
|
+
publish. A `v*` tag now publishes; manual publish is named as the fallback
|
|
38
|
+
for a repo that has not armed CI yet.
|
|
39
|
+
- `CONTRIBUTING.md` carried the same stale instruction for the family
|
|
40
|
+
catalogue, and now points at `test/check_pins.py`, the registry comparison
|
|
41
|
+
`sshlg-skills` gained in v0.20.0.
|
|
42
|
+
|
|
3
43
|
## v0.9.0 — 2026-08-03
|
|
4
44
|
|
|
5
45
|
The canon knew the Claude Code capability set and used none of it. This release
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/make-skill",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
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.
|
|
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` |
|
|
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:
|
|
@@ -306,8 +306,12 @@ claude plugin update <name>@<name> # full id required
|
|
|
306
306
|
4. Conventional commit; push; confirm CI `success`; tag `v<ver>` and push the tag;
|
|
307
307
|
`gh release create` from the CHANGELOG section (or let the release workflow
|
|
308
308
|
below do it).
|
|
309
|
-
5. npm
|
|
310
|
-
|
|
309
|
+
5. **npm publishes itself** once CI publishing is armed (launch checklist step
|
|
310
|
+
9): the `v*` tag triggers it, with provenance, skipping a version the
|
|
311
|
+
registry already has. A manual `npm publish` here is the fallback for a repo
|
|
312
|
+
that has not armed it yet — not the normal path, and leaving it as the
|
|
313
|
+
normal path is how a family ends up with a release step nobody performs and
|
|
314
|
+
everybody documents. Then e2e `npx <name>@<ver>` from a non-repo cwd.
|
|
311
315
|
6. **Refresh THIS machine's global installs — always, as Definition of Done:**
|
|
312
316
|
`claude plugin marketplace update <name>` → `claude plugin update
|
|
313
317
|
<name>@<name>` → `npx skills update <name> --global --yes && rm -f
|
|
@@ -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.
|