@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/runtime",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",
@@ -7,7 +7,7 @@ import {
7
7
  type MCPClientFetchStub,
8
8
  type ToolBinder,
9
9
  } from "../mcp.ts";
10
- import { createPrivateTool, createStreamableTool } from "../tools.ts";
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, streamable, ...toolLike } = {
109
+ const { name, handler, ...toolLike } = {
121
110
  ...toolSchema,
122
111
  ...toolImplementation,
123
112
  };
124
- if (streamable) {
125
- impl.push(
126
- createStreamableToolFn({
127
- ...toolLike,
128
- streamable,
129
- id: name,
130
- execute: ({ context }) => Promise.resolve(handler(context)),
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("readonly"),
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
@@ -29,6 +29,7 @@ export {
29
29
  type ResourceContents,
30
30
  type CreatedResource,
31
31
  type WorkflowDefinition,
32
+ ensureAuthenticated,
32
33
  } from "./tools.ts";
33
34
  export { createWorkflow } from "./workflows.ts";
34
35
  import type { Binding } from "./wrangler.ts";
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
- * Streamable tool interface for tools that return Response streams.
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(context: {
101
- context: unknown;
102
- runtimeContext: AppContext;
103
- }): Promise<unknown>;
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(context: {
154
- args: Record<string, string | undefined>;
155
- runtimeContext: AppContext;
156
- }): Promise<GetPromptResult> | GetPromptResult;
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(context: {
215
- uri: URL;
216
- runtimeContext: AppContext;
217
- }): Promise<ResourceContents> | ResourceContents;
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
- * creates a private tool that always ensure for athentication before being executed
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
- const execute = opts.execute;
228
- if (typeof execute === "function") {
229
- opts.execute = (input: ToolExecutionContext<TSchemaIn>) => {
230
- const env = input.runtimeContext.env;
231
- if (env) {
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
- return createTool(opts);
238
- }
239
-
240
- export function createStreamableTool<TSchemaIn extends ZodSchema = ZodSchema>(
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
- return opts.execute({
267
- ...input,
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
- return opts.execute({
284
- ...input,
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
- const env = input.runtimeContext.env;
303
- if (env) {
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
- return opts.read({
319
- ...input,
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
- const env = input.runtimeContext.env;
336
- if (env) {
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
- env: TEnv,
505
- ) =>
506
- | Promise<CreatedTool>
507
- | CreatedTool
508
- | CreatedTool[]
509
- | Promise<CreatedTool[]>
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
- env: TEnv,
516
- ) =>
517
- | Promise<CreatedPrompt>
518
- | CreatedPrompt
519
- | CreatedPrompt[]
520
- | Promise<CreatedPrompt[]>
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
- env: TEnv,
527
- ) =>
528
- | Promise<CreatedResource>
529
- | CreatedResource
530
- | CreatedResource[]
531
- | Promise<CreatedResource[]>
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), so reusing definitions is safe.
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
- const toolsFn =
842
- typeof options.tools === "function"
843
- ? options.tools
844
- : async (bindings: TEnv) => {
845
- if (typeof options.tools === "function") {
846
- return await options.tools(bindings);
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
- const promptsFn =
871
- typeof options.prompts === "function"
872
- ? options.prompts
873
- : async (bindings: TEnv) => {
874
- if (typeof options.prompts === "function") {
875
- return await options.prompts(bindings);
876
- }
877
- return await Promise.all(
878
- options.prompts?.flatMap(async (prompt) => {
879
- const promptResult = prompt(bindings);
880
- const awaited = await promptResult;
881
- if (Array.isArray(awaited)) {
882
- return awaited;
883
- }
884
- return [awaited];
885
- }) ?? [],
886
- ).then((p) => p.flat());
887
- };
888
- const prompts = await promptsFn(bindings);
889
-
890
- const resourcesFn =
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: isStreamableTool(tool)
946
- ? z.object({ bytes: z.record(z.string(), z.number()) }).shape
947
- : tool.outputSchema &&
948
- typeof tool.outputSchema === "object" &&
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 result = await tool.execute({
955
- context: args,
956
- runtimeContext: createRuntimeContext(),
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
- return await prompt.execute({
1001
- args: args as Record<string, string | undefined>,
1002
- runtimeContext: createRuntimeContext(),
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 result = await resource.read({
1018
- uri,
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.pipeTo(writable).catch(() => {}).finally(cleanup);
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
- return execute({
1148
- context: toolCallInput,
1149
- runtimeContext: createRuntimeContext(),
1150
- });
1136
+ const ctx = createRuntimeContext();
1137
+ return execute({ context: toolCallInput, runtimeContext: ctx }, ctx);
1151
1138
  };
1152
1139
 
1153
1140
  return {