@silkweave/mcp 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -2,22 +2,21 @@ import { intro } from "@clack/prompts";
2
2
  import { Client } from "@modelcontextprotocol/sdk/client";
3
3
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
4
  import { LoggingMessageNotificationSchema, ProgressNotificationSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5
- import { SilkweaveError, unwrap } from "@silkweave/core";
6
5
  import { createCLILogger, createLogger } from "@silkweave/logger";
7
6
  import { capitalCase, kebabCase, pascalCase } from "change-case";
8
7
  import { Command } from "commander";
9
8
  import { randomUUID } from "crypto";
10
- import { z } from "zod/v4";
9
+ import { SilkweaveError, createContext, isStreamingAction, runStreamingAction } from "@silkweave/core";
11
10
  import { randomUUID as randomUUID$1 } from "node:crypto";
12
- import { InMemoryEventStore } from "@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js";
13
11
  import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
14
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
12
+ import express from "express";
16
13
  import { generateProtectedResourceMetadata, validateToken } from "@silkweave/auth";
14
+ import { AsyncLocalStorage } from "node:async_hooks";
17
15
  import cors from "cors";
18
- import express from "express";
19
16
  import { readFile, writeFile } from "fs/promises";
20
- import { AsyncLocalStorage } from "node:async_hooks";
17
+ import { InMemoryEventStore } from "@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js";
18
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
21
20
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
21
  //#region src/util/result.ts
23
22
  function smartToolResult(data) {
@@ -91,16 +90,37 @@ function parseResourceMessage({ resource }) {
91
90
  }
92
91
  //#endregion
93
92
  //#region src/adapter/cliProxy.ts
94
- function addCliOption(command, key, type, defaultValue) {
95
- const description = type.description;
93
+ function coerce(value, type) {
94
+ if (value === void 0) return;
95
+ if (type === "number" || type === "integer") {
96
+ const n = Number(value);
97
+ return Number.isNaN(n) ? value : n;
98
+ }
99
+ return value;
100
+ }
101
+ function addCliOption(command, key, prop) {
96
102
  const flag = kebabCase(key);
97
- if (type instanceof z.ZodBoolean) {
103
+ const description = prop.description;
104
+ const defaultValue = prop.default;
105
+ const type = prop.type;
106
+ if (type === "boolean") {
98
107
  command.option(`--${flag}`, description, defaultValue);
99
108
  command.option(`--no-${flag}`);
100
- } else if (type instanceof z.ZodNumber) command.option(`--${flag} <number>`, description, defaultValue);
101
- else if (type instanceof z.ZodString || type instanceof z.ZodEnum) command.option(`--${flag} <string>`, description, defaultValue);
102
- else if (type instanceof z.ZodObject || type instanceof z.ZodRecord || type instanceof z.ZodArray) command.option(`--${flag} <json>`, description ?? "", JSON.parse, defaultValue);
103
- else throw new Error(`Invalid zod type: ${type.def.type}`);
109
+ return;
110
+ }
111
+ if (type === "number" || type === "integer") {
112
+ command.option(`--${flag} <number>`, description, defaultValue);
113
+ return;
114
+ }
115
+ if (type === "string" || prop.enum) {
116
+ command.option(`--${flag} <string>`, description, defaultValue);
117
+ return;
118
+ }
119
+ if (type === "object" || type === "array") {
120
+ command.option(`--${flag} <json>`, description ?? "", JSON.parse, defaultValue);
121
+ return;
122
+ }
123
+ throw new Error(`Unsupported JSON Schema type for CLI option "${key}": ${type ?? "undefined"}`);
104
124
  }
105
125
  const defaultFormatter = (message) => {
106
126
  if (message.type === "text" && !message.text.includes("mcp://toolResult/")) return `${message.text}`;
@@ -126,13 +146,8 @@ const cliProxy = ({ url, formatter = defaultFormatter }) => {
126
146
  const name = kebabCase(tool.name);
127
147
  const command = program.command(name);
128
148
  if (tool.description) command.description(tool.description);
129
- const schema = z.fromJSONSchema(tool.inputSchema);
130
- if (!(schema instanceof z.ZodObject)) throw new Error("Invalid schema");
131
- const shape = schema.shape;
132
- for (const key of Object.keys(shape)) {
133
- const [type, { defaultValue }] = unwrap(shape[key]);
134
- addCliOption(command, key, type, defaultValue);
135
- }
149
+ const properties = tool.inputSchema.properties ?? {};
150
+ for (const key of Object.keys(properties)) addCliOption(command, key, properties[key]);
136
151
  command.action(async (args) => {
137
152
  const { silent } = program.opts();
138
153
  const logger = createCLILogger();
@@ -149,7 +164,11 @@ const cliProxy = ({ url, formatter = defaultFormatter }) => {
149
164
  });
150
165
  });
151
166
  }
152
- const input = await schema.parseAsync(args);
167
+ const input = {};
168
+ for (const key of Object.keys(properties)) {
169
+ const value = args[key];
170
+ if (value !== void 0) input[key] = coerce(value, properties[key]?.type);
171
+ }
153
172
  (await client.callTool({
154
173
  name: tool.name,
155
174
  arguments: input,
@@ -171,67 +190,147 @@ const cliProxy = ({ url, formatter = defaultFormatter }) => {
171
190
  };
172
191
  };
173
192
  //#endregion
174
- //#region src/adapter/http.ts
193
+ //#region src/handlers/auth.ts
194
+ /**
195
+ * Per-request bearer-token storage used by tool handlers to read the resolved
196
+ * `AuthInfo` for the currently-handled MCP call.
197
+ */
175
198
  const authStorage = new AsyncLocalStorage();
176
- /** Headers required by the MCP protocol that must always be exposed */
199
+ /**
200
+ * Express middleware that validates the `Authorization: Bearer …` header via
201
+ * the supplied `AuthConfig`. On success the resolved `AuthInfo` is placed in
202
+ * `authStorage` for the duration of the downstream handler - `mcpTransport`'s
203
+ * tool callbacks pick it up to populate the silkweave context's `auth` key.
204
+ *
205
+ * The middleware should NOT be applied to OAuth-discovery / token routes -
206
+ * compose it only on the routes that require an authenticated caller (the MCP
207
+ * transport itself, sideload, etc.).
208
+ */
209
+ function authMiddleware(auth, context) {
210
+ return async (req, res, next) => {
211
+ const result = await validateToken(req.headers.authorization, auth, context.fork({ request: req }));
212
+ if (result.error) {
213
+ for (const [key, value] of Object.entries(result.error.headers)) res.header(key, value);
214
+ res.status(result.error.statusCode).json(result.error.body);
215
+ return;
216
+ }
217
+ if (result.auth) authStorage.run(result.auth, () => {
218
+ next();
219
+ });
220
+ else next();
221
+ };
222
+ }
223
+ //#endregion
224
+ //#region src/handlers/cors.ts
225
+ /** Headers required by the MCP protocol that must always be exposed when CORS is in use. */
177
226
  const MCP_REQUIRED_HEADERS = [
178
227
  "WWW-Authenticate",
179
228
  "Mcp-Session-Id",
180
229
  "Last-Event-Id",
181
230
  "Mcp-Protocol-Version"
182
231
  ];
183
- function mountOAuthRoutes(app, auth) {
184
- const provider = auth.provider;
185
- const callbackPath = auth.callbackPath ?? "/auth/callback";
186
- const toOAuthReq = (req) => ({
232
+ /**
233
+ * CORS middleware preconfigured to expose the headers MCP clients need
234
+ * (`Mcp-Session-Id`, `Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`)
235
+ * on top of any user-supplied options.
236
+ *
237
+ * Pass `false` to disable, omit / pass `true` for permissive defaults, or pass
238
+ * a `CorsOptions` object to override.
239
+ */
240
+ function mcpCors(corsConfig = true) {
241
+ if (corsConfig === false) return null;
242
+ const userConfig = corsConfig === true ? {} : corsConfig;
243
+ const userExposed = userConfig.exposedHeaders;
244
+ const exposedHeaders = [...MCP_REQUIRED_HEADERS, ...Array.isArray(userExposed) ? userExposed : userExposed ? [userExposed] : []];
245
+ return cors({
246
+ origin: "*",
247
+ ...userConfig,
248
+ exposedHeaders
249
+ });
250
+ }
251
+ //#endregion
252
+ //#region src/handlers/metadata.ts
253
+ /**
254
+ * Handler for `GET /.well-known/oauth-protected-resource` (RFC 9728). Returns
255
+ * the resource server's metadata pointing at the configured authorization
256
+ * servers. Requires `auth.resourceUrl` and a non-empty `auth.authorizationServers`.
257
+ */
258
+ function protectedResourceMetadata(auth) {
259
+ if (!auth.resourceUrl || !auth.authorizationServers?.length) throw new Error("@silkweave/mcp protectedResourceMetadata(): auth.resourceUrl and auth.authorizationServers are required");
260
+ const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers);
261
+ return (_req, res) => {
262
+ res.json(metadata);
263
+ };
264
+ }
265
+ //#endregion
266
+ //#region src/handlers/oauth.ts
267
+ function toOAuthReq(req) {
268
+ return {
187
269
  method: req.method,
188
270
  url: new URL(req.url, `${req.protocol}://${req.get("host")}`),
189
271
  headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),
190
272
  body: req.body
191
- });
192
- const sendOAuth = (res, oauthRes) => {
193
- for (const [key, value] of Object.entries(oauthRes.headers)) res.header(key, value);
194
- if (oauthRes.body) res.status(oauthRes.status).send(typeof oauthRes.body === "string" ? oauthRes.body : JSON.stringify(oauthRes.body));
195
- else res.status(oauthRes.status).end();
196
273
  };
197
- app.get("/.well-known/oauth-authorization-server", (_req, res) => {
198
- sendOAuth(res, provider.metadata());
199
- });
200
- app.get("/authorize", async (req, res) => {
201
- sendOAuth(res, await provider.authorize(toOAuthReq(req)));
202
- });
203
- app.get(callbackPath, async (req, res) => {
204
- sendOAuth(res, await provider.callback(toOAuthReq(req)));
205
- });
206
- app.post("/token", express.urlencoded({ extended: false }), async (req, res) => {
207
- sendOAuth(res, await provider.token(toOAuthReq(req)));
208
- });
209
- app.post("/register", express.json(), async (req, res) => {
210
- sendOAuth(res, await provider.register(toOAuthReq(req)));
211
- });
212
- return new Set([
213
- "/.well-known/oauth-authorization-server",
214
- "/authorize",
215
- callbackPath,
216
- "/token",
217
- "/register"
218
- ]);
219
274
  }
220
- function mountAuthMiddleware(app, auth, oauthPaths, context) {
221
- app.use(async (req, res, next) => {
222
- if (req.path.startsWith("/.well-known/") || oauthPaths.has(req.path)) return next();
223
- const result = await validateToken(req.headers.authorization, auth, context.fork({ request: req }));
224
- if (result.error) {
225
- for (const [key, value] of Object.entries(result.error.headers)) res.header(key, value);
226
- res.status(result.error.statusCode).json(result.error.body);
227
- return;
228
- }
229
- if (result.auth) authStorage.run(result.auth, () => {
230
- next();
231
- });
232
- else next();
233
- });
275
+ function sendOAuth(res, oauthRes) {
276
+ for (const [key, value] of Object.entries(oauthRes.headers)) res.header(key, value);
277
+ if (oauthRes.body) res.status(oauthRes.status).send(typeof oauthRes.body === "string" ? oauthRes.body : JSON.stringify(oauthRes.body));
278
+ else res.status(oauthRes.status).end();
279
+ }
280
+ /**
281
+ * Build the OAuth 2.1 proxy route handlers (authorize, callback, token,
282
+ * register, well-known) backed by the configured `auth.provider`. Returns the
283
+ * handlers as individual `RequestHandler`s so callers can register them
284
+ * wherever they like - under `/mcp`, at the server root, etc.
285
+ *
286
+ * `token` and `register` are returned as middleware arrays because the
287
+ * appropriate body parser must run before the handler. Apply with
288
+ * `app.post(path, ...token)`.
289
+ */
290
+ function oauthRoutes(auth) {
291
+ if (!auth.provider) throw new Error("@silkweave/mcp oauthRoutes(): auth.provider is required");
292
+ const provider = auth.provider;
293
+ return {
294
+ callbackPath: auth.callbackPath ?? "/auth/callback",
295
+ wellKnownAuthServer: (_req, res) => {
296
+ sendOAuth(res, provider.metadata());
297
+ },
298
+ authorize: async (req, res) => {
299
+ sendOAuth(res, await provider.authorize(toOAuthReq(req)));
300
+ },
301
+ callback: async (req, res) => {
302
+ sendOAuth(res, await provider.callback(toOAuthReq(req)));
303
+ },
304
+ token: [express.urlencoded({ extended: false }), async (req, res) => {
305
+ sendOAuth(res, await provider.token(toOAuthReq(req)));
306
+ }],
307
+ register: [express.json(), async (req, res) => {
308
+ sendOAuth(res, await provider.register(toOAuthReq(req)));
309
+ }]
310
+ };
311
+ }
312
+ //#endregion
313
+ //#region src/handlers/sideload.ts
314
+ /**
315
+ * Handler for `GET /resource/:id` - serves a large MCP response that was
316
+ * sideloaded to disk as a `{id}` payload with a `{id}.json` metadata sidecar.
317
+ *
318
+ * The route param `id` is provided by the host framework (Express, Nest, etc.).
319
+ */
320
+ function sideloadResource(options = {}) {
321
+ const { resourceDir = "resources" } = options;
322
+ return async (req, res) => {
323
+ const id = req.params["id"];
324
+ if (!id || typeof id !== "string") throw new Error("Invalid ID");
325
+ const resourceMeta = JSON.parse(await readFile(`${resourceDir}/${id}.json`, "utf-8"));
326
+ const buffer = await readFile(`${resourceDir}/${id}`);
327
+ res.status(200);
328
+ res.header("Content-Type", resourceMeta.contentType);
329
+ res.send(buffer);
330
+ };
234
331
  }
332
+ //#endregion
333
+ //#region src/handlers/transport.ts
235
334
  function registerTools$1(server, actions, context) {
236
335
  for (const action of actions) server.registerTool(pascalCase(action.name), {
237
336
  title: capitalCase(action.name),
@@ -269,14 +368,28 @@ function registerTools$1(server, actions, context) {
269
368
  ...currentAuth ? { auth: currentAuth } : {}
270
369
  });
271
370
  const disposition = extra._meta?.disposition;
272
- return action.run(input, actionContext).then((result) => {
371
+ const progressToken = extra._meta?.progressToken;
372
+ try {
373
+ let result;
374
+ if (isStreamingAction(action)) result = await runStreamingAction(action, input, actionContext, progressToken ? async (chunk, index) => {
375
+ await extra.sendNotification({
376
+ method: "notifications/progress",
377
+ params: {
378
+ progressToken,
379
+ progress: index + 1,
380
+ message: JSON.stringify(chunk)
381
+ }
382
+ });
383
+ } : void 0);
384
+ else result = await action.run(input, actionContext);
273
385
  if (action.toolResult) {
274
386
  const response = action.toolResult(result, actionContext);
275
387
  if (response) return response;
276
388
  }
277
- if (disposition === "json") return jsonToolResult(result);
278
- else return smartToolResult(result);
279
- }).catch(handleToolError);
389
+ return disposition === "json" ? jsonToolResult(result) : smartToolResult(result);
390
+ } catch (error) {
391
+ return handleToolError(error);
392
+ }
280
393
  });
281
394
  }
282
395
  function createMcpServer(options, actions, context) {
@@ -297,7 +410,6 @@ function createSessionTransport(transports) {
297
410
  enableJsonResponse: false,
298
411
  eventStore: new InMemoryEventStore(),
299
412
  onsessioninitialized: (sId) => {
300
- console.log(`Session initialized with ID: ${sId}`);
301
413
  transports[sId] = transport;
302
414
  }
303
415
  });
@@ -306,15 +418,20 @@ function createSessionTransport(transports) {
306
418
  };
307
419
  transport.onclose = () => {
308
420
  const sid = transport.sessionId;
309
- if (sid && transports[sid]) {
310
- console.log(`Transport closed for session ${sid}, removing from transports map`);
311
- delete transports[sid];
312
- }
421
+ if (sid && transports[sid]) delete transports[sid];
313
422
  };
314
423
  return transport;
315
424
  }
316
- function mountMcpTransport(app, transports, createServer) {
317
- app.post("/mcp", async (req, res) => {
425
+ /**
426
+ * Build the MCP Streamable HTTP transport route handlers.
427
+ *
428
+ * Sessions are kept in a per-call closure (one map per `mcpTransport()` call),
429
+ * so the same handler object should be registered for all three transport
430
+ * routes - `POST /mcp`, `GET /mcp`, `DELETE /mcp` - to share session state.
431
+ */
432
+ function mcpTransport(silkweaveOptions, context, actions) {
433
+ const transports = {};
434
+ const post = async (req, res) => {
318
435
  const sessionId = req.headers["mcp-session-id"];
319
436
  try {
320
437
  if (sessionId && transports[sessionId]) {
@@ -333,7 +450,7 @@ function mountMcpTransport(app, transports, createServer) {
333
450
  return;
334
451
  }
335
452
  const transport = createSessionTransport(transports);
336
- await createServer().connect(transport);
453
+ await createMcpServer(silkweaveOptions, actions, context).connect(transport);
337
454
  await transport.handleRequest(req, res, req.body);
338
455
  } catch (error) {
339
456
  console.error("Error handling MCP request:", error);
@@ -346,8 +463,8 @@ function mountMcpTransport(app, transports, createServer) {
346
463
  id: null
347
464
  });
348
465
  }
349
- });
350
- const handleSessionStream = async (req, res) => {
466
+ };
467
+ const stream = async (req, res) => {
351
468
  const sessionId = req.headers["mcp-session-id"];
352
469
  if (!sessionId || !transports[sessionId]) {
353
470
  res.status(400).send("Invalid or missing session ID");
@@ -355,64 +472,106 @@ function mountMcpTransport(app, transports, createServer) {
355
472
  }
356
473
  await transports[sessionId].handleRequest(req, res);
357
474
  };
358
- app.get("/mcp", handleSessionStream);
359
- app.delete("/mcp", handleSessionStream);
360
- app.get("/mcp/resource/:id", handleSessionStream);
475
+ return {
476
+ post,
477
+ stream
478
+ };
361
479
  }
362
- const http = ({ host, port, auth, cors: corsConfig, ...mcpOptions }) => {
363
- return (options, baseContext) => {
364
- const context = baseContext.fork({ adapter: "http" });
365
- const app = createMcpExpressApp({
366
- ...mcpOptions,
367
- host
368
- });
369
- if (corsConfig !== false) {
370
- const userConfig = corsConfig === true || corsConfig === void 0 ? {} : corsConfig;
371
- const userExposed = userConfig.exposedHeaders;
372
- const exposedHeaders = [...MCP_REQUIRED_HEADERS, ...Array.isArray(userExposed) ? userExposed : userExposed ? [userExposed] : []];
373
- app.use(cors({
374
- origin: "*",
375
- ...userConfig,
376
- exposedHeaders
377
- }));
378
- }
379
- if (auth?.authorizationServers?.length && auth.resourceUrl) app.get("/.well-known/oauth-protected-resource", (_req, res) => {
380
- const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers);
381
- res.json(metadata);
480
+ //#endregion
481
+ //#region src/adapter/http.ts
482
+ /**
483
+ * Build a fully-wired Express app that exposes the MCP Streamable HTTP
484
+ * transport (plus OAuth / sideload / well-known routes when configured).
485
+ *
486
+ * Pass the resulting `app` to `app.listen(port, host)` yourself, or use the
487
+ * top-level `startMcpServer()` / `http()` adapter conveniences.
488
+ */
489
+ function buildMcpExpressApp(silkweaveOptions, context, actions, options) {
490
+ const { host, auth, cors: corsConfig, sideloadResources = true, resourceDir, ...mcpAppOptions } = options;
491
+ const app = createMcpExpressApp({
492
+ ...mcpAppOptions,
493
+ host
494
+ });
495
+ const corsHandler = mcpCors(corsConfig ?? true);
496
+ if (corsHandler) app.use(corsHandler);
497
+ if (auth?.authorizationServers?.length && auth.resourceUrl) app.get("/.well-known/oauth-protected-resource", protectedResourceMetadata(auth));
498
+ let oauthPaths = /* @__PURE__ */ new Set();
499
+ if (auth?.provider) {
500
+ const oauth = oauthRoutes(auth);
501
+ app.get("/.well-known/oauth-authorization-server", oauth.wellKnownAuthServer);
502
+ app.get("/authorize", oauth.authorize);
503
+ app.get(oauth.callbackPath, oauth.callback);
504
+ app.post("/token", ...oauth.token);
505
+ app.post("/register", ...oauth.register);
506
+ oauthPaths = new Set([
507
+ "/.well-known/oauth-authorization-server",
508
+ "/authorize",
509
+ oauth.callbackPath,
510
+ "/token",
511
+ "/register"
512
+ ]);
513
+ }
514
+ if (auth) {
515
+ const guard = authMiddleware(auth, context);
516
+ app.use((req, res, next) => {
517
+ if (req.path.startsWith("/.well-known/") || oauthPaths.has(req.path)) return next();
518
+ return guard(req, res, next);
382
519
  });
383
- const oauthPaths = auth?.provider ? mountOAuthRoutes(app, auth) : /* @__PURE__ */ new Set();
384
- if (auth) mountAuthMiddleware(app, auth, oauthPaths, context);
385
- const transports = {};
386
- let mountedActions = [];
387
- app.get("/resource/:id", async (req, res) => {
388
- const id = req.params.id;
389
- if (!id || typeof id !== "string") throw new Error("Invalid ID");
390
- const resourceMeta = JSON.parse(await readFile(`resources/${id}.json`, "utf-8"));
391
- const buffer = await readFile(`resources/${id}`);
392
- res.status(200);
393
- res.header("Content-Type", resourceMeta.contentType);
394
- res.send(buffer);
520
+ }
521
+ if (sideloadResources) app.get("/resource/:id", sideloadResource({ resourceDir }));
522
+ const transport = mcpTransport(silkweaveOptions, context, actions);
523
+ app.post("/mcp", express.json(), transport.post);
524
+ app.get("/mcp", transport.stream);
525
+ app.delete("/mcp", transport.stream);
526
+ app.get("/mcp/resource/:id", transport.stream);
527
+ return app;
528
+ }
529
+ /**
530
+ * Spin up a standalone MCP Streamable HTTP server on `host:port` for the
531
+ * given `actions`. Returns the underlying `Server` so callers can close it.
532
+ *
533
+ * Convenience for use cases that don't go through the `silkweave()` builder.
534
+ */
535
+ async function startMcpServer(silkweaveOptions, actions, options, context) {
536
+ const app = buildMcpExpressApp(silkweaveOptions, context ?? createContext({ adapter: "http" }), actions, options);
537
+ return new Promise((resolve, reject) => {
538
+ const server = app.listen(options.port, options.host, (error) => {
539
+ if (error) {
540
+ reject(error);
541
+ return;
542
+ }
543
+ console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`);
544
+ resolve(server);
395
545
  });
396
- mountMcpTransport(app, transports, () => createMcpServer(options, mountedActions, context));
546
+ });
547
+ }
548
+ /**
549
+ * Silkweave adapter that owns its own HTTP server. Composes the MCP handler
550
+ * primitives into a fully-wired Express app and listens on `host:port`.
551
+ */
552
+ const http = (options) => {
553
+ return (silkweaveOptions, baseContext) => {
554
+ const context = baseContext.fork({ adapter: "http" });
397
555
  let httpServer;
398
556
  return {
399
557
  context,
400
558
  start: async (actions) => {
401
- mountedActions = actions;
402
- httpServer = app.listen(port, host, (error) => {
403
- if (error) {
404
- console.error("Failed to start server:", error);
405
- process.exit(1);
406
- }
407
- console.log(`MCP Streamable HTTP Server listening on http://${host}:${port}/mcp`);
559
+ const app = buildMcpExpressApp(silkweaveOptions, context, actions, options);
560
+ httpServer = await new Promise((resolve, reject) => {
561
+ const s = app.listen(options.port, options.host, (error) => {
562
+ if (error) {
563
+ reject(error);
564
+ return;
565
+ }
566
+ console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`);
567
+ resolve(s);
568
+ });
408
569
  });
409
570
  },
410
571
  stop: async () => {
411
- if (httpServer) await new Promise((resolve, reject) => {
412
- return httpServer.close((err) => {
413
- if (err) reject(err);
414
- else resolve();
415
- });
572
+ if (!httpServer) return;
573
+ await new Promise((resolve, reject) => {
574
+ httpServer.close((err) => err ? reject(err) : resolve());
416
575
  });
417
576
  httpServer = void 0;
418
577
  }
@@ -455,7 +614,24 @@ function registerTools(server, actions, context) {
455
614
  logger,
456
615
  extra
457
616
  });
458
- return action.run(input, actionContext).then((result) => action.toolResult?.(result, actionContext) ?? smartToolResult(result)).catch(handleToolError);
617
+ const progressToken = extra._meta?.progressToken;
618
+ try {
619
+ let result;
620
+ if (isStreamingAction(action)) result = await runStreamingAction(action, input, actionContext, progressToken ? async (chunk, index) => {
621
+ await extra.sendNotification({
622
+ method: "notifications/progress",
623
+ params: {
624
+ progressToken,
625
+ progress: index + 1,
626
+ message: JSON.stringify(chunk)
627
+ }
628
+ });
629
+ } : void 0);
630
+ else result = await action.run(input, actionContext);
631
+ return action.toolResult?.(result, actionContext) ?? smartToolResult(result);
632
+ } catch (error) {
633
+ return handleToolError(error);
634
+ }
459
635
  });
460
636
  }
461
637
  const stdio = () => {
@@ -496,6 +672,6 @@ async function createSideloadResource(buffer, { name, contentType }) {
496
672
  return resource;
497
673
  }
498
674
  //#endregion
499
- export { cliProxy, createSideloadResource, errorToolResult, handleToolError, http, jsonToolResult, parseResourceMessage, smartToolResult, stdio };
675
+ export { MCP_REQUIRED_HEADERS, authMiddleware, authStorage, buildMcpExpressApp, cliProxy, createSideloadResource, errorToolResult, handleToolError, http, jsonToolResult, mcpCors, mcpTransport, oauthRoutes, parseResourceMessage, protectedResourceMetadata, sideloadResource, smartToolResult, startMcpServer, stdio };
500
676
 
501
677
  //# sourceMappingURL=index.mjs.map