@decocms/runtime 0.0.1-testing-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config-schema.json +553 -0
- package/dist/admin.d.ts +5 -0
- package/dist/admin.js +21 -0
- package/dist/admin.js.map +1 -0
- package/dist/bindings/deconfig/index.d.ts +9 -0
- package/dist/bindings/deconfig/index.js +9 -0
- package/dist/bindings/deconfig/index.js.map +1 -0
- package/dist/bindings/index.d.ts +1053 -0
- package/dist/bindings/index.js +132 -0
- package/dist/bindings/index.js.map +1 -0
- package/dist/chunk-4XSQKJLU.js +105 -0
- package/dist/chunk-4XSQKJLU.js.map +1 -0
- package/dist/chunk-AOFOWQXY.js +27 -0
- package/dist/chunk-AOFOWQXY.js.map +1 -0
- package/dist/chunk-F6XZPFWM.js +127 -0
- package/dist/chunk-F6XZPFWM.js.map +1 -0
- package/dist/chunk-IB3KGSMB.js +150 -0
- package/dist/chunk-IB3KGSMB.js.map +1 -0
- package/dist/chunk-NKUMVYKI.js +128 -0
- package/dist/chunk-NKUMVYKI.js.map +1 -0
- package/dist/chunk-NMXOC7PT.js +763 -0
- package/dist/chunk-NMXOC7PT.js.map +1 -0
- package/dist/chunk-OSSKGDAG.js +395 -0
- package/dist/chunk-OSSKGDAG.js.map +1 -0
- package/dist/chunk-UHR3BLMF.js +92 -0
- package/dist/chunk-UHR3BLMF.js.map +1 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -0
- package/dist/connection-DDtQYrea.d.ts +30 -0
- package/dist/drizzle.d.ts +47 -0
- package/dist/drizzle.js +121 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/index-AKVjfH4b.d.ts +336 -0
- package/dist/index-kMsI0ELb.d.ts +530 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +507 -0
- package/dist/index.js.map +1 -0
- package/dist/mastra.d.ts +8 -0
- package/dist/mastra.js +5 -0
- package/dist/mastra.js.map +1 -0
- package/dist/mcp-Bv7IAgWX.d.ts +109 -0
- package/dist/mcp-client.d.ts +236 -0
- package/dist/mcp-client.js +3 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/proxy.d.ts +10 -0
- package/dist/proxy.js +4 -0
- package/dist/proxy.js.map +1 -0
- package/dist/resources.d.ts +362 -0
- package/dist/resources.js +3 -0
- package/dist/resources.js.map +1 -0
- package/dist/views.d.ts +72 -0
- package/dist/views.js +3 -0
- package/dist/views.js.map +1 -0
- package/package.json +98 -0
- package/src/admin.ts +16 -0
- package/src/auth.ts +233 -0
- package/src/bindings/README.md +132 -0
- package/src/bindings/binder.ts +143 -0
- package/src/bindings/channels.ts +54 -0
- package/src/bindings/deconfig/helpers.ts +107 -0
- package/src/bindings/deconfig/index.ts +1 -0
- package/src/bindings/deconfig/resources.ts +659 -0
- package/src/bindings/deconfig/types.ts +106 -0
- package/src/bindings/index.ts +61 -0
- package/src/bindings/resources/bindings.ts +99 -0
- package/src/bindings/resources/helpers.ts +95 -0
- package/src/bindings/resources/schemas.ts +265 -0
- package/src/bindings/utils.ts +22 -0
- package/src/bindings/views.ts +14 -0
- package/src/bindings.ts +179 -0
- package/src/client.ts +201 -0
- package/src/connection.ts +53 -0
- package/src/drizzle.ts +201 -0
- package/src/http-client-transport.ts +66 -0
- package/src/index.ts +394 -0
- package/src/mastra.ts +666 -0
- package/src/mcp-client.ts +119 -0
- package/src/mcp.ts +171 -0
- package/src/proxy.ts +204 -0
- package/src/resources.ts +168 -0
- package/src/state.ts +44 -0
- package/src/views.ts +26 -0
- package/src/well-known.ts +20 -0
- package/src/wrangler.ts +146 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Client as BaseClient,
|
|
3
|
+
ClientOptions,
|
|
4
|
+
} from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
|
+
import {
|
|
6
|
+
SSEClientTransport,
|
|
7
|
+
SSEClientTransportOptions,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/client/sse.js";
|
|
9
|
+
import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
|
|
10
|
+
import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
11
|
+
import {
|
|
12
|
+
Implementation,
|
|
13
|
+
ListToolsRequest,
|
|
14
|
+
ListToolsResultSchema,
|
|
15
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
+
import { MCPConnection } from "./connection.ts";
|
|
17
|
+
import { HTTPClientTransport } from "./http-client-transport.ts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* WARNNING: This is a hack to prevent schema compilation errors.
|
|
21
|
+
* More info at: https://github.com/modelcontextprotocol/typescript-sdk/issues/923
|
|
22
|
+
*
|
|
23
|
+
* Make sure to keep this updated with the right version of the SDK.
|
|
24
|
+
* https://github.com/modelcontextprotocol/typescript-sdk/blob/bf817939917277a4c59f2e19e7b44b8dd7ff140c/src/client/index.ts#L480
|
|
25
|
+
*/
|
|
26
|
+
class Client extends BaseClient {
|
|
27
|
+
constructor(_clientInfo: Implementation, options?: ClientOptions) {
|
|
28
|
+
super(_clientInfo, options);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override async listTools(
|
|
32
|
+
params?: ListToolsRequest["params"],
|
|
33
|
+
options?: RequestOptions,
|
|
34
|
+
) {
|
|
35
|
+
const result = await this.request(
|
|
36
|
+
{ method: "tools/list", params },
|
|
37
|
+
ListToolsResultSchema,
|
|
38
|
+
options,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const createServerClient = async (
|
|
46
|
+
mcpServer: { connection: MCPConnection; name?: string },
|
|
47
|
+
signal?: AbortSignal,
|
|
48
|
+
extraHeaders?: Record<string, string>,
|
|
49
|
+
): Promise<Client> => {
|
|
50
|
+
const transport = createTransport(mcpServer.connection, signal, extraHeaders);
|
|
51
|
+
|
|
52
|
+
if (!transport) {
|
|
53
|
+
throw new Error("Unknown MCP connection type");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const client = new Client({
|
|
57
|
+
name: mcpServer?.name ?? "MCP Client",
|
|
58
|
+
version: "1.0.0",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await client.connect(transport);
|
|
62
|
+
|
|
63
|
+
return client;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const createTransport = (
|
|
67
|
+
connection: MCPConnection,
|
|
68
|
+
signal?: AbortSignal,
|
|
69
|
+
extraHeaders?: Record<string, string>,
|
|
70
|
+
) => {
|
|
71
|
+
if (connection.type === "Websocket") {
|
|
72
|
+
return new WebSocketClientTransport(new URL(connection.url));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (connection.type !== "SSE" && connection.type !== "HTTP") {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const authHeaders: Record<string, string> = connection.token
|
|
80
|
+
? { authorization: `Bearer ${connection.token}` }
|
|
81
|
+
: {};
|
|
82
|
+
|
|
83
|
+
const headers: Record<string, string> = {
|
|
84
|
+
...authHeaders,
|
|
85
|
+
...(extraHeaders ?? {}),
|
|
86
|
+
...("headers" in connection ? connection.headers || {} : {}),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (connection.type === "SSE") {
|
|
90
|
+
const config: SSEClientTransportOptions = {
|
|
91
|
+
requestInit: { headers, signal },
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (connection.token) {
|
|
95
|
+
config.eventSourceInit = {
|
|
96
|
+
fetch: (req, init) => {
|
|
97
|
+
return fetch(req, {
|
|
98
|
+
...init,
|
|
99
|
+
headers: {
|
|
100
|
+
...headers,
|
|
101
|
+
Accept: "text/event-stream",
|
|
102
|
+
},
|
|
103
|
+
signal,
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return new SSEClientTransport(new URL(connection.url), config);
|
|
110
|
+
}
|
|
111
|
+
return new HTTPClientTransport(new URL(connection.url), {
|
|
112
|
+
requestInit: {
|
|
113
|
+
headers,
|
|
114
|
+
signal,
|
|
115
|
+
// @ts-ignore - this is a valid option for fetch
|
|
116
|
+
credentials: "include",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
};
|
package/src/mcp.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
|
|
6
|
+
export interface FetchOptions extends RequestInit {
|
|
7
|
+
path?: string;
|
|
8
|
+
segments?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Timings = z.object({
|
|
12
|
+
sql_duration_ms: z.number().optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const Meta = z.object({
|
|
16
|
+
changed_db: z.boolean().optional(),
|
|
17
|
+
changes: z.number().optional(),
|
|
18
|
+
duration: z.number().optional(),
|
|
19
|
+
last_row_id: z.number().optional(),
|
|
20
|
+
rows_read: z.number().optional(),
|
|
21
|
+
rows_written: z.number().optional(),
|
|
22
|
+
served_by_primary: z.boolean().optional(),
|
|
23
|
+
served_by_region: z
|
|
24
|
+
.enum(["WNAM", "ENAM", "WEUR", "EEUR", "APAC", "OC"])
|
|
25
|
+
.optional(),
|
|
26
|
+
size_after: z.number().optional(),
|
|
27
|
+
timings: Timings.optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const QueryResult = z.object({
|
|
31
|
+
meta: Meta.optional(),
|
|
32
|
+
results: z.array(z.unknown()).optional(),
|
|
33
|
+
success: z.boolean().optional(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export type QueryResult = z.infer<typeof QueryResult>;
|
|
37
|
+
|
|
38
|
+
const workspaceTools = [
|
|
39
|
+
{
|
|
40
|
+
name: "INTEGRATIONS_GET" as const,
|
|
41
|
+
inputSchema: z.object({
|
|
42
|
+
id: z.string(),
|
|
43
|
+
}),
|
|
44
|
+
outputSchema: z.object({
|
|
45
|
+
connection: z.object({}),
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "DATABASES_RUN_SQL" as const,
|
|
50
|
+
inputSchema: z.object({
|
|
51
|
+
sql: z.string().describe("The SQL query to run"),
|
|
52
|
+
params: z
|
|
53
|
+
.array(z.string())
|
|
54
|
+
.describe("The parameters to pass to the SQL query"),
|
|
55
|
+
}),
|
|
56
|
+
outputSchema: z.object({
|
|
57
|
+
result: z.array(QueryResult),
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
] satisfies ToolBinder<string, unknown, object>[];
|
|
61
|
+
|
|
62
|
+
// Default fetcher instance with API_SERVER_URL and API_HEADERS
|
|
63
|
+
const global = createMCPFetchStub<[]>({});
|
|
64
|
+
export const MCPClient = new Proxy(
|
|
65
|
+
{} as typeof global & {
|
|
66
|
+
forWorkspace: (
|
|
67
|
+
workspace: string,
|
|
68
|
+
token?: string,
|
|
69
|
+
decoCmsApiUrl?: string,
|
|
70
|
+
) => MCPClientFetchStub<typeof workspaceTools>;
|
|
71
|
+
forConnection: <TDefinition extends readonly ToolBinder[]>(
|
|
72
|
+
connection: MCPConnectionProvider,
|
|
73
|
+
decoCmsApiUrl?: string,
|
|
74
|
+
) => MCPClientFetchStub<TDefinition>;
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
get(_, name) {
|
|
78
|
+
if (name === "toJSON") {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (name === "forWorkspace") {
|
|
83
|
+
return (workspace: string, token?: string, decoCmsApiUrl?: string) =>
|
|
84
|
+
createMCPFetchStub<[]>({
|
|
85
|
+
workspace,
|
|
86
|
+
token,
|
|
87
|
+
decoCmsApiUrl,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (name === "forConnection") {
|
|
91
|
+
return <TDefinition extends readonly ToolBinder[]>(
|
|
92
|
+
connection: MCPConnectionProvider,
|
|
93
|
+
decoCmsApiUrl?: string,
|
|
94
|
+
) =>
|
|
95
|
+
createMCPFetchStub<TDefinition>({
|
|
96
|
+
connection,
|
|
97
|
+
decoCmsApiUrl,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return global[name as keyof typeof global];
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
export interface ToolBinder<
|
|
106
|
+
TName extends string = string,
|
|
107
|
+
TInput = any,
|
|
108
|
+
TReturn extends object | null | boolean = object,
|
|
109
|
+
> {
|
|
110
|
+
name: TName;
|
|
111
|
+
inputSchema: z.ZodType<TInput>;
|
|
112
|
+
outputSchema?: z.ZodType<TReturn>;
|
|
113
|
+
opt?: true;
|
|
114
|
+
}
|
|
115
|
+
export type MCPClientStub<TDefinition extends readonly ToolBinder[]> = {
|
|
116
|
+
[K in TDefinition[number] as K["name"]]: K extends ToolBinder<
|
|
117
|
+
string,
|
|
118
|
+
infer TInput,
|
|
119
|
+
infer TReturn
|
|
120
|
+
>
|
|
121
|
+
? (params: TInput, init?: RequestInit) => Promise<TReturn>
|
|
122
|
+
: never;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export type MCPClientFetchStub<TDefinition extends readonly ToolBinder[]> = {
|
|
126
|
+
[K in TDefinition[number] as K["name"]]: K extends ToolBinder<
|
|
127
|
+
string,
|
|
128
|
+
infer TInput,
|
|
129
|
+
infer TReturn
|
|
130
|
+
>
|
|
131
|
+
? (params: TInput, init?: RequestInit) => Promise<Awaited<TReturn>>
|
|
132
|
+
: never;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type MCPConnectionProvider = MCPConnection;
|
|
136
|
+
|
|
137
|
+
export interface MCPClientRaw {
|
|
138
|
+
callTool: (tool: string, args: unknown) => Promise<unknown>;
|
|
139
|
+
listTools: () => Promise<
|
|
140
|
+
{
|
|
141
|
+
name: string;
|
|
142
|
+
inputSchema: any;
|
|
143
|
+
outputSchema?: any;
|
|
144
|
+
description: string;
|
|
145
|
+
}[]
|
|
146
|
+
>;
|
|
147
|
+
}
|
|
148
|
+
export type JSONSchemaToZodConverter = (jsonSchema: any) => z.ZodTypeAny;
|
|
149
|
+
export interface CreateStubAPIOptions {
|
|
150
|
+
mcpPath?: string;
|
|
151
|
+
decoCmsApiUrl?: string;
|
|
152
|
+
workspace?: string;
|
|
153
|
+
token?: string;
|
|
154
|
+
connection?: MCPConnectionProvider;
|
|
155
|
+
debugId?: () => string;
|
|
156
|
+
getErrorByStatusCode?: (
|
|
157
|
+
statusCode: number,
|
|
158
|
+
message?: string,
|
|
159
|
+
traceId?: string,
|
|
160
|
+
errorObject?: unknown,
|
|
161
|
+
) => Error;
|
|
162
|
+
supportsToolName?: boolean;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createMCPFetchStub<TDefinition extends readonly ToolBinder[]>(
|
|
166
|
+
options?: CreateStubAPIOptions,
|
|
167
|
+
): MCPClientFetchStub<TDefinition> {
|
|
168
|
+
return createMCPClientProxy<MCPClientFetchStub<TDefinition>>({
|
|
169
|
+
...(options ?? {}),
|
|
170
|
+
});
|
|
171
|
+
}
|
package/src/proxy.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/* oxlint-disable no-explicit-any */
|
|
2
|
+
import type { ToolExecutionContext as _ToolExecutionContext } from "@mastra/core";
|
|
3
|
+
import { convertJsonSchemaToZod } from "zod-from-json-schema";
|
|
4
|
+
import { MCPConnection } from "./connection.ts";
|
|
5
|
+
import { createServerClient } from "./mcp-client.ts";
|
|
6
|
+
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
|
+
|
|
16
|
+
const safeParse = (content: string) => {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(content as string);
|
|
19
|
+
} catch {
|
|
20
|
+
return content;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const toolsMap = new Map<
|
|
25
|
+
string,
|
|
26
|
+
Promise<
|
|
27
|
+
Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
inputSchema: any;
|
|
30
|
+
outputSchema?: any;
|
|
31
|
+
description: string;
|
|
32
|
+
}>
|
|
33
|
+
>
|
|
34
|
+
>();
|
|
35
|
+
|
|
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
|
+
/**
|
|
51
|
+
* The base fetcher used to fetch the MCP from API.
|
|
52
|
+
*/
|
|
53
|
+
export function createMCPClientProxy<T extends Record<string, unknown>>(
|
|
54
|
+
options?: CreateStubAPIOptions,
|
|
55
|
+
): T {
|
|
56
|
+
const mcpPath = options?.mcpPath ?? "/mcp";
|
|
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
|
+
};
|
|
66
|
+
|
|
67
|
+
return new Proxy<T>({} as T, {
|
|
68
|
+
get(_, name) {
|
|
69
|
+
if (name === "toJSON") {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (typeof name !== "string") {
|
|
73
|
+
throw new Error("Name must be a string");
|
|
74
|
+
}
|
|
75
|
+
async function callToolFn(args: unknown) {
|
|
76
|
+
const debugId = options?.debugId?.();
|
|
77
|
+
const extraHeaders = debugId
|
|
78
|
+
? { "x-trace-debug-id": debugId }
|
|
79
|
+
: undefined;
|
|
80
|
+
|
|
81
|
+
// Create a connection with the tool name in the URL path for better logging
|
|
82
|
+
// Only modify connections that have a URL property (HTTP, SSE, Websocket)
|
|
83
|
+
// 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
|
+
|
|
104
|
+
const client = await createServerClient(
|
|
105
|
+
{ connection: toolConnection },
|
|
106
|
+
undefined,
|
|
107
|
+
extraHeaders,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const { structuredContent, isError, content } = await client.callTool(
|
|
111
|
+
{
|
|
112
|
+
name: String(name),
|
|
113
|
+
arguments: args as Record<string, unknown>,
|
|
114
|
+
},
|
|
115
|
+
undefined,
|
|
116
|
+
{
|
|
117
|
+
timeout: 3000000,
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (isError) {
|
|
122
|
+
// @ts-expect-error - content is not typed
|
|
123
|
+
const maybeErrorMessage = content?.[0]?.text;
|
|
124
|
+
const error =
|
|
125
|
+
typeof maybeErrorMessage === "string"
|
|
126
|
+
? safeParse(maybeErrorMessage)
|
|
127
|
+
: null;
|
|
128
|
+
|
|
129
|
+
const throwableError =
|
|
130
|
+
error?.code && typeof options?.getErrorByStatusCode === "function"
|
|
131
|
+
? options.getErrorByStatusCode(
|
|
132
|
+
error.code,
|
|
133
|
+
error.message,
|
|
134
|
+
error.traceId,
|
|
135
|
+
)
|
|
136
|
+
: null;
|
|
137
|
+
|
|
138
|
+
if (throwableError) {
|
|
139
|
+
throw throwableError;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Tool ${String(name)} returned an error: ${JSON.stringify(
|
|
144
|
+
structuredContent ?? content,
|
|
145
|
+
)}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return structuredContent;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const listToolsFn = async () => {
|
|
152
|
+
const client = await createServerClient({ connection });
|
|
153
|
+
const { tools } = await client.listTools();
|
|
154
|
+
|
|
155
|
+
return tools as {
|
|
156
|
+
name: string;
|
|
157
|
+
inputSchema: any;
|
|
158
|
+
outputSchema?: any;
|
|
159
|
+
description: string;
|
|
160
|
+
}[];
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
async function listToolsOnce() {
|
|
164
|
+
const conn = connection;
|
|
165
|
+
const key = JSON.stringify(conn);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
if (!toolsMap.has(key)) {
|
|
169
|
+
toolsMap.set(key, listToolsFn());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return await toolsMap.get(key)!;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error("Failed to list tools", error);
|
|
175
|
+
|
|
176
|
+
toolsMap.delete(key);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
callToolFn.asTool = async () => {
|
|
181
|
+
const tools = (await listToolsOnce()) ?? [];
|
|
182
|
+
const tool = tools.find((t) => t.name === name);
|
|
183
|
+
if (!tool) {
|
|
184
|
+
throw new Error(`Tool ${name} not found`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
...tool,
|
|
189
|
+
id: tool.name,
|
|
190
|
+
inputSchema: tool.inputSchema
|
|
191
|
+
? convertJsonSchemaToZod(tool.inputSchema)
|
|
192
|
+
: undefined,
|
|
193
|
+
outputSchema: tool.outputSchema
|
|
194
|
+
? convertJsonSchemaToZod(tool.outputSchema)
|
|
195
|
+
: undefined,
|
|
196
|
+
execute: (input: any) => {
|
|
197
|
+
return callToolFn(input.context);
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
return callToolFn;
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
package/src/resources.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import mimeDb from "mime-db";
|
|
3
|
+
|
|
4
|
+
type MimeDb = Record<string, { extensions?: string[] }>;
|
|
5
|
+
|
|
6
|
+
const EXTENSION_TO_MIME = (() => {
|
|
7
|
+
const map = new Map<string, string>();
|
|
8
|
+
Object.entries(mimeDb as MimeDb).forEach(([type, meta]) => {
|
|
9
|
+
meta.extensions?.forEach((ext) => {
|
|
10
|
+
if (!map.has(ext)) {
|
|
11
|
+
map.set(ext, type);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
return map;
|
|
16
|
+
})();
|
|
17
|
+
|
|
18
|
+
function extractExtension(value: string): string | undefined {
|
|
19
|
+
if (!value) return undefined;
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
if (!trimmed) return undefined;
|
|
22
|
+
|
|
23
|
+
const sanitized = trimmed.replace(/^[^?#]*/g, (match) => match);
|
|
24
|
+
const withoutQuery = sanitized.split(/[?#]/)[0];
|
|
25
|
+
|
|
26
|
+
if (withoutQuery.startsWith(".")) {
|
|
27
|
+
return withoutQuery.slice(1).toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!withoutQuery.includes(".")) {
|
|
31
|
+
if (!withoutQuery.includes("/") && !withoutQuery.includes("\\")) {
|
|
32
|
+
return withoutQuery.toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const lastDot = withoutQuery.lastIndexOf(".");
|
|
38
|
+
if (lastDot === -1 || lastDot === withoutQuery.length - 1) return undefined;
|
|
39
|
+
return withoutQuery.slice(lastDot + 1).toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Base Resource Schema (enhanced)
|
|
43
|
+
const ResourceSchema = z.object({
|
|
44
|
+
name: z.string(),
|
|
45
|
+
title: z.string().optional(),
|
|
46
|
+
description: z.string().optional(),
|
|
47
|
+
uri: z.string().url(),
|
|
48
|
+
mimeType: z.string().optional(),
|
|
49
|
+
thumbnail: z.string().url().optional(),
|
|
50
|
+
timestamp: z.string().datetime().optional(),
|
|
51
|
+
size: z.number().positive().optional(),
|
|
52
|
+
annotations: z.record(z.string(), z.string()).optional(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Tool Input/Output Schemas (unified)
|
|
56
|
+
export const ResourcesReadInputSchema = z.object({
|
|
57
|
+
name: z.string().describe("Resource type name (e.g., 'Page', 'GoogleDrive')"),
|
|
58
|
+
uri: z
|
|
59
|
+
.string()
|
|
60
|
+
.url()
|
|
61
|
+
.describe(
|
|
62
|
+
"The URI of the resource to read. It's important to add the url scheme. Use file:// for files. Use https:// or http:// for remote files",
|
|
63
|
+
),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const ResourcesReadOutputSchema = ResourceSchema.extend({
|
|
67
|
+
data: z.string().describe("The resource content as a string"),
|
|
68
|
+
type: z
|
|
69
|
+
.enum(["text", "blob"])
|
|
70
|
+
.describe(
|
|
71
|
+
"Type of data: 'text' for plain text, 'blob' for base64-encoded binary",
|
|
72
|
+
),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const ResourceSearchInputSchema = z.object({
|
|
76
|
+
name: z.string().describe("Resource type name (e.g., 'Page', 'GoogleDrive')"),
|
|
77
|
+
term: z.string().describe("The term to search for"),
|
|
78
|
+
cursor: z.string().optional(),
|
|
79
|
+
limit: z.number().positive().optional(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const ResourceSearchOutputSchema = z.object({
|
|
83
|
+
items: z.array(ResourceSchema),
|
|
84
|
+
hasMore: z.boolean(),
|
|
85
|
+
nextCursor: z.string().optional(),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const ResourceCreateInputSchema = z.object({
|
|
89
|
+
name: z.string().describe("Resource type name (e.g., 'Page', 'GoogleDrive')"),
|
|
90
|
+
resourceName: z
|
|
91
|
+
.string()
|
|
92
|
+
.describe(
|
|
93
|
+
"Name of the specific resource instance. DO NOT ADD EXTENSIONS TO THE NAME",
|
|
94
|
+
),
|
|
95
|
+
title: z.string().optional(),
|
|
96
|
+
description: z.string().optional(),
|
|
97
|
+
content: z
|
|
98
|
+
.object({
|
|
99
|
+
data: z.string(),
|
|
100
|
+
type: z.enum(["text", "blob"]),
|
|
101
|
+
mimeType: z.string().optional(),
|
|
102
|
+
})
|
|
103
|
+
.describe("Content to create the resource with"),
|
|
104
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
105
|
+
});
|
|
106
|
+
export const ResourceCreateOutputSchema = ResourceSchema;
|
|
107
|
+
|
|
108
|
+
export const ResourceUpdateInputSchema = z.object({
|
|
109
|
+
name: z.string().describe("Resource type name (e.g., 'Page', 'GoogleDrive')"),
|
|
110
|
+
uri: z.string().url().describe("URI of the resource to update"),
|
|
111
|
+
resourceName: z.string().optional(),
|
|
112
|
+
title: z.string().optional(),
|
|
113
|
+
description: z.string().optional(),
|
|
114
|
+
content: z
|
|
115
|
+
.object({
|
|
116
|
+
data: z.string(),
|
|
117
|
+
type: z.enum(["text", "blob"]),
|
|
118
|
+
mimeType: z.string().optional(),
|
|
119
|
+
})
|
|
120
|
+
.optional(),
|
|
121
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
122
|
+
});
|
|
123
|
+
export const ResourceUpdateOutputSchema = ResourceSchema;
|
|
124
|
+
|
|
125
|
+
export const ResourceDeleteInputSchema = z.object({
|
|
126
|
+
name: z.string().describe("Resource type name (e.g., 'Page', 'GoogleDrive')"),
|
|
127
|
+
uri: z.string().url().describe("URI of the resource to delete"),
|
|
128
|
+
force: z.boolean().optional(),
|
|
129
|
+
});
|
|
130
|
+
export const ResourceDeleteOutputSchema = z.object({
|
|
131
|
+
deletedUri: z.string().url(),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export const ResourcesListInputSchema = z.object({});
|
|
135
|
+
export const ResourcesListOutputSchema = z.object({
|
|
136
|
+
resources: z.array(
|
|
137
|
+
z.object({
|
|
138
|
+
name: z.string(),
|
|
139
|
+
icon: z.string(),
|
|
140
|
+
title: z.string(),
|
|
141
|
+
description: z.string(),
|
|
142
|
+
hasCreate: z.boolean().optional(),
|
|
143
|
+
hasUpdate: z.boolean().optional(),
|
|
144
|
+
hasDelete: z.boolean().optional(),
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Export Types
|
|
150
|
+
export type Resource = z.infer<typeof ResourceCreateOutputSchema>;
|
|
151
|
+
export type ResourcesReadInput = z.infer<typeof ResourcesReadInputSchema>;
|
|
152
|
+
export type ResourcesReadOutput = z.infer<typeof ResourcesReadOutputSchema>;
|
|
153
|
+
export type ResourcesSearchInput = z.infer<typeof ResourceSearchInputSchema>;
|
|
154
|
+
export type ResourcesSearchOutput = z.infer<typeof ResourceSearchOutputSchema>;
|
|
155
|
+
export type ResourceCreateInput = z.infer<typeof ResourceCreateInputSchema>;
|
|
156
|
+
export type ResourceCreateOutput = z.infer<typeof ResourceCreateOutputSchema>;
|
|
157
|
+
export type ResourceUpdateInput = z.infer<typeof ResourceUpdateInputSchema>;
|
|
158
|
+
export type ResourceUpdateOutput = z.infer<typeof ResourceUpdateOutputSchema>;
|
|
159
|
+
export type ResourceDeleteInput = z.infer<typeof ResourceDeleteInputSchema>;
|
|
160
|
+
export type ResourceDeleteOutput = z.infer<typeof ResourceDeleteOutputSchema>;
|
|
161
|
+
export type ResourcesListInput = z.infer<typeof ResourcesListInputSchema>;
|
|
162
|
+
export type ResourcesListOutput = z.infer<typeof ResourcesListOutputSchema>;
|
|
163
|
+
|
|
164
|
+
export const mimeType = (filePathOrExtension: string) => {
|
|
165
|
+
const ext = extractExtension(filePathOrExtension);
|
|
166
|
+
if (!ext) return "text/plain";
|
|
167
|
+
return EXTENSION_TO_MIME.get(ext) ?? "text/plain";
|
|
168
|
+
};
|
package/src/state.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { AppContext } from "./mastra.ts";
|
|
4
|
+
import { createTool } from "./mastra.ts";
|
|
5
|
+
|
|
6
|
+
const asyncLocalStorage = new AsyncLocalStorage<AppContext | undefined>();
|
|
7
|
+
|
|
8
|
+
export const State = {
|
|
9
|
+
getStore: () => {
|
|
10
|
+
return asyncLocalStorage.getStore();
|
|
11
|
+
},
|
|
12
|
+
run: <TEnv, R, TArgs extends unknown[]>(
|
|
13
|
+
ctx: AppContext<TEnv>,
|
|
14
|
+
f: (...args: TArgs) => R,
|
|
15
|
+
...args: TArgs
|
|
16
|
+
): R => asyncLocalStorage.run(ctx, f, ...args),
|
|
17
|
+
};
|
|
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
|
+
};
|