@decocms/runtime 1.0.0-alpha.10 → 1.0.0-alpha.12

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.
@@ -434,14 +434,14 @@
434
434
  "MCPBinding": {
435
435
  "anyOf": [
436
436
  {
437
- "$ref": "#/definitions/MCPIntegrationIdBinding"
437
+ "$ref": "#/definitions/MCPConnectionBinding"
438
438
  },
439
439
  {
440
- "$ref": "#/definitions/MCPIntegrationNameBinding"
440
+ "$ref": "#/definitions/MCPAppBinding"
441
441
  }
442
442
  ]
443
443
  },
444
- "MCPIntegrationIdBinding": {
444
+ "MCPConnectionBinding": {
445
445
  "type": "object",
446
446
  "properties": {
447
447
  "name": {
@@ -451,19 +451,19 @@
451
451
  "type": "string",
452
452
  "const": "mcp"
453
453
  },
454
- "integration_id": {
454
+ "connection_id": {
455
455
  "type": "string",
456
456
  "description": "If not provided, will return a function that takes the integration id and return the binding implementation.."
457
457
  }
458
458
  },
459
459
  "required": [
460
- "integration_id",
460
+ "connection_id",
461
461
  "name",
462
462
  "type"
463
463
  ],
464
464
  "additionalProperties": false
465
465
  },
466
- "MCPIntegrationNameBinding": {
466
+ "MCPAppBinding": {
467
467
  "type": "object",
468
468
  "properties": {
469
469
  "name": {
@@ -473,13 +473,13 @@
473
473
  "type": "string",
474
474
  "const": "mcp"
475
475
  },
476
- "integration_name": {
476
+ "app_name": {
477
477
  "type": "string",
478
478
  "description": "The name of the integration to bind."
479
479
  }
480
480
  },
481
481
  "required": [
482
- "integration_name",
482
+ "app_name",
483
483
  "name",
484
484
  "type"
485
485
  ],
package/package.json CHANGED
@@ -1,19 +1,15 @@
1
1
  {
2
2
  "name": "@decocms/runtime",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.12",
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": "*",
9
- "@mastra/core": "^0.20.2",
8
+ "@decocms/bindings": "1.0.1-alpha.11",
10
9
  "@modelcontextprotocol/sdk": "1.20.2",
11
10
  "@ai-sdk/provider": "^2.0.0",
12
- "bidc": "0.0.3",
13
- "drizzle-orm": "^0.44.5",
14
11
  "hono": "^4.10.7",
15
12
  "jose": "^6.0.11",
16
- "mime-db": "1.52.0",
17
13
  "zod": "^3.25.76",
18
14
  "zod-from-json-schema": "^0.0.5",
19
15
  "zod-to-json-schema": "3.25.0"
@@ -21,16 +17,11 @@
21
17
  "exports": {
22
18
  ".": "./src/index.ts",
23
19
  "./proxy": "./src/proxy.ts",
24
- "./admin": "./src/admin.ts",
25
20
  "./client": "./src/client.ts",
26
- "./mastra": "./src/mastra.ts",
27
- "./drizzle": "./src/drizzle.ts",
28
- "./resources": "./src/resources.ts",
29
- "./views": "./src/views.ts",
30
21
  "./mcp-client": "./src/mcp-client.ts",
31
22
  "./bindings": "./src/bindings/index.ts",
32
- "./deconfig": "./src/bindings/deconfig/index.ts",
33
- "./asset-server": "./src/asset-server/index.ts"
23
+ "./asset-server": "./src/asset-server/index.ts",
24
+ "./tools": "./src/tools.ts"
34
25
  },
35
26
  "devDependencies": {
36
27
  "@types/mime-db": "^1.43.6",
@@ -1,14 +1,13 @@
1
1
  /* oxlint-disable no-explicit-any */
2
2
  import { z } from "zod";
3
3
  import type { MCPConnection } from "../connection.ts";
4
- import { createPrivateTool, createStreamableTool } from "../mastra.ts";
5
4
  import {
6
5
  createMCPFetchStub,
7
6
  type MCPClientFetchStub,
8
7
  type ToolBinder,
9
8
  } from "../mcp.ts";
9
+ import { createPrivateTool, createStreamableTool } from "../tools.ts";
10
10
  import { CHANNEL_BINDING } from "./channels.ts";
11
- import { VIEW_BINDING } from "./views.ts";
12
11
 
13
12
  // ToolLike is a simplified version of the Tool interface that matches what we need for bindings
14
13
  export interface ToolLike<
@@ -93,8 +92,6 @@ export type MCPBindingClient<T extends ReturnType<typeof bindingClient>> =
93
92
  ReturnType<T["forConnection"]>;
94
93
 
95
94
  export const ChannelBinding = bindingClient(CHANNEL_BINDING);
96
- export const ViewBinding = bindingClient(VIEW_BINDING);
97
-
98
95
  export type { Callbacks } from "./channels.ts";
99
96
 
100
97
  export const impl = <TBinder extends Binder>(
@@ -1,16 +1,13 @@
1
1
  import { CHANNEL_BINDING } from "./channels.ts";
2
- import { VIEW_BINDING as VIEWS_BINDING } from "./views.ts";
3
2
 
4
3
  // Import new Resources 2.0 bindings function
5
4
  import { LANGUAGE_MODEL_BINDING } from "@decocms/bindings/llm";
6
- import { createResourceBindings } from "./resources/bindings.ts";
7
5
 
8
6
  // Export types and utilities from binder
9
7
  export {
10
8
  bindingClient,
11
9
  ChannelBinding,
12
10
  impl,
13
- ViewBinding,
14
11
  type Binder,
15
12
  type BinderImplementation,
16
13
  type MCPBindingClient,
@@ -23,40 +20,10 @@ export * from "./channels.ts";
23
20
  // Export binding utilities
24
21
  export * from "./utils.ts";
25
22
 
26
- // Export views schemas
27
- export * from "./views.ts";
28
-
29
- // Re-export Resources bindings function for convenience
30
- export { createResourceBindings };
31
-
32
- // Export resources types and schemas
33
- export * from "./resources/bindings.ts";
34
- export * from "./resources/helpers.ts";
35
- export * from "./resources/schemas.ts";
36
-
37
- // Export deconfig helpers and types
38
- export {
39
- getMetadataString as deconfigGetMetadataString,
40
- getMetadataValue as deconfigGetMetadataValue,
41
- normalizeDirectory as deconfigNormalizeDirectory,
42
- ResourcePath,
43
- ResourceUri,
44
- } from "./deconfig/helpers.ts";
45
- export { createDeconfigResource } from "./deconfig/index.ts";
46
- export type {
47
- DeconfigClient,
48
- DeconfigResourceOptions,
49
- EnhancedResourcesTools,
50
- ResourcesBinding,
51
- ResourcesTools,
52
- } from "./deconfig/index.ts";
53
- export { deconfigTools } from "./deconfig/types.ts";
54
-
55
23
  export { streamToResponse } from "./language-model/utils.ts";
56
24
 
57
25
  export const WellKnownBindings = {
58
26
  Channel: CHANNEL_BINDING,
59
- View: VIEWS_BINDING,
60
27
  LanguageModel: LANGUAGE_MODEL_BINDING,
61
28
  // Note: Resources is not included here since it's a generic function
62
29
  // Use createResourceBindings(dataSchema) directly for Resources 2.0
package/src/bindings.ts CHANGED
@@ -4,89 +4,22 @@ import { MCPClient } from "./mcp.ts";
4
4
  import type {
5
5
  BindingBase,
6
6
  ContractBinding,
7
+ MCPAppBinding,
7
8
  MCPBinding,
8
- MCPIntegrationNameBinding,
9
9
  } from "./wrangler.ts";
10
10
 
11
- interface IntegrationContext {
12
- integrationId: string;
13
- workspace: string;
14
- branch?: string;
15
- decoCmsApiUrl?: string;
16
- }
17
-
18
- const normalizeWorkspace = (workspace: string) => {
19
- if (workspace.startsWith("/users")) {
20
- return workspace;
21
- }
22
- if (workspace.startsWith("/shared")) {
23
- return workspace;
24
- }
25
- if (workspace.includes("/")) {
26
- return workspace;
27
- }
28
- return `/shared/${workspace}`;
29
- };
30
-
31
- /**
32
- * Url: /apps/mcp?appName=$appName
33
- */
34
- const createAppsUrl = ({
35
- appName,
36
- decoChatApiUrl,
37
- }: {
38
- appName: string;
39
- decoChatApiUrl?: string;
40
- }) =>
41
- new URL(
42
- `/apps/mcp?appName=${appName}`,
43
- decoChatApiUrl ?? "https://api.decocms.com",
44
- ).href;
45
- /**
46
- * Url: /:workspace.root/:workspace.slug/:integrationId/mcp
47
- */
48
- const createIntegrationsUrl = ({
49
- integrationId,
50
- workspace,
51
- decoCmsApiUrl,
52
- branch,
53
- }: IntegrationContext) => {
54
- const base = `${normalizeWorkspace(workspace)}/${integrationId}/mcp`;
55
- const url = new URL(base, decoCmsApiUrl ?? "https://api.decocms.com");
56
- branch && url.searchParams.set("branch", branch);
57
- return url.href;
58
- };
59
-
60
- type WorkspaceClientContext = Omit<
11
+ type ClientContext = Omit<
61
12
  RequestContext,
62
13
  "ensureAuthenticated" | "state" | "fetchIntegrationMetadata"
63
14
  >;
64
- export const workspaceClient = (
65
- ctx: WorkspaceClientContext,
66
- decocmsApiUrl?: string,
67
- ): ReturnType<(typeof MCPClient)["forWorkspace"]> => {
68
- return MCPClient.forWorkspace(ctx.workspace, ctx.token, decocmsApiUrl);
69
- };
70
-
71
- const mcpClientForAppName = (appName: string, decoChatApiUrl?: string) => {
72
- const mcpConnection: MCPConnection = {
73
- type: "HTTP",
74
- url: createAppsUrl({
75
- appName,
76
- decoChatApiUrl,
77
- }),
78
- };
79
-
80
- return MCPClient.forConnection(mcpConnection, decoChatApiUrl);
81
- };
82
15
 
83
16
  export const proxyConnectionForId = (
84
- integrationId: string,
85
- ctx: Omit<WorkspaceClientContext, "token"> & {
17
+ connectionId: string,
18
+ ctx: Omit<ClientContext, "token"> & {
86
19
  token?: string;
87
20
  cookie?: string;
21
+ meshUrl: string;
88
22
  },
89
- decocmsApiUrl?: string,
90
23
  appName?: string,
91
24
  ): MCPConnection => {
92
25
  let headers: Record<string, string> | undefined = appName
@@ -98,55 +31,39 @@ export const proxyConnectionForId = (
98
31
  }
99
32
  return {
100
33
  type: "HTTP",
101
- url: createIntegrationsUrl({
102
- integrationId,
103
- workspace: ctx.workspace,
104
- decoCmsApiUrl: decocmsApiUrl,
105
- branch: ctx.branch,
106
- }),
34
+ url: new URL(`/mcp/${connectionId}`, ctx.meshUrl).href,
107
35
  token: ctx.token,
108
36
  headers,
109
37
  };
110
38
  };
111
- const mcpClientForIntegrationId = (
112
- integrationId: string,
113
- ctx: WorkspaceClientContext,
114
- decocmsApiUrl?: string,
39
+ const mcpClientForConnectionId = (
40
+ connectionId: string,
41
+ ctx: ClientContext,
115
42
  appName?: string,
116
43
  ) => {
117
- const mcpConnection = proxyConnectionForId(
118
- integrationId,
119
- ctx,
120
- decocmsApiUrl,
121
- appName,
122
- );
44
+ const mcpConnection = proxyConnectionForId(connectionId, ctx, appName);
123
45
 
124
46
  // TODO(@igorbrasileiro): Switch this proxy to be a proxy that call MCP Client.toolCall from @modelcontextprotocol
125
- return MCPClient.forConnection(mcpConnection, decocmsApiUrl);
47
+ return MCPClient.forConnection(mcpConnection);
126
48
  };
127
49
 
128
50
  function mcpClientFromState(
129
- binding: BindingBase | MCPIntegrationNameBinding,
51
+ binding: BindingBase | MCPAppBinding,
130
52
  env: DefaultEnv,
131
53
  ) {
132
- const ctx = env.DECO_REQUEST_CONTEXT;
54
+ const ctx = env.MESH_REQUEST_CONTEXT;
133
55
  const bindingFromState = ctx?.state?.[binding.name];
134
- const integrationId =
56
+ const connectionId =
135
57
  bindingFromState &&
136
58
  typeof bindingFromState === "object" &&
137
59
  "value" in bindingFromState
138
60
  ? bindingFromState.value
139
61
  : undefined;
140
- if (typeof integrationId !== "string" && "integration_name" in binding) {
62
+ if (typeof connectionId !== "string" && "app_name" in binding) {
141
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
142
- return mcpClientForAppName(binding.integration_name, env.DECO_API_URL);
64
+ throw new Error("Binding to an app name is not supported");
143
65
  }
144
- return mcpClientForIntegrationId(
145
- integrationId,
146
- ctx,
147
- env.DECO_API_URL,
148
- env.DECO_APP_NAME,
149
- );
66
+ return mcpClientForConnectionId(connectionId, ctx);
150
67
  }
151
68
 
152
69
  export const createContractBinding = (
@@ -160,20 +77,24 @@ export const createIntegrationBinding = (
160
77
  binding: MCPBinding,
161
78
  env: DefaultEnv,
162
79
  ) => {
163
- const integrationId =
164
- "integration_id" in binding ? binding.integration_id : undefined;
165
- if (!integrationId) {
80
+ const connectionId =
81
+ "connection_id" in binding ? binding.connection_id : undefined;
82
+ if (!connectionId) {
166
83
  return mcpClientFromState(binding, env);
167
84
  }
85
+ if (!env.MESH_RUNTIME_TOKEN) {
86
+ throw new Error("MESH_RUNTIME_TOKEN is required");
87
+ }
88
+ if (!env.MESH_URL) {
89
+ throw new Error("MESH_URL is required");
90
+ }
168
91
  // bindings pointed to an specific integration id are binded using the app deployment workspace
169
- return mcpClientForIntegrationId(
170
- integrationId,
92
+ return mcpClientForConnectionId(
93
+ connectionId,
171
94
  {
172
- workspace: env.DECO_WORKSPACE,
173
- token: env.DECO_API_TOKEN,
174
- branch: env.DECO_REQUEST_CONTEXT?.branch,
95
+ token: env.MESH_RUNTIME_TOKEN,
96
+ meshUrl: env.MESH_URL,
175
97
  },
176
- env.DECO_API_URL,
177
- env.DECO_APP_NAME,
98
+ env.MESH_APP_NAME,
178
99
  );
179
100
  };
package/src/client.ts CHANGED
@@ -1,35 +1,3 @@
1
- import { toAsyncIterator } from "./bindings/deconfig/helpers.ts";
2
- // Extract resource name from DECO_RESOURCE_${NAME}_READ pattern
3
- type ExtractResourceName<K> = K extends `DECO_RESOURCE_${infer Name}_READ`
4
- ? Name
5
- : never;
6
-
7
- // Generate SUBSCRIBE method name from resource name
8
- type SubscribeMethodName<Name extends string> =
9
- `DECO_RESOURCE_${Name}_SUBSCRIBE`;
10
-
11
- // Extract data type from READ method return type
12
- type ExtractReadData<T> = T extends Promise<{ data: infer D }>
13
- ? D
14
- : T extends { data: infer D }
15
- ? D
16
- : never;
17
-
18
- // Generate all SUBSCRIBE method names for a given type
19
- type SubscribeMethods<T> = {
20
- [K in keyof T as K extends `DECO_RESOURCE_${string}_READ`
21
- ? SubscribeMethodName<ExtractResourceName<K>>
22
- : never]: K extends `DECO_RESOURCE_${string}_READ`
23
- ? // oxlint-disable-next-line no-explicit-any
24
- T[K] extends (...args: any) => any
25
- ? (args: { id: string } | { uri: string }) => AsyncIterableIterator<{
26
- uri: string;
27
- data: ExtractReadData<Awaited<ReturnType<T[K]>>>;
28
- }>
29
- : never
30
- : never;
31
- };
32
-
33
1
  export type MCPClient<T> = {
34
2
  // oxlint-disable-next-line no-explicit-any
35
3
  [K in keyof T]: T[K] extends (...args: any) => any
@@ -38,7 +6,7 @@ export type MCPClient<T> = {
38
6
  init?: CustomInit,
39
7
  ) => Promise<Awaited<ReturnType<T[K]>>>
40
8
  : never;
41
- } & SubscribeMethods<T>;
9
+ };
42
10
 
43
11
  export type CustomInit = RequestInit & {
44
12
  handleResponse?: (response: Response) => Promise<unknown>;
@@ -53,123 +21,11 @@ export const DEFAULT_INIT: CustomInit = {
53
21
  },
54
22
  };
55
23
 
56
- /**
57
- * Helper function to call an MCP tool via fetch
58
- */
59
- async function callMCPTool<T = unknown>(
60
- methodName: string,
61
- args: unknown,
62
- init?: CustomInit,
63
- ): Promise<T> {
64
- const mergedInit: CustomInit = {
65
- ...init,
66
- headers: {
67
- ...DEFAULT_INIT.headers,
68
- ...init?.headers,
69
- },
70
- };
71
-
72
- const response = await fetch(`/mcp/call-tool/${methodName}`, {
73
- method: "POST",
74
- body: JSON.stringify(args),
75
- credentials: "include",
76
- ...mergedInit,
77
- });
78
-
79
- if (!response.ok) {
80
- throw new Error(`Failed to call ${methodName}: ${response.statusText}`);
81
- }
82
-
83
- return response.json() as Promise<T>;
84
- }
85
-
86
- /**
87
- * Creates a subscribe method for a resource that returns an async iterator
88
- * yielding {uri, data} objects as resources are updated.
89
- */
90
- function createSubscribeMethod(
91
- resourceName: string,
92
- init?: CustomInit,
93
- ): (args: { id: string }) => AsyncIterableIterator<{
94
- uri: string;
95
- data: unknown;
96
- }> {
97
- return async function* (args: { id: string } | { uri: string }) {
98
- // Step 1: Call DESCRIBE to get watch endpoint configuration and URI template
99
- const describeMethodName = `DECO_RESOURCE_${resourceName}_DESCRIBE`;
100
- const readMethodName = `DECO_RESOURCE_${resourceName}_READ`;
101
-
102
- // Get describe information
103
- const describeData = await callMCPTool<{
104
- uriTemplate?: string;
105
- features?: {
106
- watch?: {
107
- pathname?: string;
108
- };
109
- };
110
- }>(describeMethodName, {}, init);
111
-
112
- const watchPathname = describeData?.features?.watch?.pathname;
113
- const uriTemplate = describeData?.uriTemplate;
114
-
115
- if (!watchPathname) {
116
- throw new Error(
117
- `Resource ${resourceName} does not support watch functionality`,
118
- );
119
- }
120
-
121
- if (!uriTemplate) {
122
- throw new Error(`Resource ${resourceName} does not provide uriTemplate`);
123
- }
124
-
125
- // Step 2: Construct URI from template by replacing * with id
126
- const resourceUri =
127
- "uri" in args ? args.uri : uriTemplate.replace("*", args.id);
128
-
129
- // Step 3: Construct watch URL and create EventSource
130
- const watchUrl = new URL(watchPathname, globalThis.location.origin);
131
- watchUrl.searchParams.set("uri", resourceUri);
132
-
133
- const eventSource = new EventSource(watchUrl.href);
134
-
135
- // Step 4: Use toAsyncIterator to consume SSE events and enrich with READ data
136
- const eventStream = toAsyncIterator<{ uri: string }>(
137
- eventSource,
138
- "message",
139
- );
140
-
141
- // Iterate over SSE events and enrich with full data
142
- for await (const event of eventStream) {
143
- const uri = event.uri;
144
-
145
- if (uri) {
146
- // Call READ to get full resource data
147
- const readData = await callMCPTool<{ data: unknown }>(
148
- readMethodName,
149
- { uri },
150
- init,
151
- );
152
-
153
- yield { uri, data: readData.data };
154
- }
155
- }
156
- };
157
- }
158
-
159
24
  export const createClient = <T>(init?: CustomInit): MCPClient<T> => {
160
25
  return new Proxy(
161
26
  {},
162
27
  {
163
28
  get: (_, prop) => {
164
- const propStr = String(prop);
165
-
166
- // Check if this is a SUBSCRIBE method call
167
- const subscribeMatch = propStr.match(/^DECO_RESOURCE_(.+)_SUBSCRIBE$/);
168
- if (subscribeMatch) {
169
- const resourceName = subscribeMatch[1];
170
- return createSubscribeMethod(resourceName, init);
171
- }
172
-
173
29
  // Regular method call
174
30
  return async (args: unknown, innerInit?: CustomInit) => {
175
31
  const mergedInit: CustomInit = {