@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 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. Peer dependencies: `@modelcontextprotocol/sdk` (1.23 or later: the first that takes zod 4) and `zod` (3.25 or later, or 4). 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.
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
 
@@ -1,4 +1,4 @@
1
- /** The protocol revision that has server/discover and MCP Events. */
2
- export declare const STATELESS_VERSION = "2026-07-28";
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 for an app on the MCP TypeScript SDK 1.x, which
2
- // doesn't have it: MCP Events is defined only for 2026-07-28 and later, and
3
- // some clients speak nothing older (OpenClaw). With attach, a server whose
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. The MCP TypeScript SDK 2.x speaks
21
- // 2026-07-28 itself.
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
- /** The protocol revision that has server/discover and MCP Events. */
26
- export const STATELESS_VERSION = "2026-07-28";
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({ params: { _meta: meta } }, extra))
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(request) {
56
- const ext = request?.params?._meta?.[CAPABILITIES_META]?.extensions;
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(server, opts = {}) {
61
- // The SDK's own list, which its transport checks the header against.
62
- // Mutating it is how 1.x can be taught a version; 2.x already has it.
63
- const versions = SUPPORTED_PROTOCOL_VERSIONS;
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 wrap = (method, h) => (async (request, extra) => {
72
- const result = await h(request, extra);
73
- if (!is2026(request, extra) || !result || typeof result !== "object")
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
- if (method === "tools/list" && hidden.size > 0 && declaresEvents(request) && Array.isArray(result.tools)) {
76
- result.tools = result.tools.filter((t) => !hidden.has(t?.name));
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
- const out = result.resultType === undefined ? { ...result, resultType: RESULT_TYPE } : result;
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
- handlers.set(method, wrap(method, h));
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
- server.server.setRequestHandler(z.object({ method: z.literal("server/discover"), params: z.any().optional() }), async () => {
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: [...versions],
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
@@ -25,4 +25,3 @@ export class WakeshiftError extends Error {
25
25
  export function isCode(err, code) {
26
26
  return err instanceof WakeshiftError && err.code === code;
27
27
  }
28
- //# sourceMappingURL=errors.js.map
package/dist/events.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
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 ToolExtra, type Via, type Wakeshift } from "./wakeshift.js";
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
- export interface AttachOptions {
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?: (extra: ToolExtra & {
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"];