@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/dist/mastra.js ADDED
@@ -0,0 +1,248 @@
1
+ // @wakeshift/sdk/mastra: Wakeshift in a Mastra MCPServer (@mastra/mcp 2.x).
2
+ //
3
+ // MCPServer builds a low-level MCP Server (@modelcontextprotocol/server
4
+ // 2.x) for each request and fills it in itself; it never hands it to the
5
+ // app, and its public extension points (Mastra tools, its own events) can't
6
+ // carry what Wakeshift needs: a tool's result is Mastra's conversion of
7
+ // what the tool returned, without _meta, which Codex's and Claude Code's
8
+ // hooks read Wakeshift's beacon from; and there's no way to add a method
9
+ // (tasks/get for the wait tool) or answer a tool's call another way.
10
+ //
11
+ // So serve() takes the one seam there is: MCPServer's createServerInstance,
12
+ // a private method every transport (HTTP, stdio) builds its servers with.
13
+ // Each server it builds gets what attach gives an McpServer, on that
14
+ // server's request handlers (host.ts's lowServerHost): attach's own tools,
15
+ // MCP Events, the wait tool and the 2026-07-28 tools/list changes; and the
16
+ // tools wrapped with attached.events get Wakeshift's block in their results.
17
+ // serve() checks @mastra/mcp's version and that the seam is there, and the
18
+ // compatibility suite (sdk/compat, ts-mastra) runs it against Mastra's
19
+ // newest release nightly, so a Mastra release that moves it shows up there.
20
+ //
21
+ // Mastra's own MCP Events (MCPServer's events option) keep working beside
22
+ // Wakeshift's: events/list lists both, and every other events/* request
23
+ // goes to whichever serves the event it names (Wakeshift's, if both do).
24
+ //
25
+ // const attached = attachMastra(wake, { identify: (ctx) => ({ user: userOf(ctx.http?.authInfo) }) })
26
+ // const watchCi = createTool({
27
+ // id: "watch_ci",
28
+ // description: "Watch CI on a repo",
29
+ // inputSchema: attached.inputs(z.object({ owner: z.string(), repo: z.string() })),
30
+ // execute: attached.events("watch_ci", async ({ owner, repo }, call) => {
31
+ // await call.subscribe(ciFinished, { owner, repo })
32
+ // return `Watching CI on ${owner}/${repo}.`
33
+ // }),
34
+ // })
35
+ // const server = attached.serve(new MCPServer({ id: "ci", name: "CI", version: "1.0.0", tools: { watch_ci: watchCi } }))
36
+ import { createRequire } from "node:module";
37
+ import { attachHost, checkAttach } from "./events.js";
38
+ import { lowServerHost, toolsOnlyHost, v2Req } from "./host.js";
39
+ import { Attached, CallEnded } from "./toolcall.js";
40
+ /** The @mastra/mcp releases serve() is built for: 2.x, from 2.7. */
41
+ export const MASTRA_MCP_RANGE = ">=2.7.0 <3.0.0";
42
+ const EVENTS_METHODS = ["events/list", "events/subscribe", "events/unsubscribe", "events/poll", "events/stream"];
43
+ /** @internal Whether a version of @mastra/mcp is one serve() is built for. */
44
+ export function supportedMastra(version) {
45
+ const m = /^(\d+)\.(\d+)\./.exec(version);
46
+ return !!m && Number(m[1]) === 2 && Number(m[2]) >= 7;
47
+ }
48
+ // installedMastra is the @mastra/mcp version this process loads, if it can
49
+ // tell (a bundled app may not ship package.json files).
50
+ function installedMastra() {
51
+ try {
52
+ return createRequire(import.meta.url)("@mastra/mcp/package.json").version;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
58
+ /** Wakeshift in a Mastra MCPServer: make it with attachMastra, then serve your MCPServer with it. */
59
+ export class MastraAttached {
60
+ wake;
61
+ opts;
62
+ wrapped = new Set();
63
+ // The call each wrapped tool's execute belongs to, by its request (ctx.mcpReq,
64
+ // which Mastra passes the tool as context.mcp.extra.mcpReq).
65
+ scopes = new WeakMap();
66
+ // Only for inputs and output, which need no server.
67
+ shapes;
68
+ served = new WeakSet();
69
+ /** @internal Use attachMastra. */
70
+ constructor(wake, opts) {
71
+ this.wake = wake;
72
+ this.opts = opts;
73
+ const host = toolsOnlyHost({ framework: "mastra", req: v2Req, registerTool: () => { } });
74
+ this.shapes = new Attached({ wake, host, identify: () => undefined, clientOf: () => "", events: {}, warnOnce: () => { } });
75
+ }
76
+ inputs(schema) {
77
+ return this.shapes.inputs(schema);
78
+ }
79
+ output(schema) {
80
+ return this.shapes.output(schema);
81
+ }
82
+ /**
83
+ * Wraps a Mastra tool's execute, for the tool your MCPServer serves under
84
+ * the same name (its key in `tools`): `execute: attached.events("watch_ci",
85
+ * async (args, call) => …)`. handler gets your own arguments, a ToolCall
86
+ * and Mastra's tool context, and returns what a Mastra tool returns (a
87
+ * string, or your output). The reply an MCP client gets carries what
88
+ * Wakeshift adds for the conversation; a call that fails ends the
89
+ * subscriptions it made. Its inputSchema must come from attached.inputs.
90
+ * Run outside an MCP request (by an agent, a workflow or Studio), the
91
+ * handler gets a call whose subscribe throws.
92
+ */
93
+ events(name, handler) {
94
+ if (!name || typeof name !== "string")
95
+ throw new Error("wakeshift: attached.events needs the tool's name, as your MCPServer serves it");
96
+ if (typeof handler !== "function")
97
+ throw new Error("wakeshift: attached.events needs a handler");
98
+ this.wrapped.add(name);
99
+ return async (input, context) => {
100
+ const key = context?.mcp?.extra?.mcpReq;
101
+ const scope = key && typeof key === "object" ? this.scopes.get(key) : undefined;
102
+ const args = {};
103
+ for (const [k, v] of Object.entries((input ?? {}))) {
104
+ if (k !== "session_key" && k !== "harness" && k !== "conversation_id")
105
+ args[k] = v;
106
+ }
107
+ const call = scope && scope.call.tool === name ? scope.call : detachedCall(name, context);
108
+ return handler(args, call, context ?? {});
109
+ };
110
+ }
111
+ /**
112
+ * Serves Wakeshift from your MCPServer (@mastra/mcp 2.7 or later, 2.x),
113
+ * after your last defineEvent: attach's own tools, MCP Events for clients
114
+ * that speak it, the wait tool, and your wrapped tools' blocks. Returns
115
+ * the server. Call it once, before the server takes requests.
116
+ */
117
+ serve(server) {
118
+ const version = installedMastra();
119
+ if (version && !supportedMastra(version)) {
120
+ throw new Error(`wakeshift: attachMastra serves @mastra/mcp ${MASTRA_MCP_RANGE}, and this is ${version}: check for an @wakeshift/sdk that supports it`);
121
+ }
122
+ const s = server;
123
+ if (typeof s.createServerInstance !== "function") {
124
+ throw new Error(`wakeshift: attached.serve takes Mastra's MCPServer (@mastra/mcp ${MASTRA_MCP_RANGE}), and this one has no createServerInstance${version ? ` (@mastra/mcp ${version})` : ""}: check for a newer @wakeshift/sdk`);
125
+ }
126
+ if (this.served.has(server))
127
+ return server;
128
+ this.served.add(server);
129
+ const build = s.createServerInstance;
130
+ const self = this;
131
+ s.createServerInstance = function (...args) {
132
+ const low = build.apply(this, args);
133
+ self.attachTo(low);
134
+ return low;
135
+ };
136
+ return server;
137
+ }
138
+ // attachTo gives one server MCPServer built what attach gives an McpServer.
139
+ // A failure leaves the request served without Wakeshift, and says why once.
140
+ attachTo(low) {
141
+ try {
142
+ const host = lowServerHost(low, "Mastra's MCPServer");
143
+ const handlers = host.handlers();
144
+ const theirs = new Map();
145
+ for (const m of EVENTS_METHODS) {
146
+ const h = handlers.get(m);
147
+ if (h)
148
+ theirs.set(m, h);
149
+ }
150
+ const events = this.wake.serveEvents();
151
+ const attached = attachHost(this.wake, host, events, this.opts);
152
+ for (const name of this.wrapped)
153
+ attached.markWrapped(name);
154
+ if (theirs.size > 0)
155
+ shareEvents(handlers, theirs, new Set(events.map((e) => e.name)));
156
+ this.wrapCalls(handlers, attached);
157
+ }
158
+ catch (err) {
159
+ this.wake.warnOnce("mastra-attach", `wakeshift: couldn't attach to the server Mastra's MCPServer built, so its requests are served without Wakeshift: ${String(err?.message ?? err)}`);
160
+ }
161
+ }
162
+ // wrapCalls runs each wrapped tool's call in a scope its execute finds, and
163
+ // adds Wakeshift's block to the result Mastra makes of what it returned.
164
+ wrapCalls(handlers, attached) {
165
+ const inner = handlers.get("tools/call");
166
+ if (!inner)
167
+ return;
168
+ Map.prototype.set.call(handlers, "tools/call", async (request, ctx) => {
169
+ const name = request?.params?.name;
170
+ if (typeof name !== "string" || !this.wrapped.has(name))
171
+ return inner(request, ctx);
172
+ const { call } = attached.begin(name, (request?.params?.arguments ?? {}), ctx);
173
+ const key = ctx?.mcpReq && typeof ctx.mcpReq === "object" ? ctx.mcpReq : undefined;
174
+ if (key)
175
+ this.scopes.set(key, { call });
176
+ let result;
177
+ try {
178
+ result = await inner(request, ctx);
179
+ }
180
+ catch (err) {
181
+ await attached.fail(call);
182
+ throw err;
183
+ }
184
+ finally {
185
+ if (key)
186
+ this.scopes.delete(key);
187
+ }
188
+ if (!result || typeof result !== "object" || !Array.isArray(result.content)) {
189
+ // Not a tool's answer (a task handle, an input request): no block to
190
+ // add, so nothing the call subscribed stays.
191
+ await attached.fail(call);
192
+ this.wake.warnOnce("mastra-result:" + name, `wakeshift: ${name} answered with something other than a tool result (a Mastra task, or a request for input), so what it subscribed was rolled back: a wrapped tool must answer in one call`);
193
+ return result;
194
+ }
195
+ return attached.finish(call, result, undefined);
196
+ });
197
+ }
198
+ }
199
+ // shareEvents serves Mastra's own MCP Events beside Wakeshift's: events/list
200
+ // lists both (Wakeshift's first, on the first page), and every other method
201
+ // goes to whichever serves the event it names.
202
+ function shareEvents(handlers, theirs, ours) {
203
+ for (const [method, their] of theirs) {
204
+ const mine = handlers.get(method);
205
+ if (!mine || mine === their)
206
+ continue;
207
+ if (method === "events/list") {
208
+ Map.prototype.set.call(handlers, method, async (request, ctx) => {
209
+ if (request?.params?.cursor !== undefined)
210
+ return their(request, ctx);
211
+ const [a, b] = await Promise.all([mine(request, ctx), their(request, ctx)]);
212
+ const listed = [...(a?.events ?? []), ...(b?.events ?? []).filter((e) => !ours.has(e?.name ?? ""))];
213
+ return { ...b, ...a, events: listed, ...(b?.nextCursor !== undefined ? { nextCursor: b.nextCursor } : {}) };
214
+ });
215
+ continue;
216
+ }
217
+ Map.prototype.set.call(handlers, method, (request, ctx) => (ours.has(request?.params?.name) ? mine(request, ctx) : their(request, ctx)));
218
+ }
219
+ }
220
+ // detachedCall is the call a wrapped tool's handler gets outside an MCP
221
+ // tools/call (an agent, a workflow or Studio ran the tool): nothing to subscribe.
222
+ function detachedCall(tool, context) {
223
+ const refuse = () => Promise.reject(new Error(`wakeshift: ${tool} ran outside an MCP client's call to a server attachMastra serves (from an agent, a workflow or Studio?), so there's no conversation to subscribe`));
224
+ return {
225
+ user: undefined,
226
+ tool,
227
+ native: false,
228
+ nativeConversation: null,
229
+ session: null,
230
+ recognized: null,
231
+ extra: (context?.mcp?.extra ?? {}),
232
+ subscribe: refuse,
233
+ unsubscribe: refuse,
234
+ resolveSession: refuse,
235
+ };
236
+ }
237
+ /**
238
+ * Wakeshift for a Mastra MCPServer (@mastra/mcp 2.7 or later). Returns the
239
+ * handle your tools use (attached.inputs, attached.events) and that serves
240
+ * your MCPServer (attached.serve). Options are attach's; identify and
241
+ * events.native get the request's MCP context (ctx: the verified token is
242
+ * ctx.http?.authInfo).
243
+ */
244
+ export function attachMastra(wake, opts = {}) {
245
+ checkAttach(wake.serveEvents(), opts, "attachMastra");
246
+ return new MastraAttached(wake, opts);
247
+ }
248
+ export { CallEnded };
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ /** An MCP content block: text, or anything else the protocol carries. */
2
+ export type ContentBlock = {
3
+ type: "text";
4
+ text: string;
5
+ [k: string]: unknown;
6
+ } | {
7
+ type: "image" | "audio" | "resource_link" | "resource";
8
+ [k: string]: unknown;
9
+ };
10
+ /** A tools/call result, as either MCP SDK types one. */
11
+ export interface CallToolResult {
12
+ content: ContentBlock[];
13
+ /** An object on protocol 2025-11-25; any JSON value on 2026-07-28. */
14
+ structuredContent?: unknown;
15
+ isError?: boolean;
16
+ _meta?: Record<string, unknown>;
17
+ [k: string]: unknown;
18
+ }
19
+ /** A verified access token, as an MCP SDK's auth middleware passes it to handlers. */
20
+ export interface AuthInfo {
21
+ token: string;
22
+ clientId: string;
23
+ scopes: string[];
24
+ expiresAt?: number;
25
+ resource?: URL;
26
+ extra?: Record<string, unknown>;
27
+ }
28
+ /**
29
+ * A JSON-RPC error a handler throws: both MCP SDKs answer with its code,
30
+ * message and data (1.x calls its own McpError, 2.x ProtocolError).
31
+ */
32
+ export declare class RpcError extends Error {
33
+ readonly code: number;
34
+ readonly data?: unknown;
35
+ constructor(code: number, message: string, data?: unknown);
36
+ }
37
+ /**
38
+ * An McpServer from either MCP SDK: @modelcontextprotocol/sdk 1.x's
39
+ * (`@modelcontextprotocol/sdk/server/mcp.js`) or
40
+ * @modelcontextprotocol/server 2.x's.
41
+ */
42
+ export interface McpServerLike {
43
+ readonly server: object;
44
+ registerTool(...args: any[]): unknown;
45
+ }
46
+ type LowOf<S> = S extends {
47
+ server: infer L;
48
+ } ? L : never;
49
+ /**
50
+ * The context a server's request handlers get, read from the server's own
51
+ * type: @modelcontextprotocol/server 2.x's ServerContext (`ctx`), or
52
+ * @modelcontextprotocol/sdk 1.x's RequestHandlerExtra (`extra`).
53
+ */
54
+ export type HostContext<S> = LowOf<S> extends {
55
+ projectCallToolResult: unknown;
56
+ } ? LowOf<S> extends {
57
+ setRequestHandler(method: string, schemas: any, handler: (params: any, ctx: infer C) => any): void;
58
+ } ? C : ToolCallExtra : S extends {
59
+ registerTool(name: string, config: any, cb: (args: any, extra: infer E) => any): any;
60
+ } ? E : ToolCallExtra;
61
+ /**
62
+ * The parts of a tool call's context Wakeshift reads, whichever MCP SDK
63
+ * serves it: 1.x's RequestHandlerExtra (`_meta`, `authInfo`,
64
+ * `requestInfo`), 2.x's ServerContext (`mcpReq`, `http`). A wrapped tool's
65
+ * `call.extra` is typed as your MCP SDK's own.
66
+ */
67
+ export interface ToolCallExtra {
68
+ _meta?: Record<string, unknown>;
69
+ authInfo?: AuthInfo;
70
+ requestInfo?: {
71
+ headers?: Record<string, string | string[] | undefined>;
72
+ };
73
+ signal?: AbortSignal;
74
+ sessionId?: string;
75
+ mcpReq?: {
76
+ _meta?: Record<string, unknown>;
77
+ envelope?: Record<string, unknown>;
78
+ signal?: AbortSignal;
79
+ id?: string | number;
80
+ };
81
+ http?: {
82
+ authInfo?: AuthInfo;
83
+ req?: {
84
+ headers: {
85
+ get(name: string): string | null;
86
+ };
87
+ };
88
+ };
89
+ /** The rest of the SDK's own context (1.x: requestId, sendNotification…; 2.x: mcpReq.send…). */
90
+ [k: string]: any;
91
+ }
92
+ export {};
package/dist/mcp.js ADDED
@@ -0,0 +1,21 @@
1
+ // The MCP types the SDK's API names, written out here rather than imported,
2
+ // so its published types load with either MCP TypeScript SDK installed and
3
+ // the other missing: @modelcontextprotocol/sdk 1.x or
4
+ // @modelcontextprotocol/server 2.x (host.ts tells them apart at run time).
5
+ // Each is what both SDKs' own types have in common, so a value of either's
6
+ // type fits.
7
+ /**
8
+ * A JSON-RPC error a handler throws: both MCP SDKs answer with its code,
9
+ * message and data (1.x calls its own McpError, 2.x ProtocolError).
10
+ */
11
+ export class RpcError extends Error {
12
+ code;
13
+ data;
14
+ constructor(code, message, data) {
15
+ super(message);
16
+ this.name = "RpcError";
17
+ this.code = code;
18
+ if (data !== undefined)
19
+ this.data = data;
20
+ }
21
+ }
package/dist/retry.js CHANGED
@@ -55,4 +55,3 @@ function pause(ms, signal) {
55
55
  signal?.addEventListener("abort", stop, { once: true });
56
56
  });
57
57
  }
58
- //# sourceMappingURL=retry.js.map
package/dist/spec.js CHANGED
@@ -42,4 +42,3 @@ export function headerValue(h) {
42
42
  return null;
43
43
  }
44
44
  }
45
- //# sourceMappingURL=spec.js.map
package/dist/tasks.js CHANGED
@@ -14,9 +14,9 @@
14
14
  // tool, and a wrapped tool's call from it tells Wakeshift so (call.tasks),
15
15
  // which then gives the wait tool's instructions instead of its harness's
16
16
  // zero-install path (Claude Code's Monitor). Everything else is unchanged.
17
- import { McpError } from "@modelcontextprotocol/sdk/types.js";
18
17
  import { WakeshiftError } from "./errors.js";
19
18
  import { TASKS_EXTENSION, takesTasks } from "./discover.js";
19
+ import { RpcError } from "./mcp.js";
20
20
  import { WAKE_ARG_DESCRIPTIONS } from "./toolcall.js";
21
21
  import { toolCallOf } from "./wakeshift.js";
22
22
  /** The wait tool's name. */
@@ -48,19 +48,22 @@ export { TASKS_EXTENSION, takesTasks };
48
48
  * in tools/list for a request that takes tasks, and its calls and task
49
49
  * requests passed to Wakeshift.
50
50
  */
51
- export function serveTasks(wake, server, callerOf) {
52
- const low = server.server;
53
- const handlers = low._requestHandlers;
54
- if (!(handlers instanceof Map))
51
+ export function serveTasks(wake, host, callerOf) {
52
+ const handlers = host.handlers();
53
+ if (!handlers)
55
54
  return; // a future SDK: nothing to hook (no wait tool)
56
- const call = (extra, args) => {
57
- const { user, client } = callerOf(extra);
58
- return toolCallOf(extra, client, user, args, WAIT_TOOL);
55
+ const call = (raw, args) => {
56
+ const { user, client } = callerOf(raw);
57
+ return toolCallOf(host.req(raw), client, user, args, WAIT_TOOL);
59
58
  };
60
- const waitCall = async (request, extra) => {
59
+ const takes = (raw) => {
60
+ const req = host.req(raw);
61
+ return takesTasks(req.meta, req);
62
+ };
63
+ const waitCall = async (request, raw) => {
61
64
  const args = (request?.params?.arguments ?? {});
62
65
  try {
63
- const out = await wake.requestApp("POST", "/calls/task", { call: call(extra, args) });
66
+ const out = await wake.requestApp("POST", "/calls/task", { call: call(raw, args) });
64
67
  if (out.result)
65
68
  return out.result;
66
69
  }
@@ -69,74 +72,83 @@ export function serveTasks(wake, server, callerOf) {
69
72
  }
70
73
  return { isError: true, content: [{ type: "text", text: `Wakeshift didn't answer the wait. Call ${WAIT_TOOL} again.` }] };
71
74
  };
72
- const taskRequest = (path, hold) => async (request, extra) => {
75
+ const taskRequest = (path, hold) => async (request, raw) => {
73
76
  const task = request?.params?.taskId;
74
- const body = { task: typeof task === "string" ? task : "", call: call(extra, {}) };
77
+ const body = { task: typeof task === "string" ? task : "", call: call(raw, {}) };
75
78
  if (hold)
76
79
  body.hold_seconds = TASK_HOLD_SECONDS;
80
+ const signal = host.req(raw).signal;
77
81
  let out;
78
82
  try {
79
- out = await wake.requestApp("POST", path, body, { timeoutMs: (TASK_HOLD_SECONDS + 15) * 1000, signal: extra?.signal });
83
+ out = await wake.requestApp("POST", path, body, { timeoutMs: (TASK_HOLD_SECONDS + 15) * 1000, signal });
80
84
  }
81
85
  catch (err) {
82
- if (extra?.signal?.aborted)
83
- throw new McpError(-32603, "canceled by the client");
84
- throw new McpError(-32603, `Wakeshift: ${detailOf(err)}`);
86
+ if (signal?.aborted)
87
+ throw new RpcError(-32603, "canceled by the client");
88
+ throw new RpcError(-32603, `Wakeshift: ${detailOf(err)}`);
85
89
  }
86
90
  if (out.rpc_error)
87
- throw new McpError(out.rpc_error.code, out.rpc_error.message);
91
+ throw new RpcError(out.rpc_error.code, out.rpc_error.message);
88
92
  if (!out.result)
89
- throw new McpError(-32603, "Wakeshift didn't answer for this task");
93
+ throw new RpcError(-32603, "Wakeshift didn't answer for this task");
90
94
  return out.result;
91
95
  };
92
- // server/discover (discover.ts) gets the tasks extension.
93
- const wrapDiscover = (h) => async (request, extra) => {
94
- const out = await h(request, extra);
96
+ // server/discover gets the tasks extension (1.x: discover.ts's handler;
97
+ // 2.x: its own). initialize doesn't: older revisions' clients never see it.
98
+ const wrapDiscover = (h) => async (request, raw) => {
99
+ const out = await h(request, raw);
95
100
  if (out && typeof out === "object") {
96
- const caps = (out.capabilities ??= {});
101
+ const caps = { ...(out.capabilities ?? {}) };
97
102
  caps.extensions = { ...(caps.extensions ?? {}), [TASKS_EXTENSION]: {} };
103
+ return { ...out, capabilities: caps };
98
104
  }
99
105
  return out;
100
106
  };
101
107
  // tools/list lists the wait tool for a request that takes tasks; tools/call
102
108
  // answers it. McpServer sets these when the first tool is registered,
103
109
  // perhaps after attach, so a later set is wrapped too.
104
- const wrapList = (h) => async (request, extra) => {
105
- const out = await h(request, extra);
106
- if (takesTasks(request?.params?._meta, extra) && out && Array.isArray(out.tools) && !out.tools.some((t) => t?.name === WAIT_TOOL)) {
107
- out.tools = [...out.tools, WAIT_TOOL_DEFINITION];
110
+ const wrapList = (h) => async (request, raw) => {
111
+ const out = await h(request, raw);
112
+ if (takes(raw) && out && Array.isArray(out.tools) && !out.tools.some((t) => t?.name === WAIT_TOOL)) {
113
+ return { ...out, tools: [...out.tools, WAIT_TOOL_DEFINITION] };
108
114
  }
109
115
  return out;
110
116
  };
111
- const wrapCall = (h) => async (request, extra) => (request?.params?.name === WAIT_TOOL ? waitCall(request, extra) : h(request, extra));
117
+ const wrapCall = (h) => async (request, raw) => (request?.params?.name === WAIT_TOOL ? waitCall(request, raw) : h(request, raw));
112
118
  // tasks/get and tasks/cancel: Wakeshift's own tasks (their IDs start
113
119
  // wkt1_) go to Wakeshift; any other goes to the app's own handler, if it
114
120
  // has one.
115
121
  const wrapTask = (path, hold) => (h) => {
116
122
  const ours = taskRequest(path, hold);
117
- return async (request, extra) => {
123
+ return async (request, raw) => {
118
124
  const id = request?.params?.taskId;
119
125
  if (typeof id === "string" && id.startsWith(TASK_ID_PREFIX))
120
- return ours(request, extra);
126
+ return ours(request, raw);
121
127
  if (h)
122
- return h(request, extra);
123
- throw new McpError(-32601, "Method not found");
128
+ return h(request, raw);
129
+ throw new RpcError(-32601, "Method not found");
124
130
  };
125
131
  };
126
132
  const wrappers = {
127
- "server/discover": wrapDiscover,
128
- "tools/list": wrapList,
129
- "tools/call": wrapCall,
133
+ "server/discover": (h) => wrapDiscover(h),
134
+ "tools/list": (h) => wrapList(h),
135
+ "tools/call": (h) => wrapCall(h),
130
136
  "tasks/get": wrapTask("/calls/task/get", true),
131
137
  "tasks/cancel": wrapTask("/calls/task/cancel", false),
132
138
  };
133
- // Set directly, not through the MCP SDK's setRequestHandler, which would
134
- // ask for its own tasks capability (an older revision's), never advertised;
135
- // and once only (serveStateless wraps what's set through handlers.set).
136
139
  for (const [method, wrap] of Object.entries(wrappers)) {
137
140
  const h = handlers.get(method);
138
- if (h || method.startsWith("tasks/"))
141
+ if (method.startsWith("tasks/")) {
142
+ // Routed by the SDK on every protocol revision (host.setTaskMethod),
143
+ // then answered by the wrapper, which hands anything that isn't
144
+ // Wakeshift's to the app's own handler, if it had one.
145
+ host.setTaskMethod(method);
146
+ Map.prototype.set.call(handlers, method, wrap(h));
147
+ }
148
+ else if (h) {
149
+ // Set once (serveStateless wraps what's set through handlers.set).
139
150
  Map.prototype.set.call(handlers, method, wrap(h));
151
+ }
140
152
  }
141
153
  const set = handlers.set.bind(handlers);
142
154
  handlers.set = (method, h) => set(method, wrappers[method] ? wrappers[method](h) : h);
@@ -144,4 +156,3 @@ export function serveTasks(wake, server, callerOf) {
144
156
  function detailOf(err) {
145
157
  return err instanceof WakeshiftError ? err.detail : String(err?.message ?? err);
146
158
  }
147
- //# sourceMappingURL=tasks.js.map