@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.
- package/config-schema.json +8 -8
- package/package.json +4 -13
- package/src/bindings/binder.ts +1 -4
- package/src/bindings/index.ts +0 -33
- package/src/bindings.ts +31 -110
- package/src/client.ts +1 -145
- package/src/index.ts +48 -157
- package/src/mcp.ts +7 -161
- package/src/proxy.ts +3 -54
- package/src/state.ts +3 -31
- package/src/tools.ts +342 -0
- package/src/wrangler.ts +5 -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/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/mastra.ts +0 -670
- package/src/resources.ts +0 -168
- package/src/views.ts +0 -26
- package/src/well-known.ts +0 -20
package/src/index.ts
CHANGED
|
@@ -2,26 +2,13 @@
|
|
|
2
2
|
import type { ExecutionContext } from "@cloudflare/workers-types";
|
|
3
3
|
import { decodeJwt } from "jose";
|
|
4
4
|
import type { z } from "zod";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
handleAuthCallback,
|
|
8
|
-
handleLogout,
|
|
9
|
-
StateParser,
|
|
10
|
-
} from "./auth.ts";
|
|
11
|
-
import {
|
|
12
|
-
createContractBinding,
|
|
13
|
-
createIntegrationBinding,
|
|
14
|
-
workspaceClient,
|
|
15
|
-
} from "./bindings.ts";
|
|
16
|
-
import { DeconfigResource } from "./bindings/deconfig/index.ts";
|
|
17
|
-
import { DECO_MCP_CLIENT_HEADER } from "./client.ts";
|
|
5
|
+
import { createContractBinding, createIntegrationBinding } from "./bindings.ts";
|
|
6
|
+
import { State } from "./state.ts";
|
|
18
7
|
import {
|
|
19
8
|
createMCPServer,
|
|
20
9
|
type CreateMCPServerOptions,
|
|
21
10
|
MCPServer,
|
|
22
|
-
} from "./
|
|
23
|
-
import { MCPClient, type QueryResult } from "./mcp.ts";
|
|
24
|
-
import { State } from "./state.ts";
|
|
11
|
+
} from "./tools.ts";
|
|
25
12
|
import type { Binding, ContractBinding, MCPBinding } from "./wrangler.ts";
|
|
26
13
|
export { proxyConnectionForId } from "./bindings.ts";
|
|
27
14
|
export {
|
|
@@ -29,28 +16,15 @@ export {
|
|
|
29
16
|
type CreateStubAPIOptions,
|
|
30
17
|
type ToolBinder,
|
|
31
18
|
} from "./mcp.ts";
|
|
32
|
-
export interface WorkspaceDB {
|
|
33
|
-
query: (params: {
|
|
34
|
-
sql: string;
|
|
35
|
-
params: string[];
|
|
36
|
-
}) => Promise<{ result: QueryResult[] }>;
|
|
37
|
-
}
|
|
38
19
|
|
|
39
20
|
export interface DefaultEnv<TSchema extends z.ZodTypeAny = any> {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
DECO_APP_ENTRYPOINT: string;
|
|
44
|
-
DECO_API_URL?: string;
|
|
45
|
-
DECO_WORKSPACE: string;
|
|
46
|
-
DECO_API_JWT_PUBLIC_KEY: string;
|
|
47
|
-
DECO_APP_DEPLOYMENT_ID: string;
|
|
48
|
-
DECO_BINDINGS: string;
|
|
49
|
-
DECO_API_TOKEN: string;
|
|
50
|
-
DECO_WORKSPACE_DB: WorkspaceDB & {
|
|
51
|
-
forContext: (ctx: RequestContext) => WorkspaceDB;
|
|
52
|
-
};
|
|
21
|
+
MESH_REQUEST_CONTEXT: RequestContext<TSchema>;
|
|
22
|
+
MESH_BINDINGS: string;
|
|
23
|
+
MESH_APP_DEPLOYMENT_ID: string;
|
|
53
24
|
IS_LOCAL: boolean;
|
|
25
|
+
MESH_URL?: string;
|
|
26
|
+
MESH_RUNTIME_TOKEN?: string;
|
|
27
|
+
MESH_APP_NAME?: string;
|
|
54
28
|
[key: string]: unknown;
|
|
55
29
|
}
|
|
56
30
|
|
|
@@ -58,7 +32,7 @@ export interface BindingsObject {
|
|
|
58
32
|
bindings?: Binding[];
|
|
59
33
|
}
|
|
60
34
|
|
|
61
|
-
export const
|
|
35
|
+
export const MCPBindings = {
|
|
62
36
|
parse: (bindings?: string): Binding[] => {
|
|
63
37
|
if (!bindings) return [];
|
|
64
38
|
try {
|
|
@@ -104,14 +78,13 @@ export interface User {
|
|
|
104
78
|
|
|
105
79
|
export interface RequestContext<TSchema extends z.ZodTypeAny = any> {
|
|
106
80
|
state: z.infer<TSchema>;
|
|
107
|
-
branch?: string;
|
|
108
81
|
token: string;
|
|
109
|
-
|
|
82
|
+
meshUrl: string;
|
|
110
83
|
ensureAuthenticated: (options?: {
|
|
111
84
|
workspaceHint?: string;
|
|
112
85
|
}) => User | undefined;
|
|
113
86
|
callerApp?: string;
|
|
114
|
-
|
|
87
|
+
connectionId?: string;
|
|
115
88
|
}
|
|
116
89
|
|
|
117
90
|
// 2. Map binding type to its creator function
|
|
@@ -131,26 +104,12 @@ const creatorByType: CreatorByType = {
|
|
|
131
104
|
const withDefaultBindings = ({
|
|
132
105
|
env,
|
|
133
106
|
server,
|
|
134
|
-
ctx,
|
|
135
107
|
url,
|
|
136
108
|
}: {
|
|
137
109
|
env: DefaultEnv;
|
|
138
110
|
server: MCPServer<any, any>;
|
|
139
|
-
ctx: RequestContext;
|
|
140
111
|
url?: string;
|
|
141
112
|
}) => {
|
|
142
|
-
const client = workspaceClient(ctx, env.DECO_API_URL);
|
|
143
|
-
const createWorkspaceDB = (ctx: RequestContext): WorkspaceDB => {
|
|
144
|
-
const client = workspaceClient(ctx, env.DECO_API_URL);
|
|
145
|
-
return {
|
|
146
|
-
query: ({ sql, params }) => {
|
|
147
|
-
return client.DATABASES_RUN_SQL({
|
|
148
|
-
sql,
|
|
149
|
-
params,
|
|
150
|
-
});
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
};
|
|
154
113
|
env["SELF"] = new Proxy(
|
|
155
114
|
{},
|
|
156
115
|
{
|
|
@@ -169,15 +128,6 @@ const withDefaultBindings = ({
|
|
|
169
128
|
},
|
|
170
129
|
);
|
|
171
130
|
|
|
172
|
-
const workspaceDbBinding = {
|
|
173
|
-
...createWorkspaceDB(ctx),
|
|
174
|
-
forContext: createWorkspaceDB,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
env["DECO_API"] = MCPClient;
|
|
178
|
-
env["DECO_WORKSPACE_API"] = client;
|
|
179
|
-
env["DECO_WORKSPACE_DB"] = workspaceDbBinding;
|
|
180
|
-
|
|
181
131
|
env["IS_LOCAL"] =
|
|
182
132
|
(url?.startsWith("http://localhost") ||
|
|
183
133
|
url?.startsWith("http://127.0.0.1")) ??
|
|
@@ -194,13 +144,9 @@ export class UnauthorizedError extends Error {
|
|
|
194
144
|
}
|
|
195
145
|
}
|
|
196
146
|
|
|
197
|
-
const
|
|
198
|
-
const AUTH_START_ENDPOINT = "/oauth/start";
|
|
199
|
-
const AUTH_LOGOUT_ENDPOINT = "/oauth/logout";
|
|
200
|
-
const AUTHENTICATED = (user?: unknown, workspace?: string) => () => {
|
|
147
|
+
const AUTHENTICATED = (user?: unknown) => () => {
|
|
201
148
|
return {
|
|
202
149
|
...((user as User) ?? {}),
|
|
203
|
-
workspace,
|
|
204
150
|
} as User;
|
|
205
151
|
};
|
|
206
152
|
|
|
@@ -208,66 +154,55 @@ export const withBindings = <TEnv>({
|
|
|
208
154
|
env: _env,
|
|
209
155
|
server,
|
|
210
156
|
tokenOrContext,
|
|
211
|
-
origin,
|
|
212
157
|
url,
|
|
213
|
-
branch,
|
|
214
158
|
}: {
|
|
215
159
|
env: TEnv;
|
|
216
160
|
server: MCPServer<TEnv, any>;
|
|
217
161
|
tokenOrContext?: string | RequestContext;
|
|
218
|
-
origin?: string | null;
|
|
219
162
|
url?: string;
|
|
220
|
-
branch?: string | null;
|
|
221
163
|
}): TEnv => {
|
|
222
|
-
branch ??= undefined;
|
|
223
164
|
const env = _env as DefaultEnv<any>;
|
|
224
165
|
|
|
225
|
-
const apiUrl = env.DECO_API_URL ?? "https://api.decocms.com";
|
|
226
166
|
let context;
|
|
227
167
|
if (typeof tokenOrContext === "string") {
|
|
228
168
|
const decoded = decodeJwt(tokenOrContext);
|
|
229
|
-
|
|
169
|
+
// Support both new JWT format (fields directly on payload) and legacy format (nested in metadata)
|
|
170
|
+
const metadata =
|
|
171
|
+
(decoded.metadata as {
|
|
172
|
+
state?: Record<string, unknown>;
|
|
173
|
+
meshUrl?: string;
|
|
174
|
+
connectionId?: string;
|
|
175
|
+
}) ?? {};
|
|
230
176
|
|
|
231
177
|
context = {
|
|
232
|
-
state: decoded.state
|
|
178
|
+
state: decoded.state ?? metadata.state,
|
|
233
179
|
token: tokenOrContext,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
ensureAuthenticated: AUTHENTICATED(decoded.user
|
|
237
|
-
branch,
|
|
180
|
+
meshUrl: (decoded.meshUrl as string) ?? metadata.meshUrl,
|
|
181
|
+
connectionId: (decoded.connectionId as string) ?? metadata.connectionId,
|
|
182
|
+
ensureAuthenticated: AUTHENTICATED(decoded.user ?? decoded.sub),
|
|
238
183
|
} as RequestContext<any>;
|
|
239
184
|
} else if (typeof tokenOrContext === "object") {
|
|
240
185
|
context = tokenOrContext;
|
|
241
186
|
const decoded = decodeJwt(tokenOrContext.token);
|
|
242
|
-
|
|
187
|
+
// Support both new JWT format (fields directly on payload) and legacy format (nested in metadata)
|
|
188
|
+
const metadata =
|
|
189
|
+
(decoded.metadata as {
|
|
190
|
+
state?: Record<string, unknown>;
|
|
191
|
+
meshUrl?: string;
|
|
192
|
+
connectionId?: string;
|
|
193
|
+
}) ?? {};
|
|
243
194
|
const appName = decoded.appName as string | undefined;
|
|
244
195
|
context.callerApp = appName;
|
|
245
|
-
context.
|
|
246
|
-
|
|
196
|
+
context.connectionId ??=
|
|
197
|
+
(decoded.connectionId as string) ?? metadata.connectionId;
|
|
198
|
+
context.ensureAuthenticated = AUTHENTICATED(decoded.user ?? decoded.sub);
|
|
247
199
|
} else {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
token: env.DECO_API_TOKEN,
|
|
251
|
-
workspace: env.DECO_WORKSPACE,
|
|
252
|
-
branch,
|
|
253
|
-
ensureAuthenticated: (options?: { workspaceHint?: string }) => {
|
|
254
|
-
const workspaceHint = options?.workspaceHint ?? env.DECO_WORKSPACE;
|
|
255
|
-
const authUri = new URL("/apps/oauth", apiUrl);
|
|
256
|
-
authUri.searchParams.set("client_id", env.DECO_APP_NAME);
|
|
257
|
-
authUri.searchParams.set(
|
|
258
|
-
"redirect_uri",
|
|
259
|
-
new URL(AUTH_CALLBACK_ENDPOINT, origin ?? env.DECO_APP_ENTRYPOINT)
|
|
260
|
-
.href,
|
|
261
|
-
);
|
|
262
|
-
workspaceHint &&
|
|
263
|
-
authUri.searchParams.set("workspace_hint", workspaceHint);
|
|
264
|
-
throw new UnauthorizedError("Unauthorized", authUri);
|
|
265
|
-
},
|
|
266
|
-
};
|
|
200
|
+
// should not reach here
|
|
201
|
+
throw new Error("Invalid token or context");
|
|
267
202
|
}
|
|
268
203
|
|
|
269
|
-
env.
|
|
270
|
-
const bindings =
|
|
204
|
+
env.MESH_REQUEST_CONTEXT = context;
|
|
205
|
+
const bindings = MCPBindings.parse(env.MESH_BINDINGS);
|
|
271
206
|
|
|
272
207
|
for (const binding of bindings) {
|
|
273
208
|
env[binding.name] = creatorByType[binding.type](binding as any, env);
|
|
@@ -276,7 +211,6 @@ export const withBindings = <TEnv>({
|
|
|
276
211
|
withDefaultBindings({
|
|
277
212
|
env,
|
|
278
213
|
server,
|
|
279
|
-
ctx: env.DECO_REQUEST_CONTEXT,
|
|
280
214
|
url,
|
|
281
215
|
});
|
|
282
216
|
|
|
@@ -293,21 +227,6 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
|
293
227
|
ctx: ExecutionContext,
|
|
294
228
|
) => {
|
|
295
229
|
const url = new URL(req.url);
|
|
296
|
-
if (url.pathname === AUTH_CALLBACK_ENDPOINT) {
|
|
297
|
-
return handleAuthCallback(req, {
|
|
298
|
-
apiUrl: env.DECO_API_URL,
|
|
299
|
-
appName: env.DECO_APP_NAME,
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
if (url.pathname === AUTH_START_ENDPOINT) {
|
|
303
|
-
env.DECO_REQUEST_CONTEXT.ensureAuthenticated();
|
|
304
|
-
const redirectTo = new URL("/", url);
|
|
305
|
-
const next = url.searchParams.get("next");
|
|
306
|
-
return Response.redirect(next ?? redirectTo, 302);
|
|
307
|
-
}
|
|
308
|
-
if (url.pathname === AUTH_LOGOUT_ENDPOINT) {
|
|
309
|
-
return handleLogout(req);
|
|
310
|
-
}
|
|
311
230
|
if (url.pathname === "/mcp") {
|
|
312
231
|
return server.fetch(req, env, ctx);
|
|
313
232
|
}
|
|
@@ -334,9 +253,6 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
|
334
253
|
});
|
|
335
254
|
}
|
|
336
255
|
|
|
337
|
-
if (url.pathname.startsWith(DeconfigResource.WatchPathNameBase)) {
|
|
338
|
-
return DeconfigResource.watchAPI(req, env);
|
|
339
|
-
}
|
|
340
256
|
return (
|
|
341
257
|
userFns.fetch?.(req, env, ctx) ||
|
|
342
258
|
new Response("Not found", { status: 404 })
|
|
@@ -348,41 +264,16 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
|
348
264
|
env: TEnv & DefaultEnv<TSchema>,
|
|
349
265
|
ctx: ExecutionContext,
|
|
350
266
|
) => {
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
tokenOrContext: await getReqToken(req, env),
|
|
362
|
-
origin:
|
|
363
|
-
referer ?? req.headers.get("origin") ?? new URL(req.url).origin,
|
|
364
|
-
url: req.url,
|
|
365
|
-
});
|
|
366
|
-
return await State.run(
|
|
367
|
-
{ req, env: bindings, ctx },
|
|
368
|
-
async () => await fetcher(req, bindings, ctx),
|
|
369
|
-
);
|
|
370
|
-
} catch (error) {
|
|
371
|
-
if (error instanceof UnauthorizedError) {
|
|
372
|
-
if (!isFetchRequest) {
|
|
373
|
-
const url = new URL(req.url);
|
|
374
|
-
error.redirectTo.searchParams.set(
|
|
375
|
-
"state",
|
|
376
|
-
StateParser.stringify({
|
|
377
|
-
next: url.searchParams.get("next") ?? referer ?? req.url,
|
|
378
|
-
}),
|
|
379
|
-
);
|
|
380
|
-
return Response.redirect(error.redirectTo, 302);
|
|
381
|
-
}
|
|
382
|
-
return new Response(null, { status: 401 });
|
|
383
|
-
}
|
|
384
|
-
throw error;
|
|
385
|
-
}
|
|
267
|
+
const bindings = withBindings({
|
|
268
|
+
env,
|
|
269
|
+
server,
|
|
270
|
+
tokenOrContext: req.headers.get("x-mesh-token") ?? undefined,
|
|
271
|
+
url: req.url,
|
|
272
|
+
});
|
|
273
|
+
return await State.run(
|
|
274
|
+
{ req, env: bindings, ctx },
|
|
275
|
+
async () => await fetcher(req, bindings, ctx),
|
|
276
|
+
);
|
|
386
277
|
},
|
|
387
278
|
};
|
|
388
279
|
};
|
package/src/mcp.ts
CHANGED
|
@@ -1,165 +1,11 @@
|
|
|
1
1
|
/* oxlint-disable no-explicit-any */
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import type { MCPConnection } from "./connection.ts";
|
|
4
|
-
import { createMCPClientProxy } from "./proxy.ts";
|
|
5
2
|
import type { ToolBinder } from "@decocms/bindings";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
sql_duration_ms: z.number().optional(),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const Meta = z.object({
|
|
17
|
-
changed_db: z.boolean().optional(),
|
|
18
|
-
changes: z.number().optional(),
|
|
19
|
-
duration: z.number().optional(),
|
|
20
|
-
last_row_id: z.number().optional(),
|
|
21
|
-
rows_read: z.number().optional(),
|
|
22
|
-
rows_written: z.number().optional(),
|
|
23
|
-
served_by_primary: z.boolean().optional(),
|
|
24
|
-
served_by_region: z
|
|
25
|
-
.enum(["WNAM", "ENAM", "WEUR", "EEUR", "APAC", "OC"])
|
|
26
|
-
.optional(),
|
|
27
|
-
size_after: z.number().optional(),
|
|
28
|
-
timings: Timings.optional(),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const QueryResult = z.object({
|
|
32
|
-
meta: Meta.optional(),
|
|
33
|
-
results: z.array(z.unknown()).optional(),
|
|
34
|
-
success: z.boolean().optional(),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
export type QueryResult = z.infer<typeof QueryResult>;
|
|
38
|
-
|
|
39
|
-
const workspaceTools = [
|
|
40
|
-
{
|
|
41
|
-
name: "INTEGRATIONS_GET" as const,
|
|
42
|
-
inputSchema: z.object({
|
|
43
|
-
id: z.string(),
|
|
44
|
-
}),
|
|
45
|
-
outputSchema: z.object({
|
|
46
|
-
connection: z.object({}),
|
|
47
|
-
}),
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
name: "DATABASES_RUN_SQL" as const,
|
|
51
|
-
inputSchema: z.object({
|
|
52
|
-
sql: z.string().describe("The SQL query to run"),
|
|
53
|
-
params: z
|
|
54
|
-
.array(z.string())
|
|
55
|
-
.describe("The parameters to pass to the SQL query"),
|
|
56
|
-
}),
|
|
57
|
-
outputSchema: z.object({
|
|
58
|
-
result: z.array(QueryResult),
|
|
59
|
-
}),
|
|
60
|
-
},
|
|
61
|
-
] satisfies ToolBinder<string, unknown, object>[];
|
|
62
|
-
|
|
63
|
-
// Default fetcher instance with API_SERVER_URL and API_HEADERS
|
|
64
|
-
const global = createMCPFetchStub<[]>({});
|
|
65
|
-
export const MCPClient = new Proxy(
|
|
66
|
-
{} as typeof global & {
|
|
67
|
-
forWorkspace: (
|
|
68
|
-
workspace: string,
|
|
69
|
-
token?: string,
|
|
70
|
-
decoCmsApiUrl?: string,
|
|
71
|
-
) => MCPClientFetchStub<typeof workspaceTools>;
|
|
72
|
-
forConnection: <TDefinition extends readonly ToolBinder[]>(
|
|
73
|
-
connection: MCPConnectionProvider,
|
|
74
|
-
decoCmsApiUrl?: string,
|
|
75
|
-
) => MCPClientFetchStub<TDefinition>;
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
get(_, name) {
|
|
79
|
-
if (name === "toJSON") {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (name === "forWorkspace") {
|
|
84
|
-
return (workspace: string, token?: string, decoCmsApiUrl?: string) =>
|
|
85
|
-
createMCPFetchStub<[]>({
|
|
86
|
-
workspace,
|
|
87
|
-
token,
|
|
88
|
-
decoCmsApiUrl,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
if (name === "forConnection") {
|
|
92
|
-
return <TDefinition extends readonly ToolBinder[]>(
|
|
93
|
-
connection: MCPConnectionProvider,
|
|
94
|
-
decoCmsApiUrl?: string,
|
|
95
|
-
) =>
|
|
96
|
-
createMCPFetchStub<TDefinition>({
|
|
97
|
-
connection,
|
|
98
|
-
decoCmsApiUrl,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
return global[name as keyof typeof global];
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
);
|
|
3
|
+
export {
|
|
4
|
+
createMCPFetchStub,
|
|
5
|
+
MCPClient,
|
|
6
|
+
type CreateStubAPIOptions,
|
|
7
|
+
type MCPClientFetchStub,
|
|
8
|
+
type MCPClientStub,
|
|
9
|
+
} from "@decocms/bindings/client"; // Default fetcher instance with API_SERVER_URL and API_HEADERS
|
|
105
10
|
|
|
106
11
|
export type { ToolBinder };
|
|
107
|
-
|
|
108
|
-
export type MCPClientStub<TDefinition extends readonly ToolBinder[]> = {
|
|
109
|
-
[K in TDefinition[number] as K["name"]]: K extends ToolBinder<
|
|
110
|
-
string,
|
|
111
|
-
infer TInput,
|
|
112
|
-
infer TReturn
|
|
113
|
-
>
|
|
114
|
-
? (params: TInput, init?: RequestInit) => Promise<TReturn>
|
|
115
|
-
: never;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export type MCPClientFetchStub<TDefinition extends readonly ToolBinder[]> = {
|
|
119
|
-
[K in TDefinition[number] as K["name"]]: K["streamable"] extends true
|
|
120
|
-
? K extends ToolBinder<string, infer TInput, any, true>
|
|
121
|
-
? (params: TInput, init?: RequestInit) => Promise<Response>
|
|
122
|
-
: never
|
|
123
|
-
: K extends ToolBinder<string, infer TInput, infer TReturn, any>
|
|
124
|
-
? (params: TInput, init?: RequestInit) => Promise<Awaited<TReturn>>
|
|
125
|
-
: never;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
export type MCPConnectionProvider = MCPConnection;
|
|
129
|
-
|
|
130
|
-
export interface MCPClientRaw {
|
|
131
|
-
callTool: (tool: string, args: unknown) => Promise<unknown>;
|
|
132
|
-
listTools: () => Promise<
|
|
133
|
-
{
|
|
134
|
-
name: string;
|
|
135
|
-
inputSchema: any;
|
|
136
|
-
outputSchema?: any;
|
|
137
|
-
description: string;
|
|
138
|
-
}[]
|
|
139
|
-
>;
|
|
140
|
-
}
|
|
141
|
-
export type JSONSchemaToZodConverter = (jsonSchema: any) => z.ZodTypeAny;
|
|
142
|
-
export interface CreateStubAPIOptions {
|
|
143
|
-
mcpPath?: string;
|
|
144
|
-
decoCmsApiUrl?: string;
|
|
145
|
-
workspace?: string;
|
|
146
|
-
token?: string;
|
|
147
|
-
connection?: MCPConnectionProvider;
|
|
148
|
-
streamable?: Record<string, boolean>;
|
|
149
|
-
debugId?: () => string;
|
|
150
|
-
getErrorByStatusCode?: (
|
|
151
|
-
statusCode: number,
|
|
152
|
-
message?: string,
|
|
153
|
-
traceId?: string,
|
|
154
|
-
errorObject?: unknown,
|
|
155
|
-
) => Error;
|
|
156
|
-
supportsToolName?: boolean;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function createMCPFetchStub<TDefinition extends readonly ToolBinder[]>(
|
|
160
|
-
options?: CreateStubAPIOptions,
|
|
161
|
-
): MCPClientFetchStub<TDefinition> {
|
|
162
|
-
return createMCPClientProxy<MCPClientFetchStub<TDefinition>>({
|
|
163
|
-
...(options ?? {}),
|
|
164
|
-
});
|
|
165
|
-
}
|
package/src/proxy.ts
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
/* oxlint-disable no-explicit-any */
|
|
2
|
-
import type { ToolExecutionContext as _ToolExecutionContext } from "@mastra/core";
|
|
3
2
|
import { convertJsonSchemaToZod } from "zod-from-json-schema";
|
|
4
3
|
import { MCPConnection } from "./connection.ts";
|
|
5
4
|
import { createServerClient } from "./mcp-client.ts";
|
|
6
5
|
import type { CreateStubAPIOptions } from "./mcp.ts";
|
|
7
|
-
import { WELL_KNOWN_API_HOSTNAMES } from "./well-known.ts";
|
|
8
|
-
|
|
9
|
-
const getWorkspace = (workspace?: string) => {
|
|
10
|
-
if (workspace && workspace.length > 0 && !workspace.includes("/")) {
|
|
11
|
-
return `/shared/${workspace}`;
|
|
12
|
-
}
|
|
13
|
-
return workspace ?? "";
|
|
14
|
-
};
|
|
15
6
|
|
|
16
7
|
const safeParse = (content: string) => {
|
|
17
8
|
try {
|
|
@@ -33,36 +24,13 @@ const toolsMap = new Map<
|
|
|
33
24
|
>
|
|
34
25
|
>();
|
|
35
26
|
|
|
36
|
-
/**
|
|
37
|
-
* Determines if a given URL supports tool names in the path.
|
|
38
|
-
* Our APIs (api.decocms.com, api.deco.chat, localhost) support /tool/${toolName} routing.
|
|
39
|
-
* Third-party APIs typically don't support this pattern.
|
|
40
|
-
*/
|
|
41
|
-
function supportsToolNameInPath(url: string): boolean {
|
|
42
|
-
try {
|
|
43
|
-
// Our main APIs that support /tool/${toolName} routing
|
|
44
|
-
return WELL_KNOWN_API_HOSTNAMES.includes(new URL(url).hostname);
|
|
45
|
-
} catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
27
|
/**
|
|
51
28
|
* The base fetcher used to fetch the MCP from API.
|
|
52
29
|
*/
|
|
53
30
|
export function createMCPClientProxy<T extends Record<string, unknown>>(
|
|
54
|
-
options
|
|
31
|
+
options: CreateStubAPIOptions,
|
|
55
32
|
): T {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const connection: MCPConnection = options?.connection || {
|
|
59
|
-
type: "HTTP",
|
|
60
|
-
token: options?.token,
|
|
61
|
-
url: new URL(
|
|
62
|
-
`${getWorkspace(options?.workspace)}${mcpPath}`,
|
|
63
|
-
options?.decoCmsApiUrl ?? `https://api.decocms.com`,
|
|
64
|
-
).href,
|
|
65
|
-
};
|
|
33
|
+
const connection: MCPConnection = options.connection;
|
|
66
34
|
|
|
67
35
|
return new Proxy<T>({} as T, {
|
|
68
36
|
get(_, name) {
|
|
@@ -81,28 +49,9 @@ export function createMCPClientProxy<T extends Record<string, unknown>>(
|
|
|
81
49
|
// Create a connection with the tool name in the URL path for better logging
|
|
82
50
|
// Only modify connections that have a URL property (HTTP, SSE, Websocket)
|
|
83
51
|
// Use automatic detection based on URL, with optional override
|
|
84
|
-
let toolConnection = connection;
|
|
85
|
-
const shouldAddToolName =
|
|
86
|
-
options?.supportsToolName ??
|
|
87
|
-
("url" in connection &&
|
|
88
|
-
typeof connection.url === "string" &&
|
|
89
|
-
supportsToolNameInPath(connection.url));
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
shouldAddToolName &&
|
|
93
|
-
"url" in connection &&
|
|
94
|
-
typeof connection.url === "string"
|
|
95
|
-
) {
|
|
96
|
-
toolConnection = {
|
|
97
|
-
...connection,
|
|
98
|
-
url: connection.url.endsWith("/")
|
|
99
|
-
? `${connection.url}tool/${String(name)}`
|
|
100
|
-
: `${connection.url}/tool/${String(name)}`,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
52
|
|
|
104
53
|
const { client, callStreamableTool } = await createServerClient(
|
|
105
|
-
{ connection
|
|
54
|
+
{ connection },
|
|
106
55
|
undefined,
|
|
107
56
|
extraHeaders,
|
|
108
57
|
);
|
package/src/state.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import {
|
|
3
|
-
import type { AppContext } from "./
|
|
4
|
-
import { createTool } from "./mastra.ts";
|
|
2
|
+
import { DefaultEnv } from "./index.ts";
|
|
3
|
+
import type { AppContext } from "./tools.ts";
|
|
5
4
|
|
|
6
5
|
const asyncLocalStorage = new AsyncLocalStorage<AppContext | undefined>();
|
|
7
6
|
|
|
@@ -9,36 +8,9 @@ export const State = {
|
|
|
9
8
|
getStore: () => {
|
|
10
9
|
return asyncLocalStorage.getStore();
|
|
11
10
|
},
|
|
12
|
-
run: <TEnv, R, TArgs extends unknown[]>(
|
|
11
|
+
run: <TEnv extends DefaultEnv, R, TArgs extends unknown[]>(
|
|
13
12
|
ctx: AppContext<TEnv>,
|
|
14
13
|
f: (...args: TArgs) => R,
|
|
15
14
|
...args: TArgs
|
|
16
15
|
): R => asyncLocalStorage.run(ctx, f, ...args),
|
|
17
16
|
};
|
|
18
|
-
|
|
19
|
-
export interface ValidationPayload {
|
|
20
|
-
state: unknown;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const createStateValidationTool = (stateSchema?: z.ZodTypeAny) => {
|
|
24
|
-
return createTool({
|
|
25
|
-
id: "DECO_CHAT_STATE_VALIDATION",
|
|
26
|
-
description: "Validate the state of the OAuth flow",
|
|
27
|
-
inputSchema: z.object({
|
|
28
|
-
state: z.unknown(),
|
|
29
|
-
}),
|
|
30
|
-
outputSchema: z.object({
|
|
31
|
-
valid: z.boolean(),
|
|
32
|
-
}),
|
|
33
|
-
execute: (ctx) => {
|
|
34
|
-
if (!stateSchema) {
|
|
35
|
-
return Promise.resolve({ valid: true });
|
|
36
|
-
}
|
|
37
|
-
const parsed = stateSchema.safeParse(ctx.context.state);
|
|
38
|
-
return Promise.resolve({
|
|
39
|
-
valid: parsed.success,
|
|
40
|
-
reason: parsed.error?.message,
|
|
41
|
-
});
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
};
|