@wakeshift/sdk 0.5.2 → 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 +30 -0
- package/README.md +133 -2
- package/dist/discover.d.ts +2 -2
- package/dist/discover.js +67 -47
- package/dist/errors.js +0 -1
- package/dist/events.d.ts +13 -9
- package/dist/events.js +90 -88
- package/dist/harness.js +0 -1
- package/dist/host.d.ts +4 -0
- package/dist/host.js +380 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +0 -1
- package/dist/key.js +0 -1
- package/dist/mastra.d.ts +79 -0
- package/dist/mastra.js +248 -0
- package/dist/mcp.d.ts +92 -0
- package/dist/mcp.js +21 -0
- package/dist/retry.js +0 -1
- package/dist/spec.js +0 -1
- package/dist/tasks.js +50 -39
- package/dist/toolcall.d.ts +72 -24
- package/dist/toolcall.js +103 -72
- package/dist/wakeshift.d.ts +7 -3
- package/dist/wakeshift.js +21 -12
- package/dist/xmcp.d.ts +73 -0
- package/dist/xmcp.js +125 -0
- package/dist/zod.d.ts +16 -0
- package/dist/zod.js +94 -0
- package/package.json +36 -5
- package/dist/discover.js.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/harness.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/key.js.map +0 -1
- package/dist/retry.js.map +0 -1
- package/dist/spec.js.map +0 -1
- package/dist/tasks.js.map +0 -1
- package/dist/toolcall.js.map +0 -1
- package/dist/wakeshift.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
What changed in each release of `@wakeshift/sdk`. 0.4.0 is the first published release: to start with it, read the [README](README.md). Its notes below, and the earlier releases', are for apps that ran the preview builds.
|
|
4
4
|
|
|
5
|
+
## 0.7.0 (2026-10-31)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Mastra.** `@wakeshift/sdk/mastra` serves Wakeshift from a Mastra `MCPServer` (`@mastra/mcp` 2.7 or later): `attachMastra(wake, opts)`, then `attached.inputs` and `attached.events` for the Mastra tools that subscribe, and `attached.serve(new MCPServer({ … }))`. Everything `attach` gives an `McpServer` works there: Wakeshift's block (text and `_meta`) in your wrapped tools' results, the built-in tools, MCP Events, Claude Code's `wakeshift_wait`, and who's calling (`identify` gets the request's `ctx`). Mastra's own MCP Events keep working beside Wakeshift's: `events/list` lists both. See [Mastra](README.md#mastra).
|
|
10
|
+
- **xmcp.** `@wakeshift/sdk/xmcp` brings Wakeshift into an xmcp app through its tool files: `attachXmcp(wake, opts)`, then `attached.inputs` and `attached.events` in the tools that subscribe, and `attached.tool("subscribe_events")` (and the other two) for a tool file each. xmcp offers no way to add MCP methods, so clients that subscribe natively (ChatGPT) and Claude Code's `wakeshift_wait` aren't served there; Claude Code and every other agent are woken their usual ways. See [xmcp](README.md#xmcp).
|
|
11
|
+
- Both are subpath exports, so an app that uses neither loads neither, and `@mastra/mcp` and `xmcp` are optional peer dependencies.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **A smaller package.** It no longer ships source maps (`dist/*.js.map`), which carried the SDK's TypeScript source inline: about half the unpacked size. Stack traces point at the compiled JavaScript in `dist/`, which is readable as it is.
|
|
16
|
+
- **A clearer error from `attach`.** Given something other than an `McpServer`, `attach` says what it got and what it takes, and, for a framework it recognizes (Mastra's `MCPServer`, punkpeye's `fastmcp`, a low-level `Server`, a Cloudflare `McpAgent`), what to do instead (for Mastra, `attachMastra`), with a link to [the frameworks Wakeshift works with](https://wakeshift.ai/docs/frameworks). It says to call `attach` before connecting only when the server is already connected.
|
|
17
|
+
|
|
18
|
+
## 0.6.0 (2026-10-31)
|
|
19
|
+
|
|
20
|
+
### Breaking
|
|
21
|
+
|
|
22
|
+
- Both MCP SDKs are optional peer dependencies: install the one you use. npm no longer installs `@modelcontextprotocol/sdk` for you, so list it in your app's own dependencies if your code imports it (as the install line has always said).
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`@modelcontextprotocol/server` 2.x.** `attach` now takes an `McpServer` from the MCP TypeScript SDK 2.x (2.0 or later) as well as from `@modelcontextprotocol/sdk` 1.x, and tells them apart by the server you pass. Everything works the same on both: wrapped tools (Wakeshift's block in text and in `structuredContent`, rollback, `call.subscribe`), the built-in tools, MCP Events (`events/list`, `events/subscribe`, `events/unsubscribe`, `events/poll`), Claude Code's `wakeshift_wait` with `tasks/get` and `tasks/cancel`, and who's calling. 2.x speaks protocol 2026-07-28 itself, so with `createMcpHandler` clients on either revision connect with nothing more to set up. See [Using @modelcontextprotocol/server 2.x](README.md#using-modelcontextprotocolserver-2x).
|
|
27
|
+
- On 2.x, `identify`, `events.native` and a wrapped tool's `call.extra` get 2.x's request context (`ctx`: the token in `ctx.http?.authInfo`), and are typed as it. On 1.x they get `extra`, as before.
|
|
28
|
+
- `attached.inputs` and `attached.output` take a zod object as well as a shape (`attached.inputs(z.object({ table: z.string() }))`), as 2.x's `registerTool` does. Shapes work as before.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- The SDK's published types no longer import either MCP SDK, so they load with only one installed. `attached.events` returns a callback typed from your tool's arguments and your MCP SDK's context; `ToolCallExtra` is now the parts of a request's context Wakeshift reads, whichever SDK serves it (a wrapped tool's `call.extra` is still your MCP SDK's own type). If you passed a `ToolCallExtra` where 1.x's `RequestHandlerExtra` is expected, use `call.extra` instead.
|
|
33
|
+
- A JSON-RPC error from `attach`'s own methods (for example `events/subscribe` for an event your app doesn't offer) carries just its message, without the MCP SDK 1.x's `MCP error <code>: ` prefix in front of it.
|
|
34
|
+
|
|
5
35
|
## 0.5.2 (2026-10-30)
|
|
6
36
|
|
|
7
37
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Let agents subscribe to things in your app. You define the events your app offers, serve them on your MCP server, and emit them when they happen; Wakeshift wakes every subscribed agent, whether it's Claude Code, Codex, ChatGPT or another. Your events follow MCP Events: clients that speak it subscribe natively, and Wakeshift bridges every other agent. You don't deal with harnesses.
|
|
4
4
|
|
|
5
|
-
Node 22 or later. The package is ESM-only: load it with `import`, from an ES module (`"type": "module"`, or a `.mjs` or `.mts` file). It has no CommonJS build, so `require("@wakeshift/sdk")` works only where Node can require ES modules.
|
|
5
|
+
Node 22 or later. The package is ESM-only: load it with `import`, from an ES module (`"type": "module"`, or a `.mjs` or `.mts` file). It has no CommonJS build, so `require("@wakeshift/sdk")` works only where Node can require ES modules. It works with either MCP TypeScript SDK: `@modelcontextprotocol/sdk` 1.x (1.23 or later: the first that takes zod 4) or `@modelcontextprotocol/server` 2.x ([below](#using-modelcontextprotocolserver-2x)). Its peer dependencies are the one you use and `zod` (3.25 or later, or 4; 4.2 or later with 2.x). Import `z` from `"zod"`, not `"zod/v4"`: the SDK's schemas are built from `"zod"`, and the MCP SDK refuses a schema that mixes the two.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,6 +10,8 @@ Node 22 or later. The package is ESM-only: load it with `import`, from an ES mod
|
|
|
10
10
|
npm install @wakeshift/sdk @modelcontextprotocol/sdk zod
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
On `@modelcontextprotocol/server` 2.x, install that instead of `@modelcontextprotocol/sdk`.
|
|
14
|
+
|
|
13
15
|
Install the package, not a checkout of this repository (a `file:` dependency): a `file:` dependency links the directory along with its `node_modules`, so the SDK can load its own development copy of zod instead of yours, and the MCP SDK then fails with "Mixed Zod versions detected in object shape". To try a change of your own, `npm pack` and install the tarball it writes.
|
|
14
16
|
|
|
15
17
|
## Quickstart: a CI bot
|
|
@@ -153,6 +155,134 @@ server.registerTool(
|
|
|
153
155
|
|
|
154
156
|
A wrapped tool that subscribes to nothing, like `look`, still gets what Wakeshift has for the conversation calling it: occurrences held for an agent Wakeshift can't wake, or what it needs to reconnect. Usually that's nothing.
|
|
155
157
|
|
|
158
|
+
## Using @modelcontextprotocol/server 2.x
|
|
159
|
+
|
|
160
|
+
`attach` takes an `McpServer` from either MCP TypeScript SDK: `@modelcontextprotocol/sdk` 1.x or `@modelcontextprotocol/server` 2.x (2.0 or later). It tells them apart by the server you pass, and everything on this page works the same on both.
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
npm install @wakeshift/sdk @modelcontextprotocol/server @modelcontextprotocol/node zod
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
2.x serves HTTP with `createMcpHandler`, which makes a server for each request, so attach in the function that makes it. Define your events once, outside it:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { createServer } from "node:http"
|
|
170
|
+
import { McpServer, createMcpHandler } from "@modelcontextprotocol/server"
|
|
171
|
+
import { toNodeHandler } from "@modelcontextprotocol/node"
|
|
172
|
+
import { z } from "zod"
|
|
173
|
+
import { Wakeshift } from "@wakeshift/sdk"
|
|
174
|
+
|
|
175
|
+
const wake = new Wakeshift({ key: process.env.WAKESHIFT_KEY! })
|
|
176
|
+
|
|
177
|
+
const ciFinished = wake.defineEvent("ci.finished", {
|
|
178
|
+
description: "CI finished on a GitHub repo",
|
|
179
|
+
args: z.object({ owner: z.string(), repo: z.string() }),
|
|
180
|
+
payload: z.object({ branch: z.string(), conclusion: z.string() }),
|
|
181
|
+
text: (run, { owner, repo }) => `CI ${run.conclusion} on ${owner}/${repo} ${run.branch}`,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
const mcp = createMcpHandler(() => {
|
|
185
|
+
const server = new McpServer({ name: "ci-bot", version: "1.0.0" })
|
|
186
|
+
// identify gets 2.x's request context: your auth middleware's verified token is ctx.http?.authInfo.
|
|
187
|
+
const attached = wake.attach(server, { identify: (ctx) => ({ user: myUser(ctx.http?.authInfo) }) })
|
|
188
|
+
server.registerTool(
|
|
189
|
+
"watch_ci",
|
|
190
|
+
{ description: "Watch CI on a repo", inputSchema: attached.inputs(z.object({ owner: z.string(), repo: z.string() })) },
|
|
191
|
+
attached.events("watch_ci", async ({ owner, repo }, call) => {
|
|
192
|
+
await call.subscribe(ciFinished, { owner, repo }, { reason: `Watching ${owner}/${repo}` })
|
|
193
|
+
return `Watching CI on ${owner}/${repo}.`
|
|
194
|
+
}),
|
|
195
|
+
)
|
|
196
|
+
return server
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
createServer(toNodeHandler(mcp)).listen(3000)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
What's different on 2.x:
|
|
203
|
+
|
|
204
|
+
- `identify`, `events.native` and a wrapped tool's `call.extra` get 2.x's request context (`ctx`) instead of 1.x's `extra`: the verified token is `ctx.http?.authInfo`, and the HTTP request is `ctx.http?.req`.
|
|
205
|
+
- `attached.inputs` and `attached.output` take a zod object, as 2.x's `registerTool` does: `attached.inputs(z.object({ … }))`. A bare shape works too.
|
|
206
|
+
- Write your schemas, events' included, with zod 4 (4.2 or later), which 2.x requires.
|
|
207
|
+
- 2.x speaks MCP protocol 2026-07-28 itself, and `createMcpHandler` answers older clients too, so clients on either revision connect with nothing more to set up. Wakeshift lists its tools for each request's client, so `tools/list` is cached by that client only, whatever cache hint you set for it.
|
|
208
|
+
|
|
209
|
+
## Mastra
|
|
210
|
+
|
|
211
|
+
Mastra's `MCPServer` (`@mastra/mcp` 2.7 or later) builds its MCP server itself, so it has its own entry point, `@wakeshift/sdk/mastra` (0.7.0 or later). Wrap the `execute` of each Mastra tool that subscribes, and serve your `MCPServer` with it:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { createTool } from "@mastra/core/tools"
|
|
215
|
+
import { MCPServer } from "@mastra/mcp"
|
|
216
|
+
import { z } from "zod"
|
|
217
|
+
import { Wakeshift } from "@wakeshift/sdk"
|
|
218
|
+
import { attachMastra } from "@wakeshift/sdk/mastra"
|
|
219
|
+
|
|
220
|
+
const wake = new Wakeshift({ key: process.env.WAKESHIFT_KEY! })
|
|
221
|
+
const ciFinished = wake.defineEvent("ci.finished", {
|
|
222
|
+
description: "CI finished on a GitHub repo",
|
|
223
|
+
args: z.object({ owner: z.string(), repo: z.string() }),
|
|
224
|
+
payload: z.object({ branch: z.string(), conclusion: z.string() }),
|
|
225
|
+
text: (run, { owner, repo }) => `CI ${run.conclusion} on ${owner}/${repo} ${run.branch}`,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// identify gets the request's MCP context: the verified token is ctx.http?.authInfo.
|
|
229
|
+
const attached = attachMastra(wake, { identify: (ctx) => ({ user: myUser(ctx.http?.authInfo) }) })
|
|
230
|
+
|
|
231
|
+
const watchCi = createTool({
|
|
232
|
+
id: "watch_ci",
|
|
233
|
+
description: "Watch CI on a repo",
|
|
234
|
+
inputSchema: attached.inputs(z.object({ owner: z.string(), repo: z.string() })),
|
|
235
|
+
execute: attached.events("watch_ci", async ({ owner, repo }: { owner: string; repo: string }, call) => {
|
|
236
|
+
await call.subscribe(ciFinished, { owner, repo }, { reason: `Watching ${owner}/${repo}` })
|
|
237
|
+
return `Watching CI on ${owner}/${repo}.`
|
|
238
|
+
}),
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
const server = attached.serve(new MCPServer({ id: "ci-bot", name: "CI bot", version: "1.0.0", tools: { watch_ci: watchCi } }))
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Serve it as Mastra's docs do (`server.startHTTP`, `server.startStdio`, or registered on your `Mastra` instance). It gets everything `attach` gives: the built-in tools, MCP Events for clients that speak it, Claude Code's `wakeshift_wait`, and Wakeshift's addition in your wrapped tools' results.
|
|
245
|
+
|
|
246
|
+
- `attached.events(name, handler)` wraps a tool's `execute` for the tool your `MCPServer` serves under `name` (its key in `tools`). `handler(args, call, context)` returns what a Mastra tool returns: a string, or your output. Run outside an MCP client's call (by an agent, a workflow or Studio), `call.subscribe` throws.
|
|
247
|
+
- Wrapped tools answer in one call: a Mastra task or a request for input from one ends what it subscribed.
|
|
248
|
+
- Your own MCP Events (`MCPServer`'s `events`) keep working: `events/list` lists yours and Wakeshift's, and each request goes to whichever serves the event it names.
|
|
249
|
+
- `attached.serve` checks your `@mastra/mcp` version and throws for one outside 2.7–2.x.
|
|
250
|
+
|
|
251
|
+
## xmcp
|
|
252
|
+
|
|
253
|
+
xmcp builds its MCP server from your tool files, so Wakeshift comes in through them, with `@wakeshift/sdk/xmcp` (0.7.0 or later):
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { z } from "zod"
|
|
257
|
+
import { Wakeshift } from "@wakeshift/sdk"
|
|
258
|
+
import { attachXmcp } from "@wakeshift/sdk/xmcp"
|
|
259
|
+
|
|
260
|
+
// src/wake.ts
|
|
261
|
+
const wake = new Wakeshift({ key: process.env.WAKESHIFT_KEY! })
|
|
262
|
+
const ciFinished = wake.defineEvent("ci.finished", {
|
|
263
|
+
description: "CI finished on a GitHub repo",
|
|
264
|
+
args: z.object({ owner: z.string(), repo: z.string() }),
|
|
265
|
+
payload: z.object({ branch: z.string(), conclusion: z.string() }),
|
|
266
|
+
text: (run, { owner, repo }) => `CI ${run.conclusion} on ${owner}/${repo} ${run.branch}`,
|
|
267
|
+
})
|
|
268
|
+
// identify gets xmcp's tool context: the verified token is extra.authInfo.
|
|
269
|
+
const attached = attachXmcp(wake, { identify: (extra) => ({ user: myUser(extra.authInfo) }) })
|
|
270
|
+
|
|
271
|
+
// src/tools/watch_ci.ts: export these three
|
|
272
|
+
const schema = attached.inputs({ owner: z.string(), repo: z.string() })
|
|
273
|
+
const metadata = { name: "watch_ci", description: "Watch CI on a repo" }
|
|
274
|
+
const handler = attached.events("watch_ci", async ({ owner, repo }: { owner: string; repo: string }, call) => {
|
|
275
|
+
await call.subscribe(ciFinished, { owner, repo }, { reason: `Watching ${owner}/${repo}` })
|
|
276
|
+
return `Watching CI on ${owner}/${repo}.`
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
// src/tools/subscribe_events.ts, and the same for unsubscribe_events and list_subscriptions
|
|
280
|
+
const tool = attached.tool("subscribe_events")
|
|
281
|
+
// export const schema = tool.schema; export const metadata = tool.metadata; export default tool.handler
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Each wrapped tool's result gets Wakeshift's addition in its content and `_meta` (not in `structuredContent`, which xmcp checks strictly against your output schema). xmcp can't serve MCP methods of an app's own, so on xmcp there are no MCP Events (ChatGPT and other clients that subscribe natively can't subscribe) and no `wakeshift_wait`: Claude Code is woken its other ways, and every other agent as usual.
|
|
285
|
+
|
|
156
286
|
## Reference
|
|
157
287
|
|
|
158
288
|
### `new Wakeshift({ key, core?, fetch?, timeoutMs?, onWarning? })`
|
|
@@ -201,7 +331,8 @@ The guides and the full reference are at [wakeshift.ai/docs](https://wakeshift.a
|
|
|
201
331
|
```sh
|
|
202
332
|
npm ci
|
|
203
333
|
npm run typecheck
|
|
204
|
-
npm test # unit tests (node:test) and the README's examples
|
|
334
|
+
npm test # unit tests (node:test) on @modelcontextprotocol/sdk 1.x, and the README's examples
|
|
335
|
+
npm run test:v2 # the same unit tests on @modelcontextprotocol/server 2.x
|
|
205
336
|
npm run build # dist/
|
|
206
337
|
```
|
|
207
338
|
|
package/dist/discover.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import { STATELESS_VERSION } from "./host.js";
|
|
2
|
+
export { STATELESS_VERSION };
|
|
3
3
|
/** The MCP tasks extension's capability key. */
|
|
4
4
|
export declare const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
package/dist/discover.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
// Protocol 2026-07-28
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Protocol 2026-07-28. MCP Events is defined only for 2026-07-28 and later,
|
|
2
|
+
// and some clients speak nothing older (OpenClaw).
|
|
3
|
+
//
|
|
4
|
+
// @modelcontextprotocol/server 2.x speaks it itself: server/discover,
|
|
5
|
+
// resultType, and ttlMs and cacheScope on the lists. attach only varies
|
|
6
|
+
// tools/list per request (below), so it marks that list private.
|
|
7
|
+
//
|
|
8
|
+
// @modelcontextprotocol/sdk 1.x doesn't. With attach, a 1.x server whose
|
|
4
9
|
// transport is stateless (no sessions) also answers those clients:
|
|
5
10
|
//
|
|
6
11
|
// - server/discover, with the server's capabilities (the events keys
|
|
@@ -10,36 +15,37 @@
|
|
|
10
15
|
// - resultType: "complete" on every result of a 2026-07-28 request, as
|
|
11
16
|
// that revision requires (clients on the official 2.x SDKs refuse a
|
|
12
17
|
// result without it), and ttlMs and cacheScope on server/discover and
|
|
13
|
-
// the lists (Claude Code connects the old way without them)
|
|
14
|
-
// - tools/list without subscribe_events for a request that declares the
|
|
15
|
-
// events extension in its own capabilities (2026-07-28 has no
|
|
16
|
-
// initialize, so each request carries them).
|
|
18
|
+
// the lists (Claude Code connects the old way without them).
|
|
17
19
|
//
|
|
18
|
-
// A stateful transport refuses a request without a session before any
|
|
20
|
+
// A stateful 1.x transport refuses a request without a session before any
|
|
19
21
|
// handler runs, so a server with sessions is unchanged: its clients fall
|
|
20
|
-
// back to initialize, as they do today.
|
|
21
|
-
//
|
|
22
|
+
// back to initialize, as they do today.
|
|
23
|
+
//
|
|
24
|
+
// On both, tools/list leaves out subscribe_events for a request that
|
|
25
|
+
// declares the events extension in its own capabilities (2026-07-28 has no
|
|
26
|
+
// initialize, so each request carries them).
|
|
22
27
|
import { z } from "zod";
|
|
23
|
-
import { SUPPORTED_PROTOCOL_VERSIONS } from "@modelcontextprotocol/sdk/types.js";
|
|
24
28
|
import { EXTENSION_KEY, RESULT_TYPE } from "./spec.js";
|
|
25
|
-
|
|
26
|
-
export
|
|
27
|
-
const VERSION_META = "io.modelcontextprotocol/protocolVersion";
|
|
29
|
+
import { STATELESS_VERSION, is2026, v1Req } from "./host.js";
|
|
30
|
+
export { STATELESS_VERSION };
|
|
28
31
|
const CAPABILITIES_META = "io.modelcontextprotocol/clientCapabilities";
|
|
29
32
|
const SERVER_INFO_META = "io.modelcontextprotocol/serverInfo";
|
|
33
|
+
// The MCP SDK 1.x's list of supported versions, which its transport checks
|
|
34
|
+
// the MCP-Protocol-Version header against: the same module the app's own
|
|
35
|
+
// copy of the SDK loaded (loaded only if it's installed).
|
|
36
|
+
const v1Types = await import(
|
|
37
|
+
/* webpackIgnore: true */ String("@modelcontextprotocol/sdk/types.js")).catch(() => undefined);
|
|
30
38
|
// The list methods whose 2026-07-28 results carry ttlMs and cacheScope.
|
|
31
39
|
const LISTS = new Set(["tools/list", "prompts/list", "resources/list", "resources/templates/list"]);
|
|
32
|
-
// is2026 reports whether a request is a 2026-07-28 one: its _meta names the
|
|
33
|
-
// version, or its MCP-Protocol-Version header does.
|
|
34
|
-
function is2026(request, extra) {
|
|
35
|
-
if (request?.params?._meta?.[VERSION_META] === STATELESS_VERSION)
|
|
36
|
-
return true;
|
|
37
|
-
const h = extra?.requestInfo?.headers;
|
|
38
|
-
const v = h?.["mcp-protocol-version"] ?? h?.["MCP-Protocol-Version"];
|
|
39
|
-
return (Array.isArray(v) ? v[0] : v) === STATELESS_VERSION;
|
|
40
|
-
}
|
|
41
40
|
/** The MCP tasks extension's capability key. */
|
|
42
41
|
export const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
42
|
+
// reqOf reads meta, and extra's headers (an already-read request's, or
|
|
43
|
+
// 1.x's requestInfo), as one request.
|
|
44
|
+
function reqOf(meta, extra) {
|
|
45
|
+
if (extra && typeof extra.header === "function")
|
|
46
|
+
return { ...extra, meta };
|
|
47
|
+
return v1Req({ ...(extra ?? {}), _meta: meta });
|
|
48
|
+
}
|
|
43
49
|
/**
|
|
44
50
|
* @internal Whether a request (its _meta, as a tool callback's extra has
|
|
45
51
|
* it, and its headers) is protocol 2026-07-28 and declares the tasks
|
|
@@ -47,35 +53,45 @@ export const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
|
47
53
|
* (tasks.ts).
|
|
48
54
|
*/
|
|
49
55
|
export function takesTasks(meta, extra) {
|
|
50
|
-
if (!is2026(
|
|
56
|
+
if (!is2026(reqOf(meta, extra)))
|
|
51
57
|
return false;
|
|
52
58
|
const ext = meta?.[CAPABILITIES_META]?.extensions;
|
|
53
59
|
return !!ext && typeof ext === "object" && !Array.isArray(ext) && TASKS_EXTENSION in ext;
|
|
54
60
|
}
|
|
55
|
-
function declaresEvents(
|
|
56
|
-
const ext =
|
|
61
|
+
function declaresEvents(meta) {
|
|
62
|
+
const ext = meta?.[CAPABILITIES_META]?.extensions;
|
|
57
63
|
return !!ext && typeof ext === "object" && EXTENSION_KEY in ext;
|
|
58
64
|
}
|
|
59
65
|
/** @internal Makes server answer 2026-07-28 requests (see the file's comment). */
|
|
60
|
-
export function serveStateless(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!versions.includes(STATELESS_VERSION))
|
|
65
|
-
versions.unshift(STATELESS_VERSION);
|
|
66
|
-
const low = server.server;
|
|
67
|
-
const handlers = low._requestHandlers;
|
|
68
|
-
if (!(handlers instanceof Map))
|
|
69
|
-
return; // a future SDK without it: it speaks 2026-07-28 itself
|
|
66
|
+
export function serveStateless(host, opts = {}) {
|
|
67
|
+
const handlers = host.handlers();
|
|
68
|
+
if (!handlers)
|
|
69
|
+
return; // a future SDK without it: nothing to vary
|
|
70
70
|
const hidden = new Set(opts.bridgedOnly ?? []);
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const v1 = host.version === 1;
|
|
72
|
+
if (v1) {
|
|
73
|
+
// Mutating the SDK's own list is how 1.x can be taught a version.
|
|
74
|
+
const versions = v1Types?.SUPPORTED_PROTOCOL_VERSIONS;
|
|
75
|
+
if (Array.isArray(versions) && !versions.includes(STATELESS_VERSION))
|
|
76
|
+
versions.unshift(STATELESS_VERSION);
|
|
77
|
+
}
|
|
78
|
+
const wrap = (method, h) => (async (request, raw) => {
|
|
79
|
+
const result = await h(request, raw);
|
|
80
|
+
const req = host.req(raw);
|
|
81
|
+
if (!is2026(req) || !result || typeof result !== "object")
|
|
74
82
|
return result;
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
let out = result;
|
|
84
|
+
if (method === "tools/list" && hidden.size > 0 && declaresEvents(req.meta) && Array.isArray(out.tools)) {
|
|
85
|
+
out = { ...out, tools: out.tools.filter((t) => !hidden.has(t?.name)) };
|
|
86
|
+
}
|
|
87
|
+
if (!v1) {
|
|
88
|
+
// tools/list differs by what each request declares (this filter,
|
|
89
|
+
// the wait tool), so it's cached by this client only, whatever
|
|
90
|
+
// cache hint the app set; 2.x fills in the rest.
|
|
91
|
+
return method === "tools/list" ? { ...out, cacheScope: "private" } : out;
|
|
77
92
|
}
|
|
78
|
-
|
|
93
|
+
if (out.resultType === undefined)
|
|
94
|
+
out = { ...out, resultType: RESULT_TYPE };
|
|
79
95
|
// A list's result says how long a client may cache it, as 2026-07-28
|
|
80
96
|
// requires (Claude Code falls back to initialize without it): not at
|
|
81
97
|
// all, and for this client only, since tools/list differs by what
|
|
@@ -84,14 +100,19 @@ export function serveStateless(server, opts = {}) {
|
|
|
84
100
|
return { ...out, ttlMs: 0, cacheScope: "private" };
|
|
85
101
|
return out;
|
|
86
102
|
});
|
|
103
|
+
const wraps = (method) => v1 || method === "tools/list";
|
|
87
104
|
for (const [method, h] of handlers)
|
|
88
|
-
|
|
105
|
+
if (wraps(method))
|
|
106
|
+
handlers.set(method, wrap(method, h));
|
|
89
107
|
const set = handlers.set.bind(handlers);
|
|
90
|
-
handlers.set = (method, h) => set(method, wrap(method, h));
|
|
91
|
-
|
|
108
|
+
handlers.set = (method, h) => set(method, wraps(method) ? wrap(method, h) : h);
|
|
109
|
+
if (!v1)
|
|
110
|
+
return;
|
|
111
|
+
const low = host.server.server;
|
|
112
|
+
low.setRequestHandler(z.object({ method: z.literal("server/discover"), params: z.any().optional() }), async () => {
|
|
92
113
|
const out = {
|
|
93
114
|
resultType: RESULT_TYPE,
|
|
94
|
-
supportedVersions: [...
|
|
115
|
+
supportedVersions: [...(v1Types?.SUPPORTED_PROTOCOL_VERSIONS ?? [STATELESS_VERSION])],
|
|
95
116
|
// Cacheable results carry these on 2026-07-28 (Claude Code refuses a
|
|
96
117
|
// server/discover without them, and connects the old way).
|
|
97
118
|
ttlMs: 0,
|
|
@@ -104,4 +125,3 @@ export function serveStateless(server, opts = {}) {
|
|
|
104
125
|
return out;
|
|
105
126
|
});
|
|
106
127
|
}
|
|
107
|
-
//# sourceMappingURL=discover.js.map
|
package/dist/errors.js
CHANGED
package/dist/events.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type AnySchema, type SchemaInput, type SchemaOutput, type ZodRawShapeCompat } from "@modelcontextprotocol/sdk/server/zod-compat.js";
|
|
1
|
+
import { type AnySchema, type SchemaInput, type SchemaOutput, type ZodRawShapeCompat } from "./zod.js";
|
|
3
2
|
import { WakeshiftError } from "./errors.js";
|
|
4
3
|
import { type Dialect } from "./spec.js";
|
|
5
|
-
import { type Emitted, type PublishUrl, type Recipient, type
|
|
4
|
+
import { type Emitted, type PublishUrl, type Recipient, type Via, type Wakeshift } from "./wakeshift.js";
|
|
6
5
|
import { type EventsOptions } from "./toolcall.js";
|
|
7
6
|
/** Who is subscribing: what authorize decides on. */
|
|
8
7
|
export interface Subscriber {
|
|
@@ -174,16 +173,21 @@ export interface Identity {
|
|
|
174
173
|
*/
|
|
175
174
|
client?: string;
|
|
176
175
|
}
|
|
177
|
-
|
|
176
|
+
/**
|
|
177
|
+
* attach's options. C is the request context your MCP SDK's handlers get:
|
|
178
|
+
* @modelcontextprotocol/server 2.x's ServerContext (`ctx`), or
|
|
179
|
+
* @modelcontextprotocol/sdk 1.x's RequestHandlerExtra (`extra`).
|
|
180
|
+
*/
|
|
181
|
+
export interface AttachOptions<C = any> {
|
|
178
182
|
/**
|
|
179
183
|
* Who is behind a request. The app passes identify, returning { user }:
|
|
180
184
|
* its own ID for the person, from its own sign-in (and, on a stateless
|
|
181
185
|
* server, the client's name as `client`). Without it, requests have no
|
|
182
|
-
* user, so personal events and list_subscriptions refuse.
|
|
186
|
+
* user, so personal events and list_subscriptions refuse. It gets the
|
|
187
|
+
* request's context as your MCP SDK passes it to handlers: 2.x's ctx
|
|
188
|
+
* (the token in ctx.http?.authInfo), 1.x's extra (extra.authInfo).
|
|
183
189
|
*/
|
|
184
|
-
identify
|
|
185
|
-
authInfo?: AuthInfo;
|
|
186
|
-
}) => Identity | undefined;
|
|
190
|
+
identify?(extra: C): Identity | undefined;
|
|
187
191
|
/**
|
|
188
192
|
* Which of attach's own tools it adds (BUILTIN_TOOLS): subscribe_events,
|
|
189
193
|
* unsubscribe_events and list_subscriptions. Default: all three; `[]`
|
|
@@ -192,7 +196,7 @@ export interface AttachOptions {
|
|
|
192
196
|
*/
|
|
193
197
|
tools?: readonly BuiltinTool[];
|
|
194
198
|
/** The event features' options: events.native overrides native detection for wrapped tools (attached.events). */
|
|
195
|
-
events?: EventsOptions
|
|
199
|
+
events?: EventsOptions<C>;
|
|
196
200
|
}
|
|
197
201
|
/** The tools attach adds, unless AttachOptions.tools names fewer. */
|
|
198
202
|
export declare const BUILTIN_TOOLS: readonly ["subscribe_events", "unsubscribe_events", "list_subscriptions"];
|