@fragno-dev/mcp-fragment 0.0.0-stage → 0.0.3

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.
Files changed (90) hide show
  1. package/LICENSE.md +16 -0
  2. package/README.md +312 -2
  3. package/bin/run.js +7 -0
  4. package/dist/browser/client/react.d.ts +253 -0
  5. package/dist/browser/client/react.d.ts.map +1 -0
  6. package/dist/browser/client/react.js +194 -0
  7. package/dist/browser/client/react.js.map +1 -0
  8. package/dist/browser/client/solid.d.ts +256 -0
  9. package/dist/browser/client/solid.d.ts.map +1 -0
  10. package/dist/browser/client/solid.js +129 -0
  11. package/dist/browser/client/solid.js.map +1 -0
  12. package/dist/browser/client/svelte.d.ts +256 -0
  13. package/dist/browser/client/svelte.d.ts.map +1 -0
  14. package/dist/browser/client/svelte.js +131 -0
  15. package/dist/browser/client/svelte.js.map +1 -0
  16. package/dist/browser/client/vanilla.d.ts +283 -0
  17. package/dist/browser/client/vanilla.d.ts.map +1 -0
  18. package/dist/browser/client/vanilla.js +159 -0
  19. package/dist/browser/client/vanilla.js.map +1 -0
  20. package/dist/browser/client/vue.d.ts +256 -0
  21. package/dist/browser/client/vue.d.ts.map +1 -0
  22. package/dist/browser/client/vue.js +127 -0
  23. package/dist/browser/client/vue.js.map +1 -0
  24. package/dist/browser/client-BCzNDSlW.d.ts +711 -0
  25. package/dist/browser/client-BCzNDSlW.d.ts.map +1 -0
  26. package/dist/browser/index.d.ts +813 -0
  27. package/dist/browser/index.d.ts.map +1 -0
  28. package/dist/browser/index.js +1 -0
  29. package/dist/browser/src-B6dHZXqB.js +2303 -0
  30. package/dist/browser/src-B6dHZXqB.js.map +1 -0
  31. package/dist/cli/cli.d.ts +6 -0
  32. package/dist/cli/cli.d.ts.map +1 -0
  33. package/dist/cli/cli.js +227 -0
  34. package/dist/cli/cli.js.map +1 -0
  35. package/dist/cli/definition.js +330 -0
  36. package/dist/cli/definition.js.map +1 -0
  37. package/dist/cli/diagnostics.js +93 -0
  38. package/dist/cli/diagnostics.js.map +1 -0
  39. package/dist/cli/index.js +13 -0
  40. package/dist/cli/index.js.map +1 -0
  41. package/dist/cli/mcp-api.js +87 -0
  42. package/dist/cli/mcp-api.js.map +1 -0
  43. package/dist/cli/mcp-types.js +100 -0
  44. package/dist/cli/mcp-types.js.map +1 -0
  45. package/dist/cli/oauth-provider.js +113 -0
  46. package/dist/cli/oauth-provider.js.map +1 -0
  47. package/dist/cli/routes.js +743 -0
  48. package/dist/cli/routes.js.map +1 -0
  49. package/dist/cli/schema.js +13 -0
  50. package/dist/cli/schema.js.map +1 -0
  51. package/dist/cli/services.js +330 -0
  52. package/dist/cli/services.js.map +1 -0
  53. package/dist/node/definition.d.ts +260 -0
  54. package/dist/node/definition.d.ts.map +1 -0
  55. package/dist/node/definition.js +330 -0
  56. package/dist/node/definition.js.map +1 -0
  57. package/dist/node/diagnostics.js +93 -0
  58. package/dist/node/diagnostics.js.map +1 -0
  59. package/dist/node/index.d.ts +694 -0
  60. package/dist/node/index.d.ts.map +1 -0
  61. package/dist/node/index.js +26 -0
  62. package/dist/node/index.js.map +1 -0
  63. package/dist/node/mcp-api.d.ts +2 -0
  64. package/dist/node/mcp-api.js +87 -0
  65. package/dist/node/mcp-api.js.map +1 -0
  66. package/dist/node/mcp-types.d.ts +146 -0
  67. package/dist/node/mcp-types.d.ts.map +1 -0
  68. package/dist/node/mcp-types.js +100 -0
  69. package/dist/node/mcp-types.js.map +1 -0
  70. package/dist/node/oauth-provider.d.ts +24 -0
  71. package/dist/node/oauth-provider.d.ts.map +1 -0
  72. package/dist/node/oauth-provider.js +113 -0
  73. package/dist/node/oauth-provider.js.map +1 -0
  74. package/dist/node/routes.d.ts +466 -0
  75. package/dist/node/routes.d.ts.map +1 -0
  76. package/dist/node/routes.js +743 -0
  77. package/dist/node/routes.js.map +1 -0
  78. package/dist/node/schema.d.ts +9 -0
  79. package/dist/node/schema.d.ts.map +1 -0
  80. package/dist/node/schema.js +13 -0
  81. package/dist/node/schema.js.map +1 -0
  82. package/dist/node/services.d.ts +40 -0
  83. package/dist/node/services.d.ts.map +1 -0
  84. package/dist/node/services.js +330 -0
  85. package/dist/node/services.js.map +1 -0
  86. package/dist/node/testing/mcp-test-server.d.ts +42 -0
  87. package/dist/node/testing/mcp-test-server.d.ts.map +1 -0
  88. package/dist/node/testing/mcp-test-server.js +218 -0
  89. package/dist/node/testing/mcp-test-server.js.map +1 -0
  90. package/package.json +130 -3
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env node
2
+ import { MCP_OAUTH_REDIRECT_URI_QUERY_PARAMETER } from "./mcp-types.js";
3
+ import { createMcpFragment } from "./index.js";
4
+ import { spawn } from "node:child_process";
5
+ import { mkdir } from "node:fs/promises";
6
+ import { createServer } from "node:http";
7
+ import { homedir } from "node:os";
8
+ import path from "node:path";
9
+ import { migrate } from "@fragno-dev/db";
10
+ import { toNodeHandler } from "@fragno-dev/node";
11
+ //#region src/cli/cli.ts
12
+ const DEFAULT_PORT = 3927;
13
+ const DEFAULT_HOST = "127.0.0.1";
14
+ const DEFAULT_MOUNT_ROUTE = "/api/mcp-fragment";
15
+ const USAGE = `fragno-mcp <command> [options]
16
+
17
+ Commands:
18
+ serve Run the local MCP fragment server
19
+ add <slug> <endpoint-url> Register a remote MCP server
20
+ oauth <slug> Start OAuth and open the authorization URL
21
+ status <slug> Show auth status
22
+ tools <slug> List remote MCP tools
23
+
24
+ Options:
25
+ --base-url <url> Local fragment base URL (default: http://127.0.0.1:3927)
26
+ --port <port> Server port for serve/oauth (default: 3927)
27
+ --host <host> Server host for serve (default: 127.0.0.1)
28
+ --data-dir <path> SQLite data directory (default: ~/.fragno/mcp-fragment)
29
+ --mount-route <path> Fragment mount route (default: /api/mcp-fragment)
30
+ --scope <scope> OAuth scope
31
+ --client-id <id> OAuth client id
32
+ --client-secret <secret> OAuth client secret
33
+ --json Print JSON
34
+ --no-open Do not open the browser for oauth
35
+ -h, --help Show help`;
36
+ function parse(argv) {
37
+ const args = [];
38
+ const options = {};
39
+ for (let i = 0; i < argv.length; i += 1) {
40
+ const arg = argv[i];
41
+ if (arg === "--") {
42
+ args.push(...argv.slice(i + 1));
43
+ break;
44
+ }
45
+ if (arg === "--help" || arg === "-h") {
46
+ options["help"] = true;
47
+ continue;
48
+ }
49
+ if (arg.startsWith("--")) {
50
+ const [rawKey, rawValue] = arg.split("=", 2);
51
+ const key = rawKey.slice(2);
52
+ if (key === "json" || key === "no-open") {
53
+ options[key] = rawValue === void 0 ? true : rawValue !== "false";
54
+ continue;
55
+ }
56
+ const value = rawValue ?? argv[i + 1];
57
+ if (rawValue === void 0) i += 1;
58
+ options[key] = value ?? "";
59
+ continue;
60
+ }
61
+ args.push(arg);
62
+ }
63
+ const [command, ...rest] = args;
64
+ return {
65
+ command,
66
+ args: rest,
67
+ options
68
+ };
69
+ }
70
+ function optionString(options, key) {
71
+ const value = options[key];
72
+ return typeof value === "string" && value.length > 0 ? value : void 0;
73
+ }
74
+ function optionBoolean(options, key) {
75
+ return options[key] === true;
76
+ }
77
+ function localBaseUrl(options) {
78
+ return optionString(options, "base-url") ?? `http://${DEFAULT_HOST}:${optionString(options, "port") ?? DEFAULT_PORT}`;
79
+ }
80
+ function mountRoute(options) {
81
+ return optionString(options, "mount-route") ?? DEFAULT_MOUNT_ROUTE;
82
+ }
83
+ function dataDir(options) {
84
+ return optionString(options, "data-dir") ?? path.join(homedir(), ".fragno", "mcp-fragment");
85
+ }
86
+ function fragmentUrl(options, route) {
87
+ return `${localBaseUrl(options).replace(/\/$/, "")}${mountRoute(options)}${route}`;
88
+ }
89
+ async function requestJson(method, url, body) {
90
+ const response = await fetch(url, {
91
+ method,
92
+ headers: body === void 0 ? void 0 : { "content-type": "application/json" },
93
+ body: body === void 0 ? void 0 : JSON.stringify(body)
94
+ });
95
+ const text = await response.text();
96
+ const payload = text ? JSON.parse(text) : void 0;
97
+ if (!response.ok) {
98
+ const message = payload && typeof payload === "object" && "message" in payload ? String(payload.message) : text || `HTTP ${response.status}`;
99
+ throw new Error(message);
100
+ }
101
+ return payload;
102
+ }
103
+ async function startServer(options, logger) {
104
+ const dir = dataDir(options);
105
+ await mkdir(dir, { recursive: true });
106
+ process.env["FRAGNO_DATA_DIR"] = dir;
107
+ const baseUrl = localBaseUrl(options).replace(/\/$/, "");
108
+ const fragmentMountRoute = mountRoute(options);
109
+ const oauthRedirectUri = `${baseUrl}${fragmentMountRoute}/oauth/callback`;
110
+ const fragment = createMcpFragment({ allowedOAuthRedirectUris: (url) => url.toString() === oauthRedirectUri }, {
111
+ mountRoute: fragmentMountRoute,
112
+ databaseNamespace: "mcp-fragment"
113
+ });
114
+ logger.log("migrating sqlite database if needed...");
115
+ await migrate(fragment);
116
+ const handler = toNodeHandler(fragment.handler.bind(fragment));
117
+ const server = createServer((req, res) => {
118
+ if (req.url?.startsWith(fragment.mountRoute)) {
119
+ handler(req, res);
120
+ return;
121
+ }
122
+ res.writeHead(404, { "content-type": "text/plain" }).end("Not found");
123
+ });
124
+ const port = Number(optionString(options, "port") ?? DEFAULT_PORT);
125
+ const host = optionString(options, "host") ?? DEFAULT_HOST;
126
+ await new Promise((resolve, reject) => {
127
+ server.once("error", reject);
128
+ server.listen(port, host, () => {
129
+ server.off("error", reject);
130
+ resolve();
131
+ });
132
+ });
133
+ logger.log(`fragno-mcp listening at ${baseUrl}${fragment.mountRoute}`);
134
+ logger.log(`sqlite data dir: ${dir}`);
135
+ return server;
136
+ }
137
+ function openBrowser(url) {
138
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
139
+ const args = process.platform === "win32" ? [
140
+ "/c",
141
+ "start",
142
+ "",
143
+ url
144
+ ] : [url];
145
+ spawn(command, args, {
146
+ stdio: "ignore",
147
+ detached: true
148
+ }).unref();
149
+ }
150
+ function print(value, json, logger) {
151
+ if (json) {
152
+ logger.log(JSON.stringify(value, null, 2));
153
+ return;
154
+ }
155
+ if (value && typeof value === "object" && "tools" in value && Array.isArray(value.tools)) {
156
+ for (const tool of value.tools) {
157
+ const record = tool && typeof tool === "object" ? tool : {};
158
+ logger.log(`${String(record["name"] ?? "(unnamed)")}\t${String(record["description"] ?? "")}`);
159
+ }
160
+ return;
161
+ }
162
+ logger.log(JSON.stringify(value, null, 2));
163
+ }
164
+ async function run(argv, logger = console) {
165
+ const parsed = parse(argv.slice(2));
166
+ if (optionBoolean(parsed.options, "help") || !parsed.command) {
167
+ logger.log(USAGE);
168
+ return parsed.command ? 0 : 1;
169
+ }
170
+ try {
171
+ switch (parsed.command) {
172
+ case "serve":
173
+ await startServer(parsed.options, logger);
174
+ await new Promise(() => void 0);
175
+ return 0;
176
+ case "add": {
177
+ const [slug, endpointUrl] = parsed.args;
178
+ if (!slug || !endpointUrl) throw new Error("Usage: fragno-mcp add <slug> <endpoint-url>");
179
+ print(await requestJson("POST", fragmentUrl(parsed.options, "/servers"), {
180
+ slug,
181
+ endpointUrl,
182
+ auth: { type: "none" }
183
+ }), optionBoolean(parsed.options, "json"), logger);
184
+ return 0;
185
+ }
186
+ case "oauth": {
187
+ const [slug] = parsed.args;
188
+ if (!slug) throw new Error("Usage: fragno-mcp oauth <slug>");
189
+ const startUrl = new URL(fragmentUrl(parsed.options, `/servers/${encodeURIComponent(slug)}/auth/start`));
190
+ startUrl.searchParams.set(MCP_OAUTH_REDIRECT_URI_QUERY_PARAMETER, fragmentUrl(parsed.options, "/oauth/callback"));
191
+ const result = await requestJson("POST", startUrl.toString(), {
192
+ scope: optionString(parsed.options, "scope"),
193
+ clientId: optionString(parsed.options, "client-id"),
194
+ clientSecret: optionString(parsed.options, "client-secret")
195
+ });
196
+ logger.log(`Open this URL to authorize:\n${result.authorizationUrl}`);
197
+ if (!optionBoolean(parsed.options, "no-open")) openBrowser(result.authorizationUrl);
198
+ logger.log("Keep `fragno-mcp serve` running until the browser redirects back to the local callback.");
199
+ return 0;
200
+ }
201
+ case "status": {
202
+ const [slug] = parsed.args;
203
+ if (!slug) throw new Error("Usage: fragno-mcp status <slug>");
204
+ print(await requestJson("GET", fragmentUrl(parsed.options, `/servers/${encodeURIComponent(slug)}/auth/status`)), optionBoolean(parsed.options, "json"), logger);
205
+ return 0;
206
+ }
207
+ case "tools": {
208
+ const [slug] = parsed.args;
209
+ if (!slug) throw new Error("Usage: fragno-mcp tools <slug>");
210
+ print(await requestJson("GET", fragmentUrl(parsed.options, `/servers/${encodeURIComponent(slug)}/tools`)), optionBoolean(parsed.options, "json"), logger);
211
+ return 0;
212
+ }
213
+ default: throw new Error(`Unknown command: ${parsed.command}`);
214
+ }
215
+ } catch (error) {
216
+ logger.error(`${error instanceof Error ? error.message : String(error)}\n\n${USAGE}`);
217
+ return 1;
218
+ }
219
+ }
220
+ if (process.argv[1] ? import.meta.url === new URL(process.argv[1], "file:").href : false) {
221
+ const exitCode = await run(process.argv);
222
+ if (typeof exitCode === "number") process.exitCode = exitCode;
223
+ }
224
+ //#endregion
225
+ export { run };
226
+
227
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../../src/cli/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { spawn } from \"node:child_process\";\nimport { mkdir } from \"node:fs/promises\";\nimport { createServer, type Server } from \"node:http\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nimport { migrate } from \"@fragno-dev/db\";\nimport { toNodeHandler } from \"@fragno-dev/node\";\n\nimport { createMcpFragment } from \"../index\";\nimport { MCP_OAUTH_REDIRECT_URI_QUERY_PARAMETER } from \"../mcp-types\";\n\nconst DEFAULT_PORT = 3927;\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst DEFAULT_MOUNT_ROUTE = \"/api/mcp-fragment\";\n\nconst USAGE = `fragno-mcp <command> [options]\n\nCommands:\n serve Run the local MCP fragment server\n add <slug> <endpoint-url> Register a remote MCP server\n oauth <slug> Start OAuth and open the authorization URL\n status <slug> Show auth status\n tools <slug> List remote MCP tools\n\nOptions:\n --base-url <url> Local fragment base URL (default: http://127.0.0.1:3927)\n --port <port> Server port for serve/oauth (default: 3927)\n --host <host> Server host for serve (default: 127.0.0.1)\n --data-dir <path> SQLite data directory (default: ~/.fragno/mcp-fragment)\n --mount-route <path> Fragment mount route (default: /api/mcp-fragment)\n --scope <scope> OAuth scope\n --client-id <id> OAuth client id\n --client-secret <secret> OAuth client secret\n --json Print JSON\n --no-open Do not open the browser for oauth\n -h, --help Show help`;\n\ntype Logger = Pick<Console, \"log\" | \"error\">;\n\ntype Parsed = {\n command?: string;\n args: string[];\n options: Record<string, string | boolean>;\n};\n\nfunction parse(argv: string[]): Parsed {\n const args: string[] = [];\n const options: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === \"--\") {\n args.push(...argv.slice(i + 1));\n break;\n }\n if (arg === \"--help\" || arg === \"-h\") {\n options[\"help\"] = true;\n continue;\n }\n if (arg.startsWith(\"--\")) {\n const [rawKey, rawValue] = arg.split(\"=\", 2);\n const key = rawKey.slice(2);\n if (key === \"json\" || key === \"no-open\") {\n options[key] = rawValue === undefined ? true : rawValue !== \"false\";\n continue;\n }\n const value = rawValue ?? argv[i + 1];\n if (rawValue === undefined) {\n i += 1;\n }\n options[key] = value ?? \"\";\n continue;\n }\n args.push(arg);\n }\n const [command, ...rest] = args;\n return { command, args: rest, options };\n}\n\nfunction optionString(options: Parsed[\"options\"], key: string): string | undefined {\n const value = options[key];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction optionBoolean(options: Parsed[\"options\"], key: string): boolean {\n return options[key] === true;\n}\n\nfunction localBaseUrl(options: Parsed[\"options\"]): string {\n return (\n optionString(options, \"base-url\") ??\n `http://${DEFAULT_HOST}:${optionString(options, \"port\") ?? DEFAULT_PORT}`\n );\n}\n\nfunction mountRoute(options: Parsed[\"options\"]): string {\n return optionString(options, \"mount-route\") ?? DEFAULT_MOUNT_ROUTE;\n}\n\nfunction dataDir(options: Parsed[\"options\"]): string {\n return optionString(options, \"data-dir\") ?? path.join(homedir(), \".fragno\", \"mcp-fragment\");\n}\n\nfunction fragmentUrl(options: Parsed[\"options\"], route: string): string {\n return `${localBaseUrl(options).replace(/\\/$/, \"\")}${mountRoute(options)}${route}`;\n}\n\nasync function requestJson(method: string, url: string, body?: unknown): Promise<unknown> {\n const response = await fetch(url, {\n method,\n headers: body === undefined ? undefined : { \"content-type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n const text = await response.text();\n const payload = text ? JSON.parse(text) : undefined;\n if (!response.ok) {\n const message =\n payload && typeof payload === \"object\" && \"message\" in payload\n ? String((payload as { message: unknown }).message)\n : text || `HTTP ${response.status}`;\n throw new Error(message);\n }\n return payload;\n}\n\nasync function startServer(options: Parsed[\"options\"], logger: Logger): Promise<Server> {\n const dir = dataDir(options);\n await mkdir(dir, { recursive: true });\n process.env[\"FRAGNO_DATA_DIR\"] = dir;\n\n const baseUrl = localBaseUrl(options).replace(/\\/$/, \"\");\n const fragmentMountRoute = mountRoute(options);\n const oauthRedirectUri = `${baseUrl}${fragmentMountRoute}/oauth/callback`;\n const fragment = createMcpFragment(\n {\n allowedOAuthRedirectUris: (url) => url.toString() === oauthRedirectUri,\n },\n { mountRoute: fragmentMountRoute, databaseNamespace: \"mcp-fragment\" },\n );\n logger.log(\"migrating sqlite database if needed...\");\n await migrate(fragment);\n const handler = toNodeHandler(fragment.handler.bind(fragment));\n const server = createServer((req, res) => {\n if (req.url?.startsWith(fragment.mountRoute)) {\n handler(req, res);\n return;\n }\n res.writeHead(404, { \"content-type\": \"text/plain\" }).end(\"Not found\");\n });\n const port = Number(optionString(options, \"port\") ?? DEFAULT_PORT);\n const host = optionString(options, \"host\") ?? DEFAULT_HOST;\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, host, () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n logger.log(`fragno-mcp listening at ${baseUrl}${fragment.mountRoute}`);\n logger.log(`sqlite data dir: ${dir}`);\n return server;\n}\n\nfunction openBrowser(url: string) {\n const command =\n process.platform === \"darwin\" ? \"open\" : process.platform === \"win32\" ? \"cmd\" : \"xdg-open\";\n const args = process.platform === \"win32\" ? [\"/c\", \"start\", \"\", url] : [url];\n spawn(command, args, { stdio: \"ignore\", detached: true }).unref();\n}\n\nfunction print(value: unknown, json: boolean, logger: Logger) {\n if (json) {\n logger.log(JSON.stringify(value, null, 2));\n return;\n }\n if (\n value &&\n typeof value === \"object\" &&\n \"tools\" in value &&\n Array.isArray((value as { tools: unknown[] }).tools)\n ) {\n for (const tool of (value as { tools: unknown[] }).tools) {\n const record = tool && typeof tool === \"object\" ? (tool as Record<string, unknown>) : {};\n logger.log(\n `${String(record[\"name\"] ?? \"(unnamed)\")}\\t${String(record[\"description\"] ?? \"\")}`,\n );\n }\n return;\n }\n logger.log(JSON.stringify(value, null, 2));\n}\n\nexport async function run(argv: string[], logger: Logger = console): Promise<number> {\n const parsed = parse(argv.slice(2));\n if (optionBoolean(parsed.options, \"help\") || !parsed.command) {\n logger.log(USAGE);\n return parsed.command ? 0 : 1;\n }\n\n try {\n switch (parsed.command) {\n case \"serve\": {\n await startServer(parsed.options, logger);\n await new Promise(() => undefined);\n return 0;\n }\n case \"add\": {\n const [slug, endpointUrl] = parsed.args;\n if (!slug || !endpointUrl) {\n throw new Error(\"Usage: fragno-mcp add <slug> <endpoint-url>\");\n }\n const result = await requestJson(\"POST\", fragmentUrl(parsed.options, \"/servers\"), {\n slug,\n endpointUrl,\n auth: { type: \"none\" },\n });\n print(result, optionBoolean(parsed.options, \"json\"), logger);\n return 0;\n }\n case \"oauth\": {\n const [slug] = parsed.args;\n if (!slug) {\n throw new Error(\"Usage: fragno-mcp oauth <slug>\");\n }\n const startUrl = new URL(\n fragmentUrl(parsed.options, `/servers/${encodeURIComponent(slug)}/auth/start`),\n );\n startUrl.searchParams.set(\n MCP_OAUTH_REDIRECT_URI_QUERY_PARAMETER,\n fragmentUrl(parsed.options, \"/oauth/callback\"),\n );\n const result = (await requestJson(\"POST\", startUrl.toString(), {\n scope: optionString(parsed.options, \"scope\"),\n clientId: optionString(parsed.options, \"client-id\"),\n clientSecret: optionString(parsed.options, \"client-secret\"),\n })) as { authorizationUrl: string };\n logger.log(`Open this URL to authorize:\\n${result.authorizationUrl}`);\n if (!optionBoolean(parsed.options, \"no-open\")) {\n openBrowser(result.authorizationUrl);\n }\n logger.log(\n \"Keep `fragno-mcp serve` running until the browser redirects back to the local callback.\",\n );\n return 0;\n }\n case \"status\": {\n const [slug] = parsed.args;\n if (!slug) {\n throw new Error(\"Usage: fragno-mcp status <slug>\");\n }\n print(\n await requestJson(\n \"GET\",\n fragmentUrl(parsed.options, `/servers/${encodeURIComponent(slug)}/auth/status`),\n ),\n optionBoolean(parsed.options, \"json\"),\n logger,\n );\n return 0;\n }\n case \"tools\": {\n const [slug] = parsed.args;\n if (!slug) {\n throw new Error(\"Usage: fragno-mcp tools <slug>\");\n }\n print(\n await requestJson(\n \"GET\",\n fragmentUrl(parsed.options, `/servers/${encodeURIComponent(slug)}/tools`),\n ),\n optionBoolean(parsed.options, \"json\"),\n logger,\n );\n return 0;\n }\n default:\n throw new Error(`Unknown command: ${parsed.command}`);\n }\n } catch (error) {\n logger.error(`${error instanceof Error ? error.message : String(error)}\\n\\n${USAGE}`);\n return 1;\n }\n}\n\nconst isDirectCliRun = process.argv[1]\n ? import.meta.url === new URL(process.argv[1], \"file:\").href\n : false;\n\nif (isDirectCliRun) {\n const exitCode = await run(process.argv);\n if (typeof exitCode === \"number\") {\n process.exitCode = exitCode;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,330 @@
1
+ import { listMcpTools } from "./mcp-api.js";
2
+ import { mcpSchema } from "./schema.js";
3
+ import { createMcpOperationAuth, createOAuthCallbackSnapshot, createOAuthStartSnapshot, isMcpAuthReady, mcpAuthReadyUntil, resolveMcpOperationAuth, resolveOAuthStartClient, withoutOAuthConsent, writeOAuthSecretChanges } from "./services.js";
4
+ import { withDatabase } from "@fragno-dev/db";
5
+ import { defineFragment } from "@fragno-dev/core";
6
+ //#region src/definition.ts
7
+ /** Expiry changes readiness without a write, so a write that starts the countdown schedules it. */
8
+ function scheduleMcpAuthExpiry(uow, serverId, authMode, after) {
9
+ const readyUntil = mcpAuthReadyUntil(authMode, after);
10
+ if (readyUntil && after.expiresAt && isMcpAuthReady(authMode, after)) uow.triggerHook("internalAuthExpired", {
11
+ serverId,
12
+ expiresAt: after.expiresAt.toISOString()
13
+ }, { processAt: readyUntil });
14
+ }
15
+ const mcpFragmentDefinition = defineFragment("mcp-fragment").extend(withDatabase(mcpSchema)).provideHooks(({ defineHook, config }) => ({
16
+ internalRefreshServerConfiguration: defineHook(async function({ serverId }) {
17
+ let prepared;
18
+ const result = await this.handlerTx().retrieve(({ forSchema }) => forSchema(mcpSchema).findFirst("server_configuration", (b) => b.whereIndex("primary", (eb) => eb("id", "=", serverId))).find("secret", (b) => b.whereIndex("idx_secret_server_kind", (eb) => eb("serverId", "=", serverId))).findFirst("server_connection_cache", (b) => b.whereIndex("primary", (eb) => eb("id", "=", serverId)))).afterRetrieve(async (_uow, [server, secrets]) => {
19
+ if (!server) return;
20
+ const auth = await createMcpOperationAuth({
21
+ config,
22
+ server,
23
+ secrets
24
+ });
25
+ const toolsOperation = await listMcpTools({
26
+ endpointUrl: server.endpointUrl,
27
+ auth,
28
+ fetchImplementation: config.fetch
29
+ });
30
+ prepared = {
31
+ authChanges: toolsOperation.authChanges,
32
+ toolsOperation
33
+ };
34
+ }).mutate(({ forSchema, retrieveResult: [server, secrets, cache] }) => {
35
+ if (!server) return { action: "missing" };
36
+ if (!prepared) throw new Error("MCP refresh operation was not prepared");
37
+ const preparedOperation = prepared;
38
+ const uow = forSchema(mcpSchema);
39
+ const upsertSecret = (kind, payload, expiresAt) => {
40
+ const existing = secrets.find((secret) => secret.kind === kind);
41
+ if (existing) {
42
+ uow.update("secret", existing.id, (b) => b.set({
43
+ payload,
44
+ expiresAt,
45
+ updatedAt: b.now()
46
+ }).check());
47
+ return;
48
+ }
49
+ uow.create("secret", {
50
+ id: `${serverId}:${kind}`,
51
+ serverId,
52
+ kind,
53
+ payload,
54
+ expiresAt
55
+ });
56
+ };
57
+ if (preparedOperation.authChanges) writeOAuthSecretChanges(preparedOperation.authChanges, {
58
+ upsert: (kind, payload) => {
59
+ upsertSecret(kind, payload, null);
60
+ },
61
+ remove: (kind) => {
62
+ const existing = secrets.find((secret) => secret.kind === kind);
63
+ if (existing) uow.delete("secret", existing.id);
64
+ }
65
+ });
66
+ if (preparedOperation.authChanges?.authPayload) {
67
+ const after = {
68
+ payload: preparedOperation.authChanges.authPayload,
69
+ expiresAt: preparedOperation.authChanges.authExpiresAt ?? null
70
+ };
71
+ upsertSecret("auth", after.payload, after.expiresAt);
72
+ const before = secrets.find((secret) => secret.kind === "auth");
73
+ const ready = isMcpAuthReady(server.authMode, after);
74
+ if (ready !== isMcpAuthReady(server.authMode, before)) uow.triggerHook("onServerReadinessChanged", {
75
+ serverId,
76
+ ready
77
+ });
78
+ scheduleMcpAuthExpiry(uow, serverId, server.authMode, after);
79
+ }
80
+ if (!preparedOperation.toolsOperation.ok) return {
81
+ action: "error",
82
+ error: preparedOperation.toolsOperation.error instanceof Error ? preparedOperation.toolsOperation.error.message : String(preparedOperation.toolsOperation.error)
83
+ };
84
+ const tools = Array.isArray(preparedOperation.toolsOperation.result.tools) ? preparedOperation.toolsOperation.result.tools : [];
85
+ const previousTools = Array.isArray(cache?.tools) ? cache.tools : null;
86
+ if (!previousTools || JSON.stringify(previousTools) !== JSON.stringify(tools)) uow.triggerHook("onServerConfigurationChanged", {
87
+ serverId,
88
+ current: { tools }
89
+ });
90
+ uow.delete("server_connection_cache", serverId);
91
+ uow.create("server_connection_cache", {
92
+ id: serverId,
93
+ serverId,
94
+ protocolVersion: preparedOperation.toolsOperation.metadata.protocolVersion ?? null,
95
+ serverInfo: preparedOperation.toolsOperation.metadata.serverInfo ?? null,
96
+ capabilities: preparedOperation.toolsOperation.metadata.capabilities ?? null,
97
+ tools
98
+ });
99
+ return { action: "refreshed" };
100
+ }).execute();
101
+ if (result.action === "error") throw new Error(result.error);
102
+ }),
103
+ onServerConfigurationChanged: defineHook(async function(payload) {
104
+ await config.onServerConfigurationChanged?.(payload, this);
105
+ }),
106
+ onServerConfigurationDeleted: defineHook(async function(payload) {
107
+ await config.onServerConfigurationDeleted?.(payload, this);
108
+ }),
109
+ onServerReadinessChanged: defineHook(async function(payload) {
110
+ const server = await this.handlerTx().retrieve(({ forSchema }) => forSchema(mcpSchema).findFirst("server_configuration", (b) => b.whereIndex("primary", (eb) => eb("id", "=", payload.serverId)))).transformRetrieve(([server]) => server && {
111
+ name: server.name,
112
+ authMode: server.authMode
113
+ }).execute();
114
+ await config.onServerReadinessChanged?.({
115
+ ...payload,
116
+ server
117
+ }, this);
118
+ }),
119
+ internalAuthExpired: defineHook(async function({ serverId, expiresAt }) {
120
+ const expired = await this.handlerTx().retrieve(({ forSchema }) => forSchema(mcpSchema).findFirst("server_configuration", (b) => b.whereIndex("primary", (eb) => eb("id", "=", serverId))).findFirst("secret", (b) => b.whereIndex("idx_secret_server_kind", (eb) => eb.and(eb("serverId", "=", serverId), eb("kind", "=", "auth"))))).transformRetrieve(([server, secret]) => server !== null && secret !== null && secret.expiresAt?.getTime() === Date.parse(expiresAt) && mcpAuthReadyUntil(server.authMode, secret) !== null ? {
121
+ name: server.name,
122
+ authMode: server.authMode
123
+ } : null).execute();
124
+ if (expired) await config.onServerReadinessChanged?.({
125
+ serverId,
126
+ ready: false,
127
+ server: expired
128
+ }, this);
129
+ })
130
+ })).providesBaseService(({ defineService, config }) => defineService({
131
+ startOAuth: function(input) {
132
+ return this.serviceTx(mcpSchema).retrieve((uow) => uow.findFirst("server_configuration", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.serverId))).find("secret", (b) => b.whereIndex("idx_secret_server_kind", (eb) => eb("serverId", "=", input.serverId)))).transformRetrieve(async ([server, secrets]) => {
133
+ if (!server) return { found: false };
134
+ const { authorizationUrl, changes } = await createOAuthStartSnapshot({
135
+ config,
136
+ server,
137
+ secrets,
138
+ stateId: input.stateId,
139
+ redirectUri: input.redirectUri,
140
+ ...resolveOAuthStartClient(secrets, input)
141
+ });
142
+ return {
143
+ found: true,
144
+ authorizationUrl: authorizationUrl.toString(),
145
+ serverId: server.id.toString(),
146
+ authMode: server.authMode,
147
+ secrets,
148
+ changes
149
+ };
150
+ }).mutate(({ uow, retrieveResult }) => {
151
+ if (!retrieveResult.found) return { found: false };
152
+ const upsertSecret = (kind, payload, expiresAt) => {
153
+ const existing = retrieveResult.secrets.find((secret) => secret.kind === kind);
154
+ if (existing) {
155
+ uow.update("secret", existing.id, (b) => b.set({
156
+ payload,
157
+ expiresAt,
158
+ updatedAt: b.now()
159
+ }).check());
160
+ return;
161
+ }
162
+ uow.create("secret", {
163
+ id: `${retrieveResult.serverId}:${kind}`,
164
+ serverId: retrieveResult.serverId,
165
+ kind,
166
+ payload,
167
+ expiresAt
168
+ });
169
+ };
170
+ writeOAuthSecretChanges(retrieveResult.changes, {
171
+ upsert: (kind, payload) => {
172
+ upsertSecret(kind, payload, null);
173
+ },
174
+ remove: (kind) => {
175
+ const existing = retrieveResult.secrets.find((secret) => secret.kind === kind);
176
+ if (existing) uow.delete("secret", existing.id);
177
+ }
178
+ });
179
+ const authSecret = retrieveResult.secrets.find((secret) => secret.kind === "auth");
180
+ if (input.discardTokens && authSecret) {
181
+ const payload = withoutOAuthConsent(authSecret.payload);
182
+ uow.update("secret", authSecret.id, (b) => b.set({
183
+ payload,
184
+ expiresAt: null,
185
+ updatedAt: b.now()
186
+ }).check());
187
+ const { authMode } = retrieveResult;
188
+ const ready = isMcpAuthReady(authMode, {
189
+ payload,
190
+ expiresAt: null
191
+ });
192
+ if (ready !== isMcpAuthReady(authMode, authSecret)) uow.triggerHook("onServerReadinessChanged", {
193
+ serverId: retrieveResult.serverId,
194
+ ready
195
+ });
196
+ }
197
+ if (retrieveResult.changes.oauthState) uow.create("oauthState", {
198
+ id: retrieveResult.changes.oauthState.id,
199
+ serverId: retrieveResult.serverId,
200
+ authorizationUrl: retrieveResult.authorizationUrl,
201
+ codeVerifier: retrieveResult.changes.oauthState.codeVerifier,
202
+ redirectUri: retrieveResult.changes.oauthState.redirectUri,
203
+ scope: retrieveResult.changes.oauthState.scope ?? null,
204
+ expiresAt: retrieveResult.changes.oauthState.expiresAt,
205
+ consumedAt: null
206
+ });
207
+ return { found: true };
208
+ }).transform(({ retrieveResult }) => retrieveResult.found ? {
209
+ found: true,
210
+ authorizationUrl: retrieveResult.authorizationUrl
211
+ } : { found: false }).build();
212
+ },
213
+ completeOAuthCallback: function(input) {
214
+ const serverId = input.stateId.split(":")[0] ?? "";
215
+ return this.serviceTx(mcpSchema).retrieve((uow) => uow.findFirst("oauthState", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.stateId))).findFirst("server_configuration", (b) => b.whereIndex("primary", (eb) => eb("id", "=", serverId))).find("secret", (b) => b.whereIndex("idx_secret_server_kind", (eb) => eb("serverId", "=", serverId))).findFirst("server_connection_cache", (b) => b.whereIndex("primary", (eb) => eb("id", "=", serverId)))).transformRetrieve(async ([state, server, secrets, cache]) => {
216
+ if (!state || state.consumedAt || new Date(state.expiresAt).getTime() < Date.now()) return {
217
+ found: false,
218
+ reason: "invalid_state"
219
+ };
220
+ if (!server) return {
221
+ found: false,
222
+ reason: "server_not_found"
223
+ };
224
+ return {
225
+ found: true,
226
+ server,
227
+ secrets,
228
+ cache,
229
+ changes: await createOAuthCallbackSnapshot({
230
+ config,
231
+ stateId: input.stateId,
232
+ code: input.code,
233
+ iss: input.iss,
234
+ state,
235
+ server,
236
+ secrets
237
+ })
238
+ };
239
+ }).mutate(({ uow, retrieveResult }) => {
240
+ if (!retrieveResult.found) return {
241
+ found: false,
242
+ reason: retrieveResult.reason
243
+ };
244
+ const serverId = retrieveResult.server.id.toString();
245
+ const upsertSecret = (kind, payload, expiresAt) => {
246
+ const existing = retrieveResult.secrets.find((secret) => secret.kind === kind);
247
+ if (existing) {
248
+ uow.update("secret", existing.id, (b) => b.set({
249
+ payload,
250
+ expiresAt,
251
+ updatedAt: b.now()
252
+ }).check());
253
+ return;
254
+ }
255
+ uow.create("secret", {
256
+ id: `${serverId}:${kind}`,
257
+ serverId,
258
+ kind,
259
+ payload,
260
+ expiresAt
261
+ });
262
+ };
263
+ writeOAuthSecretChanges(retrieveResult.changes, {
264
+ upsert: (kind, payload) => {
265
+ upsertSecret(kind, payload, null);
266
+ },
267
+ remove: (kind) => {
268
+ const existing = retrieveResult.secrets.find((secret) => secret.kind === kind);
269
+ if (existing) uow.delete("secret", existing.id);
270
+ }
271
+ });
272
+ if (retrieveResult.changes.authPayload) {
273
+ const after = {
274
+ payload: retrieveResult.changes.authPayload,
275
+ expiresAt: retrieveResult.changes.authExpiresAt ?? null
276
+ };
277
+ upsertSecret("auth", after.payload, after.expiresAt);
278
+ uow.update("server_configuration", retrieveResult.server.id, (b) => b.set({
279
+ authMode: "oauth",
280
+ updatedAt: b.now()
281
+ }).check());
282
+ const before = retrieveResult.secrets.find((secret) => secret.kind === "auth");
283
+ const ready = isMcpAuthReady("oauth", after);
284
+ if (ready !== isMcpAuthReady(retrieveResult.server.authMode, before)) uow.triggerHook("onServerReadinessChanged", {
285
+ serverId,
286
+ ready
287
+ });
288
+ scheduleMcpAuthExpiry(uow, serverId, "oauth", after);
289
+ if (retrieveResult.cache) uow.delete("server_connection_cache", retrieveResult.cache.id);
290
+ uow.triggerHook("internalRefreshServerConfiguration", { serverId });
291
+ }
292
+ if (retrieveResult.changes.consumeStateId) uow.update("oauthState", input.stateId, (b) => b.set({ consumedAt: b.now() }));
293
+ return { found: true };
294
+ }).transform(({ retrieveResult }) => retrieveResult.found ? { found: true } : {
295
+ found: false,
296
+ reason: retrieveResult.reason
297
+ }).build();
298
+ },
299
+ prepareMcpOperation: function(input) {
300
+ return this.serviceTx(mcpSchema).retrieve((uow) => uow.findFirst("server_configuration", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.serverId))).find("secret", (b) => b.whereIndex("idx_secret_server_kind", (eb) => eb("serverId", "=", input.serverId))).findFirst("server_connection_cache", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.serverId)))).transformRetrieve(async ([server, secrets, cache]) => {
301
+ if (!server) return { found: false };
302
+ const resolved = await resolveMcpOperationAuth({
303
+ config,
304
+ server,
305
+ secrets
306
+ });
307
+ return {
308
+ found: true,
309
+ serverId: server.id.toString(),
310
+ endpointUrl: server.endpointUrl,
311
+ token: resolved.token,
312
+ secrets,
313
+ cache,
314
+ authChanges: resolved.authChanges
315
+ };
316
+ }).transform(({ retrieveResult }) => retrieveResult.found ? {
317
+ found: true,
318
+ endpointUrl: retrieveResult.endpointUrl,
319
+ token: retrieveResult.token,
320
+ secrets: retrieveResult.secrets,
321
+ cache: retrieveResult.cache,
322
+ authChanges: retrieveResult.authChanges,
323
+ serverId: retrieveResult.serverId
324
+ } : { found: false }).build();
325
+ }
326
+ })).build();
327
+ //#endregion
328
+ export { mcpFragmentDefinition, scheduleMcpAuthExpiry };
329
+
330
+ //# sourceMappingURL=definition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition.js","names":[],"sources":["../../src/definition.ts"],"sourcesContent":["import { defineFragment } from \"@fragno-dev/core\";\nimport { withDatabase, type HookContext, type HookFn } from \"@fragno-dev/db\";\n\nimport { listMcpTools } from \"./mcp-api\";\nimport type { McpFragmentConfig as BaseMcpFragmentConfig } from \"./mcp-types\";\nimport { mcpSchema } from \"./schema\";\nimport {\n createOAuthCallbackSnapshot,\n createMcpOperationAuth,\n createOAuthStartSnapshot,\n isMcpAuthReady,\n mcpAuthReadyUntil,\n resolveMcpOperationAuth,\n resolveOAuthStartClient,\n withoutOAuthConsent,\n writeOAuthSecretChanges,\n type AuthPersistenceChanges,\n} from \"./services\";\n\nexport interface McpServerConfigurationChangedPayload {\n serverId: string;\n current: {\n tools: unknown[];\n };\n}\n\nexport interface McpServerConfigurationDeletedPayload {\n serverId: string;\n}\n\nexport interface McpServerReadinessChangedPayload {\n serverId: string;\n /** Whether stored auth can authorize operations; the server can still reject it. */\n ready: boolean;\n}\n\n/**\n * A readiness change as delivered. The server is read at delivery rather than stored with the hook,\n * so it is the current configuration, or null after it was deleted.\n */\nexport interface McpServerReadinessChange extends McpServerReadinessChangedPayload {\n server: { name: string | null; authMode: string } | null;\n}\n\nexport interface McpFragmentHooksConfig {\n onServerConfigurationChanged?: (\n payload: McpServerConfigurationChangedPayload,\n context: HookContext,\n ) => Promise<void> | void;\n onServerConfigurationDeleted?: (\n payload: McpServerConfigurationDeletedPayload,\n context: HookContext,\n ) => Promise<void> | void;\n /**\n * Fires when a write changes whether the server can authorize operations, including creation\n * and credentials the server invalidates during an operation, and when an access token without a\n * refresh token expires.\n */\n onServerReadinessChanged?: (\n payload: McpServerReadinessChange,\n context: HookContext,\n ) => Promise<void> | void;\n}\n\nexport type McpFragmentConfig = BaseMcpFragmentConfig & McpFragmentHooksConfig;\n\nexport interface McpInternalRefreshServerConfigurationPayload {\n serverId: string;\n}\n\n/** The token stored when expiry was scheduled; a later write replaces it and reports itself. */\ninterface McpInternalAuthExpiredPayload {\n serverId: string;\n expiresAt: string;\n}\n\nexport type McpHooksMap = {\n internalRefreshServerConfiguration: HookFn<McpInternalRefreshServerConfigurationPayload>;\n internalAuthExpired: HookFn<McpInternalAuthExpiredPayload>;\n onServerConfigurationChanged: HookFn<McpServerConfigurationChangedPayload>;\n onServerConfigurationDeleted: HookFn<McpServerConfigurationDeletedPayload>;\n onServerReadinessChanged: HookFn<McpServerReadinessChangedPayload>;\n};\n\n/** Expiry changes readiness without a write, so a write that starts the countdown schedules it. */\nexport function scheduleMcpAuthExpiry(\n // Hook transactions type their hooks loosely, so only the one trigger is required.\n uow: {\n triggerHook(\n name: \"internalAuthExpired\",\n payload: McpInternalAuthExpiredPayload,\n options: { processAt: Date },\n ): void;\n },\n serverId: string,\n authMode: string,\n after: { payload: string; expiresAt: Date | null },\n) {\n const readyUntil = mcpAuthReadyUntil(authMode, after);\n if (readyUntil && after.expiresAt && isMcpAuthReady(authMode, after)) {\n uow.triggerHook(\n \"internalAuthExpired\",\n { serverId, expiresAt: after.expiresAt.toISOString() },\n { processAt: readyUntil },\n );\n }\n}\n\nexport const mcpFragmentDefinition = defineFragment<McpFragmentConfig>(\"mcp-fragment\")\n .extend(withDatabase(mcpSchema))\n .provideHooks<McpHooksMap>(({ defineHook, config }) => ({\n internalRefreshServerConfiguration: defineHook(async function ({ serverId }) {\n let prepared:\n | {\n authChanges?: AuthPersistenceChanges;\n toolsOperation: Awaited<ReturnType<typeof listMcpTools<AuthPersistenceChanges>>>;\n }\n | undefined;\n\n const result = await this.handlerTx()\n .retrieve(({ forSchema }) =>\n forSchema(mcpSchema)\n .findFirst(\"server_configuration\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", serverId)),\n )\n .find(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_server_kind\", (eb) => eb(\"serverId\", \"=\", serverId)),\n )\n .findFirst(\"server_connection_cache\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", serverId)),\n ),\n )\n .afterRetrieve(async (_uow, [server, secrets]) => {\n if (!server) {\n return;\n }\n\n const auth = await createMcpOperationAuth({ config, server, secrets });\n const toolsOperation = await listMcpTools({\n endpointUrl: server.endpointUrl,\n auth,\n fetchImplementation: config.fetch,\n });\n prepared = {\n authChanges: toolsOperation.authChanges,\n toolsOperation,\n };\n })\n .mutate(({ forSchema, retrieveResult: [server, secrets, cache] }) => {\n if (!server) {\n return { action: \"missing\" as const };\n }\n if (!prepared) {\n throw new Error(\"MCP refresh operation was not prepared\");\n }\n\n const preparedOperation = prepared;\n const uow = forSchema(mcpSchema);\n const upsertSecret = (kind: string, payload: string, expiresAt: Date | null) => {\n const existing = secrets.find((secret) => secret.kind === kind);\n if (existing) {\n uow.update(\"secret\", existing.id, (b) =>\n b.set({ payload, expiresAt, updatedAt: b.now() }).check(),\n );\n return;\n }\n uow.create(\"secret\", {\n id: `${serverId}:${kind}`,\n serverId,\n kind,\n payload,\n expiresAt,\n });\n };\n\n if (preparedOperation.authChanges) {\n writeOAuthSecretChanges(preparedOperation.authChanges, {\n upsert: (kind, payload) => {\n upsertSecret(kind, payload, null);\n },\n remove: (kind) => {\n const existing = secrets.find((secret) => secret.kind === kind);\n if (existing) {\n uow.delete(\"secret\", existing.id);\n }\n },\n });\n }\n if (preparedOperation.authChanges?.authPayload) {\n const after = {\n payload: preparedOperation.authChanges.authPayload,\n expiresAt: preparedOperation.authChanges.authExpiresAt ?? null,\n };\n upsertSecret(\"auth\", after.payload, after.expiresAt);\n const before = secrets.find((secret) => secret.kind === \"auth\");\n const ready = isMcpAuthReady(server.authMode, after);\n if (ready !== isMcpAuthReady(server.authMode, before)) {\n uow.triggerHook(\"onServerReadinessChanged\", { serverId, ready });\n }\n scheduleMcpAuthExpiry(uow, serverId, server.authMode, after);\n }\n\n if (!preparedOperation.toolsOperation.ok) {\n return {\n action: \"error\" as const,\n error:\n preparedOperation.toolsOperation.error instanceof Error\n ? preparedOperation.toolsOperation.error.message\n : String(preparedOperation.toolsOperation.error),\n };\n }\n\n const tools = Array.isArray(preparedOperation.toolsOperation.result.tools)\n ? preparedOperation.toolsOperation.result.tools\n : [];\n const previousTools = Array.isArray(cache?.tools) ? cache.tools : null;\n if (!previousTools || JSON.stringify(previousTools) !== JSON.stringify(tools)) {\n uow.triggerHook(\"onServerConfigurationChanged\", {\n serverId,\n current: { tools },\n });\n }\n\n uow.delete(\"server_connection_cache\", serverId);\n uow.create(\"server_connection_cache\", {\n id: serverId,\n serverId,\n protocolVersion: preparedOperation.toolsOperation.metadata.protocolVersion ?? null,\n serverInfo: preparedOperation.toolsOperation.metadata.serverInfo ?? null,\n capabilities: preparedOperation.toolsOperation.metadata.capabilities ?? null,\n tools,\n });\n\n return { action: \"refreshed\" as const };\n })\n .execute();\n\n if (result.action === \"error\") {\n throw new Error(result.error);\n }\n }),\n onServerConfigurationChanged: defineHook(async function (payload) {\n await config.onServerConfigurationChanged?.(payload, this);\n }),\n onServerConfigurationDeleted: defineHook(async function (payload) {\n await config.onServerConfigurationDeleted?.(payload, this);\n }),\n onServerReadinessChanged: defineHook(async function (payload) {\n const server = await this.handlerTx()\n .retrieve(({ forSchema }) =>\n forSchema(mcpSchema).findFirst(\"server_configuration\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", payload.serverId)),\n ),\n )\n .transformRetrieve(([server]) => server && { name: server.name, authMode: server.authMode })\n .execute();\n await config.onServerReadinessChanged?.({ ...payload, server }, this);\n }),\n internalAuthExpired: defineHook(async function ({ serverId, expiresAt }) {\n const expired = await this.handlerTx()\n .retrieve(({ forSchema }) =>\n forSchema(mcpSchema)\n .findFirst(\"server_configuration\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", serverId)),\n )\n .findFirst(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_server_kind\", (eb) =>\n eb.and(eb(\"serverId\", \"=\", serverId), eb(\"kind\", \"=\", \"auth\")),\n ),\n ),\n )\n .transformRetrieve(([server, secret]) =>\n // The hook's due time already passed, so an untouched token is the expired one.\n server !== null &&\n secret !== null &&\n secret.expiresAt?.getTime() === Date.parse(expiresAt) &&\n mcpAuthReadyUntil(server.authMode, secret) !== null\n ? { name: server.name, authMode: server.authMode }\n : null,\n )\n .execute();\n if (expired) {\n await config.onServerReadinessChanged?.({ serverId, ready: false, server: expired }, this);\n }\n }),\n }))\n .providesBaseService(({ defineService, config }) =>\n defineService({\n startOAuth: function (input: {\n serverId: string;\n stateId: string;\n redirectUri: string;\n scope?: string;\n clientId?: string;\n clientSecret?: string;\n discardTokens: boolean;\n }) {\n return this.serviceTx(mcpSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"server_configuration\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.serverId)),\n )\n .find(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_server_kind\", (eb) => eb(\"serverId\", \"=\", input.serverId)),\n ),\n )\n .transformRetrieve(async ([server, secrets]) => {\n if (!server) {\n return { found: false as const };\n }\n const { authorizationUrl, changes } = await createOAuthStartSnapshot({\n config,\n server,\n secrets,\n stateId: input.stateId,\n redirectUri: input.redirectUri,\n ...resolveOAuthStartClient(secrets, input),\n });\n return {\n found: true as const,\n authorizationUrl: authorizationUrl.toString(),\n serverId: server.id.toString(),\n authMode: server.authMode,\n secrets,\n changes,\n };\n })\n .mutate(({ uow, retrieveResult }) => {\n if (!retrieveResult.found) {\n return { found: false as const };\n }\n\n const upsertSecret = (kind: string, payload: string, expiresAt: Date | null) => {\n const existing = retrieveResult.secrets.find((secret) => secret.kind === kind);\n if (existing) {\n uow.update(\"secret\", existing.id, (b) =>\n b.set({ payload, expiresAt, updatedAt: b.now() }).check(),\n );\n return;\n }\n uow.create(\"secret\", {\n id: `${retrieveResult.serverId}:${kind}`,\n serverId: retrieveResult.serverId,\n kind,\n payload,\n expiresAt,\n });\n };\n\n writeOAuthSecretChanges(retrieveResult.changes, {\n upsert: (kind, payload) => {\n upsertSecret(kind, payload, null);\n },\n remove: (kind) => {\n const existing = retrieveResult.secrets.find((secret) => secret.kind === kind);\n if (existing) {\n uow.delete(\"secret\", existing.id);\n }\n },\n });\n const authSecret = retrieveResult.secrets.find((secret) => secret.kind === \"auth\");\n if (input.discardTokens && authSecret) {\n // Keeps the client configuration; only consent evidence is discarded.\n const payload = withoutOAuthConsent(authSecret.payload);\n uow.update(\"secret\", authSecret.id, (b) =>\n b.set({ payload, expiresAt: null, updatedAt: b.now() }).check(),\n );\n const { authMode } = retrieveResult;\n const ready = isMcpAuthReady(authMode, { payload, expiresAt: null });\n if (ready !== isMcpAuthReady(authMode, authSecret)) {\n uow.triggerHook(\"onServerReadinessChanged\", {\n serverId: retrieveResult.serverId,\n ready,\n });\n }\n }\n if (retrieveResult.changes.oauthState) {\n uow.create(\"oauthState\", {\n id: retrieveResult.changes.oauthState.id,\n serverId: retrieveResult.serverId,\n // The link carries only the PKCE challenge, never the verifier.\n authorizationUrl: retrieveResult.authorizationUrl,\n codeVerifier: retrieveResult.changes.oauthState.codeVerifier,\n redirectUri: retrieveResult.changes.oauthState.redirectUri,\n scope: retrieveResult.changes.oauthState.scope ?? null,\n expiresAt: retrieveResult.changes.oauthState.expiresAt,\n consumedAt: null,\n });\n }\n\n return { found: true as const };\n })\n .transform(({ retrieveResult }) =>\n retrieveResult.found\n ? {\n found: true as const,\n authorizationUrl: retrieveResult.authorizationUrl,\n }\n : { found: false as const },\n )\n .build();\n },\n\n completeOAuthCallback: function (input: {\n stateId: string;\n code: string;\n iss: string | undefined;\n }) {\n const serverId = input.stateId.split(\":\")[0] ?? \"\";\n return this.serviceTx(mcpSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"oauthState\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.stateId)),\n )\n .findFirst(\"server_configuration\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", serverId)),\n )\n .find(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_server_kind\", (eb) => eb(\"serverId\", \"=\", serverId)),\n )\n .findFirst(\"server_connection_cache\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", serverId)),\n ),\n )\n .transformRetrieve(async ([state, server, secrets, cache]) => {\n if (!state || state.consumedAt || new Date(state.expiresAt).getTime() < Date.now()) {\n return { found: false as const, reason: \"invalid_state\" as const };\n }\n if (!server) {\n return { found: false as const, reason: \"server_not_found\" as const };\n }\n const changes = await createOAuthCallbackSnapshot({\n config,\n stateId: input.stateId,\n code: input.code,\n iss: input.iss,\n state,\n server,\n secrets,\n });\n return { found: true as const, server, secrets, cache, changes };\n })\n .mutate(({ uow, retrieveResult }) => {\n if (!retrieveResult.found) {\n return { found: false as const, reason: retrieveResult.reason };\n }\n\n const serverId = retrieveResult.server.id.toString();\n const upsertSecret = (kind: string, payload: string, expiresAt: Date | null) => {\n const existing = retrieveResult.secrets.find((secret) => secret.kind === kind);\n if (existing) {\n uow.update(\"secret\", existing.id, (b) =>\n b.set({ payload, expiresAt, updatedAt: b.now() }).check(),\n );\n return;\n }\n uow.create(\"secret\", {\n id: `${serverId}:${kind}`,\n serverId,\n kind,\n payload,\n expiresAt,\n });\n };\n\n writeOAuthSecretChanges(retrieveResult.changes, {\n upsert: (kind, payload) => {\n upsertSecret(kind, payload, null);\n },\n remove: (kind) => {\n const existing = retrieveResult.secrets.find((secret) => secret.kind === kind);\n if (existing) {\n uow.delete(\"secret\", existing.id);\n }\n },\n });\n if (retrieveResult.changes.authPayload) {\n const after = {\n payload: retrieveResult.changes.authPayload,\n expiresAt: retrieveResult.changes.authExpiresAt ?? null,\n };\n upsertSecret(\"auth\", after.payload, after.expiresAt);\n uow.update(\"server_configuration\", retrieveResult.server.id, (b) =>\n b.set({ authMode: \"oauth\", updatedAt: b.now() }).check(),\n );\n const before = retrieveResult.secrets.find((secret) => secret.kind === \"auth\");\n const ready = isMcpAuthReady(\"oauth\", after);\n if (ready !== isMcpAuthReady(retrieveResult.server.authMode, before)) {\n uow.triggerHook(\"onServerReadinessChanged\", { serverId, ready });\n }\n scheduleMcpAuthExpiry(uow, serverId, \"oauth\", after);\n if (retrieveResult.cache) {\n uow.delete(\"server_connection_cache\", retrieveResult.cache.id);\n }\n uow.triggerHook(\"internalRefreshServerConfiguration\", { serverId });\n }\n if (retrieveResult.changes.consumeStateId) {\n uow.update(\"oauthState\", input.stateId, (b) => b.set({ consumedAt: b.now() }));\n }\n\n return { found: true as const };\n })\n .transform(({ retrieveResult }) =>\n retrieveResult.found\n ? { found: true as const }\n : { found: false as const, reason: retrieveResult.reason },\n )\n .build();\n },\n\n prepareMcpOperation: function (input: { serverId: string }) {\n return this.serviceTx(mcpSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"server_configuration\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.serverId)),\n )\n .find(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_server_kind\", (eb) => eb(\"serverId\", \"=\", input.serverId)),\n )\n .findFirst(\"server_connection_cache\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.serverId)),\n ),\n )\n .transformRetrieve(async ([server, secrets, cache]) => {\n if (!server) {\n return { found: false as const };\n }\n const resolved = await resolveMcpOperationAuth({ config, server, secrets });\n return {\n found: true as const,\n serverId: server.id.toString(),\n endpointUrl: server.endpointUrl,\n token: resolved.token,\n secrets,\n cache,\n authChanges: resolved.authChanges,\n };\n })\n .transform(({ retrieveResult }) =>\n retrieveResult.found\n ? {\n found: true as const,\n endpointUrl: retrieveResult.endpointUrl,\n token: retrieveResult.token,\n secrets: retrieveResult.secrets,\n cache: retrieveResult.cache,\n authChanges: retrieveResult.authChanges,\n serverId: retrieveResult.serverId,\n }\n : { found: false as const },\n )\n .build();\n },\n }),\n )\n .build();\n"],"mappings":"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"}