@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.
- package/config-schema.json +12 -1
- package/package.json +2 -2
- package/src/bindings/binder.ts +1 -1
- package/src/bindings.ts +143 -57
- package/src/events.ts +1 -39
- package/src/index.ts +35 -47
- package/src/tools.ts +36 -29
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/runtime",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
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.
|
|
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",
|
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,97 @@
|
|
|
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 = <
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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);
|
|
144
|
+
// Handle arrays
|
|
145
|
+
if (Array.isArray(obj)) {
|
|
146
|
+
return obj.map((item) => traverseAndReplace(item, ctx));
|
|
84
147
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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 {
|
|
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
|
+
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 = <
|
|
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<
|
|
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> = (
|
|
@@ -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"
|
|
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
|
-
...
|
|
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<
|
|
380
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
19
|
+
app_name?: string;
|
|
19
20
|
}
|
|
20
21
|
export interface ContractClause {
|
|
21
22
|
id: string;
|