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