@silkweave/mcp 2.6.1 → 3.1.0

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/README.md CHANGED
@@ -39,7 +39,7 @@ Configure in Claude Desktop or Claude Code:
39
39
 
40
40
  ### http
41
41
 
42
- Session-based MCP transport over HTTP with SSE streaming and resumability.
42
+ **Stateless** MCP transport over HTTP with per-call SSE streaming (no sessions, so any request can hit any instance behind a plain load balancer).
43
43
 
44
44
  ```typescript
45
45
  import { silkweave } from '@silkweave/core'
@@ -51,7 +51,7 @@ await silkweave({ name: 'my-tools', description: 'My Tools', version: '1.0.0' })
51
51
  .start()
52
52
  ```
53
53
 
54
- Exposes `POST /mcp` (invoke tools), `GET /mcp` (SSE stream), and `DELETE /mcp` (terminate session). Built on Express with `StreamableHTTPServerTransport` from the MCP SDK.
54
+ Exposes a single stateless `POST /mcp` (each call mints a fresh transport with `sessionIdGenerator: undefined`; progress streams over SSE per call). No `Mcp-Session-Id`, no `GET`/`DELETE` reconnect. Built on Express with `StreamableHTTPServerTransport` from the MCP SDK.
55
55
 
56
56
  | Option | Type | Description |
57
57
  |--------|------|-------------|
@@ -62,7 +62,7 @@ Exposes `POST /mcp` (invoke tools), `GET /mcp` (SSE stream), and `DELETE /mcp` (
62
62
 
63
63
  ### cliProxy
64
64
 
65
- MCP CLI proxy client - connects to a running HTTP MCP server and invokes tools from the command line. Imported from the dedicated `@silkweave/mcp/cli-proxy` subpath (kept out of the package root so importing the `stdio`/`http` servers does not pull the CLI client's `commander` + `@clack/prompts` into the server path). Those two are **optional peer dependencies** - install them alongside `@silkweave/mcp` when you use the CLI proxy.
65
+ MCP CLI proxy client - connects to a running HTTP MCP server and invokes tools from the command line. Imported from the dedicated `@silkweave/mcp/cli-proxy` subpath (kept out of the package root so importing the `stdio`/`http` servers does not pull the CLI client's `commander` into the server path). `commander` is an **optional peer dependency** - install it alongside `@silkweave/mcp` when you use the CLI proxy.
66
66
 
67
67
  ```typescript
68
68
  import { silkweave } from '@silkweave/core'
@@ -194,4 +194,4 @@ All result utilities are exported from `@silkweave/mcp`:
194
194
 
195
195
  - [Silkweave README](https://github.com/silkweave/silkweave) - Full documentation
196
196
  - [`@silkweave/core`](https://www.npmjs.com/package/@silkweave/core) - Core library
197
- - [`@silkweave/vercel`](https://www.npmjs.com/package/@silkweave/vercel) - Stateless MCP for Vercel
197
+ - [`@silkweave/edge`](https://www.npmjs.com/package/@silkweave/edge) - Stateless MCP on Web-Standard edge/serverless runtimes (Cloudflare Workers, Vercel, Bun, Deno)
@@ -1 +1 @@
1
- {"version":3,"file":"cliProxy.d.mts","names":[],"sources":["../src/adapter/cliProxy.ts"],"mappings":";;;;KAWY,cAAA,IAAkB,OAAA,EAAS,YAAA,EAAc,KAAA,UAAe,QAAA,EAAU,YAAA;AAAA,UAE7D,eAAA;EACf,GAAA,EAAK,GAAA;EACL,SAAA,GAAY,cAAA;AAAA;AAAA,cA4DD,QAAA,EAAU,cAAA,CAAe,eAAA"}
1
+ {"version":3,"file":"cliProxy.d.mts","names":[],"sources":["../src/adapter/cliProxy.ts"],"mappings":";;;;KASY,cAAA,IAAkB,OAAA,EAAS,YAAA,EAAc,KAAA,UAAe,QAAA,EAAU,YAAA;AAAA,UAE7D,eAAA;EACf,GAAA,EAAK,GAAA;EACL,SAAA,GAAY,cAAA;AAAA;AAAA,cA4DD,QAAA,EAAU,cAAA,CAAe,eAAA"}
@@ -1,11 +1,10 @@
1
- import { i as parseResourceMessage } from "./result-BiFuFt5B.mjs";
2
- import { LoggingMessageNotificationSchema, ProgressNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
3
- import { createCLILogger } from "@silkweave/logger";
1
+ import { i as parseResourceMessage } from "./result-B_9Eo1ey.mjs";
2
+ import { createConsoleLogger } from "@silkweave/core";
4
3
  import { kebabCase } from "change-case";
5
4
  import { randomUUID } from "crypto";
6
- import { intro } from "@clack/prompts";
7
5
  import { Client } from "@modelcontextprotocol/sdk/client";
8
6
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7
+ import { LoggingMessageNotificationSchema, ProgressNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
9
8
  import { Command } from "commander";
10
9
  //#region src/adapter/cliProxy.ts
11
10
  function coerce(value, type) {
@@ -68,9 +67,9 @@ const cliProxy = ({ url, formatter = defaultFormatter }) => {
68
67
  for (const key of Object.keys(properties)) addCliOption(command, key, properties[key]);
69
68
  command.action(async (args) => {
70
69
  const { silent } = program.opts();
71
- const logger = createCLILogger();
70
+ const logger = createConsoleLogger();
72
71
  if (!silent) {
73
- intro(`${options.name} - ${tool.name}`);
72
+ console.info(`${options.name} - ${tool.name}`);
74
73
  client.setNotificationHandler(LoggingMessageNotificationSchema, ({ params: { level, data } }) => {
75
74
  logger[level](data);
76
75
  });
@@ -1 +1 @@
1
- {"version":3,"file":"cliProxy.mjs","names":[],"sources":["../src/adapter/cliProxy.ts"],"sourcesContent":["import { intro } from '@clack/prompts'\nimport { Client } from '@modelcontextprotocol/sdk/client'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { ContentBlock, LoggingMessageNotificationSchema, ProgressNotificationSchema, ToolResultContent } from '@modelcontextprotocol/sdk/types.js'\nimport { AdapterFactory } from '@silkweave/core'\nimport { createCLILogger } from '@silkweave/logger'\nimport { kebabCase } from 'change-case'\nimport { Command } from 'commander'\nimport { randomUUID } from 'crypto'\nimport { parseResourceMessage } from '../util/result.js'\n\nexport type CLIFormatterFn = (message: ContentBlock, index: number, messages: ContentBlock[]) => string | undefined\n\nexport interface CliProxyOptions {\n url: URL\n formatter?: CLIFormatterFn\n}\n\ninterface JsonSchemaProperty {\n type?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | string\n description?: string\n default?: unknown\n enum?: unknown[]\n}\n\ninterface JsonSchemaObject {\n type?: 'object'\n properties?: Record<string, JsonSchemaProperty>\n required?: string[]\n}\n\nfunction coerce(value: unknown, type: JsonSchemaProperty['type']): unknown {\n if (value === undefined) { return undefined }\n if (type === 'number' || type === 'integer') {\n const n = Number(value)\n return Number.isNaN(n) ? value : n\n }\n return value\n}\n\nfunction addCliOption(command: Command, key: string, prop: JsonSchemaProperty) {\n const flag = kebabCase(key)\n const description = prop.description\n const defaultValue = prop.default\n const type = prop.type\n if (type === 'boolean') {\n command.option(`--${flag}`, description, defaultValue as boolean | undefined)\n command.option(`--no-${flag}`)\n return\n }\n if (type === 'number' || type === 'integer') {\n command.option(`--${flag} <number>`, description, defaultValue as string | undefined)\n return\n }\n if (type === 'string' || prop.enum) {\n command.option(`--${flag} <string>`, description, defaultValue as string | undefined)\n return\n }\n if (type === 'object' || type === 'array') {\n command.option(`--${flag} <json>`, description ?? '', JSON.parse, defaultValue as never)\n return\n }\n throw new Error(`Unsupported JSON Schema type for CLI option \"${key}\": ${type ?? 'undefined'}`)\n}\n\nconst defaultFormatter: CLIFormatterFn = (message) => {\n if (message.type === 'text' && !message.text.includes('mcp://toolResult/')) {\n return `${message.text}`\n } else if (message.type === 'resource') {\n return parseResourceMessage(message)\n } else {\n return JSON.stringify(message)\n }\n}\n\nexport const cliProxy: AdapterFactory<CliProxyOptions> = ({ url, formatter = defaultFormatter }) => {\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'cliProxy' })\n const program = new Command()\n .name(options.name)\n .description(options.description)\n .version(options.version)\n .option('-s, --silent', 'Silent mode, prevent log messages', false)\n\n return {\n context,\n start: async () => {\n const client = new Client({\n name: options.name,\n description: options.description,\n version: options.version\n })\n const transport = new StreamableHTTPClientTransport(url)\n await client.connect(transport)\n const { tools } = await client.listTools()\n for (const tool of tools) {\n const name = kebabCase(tool.name)\n const command = program.command(name)\n if (tool.description) { command.description(tool.description) }\n const schema = tool.inputSchema as JsonSchemaObject\n const properties = schema.properties ?? {}\n for (const key of Object.keys(properties)) {\n addCliOption(command, key, properties[key])\n }\n command.action(async (args: Record<string, unknown>) => {\n const { silent } = program.opts<{ silent: boolean }>()\n const logger = createCLILogger()\n if (!silent) {\n intro(`${options.name} - ${tool.name}`)\n client.setNotificationHandler(LoggingMessageNotificationSchema, ({ params: { level, data } }) => {\n logger[level](data)\n })\n client.setNotificationHandler(ProgressNotificationSchema, ({ params: { progress, total, message } }) => {\n logger.info({ progress, total, message })\n })\n }\n const input: Record<string, unknown> = {}\n for (const key of Object.keys(properties)) {\n const value = args[key]\n if (value !== undefined) { input[key] = coerce(value, properties[key]?.type) }\n }\n const response = await client.callTool({\n name: tool.name,\n arguments: input,\n _meta: { progressToken: randomUUID(), disposition: 'json' }\n }) as ToolResultContent\n response.content.forEach((message, index, messages) => {\n const text = formatter(message, index, messages)\n process.stdout.write(`${text}\\n`)\n })\n })\n }\n await program.parseAsync()\n await transport.close()\n },\n stop: async () => { /* noop */ }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cliProxy.mjs","names":[],"sources":["../src/adapter/cliProxy.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client'\nimport { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'\nimport { ContentBlock, LoggingMessageNotificationSchema, ProgressNotificationSchema, ToolResultContent } from '@modelcontextprotocol/sdk/types.js'\nimport { AdapterFactory, createConsoleLogger } from '@silkweave/core'\nimport { kebabCase } from 'change-case'\nimport { Command } from 'commander'\nimport { randomUUID } from 'crypto'\nimport { parseResourceMessage } from '../util/result.js'\n\nexport type CLIFormatterFn = (message: ContentBlock, index: number, messages: ContentBlock[]) => string | undefined\n\nexport interface CliProxyOptions {\n url: URL\n formatter?: CLIFormatterFn\n}\n\ninterface JsonSchemaProperty {\n type?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | string\n description?: string\n default?: unknown\n enum?: unknown[]\n}\n\ninterface JsonSchemaObject {\n type?: 'object'\n properties?: Record<string, JsonSchemaProperty>\n required?: string[]\n}\n\nfunction coerce(value: unknown, type: JsonSchemaProperty['type']): unknown {\n if (value === undefined) { return undefined }\n if (type === 'number' || type === 'integer') {\n const n = Number(value)\n return Number.isNaN(n) ? value : n\n }\n return value\n}\n\nfunction addCliOption(command: Command, key: string, prop: JsonSchemaProperty) {\n const flag = kebabCase(key)\n const description = prop.description\n const defaultValue = prop.default\n const type = prop.type\n if (type === 'boolean') {\n command.option(`--${flag}`, description, defaultValue as boolean | undefined)\n command.option(`--no-${flag}`)\n return\n }\n if (type === 'number' || type === 'integer') {\n command.option(`--${flag} <number>`, description, defaultValue as string | undefined)\n return\n }\n if (type === 'string' || prop.enum) {\n command.option(`--${flag} <string>`, description, defaultValue as string | undefined)\n return\n }\n if (type === 'object' || type === 'array') {\n command.option(`--${flag} <json>`, description ?? '', JSON.parse, defaultValue as never)\n return\n }\n throw new Error(`Unsupported JSON Schema type for CLI option \"${key}\": ${type ?? 'undefined'}`)\n}\n\nconst defaultFormatter: CLIFormatterFn = (message) => {\n if (message.type === 'text' && !message.text.includes('mcp://toolResult/')) {\n return `${message.text}`\n } else if (message.type === 'resource') {\n return parseResourceMessage(message)\n } else {\n return JSON.stringify(message)\n }\n}\n\nexport const cliProxy: AdapterFactory<CliProxyOptions> = ({ url, formatter = defaultFormatter }) => {\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'cliProxy' })\n const program = new Command()\n .name(options.name)\n .description(options.description)\n .version(options.version)\n .option('-s, --silent', 'Silent mode, prevent log messages', false)\n\n return {\n context,\n start: async () => {\n const client = new Client({\n name: options.name,\n description: options.description,\n version: options.version\n })\n const transport = new StreamableHTTPClientTransport(url)\n await client.connect(transport)\n const { tools } = await client.listTools()\n for (const tool of tools) {\n const name = kebabCase(tool.name)\n const command = program.command(name)\n if (tool.description) { command.description(tool.description) }\n const schema = tool.inputSchema as JsonSchemaObject\n const properties = schema.properties ?? {}\n for (const key of Object.keys(properties)) {\n addCliOption(command, key, properties[key])\n }\n command.action(async (args: Record<string, unknown>) => {\n const { silent } = program.opts<{ silent: boolean }>()\n const logger = createConsoleLogger()\n if (!silent) {\n console.info(`${options.name} - ${tool.name}`)\n client.setNotificationHandler(LoggingMessageNotificationSchema, ({ params: { level, data } }) => {\n logger[level](data)\n })\n client.setNotificationHandler(ProgressNotificationSchema, ({ params: { progress, total, message } }) => {\n logger.info({ progress, total, message })\n })\n }\n const input: Record<string, unknown> = {}\n for (const key of Object.keys(properties)) {\n const value = args[key]\n if (value !== undefined) { input[key] = coerce(value, properties[key]?.type) }\n }\n const response = await client.callTool({\n name: tool.name,\n arguments: input,\n _meta: { progressToken: randomUUID(), disposition: 'json' }\n }) as ToolResultContent\n response.content.forEach((message, index, messages) => {\n const text = formatter(message, index, messages)\n process.stdout.write(`${text}\\n`)\n })\n })\n }\n await program.parseAsync()\n await transport.close()\n },\n stop: async () => { /* noop */ }\n }\n }\n}\n"],"mappings":";;;;;;;;;AA6BA,SAAS,OAAO,OAAgB,MAA2C;AACzE,KAAI,UAAU,KAAA,EAAa;AAC3B,KAAI,SAAS,YAAY,SAAS,WAAW;EAC3C,MAAM,IAAI,OAAO,MAAM;AACvB,SAAO,OAAO,MAAM,EAAE,GAAG,QAAQ;;AAEnC,QAAO;;AAGT,SAAS,aAAa,SAAkB,KAAa,MAA0B;CAC7E,MAAM,OAAO,UAAU,IAAI;CAC3B,MAAM,cAAc,KAAK;CACzB,MAAM,eAAe,KAAK;CAC1B,MAAM,OAAO,KAAK;AAClB,KAAI,SAAS,WAAW;AACtB,UAAQ,OAAO,KAAK,QAAQ,aAAa,aAAoC;AAC7E,UAAQ,OAAO,QAAQ,OAAO;AAC9B;;AAEF,KAAI,SAAS,YAAY,SAAS,WAAW;AAC3C,UAAQ,OAAO,KAAK,KAAK,YAAY,aAAa,aAAmC;AACrF;;AAEF,KAAI,SAAS,YAAY,KAAK,MAAM;AAClC,UAAQ,OAAO,KAAK,KAAK,YAAY,aAAa,aAAmC;AACrF;;AAEF,KAAI,SAAS,YAAY,SAAS,SAAS;AACzC,UAAQ,OAAO,KAAK,KAAK,UAAU,eAAe,IAAI,KAAK,OAAO,aAAsB;AACxF;;AAEF,OAAM,IAAI,MAAM,gDAAgD,IAAI,KAAK,QAAQ,cAAc;;AAGjG,MAAM,oBAAoC,YAAY;AACpD,KAAI,QAAQ,SAAS,UAAU,CAAC,QAAQ,KAAK,SAAS,oBAAoB,CACxE,QAAO,GAAG,QAAQ;UACT,QAAQ,SAAS,WAC1B,QAAO,qBAAqB,QAAQ;KAEpC,QAAO,KAAK,UAAU,QAAQ;;AAIlC,MAAa,YAA6C,EAAE,KAAK,YAAY,uBAAuB;AAClG,SAAQ,SAAS,gBAAgB;EAC/B,MAAM,UAAU,YAAY,KAAK,EAAE,SAAS,YAAY,CAAC;EACzD,MAAM,UAAU,IAAI,SAAS,CAC1B,KAAK,QAAQ,KAAK,CAClB,YAAY,QAAQ,YAAY,CAChC,QAAQ,QAAQ,QAAQ,CACxB,OAAO,gBAAgB,qCAAqC,MAAM;AAErE,SAAO;GACL;GACA,OAAO,YAAY;IACjB,MAAM,SAAS,IAAI,OAAO;KACxB,MAAM,QAAQ;KACd,aAAa,QAAQ;KACrB,SAAS,QAAQ;KAClB,CAAC;IACF,MAAM,YAAY,IAAI,8BAA8B,IAAI;AACxD,UAAM,OAAO,QAAQ,UAAU;IAC/B,MAAM,EAAE,UAAU,MAAM,OAAO,WAAW;AAC1C,SAAK,MAAM,QAAQ,OAAO;KACxB,MAAM,OAAO,UAAU,KAAK,KAAK;KACjC,MAAM,UAAU,QAAQ,QAAQ,KAAK;AACrC,SAAI,KAAK,YAAe,SAAQ,YAAY,KAAK,YAAY;KAE7D,MAAM,aADS,KAAK,YACM,cAAc,EAAE;AAC1C,UAAK,MAAM,OAAO,OAAO,KAAK,WAAW,CACvC,cAAa,SAAS,KAAK,WAAW,KAAK;AAE7C,aAAQ,OAAO,OAAO,SAAkC;MACtD,MAAM,EAAE,WAAW,QAAQ,MAA2B;MACtD,MAAM,SAAS,qBAAqB;AACpC,UAAI,CAAC,QAAQ;AACX,eAAQ,KAAK,GAAG,QAAQ,KAAK,KAAK,KAAK,OAAO;AAC9C,cAAO,uBAAuB,mCAAmC,EAAE,QAAQ,EAAE,OAAO,aAAa;AAC/F,eAAO,OAAO,KAAK;SACnB;AACF,cAAO,uBAAuB,6BAA6B,EAAE,QAAQ,EAAE,UAAU,OAAO,gBAAgB;AACtG,eAAO,KAAK;SAAE;SAAU;SAAO;SAAS,CAAC;SACzC;;MAEJ,MAAM,QAAiC,EAAE;AACzC,WAAK,MAAM,OAAO,OAAO,KAAK,WAAW,EAAE;OACzC,MAAM,QAAQ,KAAK;AACnB,WAAI,UAAU,KAAA,EAAa,OAAM,OAAO,OAAO,OAAO,WAAW,MAAM,KAAK;;AAO9E,OAAA,MALuB,OAAO,SAAS;OACrC,MAAM,KAAK;OACX,WAAW;OACX,OAAO;QAAE,eAAe,YAAY;QAAE,aAAa;QAAQ;OAC5D,CAAC,EACO,QAAQ,SAAS,SAAS,OAAO,aAAa;OACrD,MAAM,OAAO,UAAU,SAAS,OAAO,SAAS;AAChD,eAAQ,OAAO,MAAM,GAAG,KAAK,IAAI;QACjC;OACF;;AAEJ,UAAM,QAAQ,YAAY;AAC1B,UAAM,UAAU,OAAO;;GAEzB,MAAM,YAAY;GACnB"}
package/build/index.d.mts CHANGED
@@ -1,10 +1,10 @@
1
+ import { a as smartToolResult, c as requestFromExtra, i as parseResourceMessage, n as handleToolError, o as RegisterToolsOptions, r as jsonToolResult, s as registerTools, t as errorToolResult } from "./result-_Bg2cDAG.mjs";
1
2
  import { CreateMcpExpressAppOptions } from "@modelcontextprotocol/sdk/server/express.js";
2
- import { Action, AdapterFactory, SilkweaveContext, SilkweaveError, SilkweaveOptions } from "@silkweave/core";
3
+ import { Action, AdapterFactory, SilkweaveContext, SilkweaveOptions } from "@silkweave/core";
3
4
  import { Express, RequestHandler } from "express";
4
5
  import { AuthConfig, AuthInfo } from "@silkweave/auth";
5
6
  import { AsyncLocalStorage } from "node:async_hooks";
6
7
  import { CorsOptions, CorsOptions as CorsOptions$1 } from "cors";
7
- import { CallToolResult, EmbeddedResource } from "@modelcontextprotocol/sdk/types.js";
8
8
  import { Server } from "http";
9
9
 
10
10
  //#region src/adapter/http.d.ts
@@ -66,8 +66,8 @@ declare function authMiddleware(auth: AuthConfig, context: SilkweaveContext): Re
66
66
  declare const MCP_REQUIRED_HEADERS: string[];
67
67
  /**
68
68
  * CORS middleware preconfigured to expose the headers MCP clients need
69
- * (`Mcp-Session-Id`, `Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`)
70
- * on top of any user-supplied options.
69
+ * (`Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`) on top of any
70
+ * user-supplied options. (Stateless transport - no `Mcp-Session-Id`.)
71
71
  *
72
72
  * Pass `false` to disable, omit / pass `true` for permissive defaults, or pass
73
73
  * a `CorsOptions` object to override.
@@ -124,33 +124,20 @@ declare function sideloadResource(options?: SideloadResourceOptions): RequestHan
124
124
  //#endregion
125
125
  //#region src/handlers/transport.d.ts
126
126
  interface McpTransportHandlers {
127
- /** `POST /mcp` - initialize session or dispatch request to an existing one. */
127
+ /** `POST /mcp` - handle a single stateless MCP request/response (SSE per call). */
128
128
  post: RequestHandler;
129
- /** `GET /mcp` and `DELETE /mcp` - long-poll session stream / session termination. Also covers `GET /mcp/resource/:id`. */
130
- stream: RequestHandler;
131
129
  }
132
130
  /**
133
- * Build the MCP Streamable HTTP transport route handlers.
131
+ * Build the MCP Streamable HTTP transport route handler.
134
132
  *
135
- * Sessions are kept in a per-call closure (one map per `mcpTransport()` call),
136
- * so the same handler object should be registered for all three transport
137
- * routes - `POST /mcp`, `GET /mcp`, `DELETE /mcp` - to share session state.
133
+ * Stateless (per the 2026 spec direction): each `POST /mcp` mints a fresh
134
+ * transport + server with `sessionIdGenerator: undefined`, handles exactly that
135
+ * request (streaming progress over SSE when the call carries a `progressToken`),
136
+ * and tears down on response close. No `Mcp-Session-Id`, no session map, no
137
+ * `GET`/`DELETE` reconnect - any request can hit any instance.
138
138
  */
139
139
  declare function mcpTransport(silkweaveOptions: SilkweaveOptions, context: SilkweaveContext, actions: Action[]): McpTransportHandlers;
140
140
  //#endregion
141
- //#region src/util/result.d.ts
142
- declare function smartToolResult(data: string | object | object[]): CallToolResult;
143
- declare function jsonToolResult(data: object, isError?: boolean): CallToolResult;
144
- declare function errorToolResult({
145
- code,
146
- name,
147
- message
148
- }: SilkweaveError): CallToolResult;
149
- declare function handleToolError(error: unknown): CallToolResult;
150
- declare function parseResourceMessage({
151
- resource
152
- }: EmbeddedResource): string;
153
- //#endregion
154
141
  //#region src/util/sideload.d.ts
155
142
  interface SideloadResource {
156
143
  id: string;
@@ -163,5 +150,5 @@ declare function createSideloadResource(buffer: Buffer, {
163
150
  contentType
164
151
  }: Pick<SideloadResource, 'name' | 'contentType'>): Promise<SideloadResource>;
165
152
  //#endregion
166
- export { type CorsOptions, MCP_REQUIRED_HEADERS, McpTransportHandlers, OAuthRouteHandlers, SideloadResource, SideloadResourceOptions, StartMcpHttpOptions, authMiddleware, authStorage, buildMcpExpressApp, createSideloadResource, errorToolResult, handleToolError, http, jsonToolResult, mcpCors, mcpTransport, oauthRoutes, parseResourceMessage, protectedResourceMetadata, sideloadResource, smartToolResult, startMcpServer, stdio };
153
+ export { type CorsOptions, MCP_REQUIRED_HEADERS, McpTransportHandlers, OAuthRouteHandlers, RegisterToolsOptions, SideloadResource, SideloadResourceOptions, StartMcpHttpOptions, authMiddleware, authStorage, buildMcpExpressApp, createSideloadResource, errorToolResult, handleToolError, http, jsonToolResult, mcpCors, mcpTransport, oauthRoutes, parseResourceMessage, protectedResourceMetadata, registerTools, requestFromExtra, sideloadResource, smartToolResult, startMcpServer, stdio };
167
154
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/http.ts","../src/adapter/stdio.ts","../src/handlers/auth.ts","../src/handlers/cors.ts","../src/handlers/metadata.ts","../src/handlers/oauth.ts","../src/handlers/sideload.ts","../src/handlers/transport.ts","../src/util/result.ts","../src/util/sideload.ts"],"mappings":";;;;;;;;;;UAaiB,mBAAA,SAA4B,0BAAA;EAC3C,IAAA;EACA,IAAA;EACA,IAAA,GAAO,UAAA;EAHQ;EAKf,IAAA,GAAO,aAAA;;EAEP,iBAAA;EAFO;EAIP,WAAA;AAAA;;;;;;;;iBAUc,kBAAA,CACd,gBAAA,EAAkB,gBAAA,EAClB,OAAA,EAAS,gBAAA,EACT,OAAA,EAAS,MAAA,IACT,OAAA,EAAS,mBAAA,GACR,OAAA;;;;;AALH;;iBAsDsB,cAAA,CACpB,gBAAA,EAAkB,gBAAA,EAClB,OAAA,EAAS,MAAA,IACT,OAAA,EAAS,mBAAA,EACT,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,MAAA;;;;;cAgBE,IAAA,EAAM,cAAA,CAAe,mBAAA;;;cCxDrB,KAAA,EAAO,cAAA;;;;;;;cC1CP,WAAA,EAAW,iBAAA,CAAA,QAAA;;AFIxB;;;;;;;;;iBEQgB,cAAA,CAAe,IAAA,EAAM,UAAA,EAAY,OAAA,EAAS,gBAAA,GAAmB,cAAA;;;;cCjBhE,oBAAA;;;;;;;AHSb;;iBGCgB,OAAA,CAAQ,UAAA,GAAY,aAAA,aAA+B,cAAA;;;;;;;;iBCNnD,yBAAA,CAA0B,IAAA,EAAM,UAAA,GAAa,cAAA;;;UCa5C,kBAAA;;EAEf,mBAAA,EAAqB,cAAA;;EAErB,SAAA,EAAW,cAAA;;EAEX,QAAA,EAAU,cAAA;;EAEV,YAAA;ELhBmC;EKkBnC,KAAA,EAAO,cAAA;ELfA;EKiBP,QAAA,EAAU,cAAA;AAAA;;;;;;;;;;;iBAaI,WAAA,CAAY,IAAA,EAAM,UAAA,GAAa,kBAAA;;;UC1C9B,uBAAA;;EAEf,WAAA;AAAA;;;;;;ANOF;iBMEgB,gBAAA,CAAiB,OAAA,GAAS,uBAAA,GAA+B,cAAA;;;UCiGxD,oBAAA;;EAEf,IAAA,EAAM,cAAA;;EAEN,MAAA,EAAQ,cAAA;AAAA;;;APvGV;;;;;iBOiHgB,YAAA,CACd,gBAAA,EAAkB,gBAAA,EAClB,OAAA,EAAS,gBAAA,EACT,OAAA,EAAS,MAAA,KACR,oBAAA;;;iBC9Ha,eAAA,CAAgB,IAAA,+BAAmC,cAAA;AAAA,iBAqBnD,cAAA,CAAe,IAAA,UAAc,OAAA,aAAkB,cAAA;AAAA,iBAM/C,eAAA,CAAA;EAAkB,IAAA;EAAM,IAAA;EAAM;AAAA,GAAW,cAAA,GAAiB,cAAA;AAAA,iBAU1D,eAAA,CAAgB,KAAA,YAAiB,cAAA;AAAA,iBAejC,oBAAA,CAAA;EAAuB;AAAA,GAAY,gBAAA;;;UCrDlC,gBAAA;EACf,EAAA;EACA,IAAA;EACA,WAAA;EACA,IAAA;AAAA;AAAA,iBAGoB,sBAAA,CAAuB,MAAA,EAAQ,MAAA;EAAU,IAAA;EAAM;AAAA,GAAe,IAAA,CAAK,gBAAA,4BAAyC,OAAA,CAAA,gBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/http.ts","../src/adapter/stdio.ts","../src/handlers/auth.ts","../src/handlers/cors.ts","../src/handlers/metadata.ts","../src/handlers/oauth.ts","../src/handlers/sideload.ts","../src/handlers/transport.ts","../src/util/sideload.ts"],"mappings":";;;;;;;;;;UAaiB,mBAAA,SAA4B,0BAAA;EAC3C,IAAA;EACA,IAAA;EACA,IAAA,GAAO,UAAA;EAHQ;EAKf,IAAA,GAAO,aAAA;;EAEP,iBAAA;EAFO;EAIP,WAAA;AAAA;;;;;;;;iBAUc,kBAAA,CACd,gBAAA,EAAkB,gBAAA,EAClB,OAAA,EAAS,gBAAA,EACT,OAAA,EAAS,MAAA,IACT,OAAA,EAAS,mBAAA,GACR,OAAA;;;;;AALH;;iBAmDsB,cAAA,CACpB,gBAAA,EAAkB,gBAAA,EAClB,OAAA,EAAS,MAAA,IACT,OAAA,EAAS,mBAAA,EACT,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,MAAA;;;;;cAgBE,IAAA,EAAM,cAAA,CAAe,mBAAA;;;cCnGrB,KAAA,EAAO,cAAA;;;;;;;cCIP,WAAA,EAAW,iBAAA,CAAA,QAAA;;AFIxB;;;;;;;;;iBEQgB,cAAA,CAAe,IAAA,EAAM,UAAA,EAAY,OAAA,EAAS,gBAAA,GAAmB,cAAA;;;;cCjBhE,oBAAA;;;;;;;AHSb;;iBGCgB,OAAA,CAAQ,UAAA,GAAY,aAAA,aAA+B,cAAA;;;;;;;;iBCNnD,yBAAA,CAA0B,IAAA,EAAM,UAAA,GAAa,cAAA;;;UCa5C,kBAAA;;EAEf,mBAAA,EAAqB,cAAA;;EAErB,SAAA,EAAW,cAAA;;EAEX,QAAA,EAAU,cAAA;;EAEV,YAAA;ELhBmC;EKkBnC,KAAA,EAAO,cAAA;ELfA;EKiBP,QAAA,EAAU,cAAA;AAAA;;;;;;;;;;;iBAaI,WAAA,CAAY,IAAA,EAAM,UAAA,GAAa,kBAAA;;;UC1C9B,uBAAA;;EAEf,WAAA;AAAA;;;;;;ANOF;iBMEgB,gBAAA,CAAiB,OAAA,GAAS,uBAAA,GAA+B,cAAA;;;UCGxD,oBAAA;;EAEf,IAAA,EAAM,cAAA;AAAA;;;;;APPR;;;;;iBOmBgB,YAAA,CACd,gBAAA,EAAkB,gBAAA,EAClB,OAAA,EAAS,gBAAA,EACT,OAAA,EAAS,MAAA,KACR,oBAAA;;;UCjCc,gBAAA;EACf,EAAA;EACA,IAAA;EACA,WAAA;EACA,IAAA;AAAA;AAAA,iBAGoB,sBAAA,CAAuB,MAAA,EAAQ,MAAA;EAAU,IAAA;EAAM;AAAA,GAAe,IAAA,CAAK,gBAAA,4BAAyC,OAAA,CAAA,gBAAA"}
package/build/index.mjs CHANGED
@@ -1,62 +1,26 @@
1
- import { a as smartToolResult, i as parseResourceMessage, n as handleToolError, r as jsonToolResult, t as errorToolResult } from "./result-BiFuFt5B.mjs";
1
+ import { i as authStorage, n as requestFromExtra, r as authMiddleware, t as registerTools } from "./registerTools-Z6JnoM60.mjs";
2
+ import { a as smartToolResult, i as parseResourceMessage, n as handleToolError, r as jsonToolResult, t as errorToolResult } from "./result-B_9Eo1ey.mjs";
2
3
  import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
3
- import { createContext, isStreamingAction, runStreamingAction } from "@silkweave/core";
4
+ import { createContext } from "@silkweave/core";
4
5
  import express from "express";
5
- import { generateProtectedResourceMetadata, validateToken } from "@silkweave/auth";
6
- import { AsyncLocalStorage } from "node:async_hooks";
6
+ import { generateProtectedResourceMetadata } from "@silkweave/auth";
7
7
  import cors from "cors";
8
8
  import { readFile, writeFile } from "fs/promises";
9
- import { InMemoryEventStore } from "@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js";
10
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
10
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
12
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
13
- import { createLogger } from "@silkweave/logger";
14
- import { capitalCase, pascalCase } from "change-case";
15
- import { randomUUID } from "crypto";
16
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
- //#region src/handlers/auth.ts
18
- /**
19
- * Per-request bearer-token storage used by tool handlers to read the resolved
20
- * `AuthInfo` for the currently-handled MCP call.
21
- */
22
- const authStorage = new AsyncLocalStorage();
23
- /**
24
- * Express middleware that validates the `Authorization: Bearer …` header via
25
- * the supplied `AuthConfig`. On success the resolved `AuthInfo` is placed in
26
- * `authStorage` for the duration of the downstream handler - `mcpTransport`'s
27
- * tool callbacks pick it up to populate the silkweave context's `auth` key.
28
- *
29
- * The middleware should NOT be applied to OAuth-discovery / token routes -
30
- * compose it only on the routes that require an authenticated caller (the MCP
31
- * transport itself, sideload, etc.).
32
- */
33
- function authMiddleware(auth, context) {
34
- return async (req, res, next) => {
35
- const result = await validateToken(req.headers.authorization, auth, context.fork({ request: req }));
36
- if (result.error) {
37
- for (const [key, value] of Object.entries(result.error.headers)) res.header(key, value);
38
- res.status(result.error.statusCode).json(result.error.body);
39
- return;
40
- }
41
- if (result.auth) authStorage.run(result.auth, () => {
42
- next();
43
- });
44
- else next();
45
- };
46
- }
47
- //#endregion
12
+ import { randomUUID } from "crypto";
48
13
  //#region src/handlers/cors.ts
49
14
  /** Headers required by the MCP protocol that must always be exposed when CORS is in use. */
50
15
  const MCP_REQUIRED_HEADERS = [
51
16
  "WWW-Authenticate",
52
- "Mcp-Session-Id",
53
17
  "Last-Event-Id",
54
18
  "Mcp-Protocol-Version"
55
19
  ];
56
20
  /**
57
21
  * CORS middleware preconfigured to expose the headers MCP clients need
58
- * (`Mcp-Session-Id`, `Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`)
59
- * on top of any user-supplied options.
22
+ * (`Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`) on top of any
23
+ * user-supplied options. (Stateless transport - no `Mcp-Session-Id`.)
60
24
  *
61
25
  * Pass `false` to disable, omit / pass `true` for permissive defaults, or pass
62
26
  * a `CorsOptions` object to override.
@@ -81,7 +45,7 @@ function mcpCors(corsConfig = true) {
81
45
  */
82
46
  function protectedResourceMetadata(auth) {
83
47
  if (!auth.resourceUrl || !auth.authorizationServers?.length) throw new Error("@silkweave/mcp protectedResourceMetadata(): auth.resourceUrl and auth.authorizationServers are required");
84
- const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers);
48
+ const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers, auth.requiredScopes);
85
49
  return (_req, res) => {
86
50
  res.json(metadata);
87
51
  };
@@ -155,85 +119,6 @@ function sideloadResource(options = {}) {
155
119
  }
156
120
  //#endregion
157
121
  //#region src/handlers/transport.ts
158
- /**
159
- * Build the generic `request` context value (the same key REST/tRPC populate)
160
- * from the MCP SDK's `extra.requestInfo`, so transport-agnostic consumers - e.g.
161
- * `@silkweave/nestjs` `@UseGuards` guards reading
162
- * `switchToHttp().getRequest().headers` - work over MCP. There is no path
163
- * `params`/`query` on an MCP call, so those are empty stand-ins for graceful
164
- * degradation. Returns `undefined` when no HTTP request info is available.
165
- */
166
- function requestFromExtra(requestInfo) {
167
- if (!requestInfo) return;
168
- return {
169
- headers: requestInfo.headers ?? {},
170
- url: requestInfo.url?.toString(),
171
- params: {},
172
- query: {}
173
- };
174
- }
175
- function registerTools$1(server, actions, context) {
176
- for (const action of actions) server.registerTool(pascalCase(action.name), {
177
- title: capitalCase(action.name),
178
- description: action.description,
179
- inputSchema: action.input
180
- }, async (input, extra) => {
181
- const logger = createLogger({
182
- stream: process.stderr,
183
- onLog: (level, data) => {
184
- extra.sendNotification({
185
- method: "notifications/message",
186
- params: {
187
- level,
188
- data
189
- }
190
- });
191
- },
192
- onProgress: ({ progress, total, message }) => {
193
- if (!extra._meta?.progressToken) return;
194
- extra.sendNotification({
195
- method: "notifications/progress",
196
- params: {
197
- progress,
198
- total,
199
- message,
200
- progressToken: extra._meta.progressToken
201
- }
202
- });
203
- }
204
- });
205
- const currentAuth = authStorage.getStore();
206
- const actionContext = context.fork({
207
- logger,
208
- extra,
209
- request: requestFromExtra(extra.requestInfo),
210
- ...currentAuth ? { auth: currentAuth } : {}
211
- });
212
- const disposition = extra._meta?.disposition ?? action.disposition;
213
- const progressToken = extra._meta?.progressToken;
214
- try {
215
- let result;
216
- if (isStreamingAction(action)) result = await runStreamingAction(action, input, actionContext, progressToken ? async (chunk, index) => {
217
- await extra.sendNotification({
218
- method: "notifications/progress",
219
- params: {
220
- progressToken,
221
- progress: index + 1,
222
- message: JSON.stringify(chunk)
223
- }
224
- });
225
- } : void 0);
226
- else result = await action.run(input, actionContext);
227
- if (action.toolResult) {
228
- const response = action.toolResult(result, actionContext);
229
- if (response) return response;
230
- }
231
- return disposition === "json" ? jsonToolResult(result) : smartToolResult(result);
232
- } catch (error) {
233
- return handleToolError(error);
234
- }
235
- });
236
- }
237
122
  function createMcpServer(options, actions, context) {
238
123
  const server = new McpServer({
239
124
  name: options.name,
@@ -243,56 +128,28 @@ function createMcpServer(options, actions, context) {
243
128
  tools: {},
244
129
  logging: {}
245
130
  } });
246
- registerTools$1(server, actions, context);
131
+ registerTools(server, actions, context);
247
132
  return server;
248
133
  }
249
- function createSessionTransport(transports) {
250
- const transport = new StreamableHTTPServerTransport({
251
- sessionIdGenerator: () => randomUUID(),
252
- enableJsonResponse: false,
253
- eventStore: new InMemoryEventStore(),
254
- onsessioninitialized: (sId) => {
255
- transports[sId] = transport;
256
- }
257
- });
258
- transport.onerror = (error) => {
259
- console.error(error);
260
- };
261
- transport.onclose = () => {
262
- const sid = transport.sessionId;
263
- if (sid && transports[sid]) delete transports[sid];
264
- };
265
- return transport;
266
- }
267
134
  /**
268
- * Build the MCP Streamable HTTP transport route handlers.
135
+ * Build the MCP Streamable HTTP transport route handler.
269
136
  *
270
- * Sessions are kept in a per-call closure (one map per `mcpTransport()` call),
271
- * so the same handler object should be registered for all three transport
272
- * routes - `POST /mcp`, `GET /mcp`, `DELETE /mcp` - to share session state.
137
+ * Stateless (per the 2026 spec direction): each `POST /mcp` mints a fresh
138
+ * transport + server with `sessionIdGenerator: undefined`, handles exactly that
139
+ * request (streaming progress over SSE when the call carries a `progressToken`),
140
+ * and tears down on response close. No `Mcp-Session-Id`, no session map, no
141
+ * `GET`/`DELETE` reconnect - any request can hit any instance.
273
142
  */
274
143
  function mcpTransport(silkweaveOptions, context, actions) {
275
- const transports = {};
276
144
  const post = async (req, res) => {
277
- const sessionId = req.headers["mcp-session-id"];
278
145
  try {
279
- if (sessionId && transports[sessionId]) {
280
- await transports[sessionId].handleRequest(req, res, req.body);
281
- return;
282
- }
283
- if (!isInitializeRequest(req.body)) {
284
- res.status(404).json({
285
- jsonrpc: "2.0",
286
- error: {
287
- code: -32e3,
288
- message: "Session not found"
289
- },
290
- id: null
291
- });
292
- return;
293
- }
294
- const transport = createSessionTransport(transports);
295
- await createMcpServer(silkweaveOptions, actions, context).connect(transport);
146
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
147
+ const server = createMcpServer(silkweaveOptions, actions, context);
148
+ res.on("close", () => {
149
+ transport.close();
150
+ server.close();
151
+ });
152
+ await server.connect(transport);
296
153
  await transport.handleRequest(req, res, req.body);
297
154
  } catch (error) {
298
155
  console.error("Error handling MCP request:", error);
@@ -306,18 +163,7 @@ function mcpTransport(silkweaveOptions, context, actions) {
306
163
  });
307
164
  }
308
165
  };
309
- const stream = async (req, res) => {
310
- const sessionId = req.headers["mcp-session-id"];
311
- if (!sessionId || !transports[sessionId]) {
312
- res.status(400).send("Invalid or missing session ID");
313
- return;
314
- }
315
- await transports[sessionId].handleRequest(req, res);
316
- };
317
- return {
318
- post,
319
- stream
320
- };
166
+ return { post };
321
167
  }
322
168
  //#endregion
323
169
  //#region src/adapter/http.ts
@@ -363,9 +209,6 @@ function buildMcpExpressApp(silkweaveOptions, context, actions, options) {
363
209
  if (sideloadResources) app.get("/resource/:id", sideloadResource({ resourceDir }));
364
210
  const transport = mcpTransport(silkweaveOptions, context, actions);
365
211
  app.post("/mcp", express.json(), transport.post);
366
- app.get("/mcp", transport.stream);
367
- app.delete("/mcp", transport.stream);
368
- app.get("/mcp/resource/:id", transport.stream);
369
212
  return app;
370
213
  }
371
214
  /**
@@ -422,60 +265,6 @@ const http = (options) => {
422
265
  };
423
266
  //#endregion
424
267
  //#region src/adapter/stdio.ts
425
- function registerTools(server, actions, context) {
426
- for (const action of actions) server.registerTool(pascalCase(action.name), {
427
- title: capitalCase(action.name),
428
- description: action.description,
429
- inputSchema: action.input
430
- }, async (input, extra) => {
431
- const logger = createLogger({
432
- stream: false,
433
- onLog: (level, data) => {
434
- extra.sendNotification({
435
- method: "notifications/message",
436
- params: {
437
- level,
438
- data
439
- }
440
- });
441
- },
442
- onProgress: ({ progress, total, message }) => {
443
- if (!extra._meta?.progressToken) return;
444
- extra.sendNotification({
445
- method: "notifications/progress",
446
- params: {
447
- progress,
448
- total,
449
- message,
450
- progressToken: extra._meta.progressToken
451
- }
452
- });
453
- }
454
- });
455
- const actionContext = context.fork({
456
- logger,
457
- extra
458
- });
459
- const progressToken = extra._meta?.progressToken;
460
- try {
461
- let result;
462
- if (isStreamingAction(action)) result = await runStreamingAction(action, input, actionContext, progressToken ? async (chunk, index) => {
463
- await extra.sendNotification({
464
- method: "notifications/progress",
465
- params: {
466
- progressToken,
467
- progress: index + 1,
468
- message: JSON.stringify(chunk)
469
- }
470
- });
471
- } : void 0);
472
- else result = await action.run(input, actionContext);
473
- return action.toolResult?.(result, actionContext) ?? smartToolResult(result);
474
- } catch (error) {
475
- return handleToolError(error);
476
- }
477
- });
478
- }
479
268
  const stdio = () => {
480
269
  return (options, baseContext) => {
481
270
  const context = baseContext.fork({ adapter: "stdio" });
@@ -490,7 +279,7 @@ const stdio = () => {
490
279
  return {
491
280
  context,
492
281
  start: async (actions) => {
493
- registerTools(server, actions, context);
282
+ registerTools(server, actions, context, { logStream: false });
494
283
  const transport = new StdioServerTransport();
495
284
  await server.connect(transport);
496
285
  },
@@ -514,6 +303,6 @@ async function createSideloadResource(buffer, { name, contentType }) {
514
303
  return resource;
515
304
  }
516
305
  //#endregion
517
- export { MCP_REQUIRED_HEADERS, authMiddleware, authStorage, buildMcpExpressApp, createSideloadResource, errorToolResult, handleToolError, http, jsonToolResult, mcpCors, mcpTransport, oauthRoutes, parseResourceMessage, protectedResourceMetadata, sideloadResource, smartToolResult, startMcpServer, stdio };
306
+ export { MCP_REQUIRED_HEADERS, authMiddleware, authStorage, buildMcpExpressApp, createSideloadResource, errorToolResult, handleToolError, http, jsonToolResult, mcpCors, mcpTransport, oauthRoutes, parseResourceMessage, protectedResourceMetadata, registerTools, requestFromExtra, sideloadResource, smartToolResult, startMcpServer, stdio };
518
307
 
519
308
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["registerTools"],"sources":["../src/handlers/auth.ts","../src/handlers/cors.ts","../src/handlers/metadata.ts","../src/handlers/oauth.ts","../src/handlers/sideload.ts","../src/handlers/transport.ts","../src/adapter/http.ts","../src/adapter/stdio.ts","../src/util/sideload.ts"],"sourcesContent":["import { AuthConfig, AuthInfo, validateToken } from '@silkweave/auth'\nimport { SilkweaveContext } from '@silkweave/core'\nimport { type RequestHandler } from 'express'\nimport { AsyncLocalStorage } from 'node:async_hooks'\n\n/**\n * Per-request bearer-token storage used by tool handlers to read the resolved\n * `AuthInfo` for the currently-handled MCP call.\n */\nexport const authStorage = new AsyncLocalStorage<AuthInfo>()\n\n/**\n * Express middleware that validates the `Authorization: Bearer …` header via\n * the supplied `AuthConfig`. On success the resolved `AuthInfo` is placed in\n * `authStorage` for the duration of the downstream handler - `mcpTransport`'s\n * tool callbacks pick it up to populate the silkweave context's `auth` key.\n *\n * The middleware should NOT be applied to OAuth-discovery / token routes -\n * compose it only on the routes that require an authenticated caller (the MCP\n * transport itself, sideload, etc.).\n */\nexport function authMiddleware(auth: AuthConfig, context: SilkweaveContext): RequestHandler {\n return async (req, res, next) => {\n const result = await validateToken(req.headers.authorization, auth, context.fork({ request: req }))\n if (result.error) {\n for (const [key, value] of Object.entries(result.error.headers)) { res.header(key, value) }\n res.status(result.error.statusCode).json(result.error.body)\n return\n }\n if (result.auth) {\n authStorage.run(result.auth, () => { next() })\n } else {\n next()\n }\n }\n}\n","import cors, { CorsOptions } from 'cors'\nimport { type RequestHandler } from 'express'\n\n/** Headers required by the MCP protocol that must always be exposed when CORS is in use. */\nexport const MCP_REQUIRED_HEADERS = ['WWW-Authenticate', 'Mcp-Session-Id', 'Last-Event-Id', 'Mcp-Protocol-Version']\n\n/**\n * CORS middleware preconfigured to expose the headers MCP clients need\n * (`Mcp-Session-Id`, `Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`)\n * on top of any user-supplied options.\n *\n * Pass `false` to disable, omit / pass `true` for permissive defaults, or pass\n * a `CorsOptions` object to override.\n */\nexport function mcpCors(corsConfig: CorsOptions | boolean = true): RequestHandler | null {\n if (corsConfig === false) { return null }\n const userConfig = corsConfig === true ? {} : corsConfig\n const userExposed = userConfig.exposedHeaders\n const exposedHeaders = [\n ...MCP_REQUIRED_HEADERS,\n ...(Array.isArray(userExposed) ? userExposed : userExposed ? [userExposed] : [])\n ]\n return cors({ origin: '*', ...userConfig, exposedHeaders })\n}\n","import { AuthConfig, generateProtectedResourceMetadata } from '@silkweave/auth'\nimport { type RequestHandler } from 'express'\n\n/**\n * Handler for `GET /.well-known/oauth-protected-resource` (RFC 9728). Returns\n * the resource server's metadata pointing at the configured authorization\n * servers. Requires `auth.resourceUrl` and a non-empty `auth.authorizationServers`.\n */\nexport function protectedResourceMetadata(auth: AuthConfig): RequestHandler {\n if (!auth.resourceUrl || !auth.authorizationServers?.length) {\n throw new Error('@silkweave/mcp protectedResourceMetadata(): auth.resourceUrl and auth.authorizationServers are required')\n }\n const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers)\n return (_req, res) => { res.json(metadata) }\n}\n","import { AuthConfig, OAuthRequest, OAuthResponse } from '@silkweave/auth'\nimport express, { type Request, type RequestHandler, type Response } from 'express'\n\nfunction toOAuthReq(req: Request): OAuthRequest {\n return {\n method: req.method,\n url: new URL(req.url, `${req.protocol}://${req.get('host')}`),\n headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),\n body: req.body as Record<string, string> | undefined\n }\n}\n\nfunction sendOAuth(res: Response, oauthRes: OAuthResponse) {\n for (const [key, value] of Object.entries(oauthRes.headers)) { res.header(key, value) }\n if (oauthRes.body) {\n res.status(oauthRes.status).send(typeof oauthRes.body === 'string' ? oauthRes.body : JSON.stringify(oauthRes.body))\n } else {\n res.status(oauthRes.status).end()\n }\n}\n\nexport interface OAuthRouteHandlers {\n /** `GET /.well-known/oauth-authorization-server` - RFC 8414 discovery. */\n wellKnownAuthServer: RequestHandler\n /** `GET /authorize` - start the OAuth flow. */\n authorize: RequestHandler\n /** `GET {callbackPath}` - provider callback. */\n callback: RequestHandler\n /** Path the provider should redirect to (defaults to `/auth/callback`). */\n callbackPath: string\n /** `POST /token` - exchange code / refresh token. Includes urlencoded body parser. */\n token: RequestHandler[]\n /** `POST /register` - dynamic client registration. Includes JSON body parser. */\n register: RequestHandler[]\n}\n\n/**\n * Build the OAuth 2.1 proxy route handlers (authorize, callback, token,\n * register, well-known) backed by the configured `auth.provider`. Returns the\n * handlers as individual `RequestHandler`s so callers can register them\n * wherever they like - under `/mcp`, at the server root, etc.\n *\n * `token` and `register` are returned as middleware arrays because the\n * appropriate body parser must run before the handler. Apply with\n * `app.post(path, ...token)`.\n */\nexport function oauthRoutes(auth: AuthConfig): OAuthRouteHandlers {\n if (!auth.provider) { throw new Error('@silkweave/mcp oauthRoutes(): auth.provider is required') }\n const provider = auth.provider\n const callbackPath = auth.callbackPath ?? '/auth/callback'\n\n return {\n callbackPath,\n wellKnownAuthServer: (_req, res) => { sendOAuth(res, provider.metadata()) },\n authorize: async (req, res) => { sendOAuth(res, await provider.authorize(toOAuthReq(req))) },\n callback: async (req, res) => { sendOAuth(res, await provider.callback(toOAuthReq(req))) },\n token: [\n express.urlencoded({ extended: false }),\n async (req, res) => { sendOAuth(res, await provider.token(toOAuthReq(req))) }\n ],\n register: [\n express.json(),\n async (req, res) => { sendOAuth(res, await provider.register(toOAuthReq(req))) }\n ]\n }\n}\n","import { type RequestHandler } from 'express'\nimport { readFile } from 'fs/promises'\nimport { type SideloadResource } from '../util/sideload.js'\n\nexport interface SideloadResourceOptions {\n /** Directory to read sideload resources from. Defaults to `resources/` (cwd-relative). */\n resourceDir?: string\n}\n\n/**\n * Handler for `GET /resource/:id` - serves a large MCP response that was\n * sideloaded to disk as a `{id}` payload with a `{id}.json` metadata sidecar.\n *\n * The route param `id` is provided by the host framework (Express, Nest, etc.).\n */\nexport function sideloadResource(options: SideloadResourceOptions = {}): RequestHandler {\n const { resourceDir = 'resources' } = options\n return async (req, res) => {\n const id = req.params['id']\n if (!id || typeof id !== 'string') { throw new Error('Invalid ID') }\n const resourceMeta: SideloadResource = JSON.parse(await readFile(`${resourceDir}/${id}.json`, 'utf-8'))\n const buffer = await readFile(`${resourceDir}/${id}`)\n res.status(200)\n res.header('Content-Type', resourceMeta.contentType)\n res.send(buffer)\n }\n}\n","import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'\nimport { Action, ActionRun, isStreamingAction, runStreamingAction, SilkweaveContext, SilkweaveOptions } from '@silkweave/core'\nimport { createLogger } from '@silkweave/logger'\nimport { capitalCase, pascalCase } from 'change-case'\nimport { randomUUID } from 'crypto'\nimport { type RequestHandler } from 'express'\nimport { handleToolError, jsonToolResult, smartToolResult } from '../util/result.js'\nimport { authStorage } from './auth.js'\n\n/**\n * Build the generic `request` context value (the same key REST/tRPC populate)\n * from the MCP SDK's `extra.requestInfo`, so transport-agnostic consumers - e.g.\n * `@silkweave/nestjs` `@UseGuards` guards reading\n * `switchToHttp().getRequest().headers` - work over MCP. There is no path\n * `params`/`query` on an MCP call, so those are empty stand-ins for graceful\n * degradation. Returns `undefined` when no HTTP request info is available.\n */\nfunction requestFromExtra(requestInfo: { headers?: unknown; url?: { toString(): string } } | undefined) {\n if (!requestInfo) { return undefined }\n return { headers: requestInfo.headers ?? {}, url: requestInfo.url?.toString(), params: {}, query: {} }\n}\n\nfunction registerTools(server: McpServer, actions: Action[], context: SilkweaveContext) {\n for (const action of actions) {\n server.registerTool(pascalCase(action.name), {\n title: capitalCase(action.name),\n description: action.description,\n inputSchema: action.input\n }, async (input, extra) => {\n const logger = createLogger({\n stream: process.stderr,\n onLog: (level, data) => {\n extra.sendNotification({ method: 'notifications/message', params: { level, data } })\n },\n onProgress: ({ progress, total, message }) => {\n if (!extra._meta?.progressToken) { return }\n extra.sendNotification({\n method: 'notifications/progress',\n params: { progress, total, message, progressToken: extra._meta.progressToken }\n })\n }\n })\n const currentAuth = authStorage.getStore()\n const actionContext = context.fork({\n logger,\n extra,\n request: requestFromExtra(extra.requestInfo),\n ...(currentAuth ? { auth: currentAuth } : {})\n })\n // Client-sent `_meta.disposition` wins; otherwise fall back to the\n // action's configured default (`smart` when neither is set).\n const disposition = extra._meta?.disposition ?? action.disposition\n const progressToken = extra._meta?.progressToken\n try {\n let result: object | object[]\n if (isStreamingAction(action)) {\n result = await runStreamingAction(action, input, actionContext, progressToken\n ? async (chunk, index) => {\n await extra.sendNotification({\n method: 'notifications/progress',\n params: { progressToken, progress: index + 1, message: JSON.stringify(chunk) }\n })\n }\n : undefined)\n } else {\n result = await (action.run as ActionRun<object, object>)(input, actionContext)\n }\n if (action.toolResult) {\n const response = action.toolResult(result, actionContext)\n if (response) { return response }\n }\n return disposition === 'json' ? jsonToolResult(result) : smartToolResult(result)\n } catch (error) {\n return handleToolError(error)\n }\n })\n }\n}\n\nfunction createMcpServer(options: SilkweaveOptions, actions: Action[], context: SilkweaveContext): McpServer {\n const server = new McpServer({\n name: options.name,\n description: options.description,\n version: options.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n registerTools(server, actions, context)\n return server\n}\n\nfunction createSessionTransport(transports: Record<string, StreamableHTTPServerTransport>): StreamableHTTPServerTransport {\n const eventStore = new InMemoryEventStore()\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n enableJsonResponse: false,\n eventStore,\n onsessioninitialized: (sId) => {\n transports[sId] = transport\n }\n })\n transport.onerror = (error) => { console.error(error) }\n transport.onclose = () => {\n const sid = transport.sessionId\n if (sid && transports[sid]) { delete transports[sid] }\n }\n return transport\n}\n\nexport interface McpTransportHandlers {\n /** `POST /mcp` - initialize session or dispatch request to an existing one. */\n post: RequestHandler\n /** `GET /mcp` and `DELETE /mcp` - long-poll session stream / session termination. Also covers `GET /mcp/resource/:id`. */\n stream: RequestHandler\n}\n\n/**\n * Build the MCP Streamable HTTP transport route handlers.\n *\n * Sessions are kept in a per-call closure (one map per `mcpTransport()` call),\n * so the same handler object should be registered for all three transport\n * routes - `POST /mcp`, `GET /mcp`, `DELETE /mcp` - to share session state.\n */\nexport function mcpTransport(\n silkweaveOptions: SilkweaveOptions,\n context: SilkweaveContext,\n actions: Action[]\n): McpTransportHandlers {\n const transports: Record<string, StreamableHTTPServerTransport> = {}\n\n const post: RequestHandler = async (req, res) => {\n const sessionId = req.headers['mcp-session-id'] as string | undefined\n try {\n if (sessionId && transports[sessionId]) {\n await transports[sessionId].handleRequest(req, res, req.body)\n return\n }\n if (!isInitializeRequest(req.body)) {\n res.status(404).json({ jsonrpc: '2.0', error: { code: -32_000, message: 'Session not found' }, id: null })\n return\n }\n const transport = createSessionTransport(transports)\n await createMcpServer(silkweaveOptions, actions, context).connect(transport)\n await transport.handleRequest(req, res, req.body)\n } catch (error) {\n console.error('Error handling MCP request:', error)\n if (!res.headersSent) {\n res.status(500).json({ jsonrpc: '2.0', error: { code: -32_603, message: 'Internal server error' }, id: null })\n }\n }\n }\n\n const stream: RequestHandler = async (req, res) => {\n const sessionId = req.headers['mcp-session-id'] as string | undefined\n if (!sessionId || !transports[sessionId]) {\n res.status(400).send('Invalid or missing session ID')\n return\n }\n await transports[sessionId].handleRequest(req, res)\n }\n\n return { post, stream }\n}\n","import { createMcpExpressApp, type CreateMcpExpressAppOptions } from '@modelcontextprotocol/sdk/server/express.js'\nimport { AuthConfig } from '@silkweave/auth'\nimport { Action, AdapterFactory, createContext, SilkweaveContext, SilkweaveOptions } from '@silkweave/core'\nimport { CorsOptions } from 'cors'\nimport express, { type Express } from 'express'\nimport { Server } from 'http'\nimport { authMiddleware } from '../handlers/auth.js'\nimport { mcpCors } from '../handlers/cors.js'\nimport { protectedResourceMetadata } from '../handlers/metadata.js'\nimport { oauthRoutes } from '../handlers/oauth.js'\nimport { sideloadResource } from '../handlers/sideload.js'\nimport { mcpTransport } from '../handlers/transport.js'\n\nexport interface StartMcpHttpOptions extends CreateMcpExpressAppOptions {\n host: string\n port: number\n auth?: AuthConfig\n /** CORS configuration. `false` to disable, omitted/`true` for permissive defaults, or a `CorsOptions` object. */\n cors?: CorsOptions | boolean\n /** Mount the `/resource/:id` sideload route. Default `true`. */\n sideloadResources?: boolean\n /** Directory the sideload route reads from. Default `'resources'`. */\n resourceDir?: string\n}\n\n/**\n * Build a fully-wired Express app that exposes the MCP Streamable HTTP\n * transport (plus OAuth / sideload / well-known routes when configured).\n *\n * Pass the resulting `app` to `app.listen(port, host)` yourself, or use the\n * top-level `startMcpServer()` / `http()` adapter conveniences.\n */\nexport function buildMcpExpressApp(\n silkweaveOptions: SilkweaveOptions,\n context: SilkweaveContext,\n actions: Action[],\n options: StartMcpHttpOptions\n): Express {\n const { host, auth, cors: corsConfig, sideloadResources = true, resourceDir, ...mcpAppOptions } = options\n const app = createMcpExpressApp({ ...mcpAppOptions, host })\n\n const corsHandler = mcpCors(corsConfig ?? true)\n if (corsHandler) { app.use(corsHandler) }\n\n if (auth?.authorizationServers?.length && auth.resourceUrl) {\n app.get('/.well-known/oauth-protected-resource', protectedResourceMetadata(auth))\n }\n\n let oauthPaths = new Set<string>()\n if (auth?.provider) {\n const oauth = oauthRoutes(auth)\n app.get('/.well-known/oauth-authorization-server', oauth.wellKnownAuthServer)\n app.get('/authorize', oauth.authorize)\n app.get(oauth.callbackPath, oauth.callback)\n app.post('/token', ...oauth.token)\n app.post('/register', ...oauth.register)\n oauthPaths = new Set(['/.well-known/oauth-authorization-server', '/authorize', oauth.callbackPath, '/token', '/register'])\n }\n\n if (auth) {\n const guard = authMiddleware(auth, context)\n app.use((req, res, next) => {\n if (req.path.startsWith('/.well-known/') || oauthPaths.has(req.path)) { return next() }\n return guard(req, res, next)\n })\n }\n\n if (sideloadResources) {\n app.get('/resource/:id', sideloadResource({ resourceDir }))\n }\n\n const transport = mcpTransport(silkweaveOptions, context, actions)\n app.post('/mcp', express.json(), transport.post)\n app.get('/mcp', transport.stream)\n app.delete('/mcp', transport.stream)\n app.get('/mcp/resource/:id', transport.stream)\n\n return app\n}\n\n/**\n * Spin up a standalone MCP Streamable HTTP server on `host:port` for the\n * given `actions`. Returns the underlying `Server` so callers can close it.\n *\n * Convenience for use cases that don't go through the `silkweave()` builder.\n */\nexport async function startMcpServer(\n silkweaveOptions: SilkweaveOptions,\n actions: Action[],\n options: StartMcpHttpOptions,\n context?: SilkweaveContext\n): Promise<Server> {\n const ctx = context ?? createContext({ adapter: 'http' })\n const app = buildMcpExpressApp(silkweaveOptions, ctx, actions, options)\n return new Promise<Server>((resolve, reject) => {\n const server = app.listen(options.port, options.host, (error) => {\n if (error) { reject(error); return }\n console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`)\n resolve(server)\n })\n })\n}\n\n/**\n * Silkweave adapter that owns its own HTTP server. Composes the MCP handler\n * primitives into a fully-wired Express app and listens on `host:port`.\n */\nexport const http: AdapterFactory<StartMcpHttpOptions> = (options) => {\n return (silkweaveOptions, baseContext) => {\n const context = baseContext.fork({ adapter: 'http' })\n let httpServer: Server | undefined\n return {\n context,\n start: async (actions) => {\n const app = buildMcpExpressApp(silkweaveOptions, context, actions, options)\n httpServer = await new Promise<Server>((resolve, reject) => {\n const s = app.listen(options.port, options.host, (error) => {\n if (error) { reject(error); return }\n console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`)\n resolve(s)\n })\n })\n },\n stop: async () => {\n if (!httpServer) { return }\n await new Promise<void>((resolve, reject) => {\n httpServer!.close((err) => err ? reject(err) : resolve())\n })\n httpServer = undefined\n }\n }\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { Action, ActionRun, AdapterFactory, isStreamingAction, runStreamingAction, SilkweaveContext } from '@silkweave/core'\nimport { createLogger } from '@silkweave/logger'\nimport { capitalCase, pascalCase } from 'change-case'\nimport { handleToolError, smartToolResult } from '../util/result.js'\n\nfunction registerTools(server: McpServer, actions: Action[], context: SilkweaveContext) {\n for (const action of actions) {\n server.registerTool(pascalCase(action.name), {\n title: capitalCase(action.name),\n description: action.description,\n inputSchema: action.input\n }, async (input, extra) => {\n const logger = createLogger({\n stream: false,\n onLog: (level, data) => {\n extra.sendNotification({ method: 'notifications/message', params: { level, data } })\n },\n onProgress: ({ progress, total, message }) => {\n if (!extra._meta?.progressToken) { return }\n extra.sendNotification({\n method: 'notifications/progress',\n params: { progress, total, message, progressToken: extra._meta.progressToken }\n })\n }\n })\n const actionContext = context.fork({ logger, extra })\n const progressToken = extra._meta?.progressToken\n try {\n let result: object | object[]\n if (isStreamingAction(action)) {\n result = await runStreamingAction(action, input, actionContext, progressToken\n ? async (chunk, index) => {\n await extra.sendNotification({\n method: 'notifications/progress',\n params: { progressToken, progress: index + 1, message: JSON.stringify(chunk) }\n })\n }\n : undefined)\n } else {\n result = await (action.run as ActionRun<object, object>)(input, actionContext)\n }\n return action.toolResult?.(result, actionContext) ?? smartToolResult(result)\n } catch (error) {\n return handleToolError(error)\n }\n })\n }\n}\n\nexport const stdio: AdapterFactory = () => {\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'stdio' })\n const server = new McpServer({\n name: options.name,\n description: options.description,\n version: options.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n return {\n context,\n start: async (actions) => {\n registerTools(server, actions, context)\n const transport = new StdioServerTransport()\n await server.connect(transport)\n },\n stop: async () => {\n await server?.close()\n }\n }\n }\n}\n","import { randomUUID } from 'crypto'\nimport { writeFile } from 'fs/promises'\n\nexport interface SideloadResource {\n id: string\n name: string\n contentType: string\n size: number\n}\n\nexport async function createSideloadResource(buffer: Buffer, { name, contentType }: Pick<SideloadResource, 'name' | 'contentType'>) {\n const id = randomUUID()\n const resource: SideloadResource = { id, name, contentType, size: buffer.length }\n await Promise.all([\n writeFile(`resources/${id}`, buffer),\n writeFile(`resources/${id}.json`, JSON.stringify(resource), 'utf-8')\n ])\n return resource\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/handlers/cors.ts","../src/handlers/metadata.ts","../src/handlers/oauth.ts","../src/handlers/sideload.ts","../src/handlers/transport.ts","../src/adapter/http.ts","../src/adapter/stdio.ts","../src/util/sideload.ts"],"sourcesContent":["import cors, { CorsOptions } from 'cors'\nimport { type RequestHandler } from 'express'\n\n/** Headers required by the MCP protocol that must always be exposed when CORS is in use. */\nexport const MCP_REQUIRED_HEADERS = ['WWW-Authenticate', 'Last-Event-Id', 'Mcp-Protocol-Version']\n\n/**\n * CORS middleware preconfigured to expose the headers MCP clients need\n * (`Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`) on top of any\n * user-supplied options. (Stateless transport - no `Mcp-Session-Id`.)\n *\n * Pass `false` to disable, omit / pass `true` for permissive defaults, or pass\n * a `CorsOptions` object to override.\n */\nexport function mcpCors(corsConfig: CorsOptions | boolean = true): RequestHandler | null {\n if (corsConfig === false) { return null }\n const userConfig = corsConfig === true ? {} : corsConfig\n const userExposed = userConfig.exposedHeaders\n const exposedHeaders = [\n ...MCP_REQUIRED_HEADERS,\n ...(Array.isArray(userExposed) ? userExposed : userExposed ? [userExposed] : [])\n ]\n return cors({ origin: '*', ...userConfig, exposedHeaders })\n}\n","import { AuthConfig, generateProtectedResourceMetadata } from '@silkweave/auth'\nimport { type RequestHandler } from 'express'\n\n/**\n * Handler for `GET /.well-known/oauth-protected-resource` (RFC 9728). Returns\n * the resource server's metadata pointing at the configured authorization\n * servers. Requires `auth.resourceUrl` and a non-empty `auth.authorizationServers`.\n */\nexport function protectedResourceMetadata(auth: AuthConfig): RequestHandler {\n if (!auth.resourceUrl || !auth.authorizationServers?.length) {\n throw new Error('@silkweave/mcp protectedResourceMetadata(): auth.resourceUrl and auth.authorizationServers are required')\n }\n const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers, auth.requiredScopes)\n return (_req, res) => { res.json(metadata) }\n}\n","import { AuthConfig, OAuthRequest, OAuthResponse } from '@silkweave/auth'\nimport express, { type Request, type RequestHandler, type Response } from 'express'\n\nfunction toOAuthReq(req: Request): OAuthRequest {\n return {\n method: req.method,\n url: new URL(req.url, `${req.protocol}://${req.get('host')}`),\n headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),\n body: req.body as Record<string, string> | undefined\n }\n}\n\nfunction sendOAuth(res: Response, oauthRes: OAuthResponse) {\n for (const [key, value] of Object.entries(oauthRes.headers)) { res.header(key, value) }\n if (oauthRes.body) {\n res.status(oauthRes.status).send(typeof oauthRes.body === 'string' ? oauthRes.body : JSON.stringify(oauthRes.body))\n } else {\n res.status(oauthRes.status).end()\n }\n}\n\nexport interface OAuthRouteHandlers {\n /** `GET /.well-known/oauth-authorization-server` - RFC 8414 discovery. */\n wellKnownAuthServer: RequestHandler\n /** `GET /authorize` - start the OAuth flow. */\n authorize: RequestHandler\n /** `GET {callbackPath}` - provider callback. */\n callback: RequestHandler\n /** Path the provider should redirect to (defaults to `/auth/callback`). */\n callbackPath: string\n /** `POST /token` - exchange code / refresh token. Includes urlencoded body parser. */\n token: RequestHandler[]\n /** `POST /register` - dynamic client registration. Includes JSON body parser. */\n register: RequestHandler[]\n}\n\n/**\n * Build the OAuth 2.1 proxy route handlers (authorize, callback, token,\n * register, well-known) backed by the configured `auth.provider`. Returns the\n * handlers as individual `RequestHandler`s so callers can register them\n * wherever they like - under `/mcp`, at the server root, etc.\n *\n * `token` and `register` are returned as middleware arrays because the\n * appropriate body parser must run before the handler. Apply with\n * `app.post(path, ...token)`.\n */\nexport function oauthRoutes(auth: AuthConfig): OAuthRouteHandlers {\n if (!auth.provider) { throw new Error('@silkweave/mcp oauthRoutes(): auth.provider is required') }\n const provider = auth.provider\n const callbackPath = auth.callbackPath ?? '/auth/callback'\n\n return {\n callbackPath,\n wellKnownAuthServer: (_req, res) => { sendOAuth(res, provider.metadata()) },\n authorize: async (req, res) => { sendOAuth(res, await provider.authorize(toOAuthReq(req))) },\n callback: async (req, res) => { sendOAuth(res, await provider.callback(toOAuthReq(req))) },\n token: [\n express.urlencoded({ extended: false }),\n async (req, res) => { sendOAuth(res, await provider.token(toOAuthReq(req))) }\n ],\n register: [\n express.json(),\n async (req, res) => { sendOAuth(res, await provider.register(toOAuthReq(req))) }\n ]\n }\n}\n","import { type RequestHandler } from 'express'\nimport { readFile } from 'fs/promises'\nimport { type SideloadResource } from '../util/sideload.js'\n\nexport interface SideloadResourceOptions {\n /** Directory to read sideload resources from. Defaults to `resources/` (cwd-relative). */\n resourceDir?: string\n}\n\n/**\n * Handler for `GET /resource/:id` - serves a large MCP response that was\n * sideloaded to disk as a `{id}` payload with a `{id}.json` metadata sidecar.\n *\n * The route param `id` is provided by the host framework (Express, Nest, etc.).\n */\nexport function sideloadResource(options: SideloadResourceOptions = {}): RequestHandler {\n const { resourceDir = 'resources' } = options\n return async (req, res) => {\n const id = req.params['id']\n if (!id || typeof id !== 'string') { throw new Error('Invalid ID') }\n const resourceMeta: SideloadResource = JSON.parse(await readFile(`${resourceDir}/${id}.json`, 'utf-8'))\n const buffer = await readFile(`${resourceDir}/${id}`)\n res.status(200)\n res.header('Content-Type', resourceMeta.contentType)\n res.send(buffer)\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport { Action, SilkweaveContext, SilkweaveOptions } from '@silkweave/core'\nimport { type RequestHandler } from 'express'\nimport { registerTools } from './registerTools.js'\n\nfunction createMcpServer(options: SilkweaveOptions, actions: Action[], context: SilkweaveContext): McpServer {\n const server = new McpServer({\n name: options.name,\n description: options.description,\n version: options.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n registerTools(server, actions, context)\n return server\n}\n\nexport interface McpTransportHandlers {\n /** `POST /mcp` - handle a single stateless MCP request/response (SSE per call). */\n post: RequestHandler\n}\n\n/**\n * Build the MCP Streamable HTTP transport route handler.\n *\n * Stateless (per the 2026 spec direction): each `POST /mcp` mints a fresh\n * transport + server with `sessionIdGenerator: undefined`, handles exactly that\n * request (streaming progress over SSE when the call carries a `progressToken`),\n * and tears down on response close. No `Mcp-Session-Id`, no session map, no\n * `GET`/`DELETE` reconnect - any request can hit any instance.\n */\nexport function mcpTransport(\n silkweaveOptions: SilkweaveOptions,\n context: SilkweaveContext,\n actions: Action[]\n): McpTransportHandlers {\n const post: RequestHandler = async (req, res) => {\n try {\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })\n const server = createMcpServer(silkweaveOptions, actions, context)\n res.on('close', () => { void transport.close(); void server.close() })\n await server.connect(transport)\n await transport.handleRequest(req, res, req.body)\n } catch (error) {\n console.error('Error handling MCP request:', error)\n if (!res.headersSent) {\n res.status(500).json({ jsonrpc: '2.0', error: { code: -32_603, message: 'Internal server error' }, id: null })\n }\n }\n }\n\n return { post }\n}\n","import { createMcpExpressApp, type CreateMcpExpressAppOptions } from '@modelcontextprotocol/sdk/server/express.js'\nimport { AuthConfig } from '@silkweave/auth'\nimport { Action, AdapterFactory, createContext, SilkweaveContext, SilkweaveOptions } from '@silkweave/core'\nimport { CorsOptions } from 'cors'\nimport express, { type Express } from 'express'\nimport { Server } from 'http'\nimport { authMiddleware } from '../handlers/auth.js'\nimport { mcpCors } from '../handlers/cors.js'\nimport { protectedResourceMetadata } from '../handlers/metadata.js'\nimport { oauthRoutes } from '../handlers/oauth.js'\nimport { sideloadResource } from '../handlers/sideload.js'\nimport { mcpTransport } from '../handlers/transport.js'\n\nexport interface StartMcpHttpOptions extends CreateMcpExpressAppOptions {\n host: string\n port: number\n auth?: AuthConfig\n /** CORS configuration. `false` to disable, omitted/`true` for permissive defaults, or a `CorsOptions` object. */\n cors?: CorsOptions | boolean\n /** Mount the `/resource/:id` sideload route. Default `true`. */\n sideloadResources?: boolean\n /** Directory the sideload route reads from. Default `'resources'`. */\n resourceDir?: string\n}\n\n/**\n * Build a fully-wired Express app that exposes the MCP Streamable HTTP\n * transport (plus OAuth / sideload / well-known routes when configured).\n *\n * Pass the resulting `app` to `app.listen(port, host)` yourself, or use the\n * top-level `startMcpServer()` / `http()` adapter conveniences.\n */\nexport function buildMcpExpressApp(\n silkweaveOptions: SilkweaveOptions,\n context: SilkweaveContext,\n actions: Action[],\n options: StartMcpHttpOptions\n): Express {\n const { host, auth, cors: corsConfig, sideloadResources = true, resourceDir, ...mcpAppOptions } = options\n const app = createMcpExpressApp({ ...mcpAppOptions, host })\n\n const corsHandler = mcpCors(corsConfig ?? true)\n if (corsHandler) { app.use(corsHandler) }\n\n if (auth?.authorizationServers?.length && auth.resourceUrl) {\n app.get('/.well-known/oauth-protected-resource', protectedResourceMetadata(auth))\n }\n\n let oauthPaths = new Set<string>()\n if (auth?.provider) {\n const oauth = oauthRoutes(auth)\n app.get('/.well-known/oauth-authorization-server', oauth.wellKnownAuthServer)\n app.get('/authorize', oauth.authorize)\n app.get(oauth.callbackPath, oauth.callback)\n app.post('/token', ...oauth.token)\n app.post('/register', ...oauth.register)\n oauthPaths = new Set(['/.well-known/oauth-authorization-server', '/authorize', oauth.callbackPath, '/token', '/register'])\n }\n\n if (auth) {\n const guard = authMiddleware(auth, context)\n app.use((req, res, next) => {\n if (req.path.startsWith('/.well-known/') || oauthPaths.has(req.path)) { return next() }\n return guard(req, res, next)\n })\n }\n\n if (sideloadResources) {\n app.get('/resource/:id', sideloadResource({ resourceDir }))\n }\n\n const transport = mcpTransport(silkweaveOptions, context, actions)\n app.post('/mcp', express.json(), transport.post)\n\n return app\n}\n\n/**\n * Spin up a standalone MCP Streamable HTTP server on `host:port` for the\n * given `actions`. Returns the underlying `Server` so callers can close it.\n *\n * Convenience for use cases that don't go through the `silkweave()` builder.\n */\nexport async function startMcpServer(\n silkweaveOptions: SilkweaveOptions,\n actions: Action[],\n options: StartMcpHttpOptions,\n context?: SilkweaveContext\n): Promise<Server> {\n const ctx = context ?? createContext({ adapter: 'http' })\n const app = buildMcpExpressApp(silkweaveOptions, ctx, actions, options)\n return new Promise<Server>((resolve, reject) => {\n const server = app.listen(options.port, options.host, (error) => {\n if (error) { reject(error); return }\n console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`)\n resolve(server)\n })\n })\n}\n\n/**\n * Silkweave adapter that owns its own HTTP server. Composes the MCP handler\n * primitives into a fully-wired Express app and listens on `host:port`.\n */\nexport const http: AdapterFactory<StartMcpHttpOptions> = (options) => {\n return (silkweaveOptions, baseContext) => {\n const context = baseContext.fork({ adapter: 'http' })\n let httpServer: Server | undefined\n return {\n context,\n start: async (actions) => {\n const app = buildMcpExpressApp(silkweaveOptions, context, actions, options)\n httpServer = await new Promise<Server>((resolve, reject) => {\n const s = app.listen(options.port, options.host, (error) => {\n if (error) { reject(error); return }\n console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`)\n resolve(s)\n })\n })\n },\n stop: async () => {\n if (!httpServer) { return }\n await new Promise<void>((resolve, reject) => {\n httpServer!.close((err) => err ? reject(err) : resolve())\n })\n httpServer = undefined\n }\n }\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { AdapterFactory } from '@silkweave/core'\nimport { registerTools } from '../handlers/registerTools.js'\n\nexport const stdio: AdapterFactory = () => {\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'stdio' })\n const server = new McpServer({\n name: options.name,\n description: options.description,\n version: options.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n return {\n context,\n start: async (actions) => {\n registerTools(server, actions, context, { logStream: false })\n const transport = new StdioServerTransport()\n await server.connect(transport)\n },\n stop: async () => {\n await server?.close()\n }\n }\n }\n}\n","import { randomUUID } from 'crypto'\nimport { writeFile } from 'fs/promises'\n\nexport interface SideloadResource {\n id: string\n name: string\n contentType: string\n size: number\n}\n\nexport async function createSideloadResource(buffer: Buffer, { name, contentType }: Pick<SideloadResource, 'name' | 'contentType'>) {\n const id = randomUUID()\n const resource: SideloadResource = { id, name, contentType, size: buffer.length }\n await Promise.all([\n writeFile(`resources/${id}`, buffer),\n writeFile(`resources/${id}.json`, JSON.stringify(resource), 'utf-8')\n ])\n return resource\n}\n"],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ import { a as smartToolResult, n as handleToolError, r as jsonToolResult } from "./result-B_9Eo1ey.mjs";
2
+ import { createLogger, isStreamingAction, runStreamingAction } from "@silkweave/core";
3
+ import { validateToken } from "@silkweave/auth";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+ import { capitalCase, pascalCase } from "change-case";
6
+ //#region src/handlers/auth.ts
7
+ /**
8
+ * Per-request bearer-token storage used by tool handlers to read the resolved
9
+ * `AuthInfo` for the currently-handled MCP call.
10
+ */
11
+ const authStorage = new AsyncLocalStorage();
12
+ /**
13
+ * Express middleware that validates the `Authorization: Bearer …` header via
14
+ * the supplied `AuthConfig`. On success the resolved `AuthInfo` is placed in
15
+ * `authStorage` for the duration of the downstream handler - `mcpTransport`'s
16
+ * tool callbacks pick it up to populate the silkweave context's `auth` key.
17
+ *
18
+ * The middleware should NOT be applied to OAuth-discovery / token routes -
19
+ * compose it only on the routes that require an authenticated caller (the MCP
20
+ * transport itself, sideload, etc.).
21
+ */
22
+ function authMiddleware(auth, context) {
23
+ return async (req, res, next) => {
24
+ const result = await validateToken(req.headers.authorization, auth, context.fork({ request: req }));
25
+ if (result.error) {
26
+ for (const [key, value] of Object.entries(result.error.headers)) res.header(key, value);
27
+ res.status(result.error.statusCode).json(result.error.body);
28
+ return;
29
+ }
30
+ if (result.auth) authStorage.run(result.auth, () => {
31
+ next();
32
+ });
33
+ else next();
34
+ };
35
+ }
36
+ //#endregion
37
+ //#region src/handlers/registerTools.ts
38
+ /**
39
+ * Build the generic `request` context value (the same key REST/tRPC populate)
40
+ * from the MCP SDK's `extra.requestInfo`, so transport-agnostic consumers - e.g.
41
+ * `@silkweave/nestjs` `@UseGuards` guards reading
42
+ * `switchToHttp().getRequest().headers` - work over MCP. There are no path
43
+ * `params`/`query`/`body` on the raw MCP call, so those start as empty
44
+ * stand-ins; `@silkweave/nestjs` later fills them from the validated tool input
45
+ * per the reflected param sources (path -> `params`, `@Query` -> `query`,
46
+ * `@Body` -> `body`) so guards reading any of them decide as they would over
47
+ * REST. Returns `undefined` when no HTTP request info is available.
48
+ */
49
+ function requestFromExtra(requestInfo) {
50
+ if (!requestInfo) return;
51
+ return {
52
+ headers: requestInfo.headers ?? {},
53
+ url: requestInfo.url?.toString(),
54
+ params: {},
55
+ query: {},
56
+ body: {}
57
+ };
58
+ }
59
+ /** Per-call logger that bridges silkweave logs/progress onto MCP notifications. */
60
+ function createToolLogger(extra, stream) {
61
+ return createLogger({
62
+ stream,
63
+ onLog: (level, data) => {
64
+ extra.sendNotification({
65
+ method: "notifications/message",
66
+ params: {
67
+ level,
68
+ data
69
+ }
70
+ });
71
+ },
72
+ onProgress: ({ progress, total, message }) => {
73
+ if (!extra._meta?.progressToken) return;
74
+ extra.sendNotification({
75
+ method: "notifications/progress",
76
+ params: {
77
+ progress,
78
+ total,
79
+ message,
80
+ progressToken: extra._meta.progressToken
81
+ }
82
+ });
83
+ }
84
+ });
85
+ }
86
+ /** Run the action, streaming chunks as progress notifications when a token is set. */
87
+ async function runAction(action, input, context, extra) {
88
+ const progressToken = extra._meta?.progressToken;
89
+ if (isStreamingAction(action)) return runStreamingAction(action, input, context, progressToken ? async (chunk, index) => {
90
+ await extra.sendNotification({
91
+ method: "notifications/progress",
92
+ params: {
93
+ progressToken,
94
+ progress: index + 1,
95
+ message: JSON.stringify(chunk)
96
+ }
97
+ });
98
+ } : void 0);
99
+ return action.run(input, context);
100
+ }
101
+ /** Format via the action's `toolResult` hook, else the resolved disposition. */
102
+ function formatToolResult(action, result, context, disposition) {
103
+ if (action.toolResult) {
104
+ const response = action.toolResult(result, context);
105
+ if (response) return response;
106
+ }
107
+ return disposition === "json" ? jsonToolResult(result) : smartToolResult(result);
108
+ }
109
+ /**
110
+ * Register every action as an MCP tool on `server`. Shared by all MCP transports
111
+ * (`stdio`, `http`, `edge`). Each tool call forks `context` with a per-call
112
+ * `logger`, the SDK `extra`, a synthesized `request` (see `requestFromExtra`),
113
+ * and - when bearer auth ran for this request - the resolved `auth`. The result
114
+ * is formatted by the action's `toolResult` hook if present, otherwise per the
115
+ * resolved disposition (client `_meta.disposition` > `action.disposition` >
116
+ * `smart`).
117
+ */
118
+ function registerTools(server, actions, context, options = {}) {
119
+ const stream = options.logStream ?? process.stderr;
120
+ for (const action of actions) server.registerTool(pascalCase(action.name), {
121
+ title: capitalCase(action.name),
122
+ description: action.description,
123
+ inputSchema: action.input
124
+ }, async (input, extra) => {
125
+ const logger = createToolLogger(extra, stream);
126
+ const currentAuth = authStorage.getStore();
127
+ const actionContext = context.fork({
128
+ logger,
129
+ extra,
130
+ request: requestFromExtra(extra.requestInfo),
131
+ ...currentAuth ? { auth: currentAuth } : {}
132
+ });
133
+ const disposition = extra._meta?.disposition ?? action.disposition;
134
+ try {
135
+ return formatToolResult(action, await runAction(action, input, actionContext, extra), actionContext, disposition);
136
+ } catch (error) {
137
+ return handleToolError(error);
138
+ }
139
+ });
140
+ }
141
+ //#endregion
142
+ export { authStorage as i, requestFromExtra as n, authMiddleware as r, registerTools as t };
143
+
144
+ //# sourceMappingURL=registerTools-Z6JnoM60.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerTools-Z6JnoM60.mjs","names":[],"sources":["../src/handlers/auth.ts","../src/handlers/registerTools.ts"],"sourcesContent":["import { AuthConfig, AuthInfo, validateToken } from '@silkweave/auth'\nimport { SilkweaveContext } from '@silkweave/core'\nimport { type RequestHandler } from 'express'\nimport { AsyncLocalStorage } from 'node:async_hooks'\n\n/**\n * Per-request bearer-token storage used by tool handlers to read the resolved\n * `AuthInfo` for the currently-handled MCP call.\n */\nexport const authStorage = new AsyncLocalStorage<AuthInfo>()\n\n/**\n * Express middleware that validates the `Authorization: Bearer …` header via\n * the supplied `AuthConfig`. On success the resolved `AuthInfo` is placed in\n * `authStorage` for the duration of the downstream handler - `mcpTransport`'s\n * tool callbacks pick it up to populate the silkweave context's `auth` key.\n *\n * The middleware should NOT be applied to OAuth-discovery / token routes -\n * compose it only on the routes that require an authenticated caller (the MCP\n * transport itself, sideload, etc.).\n */\nexport function authMiddleware(auth: AuthConfig, context: SilkweaveContext): RequestHandler {\n return async (req, res, next) => {\n const result = await validateToken(req.headers.authorization, auth, context.fork({ request: req }))\n if (result.error) {\n for (const [key, value] of Object.entries(result.error.headers)) { res.header(key, value) }\n res.status(result.error.statusCode).json(result.error.body)\n return\n }\n if (result.auth) {\n authStorage.run(result.auth, () => { next() })\n } else {\n next()\n }\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'\nimport type { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js'\nimport { Action, ActionRun, createLogger, isStreamingAction, runStreamingAction, SilkweaveContext } from '@silkweave/core'\nimport { capitalCase, pascalCase } from 'change-case'\nimport { handleToolError, jsonToolResult, smartToolResult } from '../util/result.js'\nimport { authStorage } from './auth.js'\n\ntype LogStream = NonNullable<Parameters<typeof createLogger>[0]>['stream']\ntype ToolExtra = RequestHandlerExtra<ServerRequest, ServerNotification>\n\nexport interface RegisterToolsOptions {\n /**\n * Where the per-call logger writes its human-readable stream. The `stdio`\n * adapter MUST pass `false` (stdout is the MCP protocol channel); the HTTP\n * transports default to `process.stderr`.\n */\n logStream?: LogStream\n}\n\n/**\n * Build the generic `request` context value (the same key REST/tRPC populate)\n * from the MCP SDK's `extra.requestInfo`, so transport-agnostic consumers - e.g.\n * `@silkweave/nestjs` `@UseGuards` guards reading\n * `switchToHttp().getRequest().headers` - work over MCP. There are no path\n * `params`/`query`/`body` on the raw MCP call, so those start as empty\n * stand-ins; `@silkweave/nestjs` later fills them from the validated tool input\n * per the reflected param sources (path -> `params`, `@Query` -> `query`,\n * `@Body` -> `body`) so guards reading any of them decide as they would over\n * REST. Returns `undefined` when no HTTP request info is available.\n */\nexport function requestFromExtra(requestInfo: { headers?: unknown; url?: { toString(): string } } | undefined) {\n if (!requestInfo) { return undefined }\n return { headers: requestInfo.headers ?? {}, url: requestInfo.url?.toString(), params: {}, query: {}, body: {} }\n}\n\n/** Per-call logger that bridges silkweave logs/progress onto MCP notifications. */\nfunction createToolLogger(extra: ToolExtra, stream: LogStream) {\n return createLogger({\n stream,\n onLog: (level, data) => {\n extra.sendNotification({ method: 'notifications/message', params: { level, data } })\n },\n onProgress: ({ progress, total, message }) => {\n if (!extra._meta?.progressToken) { return }\n extra.sendNotification({\n method: 'notifications/progress',\n params: { progress, total, message, progressToken: extra._meta.progressToken }\n })\n }\n })\n}\n\n/** Run the action, streaming chunks as progress notifications when a token is set. */\nasync function runAction(action: Action, input: object, context: SilkweaveContext, extra: ToolExtra): Promise<object | object[]> {\n const progressToken = extra._meta?.progressToken\n if (isStreamingAction(action)) {\n return runStreamingAction(action, input, context, progressToken\n ? async (chunk, index) => {\n await extra.sendNotification({\n method: 'notifications/progress',\n params: { progressToken, progress: index + 1, message: JSON.stringify(chunk) }\n })\n }\n : undefined)\n }\n return (action.run as ActionRun<object, object>)(input, context)\n}\n\n/** Format via the action's `toolResult` hook, else the resolved disposition. */\nfunction formatToolResult(action: Action, result: object | object[], context: SilkweaveContext, disposition: unknown) {\n if (action.toolResult) {\n const response = action.toolResult(result, context)\n if (response) { return response }\n }\n return disposition === 'json' ? jsonToolResult(result) : smartToolResult(result)\n}\n\n/**\n * Register every action as an MCP tool on `server`. Shared by all MCP transports\n * (`stdio`, `http`, `edge`). Each tool call forks `context` with a per-call\n * `logger`, the SDK `extra`, a synthesized `request` (see `requestFromExtra`),\n * and - when bearer auth ran for this request - the resolved `auth`. The result\n * is formatted by the action's `toolResult` hook if present, otherwise per the\n * resolved disposition (client `_meta.disposition` > `action.disposition` >\n * `smart`).\n */\nexport function registerTools(\n server: McpServer,\n actions: Action[],\n context: SilkweaveContext,\n options: RegisterToolsOptions = {}\n) {\n const stream = options.logStream ?? process.stderr\n for (const action of actions) {\n server.registerTool(pascalCase(action.name), {\n title: capitalCase(action.name),\n description: action.description,\n inputSchema: action.input\n }, async (input, extra) => {\n const logger = createToolLogger(extra, stream)\n const currentAuth = authStorage.getStore()\n const actionContext = context.fork({\n logger,\n extra,\n request: requestFromExtra(extra.requestInfo),\n ...(currentAuth ? { auth: currentAuth } : {})\n })\n // Client-sent `_meta.disposition` wins; otherwise fall back to the\n // action's configured default (`smart` when neither is set).\n const disposition = extra._meta?.disposition ?? action.disposition\n try {\n const result = await runAction(action, input, actionContext, extra)\n return formatToolResult(action, result, actionContext, disposition)\n } catch (error) {\n return handleToolError(error)\n }\n })\n }\n}\n"],"mappings":";;;;;;;;;;AASA,MAAa,cAAc,IAAI,mBAA6B;;;;;;;;;;;AAY5D,SAAgB,eAAe,MAAkB,SAA2C;AAC1F,QAAO,OAAO,KAAK,KAAK,SAAS;EAC/B,MAAM,SAAS,MAAM,cAAc,IAAI,QAAQ,eAAe,MAAM,QAAQ,KAAK,EAAE,SAAS,KAAK,CAAC,CAAC;AACnG,MAAI,OAAO,OAAO;AAChB,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,MAAM,QAAQ,CAAI,KAAI,OAAO,KAAK,MAAM;AACzF,OAAI,OAAO,OAAO,MAAM,WAAW,CAAC,KAAK,OAAO,MAAM,KAAK;AAC3D;;AAEF,MAAI,OAAO,KACT,aAAY,IAAI,OAAO,YAAY;AAAE,SAAM;IAAG;MAE9C,OAAM;;;;;;;;;;;;;;;;ACDZ,SAAgB,iBAAiB,aAA8E;AAC7G,KAAI,CAAC,YAAe;AACpB,QAAO;EAAE,SAAS,YAAY,WAAW,EAAE;EAAE,KAAK,YAAY,KAAK,UAAU;EAAE,QAAQ,EAAE;EAAE,OAAO,EAAE;EAAE,MAAM,EAAE;EAAE;;;AAIlH,SAAS,iBAAiB,OAAkB,QAAmB;AAC7D,QAAO,aAAa;EAClB;EACA,QAAQ,OAAO,SAAS;AACtB,SAAM,iBAAiB;IAAE,QAAQ;IAAyB,QAAQ;KAAE;KAAO;KAAM;IAAE,CAAC;;EAEtF,aAAa,EAAE,UAAU,OAAO,cAAc;AAC5C,OAAI,CAAC,MAAM,OAAO,cAAiB;AACnC,SAAM,iBAAiB;IACrB,QAAQ;IACR,QAAQ;KAAE;KAAU;KAAO;KAAS,eAAe,MAAM,MAAM;KAAe;IAC/E,CAAC;;EAEL,CAAC;;;AAIJ,eAAe,UAAU,QAAgB,OAAe,SAA2B,OAA8C;CAC/H,MAAM,gBAAgB,MAAM,OAAO;AACnC,KAAI,kBAAkB,OAAO,CAC3B,QAAO,mBAAmB,QAAQ,OAAO,SAAS,gBAC9C,OAAO,OAAO,UAAU;AACxB,QAAM,MAAM,iBAAiB;GAC3B,QAAQ;GACR,QAAQ;IAAE;IAAe,UAAU,QAAQ;IAAG,SAAS,KAAK,UAAU,MAAM;IAAE;GAC/E,CAAC;KAEF,KAAA,EAAU;AAEhB,QAAQ,OAAO,IAAkC,OAAO,QAAQ;;;AAIlE,SAAS,iBAAiB,QAAgB,QAA2B,SAA2B,aAAsB;AACpH,KAAI,OAAO,YAAY;EACrB,MAAM,WAAW,OAAO,WAAW,QAAQ,QAAQ;AACnD,MAAI,SAAY,QAAO;;AAEzB,QAAO,gBAAgB,SAAS,eAAe,OAAO,GAAG,gBAAgB,OAAO;;;;;;;;;;;AAYlF,SAAgB,cACd,QACA,SACA,SACA,UAAgC,EAAE,EAClC;CACA,MAAM,SAAS,QAAQ,aAAa,QAAQ;AAC5C,MAAK,MAAM,UAAU,QACnB,QAAO,aAAa,WAAW,OAAO,KAAK,EAAE;EAC3C,OAAO,YAAY,OAAO,KAAK;EAC/B,aAAa,OAAO;EACpB,aAAa,OAAO;EACrB,EAAE,OAAO,OAAO,UAAU;EACzB,MAAM,SAAS,iBAAiB,OAAO,OAAO;EAC9C,MAAM,cAAc,YAAY,UAAU;EAC1C,MAAM,gBAAgB,QAAQ,KAAK;GACjC;GACA;GACA,SAAS,iBAAiB,MAAM,YAAY;GAC5C,GAAI,cAAc,EAAE,MAAM,aAAa,GAAG,EAAE;GAC7C,CAAC;EAGF,MAAM,cAAc,MAAM,OAAO,eAAe,OAAO;AACvD,MAAI;AAEF,UAAO,iBAAiB,QAAQ,MADX,UAAU,QAAQ,OAAO,eAAe,MAAM,EAC3B,eAAe,YAAY;WAC5D,OAAO;AACd,UAAO,gBAAgB,MAAM;;GAE/B"}
@@ -76,4 +76,4 @@ function parseResourceMessage({ resource }) {
76
76
  //#endregion
77
77
  export { smartToolResult as a, parseResourceMessage as i, handleToolError as n, jsonToolResult as r, errorToolResult as t };
78
78
 
79
- //# sourceMappingURL=result-BiFuFt5B.mjs.map
79
+ //# sourceMappingURL=result-B_9Eo1ey.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"result-BiFuFt5B.mjs","names":[],"sources":["../src/util/result.ts"],"sourcesContent":["import { EmbeddedResource, type CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { SilkweaveError } from '@silkweave/core'\nimport { randomUUID } from 'node:crypto'\n\nexport function smartToolResult(data: string | object | object[]): CallToolResult {\n const text = typeof data === 'string' ? data : JSON.stringify(data)\n const mimeType = typeof data === 'string' ? 'text/plain' : 'application/json'\n const ext = typeof data === 'string' ? 'txt' : 'json'\n if (text.length > 4096) {\n const uri = `mcp://toolResult/${randomUUID()}.${ext}`\n const buffer = Buffer.from(text)\n const blob = buffer.toString('base64')\n return {\n content: [\n { type: 'text', text: `Received resource ${uri} with ${buffer.byteLength} bytes` },\n { type: 'resource', resource: { uri, mimeType, blob } }\n ]\n }\n } else {\n return {\n content: [{ type: 'text' as const, text }]\n }\n }\n}\n\nexport function jsonToolResult(data: object, isError = false): CallToolResult {\n const result: CallToolResult = { content: [{ type: 'text' as const, text: JSON.stringify(data) }] }\n if (isError) { result.isError = true }\n return result\n}\n\nexport function errorToolResult({ code, name, message }: SilkweaveError): CallToolResult {\n return {\n isError: true,\n content: [{\n type: 'text',\n text: JSON.stringify({ success: false, code, name, message })\n }]\n }\n}\n\nexport function handleToolError(error: unknown): CallToolResult {\n if (error instanceof SilkweaveError) {\n return jsonToolResult({ success: false, name: error.name, message: error.message, code: error.code }, true)\n } else if (error instanceof Error) {\n // Log the full error (incl. stack) to stderr only - never put the stack trace\n // on the wire, where it would leak server internals to the MCP client.\n console.error(error)\n return jsonToolResult({ success: false, name: error.name, message: error.message }, true)\n } else {\n // Likewise keep the raw thrown value server-side - only a generic message goes out.\n console.error('Unknown tool error:', error)\n return jsonToolResult({ success: false, name: 'Unknown error', message: 'An unknown error occurred' }, true)\n }\n}\n\nexport function parseResourceMessage({ resource }: EmbeddedResource) {\n const text = ('blob' in resource) ? Buffer.from(resource.blob, 'base64').toString('utf-8') : resource.text\n return text\n}\n"],"mappings":";;;AAIA,SAAgB,gBAAgB,MAAkD;CAChF,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,KAAK;CACnE,MAAM,WAAW,OAAO,SAAS,WAAW,eAAe;CAC3D,MAAM,MAAM,OAAO,SAAS,WAAW,QAAQ;AAC/C,KAAI,KAAK,SAAS,MAAM;EACtB,MAAM,MAAM,oBAAoB,YAAY,CAAC,GAAG;EAChD,MAAM,SAAS,OAAO,KAAK,KAAK;EAChC,MAAM,OAAO,OAAO,SAAS,SAAS;AACtC,SAAO,EACL,SAAS,CACP;GAAE,MAAM;GAAQ,MAAM,qBAAqB,IAAI,QAAQ,OAAO,WAAW;GAAS,EAClF;GAAE,MAAM;GAAY,UAAU;IAAE;IAAK;IAAU;IAAM;GAAE,CACxD,EACF;OAED,QAAO,EACL,SAAS,CAAC;EAAE,MAAM;EAAiB;EAAM,CAAC,EAC3C;;AAIL,SAAgB,eAAe,MAAc,UAAU,OAAuB;CAC5E,MAAM,SAAyB,EAAE,SAAS,CAAC;EAAE,MAAM;EAAiB,MAAM,KAAK,UAAU,KAAK;EAAE,CAAC,EAAE;AACnG,KAAI,QAAW,QAAO,UAAU;AAChC,QAAO;;AAGT,SAAgB,gBAAgB,EAAE,MAAM,MAAM,WAA2C;AACvF,QAAO;EACL,SAAS;EACT,SAAS,CAAC;GACR,MAAM;GACN,MAAM,KAAK,UAAU;IAAE,SAAS;IAAO;IAAM;IAAM;IAAS,CAAC;GAC9D,CAAC;EACH;;AAGH,SAAgB,gBAAgB,OAAgC;AAC9D,KAAI,iBAAiB,eACnB,QAAO,eAAe;EAAE,SAAS;EAAO,MAAM,MAAM;EAAM,SAAS,MAAM;EAAS,MAAM,MAAM;EAAM,EAAE,KAAK;UAClG,iBAAiB,OAAO;AAGjC,UAAQ,MAAM,MAAM;AACpB,SAAO,eAAe;GAAE,SAAS;GAAO,MAAM,MAAM;GAAM,SAAS,MAAM;GAAS,EAAE,KAAK;QACpF;AAEL,UAAQ,MAAM,uBAAuB,MAAM;AAC3C,SAAO,eAAe;GAAE,SAAS;GAAO,MAAM;GAAiB,SAAS;GAA6B,EAAE,KAAK;;;AAIhH,SAAgB,qBAAqB,EAAE,YAA8B;AAEnE,QADc,UAAU,WAAY,OAAO,KAAK,SAAS,MAAM,SAAS,CAAC,SAAS,QAAQ,GAAG,SAAS"}
1
+ {"version":3,"file":"result-B_9Eo1ey.mjs","names":[],"sources":["../src/util/result.ts"],"sourcesContent":["import { EmbeddedResource, type CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { SilkweaveError } from '@silkweave/core'\nimport { randomUUID } from 'node:crypto'\n\nexport function smartToolResult(data: string | object | object[]): CallToolResult {\n const text = typeof data === 'string' ? data : JSON.stringify(data)\n const mimeType = typeof data === 'string' ? 'text/plain' : 'application/json'\n const ext = typeof data === 'string' ? 'txt' : 'json'\n if (text.length > 4096) {\n const uri = `mcp://toolResult/${randomUUID()}.${ext}`\n const buffer = Buffer.from(text)\n const blob = buffer.toString('base64')\n return {\n content: [\n { type: 'text', text: `Received resource ${uri} with ${buffer.byteLength} bytes` },\n { type: 'resource', resource: { uri, mimeType, blob } }\n ]\n }\n } else {\n return {\n content: [{ type: 'text' as const, text }]\n }\n }\n}\n\nexport function jsonToolResult(data: object, isError = false): CallToolResult {\n const result: CallToolResult = { content: [{ type: 'text' as const, text: JSON.stringify(data) }] }\n if (isError) { result.isError = true }\n return result\n}\n\nexport function errorToolResult({ code, name, message }: SilkweaveError): CallToolResult {\n return {\n isError: true,\n content: [{\n type: 'text',\n text: JSON.stringify({ success: false, code, name, message })\n }]\n }\n}\n\nexport function handleToolError(error: unknown): CallToolResult {\n if (error instanceof SilkweaveError) {\n return jsonToolResult({ success: false, name: error.name, message: error.message, code: error.code }, true)\n } else if (error instanceof Error) {\n // Log the full error (incl. stack) to stderr only - never put the stack trace\n // on the wire, where it would leak server internals to the MCP client.\n console.error(error)\n return jsonToolResult({ success: false, name: error.name, message: error.message }, true)\n } else {\n // Likewise keep the raw thrown value server-side - only a generic message goes out.\n console.error('Unknown tool error:', error)\n return jsonToolResult({ success: false, name: 'Unknown error', message: 'An unknown error occurred' }, true)\n }\n}\n\nexport function parseResourceMessage({ resource }: EmbeddedResource) {\n const text = ('blob' in resource) ? Buffer.from(resource.blob, 'base64').toString('utf-8') : resource.text\n return text\n}\n"],"mappings":";;;AAIA,SAAgB,gBAAgB,MAAkD;CAChF,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,KAAK;CACnE,MAAM,WAAW,OAAO,SAAS,WAAW,eAAe;CAC3D,MAAM,MAAM,OAAO,SAAS,WAAW,QAAQ;AAC/C,KAAI,KAAK,SAAS,MAAM;EACtB,MAAM,MAAM,oBAAoB,YAAY,CAAC,GAAG;EAChD,MAAM,SAAS,OAAO,KAAK,KAAK;EAChC,MAAM,OAAO,OAAO,SAAS,SAAS;AACtC,SAAO,EACL,SAAS,CACP;GAAE,MAAM;GAAQ,MAAM,qBAAqB,IAAI,QAAQ,OAAO,WAAW;GAAS,EAClF;GAAE,MAAM;GAAY,UAAU;IAAE;IAAK;IAAU;IAAM;GAAE,CACxD,EACF;OAED,QAAO,EACL,SAAS,CAAC;EAAE,MAAM;EAAiB;EAAM,CAAC,EAC3C;;AAIL,SAAgB,eAAe,MAAc,UAAU,OAAuB;CAC5E,MAAM,SAAyB,EAAE,SAAS,CAAC;EAAE,MAAM;EAAiB,MAAM,KAAK,UAAU,KAAK;EAAE,CAAC,EAAE;AACnG,KAAI,QAAW,QAAO,UAAU;AAChC,QAAO;;AAGT,SAAgB,gBAAgB,EAAE,MAAM,MAAM,WAA2C;AACvF,QAAO;EACL,SAAS;EACT,SAAS,CAAC;GACR,MAAM;GACN,MAAM,KAAK,UAAU;IAAE,SAAS;IAAO;IAAM;IAAM;IAAS,CAAC;GAC9D,CAAC;EACH;;AAGH,SAAgB,gBAAgB,OAAgC;AAC9D,KAAI,iBAAiB,eACnB,QAAO,eAAe;EAAE,SAAS;EAAO,MAAM,MAAM;EAAM,SAAS,MAAM;EAAS,MAAM,MAAM;EAAM,EAAE,KAAK;UAClG,iBAAiB,OAAO;AAGjC,UAAQ,MAAM,MAAM;AACpB,SAAO,eAAe;GAAE,SAAS;GAAO,MAAM,MAAM;GAAM,SAAS,MAAM;GAAS,EAAE,KAAK;QACpF;AAEL,UAAQ,MAAM,uBAAuB,MAAM;AAC3C,SAAO,eAAe;GAAE,SAAS;GAAO,MAAM;GAAiB,SAAS;GAA6B,EAAE,KAAK;;;AAIhH,SAAgB,qBAAqB,EAAE,YAA8B;AAEnE,QADc,UAAU,WAAY,OAAO,KAAK,SAAS,MAAM,SAAS,CAAC,SAAS,QAAQ,GAAG,SAAS"}
@@ -0,0 +1,63 @@
1
+ import { Action, SilkweaveContext, SilkweaveError, createLogger } from "@silkweave/core";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { CallToolResult, EmbeddedResource } from "@modelcontextprotocol/sdk/types.js";
4
+
5
+ //#region src/handlers/registerTools.d.ts
6
+ type LogStream = NonNullable<Parameters<typeof createLogger>[0]>['stream'];
7
+ interface RegisterToolsOptions {
8
+ /**
9
+ * Where the per-call logger writes its human-readable stream. The `stdio`
10
+ * adapter MUST pass `false` (stdout is the MCP protocol channel); the HTTP
11
+ * transports default to `process.stderr`.
12
+ */
13
+ logStream?: LogStream;
14
+ }
15
+ /**
16
+ * Build the generic `request` context value (the same key REST/tRPC populate)
17
+ * from the MCP SDK's `extra.requestInfo`, so transport-agnostic consumers - e.g.
18
+ * `@silkweave/nestjs` `@UseGuards` guards reading
19
+ * `switchToHttp().getRequest().headers` - work over MCP. There are no path
20
+ * `params`/`query`/`body` on the raw MCP call, so those start as empty
21
+ * stand-ins; `@silkweave/nestjs` later fills them from the validated tool input
22
+ * per the reflected param sources (path -> `params`, `@Query` -> `query`,
23
+ * `@Body` -> `body`) so guards reading any of them decide as they would over
24
+ * REST. Returns `undefined` when no HTTP request info is available.
25
+ */
26
+ declare function requestFromExtra(requestInfo: {
27
+ headers?: unknown;
28
+ url?: {
29
+ toString(): string;
30
+ };
31
+ } | undefined): {
32
+ headers: {};
33
+ url: string | undefined;
34
+ params: {};
35
+ query: {};
36
+ body: {};
37
+ } | undefined;
38
+ /**
39
+ * Register every action as an MCP tool on `server`. Shared by all MCP transports
40
+ * (`stdio`, `http`, `edge`). Each tool call forks `context` with a per-call
41
+ * `logger`, the SDK `extra`, a synthesized `request` (see `requestFromExtra`),
42
+ * and - when bearer auth ran for this request - the resolved `auth`. The result
43
+ * is formatted by the action's `toolResult` hook if present, otherwise per the
44
+ * resolved disposition (client `_meta.disposition` > `action.disposition` >
45
+ * `smart`).
46
+ */
47
+ declare function registerTools(server: McpServer, actions: Action[], context: SilkweaveContext, options?: RegisterToolsOptions): void;
48
+ //#endregion
49
+ //#region src/util/result.d.ts
50
+ declare function smartToolResult(data: string | object | object[]): CallToolResult;
51
+ declare function jsonToolResult(data: object, isError?: boolean): CallToolResult;
52
+ declare function errorToolResult({
53
+ code,
54
+ name,
55
+ message
56
+ }: SilkweaveError): CallToolResult;
57
+ declare function handleToolError(error: unknown): CallToolResult;
58
+ declare function parseResourceMessage({
59
+ resource
60
+ }: EmbeddedResource): string;
61
+ //#endregion
62
+ export { smartToolResult as a, requestFromExtra as c, parseResourceMessage as i, handleToolError as n, RegisterToolsOptions as o, jsonToolResult as r, registerTools as s, errorToolResult as t };
63
+ //# sourceMappingURL=result-_Bg2cDAG.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result-_Bg2cDAG.d.mts","names":[],"sources":["../src/handlers/registerTools.ts","../src/util/result.ts"],"mappings":";;;;;KAQK,SAAA,GAAY,WAAA,CAAY,UAAA,QAAkB,YAAA;AAAA,UAG9B,oBAAA;;AARyG;;;;EAcxH,SAAA,GAAY,SAAA;AAAA;;;;;;;;AANd;;;;iBAoBgB,gBAAA,CAAiB,WAAA;EAAe,OAAA;EAAmB,GAAA;IAAQ,QAAA;EAAA;AAAA;;;;;;;;;;;AAwD3E;;;;;iBAAgB,aAAA,CACd,MAAA,EAAQ,SAAA,EACR,OAAA,EAAS,MAAA,IACT,OAAA,EAAS,gBAAA,EACT,OAAA,GAAS,oBAAA;;;iBCvFK,eAAA,CAAgB,IAAA,+BAAmC,cAAA;AAAA,iBAqBnD,cAAA,CAAe,IAAA,UAAc,OAAA,aAAkB,cAAA;AAAA,iBAM/C,eAAA,CAAA;EAAkB,IAAA;EAAM,IAAA;EAAM;AAAA,GAAW,cAAA,GAAiB,cAAA;AAAA,iBAU1D,eAAA,CAAgB,KAAA,YAAiB,cAAA;AAAA,iBAejC,oBAAA,CAAA;EAAuB;AAAA,GAAY,gBAAA"}
@@ -0,0 +1,2 @@
1
+ import { a as smartToolResult, c as requestFromExtra, i as parseResourceMessage, n as handleToolError, o as RegisterToolsOptions, r as jsonToolResult, s as registerTools, t as errorToolResult } from "./result-_Bg2cDAG.mjs";
2
+ export { RegisterToolsOptions, errorToolResult, handleToolError, jsonToolResult, parseResourceMessage, registerTools, requestFromExtra, smartToolResult };
@@ -0,0 +1,3 @@
1
+ import { n as requestFromExtra, t as registerTools } from "./registerTools-Z6JnoM60.mjs";
2
+ import { a as smartToolResult, i as parseResourceMessage, n as handleToolError, r as jsonToolResult, t as errorToolResult } from "./result-B_9Eo1ey.mjs";
3
+ export { errorToolResult, handleToolError, jsonToolResult, parseResourceMessage, registerTools, requestFromExtra, smartToolResult };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silkweave/mcp",
3
- "version": "2.6.1",
3
+ "version": "3.1.0",
4
4
  "description": "Silkweave MCP Package",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.silkweave.dev",
@@ -24,6 +24,11 @@
24
24
  "types": "./build/index.d.mts",
25
25
  "default": "./build/index.mjs"
26
26
  },
27
+ "./tools": {
28
+ "@silkweave/source": "./src/tools.ts",
29
+ "types": "./build/tools.d.mts",
30
+ "default": "./build/tools.mjs"
31
+ },
27
32
  "./cli-proxy": {
28
33
  "@silkweave/source": "./src/cliProxy.ts",
29
34
  "types": "./build/cliProxy.d.mts",
@@ -33,40 +38,43 @@
33
38
  "dependencies": {
34
39
  "@modelcontextprotocol/sdk": "^1.29.0",
35
40
  "change-case": "^5.4.4",
36
- "cors": "^2.8.6",
37
- "express": "^5.2.1",
38
41
  "zod": "^3.25.0",
39
- "@silkweave/auth": "2.6.1",
40
- "@silkweave/core": "2.6.1",
41
- "@silkweave/logger": "2.6.1"
42
+ "@silkweave/auth": "3.1.0",
43
+ "@silkweave/core": "3.1.0"
42
44
  },
43
45
  "peerDependencies": {
44
- "@clack/prompts": "^1.0.1",
45
- "commander": "^13.1.0"
46
+ "commander": "^13.1.0",
47
+ "cors": "^2.8.5",
48
+ "express": "^5.0.0"
46
49
  },
47
50
  "peerDependenciesMeta": {
48
- "@clack/prompts": {
51
+ "commander": {
49
52
  "optional": true
50
53
  },
51
- "commander": {
54
+ "cors": {
55
+ "optional": true
56
+ },
57
+ "express": {
52
58
  "optional": true
53
59
  }
54
60
  },
55
61
  "devDependencies": {
56
- "@clack/prompts": "^1.0.1",
57
62
  "@eslint/js": "^10.0.1",
58
63
  "@modelcontextprotocol/inspector": "^0.21.1",
59
- "commander": "^13.1.0",
60
64
  "@stylistic/eslint-plugin": "^5.10.0",
61
65
  "@types/cors": "^2.8.19",
62
66
  "@types/express": "^5.0.6",
63
67
  "@types/node": "^22.0.0",
68
+ "commander": "^13.1.0",
69
+ "cors": "^2.8.6",
64
70
  "eslint": "^10.4.0",
71
+ "express": "^5.2.1",
65
72
  "rimraf": "^6.1.3",
66
73
  "tsdown": "^0.21.8",
67
74
  "tsx": "^4.21.0",
68
75
  "typescript": "^5.9.3",
69
- "typescript-eslint": "^8.56.1"
76
+ "typescript-eslint": "^8.56.1",
77
+ "vitest": "^4.1.9"
70
78
  },
71
79
  "scripts": {
72
80
  "clean": "rimraf build",
@@ -74,6 +82,7 @@
74
82
  "watch": "tsdown --watch",
75
83
  "typecheck": "tsc --noEmit",
76
84
  "lint": "eslint",
77
- "check": "pnpm lint && pnpm typecheck"
85
+ "check": "pnpm lint && pnpm typecheck",
86
+ "test": "vitest run"
78
87
  }
79
88
  }