@decocms/runtime 1.6.0 → 1.6.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools.ts +53 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/runtime",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",
package/src/tools.ts CHANGED
@@ -7,14 +7,16 @@ import {
7
7
  } from "@decocms/bindings";
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
9
  import { WebStandardStreamableHTTPServerTransport as HttpServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
10
- import type {
11
- CallToolResult,
12
- GetPromptResult,
13
- Implementation,
14
- ToolAnnotations,
10
+ import {
11
+ ListToolsRequestSchema,
12
+ type CallToolResult,
13
+ type GetPromptResult,
14
+ type Implementation,
15
+ type ListToolsResult,
16
+ type ToolAnnotations,
15
17
  } from "@modelcontextprotocol/sdk/types.js";
16
18
  import { z } from "zod";
17
- import type { ZodRawShape, ZodSchema, ZodTypeAny } from "zod";
19
+ import type { ZodSchema, ZodTypeAny } from "zod";
18
20
  import { BindingRegistry, injectBindingSchemas } from "./bindings.ts";
19
21
  import { Event, type EventHandlers } from "./events.ts";
20
22
  import type { DefaultEnv, User } from "./index.ts";
@@ -823,6 +825,12 @@ export const createMCPServer = <
823
825
  let cached: Registrations | null = null;
824
826
  let inflightResolve: Promise<Registrations> | null = null;
825
827
 
828
+ // The MCP SDK's `tools/list` handler runs `toJsonSchemaCompat()` for every
829
+ // registered tool on every request. For MCPs with hundreds of tools that
830
+ // dominates per-request latency (seconds, not ms). Cache the rendered
831
+ // payload across requests within the isolate.
832
+ let cachedListToolsResult: ListToolsResult | null = null;
833
+
826
834
  let _warnedFactoryDeprecation = false;
827
835
  const warnFactoryDeprecation = () => {
828
836
  if (!_warnedFactoryDeprecation) {
@@ -940,15 +948,19 @@ export const createMCPServer = <
940
948
  _meta: tool._meta,
941
949
  description: tool.description,
942
950
  annotations: tool.annotations,
951
+ // Pass the full ZodObject (not its `.shape`) so the SDK skips
952
+ // `objectFromShape(...)` (a fresh `z.object(shape)` per tool) inside
953
+ // `_createRegisteredTool`. The SDK's `getZodSchemaObject` returns
954
+ // an already-built object as-is.
943
955
  inputSchema:
944
956
  tool.inputSchema && "shape" in tool.inputSchema
945
- ? (tool.inputSchema.shape as ZodRawShape)
946
- : z.object({}).shape,
957
+ ? (tool.inputSchema as ZodTypeAny)
958
+ : z.object({}),
947
959
  outputSchema:
948
960
  tool.outputSchema &&
949
961
  typeof tool.outputSchema === "object" &&
950
962
  "shape" in tool.outputSchema
951
- ? (tool.outputSchema.shape as ZodRawShape)
963
+ ? (tool.outputSchema as ZodTypeAny)
952
964
  : undefined,
953
965
  },
954
966
  async (args) => {
@@ -1078,6 +1090,38 @@ export const createMCPServer = <
1078
1090
  const registrations = await resolveRegistrations(bindings);
1079
1091
  registerAll(server, registrations);
1080
1092
 
1093
+ // Wrap the SDK-installed `tools/list` handler so the rendered payload is
1094
+ // computed once per isolate and reused across requests. The MCP Server
1095
+ // itself can't be shared across requests (its transport is single-use, see
1096
+ // `Protocol.connect`), so each request still spins up a fresh Server +
1097
+ // Transport — but the listTools render is by far the dominant cost for
1098
+ // large tool surfaces, and it's pure of request-scoped state.
1099
+ const innerHandlers = (
1100
+ server.server as unknown as {
1101
+ _requestHandlers: Map<
1102
+ string,
1103
+ (req: unknown, extra: unknown) => Promise<unknown>
1104
+ >;
1105
+ }
1106
+ )._requestHandlers;
1107
+ const sdkListToolsHandler = innerHandlers.get(
1108
+ ListToolsRequestSchema.shape.method.value,
1109
+ );
1110
+ if (sdkListToolsHandler) {
1111
+ innerHandlers.set(
1112
+ ListToolsRequestSchema.shape.method.value,
1113
+ async (req, extra) => {
1114
+ if (!cachedListToolsResult) {
1115
+ cachedListToolsResult = (await sdkListToolsHandler(
1116
+ req,
1117
+ extra,
1118
+ )) as ListToolsResult;
1119
+ }
1120
+ return cachedListToolsResult;
1121
+ },
1122
+ );
1123
+ }
1124
+
1081
1125
  return { server, ...registrations };
1082
1126
  };
1083
1127