trellis 3.2.3 → 3.2.4

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 (194) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +4 -0
  3. package/dist/better-sqlite-backend-CDYOAJDF.js +209 -0
  4. package/dist/boot-middleware-LBJX4N2S.js +9 -0
  5. package/dist/boot-middleware-PATBLD52.js +9 -0
  6. package/dist/bridge-G5DPW6TY.js +2361 -0
  7. package/dist/bridge-JSSPQPRH.js +2389 -0
  8. package/dist/bridge-LNXE4HAW.js +2389 -0
  9. package/dist/bridge-OAAXJWSA.js +2389 -0
  10. package/dist/bridge-SFS63RHC.js +2367 -0
  11. package/dist/bridge-U5TOUBVM.js +2389 -0
  12. package/dist/browser/index.js +12 -12
  13. package/dist/{chunk-KSU2GW22.js → chunk-276W52FK.js} +8 -0
  14. package/dist/{chunk-KFQGP6VL.js → chunk-2ESYSVXG.js} +15 -0
  15. package/dist/{chunk-L5N5PR2S.js → chunk-2WW2X2BR.js} +313 -77
  16. package/dist/chunk-3XCOAP6G.js +48 -0
  17. package/dist/chunk-53PDCTJ3.js +1856 -0
  18. package/dist/{chunk-N6MYZT4O.js → chunk-547FZFKO.js} +1 -1
  19. package/dist/{chunk-7DUMWO5L.js → chunk-7WVOPXJV.js} +3 -3
  20. package/dist/chunk-CMSFD6OV.js +11801 -0
  21. package/dist/{chunk-RSWUFTO2.js → chunk-CPUYCCLT.js} +2 -2
  22. package/dist/{chunk-4HYPLFJD.js → chunk-FA5TFVA5.js} +1 -1
  23. package/dist/{chunk-4XIVMVHC.js → chunk-G3XIHPSQ.js} +1 -1
  24. package/dist/{chunk-JHHEXEDM.js → chunk-GCQF75T4.js} +3 -3
  25. package/dist/{chunk-FKQO5A3H.js → chunk-GHG4H3AS.js} +2 -2
  26. package/dist/{chunk-GLM4HGN4.js → chunk-GRWQPKYK.js} +1 -1
  27. package/dist/{chunk-PZ4XYZ4J.js → chunk-GXA73CNC.js} +33 -8
  28. package/dist/{chunk-6GEZ6DN5.js → chunk-H6VTC5SS.js} +3 -3
  29. package/dist/{chunk-S3XLFZ27.js → chunk-IHHSOBJS.js} +1 -1
  30. package/dist/chunk-IJNC25UD.js +2243 -0
  31. package/dist/{chunk-VDAKEOML.js → chunk-J6OUIION.js} +1 -1
  32. package/dist/chunk-JAVAYMMS.js +6 -0
  33. package/dist/{chunk-S6GBJ2ZB.js → chunk-JCQQFLO5.js} +1 -1
  34. package/dist/{chunk-FWRS4CSC.js → chunk-KQG6ZYXP.js} +3 -3
  35. package/dist/chunk-KXAISKZT.js +119 -0
  36. package/dist/chunk-N3R74ZSR.js +7949 -0
  37. package/dist/{chunk-GU374KWZ.js → chunk-NLFRRGX6.js} +3 -3
  38. package/dist/{chunk-XMQ7G77X.js → chunk-OB4CMMX6.js} +1 -1
  39. package/dist/chunk-OKNASCZY.js +182 -0
  40. package/dist/chunk-ONOPPX5E.js +394 -0
  41. package/dist/chunk-QMSU2RAH.js +98 -0
  42. package/dist/{chunk-TCKXSOBP.js → chunk-RARKFVWU.js} +8 -1
  43. package/dist/{chunk-2AACZRII.js → chunk-SJWVDS5M.js} +751 -162
  44. package/dist/{chunk-SPSPV5BC.js → chunk-SZ3VAB5P.js} +159 -2
  45. package/dist/chunk-V2ASD6RQ.js +147 -0
  46. package/dist/{chunk-KQ4A2D2P.js → chunk-V4YCJDYL.js} +58 -38
  47. package/dist/{chunk-LNUIGRDZ.js → chunk-VWKLKLYB.js} +1 -1
  48. package/dist/chunk-W5PADCDF.js +1856 -0
  49. package/dist/{chunk-MLC5BUYO.js → chunk-WMERMSWI.js} +1 -1
  50. package/dist/chunk-WXAURJUD.js +1948 -0
  51. package/dist/chunk-YC5I32PS.js +147 -0
  52. package/dist/{chunk-VBYUTRXX.js → chunk-YKVB4LUQ.js} +3 -3
  53. package/dist/{chunk-NWSW5YNS.js → chunk-YPT6XMS4.js} +20 -50
  54. package/dist/cli/deploy-cli.d.ts.map +1 -1
  55. package/dist/cli/deploy-cli.js +43 -4
  56. package/dist/cli/gateway-deploy-cli.d.ts +19 -0
  57. package/dist/cli/gateway-deploy-cli.d.ts.map +1 -0
  58. package/dist/cli/index.d.ts.map +1 -1
  59. package/dist/cli/index.js +1360 -881
  60. package/dist/cli/lane.d.ts.map +1 -1
  61. package/dist/cli/protocol.d.ts +6 -0
  62. package/dist/cli/protocol.d.ts.map +1 -0
  63. package/dist/client/index.browser.js +2 -2
  64. package/dist/client/index.js +11 -11
  65. package/dist/client/sdk.browser.js +1 -1
  66. package/dist/client/sdk.js +2 -2
  67. package/dist/cms/index.js +1 -1
  68. package/dist/config-KMY6RRK3.js +19 -0
  69. package/dist/core/index.js +13 -13
  70. package/dist/core/kernel/boot-middleware.d.ts +1 -1
  71. package/dist/core/kernel/boot-middleware.d.ts.map +1 -1
  72. package/dist/core/kernel/schema-middleware.d.ts +2 -1
  73. package/dist/core/kernel/schema-middleware.d.ts.map +1 -1
  74. package/dist/core/kernel/trellis-kernel.d.ts +10 -10
  75. package/dist/core/kernel/trellis-kernel.d.ts.map +1 -1
  76. package/dist/core/persist/factory.js +2 -2
  77. package/dist/core/persist/sqljs-backend.js +2 -2
  78. package/dist/db/index.js +30 -25
  79. package/dist/decisions/index.js +4 -4
  80. package/dist/embeddings/index.js +2 -2
  81. package/dist/engine.d.ts +24 -6
  82. package/dist/engine.d.ts.map +1 -1
  83. package/dist/gateway-deploy-cli-4TU4V3QI.js +312 -0
  84. package/dist/gateway-deploy-cli-A4ISTDHJ.js +298 -0
  85. package/dist/gateway-deploy-cli-D3XTKJQJ.js +312 -0
  86. package/dist/gateway-deploy-cli-ECV3OQCN.js +305 -0
  87. package/dist/gateway-deploy-cli-JUHKVIJE.js +296 -0
  88. package/dist/gateway-deploy-cli-NSDW2JLT.js +310 -0
  89. package/dist/gateway-serve-CM5EAZ52.js +113 -0
  90. package/dist/import-AJLYHFIA.js +10 -0
  91. package/dist/index.js +11 -11
  92. package/dist/launch-explorer-FVX6ZABW.js +95 -0
  93. package/dist/links/index.js +2 -2
  94. package/dist/mcp/bridge.d.ts +45 -0
  95. package/dist/mcp/bridge.d.ts.map +1 -0
  96. package/dist/mcp/discovery.d.ts +15 -0
  97. package/dist/mcp/discovery.d.ts.map +1 -0
  98. package/dist/mcp/gateway-config.d.ts +20 -0
  99. package/dist/mcp/gateway-config.d.ts.map +1 -0
  100. package/dist/mcp/gateway-serve.d.ts +19 -0
  101. package/dist/mcp/gateway-serve.d.ts.map +1 -0
  102. package/dist/mcp/gateway-serve.js +119 -0
  103. package/dist/mcp/graph-summary.d.ts +47 -0
  104. package/dist/mcp/graph-summary.d.ts.map +1 -0
  105. package/dist/mcp/room-audit.d.ts +14 -0
  106. package/dist/mcp/room-audit.d.ts.map +1 -0
  107. package/dist/mcp/room-helpers.d.ts +43 -0
  108. package/dist/mcp/room-helpers.d.ts.map +1 -0
  109. package/dist/mcp/room-registry.d.ts +32 -0
  110. package/dist/mcp/room-registry.d.ts.map +1 -0
  111. package/dist/mcp/room.d.ts +28 -0
  112. package/dist/mcp/room.d.ts.map +1 -0
  113. package/dist/plugins/agent-memory/index.js +1 -1
  114. package/dist/plugins/idea-garden/index.js +1 -1
  115. package/dist/plugins/plan-approval/index.js +1 -1
  116. package/dist/plugins/proactive-watcher/index.js +1 -1
  117. package/dist/protocol/envelope.d.ts +30 -0
  118. package/dist/protocol/envelope.d.ts.map +1 -0
  119. package/dist/protocol/index.d.ts +3 -0
  120. package/dist/protocol/index.d.ts.map +1 -0
  121. package/dist/protocol/whereami.d.ts +33 -0
  122. package/dist/protocol/whereami.d.ts.map +1 -0
  123. package/dist/query-KHDDIR5G.js +31 -0
  124. package/dist/react/index.js +1 -1
  125. package/dist/react/realtime.js +1 -1
  126. package/dist/react/schema-hooks.js +5 -5
  127. package/dist/realtime/index.js +3 -3
  128. package/dist/realtime/relay-server.d.ts.map +1 -1
  129. package/dist/realtime/room.d.ts.map +1 -1
  130. package/dist/realtime.bundle.js +2 -0
  131. package/dist/relay-server-BT6DCJ7F.js +12 -0
  132. package/dist/relay-server-MPQVGNQU.js +12 -0
  133. package/dist/relay-server-PAPW2EWC.js +12 -0
  134. package/dist/remote-manager-6ERQUBSL.js +224 -0
  135. package/dist/remote-manager-G665WWYY.js +224 -0
  136. package/dist/remote-manager-HJTEZ4OF.js +224 -0
  137. package/dist/remote-manager-NYP2Y5UR.js +224 -0
  138. package/dist/remote-manager-WGLCAMMC.js +224 -0
  139. package/dist/scaffold/write.d.ts.map +1 -1
  140. package/dist/schema/index.js +1 -1
  141. package/dist/server/cors.d.ts.map +1 -1
  142. package/dist/server/deploy-gateway.d.ts +27 -0
  143. package/dist/server/deploy-gateway.d.ts.map +1 -0
  144. package/dist/server/deploy.d.ts.map +1 -1
  145. package/dist/server/deploy.js +3 -2
  146. package/dist/server/discovery-mcp-gateway.d.ts +13 -0
  147. package/dist/server/discovery-mcp-gateway.d.ts.map +1 -0
  148. package/dist/server/index.js +38 -31
  149. package/dist/server/mcp-gateway.d.ts +27 -0
  150. package/dist/server/mcp-gateway.d.ts.map +1 -0
  151. package/dist/server/mcp-oauth-metadata.d.ts +31 -0
  152. package/dist/server/mcp-oauth-metadata.d.ts.map +1 -0
  153. package/dist/server/node-adapter.d.ts +7 -0
  154. package/dist/server/node-adapter.d.ts.map +1 -1
  155. package/dist/server/node-adapter.js +2 -2
  156. package/dist/server/realtime.d.ts.map +1 -1
  157. package/dist/server/server.d.ts +8 -0
  158. package/dist/server/server.d.ts.map +1 -1
  159. package/dist/server/streamable-mcp-gateway.d.ts +12 -0
  160. package/dist/server/streamable-mcp-gateway.d.ts.map +1 -0
  161. package/dist/server/usage-meter.d.ts +11 -0
  162. package/dist/server/usage-meter.d.ts.map +1 -1
  163. package/dist/server-4VEZHXXS.js +23 -0
  164. package/dist/{server-OGVWNDNK.js → server-556BMK4C.js} +1 -1
  165. package/dist/server-FQT2VYRE.js +23 -0
  166. package/dist/server-HGZE4ECC.js +23 -0
  167. package/dist/server-J4JKEAMH.js +23 -0
  168. package/dist/server-LXPM5TFR.js +23 -0
  169. package/dist/server-POB4NEUX.js +18 -0
  170. package/dist/server-VJKAPGPG.js +23 -0
  171. package/dist/server-YHS3XIIG.js +23 -0
  172. package/dist/server-ZHTPWPXS.js +23 -0
  173. package/dist/sprites-NC6D7YZS.js +27 -0
  174. package/dist/svelte/index.js +3 -3
  175. package/dist/svelte/schema-hooks.js +4 -4
  176. package/dist/sync/index.js +4 -4
  177. package/dist/tenancy-2SZTP4UD.js +16 -0
  178. package/dist/tenancy-7MTSESMJ.js +16 -0
  179. package/dist/tenancy-U4E7ZEAG.js +16 -0
  180. package/dist/vcs/decompose.d.ts.map +1 -1
  181. package/dist/vcs/index.js +5 -5
  182. package/dist/vcs/lane-disk-materialize.d.ts +11 -0
  183. package/dist/vcs/lane-disk-materialize.d.ts.map +1 -0
  184. package/dist/vcs/lane-worktree.d.ts +21 -0
  185. package/dist/vcs/lane-worktree.d.ts.map +1 -0
  186. package/dist/vcs/store.d.ts +28 -0
  187. package/dist/vcs/store.d.ts.map +1 -0
  188. package/dist/vcs/types.d.ts +5 -0
  189. package/dist/vcs/types.d.ts.map +1 -1
  190. package/dist/vm-config-YZRJ3KUB.js +21 -0
  191. package/dist/vue/index.js +3 -3
  192. package/dist/vue/schema-hooks.js +4 -4
  193. package/package.json +4 -3
  194. package/dist/chunk-GFZCJ4EH.js +0 -108
@@ -0,0 +1,1856 @@
1
+ import {
2
+ CallToolRequestSchema,
3
+ CompleteRequestSchema,
4
+ DEFAULT_NEGOTIATED_PROTOCOL_VERSION,
5
+ ErrorCode,
6
+ GetPromptRequestSchema,
7
+ JSONRPCMessageSchema,
8
+ ListPromptsRequestSchema,
9
+ ListResourceTemplatesRequestSchema,
10
+ ListResourcesRequestSchema,
11
+ ListToolsRequestSchema,
12
+ McpError,
13
+ ReadResourceRequestSchema,
14
+ SUPPORTED_PROTOCOL_VERSIONS,
15
+ Server,
16
+ assertCompleteRequestPrompt,
17
+ assertCompleteRequestResourceTemplate,
18
+ getLiteralValue,
19
+ getObjectShape,
20
+ getParseErrorMessage,
21
+ getSchemaDescription,
22
+ isInitializeRequest,
23
+ isJSONRPCErrorResponse,
24
+ isJSONRPCRequest,
25
+ isJSONRPCResultResponse,
26
+ isSchemaOptional,
27
+ normalizeObjectSchema,
28
+ objectFromShape,
29
+ playgroundRoomToTenant,
30
+ safeParseAsync,
31
+ toJsonSchemaCompat
32
+ } from "./chunk-CMSFD6OV.js";
33
+ import {
34
+ gatewayPublicUrl,
35
+ getRegisteredRoom,
36
+ listRegisteredRooms
37
+ } from "./chunk-V2ASD6RQ.js";
38
+
39
+ // src/server/cors.ts
40
+ var DEFAULT_METHODS = "GET, POST, PUT, DELETE, OPTIONS";
41
+ var DEFAULT_HEADERS = "Content-Type, Authorization, mcp-session-id, mcp-protocol-version, Last-Event-ID, Accept, X-Trellis-Lane, X-Trellis-Tenant";
42
+ function corsEnabledForConfig(apiKey) {
43
+ return Boolean(process.env.TRELLIS_CORS_ORIGINS) || Boolean(apiKey);
44
+ }
45
+ function allowedOrigins() {
46
+ const raw = process.env.TRELLIS_CORS_ORIGINS?.trim();
47
+ if (!raw || raw === "*") return "*";
48
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
49
+ }
50
+ function corsHeaders(req) {
51
+ const origins = allowedOrigins();
52
+ const requestOrigin = req.headers.get("Origin");
53
+ let allowOrigin = "*";
54
+ if (origins !== "*") {
55
+ if (requestOrigin && origins.includes(requestOrigin)) {
56
+ allowOrigin = requestOrigin;
57
+ } else if (origins.length === 1) {
58
+ allowOrigin = origins[0];
59
+ } else {
60
+ allowOrigin = origins[0] ?? "*";
61
+ }
62
+ }
63
+ return {
64
+ "Access-Control-Allow-Origin": allowOrigin,
65
+ "Access-Control-Allow-Methods": DEFAULT_METHODS,
66
+ "Access-Control-Allow-Headers": DEFAULT_HEADERS,
67
+ Vary: "Origin"
68
+ };
69
+ }
70
+ function withCors(req, res) {
71
+ const headers = new Headers(res.headers);
72
+ for (const [key, value] of Object.entries(corsHeaders(req))) {
73
+ headers.set(key, value);
74
+ }
75
+ return new Response(res.body, {
76
+ status: res.status,
77
+ statusText: res.statusText,
78
+ headers
79
+ });
80
+ }
81
+ function corsPreflightResponse(req) {
82
+ return new Response(null, {
83
+ status: 204,
84
+ headers: corsHeaders(req)
85
+ });
86
+ }
87
+
88
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
89
+ var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
90
+ function isCompletable(schema) {
91
+ return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
92
+ }
93
+ function getCompleter(schema) {
94
+ const meta = schema[COMPLETABLE_SYMBOL];
95
+ return meta?.complete;
96
+ }
97
+ var McpZodTypeKind;
98
+ (function(McpZodTypeKind2) {
99
+ McpZodTypeKind2["Completable"] = "McpCompletable";
100
+ })(McpZodTypeKind || (McpZodTypeKind = {}));
101
+
102
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/toolNameValidation.js
103
+ var TOOL_NAME_REGEX = /^[A-Za-z0-9._-]{1,128}$/;
104
+ function validateToolName(name) {
105
+ const warnings = [];
106
+ if (name.length === 0) {
107
+ return {
108
+ isValid: false,
109
+ warnings: ["Tool name cannot be empty"]
110
+ };
111
+ }
112
+ if (name.length > 128) {
113
+ return {
114
+ isValid: false,
115
+ warnings: [`Tool name exceeds maximum length of 128 characters (current: ${name.length})`]
116
+ };
117
+ }
118
+ if (name.includes(" ")) {
119
+ warnings.push("Tool name contains spaces, which may cause parsing issues");
120
+ }
121
+ if (name.includes(",")) {
122
+ warnings.push("Tool name contains commas, which may cause parsing issues");
123
+ }
124
+ if (name.startsWith("-") || name.endsWith("-")) {
125
+ warnings.push("Tool name starts or ends with a dash, which may cause parsing issues in some contexts");
126
+ }
127
+ if (name.startsWith(".") || name.endsWith(".")) {
128
+ warnings.push("Tool name starts or ends with a dot, which may cause parsing issues in some contexts");
129
+ }
130
+ if (!TOOL_NAME_REGEX.test(name)) {
131
+ const invalidChars = name.split("").filter((char) => !/[A-Za-z0-9._-]/.test(char)).filter((char, index, arr) => arr.indexOf(char) === index);
132
+ warnings.push(`Tool name contains invalid characters: ${invalidChars.map((c) => `"${c}"`).join(", ")}`, "Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)");
133
+ return {
134
+ isValid: false,
135
+ warnings
136
+ };
137
+ }
138
+ return {
139
+ isValid: true,
140
+ warnings
141
+ };
142
+ }
143
+ function issueToolNameWarning(name, warnings) {
144
+ if (warnings.length > 0) {
145
+ console.warn(`Tool name validation warning for "${name}":`);
146
+ for (const warning of warnings) {
147
+ console.warn(` - ${warning}`);
148
+ }
149
+ console.warn("Tool registration will proceed, but this may cause compatibility issues.");
150
+ console.warn("Consider updating the tool name to conform to the MCP tool naming standard.");
151
+ console.warn("See SEP: Specify Format for Tool Names (https://github.com/modelcontextprotocol/modelcontextprotocol/issues/986) for more details.");
152
+ }
153
+ }
154
+ function validateAndWarnToolName(name) {
155
+ const result = validateToolName(name);
156
+ issueToolNameWarning(name, result.warnings);
157
+ return result.isValid;
158
+ }
159
+
160
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/mcp-server.js
161
+ var ExperimentalMcpServerTasks = class {
162
+ constructor(_mcpServer) {
163
+ this._mcpServer = _mcpServer;
164
+ }
165
+ registerToolTask(name, config, handler) {
166
+ const execution = { taskSupport: "required", ...config.execution };
167
+ if (execution.taskSupport === "forbidden") {
168
+ throw new Error(`Cannot register task-based tool '${name}' with taskSupport 'forbidden'. Use registerTool() instead.`);
169
+ }
170
+ const mcpServerInternal = this._mcpServer;
171
+ return mcpServerInternal._createRegisteredTool(name, config.title, config.description, config.inputSchema, config.outputSchema, config.annotations, execution, config._meta, handler);
172
+ }
173
+ };
174
+
175
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
176
+ import { ZodOptional } from "zod";
177
+ var McpServer = class {
178
+ constructor(serverInfo, options) {
179
+ this._registeredResources = {};
180
+ this._registeredResourceTemplates = {};
181
+ this._registeredTools = {};
182
+ this._registeredPrompts = {};
183
+ this._toolHandlersInitialized = false;
184
+ this._completionHandlerInitialized = false;
185
+ this._resourceHandlersInitialized = false;
186
+ this._promptHandlersInitialized = false;
187
+ this.server = new Server(serverInfo, options);
188
+ }
189
+ /**
190
+ * Access experimental features.
191
+ *
192
+ * WARNING: These APIs are experimental and may change without notice.
193
+ *
194
+ * @experimental
195
+ */
196
+ get experimental() {
197
+ if (!this._experimental) {
198
+ this._experimental = {
199
+ tasks: new ExperimentalMcpServerTasks(this)
200
+ };
201
+ }
202
+ return this._experimental;
203
+ }
204
+ /**
205
+ * Attaches to the given transport, starts it, and starts listening for messages.
206
+ *
207
+ * The `server` object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
208
+ */
209
+ async connect(transport) {
210
+ return await this.server.connect(transport);
211
+ }
212
+ /**
213
+ * Closes the connection.
214
+ */
215
+ async close() {
216
+ await this.server.close();
217
+ }
218
+ setToolRequestHandlers() {
219
+ if (this._toolHandlersInitialized) {
220
+ return;
221
+ }
222
+ this.server.assertCanSetRequestHandler(getMethodValue(ListToolsRequestSchema));
223
+ this.server.assertCanSetRequestHandler(getMethodValue(CallToolRequestSchema));
224
+ this.server.registerCapabilities({
225
+ tools: {
226
+ listChanged: true
227
+ }
228
+ });
229
+ this.server.setRequestHandler(ListToolsRequestSchema, () => ({
230
+ tools: Object.entries(this._registeredTools).filter(([, tool]) => tool.enabled).map(([name, tool]) => {
231
+ const toolDefinition = {
232
+ name,
233
+ title: tool.title,
234
+ description: tool.description,
235
+ inputSchema: (() => {
236
+ const obj = normalizeObjectSchema(tool.inputSchema);
237
+ return obj ? toJsonSchemaCompat(obj, {
238
+ strictUnions: true,
239
+ pipeStrategy: "input"
240
+ }) : EMPTY_OBJECT_JSON_SCHEMA;
241
+ })(),
242
+ annotations: tool.annotations,
243
+ execution: tool.execution,
244
+ _meta: tool._meta
245
+ };
246
+ if (tool.outputSchema) {
247
+ const obj = normalizeObjectSchema(tool.outputSchema);
248
+ if (obj) {
249
+ toolDefinition.outputSchema = toJsonSchemaCompat(obj, {
250
+ strictUnions: true,
251
+ pipeStrategy: "output"
252
+ });
253
+ }
254
+ }
255
+ return toolDefinition;
256
+ })
257
+ }));
258
+ this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
259
+ try {
260
+ const tool = this._registeredTools[request.params.name];
261
+ if (!tool) {
262
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
263
+ }
264
+ if (!tool.enabled) {
265
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} disabled`);
266
+ }
267
+ const isTaskRequest = !!request.params.task;
268
+ const taskSupport = tool.execution?.taskSupport;
269
+ const isTaskHandler = "createTask" in tool.handler;
270
+ if ((taskSupport === "required" || taskSupport === "optional") && !isTaskHandler) {
271
+ throw new McpError(ErrorCode.InternalError, `Tool ${request.params.name} has taskSupport '${taskSupport}' but was not registered with registerToolTask`);
272
+ }
273
+ if (taskSupport === "required" && !isTaskRequest) {
274
+ throw new McpError(ErrorCode.MethodNotFound, `Tool ${request.params.name} requires task augmentation (taskSupport: 'required')`);
275
+ }
276
+ if (taskSupport === "optional" && !isTaskRequest && isTaskHandler) {
277
+ return await this.handleAutomaticTaskPolling(tool, request, extra);
278
+ }
279
+ const args = await this.validateToolInput(tool, request.params.arguments, request.params.name);
280
+ const result = await this.executeToolHandler(tool, args, extra);
281
+ if (isTaskRequest) {
282
+ return result;
283
+ }
284
+ await this.validateToolOutput(tool, result, request.params.name);
285
+ return result;
286
+ } catch (error) {
287
+ if (error instanceof McpError) {
288
+ if (error.code === ErrorCode.UrlElicitationRequired) {
289
+ throw error;
290
+ }
291
+ }
292
+ return this.createToolError(error instanceof Error ? error.message : String(error));
293
+ }
294
+ });
295
+ this._toolHandlersInitialized = true;
296
+ }
297
+ /**
298
+ * Creates a tool error result.
299
+ *
300
+ * @param errorMessage - The error message.
301
+ * @returns The tool error result.
302
+ */
303
+ createToolError(errorMessage) {
304
+ return {
305
+ content: [
306
+ {
307
+ type: "text",
308
+ text: errorMessage
309
+ }
310
+ ],
311
+ isError: true
312
+ };
313
+ }
314
+ /**
315
+ * Validates tool input arguments against the tool's input schema.
316
+ */
317
+ async validateToolInput(tool, args, toolName) {
318
+ if (!tool.inputSchema) {
319
+ return void 0;
320
+ }
321
+ const inputObj = normalizeObjectSchema(tool.inputSchema);
322
+ const schemaToParse = inputObj ?? tool.inputSchema;
323
+ const parseResult = await safeParseAsync(schemaToParse, args);
324
+ if (!parseResult.success) {
325
+ const error = "error" in parseResult ? parseResult.error : "Unknown error";
326
+ const errorMessage = getParseErrorMessage(error);
327
+ throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage}`);
328
+ }
329
+ return parseResult.data;
330
+ }
331
+ /**
332
+ * Validates tool output against the tool's output schema.
333
+ */
334
+ async validateToolOutput(tool, result, toolName) {
335
+ if (!tool.outputSchema) {
336
+ return;
337
+ }
338
+ if (!("content" in result)) {
339
+ return;
340
+ }
341
+ if (result.isError) {
342
+ return;
343
+ }
344
+ if (!result.structuredContent) {
345
+ throw new McpError(ErrorCode.InvalidParams, `Output validation error: Tool ${toolName} has an output schema but no structured content was provided`);
346
+ }
347
+ const outputObj = normalizeObjectSchema(tool.outputSchema);
348
+ const parseResult = await safeParseAsync(outputObj, result.structuredContent);
349
+ if (!parseResult.success) {
350
+ const error = "error" in parseResult ? parseResult.error : "Unknown error";
351
+ const errorMessage = getParseErrorMessage(error);
352
+ throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage}`);
353
+ }
354
+ }
355
+ /**
356
+ * Executes a tool handler (either regular or task-based).
357
+ */
358
+ async executeToolHandler(tool, args, extra) {
359
+ const handler = tool.handler;
360
+ const isTaskHandler = "createTask" in handler;
361
+ if (isTaskHandler) {
362
+ if (!extra.taskStore) {
363
+ throw new Error("No task store provided.");
364
+ }
365
+ const taskExtra = { ...extra, taskStore: extra.taskStore };
366
+ if (tool.inputSchema) {
367
+ const typedHandler = handler;
368
+ return await Promise.resolve(typedHandler.createTask(args, taskExtra));
369
+ } else {
370
+ const typedHandler = handler;
371
+ return await Promise.resolve(typedHandler.createTask(taskExtra));
372
+ }
373
+ }
374
+ if (tool.inputSchema) {
375
+ const typedHandler = handler;
376
+ return await Promise.resolve(typedHandler(args, extra));
377
+ } else {
378
+ const typedHandler = handler;
379
+ return await Promise.resolve(typedHandler(extra));
380
+ }
381
+ }
382
+ /**
383
+ * Handles automatic task polling for tools with taskSupport 'optional'.
384
+ */
385
+ async handleAutomaticTaskPolling(tool, request, extra) {
386
+ if (!extra.taskStore) {
387
+ throw new Error("No task store provided for task-capable tool.");
388
+ }
389
+ const args = await this.validateToolInput(tool, request.params.arguments, request.params.name);
390
+ const handler = tool.handler;
391
+ const taskExtra = { ...extra, taskStore: extra.taskStore };
392
+ const createTaskResult = args ? await Promise.resolve(handler.createTask(args, taskExtra)) : (
393
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
394
+ await Promise.resolve(handler.createTask(taskExtra))
395
+ );
396
+ const taskId = createTaskResult.task.taskId;
397
+ let task = createTaskResult.task;
398
+ const pollInterval = task.pollInterval ?? 5e3;
399
+ while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
400
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
401
+ const updatedTask = await extra.taskStore.getTask(taskId);
402
+ if (!updatedTask) {
403
+ throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
404
+ }
405
+ task = updatedTask;
406
+ }
407
+ return await extra.taskStore.getTaskResult(taskId);
408
+ }
409
+ setCompletionRequestHandler() {
410
+ if (this._completionHandlerInitialized) {
411
+ return;
412
+ }
413
+ this.server.assertCanSetRequestHandler(getMethodValue(CompleteRequestSchema));
414
+ this.server.registerCapabilities({
415
+ completions: {}
416
+ });
417
+ this.server.setRequestHandler(CompleteRequestSchema, async (request) => {
418
+ switch (request.params.ref.type) {
419
+ case "ref/prompt":
420
+ assertCompleteRequestPrompt(request);
421
+ return this.handlePromptCompletion(request, request.params.ref);
422
+ case "ref/resource":
423
+ assertCompleteRequestResourceTemplate(request);
424
+ return this.handleResourceCompletion(request, request.params.ref);
425
+ default:
426
+ throw new McpError(ErrorCode.InvalidParams, `Invalid completion reference: ${request.params.ref}`);
427
+ }
428
+ });
429
+ this._completionHandlerInitialized = true;
430
+ }
431
+ async handlePromptCompletion(request, ref) {
432
+ const prompt = this._registeredPrompts[ref.name];
433
+ if (!prompt) {
434
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${ref.name} not found`);
435
+ }
436
+ if (!prompt.enabled) {
437
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${ref.name} disabled`);
438
+ }
439
+ if (!prompt.argsSchema) {
440
+ return EMPTY_COMPLETION_RESULT;
441
+ }
442
+ const promptShape = getObjectShape(prompt.argsSchema);
443
+ const field = promptShape?.[request.params.argument.name];
444
+ if (!isCompletable(field)) {
445
+ return EMPTY_COMPLETION_RESULT;
446
+ }
447
+ const completer = getCompleter(field);
448
+ if (!completer) {
449
+ return EMPTY_COMPLETION_RESULT;
450
+ }
451
+ const suggestions = await completer(request.params.argument.value, request.params.context);
452
+ return createCompletionResult(suggestions);
453
+ }
454
+ async handleResourceCompletion(request, ref) {
455
+ const template = Object.values(this._registeredResourceTemplates).find((t) => t.resourceTemplate.uriTemplate.toString() === ref.uri);
456
+ if (!template) {
457
+ if (this._registeredResources[ref.uri]) {
458
+ return EMPTY_COMPLETION_RESULT;
459
+ }
460
+ throw new McpError(ErrorCode.InvalidParams, `Resource template ${request.params.ref.uri} not found`);
461
+ }
462
+ const completer = template.resourceTemplate.completeCallback(request.params.argument.name);
463
+ if (!completer) {
464
+ return EMPTY_COMPLETION_RESULT;
465
+ }
466
+ const suggestions = await completer(request.params.argument.value, request.params.context);
467
+ return createCompletionResult(suggestions);
468
+ }
469
+ setResourceRequestHandlers() {
470
+ if (this._resourceHandlersInitialized) {
471
+ return;
472
+ }
473
+ this.server.assertCanSetRequestHandler(getMethodValue(ListResourcesRequestSchema));
474
+ this.server.assertCanSetRequestHandler(getMethodValue(ListResourceTemplatesRequestSchema));
475
+ this.server.assertCanSetRequestHandler(getMethodValue(ReadResourceRequestSchema));
476
+ this.server.registerCapabilities({
477
+ resources: {
478
+ listChanged: true
479
+ }
480
+ });
481
+ this.server.setRequestHandler(ListResourcesRequestSchema, async (request, extra) => {
482
+ const resources = Object.entries(this._registeredResources).filter(([_, resource]) => resource.enabled).map(([uri, resource]) => ({
483
+ uri,
484
+ name: resource.name,
485
+ ...resource.metadata
486
+ }));
487
+ const templateResources = [];
488
+ for (const template of Object.values(this._registeredResourceTemplates)) {
489
+ if (!template.resourceTemplate.listCallback) {
490
+ continue;
491
+ }
492
+ const result = await template.resourceTemplate.listCallback(extra);
493
+ for (const resource of result.resources) {
494
+ templateResources.push({
495
+ ...template.metadata,
496
+ // the defined resource metadata should override the template metadata if present
497
+ ...resource
498
+ });
499
+ }
500
+ }
501
+ return { resources: [...resources, ...templateResources] };
502
+ });
503
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
504
+ const resourceTemplates = Object.entries(this._registeredResourceTemplates).map(([name, template]) => ({
505
+ name,
506
+ uriTemplate: template.resourceTemplate.uriTemplate.toString(),
507
+ ...template.metadata
508
+ }));
509
+ return { resourceTemplates };
510
+ });
511
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => {
512
+ const uri = new URL(request.params.uri);
513
+ const resource = this._registeredResources[uri.toString()];
514
+ if (resource) {
515
+ if (!resource.enabled) {
516
+ throw new McpError(ErrorCode.InvalidParams, `Resource ${uri} disabled`);
517
+ }
518
+ return resource.readCallback(uri, extra);
519
+ }
520
+ for (const template of Object.values(this._registeredResourceTemplates)) {
521
+ const variables = template.resourceTemplate.uriTemplate.match(uri.toString());
522
+ if (variables) {
523
+ return template.readCallback(uri, variables, extra);
524
+ }
525
+ }
526
+ throw new McpError(ErrorCode.InvalidParams, `Resource ${uri} not found`);
527
+ });
528
+ this._resourceHandlersInitialized = true;
529
+ }
530
+ setPromptRequestHandlers() {
531
+ if (this._promptHandlersInitialized) {
532
+ return;
533
+ }
534
+ this.server.assertCanSetRequestHandler(getMethodValue(ListPromptsRequestSchema));
535
+ this.server.assertCanSetRequestHandler(getMethodValue(GetPromptRequestSchema));
536
+ this.server.registerCapabilities({
537
+ prompts: {
538
+ listChanged: true
539
+ }
540
+ });
541
+ this.server.setRequestHandler(ListPromptsRequestSchema, () => ({
542
+ prompts: Object.entries(this._registeredPrompts).filter(([, prompt]) => prompt.enabled).map(([name, prompt]) => {
543
+ return {
544
+ name,
545
+ title: prompt.title,
546
+ description: prompt.description,
547
+ arguments: prompt.argsSchema ? promptArgumentsFromSchema(prompt.argsSchema) : void 0
548
+ };
549
+ })
550
+ }));
551
+ this.server.setRequestHandler(GetPromptRequestSchema, async (request, extra) => {
552
+ const prompt = this._registeredPrompts[request.params.name];
553
+ if (!prompt) {
554
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${request.params.name} not found`);
555
+ }
556
+ if (!prompt.enabled) {
557
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${request.params.name} disabled`);
558
+ }
559
+ if (prompt.argsSchema) {
560
+ const argsObj = normalizeObjectSchema(prompt.argsSchema);
561
+ const parseResult = await safeParseAsync(argsObj, request.params.arguments);
562
+ if (!parseResult.success) {
563
+ const error = "error" in parseResult ? parseResult.error : "Unknown error";
564
+ const errorMessage = getParseErrorMessage(error);
565
+ throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${errorMessage}`);
566
+ }
567
+ const args = parseResult.data;
568
+ const cb = prompt.callback;
569
+ return await Promise.resolve(cb(args, extra));
570
+ } else {
571
+ const cb = prompt.callback;
572
+ return await Promise.resolve(cb(extra));
573
+ }
574
+ });
575
+ this._promptHandlersInitialized = true;
576
+ }
577
+ resource(name, uriOrTemplate, ...rest) {
578
+ let metadata;
579
+ if (typeof rest[0] === "object") {
580
+ metadata = rest.shift();
581
+ }
582
+ const readCallback = rest[0];
583
+ if (typeof uriOrTemplate === "string") {
584
+ if (this._registeredResources[uriOrTemplate]) {
585
+ throw new Error(`Resource ${uriOrTemplate} is already registered`);
586
+ }
587
+ const registeredResource = this._createRegisteredResource(name, void 0, uriOrTemplate, metadata, readCallback);
588
+ this.setResourceRequestHandlers();
589
+ this.sendResourceListChanged();
590
+ return registeredResource;
591
+ } else {
592
+ if (this._registeredResourceTemplates[name]) {
593
+ throw new Error(`Resource template ${name} is already registered`);
594
+ }
595
+ const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, void 0, uriOrTemplate, metadata, readCallback);
596
+ this.setResourceRequestHandlers();
597
+ this.sendResourceListChanged();
598
+ return registeredResourceTemplate;
599
+ }
600
+ }
601
+ registerResource(name, uriOrTemplate, config, readCallback) {
602
+ if (typeof uriOrTemplate === "string") {
603
+ if (this._registeredResources[uriOrTemplate]) {
604
+ throw new Error(`Resource ${uriOrTemplate} is already registered`);
605
+ }
606
+ const registeredResource = this._createRegisteredResource(name, config.title, uriOrTemplate, config, readCallback);
607
+ this.setResourceRequestHandlers();
608
+ this.sendResourceListChanged();
609
+ return registeredResource;
610
+ } else {
611
+ if (this._registeredResourceTemplates[name]) {
612
+ throw new Error(`Resource template ${name} is already registered`);
613
+ }
614
+ const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config.title, uriOrTemplate, config, readCallback);
615
+ this.setResourceRequestHandlers();
616
+ this.sendResourceListChanged();
617
+ return registeredResourceTemplate;
618
+ }
619
+ }
620
+ _createRegisteredResource(name, title, uri, metadata, readCallback) {
621
+ const registeredResource = {
622
+ name,
623
+ title,
624
+ metadata,
625
+ readCallback,
626
+ enabled: true,
627
+ disable: () => registeredResource.update({ enabled: false }),
628
+ enable: () => registeredResource.update({ enabled: true }),
629
+ remove: () => registeredResource.update({ uri: null }),
630
+ update: (updates) => {
631
+ if (typeof updates.uri !== "undefined" && updates.uri !== uri) {
632
+ delete this._registeredResources[uri];
633
+ if (updates.uri)
634
+ this._registeredResources[updates.uri] = registeredResource;
635
+ }
636
+ if (typeof updates.name !== "undefined")
637
+ registeredResource.name = updates.name;
638
+ if (typeof updates.title !== "undefined")
639
+ registeredResource.title = updates.title;
640
+ if (typeof updates.metadata !== "undefined")
641
+ registeredResource.metadata = updates.metadata;
642
+ if (typeof updates.callback !== "undefined")
643
+ registeredResource.readCallback = updates.callback;
644
+ if (typeof updates.enabled !== "undefined")
645
+ registeredResource.enabled = updates.enabled;
646
+ this.sendResourceListChanged();
647
+ }
648
+ };
649
+ this._registeredResources[uri] = registeredResource;
650
+ return registeredResource;
651
+ }
652
+ _createRegisteredResourceTemplate(name, title, template, metadata, readCallback) {
653
+ const registeredResourceTemplate = {
654
+ resourceTemplate: template,
655
+ title,
656
+ metadata,
657
+ readCallback,
658
+ enabled: true,
659
+ disable: () => registeredResourceTemplate.update({ enabled: false }),
660
+ enable: () => registeredResourceTemplate.update({ enabled: true }),
661
+ remove: () => registeredResourceTemplate.update({ name: null }),
662
+ update: (updates) => {
663
+ if (typeof updates.name !== "undefined" && updates.name !== name) {
664
+ delete this._registeredResourceTemplates[name];
665
+ if (updates.name)
666
+ this._registeredResourceTemplates[updates.name] = registeredResourceTemplate;
667
+ }
668
+ if (typeof updates.title !== "undefined")
669
+ registeredResourceTemplate.title = updates.title;
670
+ if (typeof updates.template !== "undefined")
671
+ registeredResourceTemplate.resourceTemplate = updates.template;
672
+ if (typeof updates.metadata !== "undefined")
673
+ registeredResourceTemplate.metadata = updates.metadata;
674
+ if (typeof updates.callback !== "undefined")
675
+ registeredResourceTemplate.readCallback = updates.callback;
676
+ if (typeof updates.enabled !== "undefined")
677
+ registeredResourceTemplate.enabled = updates.enabled;
678
+ this.sendResourceListChanged();
679
+ }
680
+ };
681
+ this._registeredResourceTemplates[name] = registeredResourceTemplate;
682
+ const variableNames = template.uriTemplate.variableNames;
683
+ const hasCompleter = Array.isArray(variableNames) && variableNames.some((v) => !!template.completeCallback(v));
684
+ if (hasCompleter) {
685
+ this.setCompletionRequestHandler();
686
+ }
687
+ return registeredResourceTemplate;
688
+ }
689
+ _createRegisteredPrompt(name, title, description, argsSchema, callback) {
690
+ const registeredPrompt = {
691
+ title,
692
+ description,
693
+ argsSchema: argsSchema === void 0 ? void 0 : objectFromShape(argsSchema),
694
+ callback,
695
+ enabled: true,
696
+ disable: () => registeredPrompt.update({ enabled: false }),
697
+ enable: () => registeredPrompt.update({ enabled: true }),
698
+ remove: () => registeredPrompt.update({ name: null }),
699
+ update: (updates) => {
700
+ if (typeof updates.name !== "undefined" && updates.name !== name) {
701
+ delete this._registeredPrompts[name];
702
+ if (updates.name)
703
+ this._registeredPrompts[updates.name] = registeredPrompt;
704
+ }
705
+ if (typeof updates.title !== "undefined")
706
+ registeredPrompt.title = updates.title;
707
+ if (typeof updates.description !== "undefined")
708
+ registeredPrompt.description = updates.description;
709
+ if (typeof updates.argsSchema !== "undefined")
710
+ registeredPrompt.argsSchema = objectFromShape(updates.argsSchema);
711
+ if (typeof updates.callback !== "undefined")
712
+ registeredPrompt.callback = updates.callback;
713
+ if (typeof updates.enabled !== "undefined")
714
+ registeredPrompt.enabled = updates.enabled;
715
+ this.sendPromptListChanged();
716
+ }
717
+ };
718
+ this._registeredPrompts[name] = registeredPrompt;
719
+ if (argsSchema) {
720
+ const hasCompletable = Object.values(argsSchema).some((field) => {
721
+ const inner = field instanceof ZodOptional ? field._def?.innerType : field;
722
+ return isCompletable(inner);
723
+ });
724
+ if (hasCompletable) {
725
+ this.setCompletionRequestHandler();
726
+ }
727
+ }
728
+ return registeredPrompt;
729
+ }
730
+ _createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, execution, _meta, handler) {
731
+ validateAndWarnToolName(name);
732
+ const registeredTool = {
733
+ title,
734
+ description,
735
+ inputSchema: getZodSchemaObject(inputSchema),
736
+ outputSchema: getZodSchemaObject(outputSchema),
737
+ annotations,
738
+ execution,
739
+ _meta,
740
+ handler,
741
+ enabled: true,
742
+ disable: () => registeredTool.update({ enabled: false }),
743
+ enable: () => registeredTool.update({ enabled: true }),
744
+ remove: () => registeredTool.update({ name: null }),
745
+ update: (updates) => {
746
+ if (typeof updates.name !== "undefined" && updates.name !== name) {
747
+ if (typeof updates.name === "string") {
748
+ validateAndWarnToolName(updates.name);
749
+ }
750
+ delete this._registeredTools[name];
751
+ if (updates.name)
752
+ this._registeredTools[updates.name] = registeredTool;
753
+ }
754
+ if (typeof updates.title !== "undefined")
755
+ registeredTool.title = updates.title;
756
+ if (typeof updates.description !== "undefined")
757
+ registeredTool.description = updates.description;
758
+ if (typeof updates.paramsSchema !== "undefined")
759
+ registeredTool.inputSchema = objectFromShape(updates.paramsSchema);
760
+ if (typeof updates.outputSchema !== "undefined")
761
+ registeredTool.outputSchema = objectFromShape(updates.outputSchema);
762
+ if (typeof updates.callback !== "undefined")
763
+ registeredTool.handler = updates.callback;
764
+ if (typeof updates.annotations !== "undefined")
765
+ registeredTool.annotations = updates.annotations;
766
+ if (typeof updates._meta !== "undefined")
767
+ registeredTool._meta = updates._meta;
768
+ if (typeof updates.enabled !== "undefined")
769
+ registeredTool.enabled = updates.enabled;
770
+ this.sendToolListChanged();
771
+ }
772
+ };
773
+ this._registeredTools[name] = registeredTool;
774
+ this.setToolRequestHandlers();
775
+ this.sendToolListChanged();
776
+ return registeredTool;
777
+ }
778
+ /**
779
+ * tool() implementation. Parses arguments passed to overrides defined above.
780
+ */
781
+ tool(name, ...rest) {
782
+ if (this._registeredTools[name]) {
783
+ throw new Error(`Tool ${name} is already registered`);
784
+ }
785
+ let description;
786
+ let inputSchema;
787
+ let outputSchema;
788
+ let annotations;
789
+ if (typeof rest[0] === "string") {
790
+ description = rest.shift();
791
+ }
792
+ if (rest.length > 1) {
793
+ const firstArg = rest[0];
794
+ if (isZodRawShapeCompat(firstArg)) {
795
+ inputSchema = rest.shift();
796
+ if (rest.length > 1 && typeof rest[0] === "object" && rest[0] !== null && !isZodRawShapeCompat(rest[0])) {
797
+ annotations = rest.shift();
798
+ }
799
+ } else if (typeof firstArg === "object" && firstArg !== null) {
800
+ if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
801
+ throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
802
+ }
803
+ annotations = rest.shift();
804
+ }
805
+ }
806
+ const callback = rest[0];
807
+ return this._createRegisteredTool(name, void 0, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, void 0, callback);
808
+ }
809
+ /**
810
+ * Registers a tool with a config object and callback.
811
+ */
812
+ registerTool(name, config, cb) {
813
+ if (this._registeredTools[name]) {
814
+ throw new Error(`Tool ${name} is already registered`);
815
+ }
816
+ const { title, description, inputSchema, outputSchema, annotations, _meta } = config;
817
+ return this._createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, _meta, cb);
818
+ }
819
+ prompt(name, ...rest) {
820
+ if (this._registeredPrompts[name]) {
821
+ throw new Error(`Prompt ${name} is already registered`);
822
+ }
823
+ let description;
824
+ if (typeof rest[0] === "string") {
825
+ description = rest.shift();
826
+ }
827
+ let argsSchema;
828
+ if (rest.length > 1) {
829
+ argsSchema = rest.shift();
830
+ }
831
+ const cb = rest[0];
832
+ const registeredPrompt = this._createRegisteredPrompt(name, void 0, description, argsSchema, cb);
833
+ this.setPromptRequestHandlers();
834
+ this.sendPromptListChanged();
835
+ return registeredPrompt;
836
+ }
837
+ /**
838
+ * Registers a prompt with a config object and callback.
839
+ */
840
+ registerPrompt(name, config, cb) {
841
+ if (this._registeredPrompts[name]) {
842
+ throw new Error(`Prompt ${name} is already registered`);
843
+ }
844
+ const { title, description, argsSchema } = config;
845
+ const registeredPrompt = this._createRegisteredPrompt(name, title, description, argsSchema, cb);
846
+ this.setPromptRequestHandlers();
847
+ this.sendPromptListChanged();
848
+ return registeredPrompt;
849
+ }
850
+ /**
851
+ * Checks if the server is connected to a transport.
852
+ * @returns True if the server is connected
853
+ */
854
+ isConnected() {
855
+ return this.server.transport !== void 0;
856
+ }
857
+ /**
858
+ * Sends a logging message to the client, if connected.
859
+ * Note: You only need to send the parameters object, not the entire JSON RPC message
860
+ * @see LoggingMessageNotification
861
+ * @param params
862
+ * @param sessionId optional for stateless and backward compatibility
863
+ */
864
+ async sendLoggingMessage(params, sessionId) {
865
+ return this.server.sendLoggingMessage(params, sessionId);
866
+ }
867
+ /**
868
+ * Sends a resource list changed event to the client, if connected.
869
+ */
870
+ sendResourceListChanged() {
871
+ if (this.isConnected()) {
872
+ this.server.sendResourceListChanged();
873
+ }
874
+ }
875
+ /**
876
+ * Sends a tool list changed event to the client, if connected.
877
+ */
878
+ sendToolListChanged() {
879
+ if (this.isConnected()) {
880
+ this.server.sendToolListChanged();
881
+ }
882
+ }
883
+ /**
884
+ * Sends a prompt list changed event to the client, if connected.
885
+ */
886
+ sendPromptListChanged() {
887
+ if (this.isConnected()) {
888
+ this.server.sendPromptListChanged();
889
+ }
890
+ }
891
+ };
892
+ var EMPTY_OBJECT_JSON_SCHEMA = {
893
+ type: "object",
894
+ properties: {}
895
+ };
896
+ function isZodTypeLike(value) {
897
+ return value !== null && typeof value === "object" && "parse" in value && typeof value.parse === "function" && "safeParse" in value && typeof value.safeParse === "function";
898
+ }
899
+ function isZodSchemaInstance(obj) {
900
+ return "_def" in obj || "_zod" in obj || isZodTypeLike(obj);
901
+ }
902
+ function isZodRawShapeCompat(obj) {
903
+ if (typeof obj !== "object" || obj === null) {
904
+ return false;
905
+ }
906
+ if (isZodSchemaInstance(obj)) {
907
+ return false;
908
+ }
909
+ if (Object.keys(obj).length === 0) {
910
+ return true;
911
+ }
912
+ return Object.values(obj).some(isZodTypeLike);
913
+ }
914
+ function getZodSchemaObject(schema) {
915
+ if (!schema) {
916
+ return void 0;
917
+ }
918
+ if (isZodRawShapeCompat(schema)) {
919
+ return objectFromShape(schema);
920
+ }
921
+ if (!isZodSchemaInstance(schema)) {
922
+ throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
923
+ }
924
+ return schema;
925
+ }
926
+ function promptArgumentsFromSchema(schema) {
927
+ const shape = getObjectShape(schema);
928
+ if (!shape)
929
+ return [];
930
+ return Object.entries(shape).map(([name, field]) => {
931
+ const description = getSchemaDescription(field);
932
+ const isOptional = isSchemaOptional(field);
933
+ return {
934
+ name,
935
+ description,
936
+ required: !isOptional
937
+ };
938
+ });
939
+ }
940
+ function getMethodValue(schema) {
941
+ const shape = getObjectShape(schema);
942
+ const methodSchema = shape?.method;
943
+ if (!methodSchema) {
944
+ throw new Error("Schema is missing a method literal");
945
+ }
946
+ const value = getLiteralValue(methodSchema);
947
+ if (typeof value === "string") {
948
+ return value;
949
+ }
950
+ throw new Error("Schema method literal must be a string");
951
+ }
952
+ function createCompletionResult(suggestions) {
953
+ return {
954
+ completion: {
955
+ values: suggestions.slice(0, 100),
956
+ total: suggestions.length,
957
+ hasMore: suggestions.length > 100
958
+ }
959
+ };
960
+ }
961
+ var EMPTY_COMPLETION_RESULT = {
962
+ completion: {
963
+ values: [],
964
+ hasMore: false
965
+ }
966
+ };
967
+
968
+ // src/mcp/discovery.ts
969
+ import { z } from "zod";
970
+ function text(content) {
971
+ return { content: [{ type: "text", text: content }] };
972
+ }
973
+ function jsonText(data) {
974
+ return text(JSON.stringify(data, null, 2));
975
+ }
976
+ function createDiscoveryMcpServer(ctx = {}) {
977
+ const server = new McpServer({
978
+ name: "trellis-mcp-gateway",
979
+ version: "0.1.0"
980
+ });
981
+ server.registerTool(
982
+ "list_rooms",
983
+ {
984
+ description: "List Trellis rooms known to this gateway (deployed Sprites + local config).",
985
+ inputSchema: {}
986
+ },
987
+ async () => {
988
+ const rooms = listRegisteredRooms(ctx).map(
989
+ ({ name, url, mcpUrl, source, active, deployedAt }) => ({
990
+ name,
991
+ url,
992
+ mcpUrl,
993
+ source,
994
+ active,
995
+ deployedAt
996
+ })
997
+ );
998
+ return jsonText({
999
+ gateway: gatewayPublicUrl(ctx),
1000
+ count: rooms.length,
1001
+ rooms
1002
+ });
1003
+ }
1004
+ );
1005
+ server.registerTool(
1006
+ "get_room",
1007
+ {
1008
+ description: "Get details for a room by name or URL.",
1009
+ inputSchema: {
1010
+ name: z.string().describe("Room name or base URL")
1011
+ }
1012
+ },
1013
+ async ({ name }) => {
1014
+ const room = getRegisteredRoom(name, ctx);
1015
+ if (!room) return text(`Room not found: ${name}`);
1016
+ return jsonText(room);
1017
+ }
1018
+ );
1019
+ server.registerTool(
1020
+ "connect_room",
1021
+ {
1022
+ description: "Return MCP client configuration to connect an agent to a room graph.",
1023
+ inputSchema: {
1024
+ name: z.string().describe("Room name or base URL"),
1025
+ client: z.string().optional().describe("Client hint: cursor | claude | generic"),
1026
+ playgroundRoom: z.string().optional().describe(
1027
+ "Playground ?room= slug \u2014 adds embed-{slug} tenant to bridge / headers"
1028
+ )
1029
+ }
1030
+ },
1031
+ async ({ name, client, playgroundRoom }) => {
1032
+ const room = getRegisteredRoom(name, ctx);
1033
+ if (!room) return text(`Room not found: ${name}`);
1034
+ const tenantId = playgroundRoom?.trim() ? playgroundRoomToTenant(playgroundRoom) : void 0;
1035
+ const authHeader = room.apiKey ? { Authorization: `Bearer ${room.apiKey}` } : void 0;
1036
+ const tenantHeader = tenantId ? { "X-Trellis-Tenant": tenantId } : void 0;
1037
+ const httpHeaders = {
1038
+ ...authHeader,
1039
+ ...tenantHeader
1040
+ };
1041
+ const bridgeTenantArgs = tenantId ? playgroundRoom?.trim().startsWith("embed-") ? ["--tenant", tenantId] : ["--playground-room", playgroundRoom.trim()] : [];
1042
+ const cursor = {
1043
+ mcpServers: {
1044
+ "trellis-room": {
1045
+ url: room.mcpUrl,
1046
+ ...Object.keys(httpHeaders).length ? { headers: httpHeaders } : {}
1047
+ }
1048
+ }
1049
+ };
1050
+ const claude = room.apiKey ? {
1051
+ mcpServers: {
1052
+ "trellis-room": {
1053
+ command: "npx",
1054
+ args: [
1055
+ "trellis",
1056
+ "mcp",
1057
+ "bridge",
1058
+ "--room",
1059
+ room.url,
1060
+ "--api-key",
1061
+ room.apiKey,
1062
+ ...bridgeTenantArgs
1063
+ ]
1064
+ }
1065
+ }
1066
+ } : {
1067
+ mcpServers: {
1068
+ "trellis-room": {
1069
+ command: "npx",
1070
+ args: [
1071
+ "trellis",
1072
+ "mcp",
1073
+ "bridge",
1074
+ "--room",
1075
+ room.url,
1076
+ ...bridgeTenantArgs
1077
+ ]
1078
+ }
1079
+ }
1080
+ };
1081
+ const oauth = ctx.origin != null ? {
1082
+ loginUrl: `${ctx.origin}/auth/oauth/google`,
1083
+ tokenUse: "Authorization: Bearer <jwt>",
1084
+ protectedResource: `${ctx.origin}/.well-known/oauth-protected-resource`
1085
+ } : void 0;
1086
+ const configs = {
1087
+ cursor,
1088
+ claude,
1089
+ generic: {
1090
+ url: room.mcpUrl,
1091
+ ...Object.keys(httpHeaders).length ? { headers: httpHeaders } : {}
1092
+ }
1093
+ };
1094
+ return jsonText({
1095
+ room: {
1096
+ name: room.name,
1097
+ url: room.url,
1098
+ mcpUrl: room.mcpUrl
1099
+ },
1100
+ ...tenantId ? {
1101
+ playground: {
1102
+ roomSlug: playgroundRoom.trim(),
1103
+ tenantId,
1104
+ urlHint: `https://playground.trellis.computer/?room=${encodeURIComponent(playgroundRoom.trim())}`,
1105
+ mcpToolHint: "Pass room or tenantId on each tool call, or set X-Trellis-Tenant header / bridge --playground-room"
1106
+ }
1107
+ } : {},
1108
+ oauth,
1109
+ config: client && configs[client] ? configs[client] : { cursor, claude, generic: configs.generic }
1110
+ });
1111
+ }
1112
+ );
1113
+ return server;
1114
+ }
1115
+
1116
+ // src/server/streamable-mcp-gateway.ts
1117
+ import { randomUUID } from "node:crypto";
1118
+
1119
+ // node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
1120
+ var WebStandardStreamableHTTPServerTransport = class {
1121
+ constructor(options = {}) {
1122
+ this._started = false;
1123
+ this._hasHandledRequest = false;
1124
+ this._streamMapping = /* @__PURE__ */ new Map();
1125
+ this._requestToStreamMapping = /* @__PURE__ */ new Map();
1126
+ this._requestResponseMap = /* @__PURE__ */ new Map();
1127
+ this._initialized = false;
1128
+ this._enableJsonResponse = false;
1129
+ this._standaloneSseStreamId = "_GET_stream";
1130
+ this.sessionIdGenerator = options.sessionIdGenerator;
1131
+ this._enableJsonResponse = options.enableJsonResponse ?? false;
1132
+ this._eventStore = options.eventStore;
1133
+ this._onsessioninitialized = options.onsessioninitialized;
1134
+ this._onsessionclosed = options.onsessionclosed;
1135
+ this._allowedHosts = options.allowedHosts;
1136
+ this._allowedOrigins = options.allowedOrigins;
1137
+ this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
1138
+ this._retryInterval = options.retryInterval;
1139
+ }
1140
+ /**
1141
+ * Starts the transport. This is required by the Transport interface but is a no-op
1142
+ * for the Streamable HTTP transport as connections are managed per-request.
1143
+ */
1144
+ async start() {
1145
+ if (this._started) {
1146
+ throw new Error("Transport already started");
1147
+ }
1148
+ this._started = true;
1149
+ }
1150
+ /**
1151
+ * Helper to create a JSON error response
1152
+ */
1153
+ createJsonErrorResponse(status, code, message, options) {
1154
+ const error = { code, message };
1155
+ if (options?.data !== void 0) {
1156
+ error.data = options.data;
1157
+ }
1158
+ return new Response(JSON.stringify({
1159
+ jsonrpc: "2.0",
1160
+ error,
1161
+ id: null
1162
+ }), {
1163
+ status,
1164
+ headers: {
1165
+ "Content-Type": "application/json",
1166
+ ...options?.headers
1167
+ }
1168
+ });
1169
+ }
1170
+ /**
1171
+ * Validates request headers for DNS rebinding protection.
1172
+ * @returns Error response if validation fails, undefined if validation passes.
1173
+ */
1174
+ validateRequestHeaders(req) {
1175
+ if (!this._enableDnsRebindingProtection) {
1176
+ return void 0;
1177
+ }
1178
+ if (this._allowedHosts && this._allowedHosts.length > 0) {
1179
+ const hostHeader = req.headers.get("host");
1180
+ if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
1181
+ const error = `Invalid Host header: ${hostHeader}`;
1182
+ this.onerror?.(new Error(error));
1183
+ return this.createJsonErrorResponse(403, -32e3, error);
1184
+ }
1185
+ }
1186
+ if (this._allowedOrigins && this._allowedOrigins.length > 0) {
1187
+ const originHeader = req.headers.get("origin");
1188
+ if (originHeader && !this._allowedOrigins.includes(originHeader)) {
1189
+ const error = `Invalid Origin header: ${originHeader}`;
1190
+ this.onerror?.(new Error(error));
1191
+ return this.createJsonErrorResponse(403, -32e3, error);
1192
+ }
1193
+ }
1194
+ return void 0;
1195
+ }
1196
+ /**
1197
+ * Handles an incoming HTTP request, whether GET, POST, or DELETE
1198
+ * Returns a Response object (Web Standard)
1199
+ */
1200
+ async handleRequest(req, options) {
1201
+ if (!this.sessionIdGenerator && this._hasHandledRequest) {
1202
+ throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
1203
+ }
1204
+ this._hasHandledRequest = true;
1205
+ const validationError = this.validateRequestHeaders(req);
1206
+ if (validationError) {
1207
+ return validationError;
1208
+ }
1209
+ switch (req.method) {
1210
+ case "POST":
1211
+ return this.handlePostRequest(req, options);
1212
+ case "GET":
1213
+ return this.handleGetRequest(req);
1214
+ case "DELETE":
1215
+ return this.handleDeleteRequest(req);
1216
+ default:
1217
+ return this.handleUnsupportedRequest();
1218
+ }
1219
+ }
1220
+ /**
1221
+ * Writes a priming event to establish resumption capability.
1222
+ * Only sends if eventStore is configured (opt-in for resumability) and
1223
+ * the client's protocol version supports empty SSE data (>= 2025-11-25).
1224
+ */
1225
+ async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
1226
+ if (!this._eventStore) {
1227
+ return;
1228
+ }
1229
+ if (protocolVersion < "2025-11-25") {
1230
+ return;
1231
+ }
1232
+ const primingEventId = await this._eventStore.storeEvent(streamId, {});
1233
+ let primingEvent = `id: ${primingEventId}
1234
+ data:
1235
+
1236
+ `;
1237
+ if (this._retryInterval !== void 0) {
1238
+ primingEvent = `id: ${primingEventId}
1239
+ retry: ${this._retryInterval}
1240
+ data:
1241
+
1242
+ `;
1243
+ }
1244
+ controller.enqueue(encoder.encode(primingEvent));
1245
+ }
1246
+ /**
1247
+ * Handles GET requests for SSE stream
1248
+ */
1249
+ async handleGetRequest(req) {
1250
+ const acceptHeader = req.headers.get("accept");
1251
+ if (!acceptHeader?.includes("text/event-stream")) {
1252
+ this.onerror?.(new Error("Not Acceptable: Client must accept text/event-stream"));
1253
+ return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept text/event-stream");
1254
+ }
1255
+ const sessionError = this.validateSession(req);
1256
+ if (sessionError) {
1257
+ return sessionError;
1258
+ }
1259
+ const protocolError = this.validateProtocolVersion(req);
1260
+ if (protocolError) {
1261
+ return protocolError;
1262
+ }
1263
+ if (this._eventStore) {
1264
+ const lastEventId = req.headers.get("last-event-id");
1265
+ if (lastEventId) {
1266
+ return this.replayEvents(lastEventId);
1267
+ }
1268
+ }
1269
+ if (this._streamMapping.get(this._standaloneSseStreamId) !== void 0) {
1270
+ this.onerror?.(new Error("Conflict: Only one SSE stream is allowed per session"));
1271
+ return this.createJsonErrorResponse(409, -32e3, "Conflict: Only one SSE stream is allowed per session");
1272
+ }
1273
+ const encoder = new TextEncoder();
1274
+ let streamController;
1275
+ const readable = new ReadableStream({
1276
+ start: (controller) => {
1277
+ streamController = controller;
1278
+ },
1279
+ cancel: () => {
1280
+ this._streamMapping.delete(this._standaloneSseStreamId);
1281
+ }
1282
+ });
1283
+ const headers = {
1284
+ "Content-Type": "text/event-stream",
1285
+ "Cache-Control": "no-cache, no-transform",
1286
+ Connection: "keep-alive"
1287
+ };
1288
+ if (this.sessionId !== void 0) {
1289
+ headers["mcp-session-id"] = this.sessionId;
1290
+ }
1291
+ this._streamMapping.set(this._standaloneSseStreamId, {
1292
+ controller: streamController,
1293
+ encoder,
1294
+ cleanup: () => {
1295
+ this._streamMapping.delete(this._standaloneSseStreamId);
1296
+ try {
1297
+ streamController.close();
1298
+ } catch {
1299
+ }
1300
+ }
1301
+ });
1302
+ return new Response(readable, { headers });
1303
+ }
1304
+ /**
1305
+ * Replays events that would have been sent after the specified event ID
1306
+ * Only used when resumability is enabled
1307
+ */
1308
+ async replayEvents(lastEventId) {
1309
+ if (!this._eventStore) {
1310
+ this.onerror?.(new Error("Event store not configured"));
1311
+ return this.createJsonErrorResponse(400, -32e3, "Event store not configured");
1312
+ }
1313
+ try {
1314
+ let streamId;
1315
+ if (this._eventStore.getStreamIdForEventId) {
1316
+ streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
1317
+ if (!streamId) {
1318
+ this.onerror?.(new Error("Invalid event ID format"));
1319
+ return this.createJsonErrorResponse(400, -32e3, "Invalid event ID format");
1320
+ }
1321
+ if (this._streamMapping.get(streamId) !== void 0) {
1322
+ this.onerror?.(new Error("Conflict: Stream already has an active connection"));
1323
+ return this.createJsonErrorResponse(409, -32e3, "Conflict: Stream already has an active connection");
1324
+ }
1325
+ }
1326
+ const headers = {
1327
+ "Content-Type": "text/event-stream",
1328
+ "Cache-Control": "no-cache, no-transform",
1329
+ Connection: "keep-alive"
1330
+ };
1331
+ if (this.sessionId !== void 0) {
1332
+ headers["mcp-session-id"] = this.sessionId;
1333
+ }
1334
+ const encoder = new TextEncoder();
1335
+ let streamController;
1336
+ const readable = new ReadableStream({
1337
+ start: (controller) => {
1338
+ streamController = controller;
1339
+ },
1340
+ cancel: () => {
1341
+ }
1342
+ });
1343
+ const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
1344
+ send: async (eventId, message) => {
1345
+ const success = this.writeSSEEvent(streamController, encoder, message, eventId);
1346
+ if (!success) {
1347
+ this.onerror?.(new Error("Failed replay events"));
1348
+ try {
1349
+ streamController.close();
1350
+ } catch {
1351
+ }
1352
+ }
1353
+ }
1354
+ });
1355
+ this._streamMapping.set(replayedStreamId, {
1356
+ controller: streamController,
1357
+ encoder,
1358
+ cleanup: () => {
1359
+ this._streamMapping.delete(replayedStreamId);
1360
+ try {
1361
+ streamController.close();
1362
+ } catch {
1363
+ }
1364
+ }
1365
+ });
1366
+ return new Response(readable, { headers });
1367
+ } catch (error) {
1368
+ this.onerror?.(error);
1369
+ return this.createJsonErrorResponse(500, -32e3, "Error replaying events");
1370
+ }
1371
+ }
1372
+ /**
1373
+ * Writes an event to an SSE stream via controller with proper formatting
1374
+ */
1375
+ writeSSEEvent(controller, encoder, message, eventId) {
1376
+ try {
1377
+ let eventData = `event: message
1378
+ `;
1379
+ if (eventId) {
1380
+ eventData += `id: ${eventId}
1381
+ `;
1382
+ }
1383
+ eventData += `data: ${JSON.stringify(message)}
1384
+
1385
+ `;
1386
+ controller.enqueue(encoder.encode(eventData));
1387
+ return true;
1388
+ } catch (error) {
1389
+ this.onerror?.(error);
1390
+ return false;
1391
+ }
1392
+ }
1393
+ /**
1394
+ * Handles unsupported requests (PUT, PATCH, etc.)
1395
+ */
1396
+ handleUnsupportedRequest() {
1397
+ this.onerror?.(new Error("Method not allowed."));
1398
+ return new Response(JSON.stringify({
1399
+ jsonrpc: "2.0",
1400
+ error: {
1401
+ code: -32e3,
1402
+ message: "Method not allowed."
1403
+ },
1404
+ id: null
1405
+ }), {
1406
+ status: 405,
1407
+ headers: {
1408
+ Allow: "GET, POST, DELETE",
1409
+ "Content-Type": "application/json"
1410
+ }
1411
+ });
1412
+ }
1413
+ /**
1414
+ * Handles POST requests containing JSON-RPC messages
1415
+ */
1416
+ async handlePostRequest(req, options) {
1417
+ try {
1418
+ const acceptHeader = req.headers.get("accept");
1419
+ if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
1420
+ this.onerror?.(new Error("Not Acceptable: Client must accept both application/json and text/event-stream"));
1421
+ return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept both application/json and text/event-stream");
1422
+ }
1423
+ const ct = req.headers.get("content-type");
1424
+ if (!ct || !ct.includes("application/json")) {
1425
+ this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json"));
1426
+ return this.createJsonErrorResponse(415, -32e3, "Unsupported Media Type: Content-Type must be application/json");
1427
+ }
1428
+ const requestInfo = {
1429
+ headers: Object.fromEntries(req.headers.entries()),
1430
+ url: new URL(req.url)
1431
+ };
1432
+ let rawMessage;
1433
+ if (options?.parsedBody !== void 0) {
1434
+ rawMessage = options.parsedBody;
1435
+ } else {
1436
+ try {
1437
+ rawMessage = await req.json();
1438
+ } catch {
1439
+ this.onerror?.(new Error("Parse error: Invalid JSON"));
1440
+ return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
1441
+ }
1442
+ }
1443
+ let messages;
1444
+ try {
1445
+ if (Array.isArray(rawMessage)) {
1446
+ messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg));
1447
+ } else {
1448
+ messages = [JSONRPCMessageSchema.parse(rawMessage)];
1449
+ }
1450
+ } catch {
1451
+ this.onerror?.(new Error("Parse error: Invalid JSON-RPC message"));
1452
+ return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
1453
+ }
1454
+ const isInitializationRequest = messages.some(isInitializeRequest);
1455
+ if (isInitializationRequest) {
1456
+ if (this._initialized && this.sessionId !== void 0) {
1457
+ this.onerror?.(new Error("Invalid Request: Server already initialized"));
1458
+ return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
1459
+ }
1460
+ if (messages.length > 1) {
1461
+ this.onerror?.(new Error("Invalid Request: Only one initialization request is allowed"));
1462
+ return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
1463
+ }
1464
+ this.sessionId = this.sessionIdGenerator?.();
1465
+ this._initialized = true;
1466
+ if (this.sessionId && this._onsessioninitialized) {
1467
+ await Promise.resolve(this._onsessioninitialized(this.sessionId));
1468
+ }
1469
+ }
1470
+ if (!isInitializationRequest) {
1471
+ const sessionError = this.validateSession(req);
1472
+ if (sessionError) {
1473
+ return sessionError;
1474
+ }
1475
+ const protocolError = this.validateProtocolVersion(req);
1476
+ if (protocolError) {
1477
+ return protocolError;
1478
+ }
1479
+ }
1480
+ const hasRequests = messages.some(isJSONRPCRequest);
1481
+ if (!hasRequests) {
1482
+ for (const message of messages) {
1483
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
1484
+ }
1485
+ return new Response(null, { status: 202 });
1486
+ }
1487
+ const streamId = crypto.randomUUID();
1488
+ const initRequest = messages.find((m) => isInitializeRequest(m));
1489
+ const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
1490
+ if (this._enableJsonResponse) {
1491
+ return new Promise((resolve) => {
1492
+ this._streamMapping.set(streamId, {
1493
+ resolveJson: resolve,
1494
+ cleanup: () => {
1495
+ this._streamMapping.delete(streamId);
1496
+ }
1497
+ });
1498
+ for (const message of messages) {
1499
+ if (isJSONRPCRequest(message)) {
1500
+ this._requestToStreamMapping.set(message.id, streamId);
1501
+ }
1502
+ }
1503
+ for (const message of messages) {
1504
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
1505
+ }
1506
+ });
1507
+ }
1508
+ const encoder = new TextEncoder();
1509
+ let streamController;
1510
+ const readable = new ReadableStream({
1511
+ start: (controller) => {
1512
+ streamController = controller;
1513
+ },
1514
+ cancel: () => {
1515
+ this._streamMapping.delete(streamId);
1516
+ }
1517
+ });
1518
+ const headers = {
1519
+ "Content-Type": "text/event-stream",
1520
+ "Cache-Control": "no-cache",
1521
+ Connection: "keep-alive"
1522
+ };
1523
+ if (this.sessionId !== void 0) {
1524
+ headers["mcp-session-id"] = this.sessionId;
1525
+ }
1526
+ for (const message of messages) {
1527
+ if (isJSONRPCRequest(message)) {
1528
+ this._streamMapping.set(streamId, {
1529
+ controller: streamController,
1530
+ encoder,
1531
+ cleanup: () => {
1532
+ this._streamMapping.delete(streamId);
1533
+ try {
1534
+ streamController.close();
1535
+ } catch {
1536
+ }
1537
+ }
1538
+ });
1539
+ this._requestToStreamMapping.set(message.id, streamId);
1540
+ }
1541
+ }
1542
+ await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
1543
+ for (const message of messages) {
1544
+ let closeSSEStream;
1545
+ let closeStandaloneSSEStream;
1546
+ if (isJSONRPCRequest(message) && this._eventStore && clientProtocolVersion >= "2025-11-25") {
1547
+ closeSSEStream = () => {
1548
+ this.closeSSEStream(message.id);
1549
+ };
1550
+ closeStandaloneSSEStream = () => {
1551
+ this.closeStandaloneSSEStream();
1552
+ };
1553
+ }
1554
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
1555
+ }
1556
+ return new Response(readable, { status: 200, headers });
1557
+ } catch (error) {
1558
+ this.onerror?.(error);
1559
+ return this.createJsonErrorResponse(400, -32700, "Parse error", { data: String(error) });
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Handles DELETE requests to terminate sessions
1564
+ */
1565
+ async handleDeleteRequest(req) {
1566
+ const sessionError = this.validateSession(req);
1567
+ if (sessionError) {
1568
+ return sessionError;
1569
+ }
1570
+ const protocolError = this.validateProtocolVersion(req);
1571
+ if (protocolError) {
1572
+ return protocolError;
1573
+ }
1574
+ await Promise.resolve(this._onsessionclosed?.(this.sessionId));
1575
+ await this.close();
1576
+ return new Response(null, { status: 200 });
1577
+ }
1578
+ /**
1579
+ * Validates session ID for non-initialization requests.
1580
+ * Returns Response error if invalid, undefined otherwise
1581
+ */
1582
+ validateSession(req) {
1583
+ if (this.sessionIdGenerator === void 0) {
1584
+ return void 0;
1585
+ }
1586
+ if (!this._initialized) {
1587
+ this.onerror?.(new Error("Bad Request: Server not initialized"));
1588
+ return this.createJsonErrorResponse(400, -32e3, "Bad Request: Server not initialized");
1589
+ }
1590
+ const sessionId = req.headers.get("mcp-session-id");
1591
+ if (!sessionId) {
1592
+ this.onerror?.(new Error("Bad Request: Mcp-Session-Id header is required"));
1593
+ return this.createJsonErrorResponse(400, -32e3, "Bad Request: Mcp-Session-Id header is required");
1594
+ }
1595
+ if (sessionId !== this.sessionId) {
1596
+ this.onerror?.(new Error("Session not found"));
1597
+ return this.createJsonErrorResponse(404, -32001, "Session not found");
1598
+ }
1599
+ return void 0;
1600
+ }
1601
+ /**
1602
+ * Validates the MCP-Protocol-Version header on incoming requests.
1603
+ *
1604
+ * For initialization: Version negotiation handles unknown versions gracefully
1605
+ * (server responds with its supported version).
1606
+ *
1607
+ * For subsequent requests with MCP-Protocol-Version header:
1608
+ * - Accept if in supported list
1609
+ * - 400 if unsupported
1610
+ *
1611
+ * For HTTP requests without the MCP-Protocol-Version header:
1612
+ * - Accept and default to the version negotiated at initialization
1613
+ */
1614
+ validateProtocolVersion(req) {
1615
+ const protocolVersion = req.headers.get("mcp-protocol-version");
1616
+ if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
1617
+ this.onerror?.(new Error(`Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`));
1618
+ return this.createJsonErrorResponse(400, -32e3, `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`);
1619
+ }
1620
+ return void 0;
1621
+ }
1622
+ async close() {
1623
+ this._streamMapping.forEach(({ cleanup }) => {
1624
+ cleanup();
1625
+ });
1626
+ this._streamMapping.clear();
1627
+ this._requestResponseMap.clear();
1628
+ this.onclose?.();
1629
+ }
1630
+ /**
1631
+ * Close an SSE stream for a specific request, triggering client reconnection.
1632
+ * Use this to implement polling behavior during long-running operations -
1633
+ * client will reconnect after the retry interval specified in the priming event.
1634
+ */
1635
+ closeSSEStream(requestId) {
1636
+ const streamId = this._requestToStreamMapping.get(requestId);
1637
+ if (!streamId)
1638
+ return;
1639
+ const stream = this._streamMapping.get(streamId);
1640
+ if (stream) {
1641
+ stream.cleanup();
1642
+ }
1643
+ }
1644
+ /**
1645
+ * Close the standalone GET SSE stream, triggering client reconnection.
1646
+ * Use this to implement polling behavior for server-initiated notifications.
1647
+ */
1648
+ closeStandaloneSSEStream() {
1649
+ const stream = this._streamMapping.get(this._standaloneSseStreamId);
1650
+ if (stream) {
1651
+ stream.cleanup();
1652
+ }
1653
+ }
1654
+ async send(message, options) {
1655
+ let requestId = options?.relatedRequestId;
1656
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
1657
+ requestId = message.id;
1658
+ }
1659
+ if (requestId === void 0) {
1660
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
1661
+ throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
1662
+ }
1663
+ let eventId;
1664
+ if (this._eventStore) {
1665
+ eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
1666
+ }
1667
+ const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
1668
+ if (standaloneSse === void 0) {
1669
+ return;
1670
+ }
1671
+ if (standaloneSse.controller && standaloneSse.encoder) {
1672
+ this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
1673
+ }
1674
+ return;
1675
+ }
1676
+ const streamId = this._requestToStreamMapping.get(requestId);
1677
+ if (!streamId) {
1678
+ throw new Error(`No connection established for request ID: ${String(requestId)}`);
1679
+ }
1680
+ const stream = this._streamMapping.get(streamId);
1681
+ if (!this._enableJsonResponse && stream?.controller && stream?.encoder) {
1682
+ let eventId;
1683
+ if (this._eventStore) {
1684
+ eventId = await this._eventStore.storeEvent(streamId, message);
1685
+ }
1686
+ this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
1687
+ }
1688
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
1689
+ this._requestResponseMap.set(requestId, message);
1690
+ const relatedIds = Array.from(this._requestToStreamMapping.entries()).filter(([_, sid]) => sid === streamId).map(([id]) => id);
1691
+ const allResponsesReady = relatedIds.every((id) => this._requestResponseMap.has(id));
1692
+ if (allResponsesReady) {
1693
+ if (!stream) {
1694
+ throw new Error(`No connection established for request ID: ${String(requestId)}`);
1695
+ }
1696
+ if (this._enableJsonResponse && stream.resolveJson) {
1697
+ const headers = {
1698
+ "Content-Type": "application/json"
1699
+ };
1700
+ if (this.sessionId !== void 0) {
1701
+ headers["mcp-session-id"] = this.sessionId;
1702
+ }
1703
+ const responses = relatedIds.map((id) => this._requestResponseMap.get(id));
1704
+ if (responses.length === 1) {
1705
+ stream.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
1706
+ } else {
1707
+ stream.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
1708
+ }
1709
+ } else {
1710
+ stream.cleanup();
1711
+ }
1712
+ for (const id of relatedIds) {
1713
+ this._requestResponseMap.delete(id);
1714
+ this._requestToStreamMapping.delete(id);
1715
+ }
1716
+ }
1717
+ }
1718
+ }
1719
+ };
1720
+
1721
+ // src/server/streamable-mcp-gateway.ts
1722
+ function mcpError(status, message) {
1723
+ return new Response(
1724
+ JSON.stringify({
1725
+ jsonrpc: "2.0",
1726
+ error: { code: -32e3, message },
1727
+ id: null
1728
+ }),
1729
+ {
1730
+ status,
1731
+ headers: { "Content-Type": "application/json" }
1732
+ }
1733
+ );
1734
+ }
1735
+ var StreamableMcpGateway = class {
1736
+ sessions = /* @__PURE__ */ new Map();
1737
+ async handle(req, createServer) {
1738
+ const sessionId = req.headers.get("mcp-session-id");
1739
+ if (sessionId) {
1740
+ const entry = this.sessions.get(sessionId);
1741
+ if (!entry) {
1742
+ return mcpError(400, "Invalid or expired MCP session");
1743
+ }
1744
+ return entry.transport.handleRequest(req);
1745
+ }
1746
+ if (req.method === "POST") {
1747
+ let body;
1748
+ try {
1749
+ body = await req.json();
1750
+ } catch {
1751
+ return mcpError(400, "Invalid JSON body");
1752
+ }
1753
+ if (!isInitializeRequest(body)) {
1754
+ return mcpError(
1755
+ 400,
1756
+ "Bad Request: POST without mcp-session-id must be an initialize request"
1757
+ );
1758
+ }
1759
+ const transport = new WebStandardStreamableHTTPServerTransport({
1760
+ sessionIdGenerator: () => randomUUID(),
1761
+ onsessioninitialized: (sid) => {
1762
+ this.sessions.set(sid, { transport });
1763
+ },
1764
+ onsessionclosed: (sid) => {
1765
+ this.sessions.delete(sid);
1766
+ }
1767
+ });
1768
+ transport.onclose = () => {
1769
+ const sid = transport.sessionId;
1770
+ if (sid) this.sessions.delete(sid);
1771
+ };
1772
+ const server = await createServer();
1773
+ await server.connect(transport);
1774
+ return transport.handleRequest(req, { parsedBody: body });
1775
+ }
1776
+ return mcpError(400, "MCP session required");
1777
+ }
1778
+ async close() {
1779
+ await Promise.all(
1780
+ [...this.sessions.values()].map(({ transport }) => transport.close())
1781
+ );
1782
+ this.sessions.clear();
1783
+ }
1784
+ };
1785
+
1786
+ // src/server/discovery-mcp-gateway.ts
1787
+ var DiscoveryMcpGateway = class {
1788
+ gateway = new StreamableMcpGateway();
1789
+ async handle(req, ctx) {
1790
+ return this.gateway.handle(req, () => createDiscoveryMcpServer(ctx));
1791
+ }
1792
+ async close() {
1793
+ await this.gateway.close();
1794
+ }
1795
+ };
1796
+ var discoveryMcpGateway = new DiscoveryMcpGateway();
1797
+
1798
+ // src/server/mcp-oauth-metadata.ts
1799
+ function oauthProtectedResourceMetadata(origin, resourcePath = "/mcp") {
1800
+ const resource = `${origin.replace(/\/$/, "")}${resourcePath}`;
1801
+ return {
1802
+ resource,
1803
+ authorization_servers: [origin.replace(/\/$/, "")],
1804
+ bearer_methods_supported: ["header"],
1805
+ scopes_supported: ["openid", "email", "profile"]
1806
+ };
1807
+ }
1808
+ function oauthAuthorizationServerMetadata(origin, providers = ["google", "github"]) {
1809
+ const base = origin.replace(/\/$/, "");
1810
+ return {
1811
+ issuer: base,
1812
+ authorization_endpoint: `${base}/auth/oauth/google`,
1813
+ token_endpoint: `${base}/auth/login`,
1814
+ registration_endpoint: `${base}/auth/register`,
1815
+ scopes_supported: ["openid", "email", "profile"],
1816
+ response_types_supported: ["code"],
1817
+ grant_types_supported: ["authorization_code", "password"],
1818
+ code_challenge_methods_supported: ["S256"],
1819
+ mcp_resource: `${base}/mcp`,
1820
+ oauth_providers: providers.map((p) => ({
1821
+ name: p,
1822
+ authorization_endpoint: `${base}/auth/oauth/${p}`
1823
+ }))
1824
+ };
1825
+ }
1826
+ function mcpServiceDocument(origin, authConfig) {
1827
+ const base = origin.replace(/\/$/, "");
1828
+ return {
1829
+ name: "trellis-room",
1830
+ version: "0.3.0",
1831
+ mcp: `${base}/mcp`,
1832
+ mcpGateway: `${base}/gateway/mcp`,
1833
+ health: `${base}/health`,
1834
+ oauth: {
1835
+ protectedResource: `${base}/.well-known/oauth-protected-resource`,
1836
+ authorizationServer: `${base}/.well-known/oauth-authorization-server`,
1837
+ apiKeySupported: Boolean(authConfig.apiKey),
1838
+ jwtSupported: Boolean(authConfig.jwtSecret)
1839
+ },
1840
+ bridge: {
1841
+ command: "npx trellis mcp bridge --room <url>"
1842
+ }
1843
+ };
1844
+ }
1845
+
1846
+ export {
1847
+ corsEnabledForConfig,
1848
+ withCors,
1849
+ corsPreflightResponse,
1850
+ McpServer,
1851
+ StreamableMcpGateway,
1852
+ discoveryMcpGateway,
1853
+ oauthProtectedResourceMetadata,
1854
+ oauthAuthorizationServerMetadata,
1855
+ mcpServiceDocument
1856
+ };