@decocms/runtime 0.25.0 → 0.26.0
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/dist/bindings/deconfig/index.d.ts +6 -5
- package/dist/bindings/deconfig/index.js +4 -4
- package/dist/bindings/index.d.ts +1506 -8
- package/dist/bindings/index.js +9 -7
- package/dist/bindings/index.js.map +1 -1
- package/dist/{chunk-F6XZPFWM.js → chunk-3AWMDSOH.js} +20 -51
- package/dist/chunk-3AWMDSOH.js.map +1 -0
- package/dist/{chunk-QELHWEZH.js → chunk-5EYZ2LVM.js} +8 -5
- package/dist/chunk-5EYZ2LVM.js.map +1 -0
- package/dist/{chunk-O6IURJAY.js → chunk-GPIGZ6DL.js} +38 -33
- package/dist/chunk-GPIGZ6DL.js.map +1 -0
- package/dist/{chunk-I2KGAHFY.js → chunk-LCU3FBI3.js} +6 -2
- package/dist/chunk-LCU3FBI3.js.map +1 -0
- package/dist/drizzle.d.ts +4 -3
- package/dist/{index-CStCyFvK.d.ts → index-COMJ3oN7.d.ts} +4 -3
- package/dist/{index-DswRTp9B.d.ts → index-DqyElLzZ.d.ts} +4 -4
- package/dist/index.d.ts +4 -3
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/mastra.d.ts +4 -3
- package/dist/mastra.js +1 -1
- package/dist/{mcp-yTBduksO.d.ts → mcp-Dbqp-p04.d.ts} +5 -9
- package/dist/mcp-client.d.ts +10 -14
- package/dist/mcp-client.js +1 -1
- package/dist/proxy.d.ts +3 -2
- package/dist/proxy.js +2 -2
- package/package.json +4 -2
- package/src/bindings/binder.ts +37 -34
- package/src/bindings/channels.ts +1 -1
- package/src/bindings/index.ts +8 -6
- package/src/bindings/language-model/ai-sdk.ts +87 -0
- package/src/bindings/language-model/index.ts +4 -0
- package/src/bindings/language-model/utils.ts +118 -0
- package/src/bindings/views.ts +1 -1
- package/src/connection.ts +8 -53
- package/src/http-client-transport.ts +1 -66
- package/src/index.ts +4 -0
- package/src/mastra.ts +4 -0
- package/src/mcp-client.ts +22 -2
- package/src/mcp.ts +16 -17
- package/src/proxy.ts +6 -2
- package/dist/chunk-F6XZPFWM.js.map +0 -1
- package/dist/chunk-I2KGAHFY.js.map +0 -1
- package/dist/chunk-O6IURJAY.js.map +0 -1
- package/dist/chunk-QELHWEZH.js.map +0 -1
- package/dist/connection-DDtQYrea.d.ts +0 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsup",
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@cloudflare/workers-types": "^4.20250617.0",
|
|
11
11
|
"@deco/mcp": "npm:@jsr/deco__mcp@0.5.5",
|
|
12
|
+
"@decocms/bindings": "workspace:*",
|
|
12
13
|
"@mastra/cloudflare-d1": "^0.13.4",
|
|
13
14
|
"@mastra/core": "^0.20.2",
|
|
14
15
|
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
16
|
+
"@ai-sdk/provider": "^2.0.0",
|
|
15
17
|
"bidc": "0.0.3",
|
|
16
18
|
"drizzle-orm": "^0.44.5",
|
|
17
19
|
"jose": "^6.0.11",
|
|
@@ -101,4 +103,4 @@
|
|
|
101
103
|
"publishConfig": {
|
|
102
104
|
"access": "public"
|
|
103
105
|
}
|
|
104
|
-
}
|
|
106
|
+
}
|
package/src/bindings/binder.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/* oxlint-disable no-explicit-any */
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { MCPConnection } from "../connection.ts";
|
|
4
|
-
import { createPrivateTool } from "../mastra.ts";
|
|
4
|
+
import { createPrivateTool, createStreamableTool } from "../mastra.ts";
|
|
5
5
|
import {
|
|
6
6
|
createMCPFetchStub,
|
|
7
7
|
type MCPClientFetchStub,
|
|
8
8
|
type ToolBinder,
|
|
9
9
|
} from "../mcp.ts";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { CHANNEL_BINDING } from "./channels.ts";
|
|
11
|
+
import { VIEW_BINDING } from "./views.ts";
|
|
12
12
|
|
|
13
13
|
// ToolLike is a simplified version of the Tool interface that matches what we need for bindings
|
|
14
14
|
export interface ToolLike<
|
|
@@ -75,27 +75,16 @@ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
|
|
|
75
75
|
forConnection: (
|
|
76
76
|
mcpConnection: MCPConnection,
|
|
77
77
|
): MCPClientFetchStub<TDefinition> => {
|
|
78
|
-
|
|
78
|
+
return createMCPFetchStub<TDefinition>({
|
|
79
79
|
connection: mcpConnection,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
get(_, name) {
|
|
85
|
-
if (typeof name !== "string") {
|
|
86
|
-
throw new Error("Name must be a string");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return (args: Record<string, unknown>) => {
|
|
90
|
-
return (
|
|
91
|
-
stub[name as keyof MCPClientFetchStub<TDefinition>] as (
|
|
92
|
-
args: Record<string, unknown>,
|
|
93
|
-
) => Promise<unknown>
|
|
94
|
-
)(args);
|
|
95
|
-
};
|
|
80
|
+
streamable: binder.reduce(
|
|
81
|
+
(acc, tool) => {
|
|
82
|
+
acc[tool.name] = tool.streamable === true;
|
|
83
|
+
return acc;
|
|
96
84
|
},
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
{} as Record<string, boolean>,
|
|
86
|
+
),
|
|
87
|
+
});
|
|
99
88
|
},
|
|
100
89
|
};
|
|
101
90
|
};
|
|
@@ -103,9 +92,8 @@ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
|
|
|
103
92
|
export type MCPBindingClient<T extends ReturnType<typeof bindingClient>> =
|
|
104
93
|
ReturnType<T["forConnection"]>;
|
|
105
94
|
|
|
106
|
-
export const ChannelBinding = bindingClient(
|
|
107
|
-
|
|
108
|
-
export const ViewBinding = bindingClient(VIEW_BINDING_SCHEMA);
|
|
95
|
+
export const ChannelBinding = bindingClient(CHANNEL_BINDING);
|
|
96
|
+
export const ViewBinding = bindingClient(VIEW_BINDING);
|
|
109
97
|
|
|
110
98
|
export type { Callbacks } from "./channels.ts";
|
|
111
99
|
|
|
@@ -113,8 +101,12 @@ export const impl = <TBinder extends Binder>(
|
|
|
113
101
|
schema: TBinder,
|
|
114
102
|
implementation: BinderImplementation<TBinder>,
|
|
115
103
|
createToolFn = createPrivateTool,
|
|
104
|
+
createStreamableToolFn = createStreamableTool,
|
|
116
105
|
) => {
|
|
117
|
-
const impl:
|
|
106
|
+
const impl: (
|
|
107
|
+
| ReturnType<typeof createToolFn>
|
|
108
|
+
| ReturnType<typeof createStreamableToolFn>
|
|
109
|
+
)[] = [];
|
|
118
110
|
for (const key in schema) {
|
|
119
111
|
const toolSchema = schema[key];
|
|
120
112
|
const toolImplementation = implementation[key];
|
|
@@ -127,17 +119,28 @@ export const impl = <TBinder extends Binder>(
|
|
|
127
119
|
throw new Error(`Implementation for ${key} is required`);
|
|
128
120
|
}
|
|
129
121
|
|
|
130
|
-
const { name, handler, ...toolLike }
|
|
122
|
+
const { name, handler, streamable, ...toolLike } = {
|
|
131
123
|
...toolSchema,
|
|
132
124
|
...toolImplementation,
|
|
133
125
|
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
126
|
+
if (streamable) {
|
|
127
|
+
impl.push(
|
|
128
|
+
createStreamableToolFn({
|
|
129
|
+
...toolLike,
|
|
130
|
+
streamable,
|
|
131
|
+
id: name,
|
|
132
|
+
execute: ({ context }) => Promise.resolve(handler(context)),
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
impl.push(
|
|
137
|
+
createToolFn({
|
|
138
|
+
...toolLike,
|
|
139
|
+
id: name,
|
|
140
|
+
execute: ({ context }) => Promise.resolve(handler(context)),
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
141
144
|
}
|
|
142
145
|
return impl;
|
|
143
146
|
};
|
package/src/bindings/channels.ts
CHANGED
|
@@ -34,7 +34,7 @@ const listChannelsSchema = z.object({
|
|
|
34
34
|
export type Callbacks = z.infer<typeof callbacksSchema>;
|
|
35
35
|
export type JoinedChannelPayload = z.infer<typeof joinChannelSchema>;
|
|
36
36
|
export type ListChannelsSchema = z.infer<typeof listChannelsSchema>;
|
|
37
|
-
export const
|
|
37
|
+
export const CHANNEL_BINDING = [
|
|
38
38
|
{
|
|
39
39
|
name: "DECO_CHAT_CHANNELS_JOIN" as const,
|
|
40
40
|
inputSchema: joinChannelSchema,
|
package/src/bindings/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { CHANNEL_BINDING } from "./channels.ts";
|
|
2
|
+
import { VIEW_BINDING as VIEWS_BINDING } from "./views.ts";
|
|
3
3
|
|
|
4
4
|
// Import new Resources 2.0 bindings function
|
|
5
|
+
import { LANGUAGE_MODEL_BINDING } from "@decocms/bindings/llm";
|
|
5
6
|
import { createResourceBindings } from "./resources/bindings.ts";
|
|
6
7
|
|
|
7
8
|
// Export types and utilities from binder
|
|
@@ -35,11 +36,11 @@ export * from "./resources/schemas.ts";
|
|
|
35
36
|
|
|
36
37
|
// Export deconfig helpers and types
|
|
37
38
|
export {
|
|
38
|
-
ResourcePath,
|
|
39
|
-
ResourceUri,
|
|
40
39
|
getMetadataString as deconfigGetMetadataString,
|
|
41
40
|
getMetadataValue as deconfigGetMetadataValue,
|
|
42
41
|
normalizeDirectory as deconfigNormalizeDirectory,
|
|
42
|
+
ResourcePath,
|
|
43
|
+
ResourceUri,
|
|
43
44
|
} from "./deconfig/helpers.ts";
|
|
44
45
|
export { createDeconfigResource } from "./deconfig/index.ts";
|
|
45
46
|
export type {
|
|
@@ -52,8 +53,9 @@ export type {
|
|
|
52
53
|
export { deconfigTools } from "./deconfig/types.ts";
|
|
53
54
|
|
|
54
55
|
export const WellKnownBindings = {
|
|
55
|
-
Channel:
|
|
56
|
-
View:
|
|
56
|
+
Channel: CHANNEL_BINDING,
|
|
57
|
+
View: VIEWS_BINDING,
|
|
58
|
+
LanguageModel: LANGUAGE_MODEL_BINDING,
|
|
57
59
|
// Note: Resources is not included here since it's a generic function
|
|
58
60
|
// Use createResourceBindings(dataSchema) directly for Resources 2.0
|
|
59
61
|
} as const;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LanguageModelV2,
|
|
3
|
+
LanguageModelV2CallOptions,
|
|
4
|
+
ProviderV2,
|
|
5
|
+
} from "@ai-sdk/provider";
|
|
6
|
+
import { LanguageModelBinding } from "@decocms/bindings/llm";
|
|
7
|
+
import { lazy, responseToStream } from "./utils";
|
|
8
|
+
|
|
9
|
+
const toRegExp = (supportedUrls: Record<string, string[]>) => {
|
|
10
|
+
return Object.fromEntries(
|
|
11
|
+
Object.entries(supportedUrls).map(([key, values]) => [
|
|
12
|
+
key,
|
|
13
|
+
values.map((v) => new RegExp(v)),
|
|
14
|
+
]),
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type LLMBindingClient = ReturnType<
|
|
19
|
+
(typeof LanguageModelBinding)["forConnection"]
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
export interface Provider extends ProviderV2 {
|
|
23
|
+
listModels: LLMBindingClient["COLLECTION_MODELS_LIST"];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a ai-sdk compatible provider for the given binding
|
|
28
|
+
* @param binding - The binding to create the provider from
|
|
29
|
+
* @returns The provider
|
|
30
|
+
*/
|
|
31
|
+
export const createProvider = (binding: LLMBindingClient): Provider => {
|
|
32
|
+
return {
|
|
33
|
+
imageModel: () => {
|
|
34
|
+
throw new Error("Image models are not supported by this provider");
|
|
35
|
+
},
|
|
36
|
+
textEmbeddingModel: () => {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Text embedding models are not supported by this provider",
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
listModels: async () => {
|
|
42
|
+
return await binding.COLLECTION_MODELS_LIST({});
|
|
43
|
+
},
|
|
44
|
+
languageModel: (modelId: string): LanguageModelV2 => {
|
|
45
|
+
const supportedUrls = lazy(() =>
|
|
46
|
+
binding
|
|
47
|
+
.LLM_METADATA({ modelId })
|
|
48
|
+
.then((metadata) => toRegExp(metadata.supportedUrls)),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
specificationVersion: "v2" as const,
|
|
53
|
+
provider: "llm-binding",
|
|
54
|
+
modelId,
|
|
55
|
+
supportedUrls,
|
|
56
|
+
doGenerate: async (options: LanguageModelV2CallOptions) => {
|
|
57
|
+
const response = await binding.LLM_DO_GENERATE({
|
|
58
|
+
callOptions: options,
|
|
59
|
+
modelId,
|
|
60
|
+
});
|
|
61
|
+
// Ensure usage fields are always present as required by LanguageModelV2
|
|
62
|
+
return {
|
|
63
|
+
...response,
|
|
64
|
+
usage: {
|
|
65
|
+
inputTokens: response.usage.inputTokens ?? undefined,
|
|
66
|
+
outputTokens: response.usage.outputTokens ?? undefined,
|
|
67
|
+
totalTokens: response.usage.totalTokens ?? undefined,
|
|
68
|
+
reasoningTokens: response.usage.reasoningTokens ?? undefined,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
doStream: async (options: LanguageModelV2CallOptions) => {
|
|
73
|
+
const response = await binding.LLM_DO_STREAM({
|
|
74
|
+
callOptions: options,
|
|
75
|
+
modelId,
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
stream: responseToStream(response),
|
|
79
|
+
response: {
|
|
80
|
+
headers: Object.fromEntries(response.headers?.entries() ?? []),
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { LanguageModelV2StreamPart } from "@ai-sdk/provider";
|
|
2
|
+
|
|
3
|
+
// Helper to convert the AI SDK stream to a Response
|
|
4
|
+
export function streamToResponse(
|
|
5
|
+
stream: ReadableStream<LanguageModelV2StreamPart>,
|
|
6
|
+
headers?: Record<string, string>,
|
|
7
|
+
): Response {
|
|
8
|
+
// Transform LanguageModelV2StreamPart objects to newline-delimited JSON
|
|
9
|
+
const encodedStream = stream.pipeThrough(
|
|
10
|
+
new TransformStream<LanguageModelV2StreamPart, Uint8Array>({
|
|
11
|
+
transform(chunk, controller) {
|
|
12
|
+
// Serialize each chunk as JSON with newline delimiter
|
|
13
|
+
const line = JSON.stringify(chunk) + "\n";
|
|
14
|
+
controller.enqueue(new TextEncoder().encode(line));
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return new Response(encodedStream, {
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "application/x-ndjson", // newline-delimited JSON
|
|
22
|
+
"Cache-Control": "no-cache",
|
|
23
|
+
Connection: "keep-alive",
|
|
24
|
+
...headers,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function responseToStream(
|
|
30
|
+
response: Response,
|
|
31
|
+
): ReadableStream<LanguageModelV2StreamPart> {
|
|
32
|
+
if (!response.body) {
|
|
33
|
+
throw new Error("Response body is null");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return response.body.pipeThrough(new TextDecoderStream()).pipeThrough(
|
|
37
|
+
new TransformStream<string, LanguageModelV2StreamPart>({
|
|
38
|
+
transform(chunk, controller) {
|
|
39
|
+
// Split by newlines and parse each line
|
|
40
|
+
const lines = chunk.split("\n");
|
|
41
|
+
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
if (line.trim()) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(line) as LanguageModelV2StreamPart;
|
|
46
|
+
controller.enqueue(parsed);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("Failed to parse stream chunk:", error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Lazy promise wrapper that defers execution until the promise is awaited.
|
|
59
|
+
* The factory function is only called when .then() is invoked for the first time.
|
|
60
|
+
*/
|
|
61
|
+
class Lazy<T> implements PromiseLike<T> {
|
|
62
|
+
private promise: Promise<T> | null = null;
|
|
63
|
+
|
|
64
|
+
constructor(private factory: () => Promise<T>) {}
|
|
65
|
+
|
|
66
|
+
private getOrCreatePromise(): Promise<T> {
|
|
67
|
+
if (!this.promise) {
|
|
68
|
+
this.promise = this.factory();
|
|
69
|
+
}
|
|
70
|
+
return this.promise;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// eslint-disable-next-line no-thenable
|
|
74
|
+
then<TResult1 = T, TResult2 = never>(
|
|
75
|
+
onfulfilled?:
|
|
76
|
+
| ((value: T) => TResult1 | PromiseLike<TResult1>)
|
|
77
|
+
| null
|
|
78
|
+
| undefined,
|
|
79
|
+
onrejected?:
|
|
80
|
+
| ((reason: unknown) => TResult2 | PromiseLike<TResult2>)
|
|
81
|
+
| null
|
|
82
|
+
| undefined,
|
|
83
|
+
): PromiseLike<TResult1 | TResult2> {
|
|
84
|
+
return this.getOrCreatePromise().then(onfulfilled, onrejected);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
catch<TResult = never>(
|
|
88
|
+
onrejected?:
|
|
89
|
+
| ((reason: unknown) => TResult | PromiseLike<TResult>)
|
|
90
|
+
| null
|
|
91
|
+
| undefined,
|
|
92
|
+
): Promise<T | TResult> {
|
|
93
|
+
return this.getOrCreatePromise().catch(onrejected);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
finally(onfinally?: (() => void) | null | undefined): Promise<T> {
|
|
97
|
+
return this.getOrCreatePromise().finally(onfinally);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates a lazy promise that only executes when awaited.
|
|
103
|
+
*
|
|
104
|
+
* @param factory - A function that returns a Promise<T>
|
|
105
|
+
* @returns A Promise-like object that defers execution until .then() is called
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const lazyData = lazy(() => fetchExpensiveData());
|
|
110
|
+
* // fetchExpensiveData() is NOT called yet
|
|
111
|
+
*
|
|
112
|
+
* const result = await lazyData;
|
|
113
|
+
* // fetchExpensiveData() is called NOW
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export function lazy<T>(factory: () => Promise<T>): Promise<T> {
|
|
117
|
+
return new Lazy(factory) as unknown as Promise<T>;
|
|
118
|
+
}
|
package/src/bindings/views.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ViewsListOutputSchema } from "../views.ts";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { ToolBinder } from "../mcp.ts";
|
|
4
4
|
|
|
5
|
-
export const
|
|
5
|
+
export const VIEW_BINDING = [
|
|
6
6
|
{
|
|
7
7
|
name: "DECO_CHAT_VIEWS_LIST" as const,
|
|
8
8
|
inputSchema: z.any(),
|
package/src/connection.ts
CHANGED
|
@@ -1,53 +1,8 @@
|
|
|
1
|
-
export type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type: "Websocket";
|
|
10
|
-
url: string;
|
|
11
|
-
token?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type DecoConnection = {
|
|
15
|
-
type: "Deco";
|
|
16
|
-
tenant: string;
|
|
17
|
-
token?: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type InnateConnection = {
|
|
21
|
-
type: "INNATE";
|
|
22
|
-
name: string;
|
|
23
|
-
workspace?: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type HTTPConnection = {
|
|
27
|
-
type: "HTTP";
|
|
28
|
-
url: string;
|
|
29
|
-
headers?: Record<string, string>;
|
|
30
|
-
token?: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type MCPConnection =
|
|
34
|
-
| SSEConnection
|
|
35
|
-
| WebsocketConnection
|
|
36
|
-
| InnateConnection
|
|
37
|
-
| DecoConnection
|
|
38
|
-
| HTTPConnection;
|
|
39
|
-
|
|
40
|
-
export type Integration = {
|
|
41
|
-
/** Unique identifier for the MCP */
|
|
42
|
-
id: string;
|
|
43
|
-
/** Human-readable name of the integration */
|
|
44
|
-
name: string;
|
|
45
|
-
/** Brief description of the integration's functionality */
|
|
46
|
-
description?: string;
|
|
47
|
-
/** URL to the integration's icon */
|
|
48
|
-
icon?: string;
|
|
49
|
-
/** Access level of the integration */
|
|
50
|
-
access?: string | null;
|
|
51
|
-
/** Connection configuration */
|
|
52
|
-
connection: MCPConnection;
|
|
53
|
-
};
|
|
1
|
+
export type {
|
|
2
|
+
DecoConnection,
|
|
3
|
+
HTTPConnection,
|
|
4
|
+
InnateConnection,
|
|
5
|
+
MCPConnection,
|
|
6
|
+
SSEConnection,
|
|
7
|
+
WebsocketConnection,
|
|
8
|
+
} from "@decocms/bindings/connection";
|
|
@@ -1,66 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
StreamableHTTPClientTransport,
|
|
4
|
-
type StreamableHTTPClientTransportOptions,
|
|
5
|
-
} from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
6
|
-
|
|
7
|
-
export class HTTPClientTransport extends StreamableHTTPClientTransport {
|
|
8
|
-
constructor(url: URL, opts?: StreamableHTTPClientTransportOptions) {
|
|
9
|
-
super(url, opts);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
override send(
|
|
13
|
-
message: JSONRPCMessage,
|
|
14
|
-
options?: {
|
|
15
|
-
resumptionToken?: string;
|
|
16
|
-
onresumptiontoken?: (token: string) => void;
|
|
17
|
-
},
|
|
18
|
-
): Promise<void> {
|
|
19
|
-
const mockAction = getMockActionFor(message);
|
|
20
|
-
if (mockAction?.type === "emit") {
|
|
21
|
-
this.onmessage?.(mockAction.message);
|
|
22
|
-
return Promise.resolve();
|
|
23
|
-
}
|
|
24
|
-
if (mockAction?.type === "suppress") {
|
|
25
|
-
return Promise.resolve();
|
|
26
|
-
}
|
|
27
|
-
return super.send(message, options);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type MockAction =
|
|
32
|
-
| { type: "emit"; message: JSONRPCMessage }
|
|
33
|
-
| { type: "suppress" };
|
|
34
|
-
|
|
35
|
-
function getMockActionFor(message: JSONRPCMessage): MockAction | null {
|
|
36
|
-
const m = message;
|
|
37
|
-
if (!m || typeof m !== "object" || !("method" in m)) return null;
|
|
38
|
-
|
|
39
|
-
switch (m.method) {
|
|
40
|
-
case "initialize": {
|
|
41
|
-
const protocolVersion = m?.params?.protocolVersion;
|
|
42
|
-
if (!protocolVersion) return null;
|
|
43
|
-
return {
|
|
44
|
-
type: "emit",
|
|
45
|
-
message: {
|
|
46
|
-
result: {
|
|
47
|
-
protocolVersion,
|
|
48
|
-
capabilities: { tools: {} },
|
|
49
|
-
serverInfo: { name: "deco-chat-server", version: "1.0.0" },
|
|
50
|
-
},
|
|
51
|
-
jsonrpc: m.jsonrpc ?? "2.0",
|
|
52
|
-
// @ts-expect-error - id is not typed
|
|
53
|
-
id: m.id,
|
|
54
|
-
} as JSONRPCMessage,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
case "notifications/roots/list_changed":
|
|
58
|
-
case "notifications/initialized":
|
|
59
|
-
case "notifications/cancelled":
|
|
60
|
-
case "notifications/progress": {
|
|
61
|
-
return { type: "suppress" };
|
|
62
|
-
}
|
|
63
|
-
default:
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
1
|
+
export { HTTPClientTransport } from "@decocms/bindings/client";
|
package/src/index.ts
CHANGED
|
@@ -337,6 +337,10 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
|
337
337
|
toolCallInput,
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
+
if (result instanceof Response) {
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
|
|
340
344
|
return new Response(JSON.stringify(result), {
|
|
341
345
|
headers: {
|
|
342
346
|
"Content-Type": "application/json",
|
package/src/mastra.ts
CHANGED
|
@@ -128,6 +128,10 @@ export function createStreamableTool<
|
|
|
128
128
|
return {
|
|
129
129
|
...streamableTool,
|
|
130
130
|
execute: (input: ToolExecutionContext<TSchemaIn>) => {
|
|
131
|
+
const env = input.runtimeContext.get("env") as DefaultEnv;
|
|
132
|
+
if (env) {
|
|
133
|
+
env.DECO_REQUEST_CONTEXT.ensureAuthenticated();
|
|
134
|
+
}
|
|
131
135
|
return streamableTool.execute({
|
|
132
136
|
...input,
|
|
133
137
|
runtimeContext: createRuntimeContext(input.runtimeContext),
|
package/src/mcp-client.ts
CHANGED
|
@@ -42,11 +42,15 @@ class Client extends BaseClient {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
export interface ServerClient {
|
|
46
|
+
client: Client;
|
|
47
|
+
callStreamableTool: (tool: string, args: unknown) => Promise<Response>;
|
|
48
|
+
}
|
|
45
49
|
export const createServerClient = async (
|
|
46
50
|
mcpServer: { connection: MCPConnection; name?: string },
|
|
47
51
|
signal?: AbortSignal,
|
|
48
52
|
extraHeaders?: Record<string, string>,
|
|
49
|
-
): Promise<
|
|
53
|
+
): Promise<ServerClient> => {
|
|
50
54
|
const transport = createTransport(mcpServer.connection, signal, extraHeaders);
|
|
51
55
|
|
|
52
56
|
if (!transport) {
|
|
@@ -60,7 +64,23 @@ export const createServerClient = async (
|
|
|
60
64
|
|
|
61
65
|
await client.connect(transport);
|
|
62
66
|
|
|
63
|
-
return
|
|
67
|
+
return {
|
|
68
|
+
client,
|
|
69
|
+
callStreamableTool: (tool, args) => {
|
|
70
|
+
if (mcpServer.connection.type !== "HTTP") {
|
|
71
|
+
throw new Error("HTTP connection required");
|
|
72
|
+
}
|
|
73
|
+
return fetch(mcpServer.connection.url + `/call-tool/${tool}`, {
|
|
74
|
+
method: "POST",
|
|
75
|
+
redirect: "manual",
|
|
76
|
+
body: JSON.stringify(args),
|
|
77
|
+
headers: {
|
|
78
|
+
...extraHeaders,
|
|
79
|
+
Authorization: `Bearer ${mcpServer.connection.token}`,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
};
|
|
64
84
|
};
|
|
65
85
|
|
|
66
86
|
export const createTransport = (
|
package/src/mcp.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import type { MCPConnection } from "./connection.ts";
|
|
5
5
|
import type { DefaultEnv } from "./index.ts";
|
|
6
6
|
import { createMCPClientProxy } from "./proxy.ts";
|
|
7
|
+
import type { ToolBinder } from "@decocms/bindings";
|
|
7
8
|
|
|
8
9
|
export interface FetchOptions extends RequestInit {
|
|
9
10
|
path?: string;
|
|
@@ -101,16 +102,13 @@ export const MCPClient = new Proxy(
|
|
|
101
102
|
},
|
|
102
103
|
);
|
|
103
104
|
|
|
104
|
-
export
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
> {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
outputSchema?: z.ZodType<TReturn>;
|
|
112
|
-
opt?: true;
|
|
113
|
-
}
|
|
105
|
+
export type { ToolBinder };
|
|
106
|
+
|
|
107
|
+
export const isStreamableToolBinder = (
|
|
108
|
+
toolBinder: ToolBinder,
|
|
109
|
+
): toolBinder is ToolBinder<string, any, any, true> => {
|
|
110
|
+
return toolBinder.streamable === true;
|
|
111
|
+
};
|
|
114
112
|
export type MCPClientStub<TDefinition extends readonly ToolBinder[]> = {
|
|
115
113
|
[K in TDefinition[number] as K["name"]]: K extends ToolBinder<
|
|
116
114
|
string,
|
|
@@ -122,13 +120,13 @@ export type MCPClientStub<TDefinition extends readonly ToolBinder[]> = {
|
|
|
122
120
|
};
|
|
123
121
|
|
|
124
122
|
export type MCPClientFetchStub<TDefinition extends readonly ToolBinder[]> = {
|
|
125
|
-
[K in TDefinition[number] as K["name"]]: K extends
|
|
126
|
-
string,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
[K in TDefinition[number] as K["name"]]: K["streamable"] extends true
|
|
124
|
+
? K extends ToolBinder<string, infer TInput, any, true>
|
|
125
|
+
? (params: TInput, init?: RequestInit) => Promise<Response>
|
|
126
|
+
: never
|
|
127
|
+
: K extends ToolBinder<string, infer TInput, infer TReturn, any>
|
|
128
|
+
? (params: TInput, init?: RequestInit) => Promise<Awaited<TReturn>>
|
|
129
|
+
: never;
|
|
132
130
|
};
|
|
133
131
|
|
|
134
132
|
export type MCPConnectionProvider = MCPConnection;
|
|
@@ -151,6 +149,7 @@ export interface CreateStubAPIOptions {
|
|
|
151
149
|
workspace?: string;
|
|
152
150
|
token?: string;
|
|
153
151
|
connection?: MCPConnectionProvider;
|
|
152
|
+
streamable?: Record<string, boolean>;
|
|
154
153
|
debugId?: () => string;
|
|
155
154
|
getErrorByStatusCode?: (
|
|
156
155
|
statusCode: number,
|