@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/dist/toolcall.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
5
|
-
import { type AnySchema, type SchemaInput, type ShapeOutput, type ZodRawShapeCompat } from "
|
|
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
|
-
|
|
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
|
-
/**
|
|
84
|
-
readonly extra:
|
|
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
|
|
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
|
-
*
|
|
134
|
-
* and conversation_id), for a wrapped tool's
|
|
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
|
-
*
|
|
140
|
-
* outputSchema
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
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<
|
|
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
|
|
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
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
213
|
-
|
|
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
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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(
|
|
454
|
+
if (dialectOf(at.clientOf(meta, identified), userAgentOf(req)) === "chatgpt")
|
|
448
455
|
return { native: true, conversation: null };
|
|
449
|
-
const caps = at.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
583
|
-
if (!
|
|
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
|
package/dist/wakeshift.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
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:
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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:
|
|
70
|
+
meta: req.meta,
|
|
74
71
|
clientName: client,
|
|
75
72
|
user,
|
|
76
73
|
session: str(args, SESSION_ARG),
|
|
77
|
-
userAgent: userAgentOf(
|
|
74
|
+
userAgent: userAgentOf(req),
|
|
78
75
|
harness: str(args, HARNESS_ARG),
|
|
79
76
|
conversation: str(args, CONVERSATION_ARG),
|
|
80
77
|
tool,
|
|
81
|
-
tasks: takesTasks(
|
|
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 };
|