@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.
@@ -1,15 +1,14 @@
1
1
  import { z } from "zod";
2
- import type { CallToolResult, ServerNotification, ServerRequest } from "@modelcontextprotocol/sdk/types.js";
3
- import type { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
5
- import { type AnySchema, type SchemaInput, type ShapeOutput, type ZodRawShapeCompat } from "@modelcontextprotocol/sdk/server/zod-compat.js";
2
+ import * as z3 from "zod/v3";
3
+ import * as z4 from "zod/v4";
4
+ import type { CallToolResult, ToolCallExtra } from "./mcp.js";
5
+ import { type AnySchema, type SchemaInput, type ShapeOutput, type ZodRawShapeCompat } from "./zod.js";
6
6
  import type { WakeEvent } from "./events.js";
7
7
  import { type Duration, type Recognized } from "./wakeshift.js";
8
8
  declare const SESSION_ARG = "session_key";
9
9
  declare const HARNESS_ARG = "harness";
10
10
  declare const CONVERSATION_ARG = "conversation_id";
11
- /** MCP's own extra for a tool call: the request's signal, auth, _meta, HTTP request. */
12
- export type ToolCallExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
11
+ export type { ToolCallExtra };
13
12
  declare const wakeInputs: {
14
13
  session_key: z.ZodOptional<z.ZodString>;
15
14
  harness: z.ZodOptional<z.ZodString>;
@@ -18,10 +17,50 @@ declare const wakeInputs: {
18
17
  declare const wakeOutput: {
19
18
  wakeshift: z.ZodOptional<z.ZodString>;
20
19
  };
20
+ declare const byVersion: {
21
+ v3: {
22
+ inputs: {
23
+ session_key: z3.ZodOptional<z3.ZodString>;
24
+ harness: z3.ZodOptional<z3.ZodString>;
25
+ conversation_id: z3.ZodOptional<z3.ZodString>;
26
+ };
27
+ output: {
28
+ wakeshift: z3.ZodOptional<z3.ZodString>;
29
+ };
30
+ };
31
+ v4: {
32
+ inputs: {
33
+ session_key: z.ZodOptional<z.ZodString>;
34
+ harness: z.ZodOptional<z.ZodString>;
35
+ conversation_id: z.ZodOptional<z.ZodString>;
36
+ };
37
+ output: {
38
+ wakeshift: z.ZodOptional<z.ZodString>;
39
+ };
40
+ };
41
+ };
21
42
  /** Wakeshift's three arguments, as attached.inputs adds them. */
22
43
  export type WakeInputs = typeof wakeInputs;
23
44
  /** The optional wakeshift string attached.output adds. */
24
45
  export type WakeOutputShape = typeof wakeOutput;
46
+ /** Wakeshift's three arguments in zod 4, as attached.inputs adds them to a zod 4 object. */
47
+ export type WakeInputsV4 = typeof byVersion.v4.inputs;
48
+ /** Wakeshift's three arguments in zod 3, as attached.inputs adds them to a zod 3 object. */
49
+ export type WakeInputsV3 = typeof byVersion.v3.inputs;
50
+ /** The optional wakeshift string in zod 4, as attached.output adds it to a zod 4 object. */
51
+ export type WakeOutputV4 = typeof byVersion.v4.output;
52
+ /** The optional wakeshift string in zod 3, as attached.output adds it to a zod 3 object. */
53
+ export type WakeOutputV3 = typeof byVersion.v3.output;
54
+ /** What a wrapped tool's arguments must include: Wakeshift's three (attached.inputs adds them). */
55
+ export type WakeArgs = {
56
+ [K in WakeKey]?: string | undefined;
57
+ };
58
+ /**
59
+ * The callback attached.events returns, for `server.registerTool`: it takes
60
+ * the tool's arguments (A, Wakeshift's three included) and your MCP SDK's
61
+ * request context (C).
62
+ */
63
+ export type WrappedCallback<A, C> = (args: A, extra: C) => Promise<any>;
25
64
  /** The names of Wakeshift's three arguments. */
26
65
  export type WakeKey = typeof SESSION_ARG | typeof HARNESS_ARG | typeof CONVERSATION_ARG;
27
66
  /** An input shape with Wakeshift's three arguments in it (attached.inputs'). */
@@ -63,7 +102,7 @@ export interface ToolSubscribeOptions {
63
102
  * calls that subscribe the calling conversation. Only the wrapper makes
64
103
  * one, and it works until the handler returns (then CallEnded).
65
104
  */
66
- export interface ToolCall {
105
+ export interface ToolCall<C = any> {
67
106
  /** Who attach's identify says is calling (undefined for none). */
68
107
  readonly user: string | undefined;
69
108
  /** The tool being called. */
@@ -80,8 +119,8 @@ export interface ToolCall {
80
119
  readonly session: string | null;
81
120
  /** What Wakeshift recognized the agent's program as: null until a subscribe or resolveSession. */
82
121
  readonly recognized: Recognized | null;
83
- /** MCP's own extra for the call. */
84
- readonly extra: ToolCallExtra;
122
+ /** The call's request context, as your MCP SDK passes it to the tool's callback: 2.x's ctx, 1.x's extra. */
123
+ readonly extra: C;
85
124
  /**
86
125
  * Subscribes the calling conversation to event with args, or renews its
87
126
  * subscription. The reply then carries what the conversation needs to be
@@ -105,14 +144,14 @@ export declare class CallEnded extends Error {
105
144
  constructor();
106
145
  }
107
146
  /** attach's event options. */
108
- export interface EventsOptions {
147
+ export interface EventsOptions<C = any> {
109
148
  /**
110
149
  * Asked first whether a wrapped tool's call comes from a client that
111
150
  * subscribes to the app's events itself (call.native), for callers the
112
151
  * SDK can't tell (an app's own house agents). False leaves it to the
113
- * SDK's own checks.
152
+ * SDK's own checks. It gets the call's request context, as identify does.
114
153
  */
115
- native?: (extra: ToolCallExtra) => boolean;
154
+ native?(extra: C): boolean;
116
155
  }
117
156
  /** /calls/addition's answer. */
118
157
  export interface CallAddition {
@@ -125,24 +164,33 @@ export declare const WAKESHIFT_FIELD = "wakeshift";
125
164
  /**
126
165
  * Wakeshift attached to an MCP server: the handle `wake.attach` returns.
127
166
  * Wrap a tool's callback with events, and give its input schema
128
- * Wakeshift's three arguments with inputs.
167
+ * Wakeshift's three arguments with inputs. C is your MCP SDK's request
168
+ * context (2.x's ctx, 1.x's extra), which a wrapped call carries as
169
+ * `call.extra`.
129
170
  */
130
- export declare class Attached {
171
+ export declare class Attached<C = any> {
131
172
  private readonly at;
132
173
  /**
133
- * shape plus Wakeshift's three optional arguments (session_key, harness
134
- * and conversation_id), for a wrapped tool's inputSchema:
174
+ * A tool's input schema plus Wakeshift's three optional arguments
175
+ * (session_key, harness and conversation_id), for a wrapped tool's
176
+ * inputSchema: a zod object, `inputSchema: attached.inputs(z.object({ table: z.string() }))`
177
+ * (as @modelcontextprotocol/server 2.x takes it), or a shape,
135
178
  * `inputSchema: attached.inputs({ table: z.string() })`.
136
179
  */
180
+ inputs<O extends z4.ZodObject>(schema: O): z4.ZodObject<O["shape"] & WakeInputsV4, O["_zod"]["config"]>;
181
+ inputs<O extends z3.AnyZodObject>(schema: O): z3.ZodObject<O["shape"] & WakeInputsV3, O["_def"]["unknownKeys"], O["_def"]["catchall"]>;
137
182
  inputs<S extends ZodRawShapeCompat>(shape: S): S & WakeInputs;
138
183
  /**
139
- * shape plus an optional wakeshift string, for a wrapped tool's
140
- * outputSchema. Optional: every structured result of a wrapped tool gets
141
- * the block's text (without the beacon line) in wakeshift, for hosts that
142
- * show the model only structuredContent, and tools/list names it in the
143
- * tool's output schema; this declares it in your own schema and types.
144
- * The block stays in the content too.
184
+ * A tool's output schema plus an optional wakeshift string, for a wrapped
185
+ * tool's outputSchema (a zod object or a shape, as inputs takes).
186
+ * Optional: every structured result of a wrapped tool gets the block's
187
+ * text (without the beacon line) in wakeshift, for hosts that show the
188
+ * model only structuredContent, and tools/list names it in the tool's
189
+ * output schema; this declares it in your own schema and types. The
190
+ * block stays in the content too.
145
191
  */
192
+ output<O extends z4.ZodObject>(schema: O): z4.ZodObject<O["shape"] & WakeOutputV4, O["_zod"]["config"]>;
193
+ output<O extends z3.AnyZodObject>(schema: O): z3.ZodObject<O["shape"] & WakeOutputV3, O["_def"]["unknownKeys"], O["_def"]["catchall"]>;
146
194
  output<S extends ZodRawShapeCompat>(shape: S): S & WakeOutputShape;
147
195
  /**
148
196
  * Wraps a tool's callback, for `server.registerTool(name, …)` with the
@@ -152,8 +200,8 @@ export declare class Attached {
152
200
  * inputSchema must come from attached.inputs (a type error otherwise).
153
201
  * Register it on the server passed to attach.
154
202
  */
155
- events<S extends WakeShape>(name: string, handler: (args: AppArgs<S>, call: ToolCall) => WrappedResult | Promise<WrappedResult>): ToolCallback<S>;
203
+ events<A extends WakeArgs>(name: string, handler: (args: Omit<A, WakeKey>, call: ToolCall<C>) => WrappedResult | Promise<WrappedResult>): WrappedCallback<A, C>;
156
204
  private run;
205
+ amendToolsList(): void;
157
206
  private amendTools;
158
207
  }
159
- export {};
package/dist/toolcall.js CHANGED
@@ -18,11 +18,12 @@
18
18
  import { z } from "zod";
19
19
  import * as z3 from "zod/v3";
20
20
  import * as z4 from "zod/v4";
21
- import { getObjectShape, isZ4Schema, normalizeObjectSchema, safeParseAsync } from "@modelcontextprotocol/sdk/server/zod-compat.js";
21
+ import * as z4mini from "zod/v4-mini";
22
+ import { getObjectShape, isSchema, isZ4Schema, normalizeObjectSchema } from "./zod.js";
22
23
  import { WakeshiftError } from "./errors.js";
23
24
  import { EXTENSION_KEY, dialectOf } from "./spec.js";
24
25
  import { takesTasks } from "./discover.js";
25
- import { packCall } from "./wakeshift.js";
26
+ import { packCall, userAgentOf } from "./wakeshift.js";
26
27
  // Wakeshift's three arguments (wakeshift.ts's SESSION_ARG, HARNESS_ARG and
27
28
  // CONVERSATION_ARG, repeated: that module imports this one).
28
29
  const SESSION_ARG = "session_key";
@@ -66,12 +67,26 @@ const byVersion = {
66
67
  },
67
68
  };
68
69
  // fieldsFor is Wakeshift's fields in the zod API shape's own fields use
69
- // (the default import's for an empty shape).
70
- function fieldsFor(shape, kind) {
70
+ // (for an empty shape, the default import's, or zod 4's for the MCP SDK
71
+ // 2.x, which takes only zod 4).
72
+ function fieldsFor(shape, kind, host) {
71
73
  const first = Object.values(shape ?? {})[0];
72
- const fields = !first ? (kind === "inputs" ? wakeInputs : wakeOutput) : byVersion[isZ4Schema(first) ? "v4" : "v3"][kind];
74
+ const version = first ? (isZ4Schema(first) ? "v4" : "v3") : host?.version === 2 ? "v4" : null;
75
+ const fields = version ? byVersion[version][kind] : kind === "inputs" ? wakeInputs : wakeOutput;
73
76
  return fields;
74
77
  }
78
+ /** @internal Wakeshift's three arguments as attach's own tools take them, in the zod the host's MCP SDK takes. */
79
+ export function wakeInputsFor(host) {
80
+ return host.version === 2 ? byVersion.v4.inputs : wakeInputs;
81
+ }
82
+ // withFields is an object schema with fields added, in the schema's own zod
83
+ // API (zod 3, zod 4 classic or mini).
84
+ function withFields(schema, fields) {
85
+ const s = schema;
86
+ if (typeof s.extend === "function")
87
+ return s.extend(fields);
88
+ return z4mini.extend(schema, fields);
89
+ }
75
90
  const WAKE_KEYS = [SESSION_ARG, HARNESS_ARG, CONVERSATION_ARG];
76
91
  /**
77
92
  * @internal The tools/call _meta keys a harness names its conversation in
@@ -209,14 +224,8 @@ function sameJSON(a, b) {
209
224
  function detailOf(err) {
210
225
  return err instanceof WakeshiftError ? err.detail : String(err?.message ?? err);
211
226
  }
212
- function registeredTool(server, name) {
213
- const tools = server._registeredTools;
214
- if (!tools || typeof tools !== "object")
215
- return null; // an MCP SDK that keeps them elsewhere
216
- return tools[name];
217
- }
218
- /** One tools/call of a wrapped tool: what its handler subscribed, for the block and the rollback. */
219
- class CallScope {
227
+ /** @internal One tools/call of a wrapped tool: what its handler subscribed, for the block and the rollback. */
228
+ export class CallScope {
220
229
  at;
221
230
  args;
222
231
  user;
@@ -252,17 +261,18 @@ class CallScope {
252
261
  // User-Agent, the agent's harness and conversation_id arguments, the
253
262
  // conversation's session, and who's calling.
254
263
  wire(session = this.session) {
255
- const meta = this.extra._meta;
264
+ const req = this.at.host.req(this.extra);
265
+ const meta = req.meta;
256
266
  return packCall({
257
267
  meta,
258
268
  clientName: this.at.clientOf(meta, this.identified),
259
269
  user: this.user,
260
270
  session: session ?? undefined,
261
- userAgent: userAgentOf(this.extra),
271
+ userAgent: userAgentOf(req),
262
272
  harness: this.args.harness,
263
273
  conversation: this.args.conversation,
264
274
  tool: this.tool,
265
- tasks: takesTasks(meta, this.extra),
275
+ tasks: takesTasks(meta, req),
266
276
  });
267
277
  }
268
278
  // request runs one request of the call to Wakeshift, after any before it
@@ -415,19 +425,12 @@ class CallScope {
415
425
  function recognizedOf(a) {
416
426
  return { harness: a?.harness ?? null, via: a?.via ?? null, claimed: a?.claimed ?? null };
417
427
  }
418
- function headerOf(extra, name) {
419
- const h = extra?.requestInfo?.headers;
420
- if (!h)
421
- return "";
422
- const v = h[name] ?? h[name.toLowerCase()];
423
- return (Array.isArray(v) ? v[0] : v) ?? "";
424
- }
425
- function userAgentOf(extra) {
426
- return headerOf(extra, "user-agent") || headerOf(extra, "User-Agent");
427
- }
428
428
  function hasKey(o, key) {
429
429
  return !!o && typeof o === "object" && !Array.isArray(o) && o[key] !== undefined;
430
430
  }
431
+ function isRecord(v) {
432
+ return !!v && typeof v === "object" && !Array.isArray(v);
433
+ }
431
434
  /**
432
435
  * @internal Whether a call comes from a client that subscribes to the app's
433
436
  * events itself, on stateless servers too (which never see initialize), in
@@ -438,15 +441,19 @@ function hasKey(o, key) {
438
441
  * _meta's). conversation is ChatGPT's conversation, when it names one.
439
442
  */
440
443
  export function nativeOf(at, extra, identified) {
441
- const meta = (extra._meta ?? {});
444
+ // A server that doesn't serve MCP Events (an xmcp app's) has no native callers.
445
+ if (!at.host.servesEvents)
446
+ return { native: false, conversation: null };
447
+ const req = at.host.req(extra);
448
+ const meta = (req.meta ?? {});
442
449
  const conversation = typeof meta["openai/session"] === "string" && meta["openai/session"] ? meta["openai/session"] : null;
443
450
  if (at.events.native?.(extra))
444
451
  return { native: true, conversation };
445
452
  if (conversation)
446
453
  return { native: true, conversation };
447
- if (dialectOf(at.clientOf(meta, identified), userAgentOf(extra)) === "chatgpt")
454
+ if (dialectOf(at.clientOf(meta, identified), userAgentOf(req)) === "chatgpt")
448
455
  return { native: true, conversation: null };
449
- const caps = at.server.server.getClientCapabilities();
456
+ const caps = at.host.clientCapabilities();
450
457
  if (hasKey(caps?.extensions, EXTENSION_KEY))
451
458
  return { native: true, conversation: null };
452
459
  const metaCaps = meta["io.modelcontextprotocol/clientCapabilities"];
@@ -457,7 +464,9 @@ export function nativeOf(at, extra, identified) {
457
464
  /**
458
465
  * Wakeshift attached to an MCP server: the handle `wake.attach` returns.
459
466
  * Wrap a tool's callback with events, and give its input schema
460
- * Wakeshift's three arguments with inputs.
467
+ * Wakeshift's three arguments with inputs. C is your MCP SDK's request
468
+ * context (2.x's ctx, 1.x's extra), which a wrapped call carries as
469
+ * `call.extra`.
461
470
  */
462
471
  export class Attached {
463
472
  at;
@@ -467,29 +476,33 @@ export class Attached {
467
476
  constructor(at) {
468
477
  this.at = at;
469
478
  }
470
- /**
471
- * shape plus Wakeshift's three optional arguments (session_key, harness
472
- * and conversation_id), for a wrapped tool's inputSchema:
473
- * `inputSchema: attached.inputs({ table: z.string() })`.
474
- */
475
479
  inputs(shape) {
480
+ if (isSchema(shape)) {
481
+ const own = getObjectShape(shape);
482
+ if (!own)
483
+ throw new Error("wakeshift: attached.inputs takes a zod object or a shape");
484
+ for (const k of WAKE_KEYS)
485
+ if (k in own)
486
+ throw new Error(`wakeshift: attached.inputs adds ${k} itself: leave it out of the schema`);
487
+ return withFields(shape, byVersion[isZ4Schema(shape) ? "v4" : "v3"].inputs);
488
+ }
476
489
  for (const k of WAKE_KEYS)
477
490
  if (k in (shape ?? {}))
478
491
  throw new Error(`wakeshift: attached.inputs adds ${k} itself: leave it out of the shape`);
479
- return { ...shape, ...fieldsFor(shape, "inputs") };
492
+ return { ...shape, ...fieldsFor(shape, "inputs", this.at.host) };
480
493
  }
481
- /**
482
- * shape plus an optional wakeshift string, for a wrapped tool's
483
- * outputSchema. Optional: every structured result of a wrapped tool gets
484
- * the block's text (without the beacon line) in wakeshift, for hosts that
485
- * show the model only structuredContent, and tools/list names it in the
486
- * tool's output schema; this declares it in your own schema and types.
487
- * The block stays in the content too.
488
- */
489
494
  output(shape) {
495
+ if (isSchema(shape)) {
496
+ const own = getObjectShape(shape);
497
+ if (!own)
498
+ throw new Error("wakeshift: attached.output takes a zod object or a shape");
499
+ if (WAKESHIFT_FIELD in own)
500
+ throw new Error("wakeshift: attached.output adds wakeshift itself: leave it out of the schema");
501
+ return withFields(shape, byVersion[isZ4Schema(shape) ? "v4" : "v3"].output);
502
+ }
490
503
  if ("wakeshift" in (shape ?? {}))
491
504
  throw new Error("wakeshift: attached.output adds wakeshift itself: leave it out of the shape");
492
- return { ...shape, ...fieldsFor(shape, "output") };
505
+ return { ...shape, ...fieldsFor(shape, "output", this.at.host) };
493
506
  }
494
507
  /**
495
508
  * Wraps a tool's callback, for `server.registerTool(name, …)` with the
@@ -509,7 +522,8 @@ export class Attached {
509
522
  return cb;
510
523
  }
511
524
  async run(name, cb, handler, input, extra) {
512
- const tool = registeredTool(this.at.server, name);
525
+ const host = this.at.host;
526
+ const tool = host.registeredTool(name);
513
527
  if (tool === undefined || (tool && (tool.handler ?? tool.callback) !== cb)) {
514
528
  throw new Error(`wakeshift: attached.events("${name}", …) isn't the tool named ${name} on the server passed to attach: register it with the same name, on that server`);
515
529
  }
@@ -521,24 +535,49 @@ export class Attached {
521
535
  }
522
536
  outputSchema = tool.outputSchema;
523
537
  }
524
- else {
538
+ else if (!host.framework) {
525
539
  this.at.warnOnce("internals", "wakeshift: this MCP SDK keeps its tools where Wakeshift can't read them, so wrapped tools' registration and output aren't checked");
526
540
  }
527
- const str = (k) => (typeof input[k] === "string" && input[k] ? input[k] : undefined);
528
- const call = new CallScope(this.at, name, extra, { session: str(SESSION_ARG), harness: str(HARNESS_ARG), conversation: str(CONVERSATION_ARG) });
529
- const appArgs = {};
530
- for (const [k, v] of Object.entries(input))
531
- if (!WAKE_KEYS.includes(k))
532
- appArgs[k] = v;
541
+ const { call, appArgs } = this.begin(name, input, extra);
533
542
  let result;
534
543
  try {
535
544
  result = await handler(appArgs, call);
536
545
  }
537
546
  catch (err) {
538
- await call.end();
539
- await call.rollback();
547
+ await this.fail(call);
540
548
  throw err;
541
549
  }
550
+ return this.finish(call, result, outputSchema);
551
+ }
552
+ /**
553
+ * @internal The first half of a wrapped call: the call's scope, from the
554
+ * tool's arguments (Wakeshift's three among them) and the request's
555
+ * context, and the app's own arguments. finish (or fail) ends it.
556
+ */
557
+ begin(name, input, extra) {
558
+ const str = (k) => (typeof input[k] === "string" && input[k] ? input[k] : undefined);
559
+ const call = new CallScope(this.at, name, extra, { session: str(SESSION_ARG), harness: str(HARNESS_ARG), conversation: str(CONVERSATION_ARG) });
560
+ const appArgs = {};
561
+ for (const [k, v] of Object.entries(input ?? {}))
562
+ if (!WAKE_KEYS.includes(k))
563
+ appArgs[k] = v;
564
+ return { call, appArgs };
565
+ }
566
+ /** @internal Ends a call whose handler threw: what it subscribed is rolled back. */
567
+ async fail(call) {
568
+ await call.end();
569
+ await call.rollback();
570
+ }
571
+ /**
572
+ * @internal The second half of a wrapped call: the handler's result with
573
+ * Wakeshift's block, or rolled back (a failed result, a canceled call, an
574
+ * output its schema refuses). outputSchema is the tool's, as its MCP SDK
575
+ * checks it (undefined: not checked here).
576
+ */
577
+ async finish(call, result, outputSchema) {
578
+ const host = this.at.host;
579
+ const name = call.tool;
580
+ const extra = call.extra;
542
581
  await call.end();
543
582
  // Rollback is decided from the final result, the one the client would
544
583
  // receive: MCP validates the output after this callback returns, so
@@ -547,7 +586,7 @@ export class Attached {
547
586
  await call.rollback();
548
587
  return result;
549
588
  }
550
- if ((typeof result === "object" && result.isError) || extra?.signal?.aborted || !(await validOutput(outputSchema, result))) {
589
+ if ((typeof result === "object" && result.isError) || host.req(extra).signal?.aborted || !(await host.validOutput(outputSchema, result))) {
551
590
  await call.rollback();
552
591
  return typeof result === "string" ? { content: [{ type: "text", text: result }] } : result;
553
592
  }
@@ -563,7 +602,7 @@ export class Attached {
563
602
  return { isError: true, content: [{ type: "text", text: `Wakeshift couldn't finish setting up this conversation's wakes (${detailOf(err)}). Try again.` }] };
564
603
  }
565
604
  const out = mergeBlock(result, add ?? {});
566
- if (out.structuredContent && WAKESHIFT_FIELD in out.structuredContent && !(await validOutput(outputSchema, out))) {
605
+ if (isRecord(out.structuredContent) && WAKESHIFT_FIELD in out.structuredContent && !(await host.validOutput(outputSchema, out))) {
567
606
  // A schema that refuses fields it doesn't name (a strict object):
568
607
  // MCP would refuse the result, so it goes without the field.
569
608
  const { [WAKESHIFT_FIELD]: _dropped, ...own } = out.structuredContent;
@@ -578,9 +617,14 @@ export class Attached {
578
617
  * sets the handler when the first tool is registered, perhaps after
579
618
  * attach, so a later set is wrapped too (as tasks.ts does).
580
619
  */
620
+ /** @internal Marks name as a wrapped tool, for tools/list (a framework adapter's tool, wrapped outside this handle). */
621
+ markWrapped(name) {
622
+ if (!this.wrapped.has(name))
623
+ this.wrapped.set(name, true);
624
+ }
581
625
  amendToolsList() {
582
- const handlers = this.at.server.server._requestHandlers;
583
- if (!(handlers instanceof Map))
626
+ const handlers = this.at.host.handlers();
627
+ if (!handlers)
584
628
  return; // a future SDK: schemas stay as registered
585
629
  const wrap = (h) => async (request, extra) => {
586
630
  const out = await h(request, extra);
@@ -605,16 +649,3 @@ export class Attached {
605
649
  }
606
650
  }
607
651
  }
608
- // validOutput mirrors McpServer's own check of a result against the tool's
609
- // outputSchema (validateToolOutput): one it would refuse fails the call.
610
- async function validOutput(outputSchema, result) {
611
- if (!outputSchema)
612
- return true;
613
- if (typeof result === "string" || !result.structuredContent)
614
- return false;
615
- const obj = normalizeObjectSchema(outputSchema);
616
- if (!obj)
617
- return true;
618
- return (await safeParseAsync(obj, result.structuredContent)).success;
619
- }
620
- //# sourceMappingURL=toolcall.js.map
@@ -1,5 +1,5 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import type { AnySchema } from "@modelcontextprotocol/sdk/server/zod-compat.js";
1
+ import type { AnySchema } from "./zod.js";
2
+ import type { HostContext, McpServerLike } from "./mcp.js";
3
3
  import { WakeEvent, type AttachOptions, type EventOptions, type EventTypeManifest } from "./events.js";
4
4
  import type { Attached } from "./toolcall.js";
5
5
  /** Wakeshift's production core, the default for every key but an older wst_test_ one. Override with `core` or WAKESHIFT_CORE. */
@@ -231,8 +231,12 @@ export declare class Wakeshift {
231
231
  * It returns the handle your own tools use to subscribe the conversation
232
232
  * calling them: wrap a tool's callback with attached.events, and give it
233
233
  * Wakeshift's arguments with attached.inputs.
234
+ *
235
+ * server is an McpServer from @modelcontextprotocol/server 2.x or
236
+ * @modelcontextprotocol/sdk 1.x; identify gets that SDK's own request
237
+ * context (2.x's ctx, 1.x's extra).
234
238
  */
235
- attach(server: McpServer, opts?: AttachOptions): Attached;
239
+ attach<S extends McpServerLike>(server: S, opts?: AttachOptions<HostContext<S>>): Attached<HostContext<S>>;
236
240
  /**
237
241
  * The workspace Wakeshift keeps for one of the app's users (user: the
238
242
  * app's own ID for them, as attach's identify gives it), created on first
package/dist/wakeshift.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { WakeshiftError } from "./errors.js";
2
+ import { v1Req } from "./host.js";
2
3
  import { WakeEvent, attachEvents } from "./events.js";
3
4
  import { parseKey, signAssertion, isTestKey } from "./key.js";
4
5
  import { retryRefused } from "./retry.js";
@@ -33,14 +34,9 @@ function recipientsOf(raw) {
33
34
  }
34
35
  return out;
35
36
  }
36
- // userAgentOf is the User-Agent of the HTTP request a tool call came in
37
- // ("" if there's none, e.g. over stdio).
38
- function userAgentOf(extra) {
39
- const headers = extra?.requestInfo?.headers;
40
- if (!headers)
41
- return "";
42
- const v = headers["user-agent"] ?? headers["User-Agent"];
43
- return (Array.isArray(v) ? v[0] : v) ?? "";
37
+ /** @internal The User-Agent of the HTTP request a tool call came in ("" if there's none, e.g. over stdio). */
38
+ export function userAgentOf(req) {
39
+ return req.header("user-agent") || req.header("User-Agent");
44
40
  }
45
41
  /** @internal Packs a tool call as Wakeshift takes it: empty fields left out, the agent's claims trimmed. */
46
42
  export function packCall(c) {
@@ -69,16 +65,17 @@ export function packCall(c) {
69
65
  * session_key, harness and conversation_id from its arguments.
70
66
  */
71
67
  export function toolCallOf(extra, client, user, args, tool) {
68
+ const req = v1Req(extra); // a request already read passes through
72
69
  return packCall({
73
- meta: extra?._meta,
70
+ meta: req.meta,
74
71
  clientName: client,
75
72
  user,
76
73
  session: str(args, SESSION_ARG),
77
- userAgent: userAgentOf(extra),
74
+ userAgent: userAgentOf(req),
78
75
  harness: str(args, HARNESS_ARG),
79
76
  conversation: str(args, CONVERSATION_ARG),
80
77
  tool,
81
- tasks: takesTasks(extra?._meta, extra),
78
+ tasks: takesTasks(req.meta, req),
82
79
  });
83
80
  }
84
81
  // str is a tool argument's value if it's a string.
@@ -297,12 +294,25 @@ export class Wakeshift {
297
294
  * It returns the handle your own tools use to subscribe the conversation
298
295
  * calling them: wrap a tool's callback with attached.events, and give it
299
296
  * Wakeshift's arguments with attached.inputs.
297
+ *
298
+ * server is an McpServer from @modelcontextprotocol/server 2.x or
299
+ * @modelcontextprotocol/sdk 1.x; identify gets that SDK's own request
300
+ * context (2.x's ctx, 1.x's extra).
300
301
  */
301
302
  attach(server, opts = {}) {
302
303
  const attached = attachEvents(this, server, [...this.events.values()], opts);
303
304
  this.serving = true; // the manifest is sent automatically from now on
304
305
  return attached;
305
306
  }
307
+ /**
308
+ * @internal For a framework adapter (@wakeshift/sdk/mastra, /xmcp): the
309
+ * defined events, and from now on this process serves the app's tools,
310
+ * so the manifest is sent automatically, as after attach.
311
+ */
312
+ serveEvents() {
313
+ this.serving = true;
314
+ return [...this.events.values()];
315
+ }
306
316
  /**
307
317
  * @internal Sends the manifest with the defined events, once (again after
308
318
  * a defineEvent), from a process that serves the app's tools (attach). A
@@ -409,4 +419,3 @@ export class Wakeshift {
409
419
  return new WakeshiftError(0, "unreachable", `can't reach Wakeshift at ${this.core} (${why}). Check that WAKESHIFT_CORE, or the core option, is the address of the console your key came from`, { cause: err });
410
420
  }
411
421
  }
412
- //# sourceMappingURL=wakeshift.js.map
package/dist/xmcp.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ import type { AuthInfo, CallToolResult } from "./mcp.js";
2
+ import type { ZodRawShapeCompat } from "./zod.js";
3
+ import { BUILTIN_TOOLS, type BuiltinTool, type Identity } from "./events.js";
4
+ import { type ToolCall, type WakeArgs, type WakeInputs, type WrappedResult } from "./toolcall.js";
5
+ import type { Wakeshift } from "./wakeshift.js";
6
+ /** The request context xmcp passes a tool's handler, its second argument: what Wakeshift reads of it. */
7
+ export interface XmcpExtra {
8
+ signal?: AbortSignal;
9
+ authInfo?: AuthInfo;
10
+ sessionId?: string;
11
+ _meta?: Record<string, unknown>;
12
+ requestId?: string | number;
13
+ requestInfo?: {
14
+ headers?: Record<string, string | string[] | undefined>;
15
+ };
16
+ clientInfo?: {
17
+ name?: string;
18
+ version?: string;
19
+ };
20
+ [k: string]: any;
21
+ }
22
+ /** attachXmcp's options. */
23
+ export interface XmcpAttachOptions {
24
+ /**
25
+ * Who is behind a request: { user }, your own ID for the person, from your
26
+ * own sign-in (the verified token is extra.authInfo). Without it, requests
27
+ * have no user, so personal events and list_subscriptions refuse.
28
+ */
29
+ identify?(extra: XmcpExtra): Identity | undefined;
30
+ /** Which of attach's own tools attached.tool gives (default: all three). */
31
+ tools?: readonly BuiltinTool[];
32
+ }
33
+ /** One of attach's own tools as an xmcp tool file exports it. */
34
+ export interface XmcpTool {
35
+ /** The tool's input, a zod 4 shape: `export const schema = tool.schema`. */
36
+ schema: ZodRawShapeCompat;
37
+ /** Its name, description and annotations: `export const metadata = tool.metadata`. */
38
+ metadata: {
39
+ name: string;
40
+ description: string;
41
+ annotations?: Record<string, unknown>;
42
+ };
43
+ /** Its handler: `export default tool.handler`. */
44
+ handler: (args: Record<string, unknown>, extra: XmcpExtra) => Promise<CallToolResult>;
45
+ }
46
+ /** Wakeshift in an xmcp app: what your tool files use. Make it with attachXmcp. */
47
+ export declare class XmcpAttached {
48
+ private readonly attached;
49
+ private readonly own;
50
+ /**
51
+ * A tool's schema plus Wakeshift's three optional arguments (session_key,
52
+ * harness and conversation_id): `export const schema = attached.inputs({ … })`.
53
+ */
54
+ inputs<S extends ZodRawShapeCompat>(shape: S): S & WakeInputs;
55
+ /**
56
+ * Wraps a tool's handler, for the tool file whose metadata names the same
57
+ * tool: `export default attached.events("watch_ci", async (args, call) => …)`.
58
+ * handler gets your own arguments and a ToolCall, and returns a string or
59
+ * a CallToolResult; the reply gets what Wakeshift adds for the
60
+ * conversation, and a call that fails ends the subscriptions it made. Its
61
+ * schema must come from attached.inputs.
62
+ */
63
+ events<P extends Record<string, any> = Record<string, any>>(name: string, handler: (args: P, call: ToolCall<XmcpExtra>) => WrappedResult | Promise<WrappedResult>): (args: P & WakeArgs, extra: XmcpExtra) => Promise<CallToolResult>;
64
+ /** One of attach's own tools, for a tool file of its own (src/tools/<name>.ts): its schema, metadata and handler. */
65
+ tool(name: BuiltinTool): XmcpTool;
66
+ }
67
+ /**
68
+ * Attaches Wakeshift to an xmcp app, after your last defineEvent. Returns
69
+ * what your tool files use: attached.inputs and attached.events for your
70
+ * own tools that subscribe, and attached.tool for attach's own three.
71
+ */
72
+ export declare function attachXmcp(wake: Wakeshift, opts?: XmcpAttachOptions): XmcpAttached;
73
+ export { BUILTIN_TOOLS, type BuiltinTool, type Identity, type ToolCall, type WakeArgs, type WrappedResult };