@silkweave/mcp 1.4.0 → 1.5.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/.turbo/turbo-build.log +14 -15
- package/.turbo/turbo-check.log +13 -12
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-prepack.log +15 -17
- package/build/index.d.mts +46 -0
- package/build/index.d.mts.map +1 -0
- package/build/index.mjs +475 -0
- package/build/index.mjs.map +1 -0
- package/package.json +13 -10
- package/tsconfig.tsbuildinfo +1 -0
- package/build/index.d.ts +0 -33
- package/build/index.js +0 -461
- package/build/index.js.map +0 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @silkweave/mcp@1.
|
|
4
|
-
>
|
|
3
|
+
> @silkweave/mcp@1.4.1 build /Users/atomic/projects/ai/silkweave/packages/mcp
|
|
4
|
+
> tsdown
|
|
5
5
|
|
|
6
|
-
[
|
|
7
|
-
[
|
|
8
|
-
[
|
|
9
|
-
[
|
|
10
|
-
[
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
DTS build/index.d.ts 1.34 KB
|
|
6
|
+
[34mℹ[39m [34mtsdown v0.21.8[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.15[39m
|
|
7
|
+
[34mℹ[39m config file: [4m/Users/atomic/projects/ai/silkweave/packages/mcp/tsdown.config.ts[24m
|
|
8
|
+
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
9
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
10
|
+
[34mℹ[39m Build start
|
|
11
|
+
[34mℹ[39m Cleaning 4 files
|
|
12
|
+
[34mℹ[39m [2mbuild/[22m[1mindex.mjs[22m [2m15.25 kB[22m [2m│ gzip: 4.33 kB[22m
|
|
13
|
+
[34mℹ[39m [2mbuild/[22mindex.mjs.map [2m28.57 kB[22m [2m│ gzip: 7.76 kB[22m
|
|
14
|
+
[34mℹ[39m [2mbuild/[22mindex.d.mts.map [2m 0.77 kB[22m [2m│ gzip: 0.38 kB[22m
|
|
15
|
+
[34mℹ[39m [2mbuild/[22m[32m[1mindex.d.mts[22m[39m [2m 1.62 kB[22m [2m│ gzip: 0.60 kB[22m
|
|
16
|
+
[34mℹ[39m 4 files, total: 46.22 kB
|
|
17
|
+
[32m✔[39m Build complete in [32m797ms[39m
|
package/.turbo/turbo-check.log
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
>
|
|
12
|
-
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @silkweave/mcp@1.4.1 check /Users/atomic/projects/ai/silkweave/packages/mcp
|
|
4
|
+
> pnpm lint && pnpm typecheck
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
> @silkweave/mcp@1.4.1 lint /Users/atomic/projects/ai/silkweave/packages/mcp
|
|
8
|
+
> eslint
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
> @silkweave/mcp@1.4.1 typecheck /Users/atomic/projects/ai/silkweave/packages/mcp
|
|
12
|
+
> tsc --noEmit
|
|
13
|
+
|
package/.turbo/turbo-lint.log
CHANGED
package/.turbo/turbo-prepack.log
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @silkweave/mcp@1.
|
|
3
|
+
> @silkweave/mcp@1.5.0 prepack /Users/atomic/projects/ai/silkweave/packages/mcp
|
|
4
4
|
> pnpm clean && pnpm build
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
> @silkweave/mcp@1.
|
|
7
|
+
> @silkweave/mcp@1.5.0 clean /Users/atomic/projects/ai/silkweave/packages/mcp
|
|
8
8
|
> rimraf build
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
> @silkweave/mcp@1.
|
|
12
|
-
>
|
|
11
|
+
> @silkweave/mcp@1.5.0 build /Users/atomic/projects/ai/silkweave/packages/mcp
|
|
12
|
+
> tsdown
|
|
13
13
|
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
19
|
-
[
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
24
|
-
|
|
25
|
-
DTS ⚡️ Build success in 728ms
|
|
26
|
-
DTS build/index.d.ts 1.34 KB
|
|
14
|
+
[34mℹ[39m [34mtsdown v0.21.8[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.15[39m
|
|
15
|
+
[34mℹ[39m config file: [4m/Users/atomic/projects/ai/silkweave/packages/mcp/tsdown.config.ts[24m
|
|
16
|
+
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
17
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
18
|
+
[34mℹ[39m Build start
|
|
19
|
+
[34mℹ[39m [2mbuild/[22m[1mindex.mjs[22m [2m15.25 kB[22m [2m│ gzip: 4.33 kB[22m
|
|
20
|
+
[34mℹ[39m [2mbuild/[22mindex.mjs.map [2m28.57 kB[22m [2m│ gzip: 7.76 kB[22m
|
|
21
|
+
[34mℹ[39m [2mbuild/[22mindex.d.mts.map [2m 0.77 kB[22m [2m│ gzip: 0.38 kB[22m
|
|
22
|
+
[34mℹ[39m [2mbuild/[22m[32m[1mindex.d.mts[22m[39m [2m 1.62 kB[22m [2m│ gzip: 0.60 kB[22m
|
|
23
|
+
[34mℹ[39m 4 files, total: 46.22 kB
|
|
24
|
+
[32m✔[39m Build complete in [32m598ms[39m
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { AdapterFactory, SilkweaveError } from "@silkweave/core";
|
|
3
|
+
import { CreateMcpExpressAppOptions } from "@modelcontextprotocol/sdk/server/express.js";
|
|
4
|
+
import { AuthConfig } from "@silkweave/auth";
|
|
5
|
+
|
|
6
|
+
//#region src/adapter/cliProxy.d.ts
|
|
7
|
+
interface CliProxyOptions {
|
|
8
|
+
url: URL;
|
|
9
|
+
}
|
|
10
|
+
declare const cliProxy: AdapterFactory<CliProxyOptions>;
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/adapter/http.d.ts
|
|
13
|
+
interface HttpAdapterOptions extends CreateMcpExpressAppOptions {
|
|
14
|
+
host: string;
|
|
15
|
+
port: number;
|
|
16
|
+
auth?: AuthConfig;
|
|
17
|
+
}
|
|
18
|
+
declare const http: AdapterFactory<HttpAdapterOptions>;
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/adapter/stdio.d.ts
|
|
21
|
+
declare const stdio: AdapterFactory;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/util/result.d.ts
|
|
24
|
+
declare function smartToolResult(data: string | object | object[]): CallToolResult;
|
|
25
|
+
declare function jsonToolResult(data: object, isError?: boolean): CallToolResult;
|
|
26
|
+
declare function errorToolResult({
|
|
27
|
+
code,
|
|
28
|
+
name,
|
|
29
|
+
message
|
|
30
|
+
}: SilkweaveError): CallToolResult;
|
|
31
|
+
declare function handleToolError(error: unknown): CallToolResult;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/util/sideload.d.ts
|
|
34
|
+
interface SideloadResource {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
contentType: string;
|
|
38
|
+
size: number;
|
|
39
|
+
}
|
|
40
|
+
declare function createSideloadResource(buffer: Buffer, {
|
|
41
|
+
name,
|
|
42
|
+
contentType
|
|
43
|
+
}: Pick<SideloadResource, 'name' | 'contentType'>): Promise<SideloadResource>;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { CliProxyOptions, HttpAdapterOptions, SideloadResource, cliProxy, createSideloadResource, errorToolResult, handleToolError, http, jsonToolResult, smartToolResult, stdio };
|
|
46
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/cliProxy.ts","../src/adapter/http.ts","../src/adapter/stdio.ts","../src/util/result.ts","../src/util/sideload.ts"],"mappings":";;;;;;UAYiB,eAAA;EACf,GAAA,EAAK,GAAA;AAAA;AAAA,cA4DM,QAAA,EAAU,cAAA,CAAe,eAAA;;;UCrDrB,kBAAA,SAA2B,0BAAA;EAC1C,IAAA;EACA,IAAA;EACA,IAAA,GAAO,UAAA;AAAA;AAAA,cAqLI,IAAA,EAAM,cAAA,CAAe,kBAAA;;;cCzKrB,KAAA,EAAO,cAAA;;;iBC/BJ,eAAA,CAAgB,IAAA,+BAAmC,cAAA;AAAA,iBAqBnD,cAAA,CAAe,IAAA,UAAc,OAAA,aAAkB,cAAA;AAAA,iBAM/C,eAAA,CAAA;EAAkB,IAAA;EAAM,IAAA;EAAM;AAAA,GAAW,cAAA,GAAiB,cAAA;AAAA,iBAU1D,eAAA,CAAgB,KAAA,YAAiB,cAAA;;;UCtChC,gBAAA;EACf,EAAA;EACA,IAAA;EACA,WAAA;EACA,IAAA;AAAA;AAAA,iBAGoB,sBAAA,CAAuB,MAAA,EAAQ,MAAA;EAAU,IAAA;EAAM;AAAA,GAAe,IAAA,CAAK,gBAAA,4BAAyC,OAAA,CAAA,gBAAA"}
|
package/build/index.mjs
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { intro } from "@clack/prompts";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
3
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
+
import { LoggingMessageNotificationSchema, ProgressNotificationSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { SilkweaveError, unwrap } from "@silkweave/core";
|
|
6
|
+
import { createCLILogger, createLogger } from "@silkweave/logger";
|
|
7
|
+
import { capitalCase, kebabCase, pascalCase } from "change-case";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { InMemoryEventStore } from "@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js";
|
|
12
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
13
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
15
|
+
import { generateProtectedResourceMetadata, validateToken } from "@silkweave/auth";
|
|
16
|
+
import cors from "cors";
|
|
17
|
+
import express from "express";
|
|
18
|
+
import { readFile, writeFile } from "fs/promises";
|
|
19
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20
|
+
import { randomUUID as randomUUID$1 } from "node:crypto";
|
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
//#region src/adapter/cliProxy.ts
|
|
23
|
+
function addCliOption(command, key, type, defaultValue) {
|
|
24
|
+
const description = type.description;
|
|
25
|
+
const flag = kebabCase(key);
|
|
26
|
+
if (type instanceof z.ZodBoolean) {
|
|
27
|
+
command.option(`--${flag}`, description, defaultValue);
|
|
28
|
+
command.option(`--no-${flag}`);
|
|
29
|
+
} else if (type instanceof z.ZodNumber) command.option(`--${flag} <number>`, description, defaultValue);
|
|
30
|
+
else if (type instanceof z.ZodString || type instanceof z.ZodEnum) command.option(`--${flag} <string>`, description, defaultValue);
|
|
31
|
+
else if (type instanceof z.ZodObject || type instanceof z.ZodRecord || type instanceof z.ZodArray) command.option(`--${flag} <json>`, description ?? "", JSON.parse, defaultValue);
|
|
32
|
+
else throw new Error(`Invalid zod type: ${type.def.type}`);
|
|
33
|
+
}
|
|
34
|
+
function registerProxyCommand(program, client, tool, options) {
|
|
35
|
+
const name = kebabCase(tool.name);
|
|
36
|
+
const command = program.command(name);
|
|
37
|
+
if (tool.description) command.description(tool.description);
|
|
38
|
+
const schema = z.fromJSONSchema(tool.inputSchema);
|
|
39
|
+
if (!(schema instanceof z.ZodObject)) throw new Error("Invalid schema");
|
|
40
|
+
const shape = schema.shape;
|
|
41
|
+
for (const key of Object.keys(shape)) {
|
|
42
|
+
const [type, { defaultValue }] = unwrap(shape[key]);
|
|
43
|
+
addCliOption(command, key, type, defaultValue);
|
|
44
|
+
}
|
|
45
|
+
command.action(async (args) => {
|
|
46
|
+
const { silent } = program.opts();
|
|
47
|
+
const logger = createCLILogger();
|
|
48
|
+
if (!silent) {
|
|
49
|
+
intro(`${options.name} - ${tool.name}`);
|
|
50
|
+
client.setNotificationHandler(LoggingMessageNotificationSchema, ({ params: { level, data } }) => {
|
|
51
|
+
logger[level](data);
|
|
52
|
+
});
|
|
53
|
+
client.setNotificationHandler(ProgressNotificationSchema, ({ params: { progress, total, message } }) => {
|
|
54
|
+
logger.info({
|
|
55
|
+
progress,
|
|
56
|
+
total,
|
|
57
|
+
message
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const input = await schema.parseAsync(args);
|
|
62
|
+
const response = await client.callTool({
|
|
63
|
+
name: tool.name,
|
|
64
|
+
arguments: input,
|
|
65
|
+
_meta: { progressToken: randomUUID() }
|
|
66
|
+
});
|
|
67
|
+
process.stdout.write(JSON.stringify(response));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const cliProxy = ({ url }) => {
|
|
71
|
+
return (options, baseContext) => {
|
|
72
|
+
const context = baseContext.fork({ adapter: "cliProxy" });
|
|
73
|
+
const program = new Command().name(options.name).description(options.description).version(options.version).option("-s, --silent", "Silent mode, prevent log messages", false);
|
|
74
|
+
return {
|
|
75
|
+
context,
|
|
76
|
+
start: async () => {
|
|
77
|
+
const client = new Client({
|
|
78
|
+
name: options.name,
|
|
79
|
+
description: options.description,
|
|
80
|
+
version: options.version
|
|
81
|
+
});
|
|
82
|
+
const transport = new StreamableHTTPClientTransport(url);
|
|
83
|
+
await client.connect(transport);
|
|
84
|
+
const { tools } = await client.listTools();
|
|
85
|
+
for (const tool of tools) registerProxyCommand(program, client, tool, options);
|
|
86
|
+
await program.parseAsync();
|
|
87
|
+
await transport.close();
|
|
88
|
+
},
|
|
89
|
+
stop: async () => {}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/util/result.ts
|
|
95
|
+
function smartToolResult(data) {
|
|
96
|
+
const text = typeof data === "string" ? data : JSON.stringify(data);
|
|
97
|
+
const mimeType = typeof data === "string" ? "text/plain" : "application/json";
|
|
98
|
+
const ext = typeof data === "string" ? "txt" : "json";
|
|
99
|
+
if (text.length > 4096) {
|
|
100
|
+
const uri = `mcp://toolResult/${randomUUID$1()}.${ext}`;
|
|
101
|
+
const buffer = Buffer.from(text);
|
|
102
|
+
const blob = buffer.toString("base64");
|
|
103
|
+
return { content: [{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: `Received resource ${uri} with ${buffer.byteLength} bytes`
|
|
106
|
+
}, {
|
|
107
|
+
type: "resource",
|
|
108
|
+
resource: {
|
|
109
|
+
uri,
|
|
110
|
+
mimeType,
|
|
111
|
+
blob
|
|
112
|
+
}
|
|
113
|
+
}] };
|
|
114
|
+
} else return { content: [{
|
|
115
|
+
type: "text",
|
|
116
|
+
text
|
|
117
|
+
}] };
|
|
118
|
+
}
|
|
119
|
+
function jsonToolResult(data, isError = false) {
|
|
120
|
+
const result = { content: [{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: JSON.stringify(data)
|
|
123
|
+
}] };
|
|
124
|
+
if (isError) result.isError = true;
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
function errorToolResult({ code, name, message }) {
|
|
128
|
+
return {
|
|
129
|
+
isError: true,
|
|
130
|
+
content: [{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: JSON.stringify({
|
|
133
|
+
success: false,
|
|
134
|
+
code,
|
|
135
|
+
name,
|
|
136
|
+
message
|
|
137
|
+
})
|
|
138
|
+
}]
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function handleToolError(error) {
|
|
142
|
+
if (error instanceof SilkweaveError) return jsonToolResult({
|
|
143
|
+
success: false,
|
|
144
|
+
name: error.name,
|
|
145
|
+
message: error.message,
|
|
146
|
+
code: error.code
|
|
147
|
+
}, true);
|
|
148
|
+
else if (error instanceof Error) return jsonToolResult({
|
|
149
|
+
success: false,
|
|
150
|
+
name: error.name,
|
|
151
|
+
message: error.message,
|
|
152
|
+
stack: error.stack
|
|
153
|
+
}, true);
|
|
154
|
+
else return jsonToolResult({
|
|
155
|
+
success: false,
|
|
156
|
+
name: "Unknown error",
|
|
157
|
+
message: "An unknown error occurred",
|
|
158
|
+
error
|
|
159
|
+
}, true);
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/adapter/http.ts
|
|
163
|
+
const authStorage = new AsyncLocalStorage();
|
|
164
|
+
function mountOAuthRoutes(app, auth) {
|
|
165
|
+
const provider = auth.provider;
|
|
166
|
+
const callbackPath = auth.callbackPath ?? "/auth/callback";
|
|
167
|
+
const toOAuthReq = (req) => ({
|
|
168
|
+
method: req.method,
|
|
169
|
+
url: new URL(req.url, `${req.protocol}://${req.get("host")}`),
|
|
170
|
+
headers: Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),
|
|
171
|
+
body: req.body
|
|
172
|
+
});
|
|
173
|
+
const sendOAuth = (res, oauthRes) => {
|
|
174
|
+
for (const [key, value] of Object.entries(oauthRes.headers)) res.header(key, value);
|
|
175
|
+
if (oauthRes.body) res.status(oauthRes.status).send(typeof oauthRes.body === "string" ? oauthRes.body : JSON.stringify(oauthRes.body));
|
|
176
|
+
else res.status(oauthRes.status).end();
|
|
177
|
+
};
|
|
178
|
+
app.get("/.well-known/oauth-authorization-server", (_req, res) => {
|
|
179
|
+
sendOAuth(res, provider.metadata());
|
|
180
|
+
});
|
|
181
|
+
app.get("/authorize", async (req, res) => {
|
|
182
|
+
sendOAuth(res, await provider.authorize(toOAuthReq(req)));
|
|
183
|
+
});
|
|
184
|
+
app.get(callbackPath, async (req, res) => {
|
|
185
|
+
sendOAuth(res, await provider.callback(toOAuthReq(req)));
|
|
186
|
+
});
|
|
187
|
+
app.post("/token", express.urlencoded({ extended: false }), async (req, res) => {
|
|
188
|
+
sendOAuth(res, await provider.token(toOAuthReq(req)));
|
|
189
|
+
});
|
|
190
|
+
app.post("/register", express.json(), async (req, res) => {
|
|
191
|
+
sendOAuth(res, await provider.register(toOAuthReq(req)));
|
|
192
|
+
});
|
|
193
|
+
return new Set([
|
|
194
|
+
"/.well-known/oauth-authorization-server",
|
|
195
|
+
"/authorize",
|
|
196
|
+
callbackPath,
|
|
197
|
+
"/token",
|
|
198
|
+
"/register"
|
|
199
|
+
]);
|
|
200
|
+
}
|
|
201
|
+
function mountAuthMiddleware(app, auth, oauthPaths, context) {
|
|
202
|
+
app.use(async (req, res, next) => {
|
|
203
|
+
if (req.path.startsWith("/.well-known/") || oauthPaths.has(req.path)) return next();
|
|
204
|
+
const result = await validateToken(req.headers.authorization, auth, context.fork({ request: req }));
|
|
205
|
+
if (result.error) {
|
|
206
|
+
for (const [key, value] of Object.entries(result.error.headers)) res.header(key, value);
|
|
207
|
+
res.status(result.error.statusCode).json(result.error.body);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (result.auth) authStorage.run(result.auth, () => {
|
|
211
|
+
next();
|
|
212
|
+
});
|
|
213
|
+
else next();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function registerTools$1(server, actions, context) {
|
|
217
|
+
for (const action of actions) server.registerTool(pascalCase(action.name), {
|
|
218
|
+
title: capitalCase(action.name),
|
|
219
|
+
description: action.description,
|
|
220
|
+
inputSchema: action.input
|
|
221
|
+
}, async (input, extra) => {
|
|
222
|
+
const logger = createLogger({
|
|
223
|
+
stream: process.stderr,
|
|
224
|
+
onLog: (level, data) => {
|
|
225
|
+
extra.sendNotification({
|
|
226
|
+
method: "notifications/message",
|
|
227
|
+
params: {
|
|
228
|
+
level,
|
|
229
|
+
data
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
onProgress: ({ progress, total, message }) => {
|
|
234
|
+
if (!extra._meta?.progressToken) return;
|
|
235
|
+
extra.sendNotification({
|
|
236
|
+
method: "notifications/progress",
|
|
237
|
+
params: {
|
|
238
|
+
progress,
|
|
239
|
+
total,
|
|
240
|
+
message,
|
|
241
|
+
progressToken: extra._meta.progressToken
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
const currentAuth = authStorage.getStore();
|
|
247
|
+
const actionContext = context.fork({
|
|
248
|
+
logger,
|
|
249
|
+
extra,
|
|
250
|
+
...currentAuth ? { auth: currentAuth } : {}
|
|
251
|
+
});
|
|
252
|
+
return action.run(input, actionContext).then((result) => {
|
|
253
|
+
return action.toolResult?.(result, actionContext) ?? smartToolResult(result);
|
|
254
|
+
}).catch(handleToolError);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function createMcpServer(options, actions, context) {
|
|
258
|
+
const server = new McpServer({
|
|
259
|
+
name: options.name,
|
|
260
|
+
description: options.description,
|
|
261
|
+
version: options.version
|
|
262
|
+
}, { capabilities: {
|
|
263
|
+
tools: {},
|
|
264
|
+
logging: {}
|
|
265
|
+
} });
|
|
266
|
+
registerTools$1(server, actions, context);
|
|
267
|
+
return server;
|
|
268
|
+
}
|
|
269
|
+
function createSessionTransport(transports) {
|
|
270
|
+
const transport = new StreamableHTTPServerTransport({
|
|
271
|
+
sessionIdGenerator: () => randomUUID(),
|
|
272
|
+
enableJsonResponse: false,
|
|
273
|
+
eventStore: new InMemoryEventStore(),
|
|
274
|
+
onsessioninitialized: (sId) => {
|
|
275
|
+
console.log(`Session initialized with ID: ${sId}`);
|
|
276
|
+
transports[sId] = transport;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
transport.onerror = (error) => {
|
|
280
|
+
console.error(error);
|
|
281
|
+
};
|
|
282
|
+
transport.onclose = () => {
|
|
283
|
+
const sid = transport.sessionId;
|
|
284
|
+
if (sid && transports[sid]) {
|
|
285
|
+
console.log(`Transport closed for session ${sid}, removing from transports map`);
|
|
286
|
+
delete transports[sid];
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
return transport;
|
|
290
|
+
}
|
|
291
|
+
function mountMcpTransport(app, transports, createServer) {
|
|
292
|
+
app.post("/mcp", async (req, res) => {
|
|
293
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
294
|
+
try {
|
|
295
|
+
if (sessionId && transports[sessionId]) {
|
|
296
|
+
await transports[sessionId].handleRequest(req, res, req.body);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!isInitializeRequest(req.body)) {
|
|
300
|
+
res.status(404).json({
|
|
301
|
+
jsonrpc: "2.0",
|
|
302
|
+
error: {
|
|
303
|
+
code: -32e3,
|
|
304
|
+
message: "Session not found"
|
|
305
|
+
},
|
|
306
|
+
id: null
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const transport = createSessionTransport(transports);
|
|
311
|
+
await createServer().connect(transport);
|
|
312
|
+
await transport.handleRequest(req, res, req.body);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error("Error handling MCP request:", error);
|
|
315
|
+
if (!res.headersSent) res.status(500).json({
|
|
316
|
+
jsonrpc: "2.0",
|
|
317
|
+
error: {
|
|
318
|
+
code: -32603,
|
|
319
|
+
message: "Internal server error"
|
|
320
|
+
},
|
|
321
|
+
id: null
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
const handleSessionStream = async (req, res) => {
|
|
326
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
327
|
+
if (!sessionId || !transports[sessionId]) {
|
|
328
|
+
res.status(400).send("Invalid or missing session ID");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
await transports[sessionId].handleRequest(req, res);
|
|
332
|
+
};
|
|
333
|
+
app.get("/mcp", handleSessionStream);
|
|
334
|
+
app.delete("/mcp", handleSessionStream);
|
|
335
|
+
app.get("/mcp/resource/:id", handleSessionStream);
|
|
336
|
+
}
|
|
337
|
+
const http = ({ host, port, auth, ...mcpOptions }) => {
|
|
338
|
+
return (options, baseContext) => {
|
|
339
|
+
const context = baseContext.fork({ adapter: "http" });
|
|
340
|
+
const app = createMcpExpressApp({
|
|
341
|
+
...mcpOptions,
|
|
342
|
+
host
|
|
343
|
+
});
|
|
344
|
+
app.use(cors({
|
|
345
|
+
exposedHeaders: [
|
|
346
|
+
"WWW-Authenticate",
|
|
347
|
+
"Mcp-Session-Id",
|
|
348
|
+
"Last-Event-Id",
|
|
349
|
+
"Mcp-Protocol-Version"
|
|
350
|
+
],
|
|
351
|
+
origin: "*"
|
|
352
|
+
}));
|
|
353
|
+
if (auth?.authorizationServers?.length && auth.resourceUrl) app.get("/.well-known/oauth-protected-resource", (_req, res) => {
|
|
354
|
+
const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers);
|
|
355
|
+
res.json(metadata);
|
|
356
|
+
});
|
|
357
|
+
const oauthPaths = auth?.provider ? mountOAuthRoutes(app, auth) : /* @__PURE__ */ new Set();
|
|
358
|
+
if (auth) mountAuthMiddleware(app, auth, oauthPaths, context);
|
|
359
|
+
const transports = {};
|
|
360
|
+
let mountedActions = [];
|
|
361
|
+
app.get("/resource/:id", async (req, res) => {
|
|
362
|
+
const id = req.params.id;
|
|
363
|
+
if (!id || typeof id !== "string") throw new Error("Invalid ID");
|
|
364
|
+
const resourceMeta = JSON.parse(await readFile(`resources/${id}.json`, "utf-8"));
|
|
365
|
+
const buffer = await readFile(`resources/${id}`);
|
|
366
|
+
res.status(200);
|
|
367
|
+
res.header("Content-Type", resourceMeta.contentType);
|
|
368
|
+
res.send(buffer);
|
|
369
|
+
});
|
|
370
|
+
mountMcpTransport(app, transports, () => createMcpServer(options, mountedActions, context));
|
|
371
|
+
let httpServer;
|
|
372
|
+
return {
|
|
373
|
+
context,
|
|
374
|
+
start: async (actions) => {
|
|
375
|
+
mountedActions = actions;
|
|
376
|
+
httpServer = app.listen(port, host, (error) => {
|
|
377
|
+
if (error) {
|
|
378
|
+
console.error("Failed to start server:", error);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
console.log(`MCP Streamable HTTP Server listening on http://${host}:${port}/mcp`);
|
|
382
|
+
});
|
|
383
|
+
},
|
|
384
|
+
stop: async () => {
|
|
385
|
+
if (httpServer) await new Promise((resolve, reject) => {
|
|
386
|
+
return httpServer.close((err) => {
|
|
387
|
+
if (err) reject(err);
|
|
388
|
+
else resolve();
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
httpServer = void 0;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/adapter/stdio.ts
|
|
398
|
+
function registerTools(server, actions, context) {
|
|
399
|
+
for (const action of actions) server.registerTool(pascalCase(action.name), {
|
|
400
|
+
title: capitalCase(action.name),
|
|
401
|
+
description: action.description,
|
|
402
|
+
inputSchema: action.input
|
|
403
|
+
}, async (input, extra) => {
|
|
404
|
+
const logger = createLogger({
|
|
405
|
+
stream: false,
|
|
406
|
+
onLog: (level, data) => {
|
|
407
|
+
extra.sendNotification({
|
|
408
|
+
method: "notifications/message",
|
|
409
|
+
params: {
|
|
410
|
+
level,
|
|
411
|
+
data
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
},
|
|
415
|
+
onProgress: ({ progress, total, message }) => {
|
|
416
|
+
if (!extra._meta?.progressToken) return;
|
|
417
|
+
extra.sendNotification({
|
|
418
|
+
method: "notifications/progress",
|
|
419
|
+
params: {
|
|
420
|
+
progress,
|
|
421
|
+
total,
|
|
422
|
+
message,
|
|
423
|
+
progressToken: extra._meta.progressToken
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
const actionContext = context.fork({
|
|
429
|
+
logger,
|
|
430
|
+
extra
|
|
431
|
+
});
|
|
432
|
+
return action.run(input, actionContext).then((result) => action.toolResult?.(result, actionContext) ?? smartToolResult(result)).catch(handleToolError);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
const stdio = () => {
|
|
436
|
+
return (options, baseContext) => {
|
|
437
|
+
const context = baseContext.fork({ adapter: "stdio" });
|
|
438
|
+
const server = new McpServer({
|
|
439
|
+
name: options.name,
|
|
440
|
+
description: options.description,
|
|
441
|
+
version: options.version
|
|
442
|
+
}, { capabilities: {
|
|
443
|
+
tools: {},
|
|
444
|
+
logging: {}
|
|
445
|
+
} });
|
|
446
|
+
return {
|
|
447
|
+
context,
|
|
448
|
+
start: async (actions) => {
|
|
449
|
+
registerTools(server, actions, context);
|
|
450
|
+
const transport = new StdioServerTransport();
|
|
451
|
+
await server.connect(transport);
|
|
452
|
+
},
|
|
453
|
+
stop: async () => {
|
|
454
|
+
await server?.close();
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/util/sideload.ts
|
|
461
|
+
async function createSideloadResource(buffer, { name, contentType }) {
|
|
462
|
+
const id = randomUUID();
|
|
463
|
+
const resource = {
|
|
464
|
+
id,
|
|
465
|
+
name,
|
|
466
|
+
contentType,
|
|
467
|
+
size: buffer.length
|
|
468
|
+
};
|
|
469
|
+
await Promise.all([writeFile(`resources/${id}`, buffer), writeFile(`resources/${id}.json`, JSON.stringify(resource), "utf-8")]);
|
|
470
|
+
return resource;
|
|
471
|
+
}
|
|
472
|
+
//#endregion
|
|
473
|
+
export { cliProxy, createSideloadResource, errorToolResult, handleToolError, http, jsonToolResult, smartToolResult, stdio };
|
|
474
|
+
|
|
475
|
+
//# sourceMappingURL=index.mjs.map
|