@decocms/runtime 1.0.0-alpha.36 → 1.0.0-alpha.38
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/config-schema.json +12 -1
- package/package.json +1 -1
- package/src/bindings/binder.ts +1 -1
- package/src/bindings.ts +142 -57
- package/src/events.ts +59 -38
- package/src/index.ts +35 -47
- package/src/tools.ts +31 -25
- package/src/wrangler.ts +2 -1
package/config-schema.json
CHANGED
|
@@ -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
package/src/bindings/binder.ts
CHANGED
|
@@ -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,96 @@
|
|
|
1
|
+
import { CollectionBinding } from "packages/bindings/src/well-known/collections.ts";
|
|
1
2
|
import type { MCPConnection } from "./connection.ts";
|
|
2
|
-
import type {
|
|
3
|
-
import { MCPClient } from "./mcp.ts";
|
|
4
|
-
import
|
|
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 = <TRegistry extends BindingRegistry>(
|
|
35
|
+
name: keyof TRegistry | "*",
|
|
36
|
+
) => {
|
|
37
|
+
return z.object({
|
|
38
|
+
__type: z.literal(name).default(name as any),
|
|
39
|
+
value: z.string(),
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Recursively transforms a type T by replacing all Binding instances with their
|
|
45
|
+
* corresponding MCPClientFetchStub based on the __type field.
|
|
46
|
+
*
|
|
47
|
+
* @template T - The source type to transform
|
|
48
|
+
* @template TBindings - A registry mapping binding __type strings to ToolBinder definitions
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* interface State {
|
|
53
|
+
* db: Binding<"@deco/database">;
|
|
54
|
+
* items: Array<Binding<"@deco/storage">>;
|
|
55
|
+
* config: { nested: Binding<"@deco/config"> };
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* type Resolved = ResolvedBindings<State, {
|
|
59
|
+
* "@deco/database": typeof DATABASE_BINDING;
|
|
60
|
+
* "@deco/storage": typeof STORAGE_BINDING;
|
|
61
|
+
* }>;
|
|
62
|
+
* // Result:
|
|
63
|
+
* // {
|
|
64
|
+
* // db: MCPClientFetchStub<typeof DATABASE_BINDING>;
|
|
65
|
+
* // items: Array<MCPClientFetchStub<typeof STORAGE_BINDING>>;
|
|
66
|
+
* // config: { nested: unknown }; // "@deco/config" not in registry
|
|
67
|
+
* // }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export type ResolvedBindings<
|
|
71
|
+
T,
|
|
72
|
+
TBindings extends BindingRegistry,
|
|
73
|
+
> = T extends Binding<infer TType>
|
|
74
|
+
? TType extends keyof TBindings
|
|
75
|
+
? MCPClientFetchStub<TBindings[TType]> & { __type: TType; value: string }
|
|
76
|
+
: MCPClientFetchStub<[]> & { __type: string; value: string }
|
|
77
|
+
: T extends Array<infer U>
|
|
78
|
+
? Array<ResolvedBindings<U, TBindings>>
|
|
79
|
+
: T extends object
|
|
80
|
+
? { [K in keyof T]: ResolvedBindings<T[K], TBindings> }
|
|
81
|
+
: T;
|
|
82
|
+
|
|
83
|
+
export const isBinding = (v: unknown): v is Binding => {
|
|
84
|
+
return (
|
|
85
|
+
typeof v === "object" &&
|
|
86
|
+
v !== null &&
|
|
87
|
+
"__type" in v &&
|
|
88
|
+
typeof v.__type === "string" &&
|
|
89
|
+
"value" in v &&
|
|
90
|
+
typeof v.value === "string"
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
16
94
|
export const proxyConnectionForId = (
|
|
17
95
|
connectionId: string,
|
|
18
96
|
ctx: Omit<ClientContext, "token"> & {
|
|
@@ -36,65 +114,72 @@ export const proxyConnectionForId = (
|
|
|
36
114
|
headers,
|
|
37
115
|
};
|
|
38
116
|
};
|
|
117
|
+
|
|
39
118
|
const mcpClientForConnectionId = (
|
|
40
119
|
connectionId: string,
|
|
41
120
|
ctx: ClientContext,
|
|
42
121
|
appName?: string,
|
|
43
122
|
) => {
|
|
44
123
|
const mcpConnection = proxyConnectionForId(connectionId, ctx, appName);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
124
|
+
return new Proxy(MCPClient.forConnection(mcpConnection), {
|
|
125
|
+
get(target, name) {
|
|
126
|
+
if (name === "value") {
|
|
127
|
+
return connectionId;
|
|
128
|
+
}
|
|
129
|
+
if (name === "__type") {
|
|
130
|
+
return appName;
|
|
131
|
+
}
|
|
132
|
+
return target[name as keyof typeof target];
|
|
133
|
+
},
|
|
134
|
+
});
|
|
48
135
|
};
|
|
49
136
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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;
|
|
137
|
+
const traverseAndReplace = (obj: unknown, ctx: ClientContext): unknown => {
|
|
138
|
+
// Handle null/undefined
|
|
139
|
+
if (obj === null || obj === undefined) {
|
|
140
|
+
return obj;
|
|
65
141
|
}
|
|
66
|
-
return mcpClientForConnectionId(connectionId, ctx);
|
|
67
|
-
}
|
|
68
142
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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);
|
|
143
|
+
// Handle arrays
|
|
144
|
+
if (Array.isArray(obj)) {
|
|
145
|
+
return obj.map((item) => traverseAndReplace(item, ctx));
|
|
84
146
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
147
|
+
|
|
148
|
+
// Handle objects
|
|
149
|
+
if (typeof obj === "object") {
|
|
150
|
+
// Check if this is a binding
|
|
151
|
+
if (isBinding(obj)) {
|
|
152
|
+
return mcpClientForConnectionId(obj.value, ctx, obj.__type);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Traverse object properties
|
|
156
|
+
const result: Record<string, unknown> = {};
|
|
157
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
158
|
+
result[key] = traverseAndReplace(value, ctx);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
90
161
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
token: env.MESH_RUNTIME_TOKEN,
|
|
96
|
-
meshUrl: env.MESH_URL,
|
|
97
|
-
},
|
|
98
|
-
env.MESH_APP_NAME,
|
|
99
|
-
);
|
|
162
|
+
|
|
163
|
+
// Return primitives as-is
|
|
164
|
+
return obj;
|
|
100
165
|
};
|
|
166
|
+
|
|
167
|
+
export const initializeBindings = <
|
|
168
|
+
T,
|
|
169
|
+
TBindings extends BindingRegistry = BindingRegistry,
|
|
170
|
+
>(
|
|
171
|
+
ctx: RequestContext,
|
|
172
|
+
): void => {
|
|
173
|
+
// resolves the state in-place
|
|
174
|
+
traverseAndReplace(ctx.state, ctx) as ResolvedBindings<T, TBindings>;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
interface DefaultRegistry extends BindingRegistry {
|
|
178
|
+
"@deco/mesh": CollectionBinding<{ hello: string }, "MESH">;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface XPTO {
|
|
182
|
+
MESH: Binding<"@deco/meh">;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
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
|
|
@@ -60,10 +56,18 @@ export interface BatchHandler<TEnv> {
|
|
|
60
56
|
* { handler: fn, events: ["order.created", "order.updated"] }
|
|
61
57
|
* ```
|
|
62
58
|
*/
|
|
63
|
-
export type BindingHandlers<TEnv> =
|
|
59
|
+
export type BindingHandlers<TEnv, Binding = unknown> =
|
|
64
60
|
| BatchHandler<TEnv>
|
|
65
|
-
| Record<string, PerEventHandler<TEnv
|
|
61
|
+
| (Record<string, PerEventHandler<TEnv>> & CronHandlers<Binding, TEnv>);
|
|
66
62
|
|
|
63
|
+
export type CronHandlers<Binding, Env = unknown> = Binding extends {
|
|
64
|
+
__type: "@deco/event-bus";
|
|
65
|
+
value: string;
|
|
66
|
+
}
|
|
67
|
+
? {
|
|
68
|
+
[key in `cron/${string}`]: (env: Env) => Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
: {};
|
|
67
71
|
/**
|
|
68
72
|
* EventHandlers type supports three granularity levels:
|
|
69
73
|
*
|
|
@@ -82,9 +86,10 @@ export type BindingHandlers<TEnv> =
|
|
|
82
86
|
* { DATABASE: { "order.created": (ctx, env) => result } }
|
|
83
87
|
* ```
|
|
84
88
|
*/
|
|
85
|
-
export type EventHandlers<
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
export type EventHandlers<
|
|
90
|
+
Env = unknown,
|
|
91
|
+
TSchema extends z.ZodTypeAny = never,
|
|
92
|
+
> = [TSchema] extends [never]
|
|
88
93
|
? Record<string, never>
|
|
89
94
|
:
|
|
90
95
|
| BatchHandler<z.infer<TSchema>> // Global handler with events
|
|
@@ -94,7 +99,7 @@ export type EventHandlers<TSchema extends z.ZodTypeAny = never> = [
|
|
|
94
99
|
value: string;
|
|
95
100
|
}
|
|
96
101
|
? K
|
|
97
|
-
: never]?: BindingHandlers<z.infer<TSchema
|
|
102
|
+
: never]?: BindingHandlers<Env, z.infer<TSchema>[K]>;
|
|
98
103
|
};
|
|
99
104
|
|
|
100
105
|
/**
|
|
@@ -109,22 +114,11 @@ export type BindingKeysOf<T> = {
|
|
|
109
114
|
// Type Guards
|
|
110
115
|
// ============================================================================
|
|
111
116
|
|
|
112
|
-
const isBinding = (v: unknown): v is Binding => {
|
|
113
|
-
return (
|
|
114
|
-
typeof v === "object" &&
|
|
115
|
-
v !== null &&
|
|
116
|
-
"__type" in v &&
|
|
117
|
-
typeof v.__type === "string" &&
|
|
118
|
-
"value" in v &&
|
|
119
|
-
typeof v.value === "string"
|
|
120
|
-
);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
117
|
/**
|
|
124
118
|
* Check if handlers is a global batch handler (has handler + events at top level)
|
|
125
119
|
*/
|
|
126
120
|
const isGlobalHandler = <TEnv>(
|
|
127
|
-
handlers: EventHandlers<z.ZodTypeAny>,
|
|
121
|
+
handlers: EventHandlers<TEnv, z.ZodTypeAny>,
|
|
128
122
|
): handlers is BatchHandler<TEnv> => {
|
|
129
123
|
return (
|
|
130
124
|
typeof handlers === "object" &&
|
|
@@ -159,10 +153,10 @@ const isBatchHandler = <TEnv>(
|
|
|
159
153
|
/**
|
|
160
154
|
* Get binding keys from event handlers object
|
|
161
155
|
*/
|
|
162
|
-
const getBindingKeys = <TSchema extends z.ZodTypeAny>(
|
|
163
|
-
handlers: EventHandlers<TSchema>,
|
|
156
|
+
const getBindingKeys = <TEnv, TSchema extends z.ZodTypeAny>(
|
|
157
|
+
handlers: EventHandlers<TEnv, TSchema>,
|
|
164
158
|
): string[] => {
|
|
165
|
-
if (isGlobalHandler(handlers)) {
|
|
159
|
+
if (isGlobalHandler<TEnv>(handlers)) {
|
|
166
160
|
return [];
|
|
167
161
|
}
|
|
168
162
|
return Object.keys(handlers);
|
|
@@ -171,11 +165,11 @@ const getBindingKeys = <TSchema extends z.ZodTypeAny>(
|
|
|
171
165
|
/**
|
|
172
166
|
* Get event types for a binding from handlers
|
|
173
167
|
*/
|
|
174
|
-
const getEventTypesForBinding = <TSchema extends z.ZodTypeAny>(
|
|
175
|
-
handlers: EventHandlers<TSchema>,
|
|
168
|
+
const getEventTypesForBinding = <TEnv, TSchema extends z.ZodTypeAny>(
|
|
169
|
+
handlers: EventHandlers<TEnv, TSchema>,
|
|
176
170
|
binding: string,
|
|
177
171
|
): string[] => {
|
|
178
|
-
if (isGlobalHandler(handlers)) {
|
|
172
|
+
if (isGlobalHandler<TEnv>(handlers)) {
|
|
179
173
|
return handlers.events;
|
|
180
174
|
}
|
|
181
175
|
const bindingHandler = handlers[binding as keyof typeof handlers];
|
|
@@ -193,10 +187,10 @@ const getEventTypesForBinding = <TSchema extends z.ZodTypeAny>(
|
|
|
193
187
|
/**
|
|
194
188
|
* Get scopes from event handlers for subscription
|
|
195
189
|
*/
|
|
196
|
-
const scopesFromEvents = <TSchema extends z.ZodTypeAny = never>(
|
|
197
|
-
handlers: EventHandlers<TSchema>,
|
|
190
|
+
const scopesFromEvents = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
191
|
+
handlers: EventHandlers<TEnv, TSchema>,
|
|
198
192
|
): string[] => {
|
|
199
|
-
if (isGlobalHandler(handlers)) {
|
|
193
|
+
if (isGlobalHandler<TEnv>(handlers)) {
|
|
200
194
|
// Global handler - scopes are based on explicit events array
|
|
201
195
|
// Note: "*" binding means all bindings
|
|
202
196
|
return handlers.events.map((event) => `*::event@${event}`);
|
|
@@ -216,11 +210,11 @@ const scopesFromEvents = <TSchema extends z.ZodTypeAny = never>(
|
|
|
216
210
|
* Get subscriptions from event handlers and state
|
|
217
211
|
* Returns flat array of { eventType, publisher } for EVENT_SYNC_SUBSCRIPTIONS
|
|
218
212
|
*/
|
|
219
|
-
const eventsSubscriptions = <TSchema extends z.ZodTypeAny = never>(
|
|
220
|
-
handlers: EventHandlers<TSchema>,
|
|
213
|
+
const eventsSubscriptions = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
214
|
+
handlers: EventHandlers<TEnv, TSchema>,
|
|
221
215
|
state: z.infer<TSchema>,
|
|
222
216
|
): EventSubscription[] => {
|
|
223
|
-
if (isGlobalHandler(handlers)) {
|
|
217
|
+
if (isGlobalHandler<TEnv>(handlers)) {
|
|
224
218
|
// Global handler - subscribe to all bindings with the explicit events
|
|
225
219
|
const subscriptions: EventSubscription[] = [];
|
|
226
220
|
for (const [, value] of Object.entries(state)) {
|
|
@@ -343,14 +337,14 @@ const mergeResults = (results: OnEventsOutput[]): OnEventsOutput => {
|
|
|
343
337
|
* 2. Per-binding: `{ BINDING: (context, env) => result }` - handles all events from binding
|
|
344
338
|
* 3. Per-event: `{ BINDING: { "event.type": (context, env) => result } }` - handles specific events
|
|
345
339
|
*/
|
|
346
|
-
const executeEventHandlers = async <TSchema extends z.ZodTypeAny>(
|
|
347
|
-
handlers: EventHandlers<TSchema>,
|
|
340
|
+
const executeEventHandlers = async <TEnv, TSchema extends z.ZodTypeAny>(
|
|
341
|
+
handlers: EventHandlers<TEnv, TSchema>,
|
|
348
342
|
events: CloudEvent[],
|
|
349
343
|
env: z.infer<TSchema>,
|
|
350
344
|
state: z.infer<TSchema>,
|
|
351
345
|
): Promise<OnEventsOutput> => {
|
|
352
346
|
// Case 1: Global handler
|
|
353
|
-
if (isGlobalHandler(handlers)) {
|
|
347
|
+
if (isGlobalHandler<TEnv>(handlers)) {
|
|
354
348
|
try {
|
|
355
349
|
return await handlers.handler({ events }, env);
|
|
356
350
|
} catch (error) {
|
|
@@ -422,6 +416,33 @@ const executeEventHandlers = async <TSchema extends z.ZodTypeAny>(
|
|
|
422
416
|
continue;
|
|
423
417
|
}
|
|
424
418
|
|
|
419
|
+
// Case 3a: Cron handlers (event type starts with "cron/")
|
|
420
|
+
// - Handler signature: (env) => Promise<void>
|
|
421
|
+
// - Fire and forget (don't await)
|
|
422
|
+
// - Always return success immediately
|
|
423
|
+
if (eventType.startsWith("cron/")) {
|
|
424
|
+
const cronHandler = eventHandler as unknown as (
|
|
425
|
+
env: z.infer<TSchema>,
|
|
426
|
+
) => Promise<void>;
|
|
427
|
+
|
|
428
|
+
// Fire and forget - don't await, just log errors
|
|
429
|
+
cronHandler(env).catch((error) => {
|
|
430
|
+
console.error(
|
|
431
|
+
`[Event] Cron handler error for ${eventType}:`,
|
|
432
|
+
error instanceof Error ? error.message : String(error),
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Immediately return success for all cron events
|
|
437
|
+
const results: Record<string, EventResult> = {};
|
|
438
|
+
for (const event of typedEvents) {
|
|
439
|
+
results[event.id] = { success: true };
|
|
440
|
+
}
|
|
441
|
+
promises.push(Promise.resolve({ results }));
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Case 3b: Regular per-event handlers
|
|
425
446
|
// Call handler for each event type (handler receives all events of that type)
|
|
426
447
|
promises.push(
|
|
427
448
|
(async () => {
|
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 {
|
|
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
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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<
|
|
83
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
for (const binding of bindings) {
|
|
229
|
-
env[binding.name] = creatorByType[binding.type](binding as any, env);
|
|
230
|
-
}
|
|
217
|
+
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 = <
|
|
256
|
-
|
|
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<
|
|
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
|
|
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<
|
|
158
|
-
state:
|
|
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:
|
|
241
|
+
before?: (env: TEnv) => Promise<void> | void;
|
|
237
242
|
oauth?: OAuthConfig;
|
|
238
243
|
events?: {
|
|
239
|
-
bus?: keyof PickByType<
|
|
240
|
-
handlers?: EventHandlers<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:
|
|
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> = (
|
|
@@ -361,9 +360,9 @@ const toolsFor = <TSchema extends z.ZodTypeAny = never>({
|
|
|
361
360
|
return Promise.resolve({
|
|
362
361
|
stateSchema: jsonSchema,
|
|
363
362
|
scopes: [
|
|
364
|
-
...(scopes ?? []),
|
|
363
|
+
...((scopes as string[]) ?? []),
|
|
365
364
|
...Event.scopes(events?.handlers ?? {}),
|
|
366
|
-
...(
|
|
365
|
+
...(events ? [`${busProp}::EVENT_SYNC_SUBSCRIPTIONS`] : []),
|
|
367
366
|
],
|
|
368
367
|
});
|
|
369
368
|
},
|
|
@@ -376,18 +375,25 @@ type CallTool = (opts: {
|
|
|
376
375
|
toolCallInput: unknown;
|
|
377
376
|
}) => Promise<unknown>;
|
|
378
377
|
|
|
379
|
-
export type MCPServer<
|
|
380
|
-
|
|
378
|
+
export type MCPServer<
|
|
379
|
+
TEnv = unknown,
|
|
380
|
+
TSchema extends z.ZodTypeAny = never,
|
|
381
|
+
TBindings extends BindingRegistry = BindingRegistry,
|
|
382
|
+
> = {
|
|
383
|
+
fetch: Fetch<TEnv & DefaultEnv<TSchema, TBindings>>;
|
|
381
384
|
callTool: CallTool;
|
|
382
385
|
};
|
|
383
386
|
|
|
384
387
|
export const createMCPServer = <
|
|
385
|
-
|
|
388
|
+
Env = unknown,
|
|
386
389
|
TSchema extends z.ZodTypeAny = never,
|
|
390
|
+
TBindings extends BindingRegistry = BindingRegistry,
|
|
391
|
+
TEnv extends Env & DefaultEnv<TSchema, TBindings> = Env &
|
|
392
|
+
DefaultEnv<TSchema, TBindings>,
|
|
387
393
|
>(
|
|
388
|
-
options: CreateMCPServerOptions<TEnv, TSchema>,
|
|
389
|
-
): MCPServer<TEnv, TSchema> => {
|
|
390
|
-
const createServer = async (bindings: TEnv
|
|
394
|
+
options: CreateMCPServerOptions<TEnv, TSchema, TBindings>,
|
|
395
|
+
): MCPServer<TEnv, TSchema, TBindings> => {
|
|
396
|
+
const createServer = async (bindings: TEnv) => {
|
|
391
397
|
await options.before?.(bindings);
|
|
392
398
|
|
|
393
399
|
const server = new McpServer(
|
|
@@ -398,7 +404,7 @@ export const createMCPServer = <
|
|
|
398
404
|
const toolsFn =
|
|
399
405
|
typeof options.tools === "function"
|
|
400
406
|
? options.tools
|
|
401
|
-
: async (bindings: TEnv
|
|
407
|
+
: async (bindings: TEnv) => {
|
|
402
408
|
if (typeof options.tools === "function") {
|
|
403
409
|
return await options.tools(bindings);
|
|
404
410
|
}
|
|
@@ -462,7 +468,7 @@ export const createMCPServer = <
|
|
|
462
468
|
return { server, tools };
|
|
463
469
|
};
|
|
464
470
|
|
|
465
|
-
const fetch = async (req: Request, env: TEnv
|
|
471
|
+
const fetch = async (req: Request, env: TEnv) => {
|
|
466
472
|
const { server } = await createServer(env);
|
|
467
473
|
const transport = new HttpServerTransport();
|
|
468
474
|
|
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
|
|
19
|
+
app_name?: string;
|
|
19
20
|
}
|
|
20
21
|
export interface ContractClause {
|
|
21
22
|
id: string;
|