@ssheleg/agent-stack 0.6.1 → 0.7.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 +69 -0
- package/README.md +29 -4
- package/package.json +2 -2
- package/plugins/agent-stack/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-stack/skills/agent-interop/SKILL.md +172 -0
- package/plugins/agent-stack/skills/agent-interop/references/a2a.md +209 -0
- package/plugins/agent-stack/skills/agent-interop/references/gateway.md +161 -0
- package/plugins/agent-stack/skills/agent-interop/references/mcp-scale.md +162 -0
- package/plugins/agent-stack/skills/agent-interop/references/mcp-ship.md +142 -0
- package/plugins/agent-stack/skills/agent-interop/references/mcp.md +241 -0
- package/plugins/agent-stack/skills/agent-interop/references/registry.md +181 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# MCP — the wire, and what moved under it
|
|
2
|
+
|
|
3
|
+
**Load this when:** you are writing an MCP server or client, debugging a handshake, or
|
|
4
|
+
deciding which primitive a capability belongs in.
|
|
5
|
+
|
|
6
|
+
**Spec pinned:** MCP revision `2026-07-28` from `modelcontextprotocol.io/specification/latest` · read 2026-08-13
|
|
7
|
+
|
|
8
|
+
**Re-verify before locking a contract.** Fetch the URL above and check which revision it
|
|
9
|
+
serves now. The rest of this file tells you *what to look for*; it is not the document.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
- What MCP is, and the two layers
|
|
14
|
+
- The change that breaks old code: stateless, and `server/discover`
|
|
15
|
+
- Server primitives — tools, resources, prompts
|
|
16
|
+
- Client primitives — elicitation, and the MRTR pattern
|
|
17
|
+
- The deprecation register
|
|
18
|
+
- Notifications are opt-in now
|
|
19
|
+
- Caching
|
|
20
|
+
- Transports and authorization
|
|
21
|
+
- Extensions
|
|
22
|
+
- Traps
|
|
23
|
+
|
|
24
|
+
## What MCP is, and the two layers
|
|
25
|
+
|
|
26
|
+
MCP connects an **AI application to capability providers**. Three participants: a **host**
|
|
27
|
+
(the AI application), a **client** (one per server, owned by the host), and a **server** (the
|
|
28
|
+
program providing context or capability). One host runs many clients; one remote server
|
|
29
|
+
serves many clients.
|
|
30
|
+
|
|
31
|
+
Two layers, and keeping them apart saves an argument later:
|
|
32
|
+
|
|
33
|
+
| Layer | What it defines |
|
|
34
|
+
|---|---|
|
|
35
|
+
| **Data** | the JSON-RPC 2.0 protocol: discovery, primitives, notifications |
|
|
36
|
+
| **Transport** | the channel, framing and authorization: stdio or Streamable HTTP |
|
|
37
|
+
|
|
38
|
+
The same JSON-RPC message shape rides both transports. A bug is almost always in one layer
|
|
39
|
+
or the other, and naming which one first is half the debugging.
|
|
40
|
+
|
|
41
|
+
## The change that breaks old code: stateless, and `server/discover`
|
|
42
|
+
|
|
43
|
+
This is the single most important paragraph in this file, because every model trained before
|
|
44
|
+
2026-07-28 gets it wrong with total confidence.
|
|
45
|
+
|
|
46
|
+
**MCP is a stateless protocol.** The specification's own summary line reads *"Stateless,
|
|
47
|
+
self-contained requests. Per-request capability negotiation."* There is no session the server
|
|
48
|
+
infers things from. Every request carries what the server needs, in `_meta`:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"jsonrpc": "2.0", "id": 1, "method": "tools/list",
|
|
53
|
+
"params": {
|
|
54
|
+
"_meta": {
|
|
55
|
+
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
|
56
|
+
"io.modelcontextprotocol/clientInfo": { "name": "example-client", "version": "1.0.0" },
|
|
57
|
+
"io.modelcontextprotocol/clientCapabilities": { "elicitation": {} }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**There is no `initialize` handshake.** Discovery is a single request, `server/discover`,
|
|
64
|
+
which every server **must** implement and a client **may** send before anything else:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"jsonrpc": "2.0", "id": 1,
|
|
69
|
+
"result": {
|
|
70
|
+
"resultType": "complete",
|
|
71
|
+
"supportedVersions": ["2026-07-28"],
|
|
72
|
+
"capabilities": { "tools": { "listChanged": true }, "resources": {} },
|
|
73
|
+
"_meta": { "io.modelcontextprotocol/serverInfo": { "name": "example-server", "version": "1.0.0" } },
|
|
74
|
+
"ttlMs": 3600000, "cacheScope": "public"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Because every request is self-describing, **calling `server/discover` is optional**: a client
|
|
80
|
+
may fire any request and handle the error if the version is unacceptable. A server that
|
|
81
|
+
cannot speak the requested version rejects with `UnsupportedProtocolVersionError`, listing
|
|
82
|
+
what it does support; the client retries on a mutually supported one. That error is a normal
|
|
83
|
+
path, not a crash.
|
|
84
|
+
|
|
85
|
+
**What this costs you if you get it wrong:** a client written to open with `initialize` and
|
|
86
|
+
then omit `_meta` from subsequent calls talks to nothing. It fails at the first request, not
|
|
87
|
+
at a subtle edge, which is the one mercy here.
|
|
88
|
+
|
|
89
|
+
## Server primitives — tools, resources, prompts
|
|
90
|
+
|
|
91
|
+
Three, and the useful distinction is **who decides to use each one**:
|
|
92
|
+
|
|
93
|
+
| Primitive | Controlled by | For | Methods |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| **Tools** | the **model** | actions — writes, API calls, queries | `tools/list`, `tools/call` |
|
|
96
|
+
| **Resources** | the **application** | read-only context — files, schemas, records | `resources/list`, `resources/templates/list`, `resources/read` |
|
|
97
|
+
| **Prompts** | the **user** | templated workflows, often surfaced as slash commands | `prompts/list`, `prompts/get` |
|
|
98
|
+
|
|
99
|
+
Getting this wrong is the most common design error in a first server: a "tool" that only
|
|
100
|
+
reads and that the model must be told not to call is a **resource**; a "resource" the model
|
|
101
|
+
is supposed to decide about is a **tool**.
|
|
102
|
+
|
|
103
|
+
**Tools.** `name` (unique within the server; prefer `calculator_arithmetic` over
|
|
104
|
+
`calculate`), `title` (human-readable), `description`, `inputSchema` (JSON Schema), and an
|
|
105
|
+
optional **`outputSchema`** — which is worth providing, because it is what lets a host
|
|
106
|
+
generate typed stubs for programmatic calling (`mcp-scale.md`).
|
|
107
|
+
|
|
108
|
+
**Tool errors do not arrive as transport failures.** A failed tool returns a *successful*
|
|
109
|
+
JSON-RPC response carrying `isError: true`. Client code that only catches transport
|
|
110
|
+
exceptions treats every tool failure as a success containing an apology.
|
|
111
|
+
|
|
112
|
+
**Resources** have a URI and a MIME type. Two discovery shapes: **direct** (`calendar://events/2024`)
|
|
113
|
+
and **templates** (`weather://forecast/{city}/{date}`), where templates carry `uriTemplate`,
|
|
114
|
+
`name`, `title`, `description`, `mimeType` and support parameter completion.
|
|
115
|
+
|
|
116
|
+
**Prompts** are user-invoked, never auto-triggered, and take declared `arguments`.
|
|
117
|
+
|
|
118
|
+
## Client primitives — elicitation, and the MRTR pattern
|
|
119
|
+
|
|
120
|
+
**One survives: elicitation.** Servers request information from the user mid-operation
|
|
121
|
+
instead of failing on missing input, via `elicitation/create`.
|
|
122
|
+
|
|
123
|
+
**Two modes, and the difference is a security rule, not a preference:**
|
|
124
|
+
|
|
125
|
+
- **Form mode** — the server sends a `requestedSchema`; the client renders a form and
|
|
126
|
+
validates the response against it.
|
|
127
|
+
- **URL mode** — the server hands over a URL the user opens. The interaction happens out of
|
|
128
|
+
band; its data never passes through the client or the model's context. The client learns
|
|
129
|
+
only whether the user consented, and **never fetches the URL automatically**.
|
|
130
|
+
|
|
131
|
+
**Servers must not use form mode for passwords, API keys, access tokens or payment
|
|
132
|
+
credentials.** Those belong in URL mode, precisely so the secret never enters the client or
|
|
133
|
+
the LLM context. This is the one rule in this file most likely to be broken by an
|
|
134
|
+
implementation that "just works".
|
|
135
|
+
|
|
136
|
+
**Delivery is the Multi Round-Trip Requests (MRTR) pattern**, and it is not a callback. When
|
|
137
|
+
a server needs input while handling, say, `tools/call`, it answers with an
|
|
138
|
+
`InputRequiredResult` whose `inputRequests` carries the `elicitation/create` request. The
|
|
139
|
+
client gathers input and **retries the original request**, attaching `inputResponses` and
|
|
140
|
+
echoing back any `requestState` the server supplied. A second request id, the same logical
|
|
141
|
+
call.
|
|
142
|
+
|
|
143
|
+
## The deprecation register
|
|
144
|
+
|
|
145
|
+
Deprecated as of `2026-07-28` under the feature-lifecycle policy (SEP-2596). Deprecated means
|
|
146
|
+
*still in the spec, scheduled for removal*: new implementations **SHOULD NOT** adopt it.
|
|
147
|
+
Earliest removal for the 2026-07-28 batch is the first revision released on or after
|
|
148
|
+
**2027-07-28** — later removal is a maintainer decision.
|
|
149
|
+
|
|
150
|
+
| Feature | Deprecated in | Migrate to |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| **Sampling** (`sampling/createMessage`) | `2026-07-28` | integrate directly with LLM provider APIs |
|
|
153
|
+
| **Roots** (`roots/list`) | `2026-07-28` | pass directories/files via tool parameters, resource URIs, or server configuration |
|
|
154
|
+
| **Logging** (`notifications/message`) | `2026-07-28` | `stderr` for stdio; OpenTelemetry for observability |
|
|
155
|
+
| **Dynamic Client Registration** | `2026-07-28` | Client ID Metadata Documents |
|
|
156
|
+
| `includeContext: "thisServer"` / `"allServers"` | `2025-11-25` | omit the field, or `"none"` |
|
|
157
|
+
| **HTTP+SSE transport** | `2025-03-26` | Streamable HTTP |
|
|
158
|
+
|
|
159
|
+
**Why this table matters more than its length suggests.** Sampling and roots were, for a
|
|
160
|
+
year, the most-written-about parts of the client side — so they are exactly what a model
|
|
161
|
+
reaches for first. A server built today around sampling is built on a feature with a
|
|
162
|
+
published removal window.
|
|
163
|
+
|
|
164
|
+
Authoritative and updated: `https://modelcontextprotocol.io/specification/2026-07-28/deprecated`.
|
|
165
|
+
|
|
166
|
+
## Notifications are opt-in now
|
|
167
|
+
|
|
168
|
+
A server does not push because it feels like it. The client opens a long-lived stream with
|
|
169
|
+
`subscriptions/listen`, naming the event types it wants:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{ "method": "subscriptions/listen",
|
|
173
|
+
"params": { "notifications": { "toolsListChanged": true } } }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The server acknowledges with `notifications/subscriptions/acknowledged`, whose `notifications`
|
|
177
|
+
field reflects **the subset it agreed to honor** — unsupported types are simply omitted, so
|
|
178
|
+
read the acknowledgement rather than assuming. Every notification on that stream carries
|
|
179
|
+
`io.modelcontextprotocol/subscriptionId` in `_meta`, matching the JSON-RPC id of the
|
|
180
|
+
`subscriptions/listen` request.
|
|
181
|
+
|
|
182
|
+
Then: `notifications/tools/list_changed`, and for watched resources (requested via the
|
|
183
|
+
`resourceSubscriptions` filter) `notifications/resources/updated`.
|
|
184
|
+
|
|
185
|
+
**Two gates, not one.** A notification arrives only if the client asked *and* the server
|
|
186
|
+
declared the matching capability (`tools: {"listChanged": true}`). Either missing means
|
|
187
|
+
silence.
|
|
188
|
+
|
|
189
|
+
**Delivery is best-effort**, explicitly so, especially across transport reconnects. **Poll as
|
|
190
|
+
well.** A cache refreshed only by notifications goes stale the first time a connection drops
|
|
191
|
+
and never recovers.
|
|
192
|
+
|
|
193
|
+
## Caching
|
|
194
|
+
|
|
195
|
+
List results, `server/discover` and `resources/read` carry `ttlMs` (freshness hint, ms) and
|
|
196
|
+
`cacheScope` (who may reuse it — e.g. `"public"`). Honor them.
|
|
197
|
+
|
|
198
|
+
**One rule beats the TTL:** treat a cached list as stale the moment a `list_changed`
|
|
199
|
+
notification arrives, even if its TTL has not expired.
|
|
200
|
+
|
|
201
|
+
## Transports and authorization
|
|
202
|
+
|
|
203
|
+
| Transport | When | Notes |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| **stdio** | local process on the same machine | no network overhead; typically one client |
|
|
206
|
+
| **Streamable HTTP** | remote | HTTP POST client→server, optional SSE for streaming; typically many clients |
|
|
207
|
+
|
|
208
|
+
Streamable HTTP supports standard HTTP auth — bearer tokens, API keys, custom headers — and
|
|
209
|
+
**MCP recommends OAuth** for obtaining them. Note the deprecation above: Dynamic Client
|
|
210
|
+
Registration is on its way out in favour of Client ID Metadata Documents, so an OAuth
|
|
211
|
+
integration designed around DCR today is designed around a scheduled removal.
|
|
212
|
+
|
|
213
|
+
**HTTP+SSE as a transport is deprecated** (since `2025-03-26`) and is not the same thing as
|
|
214
|
+
Streamable HTTP's optional SSE streaming. Documentation and SDK examples still conflate them.
|
|
215
|
+
|
|
216
|
+
## Extensions
|
|
217
|
+
|
|
218
|
+
Opt-in, negotiated, and worth checking before inventing an equivalent:
|
|
219
|
+
|
|
220
|
+
- **Tasks** — a durable handle for long-running requests: poll for status, supply input
|
|
221
|
+
mid-flight, retrieve the result later. This is the answer to "my tool takes ten minutes",
|
|
222
|
+
and it exists so you do not hold a connection open or invent a job table.
|
|
223
|
+
- **MCP Apps** — interactive UI rendered inline in the conversation.
|
|
224
|
+
- **Skills over MCP** — structured instruction sets discovered and consumed through MCP,
|
|
225
|
+
which is how a server ships Agent Skills rather than only tools.
|
|
226
|
+
|
|
227
|
+
## Traps
|
|
228
|
+
|
|
229
|
+
- **Writing the client against a remembered handshake.** Covered above; it is the big one.
|
|
230
|
+
- **Treating `isError: true` as success.** It arrives inside a 200.
|
|
231
|
+
- **A tool named for the verb, not the domain.** `search` collides the moment a second
|
|
232
|
+
server is connected; `flights_search` does not. Federation makes this expensive later
|
|
233
|
+
(`gateway.md`).
|
|
234
|
+
- **Trusting tool output.** The specification is explicit that tool descriptions and
|
|
235
|
+
annotations are **untrusted unless the server is trusted**. Output from a server is input
|
|
236
|
+
to your agent, and it is attacker-controlled if the server is.
|
|
237
|
+
- **Consent designed as a checkbox.** The spec requires the host to obtain explicit user
|
|
238
|
+
consent before invoking a tool and before exposing user data. A UI that pre-approves
|
|
239
|
+
everything satisfies the letter and loses the point.
|
|
240
|
+
- **Assuming the SDK is current.** SDKs lag revisions. The wire is the contract; the SDK is
|
|
241
|
+
a convenience that may still speak last year's.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# The MCP Registry — publishing a server so people can find it
|
|
2
|
+
|
|
3
|
+
**Load this when:** you are publishing an MCP server, building something that consumes the
|
|
4
|
+
registry, or deciding whether to run a private one.
|
|
5
|
+
|
|
6
|
+
**Spec pinned:** MCP Registry (preview), `server.json` schema `2025-12-11`, API `v0.1` · read 2026-08-13
|
|
7
|
+
|
|
8
|
+
Schema: `https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json`.
|
|
9
|
+
API: `https://registry.modelcontextprotocol.io`.
|
|
10
|
+
|
|
11
|
+
**It says preview and means it.** The documentation warns of breaking changes and data resets
|
|
12
|
+
before general availability. The API is under a declared freeze at **v0.1** with no breaking
|
|
13
|
+
changes expected during stabilization — that is a promise about a window, not about forever.
|
|
14
|
+
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- What it is, and what it is not
|
|
18
|
+
- The three refusals
|
|
19
|
+
- `server.json`
|
|
20
|
+
- Namespaces, and how ownership is proved
|
|
21
|
+
- The publish flow
|
|
22
|
+
- Consuming it
|
|
23
|
+
- Traps
|
|
24
|
+
|
|
25
|
+
## What it is, and what it is not
|
|
26
|
+
|
|
27
|
+
The official centralized **metadata** repository for publicly accessible MCP servers, backed
|
|
28
|
+
by Anthropic, GitHub, PulseMCP and Microsoft.
|
|
29
|
+
|
|
30
|
+
**The split that explains everything else:** package registries — npm, PyPI, Docker Hub —
|
|
31
|
+
host code and binaries. **The MCP Registry hosts metadata pointing at those packages.** A
|
|
32
|
+
`weather-mcp` package lives on npm; the registry maps "weather v1.2.0" to `npm:weather-mcp`.
|
|
33
|
+
|
|
34
|
+
It therefore also delegates **security scanning** downward: to the package registries, which
|
|
35
|
+
already scan, and to downstream aggregators, which may add curation and ratings. The registry
|
|
36
|
+
itself does namespace authentication and metadata hosting. Do not read presence in the
|
|
37
|
+
registry as a safety signal — it is an identity signal.
|
|
38
|
+
|
|
39
|
+
## The three refusals
|
|
40
|
+
|
|
41
|
+
Each of these is a design decision that will otherwise waste a day:
|
|
42
|
+
|
|
43
|
+
1. **No private servers.** A server qualifies if its installation method is publicly
|
|
44
|
+
available (public npm package, public Docker image) *or* the server itself is publicly
|
|
45
|
+
reachable. Something at `mcp.acme-corp.internal`, or behind a private Artifactory, does
|
|
46
|
+
not belong here. **Run your own private registry for those.**
|
|
47
|
+
2. **Not for direct host consumption.** Host applications are told to consume *other*
|
|
48
|
+
registries — downstream marketplaces — via a REST API conforming to the official
|
|
49
|
+
registry's OpenAPI spec. Aggregators are expected to pull on a regular but infrequent
|
|
50
|
+
basis, on the order of hourly.
|
|
51
|
+
3. **Not designed for self-hosting.** The official codebase is explicitly not built for it,
|
|
52
|
+
and the maintainers do not support that use. Forking means operating it yourself,
|
|
53
|
+
indefinitely. What *is* supported: implementing the published OpenAPI spec, so your
|
|
54
|
+
private registry gets existing host support for free.
|
|
55
|
+
|
|
56
|
+
## `server.json`
|
|
57
|
+
|
|
58
|
+
Generated by `mcp-publisher init` and edited by hand:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
63
|
+
"name": "io.github.my-username/weather",
|
|
64
|
+
"description": "An MCP server for weather information.",
|
|
65
|
+
"repository": { "url": "https://github.com/my-username/mcp-weather-server", "source": "github" },
|
|
66
|
+
"version": "1.0.1",
|
|
67
|
+
"packages": [
|
|
68
|
+
{
|
|
69
|
+
"registryType": "npm",
|
|
70
|
+
"identifier": "@my-username/mcp-weather-server",
|
|
71
|
+
"version": "1.0.1",
|
|
72
|
+
"transport": { "type": "stdio" }
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`environmentVariables` entries carry `name`, `description`, `isRequired`, `format` and
|
|
79
|
+
`isSecret` — the last one is how a host knows not to print it.
|
|
80
|
+
|
|
81
|
+
**The `$schema` date and your `version` are unrelated.** One is the registry's schema
|
|
82
|
+
revision; the other is your server's. They will never match and are not supposed to.
|
|
83
|
+
|
|
84
|
+
**Keep the registry version equal to the package version.** An npm package at 1.2.0 whose
|
|
85
|
+
registry entry says 1.0.0 makes every bug report unactionable, because nobody can tell which
|
|
86
|
+
artifact the reporter ran.
|
|
87
|
+
|
|
88
|
+
## Namespaces, and how ownership is proved
|
|
89
|
+
|
|
90
|
+
Names are **reverse-DNS**, and the prefix is a claim you must be able to prove:
|
|
91
|
+
|
|
92
|
+
| Name shape | Proved by |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `io.github.username/server` | GitHub login as that user, or GitHub OIDC from that repo's Actions |
|
|
95
|
+
| `com.example/server` | DNS challenge, or HTTP challenge, on that domain |
|
|
96
|
+
|
|
97
|
+
Four authentication methods: **GitHub OAuth**, **GitHub OIDC** (for publishing from CI),
|
|
98
|
+
**DNS verification**, **HTTP verification**.
|
|
99
|
+
|
|
100
|
+
**GitHub is the shortest path** — `mcp-publisher login github`, device flow, done; the name
|
|
101
|
+
must start with your username.
|
|
102
|
+
|
|
103
|
+
**DNS and HTTP claim a domain with an Ed25519 key.** DNS puts the public key in a TXT
|
|
104
|
+
record; HTTP serves it at `https://<domain>/.well-known/mcp-registry-auth`. Use HTTP when
|
|
105
|
+
you control the web root but not the zone.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
openssl genpkey -algorithm Ed25519 -out mcp-registry-key.pem
|
|
109
|
+
PUBLIC_KEY="$(openssl pkey -in mcp-registry-key.pem -pubout -outform DER | tail -c 32 | base64)"
|
|
110
|
+
# DNS: <domain>. IN TXT "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}"
|
|
111
|
+
# HTTP: echo "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}" > mcp-registry-auth
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**`mcp-registry-key.pem` is a publishing credential** — it is the thing that lets someone
|
|
115
|
+
replace your server entry. Secret store, not the repository, and the CI job that uses it
|
|
116
|
+
should be its only consumer.
|
|
117
|
+
|
|
118
|
+
**Package-side verification is separate and easy to forget.** For npm, `package.json` must
|
|
119
|
+
carry an `mcpName` property, and it must equal the `name` in `server.json`. This is what ties
|
|
120
|
+
the metadata to the artifact; without it the registry rejects the publish with *"Registry
|
|
121
|
+
validation failed for package"*.
|
|
122
|
+
|
|
123
|
+
## The publish flow
|
|
124
|
+
|
|
125
|
+
Metadata points at an artifact, so **the artifact ships first**:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm publish --access public # 1. the package must exist
|
|
129
|
+
|
|
130
|
+
brew install mcp-publisher # 2. or download the release binary
|
|
131
|
+
mcp-publisher init # 3. writes server.json from your project
|
|
132
|
+
mcp-publisher login github # 4. device-code flow, prints a code
|
|
133
|
+
mcp-publisher publish # 5.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Verify it landed, rather than trusting the success line:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.my-username/weather"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
For CI, GitHub OIDC removes the interactive device-code step — that is the whole reason it
|
|
143
|
+
exists as a separate method. Install the CLI in CI from the released binary:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
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
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Two rules for an automated publish**, both learned the expensive way:
|
|
150
|
+
|
|
151
|
+
- **Gate the publish on the validator, in the same job.** A registry entry pointing at a
|
|
152
|
+
broken build is worse than no entry, because clients cache it.
|
|
153
|
+
- **Fail on a version already published** rather than skipping quietly. A silent skip is how
|
|
154
|
+
a tag goes green while the registry still serves the previous version.
|
|
155
|
+
|
|
156
|
+
**Failure modes worth recognizing on sight:**
|
|
157
|
+
|
|
158
|
+
| Message | Cause |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `Registry validation failed for package` | `mcpName` missing from `package.json`, or not equal to `server.json`'s `name` |
|
|
161
|
+
| `Invalid or expired Registry JWT token` | re-run `mcp-publisher login github` |
|
|
162
|
+
| `You do not have permission to publish this server` | the namespace does not match the auth method — GitHub auth requires `io.github.<your-user>/` |
|
|
163
|
+
|
|
164
|
+
## Consuming it
|
|
165
|
+
|
|
166
|
+
If you are building a marketplace or an aggregator: pull on a schedule, not per request; the
|
|
167
|
+
metadata is **deliberately unopinionated**, so ratings, curation and trust signals are yours
|
|
168
|
+
to add. If you are building a host application: consume an aggregator that implements the
|
|
169
|
+
OpenAPI spec rather than the official registry directly — that is the stated topology.
|
|
170
|
+
|
|
171
|
+
## Traps
|
|
172
|
+
|
|
173
|
+
- **Publishing metadata before the artifact.** The registry validates against a package that
|
|
174
|
+
must already exist.
|
|
175
|
+
- **A namespace you cannot prove.** Pick it after you know which auth method you will use,
|
|
176
|
+
not before.
|
|
177
|
+
- **Version drift between `package.json`, `server.json` and the published package.** Three
|
|
178
|
+
places, one number.
|
|
179
|
+
- **Reading registry presence as vetting.** It proves who published, not that it is safe.
|
|
180
|
+
- **Planning to self-host the official codebase.** Implement the OpenAPI spec instead.
|
|
181
|
+
- **Building on preview guarantees.** Data resets are explicitly on the table.
|