@ssheleg/agent-stack 0.6.0 → 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.
@@ -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.