@decocms/runtime 1.0.0-alpha.14 → 1.0.0-alpha.17

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.
@@ -0,0 +1,553 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$ref": "#/definitions/WranglerConfig",
4
+ "definitions": {
5
+ "WranglerConfig": {
6
+ "type": "object",
7
+ "properties": {
8
+ "name": {
9
+ "type": "string"
10
+ },
11
+ "main": {
12
+ "type": "string"
13
+ },
14
+ "scope": {
15
+ "type": "string"
16
+ },
17
+ "main_module": {
18
+ "type": "string"
19
+ },
20
+ "routes": {
21
+ "type": "array",
22
+ "items": {
23
+ "$ref": "#/definitions/Route"
24
+ }
25
+ },
26
+ "compatibility_date": {
27
+ "type": "string"
28
+ },
29
+ "compatibility_flags": {
30
+ "type": "array",
31
+ "items": {
32
+ "type": "string"
33
+ }
34
+ },
35
+ "vars": {
36
+ "type": "object",
37
+ "additionalProperties": {
38
+ "type": "string"
39
+ }
40
+ },
41
+ "kv_namespaces": {
42
+ "type": "array",
43
+ "items": {
44
+ "$ref": "#/definitions/KVNamespace"
45
+ }
46
+ },
47
+ "triggers": {
48
+ "$ref": "#/definitions/Triggers"
49
+ },
50
+ "ai": {
51
+ "type": "object",
52
+ "properties": {
53
+ "binding": {
54
+ "type": "string"
55
+ }
56
+ },
57
+ "required": [
58
+ "binding"
59
+ ],
60
+ "additionalProperties": false
61
+ },
62
+ "browser": {
63
+ "type": "object",
64
+ "properties": {
65
+ "binding": {
66
+ "type": "string"
67
+ }
68
+ },
69
+ "required": [
70
+ "binding"
71
+ ],
72
+ "additionalProperties": false
73
+ },
74
+ "durable_objects": {
75
+ "type": "object",
76
+ "properties": {
77
+ "bindings": {
78
+ "type": "array",
79
+ "items": {
80
+ "type": "object",
81
+ "properties": {
82
+ "name": {
83
+ "type": "string"
84
+ },
85
+ "class_name": {
86
+ "type": "string"
87
+ }
88
+ },
89
+ "required": [
90
+ "name",
91
+ "class_name"
92
+ ],
93
+ "additionalProperties": false
94
+ }
95
+ }
96
+ },
97
+ "additionalProperties": false
98
+ },
99
+ "hyperdrive": {
100
+ "type": "array",
101
+ "items": {
102
+ "type": "object",
103
+ "properties": {
104
+ "binding": {
105
+ "type": "string"
106
+ },
107
+ "id": {
108
+ "type": "string"
109
+ },
110
+ "localConnectionString": {
111
+ "type": "string"
112
+ }
113
+ },
114
+ "required": [
115
+ "binding",
116
+ "id",
117
+ "localConnectionString"
118
+ ],
119
+ "additionalProperties": false
120
+ }
121
+ },
122
+ "d1_databases": {
123
+ "type": "array",
124
+ "items": {
125
+ "type": "object",
126
+ "properties": {
127
+ "database_name": {
128
+ "type": "string"
129
+ },
130
+ "database_id": {
131
+ "type": "string"
132
+ },
133
+ "binding": {
134
+ "type": "string"
135
+ }
136
+ },
137
+ "required": [
138
+ "database_name",
139
+ "binding"
140
+ ],
141
+ "additionalProperties": false
142
+ }
143
+ },
144
+ "queues": {
145
+ "type": "object",
146
+ "properties": {
147
+ "consumers": {
148
+ "type": "array",
149
+ "items": {
150
+ "type": "object",
151
+ "properties": {
152
+ "queue": {
153
+ "type": "string"
154
+ },
155
+ "max_batch_timeout": {
156
+ "type": "number"
157
+ }
158
+ },
159
+ "required": [
160
+ "queue",
161
+ "max_batch_timeout"
162
+ ],
163
+ "additionalProperties": false
164
+ }
165
+ },
166
+ "producers": {
167
+ "type": "array",
168
+ "items": {
169
+ "type": "object",
170
+ "properties": {
171
+ "queue": {
172
+ "type": "string"
173
+ },
174
+ "binding": {
175
+ "type": "string"
176
+ }
177
+ },
178
+ "required": [
179
+ "queue",
180
+ "binding"
181
+ ],
182
+ "additionalProperties": false
183
+ }
184
+ }
185
+ },
186
+ "additionalProperties": false
187
+ },
188
+ "workflows": {
189
+ "type": "array",
190
+ "items": {
191
+ "type": "object",
192
+ "properties": {
193
+ "name": {
194
+ "type": "string"
195
+ },
196
+ "binding": {
197
+ "type": "string"
198
+ },
199
+ "class_name": {
200
+ "type": "string"
201
+ },
202
+ "script_name": {
203
+ "type": "string"
204
+ }
205
+ },
206
+ "required": [
207
+ "name",
208
+ "binding"
209
+ ],
210
+ "additionalProperties": false
211
+ }
212
+ },
213
+ "migrations": {
214
+ "type": "array",
215
+ "items": {
216
+ "$ref": "#/definitions/Migration"
217
+ }
218
+ },
219
+ "assets": {
220
+ "type": "object",
221
+ "properties": {
222
+ "directory": {
223
+ "type": "string"
224
+ },
225
+ "binding": {
226
+ "type": "string"
227
+ },
228
+ "jwt": {
229
+ "type": "string"
230
+ },
231
+ "not_found_handling": {
232
+ "type": "string",
233
+ "enum": [
234
+ "none",
235
+ "404-page",
236
+ "single-page-application"
237
+ ]
238
+ },
239
+ "run_worker_first": {
240
+ "type": "boolean"
241
+ }
242
+ },
243
+ "additionalProperties": false
244
+ },
245
+ "keep_assets": {
246
+ "type": "boolean"
247
+ },
248
+ "deco": {
249
+ "type": "object",
250
+ "properties": {
251
+ "enable_workflows": {
252
+ "type": "boolean"
253
+ },
254
+ "workspace": {
255
+ "type": "string"
256
+ },
257
+ "local": {
258
+ "type": "boolean"
259
+ },
260
+ "integration": {
261
+ "type": "object",
262
+ "properties": {
263
+ "friendlyName": {
264
+ "type": "string"
265
+ },
266
+ "icon": {
267
+ "type": "string"
268
+ },
269
+ "description": {
270
+ "type": "string"
271
+ }
272
+ },
273
+ "additionalProperties": false
274
+ },
275
+ "bindings": {
276
+ "type": "array",
277
+ "items": {
278
+ "$ref": "#/definitions/Binding"
279
+ }
280
+ }
281
+ },
282
+ "additionalProperties": false
283
+ }
284
+ },
285
+ "required": [
286
+ "name"
287
+ ],
288
+ "additionalProperties": false
289
+ },
290
+ "Route": {
291
+ "type": "object",
292
+ "properties": {
293
+ "pattern": {
294
+ "type": "string"
295
+ },
296
+ "custom_domain": {
297
+ "type": "boolean"
298
+ }
299
+ },
300
+ "required": [
301
+ "pattern"
302
+ ],
303
+ "additionalProperties": false
304
+ },
305
+ "KVNamespace": {
306
+ "type": "object",
307
+ "properties": {
308
+ "binding": {
309
+ "type": "string"
310
+ },
311
+ "id": {
312
+ "type": "string"
313
+ }
314
+ },
315
+ "required": [
316
+ "binding",
317
+ "id"
318
+ ],
319
+ "additionalProperties": false
320
+ },
321
+ "Triggers": {
322
+ "type": "object",
323
+ "properties": {
324
+ "crons": {
325
+ "type": "array",
326
+ "items": {
327
+ "type": "string"
328
+ }
329
+ }
330
+ },
331
+ "required": [
332
+ "crons"
333
+ ],
334
+ "additionalProperties": false
335
+ },
336
+ "Migration": {
337
+ "anyOf": [
338
+ {
339
+ "$ref": "#/definitions/NewClassMigration"
340
+ },
341
+ {
342
+ "$ref": "#/definitions/DeletedClassMigration"
343
+ },
344
+ {
345
+ "$ref": "#/definitions/RenamedClassMigration"
346
+ }
347
+ ]
348
+ },
349
+ "NewClassMigration": {
350
+ "type": "object",
351
+ "properties": {
352
+ "tag": {
353
+ "type": "string"
354
+ },
355
+ "new_classes": {
356
+ "type": "array",
357
+ "items": {
358
+ "type": "string"
359
+ }
360
+ },
361
+ "new_sqlite_classes": {
362
+ "type": "array",
363
+ "items": {
364
+ "type": "string"
365
+ }
366
+ }
367
+ },
368
+ "additionalProperties": false,
369
+ "required": [
370
+ "tag"
371
+ ]
372
+ },
373
+ "DeletedClassMigration": {
374
+ "type": "object",
375
+ "properties": {
376
+ "tag": {
377
+ "type": "string"
378
+ },
379
+ "deleted_classes": {
380
+ "type": "array",
381
+ "items": {
382
+ "type": "string"
383
+ }
384
+ }
385
+ },
386
+ "required": [
387
+ "deleted_classes",
388
+ "tag"
389
+ ],
390
+ "additionalProperties": false
391
+ },
392
+ "RenamedClassMigration": {
393
+ "type": "object",
394
+ "properties": {
395
+ "tag": {
396
+ "type": "string"
397
+ },
398
+ "renamed_classes": {
399
+ "type": "array",
400
+ "items": {
401
+ "type": "object",
402
+ "properties": {
403
+ "from": {
404
+ "type": "string"
405
+ },
406
+ "to": {
407
+ "type": "string"
408
+ }
409
+ },
410
+ "required": [
411
+ "from",
412
+ "to"
413
+ ],
414
+ "additionalProperties": false
415
+ }
416
+ }
417
+ },
418
+ "required": [
419
+ "renamed_classes",
420
+ "tag"
421
+ ],
422
+ "additionalProperties": false
423
+ },
424
+ "Binding": {
425
+ "anyOf": [
426
+ {
427
+ "$ref": "#/definitions/MCPBinding"
428
+ },
429
+ {
430
+ "$ref": "#/definitions/ContractBinding"
431
+ }
432
+ ]
433
+ },
434
+ "MCPBinding": {
435
+ "anyOf": [
436
+ {
437
+ "$ref": "#/definitions/MCPConnectionBinding"
438
+ },
439
+ {
440
+ "$ref": "#/definitions/MCPAppBinding"
441
+ }
442
+ ]
443
+ },
444
+ "MCPConnectionBinding": {
445
+ "type": "object",
446
+ "properties": {
447
+ "name": {
448
+ "type": "string"
449
+ },
450
+ "type": {
451
+ "type": "string",
452
+ "const": "mcp"
453
+ },
454
+ "connection_id": {
455
+ "type": "string",
456
+ "description": "If not provided, will return a function that takes the integration id and return the binding implementation.."
457
+ }
458
+ },
459
+ "required": [
460
+ "connection_id",
461
+ "name",
462
+ "type"
463
+ ],
464
+ "additionalProperties": false
465
+ },
466
+ "MCPAppBinding": {
467
+ "type": "object",
468
+ "properties": {
469
+ "name": {
470
+ "type": "string"
471
+ },
472
+ "type": {
473
+ "type": "string",
474
+ "const": "mcp"
475
+ },
476
+ "app_name": {
477
+ "type": "string",
478
+ "description": "The name of the integration to bind."
479
+ }
480
+ },
481
+ "required": [
482
+ "app_name",
483
+ "name",
484
+ "type"
485
+ ],
486
+ "additionalProperties": false
487
+ },
488
+ "ContractBinding": {
489
+ "type": "object",
490
+ "properties": {
491
+ "name": {
492
+ "type": "string"
493
+ },
494
+ "type": {
495
+ "type": "string",
496
+ "const": "contract"
497
+ },
498
+ "contract": {
499
+ "$ref": "#/definitions/Contract",
500
+ "description": "The clauses of this contract"
501
+ }
502
+ },
503
+ "required": [
504
+ "contract",
505
+ "name",
506
+ "type"
507
+ ],
508
+ "additionalProperties": false
509
+ },
510
+ "Contract": {
511
+ "type": "object",
512
+ "properties": {
513
+ "body": {
514
+ "type": "string"
515
+ },
516
+ "clauses": {
517
+ "type": "array",
518
+ "items": {
519
+ "$ref": "#/definitions/ContractClause"
520
+ }
521
+ }
522
+ },
523
+ "required": [
524
+ "body",
525
+ "clauses"
526
+ ],
527
+ "additionalProperties": false
528
+ },
529
+ "ContractClause": {
530
+ "type": "object",
531
+ "properties": {
532
+ "id": {
533
+ "type": "string"
534
+ },
535
+ "price": {
536
+ "type": [
537
+ "string",
538
+ "number"
539
+ ]
540
+ },
541
+ "description": {
542
+ "type": "string"
543
+ }
544
+ },
545
+ "required": [
546
+ "id",
547
+ "price"
548
+ ],
549
+ "additionalProperties": false
550
+ }
551
+ },
552
+ "allowTrailingCommas": true
553
+ }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@decocms/runtime",
3
- "version": "1.0.0-alpha.14",
3
+ "version": "1.0.0-alpha.17",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@cloudflare/workers-types": "^4.20250617.0",
7
7
  "@deco/mcp": "npm:@jsr/deco__mcp@0.5.5",
8
- "@decocms/bindings": "1.0.1-alpha.11",
8
+ "@decocms/bindings": "1.0.1-alpha.12",
9
9
  "@modelcontextprotocol/sdk": "1.20.2",
10
10
  "@ai-sdk/provider": "^2.0.0",
11
11
  "hono": "^4.10.7",
package/src/cors.ts ADDED
@@ -0,0 +1,140 @@
1
+ export type CORSOrigin =
2
+ | string
3
+ | string[]
4
+ | ((origin: string, req: Request) => string | null | undefined);
5
+
6
+ export interface CORSOptions {
7
+ /**
8
+ * The value of "Access-Control-Allow-Origin" CORS header.
9
+ * Can be a string, array of strings, or a function that returns the allowed origin.
10
+ * @default '*'
11
+ */
12
+ origin?: CORSOrigin;
13
+ /**
14
+ * The value of "Access-Control-Allow-Methods" CORS header.
15
+ * @default ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']
16
+ */
17
+ allowMethods?: string[];
18
+ /**
19
+ * The value of "Access-Control-Allow-Headers" CORS header.
20
+ * @default []
21
+ */
22
+ allowHeaders?: string[];
23
+ /**
24
+ * The value of "Access-Control-Max-Age" CORS header (in seconds).
25
+ */
26
+ maxAge?: number;
27
+ /**
28
+ * The value of "Access-Control-Allow-Credentials" CORS header.
29
+ */
30
+ credentials?: boolean;
31
+ /**
32
+ * The value of "Access-Control-Expose-Headers" CORS header.
33
+ * @default []
34
+ */
35
+ exposeHeaders?: string[];
36
+ }
37
+
38
+ const DEFAULT_ALLOW_METHODS = ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"];
39
+
40
+ const resolveOrigin = (
41
+ origin: CORSOrigin | undefined,
42
+ requestOrigin: string | null,
43
+ req: Request,
44
+ ): string | null => {
45
+ if (!requestOrigin) return null;
46
+
47
+ if (origin === undefined || origin === "*") {
48
+ return "*";
49
+ }
50
+
51
+ if (typeof origin === "string") {
52
+ return origin === requestOrigin ? origin : null;
53
+ }
54
+
55
+ if (Array.isArray(origin)) {
56
+ return origin.includes(requestOrigin) ? requestOrigin : null;
57
+ }
58
+
59
+ if (typeof origin === "function") {
60
+ return origin(requestOrigin, req) ?? null;
61
+ }
62
+
63
+ return null;
64
+ };
65
+
66
+ const setCORSHeaders = (
67
+ headers: Headers,
68
+ req: Request,
69
+ options: CORSOptions,
70
+ ): void => {
71
+ const requestOrigin = req.headers.get("Origin");
72
+ const allowedOrigin = resolveOrigin(options.origin, requestOrigin, req);
73
+
74
+ if (allowedOrigin) {
75
+ headers.set("Access-Control-Allow-Origin", allowedOrigin);
76
+ }
77
+
78
+ if (options.credentials) {
79
+ headers.set("Access-Control-Allow-Credentials", "true");
80
+ }
81
+
82
+ if (options.exposeHeaders?.length) {
83
+ headers.set(
84
+ "Access-Control-Expose-Headers",
85
+ options.exposeHeaders.join(", "),
86
+ );
87
+ }
88
+ };
89
+
90
+ export const handlePreflight = (
91
+ req: Request,
92
+ options: CORSOptions,
93
+ ): Response => {
94
+ const headers = new Headers();
95
+ const requestOrigin = req.headers.get("Origin");
96
+ const allowedOrigin = resolveOrigin(options.origin, requestOrigin, req);
97
+
98
+ if (allowedOrigin) {
99
+ headers.set("Access-Control-Allow-Origin", allowedOrigin);
100
+ }
101
+
102
+ if (options.credentials) {
103
+ headers.set("Access-Control-Allow-Credentials", "true");
104
+ }
105
+
106
+ const allowMethods = options.allowMethods ?? DEFAULT_ALLOW_METHODS;
107
+ headers.set("Access-Control-Allow-Methods", allowMethods.join(", "));
108
+
109
+ const requestHeaders = req.headers.get("Access-Control-Request-Headers");
110
+ if (options.allowHeaders?.length) {
111
+ headers.set(
112
+ "Access-Control-Allow-Headers",
113
+ options.allowHeaders.join(", "),
114
+ );
115
+ } else if (requestHeaders) {
116
+ // Mirror the requested headers if no explicit allowHeaders configured
117
+ headers.set("Access-Control-Allow-Headers", requestHeaders);
118
+ }
119
+
120
+ if (options.maxAge !== undefined) {
121
+ headers.set("Access-Control-Max-Age", options.maxAge.toString());
122
+ }
123
+
124
+ return new Response(null, { status: 204, headers });
125
+ };
126
+
127
+ export const withCORS = (
128
+ response: Response,
129
+ req: Request,
130
+ options: CORSOptions,
131
+ ): Response => {
132
+ const newHeaders = new Headers(response.headers);
133
+ setCORSHeaders(newHeaders, req, options);
134
+
135
+ return new Response(response.body, {
136
+ status: response.status,
137
+ statusText: response.statusText,
138
+ headers: newHeaders,
139
+ });
140
+ };
package/src/index.ts CHANGED
@@ -10,12 +10,14 @@ import {
10
10
  MCPServer,
11
11
  } from "./tools.ts";
12
12
  import type { Binding, ContractBinding, MCPBinding } from "./wrangler.ts";
13
+ import { type CORSOptions, handlePreflight, withCORS } from "./cors.ts";
13
14
  export { proxyConnectionForId } from "./bindings.ts";
14
15
  export {
15
16
  createMCPFetchStub,
16
17
  type CreateStubAPIOptions,
17
18
  type ToolBinder,
18
19
  } from "./mcp.ts";
20
+ export { type CORSOptions, type CORSOrigin } from "./cors.ts";
19
21
 
20
22
  export interface DefaultEnv<TSchema extends z.ZodTypeAny = any> {
21
23
  MESH_REQUEST_CONTEXT: RequestContext<TSchema>;
@@ -56,6 +58,11 @@ export interface UserDefaultExport<
56
58
  env: TEnv,
57
59
  ctx: ExecutionContext,
58
60
  ) => Promise<Response> | Response;
61
+ /**
62
+ * CORS configuration options.
63
+ * Set to `false` to disable CORS handling entirely.
64
+ */
65
+ cors?: CORSOptions | false;
59
66
  }
60
67
 
61
68
  // 1. Map binding type to its interface
@@ -155,11 +162,13 @@ export const withBindings = <TEnv>({
155
162
  server,
156
163
  tokenOrContext,
157
164
  url,
165
+ bindings: inlineBindings,
158
166
  }: {
159
167
  env: TEnv;
160
168
  server: MCPServer<TEnv, any>;
161
169
  tokenOrContext?: string | RequestContext;
162
170
  url?: string;
171
+ bindings?: Binding[];
163
172
  }): TEnv => {
164
173
  const env = _env as DefaultEnv<any>;
165
174
 
@@ -209,7 +218,7 @@ export const withBindings = <TEnv>({
209
218
  }
210
219
 
211
220
  env.MESH_REQUEST_CONTEXT = context;
212
- const bindings = MCPBindings.parse(env.MESH_BINDINGS);
221
+ const bindings = inlineBindings ?? MCPBindings.parse(env.MESH_BINDINGS);
213
222
 
214
223
  for (const binding of bindings) {
215
224
  env[binding.name] = creatorByType[binding.type](binding as any, env);
@@ -228,6 +237,8 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
228
237
  userFns: UserDefaultExport<TEnv, TSchema>,
229
238
  ): ExportedHandler<TEnv & DefaultEnv<TSchema>> => {
230
239
  const server = createMCPServer<TEnv, TSchema>(userFns);
240
+ const corsOptions = userFns.cors;
241
+
231
242
  const fetcher = async (
232
243
  req: Request,
233
244
  env: TEnv & DefaultEnv<TSchema>,
@@ -265,22 +276,39 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
265
276
  new Response("Not found", { status: 404 })
266
277
  );
267
278
  };
279
+
268
280
  return {
269
281
  fetch: async (
270
282
  req: Request,
271
283
  env: TEnv & DefaultEnv<TSchema>,
272
284
  ctx: ExecutionContext,
273
285
  ) => {
286
+ // Handle CORS preflight (OPTIONS) requests
287
+ if (corsOptions !== false && req.method === "OPTIONS") {
288
+ const options = corsOptions ?? {};
289
+ return handlePreflight(req, options);
290
+ }
291
+
274
292
  const bindings = withBindings({
275
293
  env,
276
294
  server,
295
+ bindings: userFns.bindings,
277
296
  tokenOrContext: req.headers.get("x-mesh-token") ?? undefined,
278
297
  url: req.url,
279
298
  });
280
- return await State.run(
299
+
300
+ const response = await State.run(
281
301
  { req, env: bindings, ctx },
282
302
  async () => await fetcher(req, bindings, ctx),
283
303
  );
304
+
305
+ // Add CORS headers to response
306
+ if (corsOptions !== false) {
307
+ const options = corsOptions ?? {};
308
+ return withCORS(response, req, options);
309
+ }
310
+
311
+ return response;
284
312
  },
285
313
  };
286
314
  };
package/src/tools.ts CHANGED
@@ -6,6 +6,7 @@ import { z } from "zod";
6
6
  import { zodToJsonSchema } from "zod-to-json-schema";
7
7
  import type { DefaultEnv } from "./index.ts";
8
8
  import { State } from "./state.ts";
9
+ import { Binding } from "./wrangler.ts";
9
10
 
10
11
  export const createRuntimeContext = (prev?: AppContext) => {
11
12
  const store = State.getStore();
@@ -153,6 +154,7 @@ export interface CreateMCPServerOptions<
153
154
  state?: TSchema;
154
155
  scopes?: string[];
155
156
  };
157
+ bindings?: Binding[];
156
158
  tools?:
157
159
  | Array<
158
160
  (