@decocms/runtime 1.2.0 → 1.2.2
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/package.json +2 -2
- package/src/asset-server/index.test.ts +26 -5
- package/src/asset-server/index.ts +14 -2
- package/src/tools.ts +33 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/runtime",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "tsc --noEmit",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@cloudflare/workers-types": "^4.20250617.0",
|
|
11
11
|
"@decocms/bindings": "^1.0.7",
|
|
12
|
-
"@modelcontextprotocol/sdk": "1.25.
|
|
12
|
+
"@modelcontextprotocol/sdk": "1.25.2",
|
|
13
13
|
"@ai-sdk/provider": "^3.0.0",
|
|
14
14
|
"hono": "^4.10.7",
|
|
15
15
|
"jose": "^6.0.11",
|
|
@@ -214,13 +214,21 @@ describe("createAssetHandler", () => {
|
|
|
214
214
|
});
|
|
215
215
|
|
|
216
216
|
describe("SPA fallback for routes with dots", () => {
|
|
217
|
+
const acceptsHtml = {
|
|
218
|
+
headers: {
|
|
219
|
+
accept: "text/html",
|
|
220
|
+
},
|
|
221
|
+
};
|
|
217
222
|
test("serves index.html for /user/john.doe (non-existent path with dot)", async () => {
|
|
218
223
|
const handler = createAssetHandler({
|
|
219
224
|
env: "production",
|
|
220
225
|
clientDir: tempDir,
|
|
221
226
|
});
|
|
222
227
|
|
|
223
|
-
const request = new Request(
|
|
228
|
+
const request = new Request(
|
|
229
|
+
"http://localhost:3000/user/john.doe",
|
|
230
|
+
acceptsHtml,
|
|
231
|
+
);
|
|
224
232
|
const result = await handler(request);
|
|
225
233
|
|
|
226
234
|
expect(result).not.toBeNull();
|
|
@@ -235,7 +243,10 @@ describe("createAssetHandler", () => {
|
|
|
235
243
|
clientDir: tempDir,
|
|
236
244
|
});
|
|
237
245
|
|
|
238
|
-
const request = new Request(
|
|
246
|
+
const request = new Request(
|
|
247
|
+
"http://localhost:3000/page/v2.0",
|
|
248
|
+
acceptsHtml,
|
|
249
|
+
);
|
|
239
250
|
const result = await handler(request);
|
|
240
251
|
|
|
241
252
|
expect(result).not.toBeNull();
|
|
@@ -249,7 +260,10 @@ describe("createAssetHandler", () => {
|
|
|
249
260
|
clientDir: tempDir,
|
|
250
261
|
});
|
|
251
262
|
|
|
252
|
-
const request = new Request(
|
|
263
|
+
const request = new Request(
|
|
264
|
+
"http://localhost:3000/files/report.2024",
|
|
265
|
+
acceptsHtml,
|
|
266
|
+
);
|
|
253
267
|
const result = await handler(request);
|
|
254
268
|
|
|
255
269
|
expect(result).not.toBeNull();
|
|
@@ -263,7 +277,10 @@ describe("createAssetHandler", () => {
|
|
|
263
277
|
clientDir: tempDir,
|
|
264
278
|
});
|
|
265
279
|
|
|
266
|
-
const request = new Request(
|
|
280
|
+
const request = new Request(
|
|
281
|
+
"http://localhost:3000/assets/style.css",
|
|
282
|
+
acceptsHtml,
|
|
283
|
+
);
|
|
267
284
|
const result = await handler(request);
|
|
268
285
|
|
|
269
286
|
expect(result).not.toBeNull();
|
|
@@ -281,6 +298,7 @@ describe("createAssetHandler", () => {
|
|
|
281
298
|
// This CSS file doesn't exist, so it should fall back to index.html
|
|
282
299
|
const request = new Request(
|
|
283
300
|
"http://localhost:3000/assets/nonexistent.css",
|
|
301
|
+
acceptsHtml,
|
|
284
302
|
);
|
|
285
303
|
const result = await handler(request);
|
|
286
304
|
|
|
@@ -295,7 +313,10 @@ describe("createAssetHandler", () => {
|
|
|
295
313
|
clientDir: tempDir,
|
|
296
314
|
});
|
|
297
315
|
|
|
298
|
-
const request = new Request(
|
|
316
|
+
const request = new Request(
|
|
317
|
+
"http://localhost:3000/dashboard",
|
|
318
|
+
acceptsHtml,
|
|
319
|
+
);
|
|
299
320
|
const result = await handler(request);
|
|
300
321
|
|
|
301
322
|
expect(result).not.toBeNull();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { devServerProxy } from "./dev-server-proxy";
|
|
2
|
-
import { resolve, dirname } from "path";
|
|
2
|
+
import { resolve, dirname, join, extname } from "path";
|
|
3
3
|
|
|
4
4
|
export interface AssetServerConfig {
|
|
5
5
|
/**
|
|
@@ -209,9 +209,21 @@ export function createAssetHandler(config: AssetServerConfig = {}) {
|
|
|
209
209
|
return null; // Path traversal attempt blocked
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// Try to serve the index.html file relative to the requested file
|
|
213
|
+
const indexRelativeToFilePath = join(filePath, "index.html");
|
|
212
214
|
// Try to serve the requested file, fall back to index.html for SPA routing
|
|
213
215
|
const indexPath = resolve(clientDir, "index.html");
|
|
214
|
-
|
|
216
|
+
|
|
217
|
+
const acceptHeader = request.headers.get("accept");
|
|
218
|
+
const acceptsHtml =
|
|
219
|
+
acceptHeader?.includes("text/html") ||
|
|
220
|
+
(acceptHeader?.includes("*/*") &&
|
|
221
|
+
["", ".html"].includes(extname(filePath)));
|
|
222
|
+
|
|
223
|
+
const fallbackPaths = acceptsHtml
|
|
224
|
+
? [indexRelativeToFilePath, indexPath]
|
|
225
|
+
: [];
|
|
226
|
+
for (const pathToTry of [filePath, ...fallbackPaths]) {
|
|
215
227
|
try {
|
|
216
228
|
const file = Bun.file(pathToTry);
|
|
217
229
|
if (await file.exists()) {
|
package/src/tools.ts
CHANGED
|
@@ -44,6 +44,7 @@ export interface Tool<
|
|
|
44
44
|
TSchemaIn extends ZodTypeAny = ZodTypeAny,
|
|
45
45
|
TSchemaOut extends ZodTypeAny | undefined = undefined,
|
|
46
46
|
> {
|
|
47
|
+
_meta?: Record<string, unknown>;
|
|
47
48
|
id: string;
|
|
48
49
|
description?: string;
|
|
49
50
|
inputSchema: TSchemaIn;
|
|
@@ -59,6 +60,7 @@ export interface Tool<
|
|
|
59
60
|
* Streamable tool interface for tools that return Response streams.
|
|
60
61
|
*/
|
|
61
62
|
export interface StreamableTool<TSchemaIn extends ZodSchema = ZodSchema> {
|
|
63
|
+
_meta?: Record<string, unknown>;
|
|
62
64
|
id: string;
|
|
63
65
|
inputSchema: TSchemaIn;
|
|
64
66
|
streamable?: true;
|
|
@@ -71,6 +73,7 @@ export interface StreamableTool<TSchemaIn extends ZodSchema = ZodSchema> {
|
|
|
71
73
|
* Uses a structural type with relaxed execute signature to allow tools with any schema.
|
|
72
74
|
*/
|
|
73
75
|
export type CreatedTool = {
|
|
76
|
+
_meta?: Record<string, unknown>;
|
|
74
77
|
id: string;
|
|
75
78
|
description?: string;
|
|
76
79
|
inputSchema: ZodTypeAny;
|
|
@@ -666,6 +669,7 @@ export const createMCPServer = <
|
|
|
666
669
|
{
|
|
667
670
|
_meta: {
|
|
668
671
|
streamable: isStreamableTool(tool),
|
|
672
|
+
...(tool._meta ?? {}),
|
|
669
673
|
},
|
|
670
674
|
description: tool.description,
|
|
671
675
|
inputSchema:
|
|
@@ -681,13 +685,28 @@ export const createMCPServer = <
|
|
|
681
685
|
: z.object({}).shape,
|
|
682
686
|
},
|
|
683
687
|
async (args) => {
|
|
684
|
-
|
|
688
|
+
const result = await tool.execute({
|
|
685
689
|
context: args,
|
|
686
690
|
runtimeContext: createRuntimeContext(),
|
|
687
691
|
});
|
|
688
692
|
|
|
693
|
+
// For streamable tools, the Response is handled at the transport layer
|
|
694
|
+
// Do NOT call result.bytes() - it buffers the entire response in memory
|
|
695
|
+
// causing massive memory leaks (2GB+ Uint8Array accumulation)
|
|
689
696
|
if (isStreamableTool(tool) && result instanceof Response) {
|
|
690
|
-
|
|
697
|
+
return {
|
|
698
|
+
structuredContent: {
|
|
699
|
+
streamable: true,
|
|
700
|
+
status: result.status,
|
|
701
|
+
statusText: result.statusText,
|
|
702
|
+
},
|
|
703
|
+
content: [
|
|
704
|
+
{
|
|
705
|
+
type: "text",
|
|
706
|
+
text: `Streaming response: ${result.status} ${result.statusText}`,
|
|
707
|
+
},
|
|
708
|
+
],
|
|
709
|
+
};
|
|
691
710
|
}
|
|
692
711
|
return {
|
|
693
712
|
structuredContent: result as Record<string, unknown>,
|
|
@@ -830,7 +849,18 @@ export const createMCPServer = <
|
|
|
830
849
|
|
|
831
850
|
await server.connect(transport);
|
|
832
851
|
|
|
833
|
-
|
|
852
|
+
try {
|
|
853
|
+
return await transport.handleRequest(req);
|
|
854
|
+
} finally {
|
|
855
|
+
// CRITICAL: Close transport to prevent memory leaks
|
|
856
|
+
// Without this, ReadableStream/WritableStream controllers accumulate
|
|
857
|
+
// causing thousands of stream objects to be retained in memory
|
|
858
|
+
try {
|
|
859
|
+
await transport.close?.();
|
|
860
|
+
} catch {
|
|
861
|
+
// Ignore close errors - transport may already be closed
|
|
862
|
+
}
|
|
863
|
+
}
|
|
834
864
|
};
|
|
835
865
|
|
|
836
866
|
const callTool: CallTool = async ({ toolCallId, toolCallInput }) => {
|