@silkweave/mcp 3.2.1 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/cliProxy.mjs +3 -3
- package/build/cliProxy.mjs.map +1 -1
- package/build/{registerTools-B_vjxOrs.mjs → filter-Baxr12pu.mjs} +52 -87
- package/build/filter-Baxr12pu.mjs.map +1 -0
- package/build/filter-BuYC4eO9.d.mts +60 -0
- package/build/filter-BuYC4eO9.d.mts.map +1 -0
- package/build/index.d.mts +4 -157
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +5 -299
- package/build/index.mjs.map +1 -1
- package/build/{result-I04HIJR5.d.mts → result-CQT2v6P1.d.mts} +3 -59
- package/build/result-CQT2v6P1.d.mts.map +1 -0
- package/build/{result-CXgeE8ca.mjs → result-uIqPNNxF.mjs} +20 -7
- package/build/result-uIqPNNxF.mjs.map +1 -0
- package/build/server.d.mts +163 -0
- package/build/server.d.mts.map +1 -0
- package/build/server.mjs +339 -0
- package/build/server.mjs.map +1 -0
- package/build/tools.d.mts +2 -1
- package/build/tools.mjs +2 -2
- package/package.json +8 -3
- package/build/registerTools-B_vjxOrs.mjs.map +0 -1
- package/build/result-CXgeE8ca.mjs.map +0 -1
- package/build/result-I04HIJR5.d.mts.map +0 -1
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'] }))
|
package/build/cliProxy.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as parseResourceMessage } from "./result-
|
|
1
|
+
import { i as parseResourceMessage } from "./result-uIqPNNxF.mjs";
|
|
2
2
|
import { createConsoleLogger } from "@silkweave/core";
|
|
3
|
-
import { kebabCase } from "change-case";
|
|
3
|
+
import { camelCase, kebabCase } from "change-case";
|
|
4
4
|
import { randomUUID } from "crypto";
|
|
5
5
|
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
6
6
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -83,7 +83,7 @@ const cliProxy = ({ url, formatter = defaultFormatter }) => {
|
|
|
83
83
|
}
|
|
84
84
|
const input = {};
|
|
85
85
|
for (const key of Object.keys(properties)) {
|
|
86
|
-
const value = args[key];
|
|
86
|
+
const value = args[camelCase(key)];
|
|
87
87
|
if (value !== void 0) input[key] = coerce(value, properties[key]?.type);
|
|
88
88
|
}
|
|
89
89
|
(await client.callTool({
|
package/build/cliProxy.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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 { camelCase, 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 // Options register as kebab-case flags (--action-id) and Commander stores them\n // camelized (actionId) — read back via camelCase(key) so snake_case schema keys\n // (action_id) map too, not just single-word ones.\n const value = args[camelCase(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,87 +1,6 @@
|
|
|
1
|
-
import { a as smartToolResult, n as handleToolError, o as structuredToolResult, r as jsonToolResult, t as errorToolResult } from "./result-
|
|
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
|
-
|
|
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=
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 {
|
|
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
|
package/build/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/
|
|
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"}
|