@decocms/runtime 1.0.0-alpha.5 → 1.0.1
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 +19 -8
- package/package.json +11 -17
- package/src/asset-server/index.test.ts +306 -0
- package/src/asset-server/index.ts +217 -34
- package/src/bindings/binder.ts +2 -5
- package/src/bindings/index.ts +0 -33
- package/src/bindings/language-model/utils.ts +0 -91
- package/src/bindings.ts +146 -139
- package/src/client.ts +1 -145
- package/src/cors.ts +140 -0
- package/src/events.ts +472 -0
- package/src/index.ts +206 -202
- package/src/mcp.ts +7 -166
- package/src/oauth.ts +495 -0
- package/src/proxy.ts +1 -208
- package/src/state.ts +3 -31
- package/src/tools.ts +645 -0
- package/src/wrangler.ts +6 -5
- package/tsconfig.json +1 -1
- package/src/admin.ts +0 -16
- package/src/auth.ts +0 -233
- package/src/bindings/deconfig/helpers.ts +0 -107
- package/src/bindings/deconfig/index.ts +0 -1
- package/src/bindings/deconfig/resources.ts +0 -689
- package/src/bindings/deconfig/types.ts +0 -106
- package/src/bindings/language-model/ai-sdk.ts +0 -90
- package/src/bindings/language-model/index.ts +0 -4
- package/src/bindings/resources/bindings.ts +0 -99
- package/src/bindings/resources/helpers.ts +0 -95
- package/src/bindings/resources/schemas.ts +0 -265
- package/src/bindings/views.ts +0 -14
- package/src/drizzle.ts +0 -201
- package/src/http-client-transport.ts +0 -1
- package/src/mastra.ts +0 -670
- package/src/mcp-client.ts +0 -139
- package/src/resources.ts +0 -168
- package/src/views.ts +0 -26
- package/src/well-known.ts +0 -20
package/src/tools.ts
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
/* oxlint-disable no-explicit-any */
|
|
2
|
+
/* oxlint-disable ban-types */
|
|
3
|
+
import { HttpServerTransport } from "@deco/mcp/http";
|
|
4
|
+
import {
|
|
5
|
+
OnEventsInputSchema,
|
|
6
|
+
OnEventsOutputSchema,
|
|
7
|
+
type EventBusBindingClient,
|
|
8
|
+
} from "@decocms/bindings";
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import type { GetPromptResult } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
13
|
+
import { BindingRegistry } from "./bindings.ts";
|
|
14
|
+
import { Event, type EventHandlers } from "./events.ts";
|
|
15
|
+
import type { DefaultEnv } from "./index.ts";
|
|
16
|
+
import { State } from "./state.ts";
|
|
17
|
+
|
|
18
|
+
// Re-export EventHandlers type for external use
|
|
19
|
+
export type { EventHandlers } from "./events.ts";
|
|
20
|
+
|
|
21
|
+
export const createRuntimeContext = (prev?: AppContext) => {
|
|
22
|
+
const store = State.getStore();
|
|
23
|
+
if (!store) {
|
|
24
|
+
if (prev) {
|
|
25
|
+
return prev;
|
|
26
|
+
}
|
|
27
|
+
throw new Error("Missing context, did you forget to call State.bind?");
|
|
28
|
+
}
|
|
29
|
+
return store;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface ToolExecutionContext<
|
|
33
|
+
TSchemaIn extends z.ZodTypeAny = z.ZodTypeAny,
|
|
34
|
+
> {
|
|
35
|
+
context: z.infer<TSchemaIn>;
|
|
36
|
+
runtimeContext: AppContext;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Tool interface with generic schema types for type-safe tool creation.
|
|
41
|
+
*/
|
|
42
|
+
export interface Tool<
|
|
43
|
+
TSchemaIn extends z.ZodTypeAny = z.ZodTypeAny,
|
|
44
|
+
TSchemaOut extends z.ZodTypeAny | undefined = undefined,
|
|
45
|
+
> {
|
|
46
|
+
id: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
inputSchema: TSchemaIn;
|
|
49
|
+
outputSchema?: TSchemaOut;
|
|
50
|
+
execute(
|
|
51
|
+
context: ToolExecutionContext<TSchemaIn>,
|
|
52
|
+
): TSchemaOut extends z.ZodSchema
|
|
53
|
+
? Promise<z.infer<TSchemaOut>>
|
|
54
|
+
: Promise<unknown>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Streamable tool interface for tools that return Response streams.
|
|
59
|
+
*/
|
|
60
|
+
export interface StreamableTool<TSchemaIn extends z.ZodSchema = z.ZodSchema> {
|
|
61
|
+
id: string;
|
|
62
|
+
inputSchema: TSchemaIn;
|
|
63
|
+
streamable?: true;
|
|
64
|
+
description?: string;
|
|
65
|
+
execute(input: ToolExecutionContext<TSchemaIn>): Promise<Response>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* CreatedTool is a permissive type that any Tool or StreamableTool can be assigned to.
|
|
70
|
+
* Uses a structural type with relaxed execute signature to allow tools with any schema.
|
|
71
|
+
*/
|
|
72
|
+
export type CreatedTool = {
|
|
73
|
+
id: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
inputSchema: z.ZodTypeAny;
|
|
76
|
+
outputSchema?: z.ZodTypeAny;
|
|
77
|
+
streamable?: true;
|
|
78
|
+
// Use a permissive execute signature - accepts any context shape
|
|
79
|
+
execute(context: {
|
|
80
|
+
context: unknown;
|
|
81
|
+
runtimeContext: AppContext;
|
|
82
|
+
}): Promise<unknown>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Re-export GetPromptResult for external use
|
|
86
|
+
export type { GetPromptResult } from "@modelcontextprotocol/sdk/types.js";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Prompt argument schema shape - must be string types per MCP specification.
|
|
90
|
+
* Unlike tool arguments, prompt arguments are always strings.
|
|
91
|
+
*/
|
|
92
|
+
export type PromptArgsRawShape = {
|
|
93
|
+
[k: string]:
|
|
94
|
+
| z.ZodType<string, z.ZodTypeDef, string>
|
|
95
|
+
| z.ZodOptional<z.ZodType<string, z.ZodTypeDef, string>>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Context passed to prompt execute functions.
|
|
100
|
+
*/
|
|
101
|
+
export interface PromptExecutionContext<
|
|
102
|
+
TArgs extends PromptArgsRawShape = PromptArgsRawShape,
|
|
103
|
+
> {
|
|
104
|
+
args: z.objectOutputType<TArgs, z.ZodTypeAny>;
|
|
105
|
+
runtimeContext: AppContext;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Prompt interface with generic argument types for type-safe prompt creation.
|
|
110
|
+
*/
|
|
111
|
+
export interface Prompt<TArgs extends PromptArgsRawShape = PromptArgsRawShape> {
|
|
112
|
+
name: string;
|
|
113
|
+
title?: string;
|
|
114
|
+
description?: string;
|
|
115
|
+
argsSchema?: TArgs;
|
|
116
|
+
execute(
|
|
117
|
+
context: PromptExecutionContext<TArgs>,
|
|
118
|
+
): Promise<GetPromptResult> | GetPromptResult;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* CreatedPrompt is a permissive type that any Prompt can be assigned to.
|
|
123
|
+
* Uses a structural type with relaxed execute signature to allow prompts with any schema.
|
|
124
|
+
*/
|
|
125
|
+
export type CreatedPrompt = {
|
|
126
|
+
name: string;
|
|
127
|
+
title?: string;
|
|
128
|
+
description?: string;
|
|
129
|
+
argsSchema?: PromptArgsRawShape;
|
|
130
|
+
// Use a permissive execute signature - accepts any args shape
|
|
131
|
+
execute(context: {
|
|
132
|
+
args: Record<string, string | undefined>;
|
|
133
|
+
runtimeContext: AppContext;
|
|
134
|
+
}): Promise<GetPromptResult> | GetPromptResult;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* creates a private tool that always ensure for athentication before being executed
|
|
139
|
+
*/
|
|
140
|
+
export function createPrivateTool<
|
|
141
|
+
TSchemaIn extends z.ZodSchema = z.ZodSchema,
|
|
142
|
+
TSchemaOut extends z.ZodSchema | undefined = undefined,
|
|
143
|
+
>(opts: Tool<TSchemaIn, TSchemaOut>): Tool<TSchemaIn, TSchemaOut> {
|
|
144
|
+
const execute = opts.execute;
|
|
145
|
+
if (typeof execute === "function") {
|
|
146
|
+
opts.execute = (input: ToolExecutionContext<TSchemaIn>) => {
|
|
147
|
+
const env = input.runtimeContext.env;
|
|
148
|
+
if (env) {
|
|
149
|
+
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
150
|
+
}
|
|
151
|
+
return execute(input);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return createTool(opts);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function createStreamableTool<
|
|
158
|
+
TSchemaIn extends z.ZodSchema = z.ZodSchema,
|
|
159
|
+
>(streamableTool: StreamableTool<TSchemaIn>): StreamableTool<TSchemaIn> {
|
|
160
|
+
return {
|
|
161
|
+
...streamableTool,
|
|
162
|
+
execute: (input: ToolExecutionContext<TSchemaIn>) => {
|
|
163
|
+
const env = input.runtimeContext.env;
|
|
164
|
+
if (env) {
|
|
165
|
+
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
166
|
+
}
|
|
167
|
+
return streamableTool.execute({
|
|
168
|
+
...input,
|
|
169
|
+
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function createTool<
|
|
176
|
+
TSchemaIn extends z.ZodSchema = z.ZodSchema,
|
|
177
|
+
TSchemaOut extends z.ZodSchema | undefined = undefined,
|
|
178
|
+
>(opts: Tool<TSchemaIn, TSchemaOut>): Tool<TSchemaIn, TSchemaOut> {
|
|
179
|
+
return {
|
|
180
|
+
...opts,
|
|
181
|
+
execute: (input: ToolExecutionContext<TSchemaIn>) => {
|
|
182
|
+
return opts.execute({
|
|
183
|
+
...input,
|
|
184
|
+
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Creates a public prompt that does not require authentication.
|
|
192
|
+
*/
|
|
193
|
+
export function createPublicPrompt<TArgs extends PromptArgsRawShape>(
|
|
194
|
+
opts: Prompt<TArgs>,
|
|
195
|
+
): Prompt<TArgs> {
|
|
196
|
+
return {
|
|
197
|
+
...opts,
|
|
198
|
+
execute: (input: PromptExecutionContext<TArgs>) => {
|
|
199
|
+
return opts.execute({
|
|
200
|
+
...input,
|
|
201
|
+
runtimeContext: createRuntimeContext(input.runtimeContext),
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Creates a prompt that always ensures authentication before being executed.
|
|
209
|
+
* This is the default and recommended way to create prompts.
|
|
210
|
+
*/
|
|
211
|
+
export function createPrompt<TArgs extends PromptArgsRawShape>(
|
|
212
|
+
opts: Prompt<TArgs>,
|
|
213
|
+
): Prompt<TArgs> {
|
|
214
|
+
const execute = opts.execute;
|
|
215
|
+
return createPublicPrompt({
|
|
216
|
+
...opts,
|
|
217
|
+
execute: (input: PromptExecutionContext<TArgs>) => {
|
|
218
|
+
const env = input.runtimeContext.env;
|
|
219
|
+
if (env) {
|
|
220
|
+
env.MESH_REQUEST_CONTEXT?.ensureAuthenticated();
|
|
221
|
+
}
|
|
222
|
+
return execute(input);
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface ViewExport {
|
|
228
|
+
title: string;
|
|
229
|
+
icon: string;
|
|
230
|
+
url: string;
|
|
231
|
+
tools?: string[];
|
|
232
|
+
rules?: string[];
|
|
233
|
+
installBehavior?: "none" | "open" | "autoPin";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface Integration {
|
|
237
|
+
id: string;
|
|
238
|
+
appId: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function isStreamableTool(
|
|
242
|
+
tool: CreatedTool,
|
|
243
|
+
): tool is StreamableTool & CreatedTool {
|
|
244
|
+
return tool && "streamable" in tool && tool.streamable === true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface OnChangeCallback<TState> {
|
|
248
|
+
state: TState;
|
|
249
|
+
scopes: string[];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface OAuthParams {
|
|
253
|
+
code: string;
|
|
254
|
+
code_verifier?: string;
|
|
255
|
+
code_challenge_method?: "S256" | "plain";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface OAuthTokenResponse {
|
|
259
|
+
access_token: string;
|
|
260
|
+
token_type: string;
|
|
261
|
+
expires_in?: number;
|
|
262
|
+
refresh_token?: string;
|
|
263
|
+
scope?: string;
|
|
264
|
+
[key: string]: unknown;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* OAuth client for dynamic client registration (RFC7591)
|
|
269
|
+
*/
|
|
270
|
+
export interface OAuthClient {
|
|
271
|
+
client_id: string;
|
|
272
|
+
client_secret?: string;
|
|
273
|
+
client_name?: string;
|
|
274
|
+
redirect_uris: string[];
|
|
275
|
+
grant_types?: string[];
|
|
276
|
+
response_types?: string[];
|
|
277
|
+
token_endpoint_auth_method?: string;
|
|
278
|
+
scope?: string;
|
|
279
|
+
client_id_issued_at?: number;
|
|
280
|
+
client_secret_expires_at?: number;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* OAuth configuration for MCP servers implementing PKCE flow
|
|
285
|
+
* Per MCP Authorization spec: https://modelcontextprotocol.io/specification/draft/basic/authorization
|
|
286
|
+
*/
|
|
287
|
+
export interface OAuthConfig {
|
|
288
|
+
mode: "PKCE";
|
|
289
|
+
/**
|
|
290
|
+
* The external authorization server URL (e.g., "https://openrouter.ai")
|
|
291
|
+
* Used in protected resource metadata to indicate where clients should authenticate
|
|
292
|
+
*/
|
|
293
|
+
authorizationServer: string;
|
|
294
|
+
/**
|
|
295
|
+
* Generates the authorization URL where users should be redirected
|
|
296
|
+
* @param callbackUrl - The URL the OAuth provider will redirect back to with the code
|
|
297
|
+
* @returns The full authorization URL to redirect the user to
|
|
298
|
+
*/
|
|
299
|
+
authorizationUrl: (callbackUrl: string) => string;
|
|
300
|
+
/**
|
|
301
|
+
* Exchanges the authorization code for access tokens
|
|
302
|
+
* Called when the OAuth callback is received with a code
|
|
303
|
+
*/
|
|
304
|
+
exchangeCode: (oauthParams: OAuthParams) => Promise<OAuthTokenResponse>;
|
|
305
|
+
/**
|
|
306
|
+
* Optional: persistence for dynamic client registration (RFC7591)
|
|
307
|
+
* If not provided, clients are accepted without validation
|
|
308
|
+
*/
|
|
309
|
+
persistence?: {
|
|
310
|
+
getClient: (clientId: string) => Promise<OAuthClient | null>;
|
|
311
|
+
saveClient: (client: OAuthClient) => Promise<void>;
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Constructs a type by picking all properties from T that are assignable to Value.
|
|
317
|
+
*/
|
|
318
|
+
type PickByType<T, Value> = {
|
|
319
|
+
[P in keyof T as T[P] extends Value ? P : never]: T[P];
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
export interface CreateMCPServerOptions<
|
|
323
|
+
Env = unknown,
|
|
324
|
+
TSchema extends z.ZodTypeAny = never,
|
|
325
|
+
TBindings extends BindingRegistry = BindingRegistry,
|
|
326
|
+
TEnv extends Env & DefaultEnv<TSchema, TBindings> = Env &
|
|
327
|
+
DefaultEnv<TSchema, TBindings>,
|
|
328
|
+
State extends
|
|
329
|
+
TEnv["MESH_REQUEST_CONTEXT"]["state"] = TEnv["MESH_REQUEST_CONTEXT"]["state"],
|
|
330
|
+
> {
|
|
331
|
+
before?: (env: TEnv) => Promise<void> | void;
|
|
332
|
+
oauth?: OAuthConfig;
|
|
333
|
+
events?: {
|
|
334
|
+
bus?: keyof PickByType<State, EventBusBindingClient>;
|
|
335
|
+
handlers?: EventHandlers<TEnv, TSchema>;
|
|
336
|
+
};
|
|
337
|
+
configuration?: {
|
|
338
|
+
onChange?: (env: TEnv, cb: OnChangeCallback<State>) => Promise<void>;
|
|
339
|
+
state?: TSchema;
|
|
340
|
+
scopes?: string[];
|
|
341
|
+
};
|
|
342
|
+
tools?:
|
|
343
|
+
| Array<
|
|
344
|
+
(
|
|
345
|
+
env: TEnv,
|
|
346
|
+
) =>
|
|
347
|
+
| Promise<CreatedTool>
|
|
348
|
+
| CreatedTool
|
|
349
|
+
| CreatedTool[]
|
|
350
|
+
| Promise<CreatedTool[]>
|
|
351
|
+
>
|
|
352
|
+
| ((env: TEnv) => CreatedTool[] | Promise<CreatedTool[]>);
|
|
353
|
+
prompts?:
|
|
354
|
+
| Array<
|
|
355
|
+
(
|
|
356
|
+
env: TEnv,
|
|
357
|
+
) =>
|
|
358
|
+
| Promise<CreatedPrompt>
|
|
359
|
+
| CreatedPrompt
|
|
360
|
+
| CreatedPrompt[]
|
|
361
|
+
| Promise<CreatedPrompt[]>
|
|
362
|
+
>
|
|
363
|
+
| ((env: TEnv) => CreatedPrompt[] | Promise<CreatedPrompt[]>);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export type Fetch<TEnv = unknown> = (
|
|
367
|
+
req: Request,
|
|
368
|
+
env: TEnv,
|
|
369
|
+
ctx: any,
|
|
370
|
+
) => Promise<Response> | Response;
|
|
371
|
+
|
|
372
|
+
export interface AppContext<TEnv extends DefaultEnv = DefaultEnv> {
|
|
373
|
+
env: TEnv;
|
|
374
|
+
ctx: { waitUntil: (promise: Promise<unknown>) => void };
|
|
375
|
+
req?: Request;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const getEventBus = (
|
|
379
|
+
prop: string | number,
|
|
380
|
+
env: DefaultEnv,
|
|
381
|
+
): EventBusBindingClient | undefined => {
|
|
382
|
+
const bus = env as unknown as { [prop]: EventBusBindingClient };
|
|
383
|
+
return typeof bus[prop] !== "undefined"
|
|
384
|
+
? bus[prop]
|
|
385
|
+
: env?.MESH_REQUEST_CONTEXT.state[prop];
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const toolsFor = <TSchema extends z.ZodTypeAny = never>({
|
|
389
|
+
events,
|
|
390
|
+
configuration: { state: schema, scopes, onChange } = {},
|
|
391
|
+
}: CreateMCPServerOptions<any, TSchema> = {}): CreatedTool[] => {
|
|
392
|
+
const jsonSchema = schema
|
|
393
|
+
? zodToJsonSchema(schema)
|
|
394
|
+
: { type: "object", properties: {} };
|
|
395
|
+
const busProp = String(events?.bus ?? "EVENT_BUS");
|
|
396
|
+
return [
|
|
397
|
+
...(onChange || events
|
|
398
|
+
? [
|
|
399
|
+
createTool({
|
|
400
|
+
id: "ON_MCP_CONFIGURATION",
|
|
401
|
+
description: "MCP Configuration On Change",
|
|
402
|
+
inputSchema: z.object({
|
|
403
|
+
state: schema ?? z.unknown(),
|
|
404
|
+
scopes: z
|
|
405
|
+
.array(z.string())
|
|
406
|
+
.describe(
|
|
407
|
+
"Array of scopes in format 'KEY::SCOPE' (e.g., 'GMAIL::GetCurrentUser')",
|
|
408
|
+
),
|
|
409
|
+
}),
|
|
410
|
+
outputSchema: z.object({}),
|
|
411
|
+
execute: async (input) => {
|
|
412
|
+
const state = input.context.state as z.infer<TSchema>;
|
|
413
|
+
await onChange?.(input.runtimeContext.env, {
|
|
414
|
+
state,
|
|
415
|
+
scopes: input.context.scopes,
|
|
416
|
+
});
|
|
417
|
+
const bus = getEventBus(busProp, input.runtimeContext.env);
|
|
418
|
+
if (events && state && bus) {
|
|
419
|
+
// Sync subscriptions - always call to handle deletions too
|
|
420
|
+
const subscriptions = Event.subscriptions(
|
|
421
|
+
events?.handlers ?? {},
|
|
422
|
+
state,
|
|
423
|
+
);
|
|
424
|
+
await bus.EVENT_SYNC_SUBSCRIPTIONS({ subscriptions });
|
|
425
|
+
}
|
|
426
|
+
return Promise.resolve({});
|
|
427
|
+
},
|
|
428
|
+
}),
|
|
429
|
+
]
|
|
430
|
+
: []),
|
|
431
|
+
|
|
432
|
+
...(events?.handlers
|
|
433
|
+
? [
|
|
434
|
+
createTool({
|
|
435
|
+
id: "ON_EVENTS",
|
|
436
|
+
description:
|
|
437
|
+
"Receive and process CloudEvents from the event bus. Returns per-event or batch results.",
|
|
438
|
+
inputSchema: OnEventsInputSchema,
|
|
439
|
+
outputSchema: OnEventsOutputSchema,
|
|
440
|
+
execute: async (input) => {
|
|
441
|
+
const env = input.runtimeContext.env;
|
|
442
|
+
// Get state from MESH_REQUEST_CONTEXT - this has the binding values
|
|
443
|
+
const state = env.MESH_REQUEST_CONTEXT?.state as z.infer<TSchema>;
|
|
444
|
+
return Event.execute(
|
|
445
|
+
events.handlers!,
|
|
446
|
+
input.context.events,
|
|
447
|
+
env,
|
|
448
|
+
state,
|
|
449
|
+
);
|
|
450
|
+
},
|
|
451
|
+
}),
|
|
452
|
+
]
|
|
453
|
+
: []),
|
|
454
|
+
createTool({
|
|
455
|
+
id: "MCP_CONFIGURATION",
|
|
456
|
+
description: "MCP Configuration",
|
|
457
|
+
inputSchema: z.object({}),
|
|
458
|
+
outputSchema: z.object({
|
|
459
|
+
stateSchema: z.unknown(),
|
|
460
|
+
scopes: z.array(z.string()).optional(),
|
|
461
|
+
}),
|
|
462
|
+
execute: () => {
|
|
463
|
+
return Promise.resolve({
|
|
464
|
+
stateSchema: jsonSchema,
|
|
465
|
+
scopes: [
|
|
466
|
+
...((scopes as string[]) ?? []),
|
|
467
|
+
...(events ? [`${busProp}::EVENT_SYNC_SUBSCRIPTIONS`] : []),
|
|
468
|
+
],
|
|
469
|
+
});
|
|
470
|
+
},
|
|
471
|
+
}),
|
|
472
|
+
];
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
type CallTool = (opts: {
|
|
476
|
+
toolCallId: string;
|
|
477
|
+
toolCallInput: unknown;
|
|
478
|
+
}) => Promise<unknown>;
|
|
479
|
+
|
|
480
|
+
export type MCPServer<
|
|
481
|
+
TEnv = unknown,
|
|
482
|
+
TSchema extends z.ZodTypeAny = never,
|
|
483
|
+
TBindings extends BindingRegistry = BindingRegistry,
|
|
484
|
+
> = {
|
|
485
|
+
fetch: Fetch<TEnv & DefaultEnv<TSchema, TBindings>>;
|
|
486
|
+
callTool: CallTool;
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
export const createMCPServer = <
|
|
490
|
+
Env = unknown,
|
|
491
|
+
TSchema extends z.ZodTypeAny = never,
|
|
492
|
+
TBindings extends BindingRegistry = BindingRegistry,
|
|
493
|
+
TEnv extends Env & DefaultEnv<TSchema, TBindings> = Env &
|
|
494
|
+
DefaultEnv<TSchema, TBindings>,
|
|
495
|
+
>(
|
|
496
|
+
options: CreateMCPServerOptions<TEnv, TSchema, TBindings>,
|
|
497
|
+
): MCPServer<TEnv, TSchema, TBindings> => {
|
|
498
|
+
const createServer = async (bindings: TEnv) => {
|
|
499
|
+
await options.before?.(bindings);
|
|
500
|
+
|
|
501
|
+
const server = new McpServer(
|
|
502
|
+
{ name: "@deco/mcp-api", version: "1.0.0" },
|
|
503
|
+
{ capabilities: { tools: {}, prompts: {} } },
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
const toolsFn =
|
|
507
|
+
typeof options.tools === "function"
|
|
508
|
+
? options.tools
|
|
509
|
+
: async (bindings: TEnv) => {
|
|
510
|
+
if (typeof options.tools === "function") {
|
|
511
|
+
return await options.tools(bindings);
|
|
512
|
+
}
|
|
513
|
+
return await Promise.all(
|
|
514
|
+
options.tools?.flatMap(async (tool) => {
|
|
515
|
+
const toolResult = tool(bindings);
|
|
516
|
+
const awaited = await toolResult;
|
|
517
|
+
if (Array.isArray(awaited)) {
|
|
518
|
+
return awaited;
|
|
519
|
+
}
|
|
520
|
+
return [awaited];
|
|
521
|
+
}) ?? [],
|
|
522
|
+
).then((t) => t.flat());
|
|
523
|
+
};
|
|
524
|
+
const tools = await toolsFn(bindings);
|
|
525
|
+
|
|
526
|
+
tools.push(...toolsFor<TSchema>(options));
|
|
527
|
+
|
|
528
|
+
for (const tool of tools) {
|
|
529
|
+
server.registerTool(
|
|
530
|
+
tool.id,
|
|
531
|
+
{
|
|
532
|
+
_meta: {
|
|
533
|
+
streamable: isStreamableTool(tool),
|
|
534
|
+
},
|
|
535
|
+
description: tool.description,
|
|
536
|
+
inputSchema:
|
|
537
|
+
tool.inputSchema && "shape" in tool.inputSchema
|
|
538
|
+
? (tool.inputSchema.shape as z.ZodRawShape)
|
|
539
|
+
: z.object({}).shape,
|
|
540
|
+
outputSchema: isStreamableTool(tool)
|
|
541
|
+
? z.object({ bytes: z.record(z.string(), z.number()) }).shape
|
|
542
|
+
: tool.outputSchema &&
|
|
543
|
+
typeof tool.outputSchema === "object" &&
|
|
544
|
+
"shape" in tool.outputSchema
|
|
545
|
+
? (tool.outputSchema.shape as z.ZodRawShape)
|
|
546
|
+
: z.object({}).shape,
|
|
547
|
+
},
|
|
548
|
+
async (args) => {
|
|
549
|
+
let result = await tool.execute({
|
|
550
|
+
context: args,
|
|
551
|
+
runtimeContext: createRuntimeContext(),
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
if (isStreamableTool(tool) && result instanceof Response) {
|
|
555
|
+
result = { bytes: await result.bytes() };
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
structuredContent: result as Record<string, unknown>,
|
|
559
|
+
content: [
|
|
560
|
+
{
|
|
561
|
+
type: "text",
|
|
562
|
+
text: JSON.stringify(result),
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
};
|
|
566
|
+
},
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Resolve and register prompts
|
|
571
|
+
const promptsFn =
|
|
572
|
+
typeof options.prompts === "function"
|
|
573
|
+
? options.prompts
|
|
574
|
+
: async (bindings: TEnv) => {
|
|
575
|
+
if (typeof options.prompts === "function") {
|
|
576
|
+
return await options.prompts(bindings);
|
|
577
|
+
}
|
|
578
|
+
return await Promise.all(
|
|
579
|
+
options.prompts?.flatMap(async (prompt) => {
|
|
580
|
+
const promptResult = prompt(bindings);
|
|
581
|
+
const awaited = await promptResult;
|
|
582
|
+
if (Array.isArray(awaited)) {
|
|
583
|
+
return awaited;
|
|
584
|
+
}
|
|
585
|
+
return [awaited];
|
|
586
|
+
}) ?? [],
|
|
587
|
+
).then((p) => p.flat());
|
|
588
|
+
};
|
|
589
|
+
const prompts = await promptsFn(bindings);
|
|
590
|
+
|
|
591
|
+
for (const prompt of prompts) {
|
|
592
|
+
server.registerPrompt(
|
|
593
|
+
prompt.name,
|
|
594
|
+
{
|
|
595
|
+
title: prompt.title,
|
|
596
|
+
description: prompt.description,
|
|
597
|
+
argsSchema: prompt.argsSchema,
|
|
598
|
+
},
|
|
599
|
+
async (args) => {
|
|
600
|
+
return await prompt.execute({
|
|
601
|
+
args: args as Record<string, string | undefined>,
|
|
602
|
+
runtimeContext: createRuntimeContext(),
|
|
603
|
+
});
|
|
604
|
+
},
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return { server, tools, prompts };
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
const fetch = async (req: Request, env: TEnv) => {
|
|
612
|
+
const { server } = await createServer(env);
|
|
613
|
+
const transport = new HttpServerTransport();
|
|
614
|
+
|
|
615
|
+
await server.connect(transport);
|
|
616
|
+
|
|
617
|
+
return await transport.handleMessage(req);
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const callTool: CallTool = async ({ toolCallId, toolCallInput }) => {
|
|
621
|
+
const currentState = State.getStore();
|
|
622
|
+
if (!currentState) {
|
|
623
|
+
throw new Error("Missing state, did you forget to call State.bind?");
|
|
624
|
+
}
|
|
625
|
+
const env = currentState?.env;
|
|
626
|
+
const { tools } = await createServer(env as TEnv & DefaultEnv<TSchema>);
|
|
627
|
+
const tool = tools.find((t) => t.id === toolCallId);
|
|
628
|
+
const execute = tool?.execute;
|
|
629
|
+
if (!execute) {
|
|
630
|
+
throw new Error(
|
|
631
|
+
`Tool ${toolCallId} not found or does not have an execute function`,
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return execute({
|
|
636
|
+
context: toolCallInput,
|
|
637
|
+
runtimeContext: createRuntimeContext(),
|
|
638
|
+
});
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
fetch,
|
|
643
|
+
callTool,
|
|
644
|
+
};
|
|
645
|
+
};
|
package/src/wrangler.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
export interface BindingBase {
|
|
2
2
|
name: string;
|
|
3
|
+
array?: true;
|
|
3
4
|
}
|
|
4
5
|
|
|
5
|
-
export interface
|
|
6
|
+
export interface MCPConnectionBinding extends BindingBase {
|
|
6
7
|
type: "mcp";
|
|
7
8
|
/**
|
|
8
9
|
* If not provided, will return a function that takes the integration id and return the binding implementation..
|
|
9
10
|
*/
|
|
10
|
-
|
|
11
|
+
connection_id: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export interface
|
|
14
|
+
export interface MCPAppBinding extends BindingBase {
|
|
14
15
|
type: "mcp";
|
|
15
16
|
/**
|
|
16
17
|
* The name of the integration to bind.
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
app_name?: string;
|
|
19
20
|
}
|
|
20
21
|
export interface ContractClause {
|
|
21
22
|
id: string;
|
|
@@ -36,7 +37,7 @@ export interface ContractBinding extends BindingBase {
|
|
|
36
37
|
contract: Contract;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
export type MCPBinding =
|
|
40
|
+
export type MCPBinding = MCPConnectionBinding | MCPAppBinding;
|
|
40
41
|
|
|
41
42
|
export type Binding = MCPBinding | ContractBinding;
|
|
42
43
|
|
package/tsconfig.json
CHANGED
package/src/admin.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createChannel } from "bidc";
|
|
2
|
-
|
|
3
|
-
export const requestMissingScopes = ({ scopes }: { scopes: string[] }) => {
|
|
4
|
-
try {
|
|
5
|
-
const channel = createChannel();
|
|
6
|
-
channel.send({
|
|
7
|
-
type: "request_missing_scopes",
|
|
8
|
-
payload: {
|
|
9
|
-
scopes,
|
|
10
|
-
},
|
|
11
|
-
});
|
|
12
|
-
channel.cleanup();
|
|
13
|
-
} catch (error) {
|
|
14
|
-
console.error("Failed to request missing scopes", error);
|
|
15
|
-
}
|
|
16
|
-
};
|