@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/build/index.mjs CHANGED
@@ -1,304 +1,10 @@
1
- import { a as authMiddleware, i as rpcInfo, n as requestFromExtra, o as authStorage, r as filterErrorResponse, t as registerTools } from "./registerTools-B_vjxOrs.mjs";
2
- import { a as smartToolResult, i as parseResourceMessage, n as handleToolError, o as structuredToolResult, r as jsonToolResult, t as errorToolResult } from "./result-CXgeE8ca.mjs";
3
- import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
4
- import { createContext, validateActionDisposition } from "@silkweave/core";
5
- import express from "express";
6
- import { generateProtectedResourceMetadata } from "@silkweave/auth";
7
- import cors from "cors";
8
- import { readFile, writeFile } from "fs/promises";
9
- import { basename, resolve, sep } from "path";
1
+ import { a as smartToolResult, i as parseResourceMessage, n as handleToolError, o as structuredToolResult, r as jsonToolResult, t as errorToolResult } from "./result-uIqPNNxF.mjs";
2
+ import { i as requestFromExtra, n as rpcInfo, r as registerTools, t as filterErrorResponse } from "./filter-Baxr12pu.mjs";
10
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
12
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { validateActionDisposition } from "@silkweave/core";
13
6
  import { randomUUID } from "crypto";
14
- //#region src/handlers/cors.ts
15
- /** Headers required by the MCP protocol that must always be exposed when CORS is in use. */
16
- const MCP_REQUIRED_HEADERS = [
17
- "WWW-Authenticate",
18
- "Last-Event-Id",
19
- "Mcp-Protocol-Version"
20
- ];
21
- /**
22
- * CORS middleware preconfigured to expose the headers MCP clients need
23
- * (`Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`) on top of any
24
- * user-supplied options. (Stateless transport - no `Mcp-Session-Id`.)
25
- *
26
- * Pass `false` to disable, omit / pass `true` for permissive defaults, or pass
27
- * a `CorsOptions` object to override.
28
- */
29
- function mcpCors(corsConfig = true) {
30
- if (corsConfig === false) return null;
31
- const userConfig = corsConfig === true ? {} : corsConfig;
32
- const userExposed = userConfig.exposedHeaders;
33
- const exposedHeaders = [...MCP_REQUIRED_HEADERS, ...Array.isArray(userExposed) ? userExposed : userExposed ? [userExposed] : []];
34
- return cors({
35
- origin: "*",
36
- ...userConfig,
37
- exposedHeaders
38
- });
39
- }
40
- //#endregion
41
- //#region src/handlers/metadata.ts
42
- /**
43
- * Handler for `GET /.well-known/oauth-protected-resource` (RFC 9728). Returns
44
- * the resource server's metadata pointing at the configured authorization
45
- * servers. Requires `auth.resourceUrl` and a non-empty `auth.authorizationServers`.
46
- */
47
- function protectedResourceMetadata(auth) {
48
- if (!auth.resourceUrl || !auth.authorizationServers?.length) throw new Error("@silkweave/mcp protectedResourceMetadata(): auth.resourceUrl and auth.authorizationServers are required");
49
- const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers, auth.requiredScopes);
50
- return (_req, res) => {
51
- res.json(metadata);
52
- };
53
- }
54
- //#endregion
55
- //#region src/handlers/oauth.ts
56
- function toOAuthReq(req) {
57
- return {
58
- method: req.method,
59
- url: new URL(req.url, `${req.protocol}://${req.get("host")}`),
60
- headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),
61
- body: req.body
62
- };
63
- }
64
- function sendOAuth(res, oauthRes) {
65
- for (const [key, value] of Object.entries(oauthRes.headers)) res.header(key, value);
66
- if (oauthRes.body) res.status(oauthRes.status).send(typeof oauthRes.body === "string" ? oauthRes.body : JSON.stringify(oauthRes.body));
67
- else res.status(oauthRes.status).end();
68
- }
69
- /**
70
- * Build the OAuth 2.1 proxy route handlers (authorize, callback, token,
71
- * register, well-known) backed by the configured `auth.provider`. Returns the
72
- * handlers as individual `RequestHandler`s so callers can register them
73
- * wherever they like - under `/mcp`, at the server root, etc.
74
- *
75
- * `token` and `register` are returned as middleware arrays because the
76
- * appropriate body parser must run before the handler. Apply with
77
- * `app.post(path, ...token)`.
78
- */
79
- function oauthRoutes(auth) {
80
- if (!auth.provider) throw new Error("@silkweave/mcp oauthRoutes(): auth.provider is required");
81
- const provider = auth.provider;
82
- return {
83
- callbackPath: auth.callbackPath ?? "/auth/callback",
84
- wellKnownAuthServer: (_req, res) => {
85
- sendOAuth(res, provider.metadata());
86
- },
87
- authorize: async (req, res) => {
88
- sendOAuth(res, await provider.authorize(toOAuthReq(req)));
89
- },
90
- callback: async (req, res) => {
91
- sendOAuth(res, await provider.callback(toOAuthReq(req)));
92
- },
93
- token: [express.urlencoded({ extended: false }), async (req, res) => {
94
- sendOAuth(res, await provider.token(toOAuthReq(req)));
95
- }],
96
- register: [express.json(), async (req, res) => {
97
- sendOAuth(res, await provider.register(toOAuthReq(req)));
98
- }]
99
- };
100
- }
101
- //#endregion
102
- //#region src/handlers/sideload.ts
103
- /**
104
- * Handler for `GET /resource/:id` - serves a large MCP response that was
105
- * sideloaded to disk as a `{id}` payload with a `{id}.json` metadata sidecar.
106
- *
107
- * The route param `id` is provided by the host framework (Express, Nest, etc.).
108
- */
109
- function sideloadResource(options = {}) {
110
- const { resourceDir = "resources" } = options;
111
- const baseDir = resolve(resourceDir);
112
- return async (req, res) => {
113
- const id = req.params["id"];
114
- if (!id || typeof id !== "string") throw new Error("Invalid ID");
115
- const safeId = basename(id);
116
- const target = resolve(baseDir, safeId);
117
- if (safeId !== id || target !== baseDir && !target.startsWith(baseDir + sep)) {
118
- res.status(400).json({ error: "invalid_resource_id" });
119
- return;
120
- }
121
- const resourceMeta = JSON.parse(await readFile(`${target}.json`, "utf-8"));
122
- const buffer = await readFile(target);
123
- res.status(200);
124
- res.header("Content-Type", resourceMeta.contentType);
125
- res.send(buffer);
126
- };
127
- }
128
- //#endregion
129
- //#region src/handlers/transport.ts
130
- function createMcpServer(options, actions, context, onToolCall) {
131
- const server = new McpServer({
132
- name: options.name,
133
- description: options.description,
134
- version: options.version
135
- }, { capabilities: {
136
- tools: {},
137
- logging: {}
138
- } });
139
- registerTools(server, actions, context, { onToolCall });
140
- return server;
141
- }
142
- /**
143
- * Build the MCP Streamable HTTP transport route handler.
144
- *
145
- * Stateless (per the 2026 spec direction): each `POST /mcp` mints a fresh
146
- * transport + server with `sessionIdGenerator: undefined`, handles exactly that
147
- * request (streaming progress over SSE when the call carries a `progressToken`),
148
- * and tears down on response close. No `Mcp-Session-Id`, no session map, no
149
- * `GET`/`DELETE` reconnect - any request can hit any instance.
150
- */
151
- function mcpTransport(silkweaveOptions, context, actions, options = {}) {
152
- actions.forEach(validateActionDisposition);
153
- const post = async (req, res) => {
154
- if (Array.isArray(req.body)) {
155
- res.status(400).json({
156
- jsonrpc: "2.0",
157
- error: {
158
- code: -32600,
159
- message: "JSON-RPC batch requests are not supported"
160
- },
161
- id: null
162
- });
163
- return;
164
- }
165
- let active = actions;
166
- if (options.filterActions) try {
167
- active = await options.filterActions(actions, {
168
- headers: req.headers,
169
- url: req.originalUrl ?? req.url,
170
- ...rpcInfo(req.body)
171
- });
172
- } catch (error) {
173
- const { status, body } = filterErrorResponse(error, req.body);
174
- res.status(status).json(body);
175
- return;
176
- }
177
- try {
178
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
179
- const server = createMcpServer(silkweaveOptions, active, context, options.onToolCall);
180
- res.on("close", () => {
181
- transport.close();
182
- server.close();
183
- });
184
- await server.connect(transport);
185
- await transport.handleRequest(req, res, req.body);
186
- } catch (error) {
187
- console.error("Error handling MCP request:", error);
188
- if (!res.headersSent) res.status(500).json({
189
- jsonrpc: "2.0",
190
- error: {
191
- code: -32603,
192
- message: "Internal server error"
193
- },
194
- id: null
195
- });
196
- }
197
- };
198
- return { post };
199
- }
200
- //#endregion
201
- //#region src/adapter/http.ts
202
- /**
203
- * Build a fully-wired Express app that exposes the MCP Streamable HTTP
204
- * transport (plus OAuth / sideload / well-known routes when configured).
205
- *
206
- * Pass the resulting `app` to `app.listen(port, host)` yourself, or use the
207
- * top-level `startMcpServer()` / `http()` adapter conveniences.
208
- */
209
- function buildMcpExpressApp(silkweaveOptions, context, actions, options) {
210
- const { host, auth, cors: corsConfig, sideloadResources = true, resourceDir, filterActions, onToolCall, ...mcpAppOptions } = options;
211
- const app = createMcpExpressApp({
212
- ...mcpAppOptions,
213
- host
214
- });
215
- const corsHandler = mcpCors(corsConfig ?? true);
216
- if (corsHandler) app.use(corsHandler);
217
- if (auth?.authorizationServers?.length && auth.resourceUrl) app.get("/.well-known/oauth-protected-resource", protectedResourceMetadata(auth));
218
- let oauthPaths = /* @__PURE__ */ new Set();
219
- if (auth?.provider) {
220
- const oauth = oauthRoutes(auth);
221
- app.get("/.well-known/oauth-authorization-server", oauth.wellKnownAuthServer);
222
- app.get("/authorize", oauth.authorize);
223
- app.get(oauth.callbackPath, oauth.callback);
224
- app.post("/token", ...oauth.token);
225
- app.post("/register", ...oauth.register);
226
- oauthPaths = new Set([
227
- "/.well-known/oauth-authorization-server",
228
- "/authorize",
229
- oauth.callbackPath,
230
- "/token",
231
- "/register"
232
- ]);
233
- }
234
- if (auth) {
235
- const guard = authMiddleware(auth, context);
236
- app.use((req, res, next) => {
237
- if (req.path.startsWith("/.well-known/") || oauthPaths.has(req.path)) return next();
238
- return guard(req, res, next);
239
- });
240
- }
241
- if (sideloadResources) app.get("/resource/:id", sideloadResource({ resourceDir }));
242
- const transport = mcpTransport(silkweaveOptions, context, actions, {
243
- filterActions,
244
- onToolCall
245
- });
246
- app.post("/mcp", express.json(), transport.post);
247
- return app;
248
- }
249
- /**
250
- * Spin up a standalone MCP Streamable HTTP server on `host:port` for the
251
- * given `actions`. Returns the underlying `Server` so callers can close it.
252
- *
253
- * Convenience for use cases that don't go through the `silkweave()` builder.
254
- */
255
- async function startMcpServer(silkweaveOptions, actions, options, context) {
256
- const app = buildMcpExpressApp(silkweaveOptions, context ?? createContext({ adapter: "http" }), actions, options);
257
- return new Promise((resolve, reject) => {
258
- const server = app.listen(options.port, options.host, (error) => {
259
- if (error) {
260
- reject(error);
261
- return;
262
- }
263
- console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`);
264
- resolve(server);
265
- });
266
- });
267
- }
268
- /**
269
- * Silkweave adapter that owns its own HTTP server. Composes the MCP handler
270
- * primitives into a fully-wired Express app and listens on `host:port`.
271
- */
272
- const http = (options) => {
273
- return (silkweaveOptions, baseContext) => {
274
- const context = baseContext.fork({ adapter: "http" });
275
- let httpServer;
276
- return {
277
- context,
278
- start: async (actions) => {
279
- const app = buildMcpExpressApp(silkweaveOptions, context, actions, options);
280
- httpServer = await new Promise((resolve, reject) => {
281
- const s = app.listen(options.port, options.host, (error) => {
282
- if (error) {
283
- reject(error);
284
- return;
285
- }
286
- console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`);
287
- resolve(s);
288
- });
289
- });
290
- },
291
- stop: async () => {
292
- if (!httpServer) return;
293
- await new Promise((resolve, reject) => {
294
- httpServer.close((err) => err ? reject(err) : resolve());
295
- });
296
- httpServer = void 0;
297
- }
298
- };
299
- };
300
- };
301
- //#endregion
7
+ import { writeFile } from "fs/promises";
302
8
  //#region src/adapter/stdio.ts
303
9
  const stdio = (adapterOptions) => {
304
10
  return (options, baseContext) => {
@@ -342,6 +48,6 @@ async function createSideloadResource(buffer, { name, contentType }) {
342
48
  return resource;
343
49
  }
344
50
  //#endregion
345
- export { MCP_REQUIRED_HEADERS, authMiddleware, authStorage, buildMcpExpressApp, createSideloadResource, errorToolResult, filterErrorResponse, handleToolError, http, jsonToolResult, mcpCors, mcpTransport, oauthRoutes, parseResourceMessage, protectedResourceMetadata, registerTools, requestFromExtra, rpcInfo, sideloadResource, smartToolResult, startMcpServer, stdio, structuredToolResult };
51
+ export { createSideloadResource, errorToolResult, filterErrorResponse, handleToolError, jsonToolResult, parseResourceMessage, registerTools, requestFromExtra, rpcInfo, smartToolResult, stdio, structuredToolResult };
346
52
 
347
53
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/handlers/cors.ts","../src/handlers/metadata.ts","../src/handlers/oauth.ts","../src/handlers/sideload.ts","../src/handlers/transport.ts","../src/adapter/http.ts","../src/adapter/stdio.ts","../src/util/sideload.ts"],"sourcesContent":["import cors, { CorsOptions } from 'cors'\nimport { type RequestHandler } from 'express'\n\n/** Headers required by the MCP protocol that must always be exposed when CORS is in use. */\nexport const MCP_REQUIRED_HEADERS = ['WWW-Authenticate', 'Last-Event-Id', 'Mcp-Protocol-Version']\n\n/**\n * CORS middleware preconfigured to expose the headers MCP clients need\n * (`Last-Event-Id`, `Mcp-Protocol-Version`, `WWW-Authenticate`) on top of any\n * user-supplied options. (Stateless transport - no `Mcp-Session-Id`.)\n *\n * Pass `false` to disable, omit / pass `true` for permissive defaults, or pass\n * a `CorsOptions` object to override.\n */\nexport function mcpCors(corsConfig: CorsOptions | boolean = true): RequestHandler | null {\n if (corsConfig === false) { return null }\n const userConfig = corsConfig === true ? {} : corsConfig\n const userExposed = userConfig.exposedHeaders\n const exposedHeaders = [\n ...MCP_REQUIRED_HEADERS,\n ...(Array.isArray(userExposed) ? userExposed : userExposed ? [userExposed] : [])\n ]\n return cors({ origin: '*', ...userConfig, exposedHeaders })\n}\n","import { AuthConfig, generateProtectedResourceMetadata } from '@silkweave/auth'\nimport { type RequestHandler } from 'express'\n\n/**\n * Handler for `GET /.well-known/oauth-protected-resource` (RFC 9728). Returns\n * the resource server's metadata pointing at the configured authorization\n * servers. Requires `auth.resourceUrl` and a non-empty `auth.authorizationServers`.\n */\nexport function protectedResourceMetadata(auth: AuthConfig): RequestHandler {\n if (!auth.resourceUrl || !auth.authorizationServers?.length) {\n throw new Error('@silkweave/mcp protectedResourceMetadata(): auth.resourceUrl and auth.authorizationServers are required')\n }\n const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers, auth.requiredScopes)\n return (_req, res) => { res.json(metadata) }\n}\n","import { AuthConfig, OAuthRequest, OAuthResponse } from '@silkweave/auth'\nimport express, { type Request, type RequestHandler, type Response } from 'express'\n\nfunction toOAuthReq(req: Request): OAuthRequest {\n return {\n method: req.method,\n url: new URL(req.url, `${req.protocol}://${req.get('host')}`),\n headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),\n body: req.body as Record<string, string> | undefined\n }\n}\n\nfunction sendOAuth(res: Response, oauthRes: OAuthResponse) {\n for (const [key, value] of Object.entries(oauthRes.headers)) { res.header(key, value) }\n if (oauthRes.body) {\n res.status(oauthRes.status).send(typeof oauthRes.body === 'string' ? oauthRes.body : JSON.stringify(oauthRes.body))\n } else {\n res.status(oauthRes.status).end()\n }\n}\n\nexport interface OAuthRouteHandlers {\n /** `GET /.well-known/oauth-authorization-server` - RFC 8414 discovery. */\n wellKnownAuthServer: RequestHandler\n /** `GET /authorize` - start the OAuth flow. */\n authorize: RequestHandler\n /** `GET {callbackPath}` - provider callback. */\n callback: RequestHandler\n /** Path the provider should redirect to (defaults to `/auth/callback`). */\n callbackPath: string\n /** `POST /token` - exchange code / refresh token. Includes urlencoded body parser. */\n token: RequestHandler[]\n /** `POST /register` - dynamic client registration. Includes JSON body parser. */\n register: RequestHandler[]\n}\n\n/**\n * Build the OAuth 2.1 proxy route handlers (authorize, callback, token,\n * register, well-known) backed by the configured `auth.provider`. Returns the\n * handlers as individual `RequestHandler`s so callers can register them\n * wherever they like - under `/mcp`, at the server root, etc.\n *\n * `token` and `register` are returned as middleware arrays because the\n * appropriate body parser must run before the handler. Apply with\n * `app.post(path, ...token)`.\n */\nexport function oauthRoutes(auth: AuthConfig): OAuthRouteHandlers {\n if (!auth.provider) { throw new Error('@silkweave/mcp oauthRoutes(): auth.provider is required') }\n const provider = auth.provider\n const callbackPath = auth.callbackPath ?? '/auth/callback'\n\n return {\n callbackPath,\n wellKnownAuthServer: (_req, res) => { sendOAuth(res, provider.metadata()) },\n authorize: async (req, res) => { sendOAuth(res, await provider.authorize(toOAuthReq(req))) },\n callback: async (req, res) => { sendOAuth(res, await provider.callback(toOAuthReq(req))) },\n token: [\n express.urlencoded({ extended: false }),\n async (req, res) => { sendOAuth(res, await provider.token(toOAuthReq(req))) }\n ],\n register: [\n express.json(),\n async (req, res) => { sendOAuth(res, await provider.register(toOAuthReq(req))) }\n ]\n }\n}\n","import { type RequestHandler } from 'express'\nimport { readFile } from 'fs/promises'\nimport { basename, resolve, sep } from 'path'\nimport { type SideloadResource } from '../util/sideload.js'\n\nexport interface SideloadResourceOptions {\n /** Directory to read sideload resources from. Defaults to `resources/` (cwd-relative). */\n resourceDir?: string\n}\n\n/**\n * Handler for `GET /resource/:id` - serves a large MCP response that was\n * sideloaded to disk as a `{id}` payload with a `{id}.json` metadata sidecar.\n *\n * The route param `id` is provided by the host framework (Express, Nest, etc.).\n */\nexport function sideloadResource(options: SideloadResourceOptions = {}): RequestHandler {\n const { resourceDir = 'resources' } = options\n const baseDir = resolve(resourceDir)\n return async (req, res) => {\n const id = req.params['id']\n if (!id || typeof id !== 'string') { throw new Error('Invalid ID') }\n // Contain the read to resourceDir. Express 5 decodes %2F in the route param,\n // so an untrusted `id` like `../../etc/passwd` would otherwise escape the\n // directory. basename() strips any path separators; the resolve+prefix check\n // is defense in depth against platform-specific separator handling.\n const safeId = basename(id)\n const target = resolve(baseDir, safeId)\n if (safeId !== id || (target !== baseDir && !target.startsWith(baseDir + sep))) {\n res.status(400).json({ error: 'invalid_resource_id' })\n return\n }\n const resourceMeta: SideloadResource = JSON.parse(await readFile(`${target}.json`, 'utf-8'))\n const buffer = await readFile(target)\n res.status(200)\n res.header('Content-Type', resourceMeta.contentType)\n res.send(buffer)\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport { Action, OnToolCall, SilkweaveContext, SilkweaveOptions, validateActionDisposition } from '@silkweave/core'\nimport { type RequestHandler } from 'express'\nimport { filterErrorResponse, rpcInfo, type FilterActions } from './filter.js'\nimport { registerTools } from './registerTools.js'\n\nfunction createMcpServer(options: SilkweaveOptions, actions: Action[], context: SilkweaveContext, onToolCall?: OnToolCall): McpServer {\n const server = new McpServer({\n name: options.name,\n description: options.description,\n version: options.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n registerTools(server, actions, context, { onToolCall })\n return server\n}\n\nexport interface McpTransportHandlers {\n /** `POST /mcp` - handle a single stateless MCP request/response (SSE per call). */\n post: RequestHandler\n}\n\nexport interface McpTransportOptions {\n /**\n * Per-request tool filter, applied before `registerTools()` on every POST.\n * See `FilterActions` for the request stand-in and error semantics.\n */\n filterActions?: FilterActions\n /** Telemetry hook invoked once per tool call (fire-and-forget). */\n onToolCall?: OnToolCall\n}\n\n/**\n * Build the MCP Streamable HTTP transport route handler.\n *\n * Stateless (per the 2026 spec direction): each `POST /mcp` mints a fresh\n * transport + server with `sessionIdGenerator: undefined`, handles exactly that\n * request (streaming progress over SSE when the call carries a `progressToken`),\n * and tears down on response close. No `Mcp-Session-Id`, no session map, no\n * `GET`/`DELETE` reconnect - any request can hit any instance.\n */\nexport function mcpTransport(\n silkweaveOptions: SilkweaveOptions,\n context: SilkweaveContext,\n actions: Action[],\n options: McpTransportOptions = {}\n): McpTransportHandlers {\n // Fail at boot, not per request - the factory runs once when the app is built.\n actions.forEach(validateActionDisposition)\n\n const post: RequestHandler = async (req, res) => {\n // JSON-RPC batching was removed from the MCP spec (2025-06-18). A batch also\n // defeats per-request filterActions: rpcInfo reflects only the first message\n // but the SDK transport would execute every entry, so a later batch entry\n // could invoke a tool the filter gated on the first. Reject batches outright.\n if (Array.isArray(req.body)) {\n res.status(400).json({ jsonrpc: '2.0', error: { code: -32_600, message: 'JSON-RPC batch requests are not supported' }, id: null })\n return\n }\n let active = actions\n if (options.filterActions) {\n try {\n active = await options.filterActions(actions, { headers: req.headers, url: req.originalUrl ?? req.url, ...rpcInfo(req.body) })\n } catch (error) {\n // A throw never degrades to an empty tool list - it surfaces as its\n // statusCode (SilkweaveError) or a 500, so a bad key reads as an auth\n // failure rather than \"server has no tools\".\n const { status, body } = filterErrorResponse(error, req.body)\n res.status(status).json(body)\n return\n }\n }\n try {\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })\n const server = createMcpServer(silkweaveOptions, active, context, options.onToolCall)\n res.on('close', () => { void transport.close(); void server.close() })\n await server.connect(transport)\n await transport.handleRequest(req, res, req.body)\n } catch (error) {\n console.error('Error handling MCP request:', error)\n if (!res.headersSent) {\n res.status(500).json({ jsonrpc: '2.0', error: { code: -32_603, message: 'Internal server error' }, id: null })\n }\n }\n }\n\n return { post }\n}\n","import { createMcpExpressApp, type CreateMcpExpressAppOptions } from '@modelcontextprotocol/sdk/server/express.js'\nimport { AuthConfig } from '@silkweave/auth'\nimport { Action, AdapterFactory, createContext, OnToolCall, SilkweaveContext, SilkweaveOptions } from '@silkweave/core'\nimport { CorsOptions } from 'cors'\nimport express, { type Express } from 'express'\nimport { Server } from 'http'\nimport { authMiddleware } from '../handlers/auth.js'\nimport { mcpCors } from '../handlers/cors.js'\nimport { protectedResourceMetadata } from '../handlers/metadata.js'\nimport { oauthRoutes } from '../handlers/oauth.js'\nimport { type FilterActions } from '../handlers/filter.js'\nimport { sideloadResource } from '../handlers/sideload.js'\nimport { mcpTransport } from '../handlers/transport.js'\n\nexport interface StartMcpHttpOptions extends CreateMcpExpressAppOptions {\n host: string\n port: number\n auth?: AuthConfig\n /** CORS configuration. `false` to disable, omitted/`true` for permissive defaults, or a `CorsOptions` object. */\n cors?: CorsOptions | boolean\n /** Mount the `/resource/:id` sideload route. Default `true`. */\n sideloadResources?: boolean\n /** Directory the sideload route reads from. Default `'resources'`. */\n resourceDir?: string\n /**\n * Per-request tool filter, applied before `registerTools()` on every\n * `POST /mcp` (the stateless transport recomputes the tool list per request,\n * so permission changes apply on the next `tools/list`). See `FilterActions`\n * for the request stand-in (`headers`/`url`/`method`/`toolName`) and error\n * semantics (a throw surfaces as its `SilkweaveError.statusCode` or 500 -\n * never an empty tool list).\n */\n filterActions?: FilterActions\n /** Telemetry hook invoked once per tool call (fire-and-forget). */\n onToolCall?: OnToolCall\n}\n\n/**\n * Build a fully-wired Express app that exposes the MCP Streamable HTTP\n * transport (plus OAuth / sideload / well-known routes when configured).\n *\n * Pass the resulting `app` to `app.listen(port, host)` yourself, or use the\n * top-level `startMcpServer()` / `http()` adapter conveniences.\n */\nexport function buildMcpExpressApp(\n silkweaveOptions: SilkweaveOptions,\n context: SilkweaveContext,\n actions: Action[],\n options: StartMcpHttpOptions\n): Express {\n const { host, auth, cors: corsConfig, sideloadResources = true, resourceDir, filterActions, onToolCall, ...mcpAppOptions } = options\n const app = createMcpExpressApp({ ...mcpAppOptions, host })\n\n const corsHandler = mcpCors(corsConfig ?? true)\n if (corsHandler) { app.use(corsHandler) }\n\n if (auth?.authorizationServers?.length && auth.resourceUrl) {\n app.get('/.well-known/oauth-protected-resource', protectedResourceMetadata(auth))\n }\n\n let oauthPaths = new Set<string>()\n if (auth?.provider) {\n const oauth = oauthRoutes(auth)\n app.get('/.well-known/oauth-authorization-server', oauth.wellKnownAuthServer)\n app.get('/authorize', oauth.authorize)\n app.get(oauth.callbackPath, oauth.callback)\n app.post('/token', ...oauth.token)\n app.post('/register', ...oauth.register)\n oauthPaths = new Set(['/.well-known/oauth-authorization-server', '/authorize', oauth.callbackPath, '/token', '/register'])\n }\n\n if (auth) {\n const guard = authMiddleware(auth, context)\n app.use((req, res, next) => {\n if (req.path.startsWith('/.well-known/') || oauthPaths.has(req.path)) { return next() }\n return guard(req, res, next)\n })\n }\n\n if (sideloadResources) {\n app.get('/resource/:id', sideloadResource({ resourceDir }))\n }\n\n const transport = mcpTransport(silkweaveOptions, context, actions, { filterActions, onToolCall })\n app.post('/mcp', express.json(), transport.post)\n\n return app\n}\n\n/**\n * Spin up a standalone MCP Streamable HTTP server on `host:port` for the\n * given `actions`. Returns the underlying `Server` so callers can close it.\n *\n * Convenience for use cases that don't go through the `silkweave()` builder.\n */\nexport async function startMcpServer(\n silkweaveOptions: SilkweaveOptions,\n actions: Action[],\n options: StartMcpHttpOptions,\n context?: SilkweaveContext\n): Promise<Server> {\n const ctx = context ?? createContext({ adapter: 'http' })\n const app = buildMcpExpressApp(silkweaveOptions, ctx, actions, options)\n return new Promise<Server>((resolve, reject) => {\n const server = app.listen(options.port, options.host, (error) => {\n if (error) { reject(error); return }\n console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`)\n resolve(server)\n })\n })\n}\n\n/**\n * Silkweave adapter that owns its own HTTP server. Composes the MCP handler\n * primitives into a fully-wired Express app and listens on `host:port`.\n */\nexport const http: AdapterFactory<StartMcpHttpOptions> = (options) => {\n return (silkweaveOptions, baseContext) => {\n const context = baseContext.fork({ adapter: 'http' })\n let httpServer: Server | undefined\n return {\n context,\n start: async (actions) => {\n const app = buildMcpExpressApp(silkweaveOptions, context, actions, options)\n httpServer = await new Promise<Server>((resolve, reject) => {\n const s = app.listen(options.port, options.host, (error) => {\n if (error) { reject(error); return }\n console.log(`MCP Streamable HTTP Server listening on http://${options.host}:${options.port}/mcp`)\n resolve(s)\n })\n })\n },\n stop: async () => {\n if (!httpServer) { return }\n await new Promise<void>((resolve, reject) => {\n httpServer!.close((err) => err ? reject(err) : resolve())\n })\n httpServer = undefined\n }\n }\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { AdapterFactory, OnToolCall, validateActionDisposition } from '@silkweave/core'\nimport { registerTools } from '../handlers/registerTools.js'\n\nexport interface StdioAdapterOptions {\n /** Telemetry hook invoked once per tool call (fire-and-forget). */\n onToolCall?: OnToolCall\n}\n\nexport const stdio: AdapterFactory<StdioAdapterOptions | void> = (adapterOptions) => {\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'stdio' })\n const server = new McpServer({\n name: options.name,\n description: options.description,\n version: options.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n return {\n context,\n start: async (actions) => {\n actions.forEach(validateActionDisposition)\n registerTools(server, actions, context, { logStream: false, onToolCall: adapterOptions?.onToolCall })\n const transport = new StdioServerTransport()\n await server.connect(transport)\n },\n stop: async () => {\n await server?.close()\n }\n }\n }\n}\n","import { randomUUID } from 'crypto'\nimport { writeFile } from 'fs/promises'\n\nexport interface SideloadResource {\n id: string\n name: string\n contentType: string\n size: number\n}\n\nexport async function createSideloadResource(buffer: Buffer, { name, contentType }: Pick<SideloadResource, 'name' | 'contentType'>) {\n const id = randomUUID()\n const resource: SideloadResource = { id, name, contentType, size: buffer.length }\n await Promise.all([\n writeFile(`resources/${id}`, buffer),\n writeFile(`resources/${id}.json`, JSON.stringify(resource), 'utf-8')\n ])\n return resource\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter/stdio.ts","../src/util/sideload.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { AdapterFactory, OnToolCall, validateActionDisposition } from '@silkweave/core'\nimport { registerTools } from '../handlers/registerTools.js'\n\nexport interface StdioAdapterOptions {\n /** Telemetry hook invoked once per tool call (fire-and-forget). */\n onToolCall?: OnToolCall\n}\n\nexport const stdio: AdapterFactory<StdioAdapterOptions | void> = (adapterOptions) => {\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'stdio' })\n const server = new McpServer({\n name: options.name,\n description: options.description,\n version: options.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n return {\n context,\n start: async (actions) => {\n actions.forEach(validateActionDisposition)\n registerTools(server, actions, context, { logStream: false, onToolCall: adapterOptions?.onToolCall })\n const transport = new StdioServerTransport()\n await server.connect(transport)\n },\n stop: async () => {\n await server?.close()\n }\n }\n }\n}\n","import { randomUUID } from 'crypto'\nimport { writeFile } from 'fs/promises'\n\nexport interface SideloadResource {\n id: string\n name: string\n contentType: string\n size: number\n}\n\nexport async function createSideloadResource(buffer: Buffer, { name, contentType }: Pick<SideloadResource, 'name' | 'contentType'>) {\n const id = randomUUID()\n const resource: SideloadResource = { id, name, contentType, size: buffer.length }\n await Promise.all([\n writeFile(`resources/${id}`, buffer),\n writeFile(`resources/${id}.json`, JSON.stringify(resource), 'utf-8')\n ])\n return resource\n}\n"],"mappings":";;;;;;;;AAUA,MAAa,SAAqD,mBAAmB;AACnF,SAAQ,SAAS,gBAAgB;EAC/B,MAAM,UAAU,YAAY,KAAK,EAAE,SAAS,SAAS,CAAC;EACtD,MAAM,SAAS,IAAI,UAAU;GAC3B,MAAM,QAAQ;GACd,aAAa,QAAQ;GACrB,SAAS,QAAQ;GAClB,EAAE,EACD,cAAc;GAAE,OAAO,EAAE;GAAE,SAAS,EAAE;GAAE,EACzC,CAAC;AACF,SAAO;GACL;GACA,OAAO,OAAO,YAAY;AACxB,YAAQ,QAAQ,0BAA0B;AAC1C,kBAAc,QAAQ,SAAS,SAAS;KAAE,WAAW;KAAO,YAAY,gBAAgB;KAAY,CAAC;IACrG,MAAM,YAAY,IAAI,sBAAsB;AAC5C,UAAM,OAAO,QAAQ,UAAU;;GAEjC,MAAM,YAAY;AAChB,UAAM,QAAQ,OAAO;;GAExB;;;;;ACrBL,eAAsB,uBAAuB,QAAgB,EAAE,MAAM,eAA+D;CAClI,MAAM,KAAK,YAAY;CACvB,MAAM,WAA6B;EAAE;EAAI;EAAM;EAAa,MAAM,OAAO;EAAQ;AACjF,OAAM,QAAQ,IAAI,CAChB,UAAU,aAAa,MAAM,OAAO,EACpC,UAAU,aAAa,GAAG,QAAQ,KAAK,UAAU,SAAS,EAAE,QAAQ,CACrE,CAAC;AACF,QAAO"}
@@ -1,63 +1,7 @@
1
- import { Action, OnToolCall, SilkweaveContext, SilkweaveError, createLogger } from "@silkweave/core";
2
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { Action, OnToolCall, SilkweaveContext, SilkweaveError, createLogger } from "@silkweave/core";
3
3
  import { CallToolResult, EmbeddedResource } from "@modelcontextprotocol/sdk/types.js";
4
4
 
5
- //#region src/handlers/filter.d.ts
6
- /**
7
- * Normalized request stand-in handed to `filterActions` - the same shape for
8
- * the Express `http()` transport and the Web-Standard `edge()` adapter.
9
- */
10
- interface FilterRequest {
11
- /** Inbound HTTP headers (lower-cased keys, as delivered by the host). */
12
- headers: Record<string, string | string[] | undefined>;
13
- /** Full request URL (or path, as delivered by the host). */
14
- url: string;
15
- /**
16
- * JSON-RPC method of the POSTed message (`'initialize'`, `'tools/list'`,
17
- * `'tools/call'`, `'ping'`, ...). Lets the callback skip expensive
18
- * permission lookups on `initialize`/`ping`, and doubles as an
19
- * observability tap (e.g. counting `tools/list`). Empty string when the body
20
- * is not a recognizable JSON-RPC message. JSON-RPC batches are rejected by the
21
- * transport before the filter runs, so this always reflects a single message.
22
- */
23
- method: string;
24
- /** `params.name` of a `tools/call` message; unset for every other method. */
25
- toolName?: string;
26
- }
27
- /**
28
- * Per-request tool filter for the stateless MCP transports. Runs before
29
- * `registerTools()` on every `POST /mcp`; only the returned actions exist for
30
- * that request (`tools/list` and `tools/call` alike - a client that cached a
31
- * wider list is still denied). May be async (e.g. a DB lookup of API-key
32
- * permissions).
33
- *
34
- * Error semantics: a thrown `SilkweaveError` propagates as its `statusCode`
35
- * (e.g. 401 invalid key, 403 insufficient permissions) with a JSON-RPC error
36
- * body; any other throw maps to HTTP 500. A thrown error NEVER degrades to an
37
- * empty tool list - return `[]` explicitly if "no tools" is the intended
38
- * answer.
39
- */
40
- type FilterActions = (actions: Action[], request: FilterRequest) => Action[] | Promise<Action[]>;
41
- /**
42
- * Extract the JSON-RPC `method` (and `toolName` for `tools/call`) from a
43
- * parsed request body. Tolerates a legacy batch (first request wins) and
44
- * unrecognizable bodies (empty `method`).
45
- */
46
- declare function rpcInfo(body: unknown): {
47
- method: string;
48
- toolName?: string;
49
- };
50
- /**
51
- * Map a `filterActions` throw to an HTTP status + JSON-RPC error body: a
52
- * `SilkweaveError` keeps its `statusCode` (so an invalid key surfaces as an
53
- * auth failure, not "server has no tools"), anything else is a 500. `id` is
54
- * echoed from the request body when available.
55
- */
56
- declare function filterErrorResponse(error: unknown, body: unknown): {
57
- status: number;
58
- body: object;
59
- };
60
- //#endregion
61
5
  //#region src/handlers/registerTools.d.ts
62
6
  type LogStream = NonNullable<Parameters<typeof createLogger>[0]>['stream'];
63
7
  interface RegisterToolsOptions {
@@ -132,5 +76,5 @@ declare function parseResourceMessage({
132
76
  resource
133
77
  }: EmbeddedResource): string;
134
78
  //#endregion
135
- export { smartToolResult as a, registerTools as c, FilterRequest as d, filterErrorResponse as f, parseResourceMessage as i, requestFromExtra as l, handleToolError as n, structuredToolResult as o, rpcInfo as p, jsonToolResult as r, RegisterToolsOptions as s, errorToolResult as t, FilterActions as u };
136
- //# sourceMappingURL=result-I04HIJR5.d.mts.map
79
+ export { smartToolResult as a, registerTools as c, parseResourceMessage as i, requestFromExtra as l, handleToolError as n, structuredToolResult as o, jsonToolResult as r, RegisterToolsOptions as s, errorToolResult as t };
80
+ //# sourceMappingURL=result-CQT2v6P1.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result-CQT2v6P1.d.mts","names":[],"sources":["../src/handlers/registerTools.ts","../src/util/result.ts"],"mappings":";;;;;KAQK,SAAA,GAAY,WAAA,CAAY,UAAA,QAAkB,YAAA;AAAA,UAG9B,oBAAA;;AAPkK;;;;EAajL,SAAA,GAAY,SAAA;EATG;;;;;;EAgBf,UAAA,GAAa,UAAA;AAAA;AAbf;;;;;;;;;;AA2BA;AA3BA,iBA2BgB,gBAAA,CAAiB,WAAA;EAAe,OAAA;EAAmB,GAAA;IAAQ,QAAA;EAAA;AAAA;;;;;;;;;AA+G3E;;;;;;;;iBAAgB,aAAA,CACd,MAAA,EAAQ,SAAA,EACR,OAAA,EAAS,MAAA,IACT,OAAA,EAAS,gBAAA,EACT,OAAA,GAAS,oBAAA;;;iBCtIK,eAAA,CAAgB,IAAA,+BAAmC,cAAA;;;ADfgH;;;;;;iBC2CnK,oBAAA,CAAqB,IAAA,WAAe,cAAA;AAAA,iBAOpC,cAAA,CAAe,IAAA,UAAc,OAAA,aAAkB,cAAA;AAAA,iBAM/C,eAAA,CAAA;EAAkB,IAAA;EAAM,IAAA;EAAM;AAAA,GAAW,cAAA,GAAiB,cAAA;AAAA,iBAU1D,eAAA,CAAgB,KAAA,YAAiB,cAAA;AAAA,iBAejC,oBAAA,CAAA;EAAuB;AAAA,GAAY,gBAAA"}
@@ -1,17 +1,30 @@
1
1
  import { SilkweaveError } from "@silkweave/core";
2
- import { randomUUID } from "node:crypto";
3
2
  //#region src/util/result.ts
3
+ function utf8ToBase64(text) {
4
+ const bytes = new TextEncoder().encode(text);
5
+ let binary = "";
6
+ for (const byte of bytes) binary += String.fromCharCode(byte);
7
+ return {
8
+ base64: btoa(binary),
9
+ byteLength: bytes.length
10
+ };
11
+ }
12
+ function base64ToUtf8(base64) {
13
+ const binary = atob(base64);
14
+ const bytes = new Uint8Array(binary.length);
15
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
16
+ return new TextDecoder().decode(bytes);
17
+ }
4
18
  function smartToolResult(data) {
5
19
  const text = typeof data === "string" ? data : JSON.stringify(data);
6
20
  const mimeType = typeof data === "string" ? "text/plain" : "application/json";
7
21
  const ext = typeof data === "string" ? "txt" : "json";
8
22
  if (text.length > 4096) {
9
- const uri = `mcp://toolResult/${randomUUID()}.${ext}`;
10
- const buffer = Buffer.from(text);
11
- const blob = buffer.toString("base64");
23
+ const uri = `mcp://toolResult/${crypto.randomUUID()}.${ext}`;
24
+ const { base64: blob, byteLength } = utf8ToBase64(text);
12
25
  return { content: [{
13
26
  type: "text",
14
- text: `Received resource ${uri} with ${buffer.byteLength} bytes`
27
+ text: `Received resource ${uri} with ${byteLength} bytes`
15
28
  }, {
16
29
  type: "resource",
17
30
  resource: {
@@ -88,9 +101,9 @@ function handleToolError(error) {
88
101
  }
89
102
  }
90
103
  function parseResourceMessage({ resource }) {
91
- return "blob" in resource ? Buffer.from(resource.blob, "base64").toString("utf-8") : resource.text;
104
+ return "blob" in resource ? base64ToUtf8(resource.blob) : resource.text;
92
105
  }
93
106
  //#endregion
94
107
  export { smartToolResult as a, parseResourceMessage as i, handleToolError as n, structuredToolResult as o, jsonToolResult as r, errorToolResult as t };
95
108
 
96
- //# sourceMappingURL=result-CXgeE8ca.mjs.map
109
+ //# sourceMappingURL=result-uIqPNNxF.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result-uIqPNNxF.mjs","names":[],"sources":["../src/util/result.ts"],"sourcesContent":["import { EmbeddedResource, type CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { SilkweaveError } from '@silkweave/core'\n\n// Web-standard (Node 18+ and edge/Workers) UTF-8 <-> base64 so this subpath,\n// which @silkweave/edge imports, carries no Node-only crypto/Buffer dependency.\nfunction utf8ToBase64(text: string): { base64: string; byteLength: number } {\n const bytes = new TextEncoder().encode(text)\n let binary = ''\n for (const byte of bytes) { binary += String.fromCharCode(byte) }\n return { base64: btoa(binary), byteLength: bytes.length }\n}\n\nfunction base64ToUtf8(base64: string): string {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i += 1) { bytes[i] = binary.charCodeAt(i) }\n return new TextDecoder().decode(bytes)\n}\n\nexport function smartToolResult(data: string | object | object[]): CallToolResult {\n const text = typeof data === 'string' ? data : JSON.stringify(data)\n const mimeType = typeof data === 'string' ? 'text/plain' : 'application/json'\n const ext = typeof data === 'string' ? 'txt' : 'json'\n if (text.length > 4096) {\n const uri = `mcp://toolResult/${crypto.randomUUID()}.${ext}`\n const { base64: blob, byteLength } = utf8ToBase64(text)\n return {\n content: [\n { type: 'text', text: `Received resource ${uri} with ${byteLength} bytes` },\n { type: 'resource', resource: { uri, mimeType, blob } }\n ]\n }\n } else {\n return {\n content: [{ type: 'text' as const, text }]\n }\n }\n}\n\n/**\n * Result formatter for `disposition: 'structured'` actions: the (already\n * schema-parsed) data ships as `structuredContent` with a compact JSON text\n * mirror, per the spec's backwards-compat recommendation. Callers must pass\n * the OUTPUT-SCHEMA-PARSED data, not the raw result - parsing strips extra\n * fields, which is what keeps client-side JSON-Schema validation\n * (`additionalProperties: false`) passing by construction.\n */\nexport function structuredToolResult(data: object): CallToolResult {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(data) }],\n structuredContent: data as Record<string, unknown>\n }\n}\n\nexport function jsonToolResult(data: object, isError = false): CallToolResult {\n const result: CallToolResult = { content: [{ type: 'text' as const, text: JSON.stringify(data) }] }\n if (isError) { result.isError = true }\n return result\n}\n\nexport function errorToolResult({ code, name, message }: SilkweaveError): CallToolResult {\n return {\n isError: true,\n content: [{\n type: 'text',\n text: JSON.stringify({ success: false, code, name, message })\n }]\n }\n}\n\nexport function handleToolError(error: unknown): CallToolResult {\n if (error instanceof SilkweaveError) {\n return jsonToolResult({ success: false, name: error.name, message: error.message, code: error.code }, true)\n } else if (error instanceof Error) {\n // Log the full error (incl. stack) to stderr only - never put the stack trace\n // on the wire, where it would leak server internals to the MCP client.\n console.error(error)\n return jsonToolResult({ success: false, name: error.name, message: error.message }, true)\n } else {\n // Likewise keep the raw thrown value server-side - only a generic message goes out.\n console.error('Unknown tool error:', error)\n return jsonToolResult({ success: false, name: 'Unknown error', message: 'An unknown error occurred' }, true)\n }\n}\n\nexport function parseResourceMessage({ resource }: EmbeddedResource) {\n const text = ('blob' in resource) ? base64ToUtf8(resource.blob) : resource.text\n return text\n}\n"],"mappings":";;AAKA,SAAS,aAAa,MAAsD;CAC1E,MAAM,QAAQ,IAAI,aAAa,CAAC,OAAO,KAAK;CAC5C,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,MAAS,WAAU,OAAO,aAAa,KAAK;AAC/D,QAAO;EAAE,QAAQ,KAAK,OAAO;EAAE,YAAY,MAAM;EAAQ;;AAG3D,SAAS,aAAa,QAAwB;CAC5C,MAAM,SAAS,KAAK,OAAO;CAC3B,MAAM,QAAQ,IAAI,WAAW,OAAO,OAAO;AAC3C,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,EAAK,OAAM,KAAK,OAAO,WAAW,EAAE;AAC5E,QAAO,IAAI,aAAa,CAAC,OAAO,MAAM;;AAGxC,SAAgB,gBAAgB,MAAkD;CAChF,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,KAAK;CACnE,MAAM,WAAW,OAAO,SAAS,WAAW,eAAe;CAC3D,MAAM,MAAM,OAAO,SAAS,WAAW,QAAQ;AAC/C,KAAI,KAAK,SAAS,MAAM;EACtB,MAAM,MAAM,oBAAoB,OAAO,YAAY,CAAC,GAAG;EACvD,MAAM,EAAE,QAAQ,MAAM,eAAe,aAAa,KAAK;AACvD,SAAO,EACL,SAAS,CACP;GAAE,MAAM;GAAQ,MAAM,qBAAqB,IAAI,QAAQ,WAAW;GAAS,EAC3E;GAAE,MAAM;GAAY,UAAU;IAAE;IAAK;IAAU;IAAM;GAAE,CACxD,EACF;OAED,QAAO,EACL,SAAS,CAAC;EAAE,MAAM;EAAiB;EAAM,CAAC,EAC3C;;;;;;;;;;AAYL,SAAgB,qBAAqB,MAA8B;AACjE,QAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAiB,MAAM,KAAK,UAAU,KAAK;GAAE,CAAC;EAChE,mBAAmB;EACpB;;AAGH,SAAgB,eAAe,MAAc,UAAU,OAAuB;CAC5E,MAAM,SAAyB,EAAE,SAAS,CAAC;EAAE,MAAM;EAAiB,MAAM,KAAK,UAAU,KAAK;EAAE,CAAC,EAAE;AACnG,KAAI,QAAW,QAAO,UAAU;AAChC,QAAO;;AAGT,SAAgB,gBAAgB,EAAE,MAAM,MAAM,WAA2C;AACvF,QAAO;EACL,SAAS;EACT,SAAS,CAAC;GACR,MAAM;GACN,MAAM,KAAK,UAAU;IAAE,SAAS;IAAO;IAAM;IAAM;IAAS,CAAC;GAC9D,CAAC;EACH;;AAGH,SAAgB,gBAAgB,OAAgC;AAC9D,KAAI,iBAAiB,eACnB,QAAO,eAAe;EAAE,SAAS;EAAO,MAAM,MAAM;EAAM,SAAS,MAAM;EAAS,MAAM,MAAM;EAAM,EAAE,KAAK;UAClG,iBAAiB,OAAO;AAGjC,UAAQ,MAAM,MAAM;AACpB,SAAO,eAAe;GAAE,SAAS;GAAO,MAAM,MAAM;GAAM,SAAS,MAAM;GAAS,EAAE,KAAK;QACpF;AAEL,UAAQ,MAAM,uBAAuB,MAAM;AAC3C,SAAO,eAAe;GAAE,SAAS;GAAO,MAAM;GAAiB,SAAS;GAA6B,EAAE,KAAK;;;AAIhH,SAAgB,qBAAqB,EAAE,YAA8B;AAEnE,QADc,UAAU,WAAY,aAAa,SAAS,KAAK,GAAG,SAAS"}