@silkweave/mcp 3.2.1 → 4.0.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
@@ -43,7 +43,7 @@ Configure in Claude Desktop or Claude Code:
43
43
 
44
44
  ```typescript
45
45
  import { silkweave } from '@silkweave/core'
46
- import { http } from '@silkweave/mcp'
46
+ import { http } from '@silkweave/mcp/server'
47
47
 
48
48
  await silkweave({ name: 'my-tools', description: 'My Tools', version: '1.0.0' })
49
49
  .adapter(http({ host: 'localhost', port: 8080, allowedHosts: ['localhost'] }))
@@ -1,4 +1,4 @@
1
- import { i as parseResourceMessage } from "./result-CXgeE8ca.mjs";
1
+ import { i as parseResourceMessage } from "./result-uIqPNNxF.mjs";
2
2
  import { createConsoleLogger } from "@silkweave/core";
3
3
  import { kebabCase } from "change-case";
4
4
  import { randomUUID } from "crypto";
@@ -1,87 +1,6 @@
1
- import { a as smartToolResult, n as handleToolError, o as structuredToolResult, r as jsonToolResult, t as errorToolResult } from "./result-CXgeE8ca.mjs";
1
+ import { a as smartToolResult, n as handleToolError, o as structuredToolResult, r as jsonToolResult, t as errorToolResult } from "./result-uIqPNNxF.mjs";
2
2
  import { SilkweaveError, createLogger, emitToolCall, isStreamingAction, runStreamingAction } from "@silkweave/core";
3
- import { validateToken } from "@silkweave/auth";
4
- import { AsyncLocalStorage } from "node:async_hooks";
5
3
  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/filter.ts
38
- /**
39
- * Extract the JSON-RPC `method` (and `toolName` for `tools/call`) from a
40
- * parsed request body. Tolerates a legacy batch (first request wins) and
41
- * unrecognizable bodies (empty `method`).
42
- */
43
- function rpcInfo(body) {
44
- const message = Array.isArray(body) ? body[0] : body;
45
- const method = typeof message?.method === "string" ? message.method : "";
46
- const toolName = method === "tools/call" && typeof message?.params?.name === "string" ? message.params.name : void 0;
47
- return {
48
- method,
49
- ...toolName !== void 0 ? { toolName } : {}
50
- };
51
- }
52
- /**
53
- * Map a `filterActions` throw to an HTTP status + JSON-RPC error body: a
54
- * `SilkweaveError` keeps its `statusCode` (so an invalid key surfaces as an
55
- * auth failure, not "server has no tools"), anything else is a 500. `id` is
56
- * echoed from the request body when available.
57
- */
58
- function filterErrorResponse(error, body) {
59
- const id = (Array.isArray(body) ? body[0] : body)?.id ?? null;
60
- if (error instanceof SilkweaveError) return {
61
- status: error.statusCode,
62
- body: {
63
- jsonrpc: "2.0",
64
- error: {
65
- code: -32e3,
66
- message: error.message
67
- },
68
- id
69
- }
70
- };
71
- console.error("filterActions error:", error);
72
- return {
73
- status: 500,
74
- body: {
75
- jsonrpc: "2.0",
76
- error: {
77
- code: -32603,
78
- message: "Internal server error"
79
- },
80
- id
81
- }
82
- };
83
- }
84
- //#endregion
85
4
  //#region src/handlers/registerTools.ts
86
5
  /**
87
6
  * Build the generic `request` context value (the same key REST/tRPC populate)
@@ -215,12 +134,10 @@ function registerTools(server, actions, context, options = {}) {
215
134
  ...action.disposition === "structured" && action.output ? { outputSchema: action.output } : {}
216
135
  }, async (input, extra) => {
217
136
  const logger = createToolLogger(extra, stream);
218
- const currentAuth = authStorage.getStore();
219
137
  const actionContext = context.fork({
220
138
  logger,
221
139
  extra,
222
- request: requestFromExtra(extra.requestInfo),
223
- ...currentAuth ? { auth: currentAuth } : {}
140
+ request: requestFromExtra(extra.requestInfo)
224
141
  });
225
142
  const disposition = extra._meta?.disposition ?? action.disposition;
226
143
  const base = {
@@ -252,6 +169,54 @@ function registerTools(server, actions, context, options = {}) {
252
169
  });
253
170
  }
254
171
  //#endregion
255
- export { authMiddleware as a, rpcInfo as i, requestFromExtra as n, authStorage as o, filterErrorResponse as r, registerTools as t };
172
+ //#region src/handlers/filter.ts
173
+ /**
174
+ * Extract the JSON-RPC `method` (and `toolName` for `tools/call`) from a
175
+ * parsed request body. Tolerates a legacy batch (first request wins) and
176
+ * unrecognizable bodies (empty `method`).
177
+ */
178
+ function rpcInfo(body) {
179
+ const message = Array.isArray(body) ? body[0] : body;
180
+ const method = typeof message?.method === "string" ? message.method : "";
181
+ const toolName = method === "tools/call" && typeof message?.params?.name === "string" ? message.params.name : void 0;
182
+ return {
183
+ method,
184
+ ...toolName !== void 0 ? { toolName } : {}
185
+ };
186
+ }
187
+ /**
188
+ * Map a `filterActions` throw to an HTTP status + JSON-RPC error body: a
189
+ * `SilkweaveError` keeps its `statusCode` (so an invalid key surfaces as an
190
+ * auth failure, not "server has no tools"), anything else is a 500. `id` is
191
+ * echoed from the request body when available.
192
+ */
193
+ function filterErrorResponse(error, body) {
194
+ const id = (Array.isArray(body) ? body[0] : body)?.id ?? null;
195
+ if (error instanceof SilkweaveError) return {
196
+ status: error.statusCode,
197
+ body: {
198
+ jsonrpc: "2.0",
199
+ error: {
200
+ code: -32e3,
201
+ message: error.message
202
+ },
203
+ id
204
+ }
205
+ };
206
+ console.error("filterActions error:", error);
207
+ return {
208
+ status: 500,
209
+ body: {
210
+ jsonrpc: "2.0",
211
+ error: {
212
+ code: -32603,
213
+ message: "Internal server error"
214
+ },
215
+ id
216
+ }
217
+ };
218
+ }
219
+ //#endregion
220
+ export { requestFromExtra as i, rpcInfo as n, registerTools as r, filterErrorResponse as t };
256
221
 
257
- //# sourceMappingURL=registerTools-B_vjxOrs.mjs.map
222
+ //# sourceMappingURL=filter-Baxr12pu.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-Baxr12pu.mjs","names":[],"sources":["../src/handlers/registerTools.ts","../src/handlers/filter.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js'\nimport { Action, ActionRun, createLogger, emitToolCall, isStreamingAction, OnToolCall, runStreamingAction, SilkweaveContext, SilkweaveError, ToolCallEvent } from '@silkweave/core'\nimport { capitalCase, pascalCase } from 'change-case'\nimport { errorToolResult, handleToolError, jsonToolResult, smartToolResult, structuredToolResult } from '../util/result.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 * Telemetry hook invoked once per tool call (fire-and-forget; errors are\n * logged, never propagated). Fires after result formatting, so events carry\n * `resultBytes` (serialized raw-result size) and `sideloaded` (whether\n * `smartToolResult` offloaded to an embedded resource).\n */\n onToolCall?: OnToolCall\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/**\n * Result path for a `disposition: 'structured'` action: parse the raw result\n * through the output schema and ship the PARSED data as `structuredContent`.\n * Parsing strips extra fields (so the client-side JSON-Schema validator, which\n * rejects `additionalProperties`, passes by construction) and a genuine\n * mismatch (missing required field, wrong type) degrades to an `isError` tool\n * result - which the SDK exempts from output validation - instead of an opaque\n * protocol error.\n */\nfunction structuredResult(action: Action, result: object | object[]) {\n const parsed = action.output!.safeParse(result)\n if (!parsed.success) {\n const fields = [...new Set(parsed.error.issues.map((issue) => issue.path.join('.') || '(root)'))].join(', ')\n return errorToolResult(new SilkweaveError(\n `Output validation failed for '${action.name}' at: ${fields}. The tool returned a shape that does not match its declared output schema - this is a server-side bug, not an input problem; retrying with different arguments will not help.`,\n 'output_validation_error'\n ))\n }\n // The SDK independently re-parses `structuredContent` against the same schema.\n // A non-idempotent output schema (a field-level `.transform()`) yields data\n // that fails that second parse, which the SDK raises as an opaque protocol\n // error. Detect it here and degrade to a clear isError result (SDK-exempt)\n // instead - structured contracts must be idempotent (no transforms).\n if (!action.output!.safeParse(parsed.data).success) {\n return errorToolResult(new SilkweaveError(\n `Output schema for '${action.name}' is not idempotent (a field-level .transform()?) and cannot back a 'structured' contract - use disposition 'json' or remove the transform.`,\n 'output_schema_not_structurable'\n ))\n }\n return structuredToolResult(parsed.data as object)\n}\n\n/** MCP-only telemetry fields, computed only when a hook is registered. */\nfunction resultMeta(result: object | object[], formatted: { content?: { type: string }[] }): Pick<ToolCallEvent, 'resultBytes' | 'sideloaded'> {\n return {\n // Actual UTF-8 byte count (String.length counts UTF-16 code units, which\n // understates multibyte payloads). TextEncoder keeps this edge-safe.\n resultBytes: new TextEncoder().encode(JSON.stringify(result)).length,\n sideloaded: (formatted.content ?? []).some((block) => block.type === 'resource')\n }\n}\n\n/** Error identity for telemetry: a SilkweaveError's `code`, else the error's name. */\nfunction errorMeta(error: unknown): Pick<ToolCallEvent, 'errorCode' | 'errorMessage'> {\n if (error instanceof SilkweaveError) { return { errorCode: error.code, errorMessage: error.message } }\n if (error instanceof Error) { return { errorCode: error.name, errorMessage: error.message } }\n return { errorCode: 'unknown' }\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 // core's dependency-free ToolResult is structurally the SDK CallToolResult;\n // narrow it back at the SDK boundary.\n const response = action.toolResult(result, context) as CallToolResult | undefined\n if (response) { return response }\n }\n // A structured action's output schema is a contract fixed at tools/list\n // time - a client's `_meta.disposition` cannot demote it.\n if (action.disposition === 'structured') { return structuredResult(action, result) }\n return disposition === 'smart' ? smartToolResult(result) : jsonToolResult(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 * `json`); a `'structured'` action always ships schema-parsed\n * `structuredContent` (its contract cannot be demoted per-call).\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 // Derived base (query ⇒ read-only), explicit annotations merged over.\n annotations: { readOnlyHint: action.kind === 'query', ...action.annotations },\n // Only structured actions declare an outputSchema contract - the SDK\n // enforces it server-side and SDK clients enforce it independently, so\n // forwarding is strictly opt-in via disposition.\n ...(action.disposition === 'structured' && action.output ? { outputSchema: action.output } : {})\n }, async (input, extra) => {\n const logger = createToolLogger(extra, stream)\n // `auth` (when present) is carried on `context` by the transport's\n // per-request fork; this fork inherits it - no AsyncLocalStorage needed.\n const actionContext = context.fork({\n logger,\n extra,\n request: requestFromExtra(extra.requestInfo)\n })\n // Client-sent `_meta.disposition` wins; otherwise fall back to the\n // action's configured default (`json` when neither is set).\n const disposition = extra._meta?.disposition ?? action.disposition\n const base = { action: action.name, tool: pascalCase(action.name), transport: 'mcp' as const, context: actionContext }\n const started = Date.now()\n try {\n const result = await runAction(action, input, actionContext, extra)\n const formatted = formatToolResult(action, result, actionContext, disposition)\n emitToolCall(options.onToolCall, {\n ...base,\n durationMs: Date.now() - started,\n ok: formatted.isError !== true,\n ...(options.onToolCall ? resultMeta(result, formatted) : {})\n })\n return formatted\n } catch (error) {\n emitToolCall(options.onToolCall, { ...base, durationMs: Date.now() - started, ok: false, ...errorMeta(error) })\n return handleToolError(error)\n }\n })\n }\n}\n","import { Action, SilkweaveError } from '@silkweave/core'\n\n/**\n * Normalized request stand-in handed to `filterActions` - the same shape for\n * the Express `http()` transport and the Web-Standard `edge()` adapter.\n */\nexport interface FilterRequest {\n /** Inbound HTTP headers (lower-cased keys, as delivered by the host). */\n headers: Record<string, string | string[] | undefined>\n /** Full request URL (or path, as delivered by the host). */\n url: string\n /**\n * JSON-RPC method of the POSTed message (`'initialize'`, `'tools/list'`,\n * `'tools/call'`, `'ping'`, ...). Lets the callback skip expensive\n * permission lookups on `initialize`/`ping`, and doubles as an\n * observability tap (e.g. counting `tools/list`). Empty string when the body\n * is not a recognizable JSON-RPC message. JSON-RPC batches are rejected by the\n * transport before the filter runs, so this always reflects a single message.\n */\n method: string\n /** `params.name` of a `tools/call` message; unset for every other method. */\n toolName?: string\n}\n\n/**\n * Per-request tool filter for the stateless MCP transports. Runs before\n * `registerTools()` on every `POST /mcp`; only the returned actions exist for\n * that request (`tools/list` and `tools/call` alike - a client that cached a\n * wider list is still denied). May be async (e.g. a DB lookup of API-key\n * permissions).\n *\n * Error semantics: a thrown `SilkweaveError` propagates as its `statusCode`\n * (e.g. 401 invalid key, 403 insufficient permissions) with a JSON-RPC error\n * body; any other throw maps to HTTP 500. A thrown error NEVER degrades to an\n * empty tool list - return `[]` explicitly if \"no tools\" is the intended\n * answer.\n */\nexport type FilterActions = (actions: Action[], request: FilterRequest) => Action[] | Promise<Action[]>\n\n/**\n * Extract the JSON-RPC `method` (and `toolName` for `tools/call`) from a\n * parsed request body. Tolerates a legacy batch (first request wins) and\n * unrecognizable bodies (empty `method`).\n */\nexport function rpcInfo(body: unknown): { method: string; toolName?: string } {\n const message = (Array.isArray(body) ? body[0] : body) as { method?: unknown; params?: { name?: unknown } } | undefined\n const method = typeof message?.method === 'string' ? message.method : ''\n const toolName = method === 'tools/call' && typeof message?.params?.name === 'string' ? message.params.name : undefined\n return { method, ...(toolName !== undefined ? { toolName } : {}) }\n}\n\n/**\n * Map a `filterActions` throw to an HTTP status + JSON-RPC error body: a\n * `SilkweaveError` keeps its `statusCode` (so an invalid key surfaces as an\n * auth failure, not \"server has no tools\"), anything else is a 500. `id` is\n * echoed from the request body when available.\n */\nexport function filterErrorResponse(error: unknown, body: unknown): { status: number; body: object } {\n const message = (Array.isArray(body) ? body[0] : body) as { id?: unknown } | undefined\n const id = message?.id ?? null\n if (error instanceof SilkweaveError) {\n return {\n status: error.statusCode,\n body: { jsonrpc: '2.0', error: { code: -32_000, message: error.message }, id }\n }\n }\n console.error('filterActions error:', error)\n return {\n status: 500,\n body: { jsonrpc: '2.0', error: { code: -32_603, message: 'Internal server error' }, id }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { Action } from "@silkweave/core";
2
+
3
+ //#region src/handlers/filter.d.ts
4
+ /**
5
+ * Normalized request stand-in handed to `filterActions` - the same shape for
6
+ * the Express `http()` transport and the Web-Standard `edge()` adapter.
7
+ */
8
+ interface FilterRequest {
9
+ /** Inbound HTTP headers (lower-cased keys, as delivered by the host). */
10
+ headers: Record<string, string | string[] | undefined>;
11
+ /** Full request URL (or path, as delivered by the host). */
12
+ url: string;
13
+ /**
14
+ * JSON-RPC method of the POSTed message (`'initialize'`, `'tools/list'`,
15
+ * `'tools/call'`, `'ping'`, ...). Lets the callback skip expensive
16
+ * permission lookups on `initialize`/`ping`, and doubles as an
17
+ * observability tap (e.g. counting `tools/list`). Empty string when the body
18
+ * is not a recognizable JSON-RPC message. JSON-RPC batches are rejected by the
19
+ * transport before the filter runs, so this always reflects a single message.
20
+ */
21
+ method: string;
22
+ /** `params.name` of a `tools/call` message; unset for every other method. */
23
+ toolName?: string;
24
+ }
25
+ /**
26
+ * Per-request tool filter for the stateless MCP transports. Runs before
27
+ * `registerTools()` on every `POST /mcp`; only the returned actions exist for
28
+ * that request (`tools/list` and `tools/call` alike - a client that cached a
29
+ * wider list is still denied). May be async (e.g. a DB lookup of API-key
30
+ * permissions).
31
+ *
32
+ * Error semantics: a thrown `SilkweaveError` propagates as its `statusCode`
33
+ * (e.g. 401 invalid key, 403 insufficient permissions) with a JSON-RPC error
34
+ * body; any other throw maps to HTTP 500. A thrown error NEVER degrades to an
35
+ * empty tool list - return `[]` explicitly if "no tools" is the intended
36
+ * answer.
37
+ */
38
+ type FilterActions = (actions: Action[], request: FilterRequest) => Action[] | Promise<Action[]>;
39
+ /**
40
+ * Extract the JSON-RPC `method` (and `toolName` for `tools/call`) from a
41
+ * parsed request body. Tolerates a legacy batch (first request wins) and
42
+ * unrecognizable bodies (empty `method`).
43
+ */
44
+ declare function rpcInfo(body: unknown): {
45
+ method: string;
46
+ toolName?: string;
47
+ };
48
+ /**
49
+ * Map a `filterActions` throw to an HTTP status + JSON-RPC error body: a
50
+ * `SilkweaveError` keeps its `statusCode` (so an invalid key surfaces as an
51
+ * auth failure, not "server has no tools"), anything else is a 500. `id` is
52
+ * echoed from the request body when available.
53
+ */
54
+ declare function filterErrorResponse(error: unknown, body: unknown): {
55
+ status: number;
56
+ body: object;
57
+ };
58
+ //#endregion
59
+ export { rpcInfo as i, FilterRequest as n, filterErrorResponse as r, FilterActions as t };
60
+ //# sourceMappingURL=filter-BuYC4eO9.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-BuYC4eO9.d.mts","names":[],"sources":["../src/handlers/filter.ts"],"mappings":";;;;;AAMA;;UAAiB,aAAA;EAEA;EAAf,OAAA,EAAS,MAAA;EAAA;EAET,GAAA;EASA;;;;AAkBF;;;;EAlBE,MAAA;EAkByE;EAhBzE,QAAA;AAAA;;;;;;;;;;;;AAuBF;;KAPY,aAAA,IAAiB,OAAA,EAAS,MAAA,IAAU,OAAA,EAAS,aAAA,KAAkB,MAAA,KAAW,OAAA,CAAQ,MAAA;;;;;;iBAO9E,OAAA,CAAQ,IAAA;EAAkB,MAAA;EAAgB,QAAA;AAAA;;;;;;;iBAa1C,mBAAA,CAAoB,KAAA,WAAgB,IAAA;EAAkB,MAAA;EAAgB,IAAA;AAAA"}
package/build/index.d.mts CHANGED
@@ -1,56 +1,7 @@
1
- import { a as smartToolResult, c as registerTools, d as FilterRequest, f as filterErrorResponse, i as parseResourceMessage, l as requestFromExtra, n as handleToolError, o as structuredToolResult, p as rpcInfo, r as jsonToolResult, s as RegisterToolsOptions, t as errorToolResult, u as FilterActions } from "./result-I04HIJR5.mjs";
2
- import { CreateMcpExpressAppOptions } from "@modelcontextprotocol/sdk/server/express.js";
3
- import { Action, AdapterFactory, OnToolCall, SilkweaveContext, SilkweaveOptions } from "@silkweave/core";
4
- import { Express, RequestHandler } from "express";
5
- import { AuthConfig, AuthInfo } from "@silkweave/auth";
6
- import { AsyncLocalStorage } from "node:async_hooks";
7
- import { CorsOptions, CorsOptions as CorsOptions$1 } from "cors";
8
- import { Server } from "http";
1
+ import { i as rpcInfo, n as FilterRequest, r as filterErrorResponse, t as FilterActions } from "./filter-BuYC4eO9.mjs";
2
+ import { a as smartToolResult, c as registerTools, i as parseResourceMessage, l as requestFromExtra, n as handleToolError, o as structuredToolResult, r as jsonToolResult, s as RegisterToolsOptions, t as errorToolResult } from "./result-CQT2v6P1.mjs";
3
+ import { AdapterFactory, OnToolCall } from "@silkweave/core";
9
4
 
10
- //#region src/adapter/http.d.ts
11
- interface StartMcpHttpOptions extends CreateMcpExpressAppOptions {
12
- host: string;
13
- port: number;
14
- auth?: AuthConfig;
15
- /** CORS configuration. `false` to disable, omitted/`true` for permissive defaults, or a `CorsOptions` object. */
16
- cors?: CorsOptions$1 | boolean;
17
- /** Mount the `/resource/:id` sideload route. Default `true`. */
18
- sideloadResources?: boolean;
19
- /** Directory the sideload route reads from. Default `'resources'`. */
20
- resourceDir?: string;
21
- /**
22
- * Per-request tool filter, applied before `registerTools()` on every
23
- * `POST /mcp` (the stateless transport recomputes the tool list per request,
24
- * so permission changes apply on the next `tools/list`). See `FilterActions`
25
- * for the request stand-in (`headers`/`url`/`method`/`toolName`) and error
26
- * semantics (a throw surfaces as its `SilkweaveError.statusCode` or 500 -
27
- * never an empty tool list).
28
- */
29
- filterActions?: FilterActions;
30
- /** Telemetry hook invoked once per tool call (fire-and-forget). */
31
- onToolCall?: OnToolCall;
32
- }
33
- /**
34
- * Build a fully-wired Express app that exposes the MCP Streamable HTTP
35
- * transport (plus OAuth / sideload / well-known routes when configured).
36
- *
37
- * Pass the resulting `app` to `app.listen(port, host)` yourself, or use the
38
- * top-level `startMcpServer()` / `http()` adapter conveniences.
39
- */
40
- declare function buildMcpExpressApp(silkweaveOptions: SilkweaveOptions, context: SilkweaveContext, actions: Action[], options: StartMcpHttpOptions): Express;
41
- /**
42
- * Spin up a standalone MCP Streamable HTTP server on `host:port` for the
43
- * given `actions`. Returns the underlying `Server` so callers can close it.
44
- *
45
- * Convenience for use cases that don't go through the `silkweave()` builder.
46
- */
47
- declare function startMcpServer(silkweaveOptions: SilkweaveOptions, actions: Action[], options: StartMcpHttpOptions, context?: SilkweaveContext): Promise<Server>;
48
- /**
49
- * Silkweave adapter that owns its own HTTP server. Composes the MCP handler
50
- * primitives into a fully-wired Express app and listens on `host:port`.
51
- */
52
- declare const http: AdapterFactory<StartMcpHttpOptions>;
53
- //#endregion
54
5
  //#region src/adapter/stdio.d.ts
55
6
  interface StdioAdapterOptions {
56
7
  /** Telemetry hook invoked once per tool call (fire-and-forget). */
@@ -58,110 +9,6 @@ interface StdioAdapterOptions {
58
9
  }
59
10
  declare const stdio: AdapterFactory<StdioAdapterOptions | void>;
60
11
  //#endregion
61
- //#region src/handlers/auth.d.ts
62
- /**
63
- * Per-request bearer-token storage used by tool handlers to read the resolved
64
- * `AuthInfo` for the currently-handled MCP call.
65
- */
66
- declare const authStorage: AsyncLocalStorage<AuthInfo>;
67
- /**
68
- * Express middleware that validates the `Authorization: Bearer …` header via
69
- * the supplied `AuthConfig`. On success the resolved `AuthInfo` is placed in
70
- * `authStorage` for the duration of the downstream handler - `mcpTransport`'s
71
- * tool callbacks pick it up to populate the silkweave context's `auth` key.
72
- *
73
- * The middleware should NOT be applied to OAuth-discovery / token routes -
74
- * compose it only on the routes that require an authenticated caller (the MCP
75
- * transport itself, sideload, etc.).
76
- */
77
- declare function authMiddleware(auth: AuthConfig, context: SilkweaveContext): RequestHandler;
78
- //#endregion
79
- //#region src/handlers/cors.d.ts
80
- /** Headers required by the MCP protocol that must always be exposed when CORS is in use. */
81
- declare const MCP_REQUIRED_HEADERS: string[];
82
- /**
83
- * CORS middleware preconfigured to expose the headers MCP clients need
84
- * (`Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`) on top of any
85
- * user-supplied options. (Stateless transport - no `Mcp-Session-Id`.)
86
- *
87
- * Pass `false` to disable, omit / pass `true` for permissive defaults, or pass
88
- * a `CorsOptions` object to override.
89
- */
90
- declare function mcpCors(corsConfig?: CorsOptions$1 | boolean): RequestHandler | null;
91
- //#endregion
92
- //#region src/handlers/metadata.d.ts
93
- /**
94
- * Handler for `GET /.well-known/oauth-protected-resource` (RFC 9728). Returns
95
- * the resource server's metadata pointing at the configured authorization
96
- * servers. Requires `auth.resourceUrl` and a non-empty `auth.authorizationServers`.
97
- */
98
- declare function protectedResourceMetadata(auth: AuthConfig): RequestHandler;
99
- //#endregion
100
- //#region src/handlers/oauth.d.ts
101
- interface OAuthRouteHandlers {
102
- /** `GET /.well-known/oauth-authorization-server` - RFC 8414 discovery. */
103
- wellKnownAuthServer: RequestHandler;
104
- /** `GET /authorize` - start the OAuth flow. */
105
- authorize: RequestHandler;
106
- /** `GET {callbackPath}` - provider callback. */
107
- callback: RequestHandler;
108
- /** Path the provider should redirect to (defaults to `/auth/callback`). */
109
- callbackPath: string;
110
- /** `POST /token` - exchange code / refresh token. Includes urlencoded body parser. */
111
- token: RequestHandler[];
112
- /** `POST /register` - dynamic client registration. Includes JSON body parser. */
113
- register: RequestHandler[];
114
- }
115
- /**
116
- * Build the OAuth 2.1 proxy route handlers (authorize, callback, token,
117
- * register, well-known) backed by the configured `auth.provider`. Returns the
118
- * handlers as individual `RequestHandler`s so callers can register them
119
- * wherever they like - under `/mcp`, at the server root, etc.
120
- *
121
- * `token` and `register` are returned as middleware arrays because the
122
- * appropriate body parser must run before the handler. Apply with
123
- * `app.post(path, ...token)`.
124
- */
125
- declare function oauthRoutes(auth: AuthConfig): OAuthRouteHandlers;
126
- //#endregion
127
- //#region src/handlers/sideload.d.ts
128
- interface SideloadResourceOptions {
129
- /** Directory to read sideload resources from. Defaults to `resources/` (cwd-relative). */
130
- resourceDir?: string;
131
- }
132
- /**
133
- * Handler for `GET /resource/:id` - serves a large MCP response that was
134
- * sideloaded to disk as a `{id}` payload with a `{id}.json` metadata sidecar.
135
- *
136
- * The route param `id` is provided by the host framework (Express, Nest, etc.).
137
- */
138
- declare function sideloadResource(options?: SideloadResourceOptions): RequestHandler;
139
- //#endregion
140
- //#region src/handlers/transport.d.ts
141
- interface McpTransportHandlers {
142
- /** `POST /mcp` - handle a single stateless MCP request/response (SSE per call). */
143
- post: RequestHandler;
144
- }
145
- interface McpTransportOptions {
146
- /**
147
- * Per-request tool filter, applied before `registerTools()` on every POST.
148
- * See `FilterActions` for the request stand-in and error semantics.
149
- */
150
- filterActions?: FilterActions;
151
- /** Telemetry hook invoked once per tool call (fire-and-forget). */
152
- onToolCall?: OnToolCall;
153
- }
154
- /**
155
- * Build the MCP Streamable HTTP transport route handler.
156
- *
157
- * Stateless (per the 2026 spec direction): each `POST /mcp` mints a fresh
158
- * transport + server with `sessionIdGenerator: undefined`, handles exactly that
159
- * request (streaming progress over SSE when the call carries a `progressToken`),
160
- * and tears down on response close. No `Mcp-Session-Id`, no session map, no
161
- * `GET`/`DELETE` reconnect - any request can hit any instance.
162
- */
163
- declare function mcpTransport(silkweaveOptions: SilkweaveOptions, context: SilkweaveContext, actions: Action[], options?: McpTransportOptions): McpTransportHandlers;
164
- //#endregion
165
12
  //#region src/util/sideload.d.ts
166
13
  interface SideloadResource {
167
14
  id: string;
@@ -174,5 +21,5 @@ declare function createSideloadResource(buffer: Buffer, {
174
21
  contentType
175
22
  }: Pick<SideloadResource, 'name' | 'contentType'>): Promise<SideloadResource>;
176
23
  //#endregion
177
- export { type CorsOptions, FilterActions, FilterRequest, MCP_REQUIRED_HEADERS, McpTransportHandlers, McpTransportOptions, OAuthRouteHandlers, RegisterToolsOptions, SideloadResource, SideloadResourceOptions, StartMcpHttpOptions, StdioAdapterOptions, authMiddleware, authStorage, buildMcpExpressApp, createSideloadResource, errorToolResult, filterErrorResponse, handleToolError, http, jsonToolResult, mcpCors, mcpTransport, oauthRoutes, parseResourceMessage, protectedResourceMetadata, registerTools, requestFromExtra, rpcInfo, sideloadResource, smartToolResult, startMcpServer, stdio, structuredToolResult };
24
+ export { FilterActions, FilterRequest, RegisterToolsOptions, SideloadResource, StdioAdapterOptions, createSideloadResource, errorToolResult, filterErrorResponse, handleToolError, jsonToolResult, parseResourceMessage, registerTools, requestFromExtra, rpcInfo, smartToolResult, stdio, structuredToolResult };
178
25
  //# 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/sideload.ts"],"mappings":";;;;;;;;;;UAciB,mBAAA,SAA4B,0BAAA;EAC3C,IAAA;EACA,IAAA;EACA,IAAA,GAAO,UAAA;EAH4B;EAKnC,IAAA,GAAO,aAAA;EAFA;EAIP,iBAAA;EAWgB;EAThB,WAAA;EAT2C;;;;;;;;EAkB3C,aAAA,GAAgB,aAAA;EAbT;EAeP,UAAA,GAAa,UAAA;AAAA;;;;;;;AAUf;iBAAgB,kBAAA,CACd,gBAAA,EAAkB,gBAAA,EAClB,OAAA,EAAS,gBAAA,EACT,OAAA,EAAS,MAAA,IACT,OAAA,EAAS,mBAAA,GACR,OAAA;;;;;;;iBA8CmB,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;;;UC/GjB,mBAAA;;EAEf,UAAA,GAAa,UAAA;AAAA;AAAA,cAGF,KAAA,EAAO,cAAA,CAAe,mBAAA;;;;;;;cCDtB,WAAA,EAAW,iBAAA,CAAA,QAAA;;AFKxB;;;;;;;;;iBEOgB,cAAA,CAAe,IAAA,EAAM,UAAA,EAAY,OAAA,EAAS,gBAAA,GAAmB,cAAA;;;;cCjBhE,oBAAA;;;;;;;AHUb;;iBGAgB,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;ELfmC;EKiBnC,KAAA,EAAO,cAAA;ELdA;EKgBP,QAAA,EAAU,cAAA;AAAA;;;;;;;;;;;iBAaI,WAAA,CAAY,IAAA,EAAM,UAAA,GAAa,kBAAA;;;UCzC9B,uBAAA;;EAEf,WAAA;AAAA;;;;;;ANOF;iBMEgB,gBAAA,CAAiB,OAAA,GAAS,uBAAA,GAA+B,cAAA;;;UCGxD,oBAAA;;EAEf,IAAA,EAAM,cAAA;AAAA;AAAA,UAGS,mBAAA;;;APVjB;;EOeE,aAAA,GAAgB,aAAA;EPZT;EOcP,UAAA,GAAa,UAAA;AAAA;;;;;;;;;;iBAYC,YAAA,CACd,gBAAA,EAAkB,gBAAA,EAClB,OAAA,EAAS,gBAAA,EACT,OAAA,EAAS,MAAA,IACT,OAAA,GAAS,mBAAA,GACR,oBAAA;;;UC7Cc,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/stdio.ts","../src/util/sideload.ts"],"mappings":";;;;;UAKiB,mBAAA;;EAEf,UAAA,GAAa,UAAA;AAAA;AAAA,cAGF,KAAA,EAAO,cAAA,CAAe,mBAAA;;;UCPlB,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"}