@decocms/runtime 1.3.1 → 1.4.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/README.md +0 -33
- package/package.json +1 -1
- package/src/bindings/binder.ts +10 -32
- package/src/bindings.ts +1 -1
- package/src/index.ts +1 -0
- package/src/tools.ts +199 -212
package/README.md
CHANGED
|
@@ -152,37 +152,6 @@ const getUserDataTool = createPrivateTool({
|
|
|
152
152
|
});
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
### Streamable Tools
|
|
156
|
-
|
|
157
|
-
For tools that return streaming responses:
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
import { createStreamableTool } from "@decocms/runtime";
|
|
161
|
-
|
|
162
|
-
const streamDataTool = createStreamableTool({
|
|
163
|
-
id: "streamData",
|
|
164
|
-
description: "Streams data as a response",
|
|
165
|
-
inputSchema: z.object({
|
|
166
|
-
query: z.string(),
|
|
167
|
-
}),
|
|
168
|
-
streamable: true,
|
|
169
|
-
execute: async ({ context }) => {
|
|
170
|
-
// Return a streaming Response
|
|
171
|
-
const stream = new ReadableStream({
|
|
172
|
-
async start(controller) {
|
|
173
|
-
controller.enqueue(new TextEncoder().encode("Chunk 1\n"));
|
|
174
|
-
controller.enqueue(new TextEncoder().encode("Chunk 2\n"));
|
|
175
|
-
controller.close();
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
return new Response(stream, {
|
|
180
|
-
headers: { "Content-Type": "text/plain" },
|
|
181
|
-
});
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
```
|
|
185
|
-
|
|
186
155
|
### Registering Tools
|
|
187
156
|
|
|
188
157
|
Tools can be registered in multiple ways:
|
|
@@ -701,7 +670,6 @@ export default withRuntime({
|
|
|
701
670
|
### Types
|
|
702
671
|
|
|
703
672
|
- `Tool<TSchemaIn, TSchemaOut>` - Tool definition with typed input/output
|
|
704
|
-
- `StreamableTool<TSchemaIn>` - Tool that returns streaming Response
|
|
705
673
|
- `Prompt<TArgs>` - Prompt definition with typed arguments
|
|
706
674
|
- `Resource` - Resource definition
|
|
707
675
|
- `OAuthConfig` - OAuth configuration
|
|
@@ -714,7 +682,6 @@ export default withRuntime({
|
|
|
714
682
|
- `withRuntime(options)` - Create an MCP server
|
|
715
683
|
- `createTool(opts)` - Create a public tool
|
|
716
684
|
- `createPrivateTool(opts)` - Create an authenticated tool
|
|
717
|
-
- `createStreamableTool(opts)` - Create a streaming tool
|
|
718
685
|
- `createPrompt(opts)` - Create an authenticated prompt
|
|
719
686
|
- `createPublicPrompt(opts)` - Create a public prompt
|
|
720
687
|
- `createResource(opts)` - Create an authenticated resource
|
package/package.json
CHANGED
package/src/bindings/binder.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type MCPClientFetchStub,
|
|
8
8
|
type ToolBinder,
|
|
9
9
|
} from "../mcp.ts";
|
|
10
|
-
import { createPrivateTool
|
|
10
|
+
import { createPrivateTool } from "../tools.ts";
|
|
11
11
|
import { CHANNEL_BINDING } from "./channels.ts";
|
|
12
12
|
|
|
13
13
|
// ToolLike is a simplified version of the Tool interface that matches what we need for bindings
|
|
@@ -77,13 +77,6 @@ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
|
|
|
77
77
|
): MCPClientFetchStub<TDefinition> => {
|
|
78
78
|
return createMCPFetchStub<TDefinition>({
|
|
79
79
|
connection: mcpConnection,
|
|
80
|
-
streamable: binder.reduce(
|
|
81
|
-
(acc, tool) => {
|
|
82
|
-
acc[tool.name] = tool.streamable === true;
|
|
83
|
-
return acc;
|
|
84
|
-
},
|
|
85
|
-
{} as Record<string, boolean>,
|
|
86
|
-
),
|
|
87
80
|
});
|
|
88
81
|
},
|
|
89
82
|
};
|
|
@@ -99,12 +92,8 @@ export const impl = <TBinder extends Binder>(
|
|
|
99
92
|
schema: TBinder,
|
|
100
93
|
implementation: BinderImplementation<TBinder>,
|
|
101
94
|
createToolFn = createPrivateTool,
|
|
102
|
-
createStreamableToolFn = createStreamableTool,
|
|
103
95
|
) => {
|
|
104
|
-
const impl:
|
|
105
|
-
| ReturnType<typeof createToolFn>
|
|
106
|
-
| ReturnType<typeof createStreamableToolFn>
|
|
107
|
-
)[] = [];
|
|
96
|
+
const impl: ReturnType<typeof createToolFn>[] = [];
|
|
108
97
|
for (const key in schema) {
|
|
109
98
|
const toolSchema = schema[key];
|
|
110
99
|
const toolImplementation = implementation[key];
|
|
@@ -117,28 +106,17 @@ export const impl = <TBinder extends Binder>(
|
|
|
117
106
|
throw new Error(`Implementation for ${key} is required`);
|
|
118
107
|
}
|
|
119
108
|
|
|
120
|
-
const { name, handler,
|
|
109
|
+
const { name, handler, ...toolLike } = {
|
|
121
110
|
...toolSchema,
|
|
122
111
|
...toolImplementation,
|
|
123
112
|
};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}),
|
|
132
|
-
);
|
|
133
|
-
} else {
|
|
134
|
-
impl.push(
|
|
135
|
-
createToolFn({
|
|
136
|
-
...toolLike,
|
|
137
|
-
id: name,
|
|
138
|
-
execute: ({ context }) => Promise.resolve(handler(context)),
|
|
139
|
-
}),
|
|
140
|
-
);
|
|
141
|
-
}
|
|
113
|
+
impl.push(
|
|
114
|
+
createToolFn({
|
|
115
|
+
...toolLike,
|
|
116
|
+
id: name,
|
|
117
|
+
execute: ({ context }) => Promise.resolve(handler(context)),
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
142
120
|
}
|
|
143
121
|
return impl;
|
|
144
122
|
};
|
package/src/bindings.ts
CHANGED
|
@@ -161,7 +161,7 @@ export const AgentOf = () =>
|
|
|
161
161
|
thinking: AgentModelInfoSchema.optional(),
|
|
162
162
|
coding: AgentModelInfoSchema.optional(),
|
|
163
163
|
fast: AgentModelInfoSchema.optional(),
|
|
164
|
-
toolApprovalLevel: z.enum(["auto", "readonly", "plan"]).default("
|
|
164
|
+
toolApprovalLevel: z.enum(["auto", "readonly", "plan"]).default("auto"),
|
|
165
165
|
temperature: z.number().default(0.5),
|
|
166
166
|
});
|
|
167
167
|
|
package/src/index.ts
CHANGED
package/src/tools.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { z } from "zod";
|
|
|
16
16
|
import type { ZodRawShape, ZodSchema, ZodTypeAny } from "zod";
|
|
17
17
|
import { BindingRegistry, injectBindingSchemas } from "./bindings.ts";
|
|
18
18
|
import { Event, type EventHandlers } from "./events.ts";
|
|
19
|
-
import type { DefaultEnv } from "./index.ts";
|
|
19
|
+
import type { DefaultEnv, User } from "./index.ts";
|
|
20
20
|
import { State } from "./state.ts";
|
|
21
21
|
import {
|
|
22
22
|
type WorkflowDefinition,
|
|
@@ -67,25 +67,14 @@ export interface Tool<
|
|
|
67
67
|
outputSchema?: TSchemaOut;
|
|
68
68
|
execute(
|
|
69
69
|
context: ToolExecutionContext<TSchemaIn>,
|
|
70
|
+
ctx?: AppContext,
|
|
70
71
|
): TSchemaOut extends ZodSchema
|
|
71
72
|
? Promise<z.infer<TSchemaOut>>
|
|
72
73
|
: Promise<unknown>;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
|
-
*
|
|
77
|
-
*/
|
|
78
|
-
export interface StreamableTool<TSchemaIn extends ZodSchema = ZodSchema> {
|
|
79
|
-
_meta?: Record<string, unknown>;
|
|
80
|
-
id: string;
|
|
81
|
-
inputSchema: TSchemaIn;
|
|
82
|
-
streamable?: true;
|
|
83
|
-
description?: string;
|
|
84
|
-
execute(input: ToolExecutionContext<TSchemaIn>): Promise<Response>;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* CreatedTool is a permissive type that any Tool or StreamableTool can be assigned to.
|
|
77
|
+
* CreatedTool is a permissive type that any Tool can be assigned to.
|
|
89
78
|
* Uses a structural type with relaxed execute signature to allow tools with any schema.
|
|
90
79
|
*/
|
|
91
80
|
export type CreatedTool = {
|
|
@@ -95,12 +84,14 @@ export type CreatedTool = {
|
|
|
95
84
|
annotations?: ToolAnnotations;
|
|
96
85
|
inputSchema: ZodTypeAny;
|
|
97
86
|
outputSchema?: ZodTypeAny;
|
|
98
|
-
streamable?: true;
|
|
99
87
|
// Use a permissive execute signature - accepts any context shape
|
|
100
|
-
execute(
|
|
101
|
-
context:
|
|
102
|
-
|
|
103
|
-
|
|
88
|
+
execute(
|
|
89
|
+
context: {
|
|
90
|
+
context: unknown;
|
|
91
|
+
runtimeContext: AppContext;
|
|
92
|
+
},
|
|
93
|
+
ctx?: AppContext,
|
|
94
|
+
): Promise<unknown>;
|
|
104
95
|
};
|
|
105
96
|
|
|
106
97
|
// Re-export types for external use
|
|
@@ -137,6 +128,7 @@ export interface Prompt<TArgs extends PromptArgsRawShape = PromptArgsRawShape> {
|
|
|
137
128
|
argsSchema?: TArgs;
|
|
138
129
|
execute(
|
|
139
130
|
context: PromptExecutionContext<TArgs>,
|
|
131
|
+
ctx?: AppContext,
|
|
140
132
|
): Promise<GetPromptResult> | GetPromptResult;
|
|
141
133
|
}
|
|
142
134
|
|
|
@@ -150,10 +142,13 @@ export type CreatedPrompt = {
|
|
|
150
142
|
description?: string;
|
|
151
143
|
argsSchema?: PromptArgsRawShape;
|
|
152
144
|
// Use a permissive execute signature - accepts any args shape
|
|
153
|
-
execute(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
execute(
|
|
146
|
+
context: {
|
|
147
|
+
args: Record<string, string | undefined>;
|
|
148
|
+
runtimeContext: AppContext;
|
|
149
|
+
},
|
|
150
|
+
ctx?: AppContext,
|
|
151
|
+
): Promise<GetPromptResult> | GetPromptResult;
|
|
157
152
|
};
|
|
158
153
|
|
|
159
154
|
// ============================================================================
|
|
@@ -199,6 +194,7 @@ export interface Resource {
|
|
|
199
194
|
/** Handler function to read the resource content */
|
|
200
195
|
read(
|
|
201
196
|
context: ResourceExecutionContext,
|
|
197
|
+
ctx?: AppContext,
|
|
202
198
|
): Promise<ResourceContents> | ResourceContents;
|
|
203
199
|
}
|
|
204
200
|
|
|
@@ -211,48 +207,58 @@ export type CreatedResource = {
|
|
|
211
207
|
name: string;
|
|
212
208
|
description?: string;
|
|
213
209
|
mimeType?: string;
|
|
214
|
-
read(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
read(
|
|
211
|
+
context: {
|
|
212
|
+
uri: URL;
|
|
213
|
+
runtimeContext: AppContext;
|
|
214
|
+
},
|
|
215
|
+
ctx?: AppContext,
|
|
216
|
+
): Promise<ResourceContents> | ResourceContents;
|
|
218
217
|
};
|
|
219
218
|
|
|
220
219
|
/**
|
|
221
|
-
*
|
|
220
|
+
* Ensure the current request is authenticated.
|
|
221
|
+
* Reads from the per-request AppContext (AsyncLocalStorage), not from a cached env.
|
|
222
|
+
*
|
|
223
|
+
* @param ctx - Per-request AppContext from the second arg of execute/read handlers
|
|
224
|
+
* @returns The authenticated User
|
|
225
|
+
* @throws Error if no request context or user is not authenticated
|
|
226
|
+
*/
|
|
227
|
+
export function ensureAuthenticated(ctx: AppContext): User {
|
|
228
|
+
const reqCtx = ctx?.env?.MESH_REQUEST_CONTEXT;
|
|
229
|
+
if (!reqCtx) {
|
|
230
|
+
throw new Error("Unauthorized: missing request context");
|
|
231
|
+
}
|
|
232
|
+
const user = reqCtx.ensureAuthenticated();
|
|
233
|
+
if (!user) {
|
|
234
|
+
throw new Error("Unauthorized");
|
|
235
|
+
}
|
|
236
|
+
return user;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let _warnedPrivateTool = false;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @deprecated Use `createTool` with `ensureAuthenticated(ctx)` instead.
|
|
243
|
+
*
|
|
244
|
+
* Creates a private tool that ensures authentication before execution.
|
|
222
245
|
*/
|
|
223
246
|
export function createPrivateTool<
|
|
224
247
|
TSchemaIn extends ZodSchema = ZodSchema,
|
|
225
248
|
TSchemaOut extends ZodSchema | undefined = undefined,
|
|
226
249
|
>(opts: Tool<TSchemaIn, TSchemaOut>): Tool<TSchemaIn, TSchemaOut> {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
233
|
-
}
|
|
234
|
-
return execute(input);
|
|
235
|
-
};
|
|
250
|
+
if (!_warnedPrivateTool) {
|
|
251
|
+
console.warn(
|
|
252
|
+
"[runtime] createPrivateTool is deprecated. Use createTool with ensureAuthenticated(ctx) instead.",
|
|
253
|
+
);
|
|
254
|
+
_warnedPrivateTool = true;
|
|
236
255
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
streamableTool: StreamableTool<TSchemaIn>,
|
|
242
|
-
): StreamableTool<TSchemaIn> {
|
|
243
|
-
return {
|
|
244
|
-
...streamableTool,
|
|
245
|
-
execute: (input: ToolExecutionContext<TSchemaIn>) => {
|
|
246
|
-
const env = input.runtimeContext.env;
|
|
247
|
-
if (env) {
|
|
248
|
-
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
249
|
-
}
|
|
250
|
-
return streamableTool.execute({
|
|
251
|
-
...input,
|
|
252
|
-
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
253
|
-
});
|
|
254
|
-
},
|
|
256
|
+
const execute = opts.execute;
|
|
257
|
+
opts.execute = (input: ToolExecutionContext<TSchemaIn>, ctx: AppContext) => {
|
|
258
|
+
ensureAuthenticated(ctx);
|
|
259
|
+
return execute(input, ctx);
|
|
255
260
|
};
|
|
261
|
+
return createTool(opts);
|
|
256
262
|
}
|
|
257
263
|
|
|
258
264
|
export function createTool<
|
|
@@ -263,10 +269,8 @@ export function createTool<
|
|
|
263
269
|
return {
|
|
264
270
|
...opts,
|
|
265
271
|
execute: (input: ToolExecutionContext<TSchemaIn>) => {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
269
|
-
});
|
|
272
|
+
const ctx = createRuntimeContext(input.runtimeContext);
|
|
273
|
+
return opts.execute({ ...input, runtimeContext: ctx }, ctx);
|
|
270
274
|
},
|
|
271
275
|
};
|
|
272
276
|
}
|
|
@@ -280,10 +284,8 @@ export function createPublicPrompt<TArgs extends PromptArgsRawShape>(
|
|
|
280
284
|
return {
|
|
281
285
|
...opts,
|
|
282
286
|
execute: (input: PromptExecutionContext<TArgs>) => {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
286
|
-
});
|
|
287
|
+
const ctx = createRuntimeContext(input.runtimeContext);
|
|
288
|
+
return opts.execute({ ...input, runtimeContext: ctx }, ctx);
|
|
287
289
|
},
|
|
288
290
|
};
|
|
289
291
|
}
|
|
@@ -298,12 +300,9 @@ export function createPrompt<TArgs extends PromptArgsRawShape>(
|
|
|
298
300
|
const execute = opts.execute;
|
|
299
301
|
return createPublicPrompt({
|
|
300
302
|
...opts,
|
|
301
|
-
execute: (input: PromptExecutionContext<TArgs
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
305
|
-
}
|
|
306
|
-
return execute(input);
|
|
303
|
+
execute: (input: PromptExecutionContext<TArgs>, ctx: AppContext) => {
|
|
304
|
+
ensureAuthenticated(ctx);
|
|
305
|
+
return execute(input, ctx);
|
|
307
306
|
},
|
|
308
307
|
});
|
|
309
308
|
}
|
|
@@ -315,10 +314,8 @@ export function createPublicResource(opts: Resource): Resource {
|
|
|
315
314
|
return {
|
|
316
315
|
...opts,
|
|
317
316
|
read: (input: ResourceExecutionContext) => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
321
|
-
});
|
|
317
|
+
const ctx = createRuntimeContext(input.runtimeContext);
|
|
318
|
+
return opts.read({ ...input, runtimeContext: ctx }, ctx);
|
|
322
319
|
},
|
|
323
320
|
};
|
|
324
321
|
}
|
|
@@ -331,12 +328,9 @@ export function createResource(opts: Resource): Resource {
|
|
|
331
328
|
const read = opts.read;
|
|
332
329
|
return createPublicResource({
|
|
333
330
|
...opts,
|
|
334
|
-
read: (input: ResourceExecutionContext) => {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
338
|
-
}
|
|
339
|
-
return read(input);
|
|
331
|
+
read: (input: ResourceExecutionContext, ctx: AppContext) => {
|
|
332
|
+
ensureAuthenticated(ctx);
|
|
333
|
+
return read(input, ctx);
|
|
340
334
|
},
|
|
341
335
|
});
|
|
342
336
|
}
|
|
@@ -355,12 +349,6 @@ export interface Integration {
|
|
|
355
349
|
appId: string;
|
|
356
350
|
}
|
|
357
351
|
|
|
358
|
-
export function isStreamableTool(
|
|
359
|
-
tool: CreatedTool,
|
|
360
|
-
): tool is StreamableTool & CreatedTool {
|
|
361
|
-
return tool && "streamable" in tool && tool.streamable === true;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
352
|
export interface OnChangeCallback<TState> {
|
|
365
353
|
state: TState;
|
|
366
354
|
scopes: string[];
|
|
@@ -500,35 +488,38 @@ export interface CreateMCPServerOptions<
|
|
|
500
488
|
};
|
|
501
489
|
tools?:
|
|
502
490
|
| Array<
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
491
|
+
| CreatedTool
|
|
492
|
+
| ((
|
|
493
|
+
env: TEnv,
|
|
494
|
+
) =>
|
|
495
|
+
| Promise<CreatedTool>
|
|
496
|
+
| CreatedTool
|
|
497
|
+
| CreatedTool[]
|
|
498
|
+
| Promise<CreatedTool[]>)
|
|
510
499
|
>
|
|
511
500
|
| ((env: TEnv) => CreatedTool[] | Promise<CreatedTool[]>);
|
|
512
501
|
prompts?:
|
|
513
502
|
| Array<
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
503
|
+
| CreatedPrompt
|
|
504
|
+
| ((
|
|
505
|
+
env: TEnv,
|
|
506
|
+
) =>
|
|
507
|
+
| Promise<CreatedPrompt>
|
|
508
|
+
| CreatedPrompt
|
|
509
|
+
| CreatedPrompt[]
|
|
510
|
+
| Promise<CreatedPrompt[]>)
|
|
521
511
|
>
|
|
522
512
|
| ((env: TEnv) => CreatedPrompt[] | Promise<CreatedPrompt[]>);
|
|
523
513
|
resources?:
|
|
524
514
|
| Array<
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
515
|
+
| CreatedResource
|
|
516
|
+
| ((
|
|
517
|
+
env: TEnv,
|
|
518
|
+
) =>
|
|
519
|
+
| Promise<CreatedResource>
|
|
520
|
+
| CreatedResource
|
|
521
|
+
| CreatedResource[]
|
|
522
|
+
| Promise<CreatedResource[]>)
|
|
532
523
|
>
|
|
533
524
|
| ((env: TEnv) => CreatedResource[] | Promise<CreatedResource[]>);
|
|
534
525
|
workflows?:
|
|
@@ -819,7 +810,8 @@ export const createMCPServer = <
|
|
|
819
810
|
): MCPServer<TEnv, TSchema, TBindings> => {
|
|
820
811
|
// Tool/prompt/resource definitions are resolved once on first request and
|
|
821
812
|
// cached for the lifetime of the process. Tool *execution* reads per-request
|
|
822
|
-
// context from State (AsyncLocalStorage)
|
|
813
|
+
// context from State (AsyncLocalStorage) via the second `ctx` argument, so
|
|
814
|
+
// reusing definitions is safe.
|
|
823
815
|
type Registrations = {
|
|
824
816
|
tools: CreatedTool[];
|
|
825
817
|
prompts: CreatedPrompt[];
|
|
@@ -830,6 +822,51 @@ export const createMCPServer = <
|
|
|
830
822
|
let cached: Registrations | null = null;
|
|
831
823
|
let inflightResolve: Promise<Registrations> | null = null;
|
|
832
824
|
|
|
825
|
+
let _warnedFactoryDeprecation = false;
|
|
826
|
+
const warnFactoryDeprecation = () => {
|
|
827
|
+
if (!_warnedFactoryDeprecation) {
|
|
828
|
+
console.warn(
|
|
829
|
+
"[runtime] Passing factory functions to tools/prompts/resources is deprecated. " +
|
|
830
|
+
"Pass createTool()/createPrompt()/createResource() instances directly.",
|
|
831
|
+
);
|
|
832
|
+
_warnedFactoryDeprecation = true;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Check whether a value is an already-created instance (has an `id` or `name` property)
|
|
838
|
+
* rather than a factory function.
|
|
839
|
+
*/
|
|
840
|
+
const isInstance = (v: unknown): boolean =>
|
|
841
|
+
typeof v === "object" &&
|
|
842
|
+
v !== null &&
|
|
843
|
+
("id" in v || "name" in v || "uri" in v);
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Resolve an array that may contain both direct instances and factory functions.
|
|
847
|
+
* Factories are called with `bindings` and trigger a deprecation warning.
|
|
848
|
+
*/
|
|
849
|
+
async function resolveArray<T>(
|
|
850
|
+
items: Array<unknown> | undefined,
|
|
851
|
+
bindings: TEnv,
|
|
852
|
+
): Promise<T[]> {
|
|
853
|
+
if (!items) return [];
|
|
854
|
+
return (
|
|
855
|
+
await Promise.all(
|
|
856
|
+
items.flatMap(async (item) => {
|
|
857
|
+
if (isInstance(item)) {
|
|
858
|
+
return [item as T];
|
|
859
|
+
}
|
|
860
|
+
// Factory function — deprecated path
|
|
861
|
+
warnFactoryDeprecation();
|
|
862
|
+
const result = await (item as (env: TEnv) => unknown)(bindings);
|
|
863
|
+
if (Array.isArray(result)) return result as T[];
|
|
864
|
+
return [result as T];
|
|
865
|
+
}),
|
|
866
|
+
)
|
|
867
|
+
).flat();
|
|
868
|
+
}
|
|
869
|
+
|
|
833
870
|
const resolveRegistrations = async (
|
|
834
871
|
bindings: TEnv,
|
|
835
872
|
): Promise<Registrations> => {
|
|
@@ -838,25 +875,13 @@ export const createMCPServer = <
|
|
|
838
875
|
|
|
839
876
|
inflightResolve = (async (): Promise<Registrations> => {
|
|
840
877
|
try {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
return await Promise.all(
|
|
849
|
-
options.tools?.flatMap(async (tool) => {
|
|
850
|
-
const toolResult = tool(bindings);
|
|
851
|
-
const awaited = await toolResult;
|
|
852
|
-
if (Array.isArray(awaited)) {
|
|
853
|
-
return awaited;
|
|
854
|
-
}
|
|
855
|
-
return [awaited];
|
|
856
|
-
}) ?? [],
|
|
857
|
-
).then((t) => t.flat());
|
|
858
|
-
};
|
|
859
|
-
const tools = await toolsFn(bindings);
|
|
878
|
+
let tools: CreatedTool[];
|
|
879
|
+
if (typeof options.tools === "function") {
|
|
880
|
+
warnFactoryDeprecation();
|
|
881
|
+
tools = await options.tools(bindings);
|
|
882
|
+
} else {
|
|
883
|
+
tools = await resolveArray<CreatedTool>(options.tools, bindings);
|
|
884
|
+
}
|
|
860
885
|
|
|
861
886
|
const resolvedWorkflows =
|
|
862
887
|
typeof options.workflows === "function"
|
|
@@ -867,45 +892,27 @@ export const createMCPServer = <
|
|
|
867
892
|
...toolsFor<TSchema>({ ...options, workflows: resolvedWorkflows }),
|
|
868
893
|
);
|
|
869
894
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
typeof options.resources === "function"
|
|
892
|
-
? options.resources
|
|
893
|
-
: async (bindings: TEnv) => {
|
|
894
|
-
if (typeof options.resources === "function") {
|
|
895
|
-
return await options.resources(bindings);
|
|
896
|
-
}
|
|
897
|
-
return await Promise.all(
|
|
898
|
-
options.resources?.flatMap(async (resource) => {
|
|
899
|
-
const resourceResult = resource(bindings);
|
|
900
|
-
const awaited = await resourceResult;
|
|
901
|
-
if (Array.isArray(awaited)) {
|
|
902
|
-
return awaited;
|
|
903
|
-
}
|
|
904
|
-
return [awaited];
|
|
905
|
-
}) ?? [],
|
|
906
|
-
).then((r) => r.flat());
|
|
907
|
-
};
|
|
908
|
-
const resources = await resourcesFn(bindings);
|
|
895
|
+
let prompts: CreatedPrompt[];
|
|
896
|
+
if (typeof options.prompts === "function") {
|
|
897
|
+
warnFactoryDeprecation();
|
|
898
|
+
prompts = await options.prompts(bindings);
|
|
899
|
+
} else {
|
|
900
|
+
prompts = await resolveArray<CreatedPrompt>(
|
|
901
|
+
options.prompts,
|
|
902
|
+
bindings,
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
let resources: CreatedResource[];
|
|
907
|
+
if (typeof options.resources === "function") {
|
|
908
|
+
warnFactoryDeprecation();
|
|
909
|
+
resources = await options.resources(bindings);
|
|
910
|
+
} else {
|
|
911
|
+
resources = await resolveArray<CreatedResource>(
|
|
912
|
+
options.resources,
|
|
913
|
+
bindings,
|
|
914
|
+
);
|
|
915
|
+
}
|
|
909
916
|
|
|
910
917
|
const result = {
|
|
911
918
|
tools,
|
|
@@ -924,55 +931,32 @@ export const createMCPServer = <
|
|
|
924
931
|
return inflightResolve;
|
|
925
932
|
};
|
|
926
933
|
|
|
927
|
-
const registerAll = (
|
|
928
|
-
server: McpServer,
|
|
929
|
-
registrations: Registrations,
|
|
930
|
-
) => {
|
|
934
|
+
const registerAll = (server: McpServer, registrations: Registrations) => {
|
|
931
935
|
for (const tool of registrations.tools) {
|
|
932
936
|
server.registerTool(
|
|
933
937
|
tool.id,
|
|
934
938
|
{
|
|
935
|
-
_meta:
|
|
936
|
-
streamable: isStreamableTool(tool),
|
|
937
|
-
...(tool._meta ?? {}),
|
|
938
|
-
},
|
|
939
|
+
_meta: tool._meta,
|
|
939
940
|
description: tool.description,
|
|
940
941
|
annotations: tool.annotations,
|
|
941
942
|
inputSchema:
|
|
942
943
|
tool.inputSchema && "shape" in tool.inputSchema
|
|
943
944
|
? (tool.inputSchema.shape as ZodRawShape)
|
|
944
945
|
: z.object({}).shape,
|
|
945
|
-
outputSchema:
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
"shape" in tool.outputSchema
|
|
946
|
+
outputSchema:
|
|
947
|
+
tool.outputSchema &&
|
|
948
|
+
typeof tool.outputSchema === "object" &&
|
|
949
|
+
"shape" in tool.outputSchema
|
|
950
950
|
? (tool.outputSchema.shape as ZodRawShape)
|
|
951
951
|
: undefined,
|
|
952
952
|
},
|
|
953
953
|
async (args) => {
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
runtimeContext:
|
|
957
|
-
|
|
954
|
+
const ctx = createRuntimeContext();
|
|
955
|
+
const result = await tool.execute(
|
|
956
|
+
{ context: args, runtimeContext: ctx },
|
|
957
|
+
ctx,
|
|
958
|
+
);
|
|
958
959
|
|
|
959
|
-
// For streamable tools, the Response is handled at the transport layer.
|
|
960
|
-
// Do NOT call result.bytes() — it buffers the entire body in memory.
|
|
961
|
-
if (isStreamableTool(tool) && result instanceof Response) {
|
|
962
|
-
return {
|
|
963
|
-
structuredContent: {
|
|
964
|
-
streamable: true,
|
|
965
|
-
status: result.status,
|
|
966
|
-
statusText: result.statusText,
|
|
967
|
-
},
|
|
968
|
-
content: [
|
|
969
|
-
{
|
|
970
|
-
type: "text",
|
|
971
|
-
text: `Streaming response: ${result.status} ${result.statusText}`,
|
|
972
|
-
},
|
|
973
|
-
],
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
960
|
return {
|
|
977
961
|
structuredContent: result as Record<string, unknown>,
|
|
978
962
|
content: [
|
|
@@ -997,10 +981,14 @@ export const createMCPServer = <
|
|
|
997
981
|
: z.object({}).shape,
|
|
998
982
|
},
|
|
999
983
|
async (args) => {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
984
|
+
const ctx = createRuntimeContext();
|
|
985
|
+
return await prompt.execute(
|
|
986
|
+
{
|
|
987
|
+
args: args as Record<string, string | undefined>,
|
|
988
|
+
runtimeContext: ctx,
|
|
989
|
+
},
|
|
990
|
+
ctx,
|
|
991
|
+
);
|
|
1004
992
|
},
|
|
1005
993
|
);
|
|
1006
994
|
}
|
|
@@ -1014,10 +1002,8 @@ export const createMCPServer = <
|
|
|
1014
1002
|
mimeType: resource.mimeType,
|
|
1015
1003
|
},
|
|
1016
1004
|
async (uri) => {
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
runtimeContext: createRuntimeContext(),
|
|
1020
|
-
});
|
|
1005
|
+
const ctx = createRuntimeContext();
|
|
1006
|
+
const result = await resource.read({ uri, runtimeContext: ctx }, ctx);
|
|
1021
1007
|
|
|
1022
1008
|
const meta =
|
|
1023
1009
|
(result as { _meta?: Record<string, unknown> | null })._meta ??
|
|
@@ -1114,7 +1100,10 @@ export const createMCPServer = <
|
|
|
1114
1100
|
// finishes (server sent the response) or the client disconnects
|
|
1115
1101
|
// (cancel), the server and transport are always cleaned up.
|
|
1116
1102
|
const { readable, writable } = new TransformStream();
|
|
1117
|
-
response.body
|
|
1103
|
+
response.body
|
|
1104
|
+
.pipeTo(writable)
|
|
1105
|
+
.catch(() => {})
|
|
1106
|
+
.finally(cleanup);
|
|
1118
1107
|
|
|
1119
1108
|
return new Response(readable, {
|
|
1120
1109
|
status: response.status,
|
|
@@ -1144,10 +1133,8 @@ export const createMCPServer = <
|
|
|
1144
1133
|
);
|
|
1145
1134
|
}
|
|
1146
1135
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
runtimeContext: createRuntimeContext(),
|
|
1150
|
-
});
|
|
1136
|
+
const ctx = createRuntimeContext();
|
|
1137
|
+
return execute({ context: toolCallInput, runtimeContext: ctx }, ctx);
|
|
1151
1138
|
};
|
|
1152
1139
|
|
|
1153
1140
|
return {
|