@decocms/runtime 1.0.0-alpha.37 → 1.0.0-alpha.39

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.
@@ -447,6 +447,10 @@
447
447
  "name": {
448
448
  "type": "string"
449
449
  },
450
+ "array": {
451
+ "type": "boolean",
452
+ "const": true
453
+ },
450
454
  "type": {
451
455
  "type": "string",
452
456
  "const": "mcp"
@@ -469,6 +473,10 @@
469
473
  "name": {
470
474
  "type": "string"
471
475
  },
476
+ "array": {
477
+ "type": "boolean",
478
+ "const": true
479
+ },
472
480
  "type": {
473
481
  "type": "string",
474
482
  "const": "mcp"
@@ -479,7 +487,6 @@
479
487
  }
480
488
  },
481
489
  "required": [
482
- "app_name",
483
490
  "name",
484
491
  "type"
485
492
  ],
@@ -491,6 +498,10 @@
491
498
  "name": {
492
499
  "type": "string"
493
500
  },
501
+ "array": {
502
+ "type": "boolean",
503
+ "const": true
504
+ },
494
505
  "type": {
495
506
  "type": "string",
496
507
  "const": "contract"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/runtime",
3
- "version": "1.0.0-alpha.37",
3
+ "version": "1.0.0-alpha.39",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit"
@@ -8,7 +8,7 @@
8
8
  "dependencies": {
9
9
  "@cloudflare/workers-types": "^4.20250617.0",
10
10
  "@deco/mcp": "npm:@jsr/deco__mcp@0.7.8",
11
- "@decocms/bindings": "1.0.1-alpha.21",
11
+ "@decocms/bindings": "1.0.1-alpha.23",
12
12
  "@modelcontextprotocol/sdk": "1.20.2",
13
13
  "@ai-sdk/provider": "^2.0.0",
14
14
  "hono": "^4.10.7",
@@ -88,7 +88,7 @@ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
88
88
  };
89
89
  };
90
90
 
91
- export type MCPBindingClient<T extends ReturnType<typeof bindingClient>> =
91
+ export type MCPBindingClient<T extends ReturnType<typeof bindingClient<any>>> =
92
92
  ReturnType<T["forConnection"]>;
93
93
 
94
94
  export const ChannelBinding = bindingClient(CHANNEL_BINDING);
package/src/bindings.ts CHANGED
@@ -1,18 +1,97 @@
1
+ import { CollectionBinding } from "packages/bindings/src/well-known/collections.ts";
1
2
  import type { MCPConnection } from "./connection.ts";
2
- import type { DefaultEnv, RequestContext } from "./index.ts";
3
- import { MCPClient } from "./mcp.ts";
4
- import type {
5
- BindingBase,
6
- ContractBinding,
7
- MCPAppBinding,
8
- MCPBinding,
9
- } from "./wrangler.ts";
3
+ import type { RequestContext } from "./index.ts";
4
+ import { type MCPClientFetchStub, MCPClient, type ToolBinder } from "./mcp.ts";
5
+ import { z } from "zod";
10
6
 
11
7
  type ClientContext = Omit<
12
8
  RequestContext,
13
9
  "ensureAuthenticated" | "state" | "fetchIntegrationMetadata"
14
10
  >;
15
11
 
12
+ export interface Binding<TType extends string = string> {
13
+ __type: TType;
14
+ value: string;
15
+ }
16
+
17
+ /**
18
+ * A registry mapping binding type strings (e.g. "@deco/database") to their ToolBinder definitions.
19
+ * Used by ResolvedBindings to resolve binding types to their corresponding MCP client types.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * type MyBindings = {
24
+ * "@deco/database": typeof DATABASE_BINDING;
25
+ * "@deco/storage": typeof STORAGE_BINDING;
26
+ * };
27
+ * ```
28
+ */
29
+ export type BindingRegistry = Record<string, readonly ToolBinder[]>;
30
+
31
+ /**
32
+ * Function that returns Zod Schema
33
+ */
34
+ export const BindingOf = <
35
+ TRegistry extends BindingRegistry,
36
+ TName extends keyof TRegistry | "*",
37
+ >(
38
+ name: TName,
39
+ ) => {
40
+ return z.object({
41
+ __type: z.literal<TName>(name).default(name),
42
+ value: z.string(),
43
+ });
44
+ };
45
+
46
+ /**
47
+ * Recursively transforms a type T by replacing all Binding instances with their
48
+ * corresponding MCPClientFetchStub based on the __type field.
49
+ *
50
+ * @template T - The source type to transform
51
+ * @template TBindings - A registry mapping binding __type strings to ToolBinder definitions
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * interface State {
56
+ * db: Binding<"@deco/database">;
57
+ * items: Array<Binding<"@deco/storage">>;
58
+ * config: { nested: Binding<"@deco/config"> };
59
+ * }
60
+ *
61
+ * type Resolved = ResolvedBindings<State, {
62
+ * "@deco/database": typeof DATABASE_BINDING;
63
+ * "@deco/storage": typeof STORAGE_BINDING;
64
+ * }>;
65
+ * // Result:
66
+ * // {
67
+ * // db: MCPClientFetchStub<typeof DATABASE_BINDING>;
68
+ * // items: Array<MCPClientFetchStub<typeof STORAGE_BINDING>>;
69
+ * // config: { nested: unknown }; // "@deco/config" not in registry
70
+ * // }
71
+ * ```
72
+ */
73
+ export type ResolvedBindings<
74
+ T,
75
+ TBindings extends BindingRegistry,
76
+ > = T extends Binding<infer TType>
77
+ ? TType extends keyof TBindings
78
+ ? MCPClientFetchStub<TBindings[TType]> & { __type: TType; value: string }
79
+ : MCPClientFetchStub<[]> & { __type: string; value: string }
80
+ : T extends Array<infer U>
81
+ ? Array<ResolvedBindings<U, TBindings>>
82
+ : T extends object
83
+ ? { [K in keyof T]: ResolvedBindings<T[K], TBindings> }
84
+ : T;
85
+
86
+ export const isBinding = (v: unknown): v is Binding => {
87
+ return (
88
+ typeof v === "object" &&
89
+ v !== null &&
90
+ typeof (v as { __type: string }).__type === "string" &&
91
+ typeof (v as { value: string }).value === "string"
92
+ );
93
+ };
94
+
16
95
  export const proxyConnectionForId = (
17
96
  connectionId: string,
18
97
  ctx: Omit<ClientContext, "token"> & {
@@ -36,65 +115,72 @@ export const proxyConnectionForId = (
36
115
  headers,
37
116
  };
38
117
  };
118
+
39
119
  const mcpClientForConnectionId = (
40
120
  connectionId: string,
41
121
  ctx: ClientContext,
42
122
  appName?: string,
43
123
  ) => {
44
124
  const mcpConnection = proxyConnectionForId(connectionId, ctx, appName);
45
-
46
- // TODO(@igorbrasileiro): Switch this proxy to be a proxy that call MCP Client.toolCall from @modelcontextprotocol
47
- return MCPClient.forConnection(mcpConnection);
125
+ return new Proxy(MCPClient.forConnection(mcpConnection), {
126
+ get(target, name) {
127
+ if (name === "value") {
128
+ return connectionId;
129
+ }
130
+ if (name === "__type") {
131
+ return appName;
132
+ }
133
+ return target[name as keyof typeof target];
134
+ },
135
+ });
48
136
  };
49
137
 
50
- function mcpClientFromState(
51
- binding: BindingBase | MCPAppBinding,
52
- env: DefaultEnv,
53
- ) {
54
- const ctx = env.MESH_REQUEST_CONTEXT;
55
- const bindingFromState = ctx?.state?.[binding.name];
56
- const connectionId =
57
- bindingFromState &&
58
- typeof bindingFromState === "object" &&
59
- "value" in bindingFromState
60
- ? bindingFromState.value
61
- : undefined;
62
- if (typeof connectionId !== "string" && "app_name" in binding) {
63
- // in case of a binding to an app name, we need to use the new apps/mcp endpoint which will proxy the request to the app but without any token
64
- return undefined;
138
+ const traverseAndReplace = (obj: unknown, ctx: ClientContext): unknown => {
139
+ // Handle null/undefined
140
+ if (obj === null || obj === undefined) {
141
+ return obj;
65
142
  }
66
- return mcpClientForConnectionId(connectionId, ctx);
67
- }
68
143
 
69
- export const createContractBinding = (
70
- binding: ContractBinding,
71
- env: DefaultEnv,
72
- ) => {
73
- return mcpClientFromState(binding, env);
74
- };
75
-
76
- export const createIntegrationBinding = (
77
- binding: MCPBinding,
78
- env: DefaultEnv,
79
- ) => {
80
- const connectionId =
81
- "connection_id" in binding ? binding.connection_id : undefined;
82
- if (!connectionId) {
83
- return mcpClientFromState(binding, env);
144
+ // Handle arrays
145
+ if (Array.isArray(obj)) {
146
+ return obj.map((item) => traverseAndReplace(item, ctx));
84
147
  }
85
- if (!env.MESH_RUNTIME_TOKEN) {
86
- throw new Error("MESH_RUNTIME_TOKEN is required");
87
- }
88
- if (!env.MESH_URL) {
89
- throw new Error("MESH_URL is required");
148
+
149
+ // Handle objects
150
+ if (typeof obj === "object") {
151
+ // Check if this is a binding
152
+ if (isBinding(obj)) {
153
+ return mcpClientForConnectionId(obj.value, ctx, obj.__type);
154
+ }
155
+
156
+ // Traverse object properties
157
+ const result: Record<string, unknown> = {};
158
+ for (const [key, value] of Object.entries(obj)) {
159
+ result[key] = traverseAndReplace(value, ctx);
160
+ }
161
+ return result;
90
162
  }
91
- // bindings pointed to an specific integration id are binded using the app deployment workspace
92
- return mcpClientForConnectionId(
93
- connectionId,
94
- {
95
- token: env.MESH_RUNTIME_TOKEN,
96
- meshUrl: env.MESH_URL,
97
- },
98
- env.MESH_APP_NAME,
99
- );
163
+
164
+ // Return primitives as-is
165
+ return obj;
100
166
  };
167
+
168
+ export const initializeBindings = <
169
+ T,
170
+ TBindings extends BindingRegistry = BindingRegistry,
171
+ >(
172
+ ctx: RequestContext,
173
+ ): ResolvedBindings<T, TBindings> => {
174
+ // resolves the state in-place
175
+ return traverseAndReplace(ctx.state, ctx) as ResolvedBindings<T, TBindings>;
176
+ };
177
+
178
+ interface DefaultRegistry extends BindingRegistry {
179
+ "@deco/mesh": CollectionBinding<{ hello: string }, "MESH">;
180
+ }
181
+
182
+ export interface XPTO {
183
+ MESH: Binding<"@deco/meh">;
184
+ }
185
+
186
+ export type XPTOResolved = ResolvedBindings<XPTO, DefaultRegistry>;
package/src/events.ts CHANGED
@@ -4,6 +4,7 @@ import type {
4
4
  OnEventsOutput,
5
5
  } from "@decocms/bindings";
6
6
  import z from "zod";
7
+ import { isBinding } from "./bindings.ts";
7
8
 
8
9
  // ============================================================================
9
10
  // Types
@@ -14,11 +15,6 @@ export interface EventSubscription {
14
15
  publisher: string;
15
16
  }
16
17
 
17
- interface Binding {
18
- __type: string;
19
- value: string;
20
- }
21
-
22
18
  /**
23
19
  * Per-event handler - handles events of a specific type
24
20
  * Returns result for each event individually
@@ -118,17 +114,6 @@ export type BindingKeysOf<T> = {
118
114
  // Type Guards
119
115
  // ============================================================================
120
116
 
121
- const isBinding = (v: unknown): v is Binding => {
122
- return (
123
- typeof v === "object" &&
124
- v !== null &&
125
- "__type" in v &&
126
- typeof v.__type === "string" &&
127
- "value" in v &&
128
- typeof v.value === "string"
129
- );
130
- };
131
-
132
117
  /**
133
118
  * Check if handlers is a global batch handler (has handler + events at top level)
134
119
  */
@@ -199,28 +184,6 @@ const getEventTypesForBinding = <TEnv, TSchema extends z.ZodTypeAny>(
199
184
  return Object.keys(bindingHandler);
200
185
  };
201
186
 
202
- /**
203
- * Get scopes from event handlers for subscription
204
- */
205
- const scopesFromEvents = <TEnv, TSchema extends z.ZodTypeAny = never>(
206
- handlers: EventHandlers<TEnv, TSchema>,
207
- ): string[] => {
208
- if (isGlobalHandler<TEnv>(handlers)) {
209
- // Global handler - scopes are based on explicit events array
210
- // Note: "*" binding means all bindings
211
- return handlers.events.map((event) => `*::event@${event}`);
212
- }
213
-
214
- const scopes: string[] = [];
215
- for (const binding of getBindingKeys(handlers)) {
216
- const eventTypes = getEventTypesForBinding(handlers, binding);
217
- for (const eventType of eventTypes) {
218
- scopes.push(`${binding}::event@${eventType}`);
219
- }
220
- }
221
- return scopes;
222
- };
223
-
224
187
  /**
225
188
  * Get subscriptions from event handlers and state
226
189
  * Returns flat array of { eventType, publisher } for EVENT_SYNC_SUBSCRIPTIONS
@@ -505,6 +468,5 @@ const executeEventHandlers = async <TEnv, TSchema extends z.ZodTypeAny>(
505
468
  */
506
469
  export const Event = {
507
470
  subscriptions: eventsSubscriptions,
508
- scopes: scopesFromEvents,
509
471
  execute: executeEventHandlers,
510
472
  };
package/src/index.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  /* oxlint-disable no-explicit-any */
2
2
  import { decodeJwt } from "jose";
3
3
  import type { z } from "zod";
4
- import { createContractBinding, createIntegrationBinding } from "./bindings.ts";
4
+ import {
5
+ BindingRegistry,
6
+ initializeBindings,
7
+ ResolvedBindings,
8
+ } from "./bindings.ts";
5
9
  import { type CORSOptions, handlePreflight, withCORS } from "./cors.ts";
6
10
  import { createOAuthHandlers } from "./oauth.ts";
7
11
  import { State } from "./state.ts";
@@ -10,8 +14,8 @@ import {
10
14
  type CreateMCPServerOptions,
11
15
  MCPServer,
12
16
  } from "./tools.ts";
13
- import type { Binding, ContractBinding, MCPBinding } from "./wrangler.ts";
14
- export { proxyConnectionForId } from "./bindings.ts";
17
+ import type { Binding } from "./wrangler.ts";
18
+ export { proxyConnectionForId, BindingOf } from "./bindings.ts";
15
19
  export { type CORSOptions, type CORSOrigin } from "./cors.ts";
16
20
  export {
17
21
  createMCPFetchStub,
@@ -19,9 +23,13 @@ export {
19
23
  type ToolBinder,
20
24
  } from "./mcp.ts";
21
25
 
22
- export interface DefaultEnv<TSchema extends z.ZodTypeAny = any> {
23
- MESH_REQUEST_CONTEXT: RequestContext<TSchema>;
24
- MESH_BINDINGS: string;
26
+ export type { BindingRegistry } from "./bindings.ts";
27
+
28
+ export interface DefaultEnv<
29
+ TSchema extends z.ZodTypeAny = any,
30
+ TBindings extends BindingRegistry = BindingRegistry,
31
+ > {
32
+ MESH_REQUEST_CONTEXT: RequestContext<TSchema, TBindings>;
25
33
  MESH_APP_DEPLOYMENT_ID: string;
26
34
  IS_LOCAL: boolean;
27
35
  MESH_URL?: string;
@@ -51,8 +59,10 @@ export const MCPBindings = {
51
59
  export interface UserDefaultExport<
52
60
  TUserEnv = Record<string, unknown>,
53
61
  TSchema extends z.ZodTypeAny = never,
54
- TEnv = TUserEnv & DefaultEnv<TSchema>,
55
- > extends CreateMCPServerOptions<TEnv, TSchema> {
62
+ TBindings extends BindingRegistry = BindingRegistry,
63
+ TEnv extends TUserEnv & DefaultEnv<TSchema, TBindings> = TUserEnv &
64
+ DefaultEnv<TSchema, TBindings>,
65
+ > extends CreateMCPServerOptions<TEnv, TSchema, TBindings> {
56
66
  fetch?: (req: Request, env: TEnv, ctx: any) => Promise<Response> | Response;
57
67
  /**
58
68
  * CORS configuration options.
@@ -61,12 +71,6 @@ export interface UserDefaultExport<
61
71
  cors?: CORSOptions | false;
62
72
  }
63
73
 
64
- // 1. Map binding type to its interface
65
- interface BindingTypeMap {
66
- mcp: MCPBinding;
67
- contract: ContractBinding;
68
- }
69
-
70
74
  export interface User {
71
75
  id: string;
72
76
  email: string;
@@ -79,8 +83,11 @@ export interface User {
79
83
  };
80
84
  }
81
85
 
82
- export interface RequestContext<TSchema extends z.ZodTypeAny = any> {
83
- state: z.infer<TSchema>;
86
+ export interface RequestContext<
87
+ TSchema extends z.ZodTypeAny = any,
88
+ TBindings extends BindingRegistry = BindingRegistry,
89
+ > {
90
+ state: ResolvedBindings<z.infer<TSchema>, TBindings>;
84
91
  token: string;
85
92
  meshUrl: string;
86
93
  authorization?: string | null;
@@ -91,20 +98,6 @@ export interface RequestContext<TSchema extends z.ZodTypeAny = any> {
91
98
  connectionId?: string;
92
99
  }
93
100
 
94
- // 2. Map binding type to its creator function
95
- type CreatorByType = {
96
- [K in keyof BindingTypeMap]: (
97
- value: BindingTypeMap[K],
98
- env: DefaultEnv,
99
- ) => unknown;
100
- };
101
-
102
- // 3. Strongly type creatorByType
103
- const creatorByType: CreatorByType = {
104
- mcp: createIntegrationBinding,
105
- contract: createContractBinding,
106
- };
107
-
108
101
  const withDefaultBindings = ({
109
102
  env,
110
103
  server,
@@ -159,7 +152,6 @@ export const withBindings = <TEnv>({
159
152
  server,
160
153
  tokenOrContext,
161
154
  url,
162
- bindings: inlineBindings,
163
155
  authToken,
164
156
  }: {
165
157
  env: TEnv;
@@ -169,7 +161,6 @@ export const withBindings = <TEnv>({
169
161
  // authToken is the authorization header
170
162
  authToken?: string | null;
171
163
  url?: string;
172
- bindings?: Binding[];
173
164
  }): TEnv => {
174
165
  const env = _env as DefaultEnv<any>;
175
166
  const authorization = authToken ? authToken.split(" ")[1] : undefined;
@@ -223,11 +214,7 @@ export const withBindings = <TEnv>({
223
214
  }
224
215
 
225
216
  env.MESH_REQUEST_CONTEXT = context;
226
- const bindings = inlineBindings ?? MCPBindings.parse(env.MESH_BINDINGS);
227
-
228
- for (const binding of bindings) {
229
- env[binding.name] = creatorByType[binding.type](binding as any, env);
230
- }
217
+ context.state = initializeBindings(context);
231
218
 
232
219
  withDefaultBindings({
233
220
  env,
@@ -252,19 +239,21 @@ const DEFAULT_CORS_OPTIONS = {
252
239
  allowHeaders: ["Content-Type", "Authorization", "mcp-protocol-version"],
253
240
  };
254
241
 
255
- export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
256
- userFns: UserDefaultExport<TEnv, TSchema>,
242
+ export const withRuntime = <
243
+ TUserEnv,
244
+ TSchema extends z.ZodTypeAny = never,
245
+ TBindings extends BindingRegistry = BindingRegistry,
246
+ TEnv extends TUserEnv & DefaultEnv<TSchema, TBindings> = TUserEnv &
247
+ DefaultEnv<TSchema, TBindings>,
248
+ >(
249
+ userFns: UserDefaultExport<TUserEnv, TSchema, TBindings>,
257
250
  ) => {
258
- const server = createMCPServer<TEnv, TSchema>(userFns);
251
+ const server = createMCPServer<TUserEnv, TSchema, TBindings>(userFns);
259
252
  const corsOptions = userFns.cors ?? DEFAULT_CORS_OPTIONS;
260
253
  const oauth = userFns.oauth;
261
254
  const oauthHandlers = oauth ? createOAuthHandlers(oauth) : null;
262
255
 
263
- const fetcher = async (
264
- req: Request,
265
- env: TEnv & DefaultEnv<TSchema>,
266
- ctx: any,
267
- ) => {
256
+ const fetcher = async (req: Request, env: TEnv, ctx: any) => {
268
257
  const url = new URL(req.url);
269
258
 
270
259
  // OAuth routes (when configured)
@@ -359,7 +348,7 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
359
348
  };
360
349
 
361
350
  return {
362
- fetch: async (req: Request, env: TEnv & DefaultEnv<TSchema>, ctx?: any) => {
351
+ fetch: async (req: Request, env: TEnv, ctx?: any) => {
363
352
  if (new URL(req.url).pathname === "/_healthcheck") {
364
353
  return new Response("OK", { status: 200 });
365
354
  }
@@ -373,7 +362,6 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
373
362
  authToken: req.headers.get("authorization") ?? null,
374
363
  env: { ...process.env, ...env },
375
364
  server,
376
- bindings: userFns.bindings,
377
365
  tokenOrContext: req.headers.get("x-mesh-token") ?? undefined,
378
366
  url: req.url,
379
367
  });
package/src/tools.ts CHANGED
@@ -9,10 +9,10 @@ import {
9
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
10
  import { z } from "zod";
11
11
  import { zodToJsonSchema } from "zod-to-json-schema";
12
+ import { BindingRegistry } from "./bindings.ts";
12
13
  import { Event, type EventHandlers } from "./events.ts";
13
14
  import type { DefaultEnv } from "./index.ts";
14
15
  import { State } from "./state.ts";
15
- import { Binding } from "./wrangler.ts";
16
16
 
17
17
  // Re-export EventHandlers type for external use
18
18
  export type { EventHandlers } from "./events.ts";
@@ -154,8 +154,8 @@ export function isStreamableTool(
154
154
  return tool && "streamable" in tool && tool.streamable === true;
155
155
  }
156
156
 
157
- export interface OnChangeCallback<TSchema extends z.ZodTypeAny = never> {
158
- state: z.infer<TSchema>;
157
+ export interface OnChangeCallback<TState> {
158
+ state: TState;
159
159
  scopes: string[];
160
160
  }
161
161
 
@@ -232,35 +232,34 @@ type PickByType<T, Value> = {
232
232
  export interface CreateMCPServerOptions<
233
233
  Env = unknown,
234
234
  TSchema extends z.ZodTypeAny = never,
235
+ TBindings extends BindingRegistry = BindingRegistry,
236
+ TEnv extends Env & DefaultEnv<TSchema, TBindings> = Env &
237
+ DefaultEnv<TSchema, TBindings>,
238
+ State extends
239
+ TEnv["MESH_REQUEST_CONTEXT"]["state"] = TEnv["MESH_REQUEST_CONTEXT"]["state"],
235
240
  > {
236
- before?: (env: Env & DefaultEnv<TSchema>) => Promise<void> | void;
241
+ before?: (env: TEnv) => Promise<void> | void;
237
242
  oauth?: OAuthConfig;
238
243
  events?: {
239
- bus?: keyof PickByType<Env & DefaultEnv<TSchema>, EventBusBindingClient>;
240
- handlers?: EventHandlers<Env & DefaultEnv<TSchema>, TSchema>;
244
+ bus?: keyof PickByType<TEnv, EventBusBindingClient>;
245
+ handlers?: EventHandlers<TEnv, TSchema>;
241
246
  };
242
247
  configuration?: {
243
- onChange?: (
244
- env: Env & DefaultEnv<TSchema>,
245
- cb: OnChangeCallback<TSchema>,
246
- ) => Promise<void>;
248
+ onChange?: (env: TEnv, cb: OnChangeCallback<State>) => Promise<void>;
247
249
  state?: TSchema;
248
250
  scopes?: string[];
249
251
  };
250
- bindings?: Binding[];
251
252
  tools?:
252
253
  | Array<
253
254
  (
254
- env: Env & DefaultEnv<TSchema>,
255
+ env: TEnv,
255
256
  ) =>
256
257
  | Promise<CreatedTool>
257
258
  | CreatedTool
258
259
  | CreatedTool[]
259
260
  | Promise<CreatedTool[]>
260
261
  >
261
- | ((
262
- env: Env & DefaultEnv<TSchema>,
263
- ) => CreatedTool[] | Promise<CreatedTool[]>);
262
+ | ((env: TEnv) => CreatedTool[] | Promise<CreatedTool[]>);
264
263
  }
265
264
 
266
265
  export type Fetch<TEnv = unknown> = (
@@ -280,7 +279,9 @@ const getEventBus = (
280
279
  env: DefaultEnv,
281
280
  ): EventBusBindingClient | undefined => {
282
281
  const bus = env as unknown as { [prop]: EventBusBindingClient };
283
- return typeof bus[prop] !== "undefined" ? bus[prop] : undefined;
282
+ return typeof bus[prop] !== "undefined"
283
+ ? bus[prop]
284
+ : env?.MESH_REQUEST_CONTEXT.state[prop];
284
285
  };
285
286
 
286
287
  const toolsFor = <TSchema extends z.ZodTypeAny = never>({
@@ -292,7 +293,7 @@ const toolsFor = <TSchema extends z.ZodTypeAny = never>({
292
293
  : { type: "object", properties: {} };
293
294
  const busProp = String(events?.bus ?? "EVENT_BUS");
294
295
  return [
295
- ...(onChange
296
+ ...(onChange || events
296
297
  ? [
297
298
  createTool({
298
299
  id: "ON_MCP_CONFIGURATION",
@@ -308,7 +309,7 @@ const toolsFor = <TSchema extends z.ZodTypeAny = never>({
308
309
  outputSchema: z.object({}),
309
310
  execute: async (input) => {
310
311
  const state = input.context.state as z.infer<TSchema>;
311
- await onChange(input.runtimeContext.env, {
312
+ await onChange?.(input.runtimeContext.env, {
312
313
  state,
313
314
  scopes: input.context.scopes,
314
315
  });
@@ -361,9 +362,8 @@ const toolsFor = <TSchema extends z.ZodTypeAny = never>({
361
362
  return Promise.resolve({
362
363
  stateSchema: jsonSchema,
363
364
  scopes: [
364
- ...(scopes ?? []),
365
- ...Event.scopes(events?.handlers ?? {}),
366
- ...(busProp ? [`${busProp}::EVENT_SYNC_SUBSCRIPTIONS`] : []),
365
+ ...((scopes as string[]) ?? []),
366
+ ...(events ? [`${busProp}::EVENT_SYNC_SUBSCRIPTIONS`] : []),
367
367
  ],
368
368
  });
369
369
  },
@@ -376,18 +376,25 @@ type CallTool = (opts: {
376
376
  toolCallInput: unknown;
377
377
  }) => Promise<unknown>;
378
378
 
379
- export type MCPServer<TEnv = unknown, TSchema extends z.ZodTypeAny = never> = {
380
- fetch: Fetch<TEnv & DefaultEnv<TSchema>>;
379
+ export type MCPServer<
380
+ TEnv = unknown,
381
+ TSchema extends z.ZodTypeAny = never,
382
+ TBindings extends BindingRegistry = BindingRegistry,
383
+ > = {
384
+ fetch: Fetch<TEnv & DefaultEnv<TSchema, TBindings>>;
381
385
  callTool: CallTool;
382
386
  };
383
387
 
384
388
  export const createMCPServer = <
385
- TEnv = unknown,
389
+ Env = unknown,
386
390
  TSchema extends z.ZodTypeAny = never,
391
+ TBindings extends BindingRegistry = BindingRegistry,
392
+ TEnv extends Env & DefaultEnv<TSchema, TBindings> = Env &
393
+ DefaultEnv<TSchema, TBindings>,
387
394
  >(
388
- options: CreateMCPServerOptions<TEnv, TSchema>,
389
- ): MCPServer<TEnv, TSchema> => {
390
- const createServer = async (bindings: TEnv & DefaultEnv<TSchema>) => {
395
+ options: CreateMCPServerOptions<TEnv, TSchema, TBindings>,
396
+ ): MCPServer<TEnv, TSchema, TBindings> => {
397
+ const createServer = async (bindings: TEnv) => {
391
398
  await options.before?.(bindings);
392
399
 
393
400
  const server = new McpServer(
@@ -398,7 +405,7 @@ export const createMCPServer = <
398
405
  const toolsFn =
399
406
  typeof options.tools === "function"
400
407
  ? options.tools
401
- : async (bindings: TEnv & DefaultEnv<TSchema>) => {
408
+ : async (bindings: TEnv) => {
402
409
  if (typeof options.tools === "function") {
403
410
  return await options.tools(bindings);
404
411
  }
@@ -462,7 +469,7 @@ export const createMCPServer = <
462
469
  return { server, tools };
463
470
  };
464
471
 
465
- const fetch = async (req: Request, env: TEnv & DefaultEnv<TSchema>) => {
472
+ const fetch = async (req: Request, env: TEnv) => {
466
473
  const { server } = await createServer(env);
467
474
  const transport = new HttpServerTransport();
468
475
 
package/src/wrangler.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export interface BindingBase {
2
2
  name: string;
3
+ array?: true;
3
4
  }
4
5
 
5
6
  export interface MCPConnectionBinding extends BindingBase {
@@ -15,7 +16,7 @@ export interface MCPAppBinding extends BindingBase {
15
16
  /**
16
17
  * The name of the integration to bind.
17
18
  */
18
- app_name: string;
19
+ app_name?: string;
19
20
  }
20
21
  export interface ContractClause {
21
22
  id: string;