@silkweave/mcp 1.9.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,8 +190,38 @@ const cliProxy = ({ url, formatter = defaultFormatter }) => {
171
190
  };
172
191
  };
173
192
  //#endregion
174
- //#region src/lib/handler.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();
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
176
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",
@@ -180,6 +229,41 @@ const MCP_REQUIRED_HEADERS = [
180
229
  "Last-Event-Id",
181
230
  "Mcp-Protocol-Version"
182
231
  ];
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
183
267
  function toOAuthReq(req) {
184
268
  return {
185
269
  method: req.method,
@@ -193,47 +277,60 @@ function sendOAuth(res, oauthRes) {
193
277
  if (oauthRes.body) res.status(oauthRes.status).send(typeof oauthRes.body === "string" ? oauthRes.body : JSON.stringify(oauthRes.body));
194
278
  else res.status(oauthRes.status).end();
195
279
  }
196
- function mountOAuthRoutes(app, auth) {
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");
197
292
  const provider = auth.provider;
198
- const callbackPath = auth.callbackPath ?? "/auth/callback";
199
- app.get("/.well-known/oauth-authorization-server", (_req, res) => {
200
- sendOAuth(res, provider.metadata());
201
- });
202
- app.get("/authorize", async (req, res) => {
203
- sendOAuth(res, await provider.authorize(toOAuthReq(req)));
204
- });
205
- app.get(callbackPath, async (req, res) => {
206
- sendOAuth(res, await provider.callback(toOAuthReq(req)));
207
- });
208
- app.post("/token", express.urlencoded({ extended: false }), async (req, res) => {
209
- sendOAuth(res, await provider.token(toOAuthReq(req)));
210
- });
211
- app.post("/register", express.json(), async (req, res) => {
212
- sendOAuth(res, await provider.register(toOAuthReq(req)));
213
- });
214
- return new Set([
215
- "/.well-known/oauth-authorization-server",
216
- "/authorize",
217
- callbackPath,
218
- "/token",
219
- "/register"
220
- ]);
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
+ };
221
311
  }
222
- function mountAuthMiddleware(app, auth, oauthPaths, context) {
223
- app.use(async (req, res, next) => {
224
- if (req.path.startsWith("/.well-known/") || oauthPaths.has(req.path)) return next();
225
- const result = await validateToken(req.headers.authorization, auth, context.fork({ request: req }));
226
- if (result.error) {
227
- for (const [key, value] of Object.entries(result.error.headers)) res.header(key, value);
228
- res.status(result.error.statusCode).json(result.error.body);
229
- return;
230
- }
231
- if (result.auth) authStorage.run(result.auth, () => {
232
- next();
233
- });
234
- else next();
235
- });
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
+ };
236
331
  }
332
+ //#endregion
333
+ //#region src/handlers/transport.ts
237
334
  function registerTools$1(server, actions, context) {
238
335
  for (const action of actions) server.registerTool(pascalCase(action.name), {
239
336
  title: capitalCase(action.name),
@@ -271,14 +368,28 @@ function registerTools$1(server, actions, context) {
271
368
  ...currentAuth ? { auth: currentAuth } : {}
272
369
  });
273
370
  const disposition = extra._meta?.disposition;
274
- 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);
275
385
  if (action.toolResult) {
276
386
  const response = action.toolResult(result, actionContext);
277
387
  if (response) return response;
278
388
  }
279
- if (disposition === "json") return jsonToolResult(result);
280
- else return smartToolResult(result);
281
- }).catch(handleToolError);
389
+ return disposition === "json" ? jsonToolResult(result) : smartToolResult(result);
390
+ } catch (error) {
391
+ return handleToolError(error);
392
+ }
282
393
  });
283
394
  }
284
395
  function createMcpServer(options, actions, context) {
@@ -299,7 +410,6 @@ function createSessionTransport(transports) {
299
410
  enableJsonResponse: false,
300
411
  eventStore: new InMemoryEventStore(),
301
412
  onsessioninitialized: (sId) => {
302
- console.log(`Session initialized with ID: ${sId}`);
303
413
  transports[sId] = transport;
304
414
  }
305
415
  });
@@ -308,15 +418,20 @@ function createSessionTransport(transports) {
308
418
  };
309
419
  transport.onclose = () => {
310
420
  const sid = transport.sessionId;
311
- if (sid && transports[sid]) {
312
- console.log(`Transport closed for session ${sid}, removing from transports map`);
313
- delete transports[sid];
314
- }
421
+ if (sid && transports[sid]) delete transports[sid];
315
422
  };
316
423
  return transport;
317
424
  }
318
- function mountMcpTransport(app, transports, createServer) {
319
- 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) => {
320
435
  const sessionId = req.headers["mcp-session-id"];
321
436
  try {
322
437
  if (sessionId && transports[sessionId]) {
@@ -335,7 +450,7 @@ function mountMcpTransport(app, transports, createServer) {
335
450
  return;
336
451
  }
337
452
  const transport = createSessionTransport(transports);
338
- await createServer().connect(transport);
453
+ await createMcpServer(silkweaveOptions, actions, context).connect(transport);
339
454
  await transport.handleRequest(req, res, req.body);
340
455
  } catch (error) {
341
456
  console.error("Error handling MCP request:", error);
@@ -348,8 +463,8 @@ function mountMcpTransport(app, transports, createServer) {
348
463
  id: null
349
464
  });
350
465
  }
351
- });
352
- const handleSessionStream = async (req, res) => {
466
+ };
467
+ const stream = async (req, res) => {
353
468
  const sessionId = req.headers["mcp-session-id"];
354
469
  if (!sessionId || !transports[sessionId]) {
355
470
  res.status(400).send("Invalid or missing session ID");
@@ -357,83 +472,106 @@ function mountMcpTransport(app, transports, createServer) {
357
472
  }
358
473
  await transports[sessionId].handleRequest(req, res);
359
474
  };
360
- app.get("/mcp", handleSessionStream);
361
- app.delete("/mcp", handleSessionStream);
362
- app.get("/mcp/resource/:id", handleSessionStream);
475
+ return {
476
+ post,
477
+ stream
478
+ };
363
479
  }
480
+ //#endregion
481
+ //#region src/adapter/http.ts
364
482
  /**
365
- * Build an Express sub-app exposing the MCP Streamable HTTP transport, OAuth
366
- * routes (if configured), and bearer-token auth middleware. The returned app
367
- * can be listened on directly OR mounted onto an existing server via
368
- * `parentApp.use(basePath, app)` / Nest's `httpAdapter.use(basePath, app)`.
483
+ * Build a fully-wired Express app that exposes the MCP Streamable HTTP
484
+ * transport (plus OAuth / sideload / well-known routes when configured).
369
485
  *
370
- * Used by `@silkweave/mcp`'s `http()` adapter (server-owning) and
371
- * `@silkweave/nestjs`'s `mcp()` adapter (mounts on Nest's HTTP server).
486
+ * Pass the resulting `app` to `app.listen(port, host)` yourself, or use the
487
+ * top-level `startMcpServer()` / `http()` adapter conveniences.
372
488
  */
373
- function createMcpExpressHandler(silkweaveOptions, context, actions, options = {}) {
374
- const { auth, cors: corsConfig, host = "0.0.0.0", sideloadResources = true, ...mcpOptions } = options;
489
+ function buildMcpExpressApp(silkweaveOptions, context, actions, options) {
490
+ const { host, auth, cors: corsConfig, sideloadResources = true, resourceDir, ...mcpAppOptions } = options;
375
491
  const app = createMcpExpressApp({
376
- ...mcpOptions,
492
+ ...mcpAppOptions,
377
493
  host
378
494
  });
379
- if (corsConfig !== false) {
380
- const userConfig = corsConfig === true || corsConfig === void 0 ? {} : corsConfig;
381
- const userExposed = userConfig.exposedHeaders;
382
- const exposedHeaders = [...MCP_REQUIRED_HEADERS, ...Array.isArray(userExposed) ? userExposed : userExposed ? [userExposed] : []];
383
- app.use(cors({
384
- origin: "*",
385
- ...userConfig,
386
- exposedHeaders
387
- }));
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
+ ]);
388
513
  }
389
- if (auth?.authorizationServers?.length && auth.resourceUrl) app.get("/.well-known/oauth-protected-resource", (_req, res) => {
390
- const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers);
391
- res.json(metadata);
392
- });
393
- const oauthPaths = auth?.provider ? mountOAuthRoutes(app, auth) : /* @__PURE__ */ new Set();
394
- if (auth) mountAuthMiddleware(app, auth, oauthPaths, context);
395
- if (sideloadResources) app.get("/resource/:id", async (req, res) => {
396
- const id = req.params.id;
397
- if (!id || typeof id !== "string") throw new Error("Invalid ID");
398
- const resourceMeta = JSON.parse(await readFile(`resources/${id}.json`, "utf-8"));
399
- const buffer = await readFile(`resources/${id}`);
400
- res.status(200);
401
- res.header("Content-Type", resourceMeta.contentType);
402
- res.send(buffer);
403
- });
404
- mountMcpTransport(app, {}, () => createMcpServer(silkweaveOptions, actions, context));
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);
519
+ });
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);
405
527
  return app;
406
528
  }
407
- //#endregion
408
- //#region src/adapter/http.ts
409
- const http = ({ host, port, auth, cors: corsConfig, ...mcpOptions }) => {
410
- return (options, baseContext) => {
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);
545
+ });
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) => {
411
554
  const context = baseContext.fork({ adapter: "http" });
412
555
  let httpServer;
413
- let app;
414
556
  return {
415
557
  context,
416
558
  start: async (actions) => {
417
- app = createMcpExpressHandler(options, context, actions, {
418
- ...mcpOptions,
419
- host,
420
- auth,
421
- cors: corsConfig
422
- });
423
- httpServer = app.listen(port, host, (error) => {
424
- if (error) {
425
- console.error("Failed to start server:", error);
426
- process.exit(1);
427
- }
428
- 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
+ });
429
569
  });
430
570
  },
431
571
  stop: async () => {
432
- if (httpServer) await new Promise((resolve, reject) => {
433
- return httpServer.close((err) => {
434
- if (err) reject(err);
435
- else resolve();
436
- });
572
+ if (!httpServer) return;
573
+ await new Promise((resolve, reject) => {
574
+ httpServer.close((err) => err ? reject(err) : resolve());
437
575
  });
438
576
  httpServer = void 0;
439
577
  }
@@ -476,7 +614,24 @@ function registerTools(server, actions, context) {
476
614
  logger,
477
615
  extra
478
616
  });
479
- 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
+ }
480
635
  });
481
636
  }
482
637
  const stdio = () => {
@@ -517,6 +672,6 @@ async function createSideloadResource(buffer, { name, contentType }) {
517
672
  return resource;
518
673
  }
519
674
  //#endregion
520
- export { MCP_REQUIRED_HEADERS, cliProxy, createMcpExpressHandler, 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 };
521
676
 
522
677
  //# sourceMappingURL=index.mjs.map