@decocms/runtime 0.24.0 → 0.25.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.
Files changed (65) hide show
  1. package/dist/bindings/deconfig/index.d.ts +2 -2
  2. package/dist/bindings/deconfig/index.js +5 -5
  3. package/dist/bindings/index.d.ts +2 -2
  4. package/dist/bindings/index.js +6 -6
  5. package/dist/{chunk-4UQ5U73Y.js → chunk-F6XZPFWM.js} +3 -5
  6. package/dist/chunk-F6XZPFWM.js.map +1 -0
  7. package/dist/{chunk-ZRJ5SGAO.js → chunk-I2KGAHFY.js} +26 -6
  8. package/dist/chunk-I2KGAHFY.js.map +1 -0
  9. package/dist/{chunk-73FIKR3X.js → chunk-NKUMVYKI.js} +3 -3
  10. package/dist/chunk-NKUMVYKI.js.map +1 -0
  11. package/dist/{chunk-377XXI4J.js → chunk-O6IURJAY.js} +4 -4
  12. package/dist/{chunk-377XXI4J.js.map → chunk-O6IURJAY.js.map} +1 -1
  13. package/dist/{chunk-G3NWZG2F.js → chunk-QELHWEZH.js} +3 -3
  14. package/dist/chunk-QELHWEZH.js.map +1 -0
  15. package/dist/drizzle.d.ts +1 -1
  16. package/dist/{index-D_J_044C.d.ts → index-D8GtUDPS.d.ts} +11 -2
  17. package/dist/{index-BBAR4TQu.d.ts → index-SnnmAI05.d.ts} +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +9 -6
  20. package/dist/index.js.map +1 -1
  21. package/dist/mastra.d.ts +1 -1
  22. package/dist/mastra.js +2 -2
  23. package/dist/mcp-client.js +1 -1
  24. package/dist/proxy.js +2 -2
  25. package/dist/resources.d.ts +2 -33
  26. package/dist/resources.js +1 -1
  27. package/package.json +14 -1
  28. package/src/admin.ts +16 -0
  29. package/src/auth.ts +233 -0
  30. package/src/bindings/README.md +132 -0
  31. package/src/bindings/binder.ts +143 -0
  32. package/src/bindings/channels.ts +54 -0
  33. package/src/bindings/deconfig/helpers.ts +107 -0
  34. package/src/bindings/deconfig/index.ts +1 -0
  35. package/src/bindings/deconfig/resources.ts +659 -0
  36. package/src/bindings/deconfig/types.ts +106 -0
  37. package/src/bindings/index.ts +61 -0
  38. package/src/bindings/resources/bindings.ts +99 -0
  39. package/src/bindings/resources/helpers.ts +95 -0
  40. package/src/bindings/resources/schemas.ts +265 -0
  41. package/src/bindings/utils.ts +22 -0
  42. package/src/bindings/views.ts +14 -0
  43. package/src/bindings.ts +178 -0
  44. package/src/cf-imports.ts +1 -0
  45. package/src/client.ts +201 -0
  46. package/src/connection.ts +53 -0
  47. package/src/d1-store.ts +34 -0
  48. package/src/deprecated.ts +59 -0
  49. package/src/drizzle.ts +201 -0
  50. package/src/http-client-transport.ts +66 -0
  51. package/src/index.ts +409 -0
  52. package/src/mastra.ts +894 -0
  53. package/src/mcp-client.ts +119 -0
  54. package/src/mcp.ts +170 -0
  55. package/src/proxy.ts +212 -0
  56. package/src/resources.ts +168 -0
  57. package/src/state.ts +44 -0
  58. package/src/views.ts +26 -0
  59. package/src/well-known.ts +20 -0
  60. package/src/workflow.ts +193 -0
  61. package/src/wrangler.ts +146 -0
  62. package/dist/chunk-4UQ5U73Y.js.map +0 -1
  63. package/dist/chunk-73FIKR3X.js.map +0 -1
  64. package/dist/chunk-G3NWZG2F.js.map +0 -1
  65. package/dist/chunk-ZRJ5SGAO.js.map +0 -1
package/src/mastra.ts ADDED
@@ -0,0 +1,894 @@
1
+ /* oxlint-disable no-explicit-any */
2
+ /* oxlint-disable ban-types */
3
+ import { HttpServerTransport } from "@deco/mcp/http";
4
+ import {
5
+ createTool as mastraCreateTool,
6
+ Tool,
7
+ type ToolAction,
8
+ type ToolExecutionContext,
9
+ type Workflow,
10
+ } from "@mastra/core";
11
+ import { RuntimeContext } from "@mastra/core/di";
12
+ import {
13
+ createWorkflow,
14
+ createStep as mastraCreateStep,
15
+ type DefaultEngineType,
16
+ type ExecuteFunction,
17
+ type Step as MastraStep,
18
+ } from "@mastra/core/workflows";
19
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
20
+ import { z } from "zod";
21
+ import { zodToJsonSchema } from "zod-to-json-schema";
22
+ import type { DefaultEnv } from "./index.ts";
23
+ import {
24
+ ResourceCreateInputSchema,
25
+ ResourceCreateOutputSchema,
26
+ ResourceDeleteInputSchema,
27
+ ResourceDeleteOutputSchema,
28
+ ResourceSearchInputSchema,
29
+ ResourceSearchOutputSchema,
30
+ ResourcesListOutputSchema,
31
+ ResourcesReadInputSchema,
32
+ ResourcesReadOutputSchema,
33
+ ResourceUpdateInputSchema,
34
+ ResourceUpdateOutputSchema,
35
+ } from "./resources.ts";
36
+ import { createStateValidationTool, State } from "./state.ts";
37
+ import { ViewsListOutputSchema } from "./views.ts";
38
+ export { createWorkflow };
39
+
40
+ export { cloneStep, cloneWorkflow } from "@mastra/core/workflows";
41
+
42
+ export const createRuntimeContext = (prev?: RuntimeContext<AppContext>) => {
43
+ const runtimeContext = new RuntimeContext<AppContext>();
44
+ const store = State.getStore();
45
+ if (!store) {
46
+ if (prev) {
47
+ return prev;
48
+ }
49
+ throw new Error("Missing context, did you forget to call State.bind?");
50
+ }
51
+ const { env, ctx, req } = store;
52
+ runtimeContext.set("env", env);
53
+ runtimeContext.set("ctx", ctx);
54
+ runtimeContext.set("req", req);
55
+ return runtimeContext;
56
+ };
57
+
58
+ /**
59
+ * creates a private tool that always ensure for athentication before being executed
60
+ */
61
+ export function createPrivateTool<
62
+ TSchemaIn extends z.ZodSchema = z.ZodSchema,
63
+ TSchemaOut extends z.ZodSchema | undefined = undefined,
64
+ TSuspendSchema extends z.ZodSchema = z.ZodSchema,
65
+ TResumeSchema extends z.ZodSchema = z.ZodSchema,
66
+ TContext extends
67
+ ToolExecutionContext<TSchemaIn> = ToolExecutionContext<TSchemaIn>,
68
+ TExecute extends ToolAction<
69
+ TSchemaIn,
70
+ TSchemaOut,
71
+ any,
72
+ any,
73
+ TContext
74
+ >["execute"] = ToolAction<
75
+ TSchemaIn,
76
+ TSchemaOut,
77
+ any,
78
+ any,
79
+ TContext
80
+ >["execute"],
81
+ >(
82
+ opts: ToolAction<
83
+ TSchemaIn,
84
+ TSchemaOut,
85
+ TSuspendSchema,
86
+ TResumeSchema,
87
+ TContext
88
+ > & {
89
+ execute?: TExecute;
90
+ },
91
+ ): [TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TExecute] extends [
92
+ z.ZodSchema,
93
+ z.ZodSchema,
94
+ z.ZodSchema,
95
+ z.ZodSchema,
96
+ Function,
97
+ ]
98
+ ? Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> & {
99
+ inputSchema: TSchemaIn;
100
+ outputSchema: TSchemaOut;
101
+ execute: (context: TContext) => Promise<any>;
102
+ }
103
+ : Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> {
104
+ const execute = opts.execute;
105
+ if (typeof execute === "function") {
106
+ opts.execute = ((input, options) => {
107
+ const env = input.runtimeContext.get("env") as DefaultEnv;
108
+ if (env) {
109
+ env.DECO_REQUEST_CONTEXT.ensureAuthenticated();
110
+ }
111
+ return execute(input, options);
112
+ }) as TExecute;
113
+ }
114
+ return createTool(opts);
115
+ }
116
+
117
+ export interface StreamableTool<TSchemaIn extends z.ZodSchema = z.ZodSchema> {
118
+ id: string;
119
+ inputSchema: TSchemaIn;
120
+ streamable?: true;
121
+ description?: string;
122
+ execute: (input: ToolExecutionContext<TSchemaIn>) => Promise<Response>;
123
+ }
124
+
125
+ export function createStreamableTool<
126
+ TSchemaIn extends z.ZodSchema = z.ZodSchema,
127
+ >(streamableTool: StreamableTool<TSchemaIn>): StreamableTool<TSchemaIn> {
128
+ return {
129
+ ...streamableTool,
130
+ execute: (input: ToolExecutionContext<TSchemaIn>) => {
131
+ return streamableTool.execute({
132
+ ...input,
133
+ runtimeContext: createRuntimeContext(input.runtimeContext),
134
+ });
135
+ },
136
+ };
137
+ }
138
+
139
+ export function createTool<
140
+ TSchemaIn extends z.ZodSchema | undefined = undefined,
141
+ TSchemaOut extends z.ZodSchema | undefined = undefined,
142
+ TSuspendSchema extends z.ZodSchema = z.ZodSchema,
143
+ TResumeSchema extends z.ZodSchema = z.ZodSchema,
144
+ TContext extends ToolExecutionContext<
145
+ TSchemaIn,
146
+ TSuspendSchema,
147
+ TResumeSchema
148
+ > = ToolExecutionContext<TSchemaIn, TSuspendSchema, TResumeSchema>,
149
+ TExecute extends ToolAction<
150
+ TSchemaIn,
151
+ TSchemaOut,
152
+ TSuspendSchema,
153
+ TResumeSchema,
154
+ TContext
155
+ >["execute"] = ToolAction<
156
+ TSchemaIn,
157
+ TSchemaOut,
158
+ TSuspendSchema,
159
+ TResumeSchema,
160
+ TContext
161
+ >["execute"],
162
+ >(
163
+ opts: ToolAction<
164
+ TSchemaIn,
165
+ TSchemaOut,
166
+ TSuspendSchema,
167
+ TResumeSchema,
168
+ TContext
169
+ > & {
170
+ execute?: TExecute;
171
+ },
172
+ ): [TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TExecute] extends [
173
+ z.ZodSchema,
174
+ z.ZodSchema,
175
+ z.ZodSchema,
176
+ z.ZodSchema,
177
+ Function,
178
+ ]
179
+ ? Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> & {
180
+ inputSchema: TSchemaIn;
181
+ outputSchema: TSchemaOut;
182
+ execute: (context: TContext) => Promise<any>;
183
+ }
184
+ : Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> {
185
+ // @ts-expect-error - TSchemaIn is not a ZodType
186
+ return mastraCreateTool({
187
+ ...opts,
188
+ execute:
189
+ typeof opts?.execute === "function"
190
+ ? (((input) => {
191
+ return opts.execute!({
192
+ ...input,
193
+ runtimeContext: createRuntimeContext(input.runtimeContext),
194
+ });
195
+ }) as TExecute)
196
+ : opts.execute,
197
+ });
198
+ }
199
+
200
+ export type ExecWithContext<TF extends (...args: any[]) => any> = (
201
+ input: Omit<Parameters<TF>[0], "runtimeContext"> & {
202
+ runtimeContext: RuntimeContext<AppContext>;
203
+ },
204
+ ) => ReturnType<TF>;
205
+
206
+ export interface Step<
207
+ TStepId extends string = string,
208
+ // @ts-expect-error - TState is not a ZodObject
209
+ TState extends z.ZodObject<any> = z.ZodObject<any, z.$strip>,
210
+ TSchemaIn extends z.ZodType<any> = z.ZodType<any>,
211
+ TSchemaOut extends z.ZodType<any> = z.ZodType<any>,
212
+ TResumeSchema extends z.ZodType<any> = z.ZodType<any>,
213
+ TSuspendSchema extends z.ZodType<any> = z.ZodType<any>,
214
+ TEngineType = any,
215
+ > extends Omit<
216
+ MastraStep<
217
+ TStepId,
218
+ TState,
219
+ TSchemaIn,
220
+ TSchemaOut,
221
+ TResumeSchema,
222
+ TSuspendSchema,
223
+ TEngineType
224
+ >,
225
+ "execute"
226
+ > {
227
+ execute: ExecWithContext<
228
+ ExecuteFunction<
229
+ z.infer<TState>,
230
+ z.infer<TSchemaIn>,
231
+ z.infer<TSchemaOut>,
232
+ z.infer<TResumeSchema>,
233
+ z.infer<TSuspendSchema>,
234
+ TEngineType
235
+ >
236
+ >;
237
+ }
238
+ export function createStepFromTool<
239
+ TSchemaIn extends z.ZodType<any>,
240
+ TSchemaOut extends z.ZodType<any>,
241
+ TSuspendSchema extends z.ZodType<any>,
242
+ TResumeSchema extends z.ZodType<any>,
243
+ TContext extends ToolExecutionContext<
244
+ TSchemaIn,
245
+ TSuspendSchema,
246
+ TResumeSchema
247
+ >,
248
+ >(
249
+ tool: Tool<TSchemaIn, TSchemaOut, TSuspendSchema, TResumeSchema, TContext> & {
250
+ inputSchema: TSchemaIn;
251
+ outputSchema: TSchemaOut;
252
+ execute: (context: TContext) => Promise<any>;
253
+ },
254
+ ): Step<
255
+ string,
256
+ // @ts-expect-error - TSchemaIn is not a ZodType
257
+ TSchemaIn,
258
+ TSchemaOut,
259
+ z.ZodType<any>,
260
+ z.ZodType<any>,
261
+ DefaultEngineType
262
+ > {
263
+ // @ts-expect-error - TSchemaIn is not a ZodType
264
+ return mastraCreateStep(tool);
265
+ }
266
+
267
+ export function createStep<
268
+ TStepId extends string,
269
+ TStepInput extends z.ZodType<any>,
270
+ TStepOutput extends z.ZodType<any>,
271
+ TResumeSchema extends z.ZodType<any>,
272
+ TSuspendSchema extends z.ZodType<any>,
273
+ >(opts: {
274
+ id: TStepId;
275
+ description?: string;
276
+ inputSchema: TStepInput;
277
+ outputSchema: TStepOutput;
278
+ resumeSchema?: TResumeSchema;
279
+ suspendSchema?: TSuspendSchema;
280
+ execute: ExecWithContext<
281
+ // @ts-expect-error - TStepInput is not a ZodObject
282
+ ExecuteFunction<
283
+ z.infer<TStepInput>,
284
+ z.infer<TStepOutput>,
285
+ z.infer<TResumeSchema>,
286
+ z.infer<TSuspendSchema>,
287
+ DefaultEngineType
288
+ >
289
+ >;
290
+ }): Step<
291
+ TStepId,
292
+ // @ts-expect-error - TStepInput is not a ZodObject
293
+ TStepInput,
294
+ TStepOutput,
295
+ TResumeSchema,
296
+ TSuspendSchema,
297
+ DefaultEngineType
298
+ > {
299
+ // @ts-expect-error - TStepInput is not a ZodObject
300
+ return mastraCreateStep({
301
+ ...opts,
302
+ execute: (input) => {
303
+ return opts.execute({
304
+ ...input,
305
+ runtimeContext: createRuntimeContext(input.runtimeContext),
306
+ });
307
+ },
308
+ });
309
+ }
310
+
311
+ export interface ViewExport {
312
+ title: string;
313
+ icon: string;
314
+ url: string;
315
+ tools?: string[];
316
+ rules?: string[];
317
+ installBehavior?: "none" | "open" | "autoPin";
318
+ }
319
+
320
+ export type Resources<Env = any, TSchema extends z.ZodTypeAny = never> = Array<
321
+ (env: Env & DefaultEnv<TSchema>) => {
322
+ name: string;
323
+ icon: string;
324
+ title: string;
325
+ description?: string;
326
+ tools: {
327
+ read: (args: { uri: string }) => Promise<unknown>;
328
+ search: (args: {
329
+ term: string;
330
+ cursor?: string;
331
+ limit?: number;
332
+ }) => Promise<unknown>;
333
+ create?: (
334
+ args: z.infer<typeof ResourceCreateInputSchema>,
335
+ ) => Promise<unknown>;
336
+ update?: (
337
+ args: z.infer<typeof ResourceUpdateInputSchema>,
338
+ ) => Promise<unknown>;
339
+ delete?: (
340
+ args: z.infer<typeof ResourceDeleteInputSchema>,
341
+ ) => Promise<unknown>;
342
+ };
343
+ views?: {
344
+ list?: { url?: string; tools?: string[]; rules?: string[] };
345
+ detail?: {
346
+ url?: string;
347
+ mimeTypePattern?: string;
348
+ resourceName?: string;
349
+ tools?: string[];
350
+ rules?: string[];
351
+ };
352
+ };
353
+ }
354
+ >;
355
+ export interface Integration {
356
+ id: string;
357
+ appId: string;
358
+ }
359
+ export type CreatedTool =
360
+ | ReturnType<typeof createTool>
361
+ | ReturnType<typeof createStreamableTool>;
362
+ export function isStreamableTool(tool: CreatedTool): tool is StreamableTool {
363
+ return tool && "streamable" in tool && tool.streamable === true;
364
+ }
365
+ export interface CreateMCPServerOptions<
366
+ Env = any,
367
+ TSchema extends z.ZodTypeAny = never,
368
+ > {
369
+ before?: (env: Env & DefaultEnv<TSchema>) => Promise<void> | void;
370
+ oauth?: {
371
+ state?: TSchema;
372
+ scopes?: string[];
373
+ };
374
+ views?: (
375
+ env: Env & DefaultEnv<TSchema>,
376
+ ) => Promise<ViewExport[]> | ViewExport[];
377
+ resources?: Resources<Env, TSchema>;
378
+ tools?:
379
+ | Array<
380
+ (
381
+ env: Env & DefaultEnv<TSchema>,
382
+ ) =>
383
+ | Promise<CreatedTool>
384
+ | CreatedTool
385
+ | CreatedTool[]
386
+ | Promise<CreatedTool[]>
387
+ >
388
+ | ((
389
+ env: Env & DefaultEnv<TSchema>,
390
+ ) => CreatedTool[] | Promise<CreatedTool[]>);
391
+ workflows?: Array<
392
+ (
393
+ env: Env & DefaultEnv<TSchema>,
394
+ ) => // this is a workaround to allow workflows to be thenables
395
+ | Promise<{ workflow: ReturnType<typeof createWorkflow> }>
396
+ | ReturnType<typeof createWorkflow>
397
+ >;
398
+ }
399
+
400
+ export type Fetch<TEnv = any> = (
401
+ req: Request,
402
+ env: TEnv,
403
+ ctx: ExecutionContext,
404
+ ) => Promise<Response> | Response;
405
+
406
+ export interface AppContext<TEnv = any> {
407
+ env: TEnv;
408
+ ctx: { waitUntil: (promise: Promise<any>) => void };
409
+ req?: Request;
410
+ }
411
+
412
+ const decoChatOAuthToolsFor = <TSchema extends z.ZodTypeAny = never>({
413
+ state: schema,
414
+ scopes,
415
+ }: CreateMCPServerOptions<any, TSchema>["oauth"] = {}) => {
416
+ const jsonSchema = schema
417
+ ? zodToJsonSchema(schema)
418
+ : { type: "object", properties: {} };
419
+ return [
420
+ createTool({
421
+ id: "DECO_CHAT_OAUTH_START",
422
+ description: "OAuth for Deco Chat",
423
+ inputSchema: z.object({
424
+ returnUrl: z.string(),
425
+ }),
426
+ outputSchema: z.object({
427
+ stateSchema: z.any(),
428
+ scopes: z.array(z.string()).optional(),
429
+ }),
430
+ execute: () => {
431
+ return Promise.resolve({
432
+ stateSchema: jsonSchema,
433
+ scopes,
434
+ });
435
+ },
436
+ }),
437
+ ];
438
+ };
439
+
440
+ const createWorkflowTools = <TEnv = any, TSchema extends z.ZodTypeAny = never>(
441
+ workflow: ReturnType<typeof createWorkflow>,
442
+ bindings: TEnv & DefaultEnv<TSchema>,
443
+ ) => {
444
+ const startTool = createTool({
445
+ id: `DECO_CHAT_WORKFLOWS_START_${workflow.id}`,
446
+ description: workflow.description ?? `Start workflow ${workflow.id}`,
447
+ inputSchema:
448
+ workflow.inputSchema && "shape" in workflow.inputSchema
449
+ ? workflow.inputSchema
450
+ : z.object({}),
451
+ outputSchema: z.object({
452
+ id: z.string(),
453
+ }),
454
+ execute: async (args) => {
455
+ const store = State.getStore();
456
+ const runId =
457
+ store?.req?.headers.get("x-deco-chat-run-id") ?? crypto.randomUUID();
458
+ const workflowDO = bindings.DECO_WORKFLOW_DO.get(
459
+ bindings.DECO_WORKFLOW_DO.idFromName(runId),
460
+ );
461
+
462
+ using result = await workflowDO.start({
463
+ workflowId: workflow.id,
464
+ args: args.context,
465
+ runId,
466
+ ctx: bindings.DECO_REQUEST_CONTEXT,
467
+ });
468
+ return { id: result.runId };
469
+ },
470
+ });
471
+
472
+ const cancelTool = createTool({
473
+ id: `DECO_CHAT_WORKFLOWS_CANCEL_${workflow.id}`,
474
+ description: `Cancel ${workflow.description ?? `workflow ${workflow.id}`}`,
475
+ inputSchema: z.object({ runId: z.string() }),
476
+ outputSchema: z.object({ cancelled: z.boolean() }),
477
+ execute: async (args) => {
478
+ const runId = args.context.runId;
479
+ const workflowDO = bindings.DECO_WORKFLOW_DO.get(
480
+ bindings.DECO_WORKFLOW_DO.idFromName(runId),
481
+ );
482
+
483
+ using _ = await workflowDO.cancel({
484
+ workflowId: workflow.id,
485
+ runId,
486
+ ctx: bindings.DECO_REQUEST_CONTEXT,
487
+ });
488
+
489
+ return { cancelled: true };
490
+ },
491
+ });
492
+
493
+ const resumeTool = createTool({
494
+ id: `DECO_CHAT_WORKFLOWS_RESUME_${workflow.id}`,
495
+ description: `Resume ${workflow.description ?? `workflow ${workflow.id}`}`,
496
+ inputSchema: z.object({
497
+ runId: z.string(),
498
+ stepId: z.string(),
499
+ resumeData: z.any(),
500
+ }),
501
+ outputSchema: z.object({ resumed: z.boolean() }),
502
+ execute: async (args) => {
503
+ const runId = args.context.runId;
504
+ const workflowDO = bindings.DECO_WORKFLOW_DO.get(
505
+ bindings.DECO_WORKFLOW_DO.idFromName(runId),
506
+ );
507
+
508
+ using _ = await workflowDO.resume({
509
+ workflowId: workflow.id,
510
+ runId,
511
+ resumeData: args.context.resumeData,
512
+ stepId: args.context.stepId,
513
+ ctx: bindings.DECO_REQUEST_CONTEXT,
514
+ });
515
+
516
+ return { resumed: true };
517
+ },
518
+ });
519
+
520
+ return [startTool, cancelTool, resumeTool];
521
+ };
522
+
523
+ type CallTool = (opts: {
524
+ toolCallId: string;
525
+ toolCallInput: any;
526
+ }) => Promise<any>;
527
+
528
+ export type MCPServer<TEnv = any, TSchema extends z.ZodTypeAny = never> = {
529
+ fetch: Fetch<TEnv & DefaultEnv<TSchema>>;
530
+ callTool: CallTool;
531
+ };
532
+
533
+ export const isWorkflow = (value: any): value is Workflow => {
534
+ return value && !(value instanceof Promise);
535
+ };
536
+
537
+ export const createMCPServer = <
538
+ TEnv = any,
539
+ TSchema extends z.ZodTypeAny = never,
540
+ >(
541
+ options: CreateMCPServerOptions<TEnv, TSchema>,
542
+ ): MCPServer<TEnv, TSchema> => {
543
+ const createServer = async (bindings: TEnv & DefaultEnv<TSchema>) => {
544
+ await options.before?.(bindings);
545
+
546
+ const server = new McpServer(
547
+ { name: "@deco/mcp-api", version: "1.0.0" },
548
+ { capabilities: { tools: {} } },
549
+ );
550
+
551
+ // Resolve resources first; build resource tools; append later
552
+ const resolvedResources = await Promise.all(
553
+ options.resources?.map((r) => r(bindings)) ?? [],
554
+ );
555
+ const readHandlers = new Map<
556
+ string,
557
+ (a: { uri: string }) => Promise<any>
558
+ >();
559
+ const searchHandlers = new Map<
560
+ string,
561
+ (a: { term: string; cursor?: string; limit?: number }) => Promise<any>
562
+ >();
563
+ const createHandlers = new Map<string, (a: any) => Promise<any>>();
564
+ const updateHandlers = new Map<string, (a: any) => Promise<any>>();
565
+ const deleteHandlers = new Map<string, (a: any) => Promise<any>>();
566
+ for (const r of resolvedResources) {
567
+ if (r?.tools?.read) readHandlers.set(r.name, r.tools.read);
568
+ if (r?.tools?.search) searchHandlers.set(r.name, r.tools.search);
569
+ if (r?.tools?.create) createHandlers.set(r.name, r.tools.create);
570
+ if (r?.tools?.update) updateHandlers.set(r.name, r.tools.update);
571
+ if (r?.tools?.delete) deleteHandlers.set(r.name, r.tools.delete);
572
+ }
573
+ const resourceTools: ReturnType<typeof createTool>[] = [];
574
+ if (resolvedResources.length > 0) {
575
+ resourceTools.push(
576
+ createTool({
577
+ id: "DECO_CHAT_RESOURCES_READ",
578
+ description: "Read a resource by uri (name + uri)",
579
+ inputSchema: ResourcesReadInputSchema,
580
+ outputSchema: ResourcesReadOutputSchema,
581
+ execute: (input) => {
582
+ // @ts-expect-error - input.name is not a string
583
+ const fn = readHandlers.get(input.name);
584
+ if (!fn) {
585
+ // @ts-expect-error - input.name is not a string
586
+ throw new Error(`READ not implemented for ${input.name}`);
587
+ }
588
+ // @ts-expect-error - input.name is not a string
589
+ return fn({ uri: input.uri });
590
+ },
591
+ }),
592
+ );
593
+ resourceTools.push(
594
+ createTool({
595
+ id: "DECO_CHAT_RESOURCES_SEARCH",
596
+ description: "Search resources (name + term)",
597
+ inputSchema: ResourceSearchInputSchema,
598
+ outputSchema: ResourceSearchOutputSchema,
599
+ execute: (input) => {
600
+ // @ts-expect-error - input.name is not a string
601
+ const fn = searchHandlers.get(input.name);
602
+ if (!fn) {
603
+ // @ts-expect-error - input.name is not a string
604
+ throw new Error(`SEARCH not implemented for ${input.name}`);
605
+ }
606
+ // @ts-expect-error - input.name is not a string
607
+ const { term, cursor, limit } = input;
608
+ return fn({ term, cursor, limit });
609
+ },
610
+ }),
611
+ );
612
+ resourceTools.push(
613
+ createTool({
614
+ id: "DECO_CHAT_RESOURCES_CREATE",
615
+ description: "Create a resource (name + content)",
616
+ inputSchema: ResourceCreateInputSchema,
617
+ outputSchema: ResourceCreateOutputSchema,
618
+ execute: (input) => {
619
+ // @ts-expect-error - input.name is not a string
620
+ const fn = createHandlers.get(input.name);
621
+ if (!fn) {
622
+ // @ts-expect-error - input.name is not a string
623
+ throw new Error(`CREATE not implemented for ${input.name}`);
624
+ }
625
+ return fn(input);
626
+ },
627
+ }),
628
+ );
629
+ resourceTools.push(
630
+ createTool({
631
+ id: "DECO_CHAT_RESOURCES_UPDATE",
632
+ description: "Update a resource (name + uri)",
633
+ inputSchema: ResourceUpdateInputSchema,
634
+ outputSchema: ResourceUpdateOutputSchema,
635
+ execute: (input) => {
636
+ // @ts-expect-error - input.name is not a string
637
+ const fn = updateHandlers.get(input.name);
638
+ if (!fn) {
639
+ // @ts-expect-error - input.name is not a string
640
+ throw new Error(`UPDATE not implemented for ${input.name}`);
641
+ }
642
+ return fn(input);
643
+ },
644
+ }),
645
+ );
646
+ resourceTools.push(
647
+ createTool({
648
+ id: "DECO_CHAT_RESOURCES_DELETE",
649
+ description: "Delete a resource (name + uri)",
650
+ inputSchema: ResourceDeleteInputSchema,
651
+ outputSchema: ResourceDeleteOutputSchema,
652
+ execute: (input) => {
653
+ // @ts-expect-error - input.name is not a string
654
+ const fn = deleteHandlers.get(input.name);
655
+ if (!fn) {
656
+ // @ts-expect-error - input.name is not a string
657
+ throw new Error(`DELETE not implemented for ${input.name}`);
658
+ }
659
+ return fn(input);
660
+ },
661
+ }),
662
+ );
663
+ resourceTools.push(
664
+ createTool({
665
+ id: "DECO_CHAT_RESOURCES_LIST",
666
+ description: "List resource types",
667
+ inputSchema: z.object({}),
668
+ outputSchema: ResourcesListOutputSchema,
669
+ execute: () =>
670
+ Promise.resolve({
671
+ resources: resolvedResources.map((r) => ({
672
+ name: r.name,
673
+ icon: r.icon,
674
+ title: r.title,
675
+ description: r.description ?? "",
676
+ hasCreate: Boolean(createHandlers.get(r.name)),
677
+ hasUpdate: Boolean(updateHandlers.get(r.name)),
678
+ hasDelete: Boolean(deleteHandlers.get(r.name)),
679
+ })),
680
+ }),
681
+ }),
682
+ );
683
+ }
684
+
685
+ const toolsFn =
686
+ typeof options.tools === "function"
687
+ ? options.tools
688
+ : async (bindings: TEnv & DefaultEnv<TSchema>) => {
689
+ if (typeof options.tools === "function") {
690
+ return await options.tools(bindings);
691
+ }
692
+ return await Promise.all(
693
+ options.tools?.flatMap(async (tool) => {
694
+ const toolResult = tool(bindings);
695
+ const awaited = await toolResult;
696
+ if (Array.isArray(awaited)) {
697
+ return awaited;
698
+ }
699
+ return [awaited];
700
+ }) ?? [],
701
+ ).then((t) => t.flat());
702
+ };
703
+ const tools = await toolsFn(bindings);
704
+
705
+ // since mastra workflows are thenables, we need to await and add as a prop
706
+ const workflows = await Promise.all(
707
+ options.workflows?.map(async (workflow) => {
708
+ const workflowResult = workflow(bindings);
709
+ if (isWorkflow(workflowResult)) {
710
+ return { workflow: workflowResult };
711
+ }
712
+
713
+ return await workflowResult;
714
+ }) ?? [],
715
+ ).then((w) => w.map((w) => w.workflow));
716
+
717
+ const workflowTools =
718
+ workflows?.flatMap((workflow) =>
719
+ createWorkflowTools(workflow, bindings),
720
+ ) ?? [];
721
+
722
+ tools.push(...workflowTools);
723
+ tools.push(...decoChatOAuthToolsFor<TSchema>(options.oauth));
724
+ tools.push(createStateValidationTool(options.oauth?.state));
725
+
726
+ tools.push(
727
+ createTool({
728
+ id: `DECO_CHAT_VIEWS_LIST`,
729
+ description: "List views exposed by this MCP",
730
+ inputSchema: z.any(),
731
+ outputSchema: ViewsListOutputSchema,
732
+ execute: async () => {
733
+ const base = ((await options.views?.(bindings)) ?? []).map((v) => ({
734
+ id: undefined,
735
+ // Stable machine name for routing: UPPERCASE + underscores
736
+ name: v.title.toUpperCase().replace(/[^A-Z0-9]/g, "_"),
737
+ title: v.title,
738
+ description: undefined,
739
+ icon: v.icon,
740
+ url: v.url,
741
+ tools: v.tools ?? [],
742
+ rules: v.rules ?? [],
743
+ installBehavior: v.installBehavior ?? "none",
744
+ }));
745
+ const resourceViews = resolvedResources
746
+ .map((r) => {
747
+ const listUrl =
748
+ r.views?.list?.url ??
749
+ `internal://resource/list?name=${encodeURIComponent(r.name)}`;
750
+
751
+ // Default CRUD tool ids for resources
752
+ const defaultListTools: string[] = (() => {
753
+ const base = [
754
+ "DECO_CHAT_RESOURCES_LIST",
755
+ "DECO_CHAT_RESOURCES_READ",
756
+ "DECO_CHAT_RESOURCES_SEARCH",
757
+ ];
758
+ const canCreate = Boolean(createHandlers.get(r.name));
759
+ const canUpdate = Boolean(updateHandlers.get(r.name));
760
+ const canDelete = Boolean(deleteHandlers.get(r.name));
761
+ if (canCreate) base.push("DECO_CHAT_RESOURCES_CREATE");
762
+ if (canUpdate) base.push("DECO_CHAT_RESOURCES_UPDATE");
763
+ if (canDelete) base.push("DECO_CHAT_RESOURCES_DELETE");
764
+ return base;
765
+ })();
766
+
767
+ const defaultListRules: string[] = [
768
+ `You are viewing the ${
769
+ r.title ?? r.name
770
+ } resources list. Resources are changeable via Resource tools (DECO_CHAT_RESOURCES_*). Use the appropriate tools to read, search, create, update, or delete items; do not fabricate data.`,
771
+ ];
772
+
773
+ const list = [
774
+ {
775
+ name: `${r.name.toUpperCase()}_LIST`,
776
+ title: `${r.name} List`,
777
+ description: r.description,
778
+ icon: r.icon,
779
+ url: listUrl,
780
+ tools: r.views?.list?.tools ?? defaultListTools,
781
+ rules: r.views?.list?.rules ?? defaultListRules,
782
+ },
783
+ ];
784
+ const detailUrl =
785
+ r.views?.detail?.url ??
786
+ `internal://resource/detail?name=${encodeURIComponent(r.name)}`;
787
+ const detail = [
788
+ {
789
+ name: `${r.name.toUpperCase()}_DETAIL`,
790
+ title: `${r.name} Detail`,
791
+ description: r.description,
792
+ icon: r.icon,
793
+ url: detailUrl,
794
+ mimeTypePattern: r.views?.detail?.mimeTypePattern,
795
+ resourceName: r.views?.detail?.resourceName ?? r.name,
796
+ tools: r.views?.detail?.tools ?? [],
797
+ rules: r.views?.detail?.rules ?? [],
798
+ },
799
+ ];
800
+ return [...list, ...detail];
801
+ })
802
+ .flat();
803
+
804
+ return { views: [...base, ...resourceViews] };
805
+ },
806
+ }),
807
+ );
808
+
809
+ for (const tool of tools) {
810
+ server.registerTool(
811
+ tool.id,
812
+ {
813
+ _meta: {
814
+ streamable: isStreamableTool(tool),
815
+ },
816
+ description: tool.description,
817
+ inputSchema:
818
+ tool.inputSchema && "shape" in tool.inputSchema
819
+ ? (tool.inputSchema.shape as z.ZodRawShape)
820
+ : z.object({}).shape,
821
+ outputSchema: isStreamableTool(tool)
822
+ ? z.object({ bytes: z.record(z.string(), z.number()) }).shape
823
+ : tool.outputSchema &&
824
+ typeof tool.outputSchema === "object" &&
825
+ "shape" in tool.outputSchema
826
+ ? (tool.outputSchema.shape as z.ZodRawShape)
827
+ : z.object({}).shape,
828
+ },
829
+ async (args) => {
830
+ let result = await tool.execute!({
831
+ context: args,
832
+ runId: crypto.randomUUID(),
833
+ runtimeContext: createRuntimeContext(),
834
+ });
835
+
836
+ if (isStreamableTool(tool) && result instanceof Response) {
837
+ result = { bytes: await result.bytes() };
838
+ }
839
+ return {
840
+ structuredContent: result,
841
+ content: [
842
+ {
843
+ type: "text",
844
+ text: JSON.stringify(result),
845
+ },
846
+ ],
847
+ };
848
+ },
849
+ );
850
+ }
851
+
852
+ return { server, tools };
853
+ };
854
+
855
+ const fetch = async (
856
+ req: Request,
857
+ env: TEnv & DefaultEnv<TSchema>,
858
+ _ctx: ExecutionContext,
859
+ ) => {
860
+ const { server } = await createServer(env);
861
+ const transport = new HttpServerTransport();
862
+
863
+ await server.connect(transport);
864
+
865
+ return await transport.handleMessage(req);
866
+ };
867
+
868
+ const callTool: CallTool = async ({ toolCallId, toolCallInput }) => {
869
+ const currentState = State.getStore();
870
+ if (!currentState) {
871
+ throw new Error("Missing state, did you forget to call State.bind?");
872
+ }
873
+ const env = currentState?.env;
874
+ const { tools } = await createServer(env);
875
+ const tool = tools.find((t) => t.id === toolCallId);
876
+ const execute = tool?.execute;
877
+ if (!execute) {
878
+ throw new Error(
879
+ `Tool ${toolCallId} not found or does not have an execute function`,
880
+ );
881
+ }
882
+
883
+ return execute({
884
+ context: toolCallInput,
885
+ runId: crypto.randomUUID(),
886
+ runtimeContext: createRuntimeContext(),
887
+ });
888
+ };
889
+
890
+ return {
891
+ fetch,
892
+ callTool,
893
+ };
894
+ };