@decocms/runtime 1.3.1 → 1.5.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 +4 -1
- package/src/decopilot.ts +8 -2
- package/src/index.ts +1 -0
- package/src/tools.ts +216 -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,10 @@ export const AgentOf = () =>
|
|
|
161
161
|
thinking: AgentModelInfoSchema.optional(),
|
|
162
162
|
coding: AgentModelInfoSchema.optional(),
|
|
163
163
|
fast: AgentModelInfoSchema.optional(),
|
|
164
|
-
toolApprovalLevel: z.enum(["auto", "readonly"
|
|
164
|
+
toolApprovalLevel: z.enum(["auto", "readonly"]).default("auto"),
|
|
165
|
+
mode: z
|
|
166
|
+
.enum(["default", "plan", "web-search", "gen-image"])
|
|
167
|
+
.default("default"),
|
|
165
168
|
temperature: z.number().default(0.5),
|
|
166
169
|
});
|
|
167
170
|
|
package/src/decopilot.ts
CHANGED
|
@@ -31,7 +31,9 @@ export interface AgentBindingConfig {
|
|
|
31
31
|
thinking?: AgentModelInfo;
|
|
32
32
|
coding?: AgentModelInfo;
|
|
33
33
|
fast?: AgentModelInfo;
|
|
34
|
-
toolApprovalLevel?: "auto" | "readonly"
|
|
34
|
+
toolApprovalLevel?: "auto" | "readonly";
|
|
35
|
+
/** Decopilot stream mode — default, plan, web-search, gen-image */
|
|
36
|
+
mode?: "default" | "plan" | "web-search" | "gen-image";
|
|
35
37
|
temperature?: number;
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -45,7 +47,9 @@ export interface AgentStreamParams {
|
|
|
45
47
|
thinking?: AgentModelInfo;
|
|
46
48
|
coding?: AgentModelInfo;
|
|
47
49
|
fast?: AgentModelInfo;
|
|
48
|
-
toolApprovalLevel?: "auto" | "readonly"
|
|
50
|
+
toolApprovalLevel?: "auto" | "readonly";
|
|
51
|
+
/** Decopilot stream mode — default, plan, web-search, gen-image */
|
|
52
|
+
mode?: "default" | "plan" | "web-search" | "gen-image";
|
|
49
53
|
temperature?: number;
|
|
50
54
|
memory?: { windowSize: number; thread_id: string };
|
|
51
55
|
thread_id?: string;
|
|
@@ -126,6 +130,7 @@ export async function streamAgent(
|
|
|
126
130
|
agent: { id: agentId },
|
|
127
131
|
temperature: params.temperature ?? config.temperature,
|
|
128
132
|
toolApprovalLevel: params.toolApprovalLevel ?? config.toolApprovalLevel,
|
|
133
|
+
mode: params.mode ?? config.mode ?? "default",
|
|
129
134
|
...(params.memory ? { memory: params.memory } : {}),
|
|
130
135
|
...(params.thread_id ? { thread_id: params.thread_id } : {}),
|
|
131
136
|
};
|
|
@@ -188,6 +193,7 @@ export function createDecopilotClient(options: DecopilotClientOptions) {
|
|
|
188
193
|
coding: request.coding,
|
|
189
194
|
fast: request.fast,
|
|
190
195
|
toolApprovalLevel: request.toolApprovalLevel,
|
|
196
|
+
mode: request.mode,
|
|
191
197
|
temperature: request.temperature,
|
|
192
198
|
};
|
|
193
199
|
return streamAgent(streamUrl, token, config, request, opts);
|
package/src/index.ts
CHANGED
package/src/tools.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
9
|
import { WebStandardStreamableHTTPServerTransport as HttpServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
10
10
|
import type {
|
|
11
|
+
CallToolResult,
|
|
11
12
|
GetPromptResult,
|
|
12
13
|
Implementation,
|
|
13
14
|
ToolAnnotations,
|
|
@@ -16,7 +17,7 @@ import { z } from "zod";
|
|
|
16
17
|
import type { ZodRawShape, ZodSchema, ZodTypeAny } from "zod";
|
|
17
18
|
import { BindingRegistry, injectBindingSchemas } from "./bindings.ts";
|
|
18
19
|
import { Event, type EventHandlers } from "./events.ts";
|
|
19
|
-
import type { DefaultEnv } from "./index.ts";
|
|
20
|
+
import type { DefaultEnv, User } from "./index.ts";
|
|
20
21
|
import { State } from "./state.ts";
|
|
21
22
|
import {
|
|
22
23
|
type WorkflowDefinition,
|
|
@@ -67,25 +68,14 @@ export interface Tool<
|
|
|
67
68
|
outputSchema?: TSchemaOut;
|
|
68
69
|
execute(
|
|
69
70
|
context: ToolExecutionContext<TSchemaIn>,
|
|
71
|
+
ctx?: AppContext,
|
|
70
72
|
): TSchemaOut extends ZodSchema
|
|
71
73
|
? Promise<z.infer<TSchemaOut>>
|
|
72
74
|
: Promise<unknown>;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/**
|
|
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.
|
|
78
|
+
* CreatedTool is a permissive type that any Tool can be assigned to.
|
|
89
79
|
* Uses a structural type with relaxed execute signature to allow tools with any schema.
|
|
90
80
|
*/
|
|
91
81
|
export type CreatedTool = {
|
|
@@ -95,12 +85,14 @@ export type CreatedTool = {
|
|
|
95
85
|
annotations?: ToolAnnotations;
|
|
96
86
|
inputSchema: ZodTypeAny;
|
|
97
87
|
outputSchema?: ZodTypeAny;
|
|
98
|
-
streamable?: true;
|
|
99
88
|
// Use a permissive execute signature - accepts any context shape
|
|
100
|
-
execute(
|
|
101
|
-
context:
|
|
102
|
-
|
|
103
|
-
|
|
89
|
+
execute(
|
|
90
|
+
context: {
|
|
91
|
+
context: unknown;
|
|
92
|
+
runtimeContext: AppContext;
|
|
93
|
+
},
|
|
94
|
+
ctx?: AppContext,
|
|
95
|
+
): Promise<unknown>;
|
|
104
96
|
};
|
|
105
97
|
|
|
106
98
|
// Re-export types for external use
|
|
@@ -137,6 +129,7 @@ export interface Prompt<TArgs extends PromptArgsRawShape = PromptArgsRawShape> {
|
|
|
137
129
|
argsSchema?: TArgs;
|
|
138
130
|
execute(
|
|
139
131
|
context: PromptExecutionContext<TArgs>,
|
|
132
|
+
ctx?: AppContext,
|
|
140
133
|
): Promise<GetPromptResult> | GetPromptResult;
|
|
141
134
|
}
|
|
142
135
|
|
|
@@ -150,10 +143,13 @@ export type CreatedPrompt = {
|
|
|
150
143
|
description?: string;
|
|
151
144
|
argsSchema?: PromptArgsRawShape;
|
|
152
145
|
// Use a permissive execute signature - accepts any args shape
|
|
153
|
-
execute(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
146
|
+
execute(
|
|
147
|
+
context: {
|
|
148
|
+
args: Record<string, string | undefined>;
|
|
149
|
+
runtimeContext: AppContext;
|
|
150
|
+
},
|
|
151
|
+
ctx?: AppContext,
|
|
152
|
+
): Promise<GetPromptResult> | GetPromptResult;
|
|
157
153
|
};
|
|
158
154
|
|
|
159
155
|
// ============================================================================
|
|
@@ -199,6 +195,7 @@ export interface Resource {
|
|
|
199
195
|
/** Handler function to read the resource content */
|
|
200
196
|
read(
|
|
201
197
|
context: ResourceExecutionContext,
|
|
198
|
+
ctx?: AppContext,
|
|
202
199
|
): Promise<ResourceContents> | ResourceContents;
|
|
203
200
|
}
|
|
204
201
|
|
|
@@ -211,48 +208,58 @@ export type CreatedResource = {
|
|
|
211
208
|
name: string;
|
|
212
209
|
description?: string;
|
|
213
210
|
mimeType?: string;
|
|
214
|
-
read(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
211
|
+
read(
|
|
212
|
+
context: {
|
|
213
|
+
uri: URL;
|
|
214
|
+
runtimeContext: AppContext;
|
|
215
|
+
},
|
|
216
|
+
ctx?: AppContext,
|
|
217
|
+
): Promise<ResourceContents> | ResourceContents;
|
|
218
218
|
};
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
|
-
*
|
|
221
|
+
* Ensure the current request is authenticated.
|
|
222
|
+
* Reads from the per-request AppContext (AsyncLocalStorage), not from a cached env.
|
|
223
|
+
*
|
|
224
|
+
* @param ctx - Per-request AppContext from the second arg of execute/read handlers
|
|
225
|
+
* @returns The authenticated User
|
|
226
|
+
* @throws Error if no request context or user is not authenticated
|
|
227
|
+
*/
|
|
228
|
+
export function ensureAuthenticated(ctx: AppContext): User {
|
|
229
|
+
const reqCtx = ctx?.env?.MESH_REQUEST_CONTEXT;
|
|
230
|
+
if (!reqCtx) {
|
|
231
|
+
throw new Error("Unauthorized: missing request context");
|
|
232
|
+
}
|
|
233
|
+
const user = reqCtx.ensureAuthenticated();
|
|
234
|
+
if (!user) {
|
|
235
|
+
throw new Error("Unauthorized");
|
|
236
|
+
}
|
|
237
|
+
return user;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let _warnedPrivateTool = false;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @deprecated Use `createTool` with `ensureAuthenticated(ctx)` instead.
|
|
244
|
+
*
|
|
245
|
+
* Creates a private tool that ensures authentication before execution.
|
|
222
246
|
*/
|
|
223
247
|
export function createPrivateTool<
|
|
224
248
|
TSchemaIn extends ZodSchema = ZodSchema,
|
|
225
249
|
TSchemaOut extends ZodSchema | undefined = undefined,
|
|
226
250
|
>(opts: Tool<TSchemaIn, TSchemaOut>): Tool<TSchemaIn, TSchemaOut> {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
233
|
-
}
|
|
234
|
-
return execute(input);
|
|
235
|
-
};
|
|
251
|
+
if (!_warnedPrivateTool) {
|
|
252
|
+
console.warn(
|
|
253
|
+
"[runtime] createPrivateTool is deprecated. Use createTool with ensureAuthenticated(ctx) instead.",
|
|
254
|
+
);
|
|
255
|
+
_warnedPrivateTool = true;
|
|
236
256
|
}
|
|
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
|
-
},
|
|
257
|
+
const execute = opts.execute;
|
|
258
|
+
opts.execute = (input: ToolExecutionContext<TSchemaIn>, ctx: AppContext) => {
|
|
259
|
+
ensureAuthenticated(ctx);
|
|
260
|
+
return execute(input, ctx);
|
|
255
261
|
};
|
|
262
|
+
return createTool(opts);
|
|
256
263
|
}
|
|
257
264
|
|
|
258
265
|
export function createTool<
|
|
@@ -263,10 +270,8 @@ export function createTool<
|
|
|
263
270
|
return {
|
|
264
271
|
...opts,
|
|
265
272
|
execute: (input: ToolExecutionContext<TSchemaIn>) => {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
269
|
-
});
|
|
273
|
+
const ctx = createRuntimeContext(input.runtimeContext);
|
|
274
|
+
return opts.execute({ ...input, runtimeContext: ctx }, ctx);
|
|
270
275
|
},
|
|
271
276
|
};
|
|
272
277
|
}
|
|
@@ -280,10 +285,8 @@ export function createPublicPrompt<TArgs extends PromptArgsRawShape>(
|
|
|
280
285
|
return {
|
|
281
286
|
...opts,
|
|
282
287
|
execute: (input: PromptExecutionContext<TArgs>) => {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
286
|
-
});
|
|
288
|
+
const ctx = createRuntimeContext(input.runtimeContext);
|
|
289
|
+
return opts.execute({ ...input, runtimeContext: ctx }, ctx);
|
|
287
290
|
},
|
|
288
291
|
};
|
|
289
292
|
}
|
|
@@ -298,12 +301,9 @@ export function createPrompt<TArgs extends PromptArgsRawShape>(
|
|
|
298
301
|
const execute = opts.execute;
|
|
299
302
|
return createPublicPrompt({
|
|
300
303
|
...opts,
|
|
301
|
-
execute: (input: PromptExecutionContext<TArgs
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
305
|
-
}
|
|
306
|
-
return execute(input);
|
|
304
|
+
execute: (input: PromptExecutionContext<TArgs>, ctx: AppContext) => {
|
|
305
|
+
ensureAuthenticated(ctx);
|
|
306
|
+
return execute(input, ctx);
|
|
307
307
|
},
|
|
308
308
|
});
|
|
309
309
|
}
|
|
@@ -315,10 +315,8 @@ export function createPublicResource(opts: Resource): Resource {
|
|
|
315
315
|
return {
|
|
316
316
|
...opts,
|
|
317
317
|
read: (input: ResourceExecutionContext) => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
321
|
-
});
|
|
318
|
+
const ctx = createRuntimeContext(input.runtimeContext);
|
|
319
|
+
return opts.read({ ...input, runtimeContext: ctx }, ctx);
|
|
322
320
|
},
|
|
323
321
|
};
|
|
324
322
|
}
|
|
@@ -331,12 +329,9 @@ export function createResource(opts: Resource): Resource {
|
|
|
331
329
|
const read = opts.read;
|
|
332
330
|
return createPublicResource({
|
|
333
331
|
...opts,
|
|
334
|
-
read: (input: ResourceExecutionContext) => {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
338
|
-
}
|
|
339
|
-
return read(input);
|
|
332
|
+
read: (input: ResourceExecutionContext, ctx: AppContext) => {
|
|
333
|
+
ensureAuthenticated(ctx);
|
|
334
|
+
return read(input, ctx);
|
|
340
335
|
},
|
|
341
336
|
});
|
|
342
337
|
}
|
|
@@ -355,12 +350,6 @@ export interface Integration {
|
|
|
355
350
|
appId: string;
|
|
356
351
|
}
|
|
357
352
|
|
|
358
|
-
export function isStreamableTool(
|
|
359
|
-
tool: CreatedTool,
|
|
360
|
-
): tool is StreamableTool & CreatedTool {
|
|
361
|
-
return tool && "streamable" in tool && tool.streamable === true;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
353
|
export interface OnChangeCallback<TState> {
|
|
365
354
|
state: TState;
|
|
366
355
|
scopes: string[];
|
|
@@ -500,35 +489,38 @@ export interface CreateMCPServerOptions<
|
|
|
500
489
|
};
|
|
501
490
|
tools?:
|
|
502
491
|
| Array<
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
492
|
+
| CreatedTool
|
|
493
|
+
| ((
|
|
494
|
+
env: TEnv,
|
|
495
|
+
) =>
|
|
496
|
+
| Promise<CreatedTool>
|
|
497
|
+
| CreatedTool
|
|
498
|
+
| CreatedTool[]
|
|
499
|
+
| Promise<CreatedTool[]>)
|
|
510
500
|
>
|
|
511
501
|
| ((env: TEnv) => CreatedTool[] | Promise<CreatedTool[]>);
|
|
512
502
|
prompts?:
|
|
513
503
|
| Array<
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
504
|
+
| CreatedPrompt
|
|
505
|
+
| ((
|
|
506
|
+
env: TEnv,
|
|
507
|
+
) =>
|
|
508
|
+
| Promise<CreatedPrompt>
|
|
509
|
+
| CreatedPrompt
|
|
510
|
+
| CreatedPrompt[]
|
|
511
|
+
| Promise<CreatedPrompt[]>)
|
|
521
512
|
>
|
|
522
513
|
| ((env: TEnv) => CreatedPrompt[] | Promise<CreatedPrompt[]>);
|
|
523
514
|
resources?:
|
|
524
515
|
| Array<
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
516
|
+
| CreatedResource
|
|
517
|
+
| ((
|
|
518
|
+
env: TEnv,
|
|
519
|
+
) =>
|
|
520
|
+
| Promise<CreatedResource>
|
|
521
|
+
| CreatedResource
|
|
522
|
+
| CreatedResource[]
|
|
523
|
+
| Promise<CreatedResource[]>)
|
|
532
524
|
>
|
|
533
525
|
| ((env: TEnv) => CreatedResource[] | Promise<CreatedResource[]>);
|
|
534
526
|
workflows?:
|
|
@@ -819,7 +811,8 @@ export const createMCPServer = <
|
|
|
819
811
|
): MCPServer<TEnv, TSchema, TBindings> => {
|
|
820
812
|
// Tool/prompt/resource definitions are resolved once on first request and
|
|
821
813
|
// cached for the lifetime of the process. Tool *execution* reads per-request
|
|
822
|
-
// context from State (AsyncLocalStorage)
|
|
814
|
+
// context from State (AsyncLocalStorage) via the second `ctx` argument, so
|
|
815
|
+
// reusing definitions is safe.
|
|
823
816
|
type Registrations = {
|
|
824
817
|
tools: CreatedTool[];
|
|
825
818
|
prompts: CreatedPrompt[];
|
|
@@ -830,6 +823,51 @@ export const createMCPServer = <
|
|
|
830
823
|
let cached: Registrations | null = null;
|
|
831
824
|
let inflightResolve: Promise<Registrations> | null = null;
|
|
832
825
|
|
|
826
|
+
let _warnedFactoryDeprecation = false;
|
|
827
|
+
const warnFactoryDeprecation = () => {
|
|
828
|
+
if (!_warnedFactoryDeprecation) {
|
|
829
|
+
console.warn(
|
|
830
|
+
"[runtime] Passing factory functions to tools/prompts/resources is deprecated. " +
|
|
831
|
+
"Pass createTool()/createPrompt()/createResource() instances directly.",
|
|
832
|
+
);
|
|
833
|
+
_warnedFactoryDeprecation = true;
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Check whether a value is an already-created instance (has an `id` or `name` property)
|
|
839
|
+
* rather than a factory function.
|
|
840
|
+
*/
|
|
841
|
+
const isInstance = (v: unknown): boolean =>
|
|
842
|
+
typeof v === "object" &&
|
|
843
|
+
v !== null &&
|
|
844
|
+
("id" in v || "name" in v || "uri" in v);
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Resolve an array that may contain both direct instances and factory functions.
|
|
848
|
+
* Factories are called with `bindings` and trigger a deprecation warning.
|
|
849
|
+
*/
|
|
850
|
+
async function resolveArray<T>(
|
|
851
|
+
items: Array<unknown> | undefined,
|
|
852
|
+
bindings: TEnv,
|
|
853
|
+
): Promise<T[]> {
|
|
854
|
+
if (!items) return [];
|
|
855
|
+
return (
|
|
856
|
+
await Promise.all(
|
|
857
|
+
items.flatMap(async (item) => {
|
|
858
|
+
if (isInstance(item)) {
|
|
859
|
+
return [item as T];
|
|
860
|
+
}
|
|
861
|
+
// Factory function — deprecated path
|
|
862
|
+
warnFactoryDeprecation();
|
|
863
|
+
const result = await (item as (env: TEnv) => unknown)(bindings);
|
|
864
|
+
if (Array.isArray(result)) return result as T[];
|
|
865
|
+
return [result as T];
|
|
866
|
+
}),
|
|
867
|
+
)
|
|
868
|
+
).flat();
|
|
869
|
+
}
|
|
870
|
+
|
|
833
871
|
const resolveRegistrations = async (
|
|
834
872
|
bindings: TEnv,
|
|
835
873
|
): Promise<Registrations> => {
|
|
@@ -838,25 +876,13 @@ export const createMCPServer = <
|
|
|
838
876
|
|
|
839
877
|
inflightResolve = (async (): Promise<Registrations> => {
|
|
840
878
|
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);
|
|
879
|
+
let tools: CreatedTool[];
|
|
880
|
+
if (typeof options.tools === "function") {
|
|
881
|
+
warnFactoryDeprecation();
|
|
882
|
+
tools = await options.tools(bindings);
|
|
883
|
+
} else {
|
|
884
|
+
tools = await resolveArray<CreatedTool>(options.tools, bindings);
|
|
885
|
+
}
|
|
860
886
|
|
|
861
887
|
const resolvedWorkflows =
|
|
862
888
|
typeof options.workflows === "function"
|
|
@@ -867,45 +893,27 @@ export const createMCPServer = <
|
|
|
867
893
|
...toolsFor<TSchema>({ ...options, workflows: resolvedWorkflows }),
|
|
868
894
|
);
|
|
869
895
|
|
|
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);
|
|
896
|
+
let prompts: CreatedPrompt[];
|
|
897
|
+
if (typeof options.prompts === "function") {
|
|
898
|
+
warnFactoryDeprecation();
|
|
899
|
+
prompts = await options.prompts(bindings);
|
|
900
|
+
} else {
|
|
901
|
+
prompts = await resolveArray<CreatedPrompt>(
|
|
902
|
+
options.prompts,
|
|
903
|
+
bindings,
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
let resources: CreatedResource[];
|
|
908
|
+
if (typeof options.resources === "function") {
|
|
909
|
+
warnFactoryDeprecation();
|
|
910
|
+
resources = await options.resources(bindings);
|
|
911
|
+
} else {
|
|
912
|
+
resources = await resolveArray<CreatedResource>(
|
|
913
|
+
options.resources,
|
|
914
|
+
bindings,
|
|
915
|
+
);
|
|
916
|
+
}
|
|
909
917
|
|
|
910
918
|
const result = {
|
|
911
919
|
tools,
|
|
@@ -924,55 +932,48 @@ export const createMCPServer = <
|
|
|
924
932
|
return inflightResolve;
|
|
925
933
|
};
|
|
926
934
|
|
|
927
|
-
const registerAll = (
|
|
928
|
-
server: McpServer,
|
|
929
|
-
registrations: Registrations,
|
|
930
|
-
) => {
|
|
935
|
+
const registerAll = (server: McpServer, registrations: Registrations) => {
|
|
931
936
|
for (const tool of registrations.tools) {
|
|
932
937
|
server.registerTool(
|
|
933
938
|
tool.id,
|
|
934
939
|
{
|
|
935
|
-
_meta:
|
|
936
|
-
streamable: isStreamableTool(tool),
|
|
937
|
-
...(tool._meta ?? {}),
|
|
938
|
-
},
|
|
940
|
+
_meta: tool._meta,
|
|
939
941
|
description: tool.description,
|
|
940
942
|
annotations: tool.annotations,
|
|
941
943
|
inputSchema:
|
|
942
944
|
tool.inputSchema && "shape" in tool.inputSchema
|
|
943
945
|
? (tool.inputSchema.shape as ZodRawShape)
|
|
944
946
|
: z.object({}).shape,
|
|
945
|
-
outputSchema:
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
"shape" in tool.outputSchema
|
|
947
|
+
outputSchema:
|
|
948
|
+
tool.outputSchema &&
|
|
949
|
+
typeof tool.outputSchema === "object" &&
|
|
950
|
+
"shape" in tool.outputSchema
|
|
950
951
|
? (tool.outputSchema.shape as ZodRawShape)
|
|
951
952
|
: undefined,
|
|
952
953
|
},
|
|
953
954
|
async (args) => {
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
runtimeContext:
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
};
|
|
955
|
+
const ctx = createRuntimeContext();
|
|
956
|
+
const result = await tool.execute(
|
|
957
|
+
{ context: args, runtimeContext: ctx },
|
|
958
|
+
ctx,
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
if (
|
|
962
|
+
result != null &&
|
|
963
|
+
typeof result === "object" &&
|
|
964
|
+
"content" in result &&
|
|
965
|
+
Array.isArray(result.content) &&
|
|
966
|
+
result.content.every(
|
|
967
|
+
(item: unknown) =>
|
|
968
|
+
item != null &&
|
|
969
|
+
typeof item === "object" &&
|
|
970
|
+
"type" in item &&
|
|
971
|
+
typeof (item as Record<string, unknown>).type === "string",
|
|
972
|
+
)
|
|
973
|
+
) {
|
|
974
|
+
return result as CallToolResult;
|
|
975
975
|
}
|
|
976
|
+
|
|
976
977
|
return {
|
|
977
978
|
structuredContent: result as Record<string, unknown>,
|
|
978
979
|
content: [
|
|
@@ -997,10 +998,14 @@ export const createMCPServer = <
|
|
|
997
998
|
: z.object({}).shape,
|
|
998
999
|
},
|
|
999
1000
|
async (args) => {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1001
|
+
const ctx = createRuntimeContext();
|
|
1002
|
+
return await prompt.execute(
|
|
1003
|
+
{
|
|
1004
|
+
args: args as Record<string, string | undefined>,
|
|
1005
|
+
runtimeContext: ctx,
|
|
1006
|
+
},
|
|
1007
|
+
ctx,
|
|
1008
|
+
);
|
|
1004
1009
|
},
|
|
1005
1010
|
);
|
|
1006
1011
|
}
|
|
@@ -1014,10 +1019,8 @@ export const createMCPServer = <
|
|
|
1014
1019
|
mimeType: resource.mimeType,
|
|
1015
1020
|
},
|
|
1016
1021
|
async (uri) => {
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
runtimeContext: createRuntimeContext(),
|
|
1020
|
-
});
|
|
1022
|
+
const ctx = createRuntimeContext();
|
|
1023
|
+
const result = await resource.read({ uri, runtimeContext: ctx }, ctx);
|
|
1021
1024
|
|
|
1022
1025
|
const meta =
|
|
1023
1026
|
(result as { _meta?: Record<string, unknown> | null })._meta ??
|
|
@@ -1114,7 +1117,10 @@ export const createMCPServer = <
|
|
|
1114
1117
|
// finishes (server sent the response) or the client disconnects
|
|
1115
1118
|
// (cancel), the server and transport are always cleaned up.
|
|
1116
1119
|
const { readable, writable } = new TransformStream();
|
|
1117
|
-
response.body
|
|
1120
|
+
response.body
|
|
1121
|
+
.pipeTo(writable)
|
|
1122
|
+
.catch(() => {})
|
|
1123
|
+
.finally(cleanup);
|
|
1118
1124
|
|
|
1119
1125
|
return new Response(readable, {
|
|
1120
1126
|
status: response.status,
|
|
@@ -1144,10 +1150,8 @@ export const createMCPServer = <
|
|
|
1144
1150
|
);
|
|
1145
1151
|
}
|
|
1146
1152
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
runtimeContext: createRuntimeContext(),
|
|
1150
|
-
});
|
|
1153
|
+
const ctx = createRuntimeContext();
|
|
1154
|
+
return execute({ context: toolCallInput, runtimeContext: ctx }, ctx);
|
|
1151
1155
|
};
|
|
1152
1156
|
|
|
1153
1157
|
return {
|