@nyte-ai/host 0.0.0-stage → 0.0.18
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/LICENSE +21 -0
- package/README.md +6 -2
- package/THIRD-PARTY-NOTICES.md +800 -0
- package/dist/chunks/canvas-preview-0cmbkp2j.js +65 -0
- package/dist/chunks/canvas-preview-0nq75h9k.js +7 -0
- package/dist/chunks/canvas-preview-0nrnbt76.js +497 -0
- package/dist/chunks/canvas-preview-1sq7z9jq.js +17 -0
- package/dist/chunks/canvas-preview-1ta3avah.js +56 -0
- package/dist/chunks/canvas-preview-2zdyh9ny.js +26 -0
- package/dist/chunks/canvas-preview-31w7xm8z.js +34 -0
- package/dist/chunks/canvas-preview-4b6sayhg.js +295 -0
- package/dist/chunks/canvas-preview-4e0pxa32.js +63 -0
- package/dist/chunks/canvas-preview-518dh8by.js +169 -0
- package/dist/chunks/canvas-preview-5hxat7hh.js +239 -0
- package/dist/chunks/canvas-preview-5mex725c.js +14 -0
- package/dist/chunks/canvas-preview-5z63am63.js +295 -0
- package/dist/chunks/canvas-preview-65xqjw7f.js +20 -0
- package/dist/chunks/canvas-preview-69fkk9yn.js +95 -0
- package/dist/chunks/canvas-preview-75db60xy.js +457 -0
- package/dist/chunks/canvas-preview-76fwvd6e.js +272 -0
- package/dist/chunks/canvas-preview-8xgt67ka.js +598 -0
- package/dist/chunks/canvas-preview-98aj5p34.js +176 -0
- package/dist/chunks/canvas-preview-9aqczacs.js +73 -0
- package/dist/chunks/canvas-preview-a3yw0j70.js +72 -0
- package/dist/chunks/canvas-preview-azg56359.js +68 -0
- package/dist/chunks/canvas-preview-d9wwhbtz.js +240 -0
- package/dist/chunks/canvas-preview-ehf3vp31.js +411 -0
- package/dist/chunks/canvas-preview-evh92ywd.js +369 -0
- package/dist/chunks/canvas-preview-ewgspmsd.js +81 -0
- package/dist/chunks/canvas-preview-g3qj3y3k.js +156 -0
- package/dist/chunks/canvas-preview-h8hshnff.js +63 -0
- package/dist/chunks/canvas-preview-j1tnf51w.js +36 -0
- package/dist/chunks/canvas-preview-kdqgrstc.js +194 -0
- package/dist/chunks/canvas-preview-kn7kvbfj.js +12 -0
- package/dist/chunks/canvas-preview-m02v4yy6.js +386 -0
- package/dist/chunks/canvas-preview-mgcx2b2p.js +271 -0
- package/dist/chunks/canvas-preview-rp4x6y1h.js +144 -0
- package/dist/chunks/canvas-preview-rp6fz0w5.js +130 -0
- package/dist/chunks/canvas-preview-s94ebwf5.js +112 -0
- package/dist/chunks/canvas-preview-senpjwep.js +518 -0
- package/dist/chunks/canvas-preview-skzwvw4d.js +23 -0
- package/dist/chunks/canvas-preview-srgfpdmf.js +60 -0
- package/dist/chunks/canvas-preview-tjhabynd.js +252 -0
- package/dist/chunks/canvas-preview-vaatfznm.js +189 -0
- package/dist/chunks/canvas-preview-vax9ne4e.js +341 -0
- package/dist/chunks/canvas-preview-wg80rbc2.js +46 -0
- package/dist/chunks/canvas-preview-xn10d4f8.js +608 -0
- package/dist/chunks/canvas-preview-yjz0f7n4.js +50 -0
- package/dist/chunks/canvas-preview-ymgz0937.js +587 -0
- package/dist/chunks/canvas-preview-yrfz2ak6.js +134 -0
- package/dist/chunks/canvas-preview-zaaj99wd.js +244 -0
- package/dist/chunks/canvas-preview-zn2d0k9d.js +133 -0
- package/dist/src/canvas-preview.d.ts +29 -0
- package/dist/src/canvas-preview.js +267 -0
- package/dist/src/credentials.d.ts +44 -0
- package/dist/src/credentials.js +10 -0
- package/dist/src/deploy/cloudflare/worker.d.ts +50 -0
- package/dist/src/deploy/cloudflare/worker.js +196 -0
- package/dist/src/deploy/vercel/nitro.config.d.ts +2 -0
- package/dist/src/deploy/vercel/nitro.config.js +30 -0
- package/dist/src/deploy/vercel/server/routes/[...].d.ts +2 -0
- package/dist/src/deploy/vercel/server/routes/[...].js +18 -0
- package/dist/src/deploy/vercel/server/utils/host.d.ts +13 -0
- package/dist/src/deploy/vercel/server/utils/host.js +7 -0
- package/dist/src/deploy/vercel/server/utils/repair.d.ts +1 -0
- package/dist/src/deploy/vercel/server/utils/repair.js +6 -0
- package/dist/src/deploy/vercel/server/utils/step.d.ts +10 -0
- package/dist/src/deploy/vercel/server/utils/step.js +6 -0
- package/dist/src/deploy/vercel/server/workflows/drive.d.ts +14 -0
- package/dist/src/deploy/vercel/server/workflows/drive.js +13 -0
- package/dist/src/durable-object.d.ts +14 -0
- package/dist/src/durable-object.js +231 -0
- package/dist/src/hash.d.ts +1 -0
- package/dist/src/hash.js +6 -0
- package/dist/src/identity-key.d.ts +11 -0
- package/dist/src/identity-key.js +9 -0
- package/dist/src/identity.d.ts +12 -0
- package/dist/src/identity.js +6 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +120 -0
- package/dist/src/key-file.d.ts +1 -0
- package/dist/src/key-file.js +7 -0
- package/dist/src/listen-fetch.d.ts +8 -0
- package/dist/src/listen-fetch.js +9 -0
- package/dist/src/listen.d.ts +4 -0
- package/dist/src/listen.js +10 -0
- package/dist/src/local/command.d.ts +15 -0
- package/dist/src/local/command.js +8 -0
- package/dist/src/local/feed.d.ts +7 -0
- package/dist/src/local/feed.js +6 -0
- package/dist/src/local/files.d.ts +18 -0
- package/dist/src/local/files.js +13 -0
- package/dist/src/local/git-watch.d.ts +15 -0
- package/dist/src/local/git-watch.js +7 -0
- package/dist/src/local/git.d.ts +5 -0
- package/dist/src/local/git.js +13 -0
- package/dist/src/local/github.d.ts +30 -0
- package/dist/src/local/github.js +9 -0
- package/dist/src/local/identity.d.ts +3 -0
- package/dist/src/local/identity.js +7 -0
- package/dist/src/local/jobs.d.ts +7 -0
- package/dist/src/local/jobs.js +6 -0
- package/dist/src/local/legacy-trust.d.ts +10 -0
- package/dist/src/local/legacy-trust.js +6 -0
- package/dist/src/local/patch.d.ts +16 -0
- package/dist/src/local/patch.js +17 -0
- package/dist/src/local/provider.d.ts +11 -0
- package/dist/src/local/provider.js +20 -0
- package/dist/src/local/snapshots.d.ts +7 -0
- package/dist/src/local/snapshots.js +11 -0
- package/dist/src/local/text.d.ts +1 -0
- package/dist/src/local/text.js +6 -0
- package/dist/src/local/tree.d.ts +50 -0
- package/dist/src/local/tree.js +16 -0
- package/dist/src/local/workspace-migration.d.ts +2 -0
- package/dist/src/local/workspace-migration.js +7 -0
- package/dist/src/local/worktrees.d.ts +46 -0
- package/dist/src/local/worktrees.js +14 -0
- package/dist/src/mcp/config.d.ts +75 -0
- package/dist/src/mcp/config.js +12 -0
- package/dist/src/mcp/connect.d.ts +28 -0
- package/dist/src/mcp/connect.js +9 -0
- package/dist/src/mcp/hub.d.ts +19 -0
- package/dist/src/mcp/hub.js +11 -0
- package/dist/src/mcp/oauth.d.ts +46 -0
- package/dist/src/mcp/oauth.js +12 -0
- package/dist/src/mcp/tools.d.ts +29 -0
- package/dist/src/mcp/tools.js +12 -0
- package/dist/src/open.d.ts +42 -0
- package/dist/src/open.js +44 -0
- package/dist/src/plugins/loader.d.ts +21 -0
- package/dist/src/plugins/loader.js +11 -0
- package/dist/src/postgres.d.ts +19 -0
- package/dist/src/postgres.js +8 -0
- package/dist/src/providers.d.ts +16 -0
- package/dist/src/providers.js +112 -0
- package/dist/src/secrets.d.ts +12 -0
- package/dist/src/secrets.js +8 -0
- package/dist/src/serve/binding.d.ts +39 -0
- package/dist/src/serve/binding.js +6 -0
- package/dist/src/serve/handler.d.ts +15 -0
- package/dist/src/serve/handler.js +8 -0
- package/dist/src/serve/stream-server.d.ts +6 -0
- package/dist/src/serve/stream-server.js +6 -0
- package/dist/src/session-index.d.ts +11 -0
- package/dist/src/session-index.js +11 -0
- package/dist/src/shell-env.d.ts +14 -0
- package/dist/src/shell-env.js +108 -0
- package/dist/src/sqlite-workers.d.ts +6 -0
- package/dist/src/sqlite-workers.js +6 -0
- package/dist/src/sqlite.d.ts +7 -0
- package/dist/src/sqlite.js +10 -0
- package/dist/src/store-owner.d.ts +15 -0
- package/dist/src/store-owner.js +9 -0
- package/dist/src/store-rows.d.ts +13 -0
- package/dist/src/store-rows.js +26 -0
- package/dist/src/store-usage.d.ts +25 -0
- package/dist/src/store-usage.js +16 -0
- package/dist/src/usage-scan.d.ts +24 -0
- package/dist/src/usage-scan.js +65 -0
- package/dist/src/usage-service.d.ts +22 -0
- package/dist/src/usage-service.js +12 -0
- package/dist/src/usage-summary.d.ts +9 -0
- package/dist/src/usage-summary.js +16 -0
- package/dist/src/usage-types.d.ts +86 -0
- package/dist/src/usage-types.js +0 -0
- package/dist/src/usage-worker.d.ts +1 -0
- package/dist/src/usage-worker.js +25 -0
- package/dist/src/usage.d.ts +72 -0
- package/dist/src/usage.js +13 -0
- package/package.json +132 -3
- package/src/canvas-preview.ts +402 -0
- package/src/credentials.ts +294 -0
- package/src/deploy/cloudflare/README.md +3 -0
- package/src/deploy/cloudflare/worker.ts +306 -0
- package/src/deploy/cloudflare/wrangler.jsonc +12 -0
- package/src/deploy/vercel/README.md +3 -0
- package/src/deploy/vercel/nitro.config.ts +29 -0
- package/src/deploy/vercel/server/routes/[...].ts +10 -0
- package/src/deploy/vercel/server/utils/host.ts +104 -0
- package/src/deploy/vercel/server/utils/repair.ts +35 -0
- package/src/deploy/vercel/server/utils/step.ts +25 -0
- package/src/deploy/vercel/server/workflows/drive.ts +46 -0
- package/src/deploy/vercel/tsconfig.json +9 -0
- package/src/deploy/vercel/types/bun.d.ts +2 -0
- package/src/durable-object.ts +386 -0
- package/src/hash.ts +5 -0
- package/src/identity-key.ts +20 -0
- package/src/identity.ts +66 -0
- package/src/index.ts +21 -0
- package/src/key-file.ts +53 -0
- package/src/listen-fetch.ts +409 -0
- package/src/listen.ts +228 -0
- package/src/local/command.ts +109 -0
- package/src/local/feed.ts +70 -0
- package/src/local/files.ts +812 -0
- package/src/local/git-watch.ts +189 -0
- package/src/local/git.ts +914 -0
- package/src/local/github.ts +692 -0
- package/src/local/identity.ts +21 -0
- package/src/local/jobs.ts +587 -0
- package/src/local/legacy-trust.ts +105 -0
- package/src/local/patch.ts +334 -0
- package/src/local/provider.ts +184 -0
- package/src/local/snapshots.ts +776 -0
- package/src/local/text.ts +9 -0
- package/src/local/tree.ts +398 -0
- package/src/local/workspace-migration.ts +95 -0
- package/src/local/worktrees.ts +239 -0
- package/src/mcp/config.ts +330 -0
- package/src/mcp/connect.ts +105 -0
- package/src/mcp/hub.ts +836 -0
- package/src/mcp/oauth.ts +436 -0
- package/src/mcp/tools.ts +333 -0
- package/src/open.ts +469 -0
- package/src/plugins/loader.ts +186 -0
- package/src/postgres.ts +492 -0
- package/src/providers.ts +135 -0
- package/src/secrets.ts +31 -0
- package/src/serve/binding.ts +628 -0
- package/src/serve/handler.ts +362 -0
- package/src/serve/stream-server.ts +176 -0
- package/src/session-index.ts +99 -0
- package/src/shell-env.ts +153 -0
- package/src/sqlite-workers.ts +283 -0
- package/src/sqlite.ts +528 -0
- package/src/store-owner.ts +98 -0
- package/src/store-rows.ts +110 -0
- package/src/store-usage.ts +344 -0
- package/src/usage-scan.ts +81 -0
- package/src/usage-service.ts +73 -0
- package/src/usage-summary.ts +79 -0
- package/src/usage-types.ts +87 -0
- package/src/usage-worker.ts +24 -0
- package/src/usage.ts +701 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import {
|
|
2
|
+
watchPath2
|
|
3
|
+
} from "./canvas-preview-mgcx2b2p.js";
|
|
4
|
+
import {
|
|
5
|
+
readMcpFile2,
|
|
6
|
+
parseMcpConfig2,
|
|
7
|
+
planMcpEdit2,
|
|
8
|
+
writeMcpFile2
|
|
9
|
+
} from "./canvas-preview-kdqgrstc.js";
|
|
10
|
+
import {
|
|
11
|
+
oauthKey2,
|
|
12
|
+
oauthStore2,
|
|
13
|
+
signIn2
|
|
14
|
+
} from "./canvas-preview-5z63am63.js";
|
|
15
|
+
import {
|
|
16
|
+
connectServer2,
|
|
17
|
+
oauthSettings2
|
|
18
|
+
} from "./canvas-preview-h8hshnff.js";
|
|
19
|
+
import {
|
|
20
|
+
toolExposure2,
|
|
21
|
+
checkToolSchemas2,
|
|
22
|
+
TracingMcpClient2,
|
|
23
|
+
bridgeTools2
|
|
24
|
+
} from "./canvas-preview-vaatfznm.js";
|
|
25
|
+
|
|
26
|
+
// src/mcp/hub.ts
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
import { StdioTransport } from "@earendil-works/pi-mcp";
|
|
29
|
+
import { McpOAuthAuthorizationRequiredError } from "@earendil-works/pi-mcp/oauth";
|
|
30
|
+
import { processEnvironment, closable, dev, forwarded, traced } from "@nyte-ai/core";
|
|
31
|
+
import { isJson, isJsonObject, SdkError } from "@nyte-ai/schema";
|
|
32
|
+
var STDERR_TAIL = 4000;
|
|
33
|
+
var WATCH_DELAY_MS = 100;
|
|
34
|
+
var LINGER_MS = 30000;
|
|
35
|
+
var UNTRUSTED = {
|
|
36
|
+
kind: "invalid",
|
|
37
|
+
message: "Project servers need a trusted folder on this machine."
|
|
38
|
+
};
|
|
39
|
+
function createMcpHub2(input) {
|
|
40
|
+
return ({ secrets }) => hub({ ...input, connect: input.connect ?? connectServer2, secrets });
|
|
41
|
+
}
|
|
42
|
+
function hub(input) {
|
|
43
|
+
const pools = new Map;
|
|
44
|
+
const contents = new Map;
|
|
45
|
+
const watched = new Map;
|
|
46
|
+
const userPath = join(input.home, ".nyte", "mcp.json");
|
|
47
|
+
let closed = false;
|
|
48
|
+
const notify = (pool) => {
|
|
49
|
+
for (const listener of pool.listeners)
|
|
50
|
+
listener();
|
|
51
|
+
};
|
|
52
|
+
const reconcile = (pool) => {
|
|
53
|
+
pool.config = parseMcpConfig2(pool.files.map((file) => ({
|
|
54
|
+
...file,
|
|
55
|
+
contents: contents.get(file.path) ?? { kind: "missing" }
|
|
56
|
+
})));
|
|
57
|
+
for (const [name, link] of pool.links) {
|
|
58
|
+
const entry = pool.config.servers.get(name);
|
|
59
|
+
if (entry !== undefined && entry.enabled && linkKey(entry) === link.key) {
|
|
60
|
+
link.entry = entry;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
pool.links.delete(name);
|
|
64
|
+
link.close();
|
|
65
|
+
}
|
|
66
|
+
for (const entry of pool.config.servers.values())
|
|
67
|
+
if (entry.enabled && !pool.links.has(entry.name))
|
|
68
|
+
pool.links.set(entry.name, openLink(entry, pool));
|
|
69
|
+
notify(pool);
|
|
70
|
+
};
|
|
71
|
+
const openLink = (entry, pool) => {
|
|
72
|
+
let state = { kind: "connecting" };
|
|
73
|
+
let listed = null;
|
|
74
|
+
let bridged = null;
|
|
75
|
+
let client = null;
|
|
76
|
+
let stderr = () => null;
|
|
77
|
+
let challenge = null;
|
|
78
|
+
let ended = false;
|
|
79
|
+
let delay = 1000;
|
|
80
|
+
let timer;
|
|
81
|
+
const attempted = Promise.withResolvers();
|
|
82
|
+
let attempting = attempted.promise;
|
|
83
|
+
const attempt = () => {
|
|
84
|
+
attempting = null;
|
|
85
|
+
attempted.resolve();
|
|
86
|
+
};
|
|
87
|
+
const set = (next) => {
|
|
88
|
+
if (ended)
|
|
89
|
+
return;
|
|
90
|
+
state = next;
|
|
91
|
+
if (next.kind !== "connecting")
|
|
92
|
+
attempt();
|
|
93
|
+
notify(pool);
|
|
94
|
+
};
|
|
95
|
+
const schedule = (wait) => {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
if (!ended)
|
|
98
|
+
timer = setTimeout(() => void connectTraced(), wait);
|
|
99
|
+
};
|
|
100
|
+
const retry = () => {
|
|
101
|
+
schedule(delay);
|
|
102
|
+
delay = Math.min(delay * 2, 30000);
|
|
103
|
+
};
|
|
104
|
+
const drop = (message) => {
|
|
105
|
+
const previous = client;
|
|
106
|
+
client = null;
|
|
107
|
+
previous?.close().catch(() => {
|
|
108
|
+
return;
|
|
109
|
+
});
|
|
110
|
+
if (ended)
|
|
111
|
+
return;
|
|
112
|
+
set({ kind: "failed", message, stderr: stderr() });
|
|
113
|
+
dev("mcp", "link failed", { server: entry.name, retryIn: delay });
|
|
114
|
+
retry();
|
|
115
|
+
};
|
|
116
|
+
const connect = async () => {
|
|
117
|
+
if (link.entry.transport.kind === "stdio")
|
|
118
|
+
await processEnvironment();
|
|
119
|
+
if (ended)
|
|
120
|
+
return;
|
|
121
|
+
const next = new TracingMcpClient2({
|
|
122
|
+
name: "nyte",
|
|
123
|
+
version: "0.1.0",
|
|
124
|
+
requestTimeoutMs: link.entry.timeoutMs
|
|
125
|
+
});
|
|
126
|
+
next.onClose(() => {
|
|
127
|
+
if (client === next)
|
|
128
|
+
drop(`The connection to ${entry.name} closed.`);
|
|
129
|
+
});
|
|
130
|
+
let transport;
|
|
131
|
+
try {
|
|
132
|
+
transport = input.connect(link.entry, {
|
|
133
|
+
workspace: pool.workspace,
|
|
134
|
+
home: input.home,
|
|
135
|
+
secrets: input.secrets,
|
|
136
|
+
challenged: (next) => {
|
|
137
|
+
challenge = next;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
set({ kind: "failed", message: errorMessage(error), stderr: null });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
stderr = () => transport instanceof StdioTransport && transport.stderr !== "" ? transport.stderr.slice(-STDERR_TAIL) : null;
|
|
146
|
+
await next.connect(transport);
|
|
147
|
+
if (ended) {
|
|
148
|
+
await next.close();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const refresh = async () => {
|
|
152
|
+
const offered = await next.listTools();
|
|
153
|
+
if (client !== next)
|
|
154
|
+
return;
|
|
155
|
+
const { usable, issues } = checkToolSchemas2(entry.name, offered);
|
|
156
|
+
dev("mcp", "tools listed", { server: entry.name, tools: usable.length });
|
|
157
|
+
listed = { tools: usable, instructions: next.instructions ?? null };
|
|
158
|
+
set({ kind: "connected", tools: usable, issues });
|
|
159
|
+
};
|
|
160
|
+
client = next;
|
|
161
|
+
delay = 1000;
|
|
162
|
+
next.onNotification("notifications/tools/list_changed", async () => {
|
|
163
|
+
try {
|
|
164
|
+
await refresh();
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (client === next)
|
|
167
|
+
drop(`${entry.name} failed to list its tools: ${errorMessage(error)}`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
await refresh();
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (client === next)
|
|
173
|
+
client = null;
|
|
174
|
+
await next.close().catch(() => {
|
|
175
|
+
return;
|
|
176
|
+
});
|
|
177
|
+
if (ended)
|
|
178
|
+
return;
|
|
179
|
+
if (error instanceof McpOAuthAuthorizationRequiredError) {
|
|
180
|
+
if (state.kind !== "needs-sign-in")
|
|
181
|
+
set({ kind: "needs-sign-in" });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
set({
|
|
185
|
+
kind: "failed",
|
|
186
|
+
message: `${entry.name} failed to connect: ${errorMessage(error)}`,
|
|
187
|
+
stderr: stderr()
|
|
188
|
+
});
|
|
189
|
+
retry();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const connectTraced = () => traced("mcp.connect", null, { server: entry.name }, connect);
|
|
193
|
+
const call = (name, args, options) => {
|
|
194
|
+
if (client === null)
|
|
195
|
+
throw new Error(notConnected(entry.name, state));
|
|
196
|
+
return client.callTool(name, args, options);
|
|
197
|
+
};
|
|
198
|
+
const link = {
|
|
199
|
+
key: linkKey(entry),
|
|
200
|
+
entry,
|
|
201
|
+
state: () => state,
|
|
202
|
+
brief: () => ({
|
|
203
|
+
name: entry.name,
|
|
204
|
+
instructions: listed?.instructions ?? null,
|
|
205
|
+
unavailable: listed === null && (state.kind === "failed" || state.kind === "needs-sign-in") ? state.kind : null
|
|
206
|
+
}),
|
|
207
|
+
challenge: () => challenge,
|
|
208
|
+
tools() {
|
|
209
|
+
if (listed === null)
|
|
210
|
+
return [];
|
|
211
|
+
if (bridged?.entry !== link.entry || bridged.listed !== listed)
|
|
212
|
+
bridged = {
|
|
213
|
+
entry: link.entry,
|
|
214
|
+
listed,
|
|
215
|
+
tools: bridgeTools2(link.entry, listed.tools, call)
|
|
216
|
+
};
|
|
217
|
+
return bridged.tools;
|
|
218
|
+
},
|
|
219
|
+
attempting: () => attempting,
|
|
220
|
+
async close() {
|
|
221
|
+
ended = true;
|
|
222
|
+
attempt();
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
const previous = client;
|
|
225
|
+
client = null;
|
|
226
|
+
await previous?.close().catch(() => {
|
|
227
|
+
return;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
connectTraced();
|
|
232
|
+
return link;
|
|
233
|
+
};
|
|
234
|
+
const load = async (paths) => {
|
|
235
|
+
await Promise.all(paths.map(async (path) => {
|
|
236
|
+
contents.set(path, await readMcpFile2(path));
|
|
237
|
+
}));
|
|
238
|
+
};
|
|
239
|
+
const changed = (path) => {
|
|
240
|
+
load([path]).then(() => {
|
|
241
|
+
if (!watched.has(path)) {
|
|
242
|
+
contents.delete(path);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
for (const pool of pools.values())
|
|
246
|
+
if (pool.files.some((file) => file.path === path))
|
|
247
|
+
reconcile(pool);
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
const unfollow = (path) => {
|
|
251
|
+
if ([...pools.values()].some((pool) => pool.files.some((file) => file.path === path)))
|
|
252
|
+
return;
|
|
253
|
+
watched.get(path)?.();
|
|
254
|
+
watched.delete(path);
|
|
255
|
+
contents.delete(path);
|
|
256
|
+
};
|
|
257
|
+
const shut = (pool) => {
|
|
258
|
+
clearTimeout(pool.linger);
|
|
259
|
+
if (pools.get(pool.key) !== pool)
|
|
260
|
+
return;
|
|
261
|
+
pools.delete(pool.key);
|
|
262
|
+
for (const link of pool.links.values())
|
|
263
|
+
link.close();
|
|
264
|
+
pool.links.clear();
|
|
265
|
+
notify(pool);
|
|
266
|
+
for (const file of pool.files)
|
|
267
|
+
unfollow(file.path);
|
|
268
|
+
};
|
|
269
|
+
const idle = (pool) => {
|
|
270
|
+
clearTimeout(pool.linger);
|
|
271
|
+
if (closed || pools.get(pool.key) !== pool || pool.holders > 0)
|
|
272
|
+
return;
|
|
273
|
+
if ([...pools.values()].some((other) => other !== pool && other.workspace.id === pool.workspace.id)) {
|
|
274
|
+
shut(pool);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
pool.linger = setTimeout(() => shut(pool), LINGER_MS);
|
|
278
|
+
pool.linger.unref();
|
|
279
|
+
};
|
|
280
|
+
const filesFor = (workspace) => [
|
|
281
|
+
{ path: userPath, scope: "user" },
|
|
282
|
+
...workspace.trusted && workspace.root !== null ? [{ path: join(workspace.root, ".nyte", "mcp.json"), scope: "project" }] : []
|
|
283
|
+
];
|
|
284
|
+
const open = (workspace) => {
|
|
285
|
+
const key = poolKey(workspace);
|
|
286
|
+
const known = pools.get(key);
|
|
287
|
+
if (known !== undefined)
|
|
288
|
+
return known;
|
|
289
|
+
const files = filesFor(workspace);
|
|
290
|
+
const unread = files.flatMap((file) => watched.has(file.path) ? [] : [file.path]);
|
|
291
|
+
for (const path of unread)
|
|
292
|
+
watched.set(path, watchPath2(path, { delay: WATCH_DELAY_MS }, () => changed(path)));
|
|
293
|
+
const pool = {
|
|
294
|
+
key,
|
|
295
|
+
workspace,
|
|
296
|
+
files,
|
|
297
|
+
config: { servers: new Map, issues: [] },
|
|
298
|
+
loaded: load(unread).then(() => {
|
|
299
|
+
pool.loading = false;
|
|
300
|
+
if (pools.get(key) === pool)
|
|
301
|
+
reconcile(pool);
|
|
302
|
+
}),
|
|
303
|
+
loading: true,
|
|
304
|
+
links: new Map,
|
|
305
|
+
listeners: new Set,
|
|
306
|
+
holders: 0,
|
|
307
|
+
linger: undefined
|
|
308
|
+
};
|
|
309
|
+
pools.set(key, pool);
|
|
310
|
+
return pool;
|
|
311
|
+
};
|
|
312
|
+
const held = async (workspace, run) => {
|
|
313
|
+
if (closed)
|
|
314
|
+
throw new SdkError("unavailable", "The host is closing.");
|
|
315
|
+
const pool = open(workspace);
|
|
316
|
+
pool.holders += 1;
|
|
317
|
+
idle(pool);
|
|
318
|
+
try {
|
|
319
|
+
await pool.loaded;
|
|
320
|
+
return await run(pool);
|
|
321
|
+
} finally {
|
|
322
|
+
pool.holders -= 1;
|
|
323
|
+
idle(pool);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const restart = (pool, server) => {
|
|
327
|
+
const link = pool.links.get(server);
|
|
328
|
+
if (link !== undefined) {
|
|
329
|
+
pool.links.delete(server);
|
|
330
|
+
link.close();
|
|
331
|
+
}
|
|
332
|
+
reconcile(pool);
|
|
333
|
+
};
|
|
334
|
+
const restartSignedIn = (key) => {
|
|
335
|
+
const signed = [...pools.values()].flatMap((pool) => [...pool.links].flatMap(([server, link]) => link.entry.transport.kind === "http" && oauthKey2(server, link.entry.transport.url) === key ? [{ pool, server }] : []));
|
|
336
|
+
for (const { pool, server } of signed)
|
|
337
|
+
restart(pool, server);
|
|
338
|
+
};
|
|
339
|
+
const configFor = async (workspace, replaced) => parseMcpConfig2(await Promise.all(filesFor(workspace).map(async (file) => ({
|
|
340
|
+
...file,
|
|
341
|
+
contents: replaced?.path === file.path ? { kind: "text", text: replaced.text } : await readMcpFile2(file.path)
|
|
342
|
+
}))));
|
|
343
|
+
const writing = new Map;
|
|
344
|
+
const edit = (workspace, path, server, change, check) => {
|
|
345
|
+
const queued = (writing.get(path) ?? Promise.resolve()).then(async () => {
|
|
346
|
+
const plan = await planMcpEdit2(path, change);
|
|
347
|
+
if (plan.kind !== "planned")
|
|
348
|
+
return plan;
|
|
349
|
+
if (check === "introduced-issues") {
|
|
350
|
+
const about = (config) => config.issues.filter((issue) => issue.path[1] === server).map((issue) => issue.message);
|
|
351
|
+
const before = new Set(about(await configFor(workspace)));
|
|
352
|
+
const introduced = about(await configFor(workspace, { path, text: plan.text })).find((message) => !before.has(message));
|
|
353
|
+
if (introduced !== undefined)
|
|
354
|
+
return { kind: "invalid", message: introduced };
|
|
355
|
+
}
|
|
356
|
+
await writeMcpFile2(path, plan.text);
|
|
357
|
+
if (watched.has(path))
|
|
358
|
+
contents.set(path, { kind: "text", text: plan.text });
|
|
359
|
+
for (const pool of pools.values())
|
|
360
|
+
if (pool.files.some((file) => file.path === path))
|
|
361
|
+
reconcile(pool);
|
|
362
|
+
return { kind: "updated" };
|
|
363
|
+
});
|
|
364
|
+
writing.set(path, queued.catch(() => {
|
|
365
|
+
return;
|
|
366
|
+
}));
|
|
367
|
+
return queued;
|
|
368
|
+
};
|
|
369
|
+
const signingIn = new Set;
|
|
370
|
+
const view = (pool) => ({
|
|
371
|
+
issues: [
|
|
372
|
+
...pool.config.issues,
|
|
373
|
+
...[...pool.links.values()].flatMap((link) => {
|
|
374
|
+
const state = link.state();
|
|
375
|
+
return state.kind === "connected" ? state.issues : [];
|
|
376
|
+
})
|
|
377
|
+
],
|
|
378
|
+
servers: [...pool.config.servers.values()].toSorted((a, b) => a.name.localeCompare(b.name)).map((entry) => ({
|
|
379
|
+
name: entry.name,
|
|
380
|
+
scope: entry.scope,
|
|
381
|
+
path: entry.path,
|
|
382
|
+
override: entry.override,
|
|
383
|
+
transport: entry.transport.kind === "stdio" ? { kind: "stdio", command: entry.transport.command, args: entry.transport.args } : { kind: "http", url: entry.transport.url },
|
|
384
|
+
exposure: entry.exposure,
|
|
385
|
+
description: entry.description,
|
|
386
|
+
state: entry.enabled ? serverState(entry, pool.links.get(entry.name)?.state() ?? { kind: "connecting" }) : { kind: "disabled" }
|
|
387
|
+
}))
|
|
388
|
+
});
|
|
389
|
+
return {
|
|
390
|
+
watch(workspace) {
|
|
391
|
+
return closable(() => {
|
|
392
|
+
if (closed)
|
|
393
|
+
return { pull: async () => ({ done: true, value: undefined }), close() {} };
|
|
394
|
+
const pool = open(workspace);
|
|
395
|
+
let dirty = true;
|
|
396
|
+
let ended = false;
|
|
397
|
+
let wake = null;
|
|
398
|
+
const listener = () => {
|
|
399
|
+
dirty = true;
|
|
400
|
+
wake?.();
|
|
401
|
+
};
|
|
402
|
+
pool.holders += 1;
|
|
403
|
+
idle(pool);
|
|
404
|
+
pool.listeners.add(listener);
|
|
405
|
+
return {
|
|
406
|
+
async pull() {
|
|
407
|
+
await pool.loaded;
|
|
408
|
+
while (!dirty && !ended && !closed && pools.get(pool.key) === pool)
|
|
409
|
+
await new Promise((resolve) => wake = resolve);
|
|
410
|
+
wake = null;
|
|
411
|
+
if (ended || closed || pools.get(pool.key) !== pool)
|
|
412
|
+
return { done: true, value: undefined };
|
|
413
|
+
dirty = false;
|
|
414
|
+
return { done: false, value: view(pool) };
|
|
415
|
+
},
|
|
416
|
+
close() {
|
|
417
|
+
if (ended)
|
|
418
|
+
return;
|
|
419
|
+
ended = true;
|
|
420
|
+
wake?.();
|
|
421
|
+
pool.listeners.delete(listener);
|
|
422
|
+
pool.holders -= 1;
|
|
423
|
+
idle(pool);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
},
|
|
428
|
+
tools(workspace) {
|
|
429
|
+
const pool = pools.get(poolKey(workspace));
|
|
430
|
+
return pool === undefined ? [] : [...pool.links.values()].flatMap((link) => link.tools());
|
|
431
|
+
},
|
|
432
|
+
briefs(workspace) {
|
|
433
|
+
const pool = pools.get(poolKey(workspace));
|
|
434
|
+
return pool === undefined ? [] : [...pool.links.values()].map((link) => link.brief()).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
435
|
+
},
|
|
436
|
+
async ready(workspace, signal) {
|
|
437
|
+
const watched = [...pools.values()].filter((pool) => pool.workspace.id === workspace);
|
|
438
|
+
const attempting = (pool) => [...pool.links.values()].flatMap((link) => link.attempting() ?? []);
|
|
439
|
+
const attempts = watched.flatMap((pool) => pool.loading ? [pool.loaded.then(() => Promise.all(attempting(pool))).then(() => {
|
|
440
|
+
return;
|
|
441
|
+
})] : attempting(pool));
|
|
442
|
+
if (attempts.length === 0 || signal.aborted)
|
|
443
|
+
return false;
|
|
444
|
+
const aborted = Promise.withResolvers();
|
|
445
|
+
signal.addEventListener("abort", () => aborted.resolve(), { once: true });
|
|
446
|
+
await Promise.race([Promise.all(attempts), aborted.promise]);
|
|
447
|
+
return true;
|
|
448
|
+
},
|
|
449
|
+
reconnect: (workspace, server) => held(workspace, async (pool) => {
|
|
450
|
+
const entry = pool.config.servers.get(server);
|
|
451
|
+
if (entry === undefined)
|
|
452
|
+
return { kind: "missing" };
|
|
453
|
+
if (!entry.enabled)
|
|
454
|
+
return { kind: "invalid", message: `${server} is turned off.` };
|
|
455
|
+
restart(pool, server);
|
|
456
|
+
return { kind: "updated" };
|
|
457
|
+
}),
|
|
458
|
+
async setEnabled(workspace, server, enabled) {
|
|
459
|
+
const entry = (await configFor(workspace)).servers.get(server);
|
|
460
|
+
if (entry === undefined)
|
|
461
|
+
return { kind: "missing" };
|
|
462
|
+
const override = entry.override !== null;
|
|
463
|
+
return edit(workspace, entry.override ?? entry.path, server, (servers) => {
|
|
464
|
+
const current = servers[server];
|
|
465
|
+
if (!isJsonObject(current))
|
|
466
|
+
return null;
|
|
467
|
+
const { enabled: _previous, ...rest } = current;
|
|
468
|
+
return {
|
|
469
|
+
...servers,
|
|
470
|
+
[server]: override ? { ...rest, enabled } : enabled ? rest : { ...rest, enabled: false }
|
|
471
|
+
};
|
|
472
|
+
}, "introduced-issues");
|
|
473
|
+
},
|
|
474
|
+
login(workspace, server) {
|
|
475
|
+
let begun = null;
|
|
476
|
+
const begin = () => begun ??= held(workspace, async (pool) => {
|
|
477
|
+
const entry = pool.config.servers.get(server);
|
|
478
|
+
if (entry === undefined)
|
|
479
|
+
return failedLogin(`No MCP server is named ${server}.`);
|
|
480
|
+
const { transport } = entry;
|
|
481
|
+
if (transport.kind !== "http")
|
|
482
|
+
return failedLogin(`${server} runs a command, so it has nothing to sign in to.`);
|
|
483
|
+
if (Object.keys(transport.headers).some((name) => name.toLowerCase() === "authorization"))
|
|
484
|
+
return failedLogin(`${server} sends its own Authorization header.`);
|
|
485
|
+
const key = oauthKey2(server, transport.url);
|
|
486
|
+
if (signingIn.has(key))
|
|
487
|
+
return failedLogin(`A sign-in to ${server} is already running.`);
|
|
488
|
+
let settings;
|
|
489
|
+
try {
|
|
490
|
+
settings = oauthSettings2(transport.oauth, server);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
return failedLogin(errorMessage(error));
|
|
493
|
+
}
|
|
494
|
+
signingIn.add(key);
|
|
495
|
+
pool.holders += 1;
|
|
496
|
+
return signIn2({
|
|
497
|
+
server,
|
|
498
|
+
serverUrl: transport.url,
|
|
499
|
+
store: oauthStore2(input.secrets, key),
|
|
500
|
+
settings,
|
|
501
|
+
challenge: pool.links.get(server)?.challenge() ?? null,
|
|
502
|
+
signedIn: () => restartSignedIn(key),
|
|
503
|
+
ended: () => {
|
|
504
|
+
signingIn.delete(key);
|
|
505
|
+
pool.holders -= 1;
|
|
506
|
+
idle(pool);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
return {
|
|
511
|
+
steps: () => forwarded(async () => (await begin()).steps()),
|
|
512
|
+
async answer(code) {
|
|
513
|
+
if (begun === null)
|
|
514
|
+
throw new SdkError("invalid", "Read the login's steps before answering.");
|
|
515
|
+
await (await begun).answer(code);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
},
|
|
519
|
+
async logout(workspace, server) {
|
|
520
|
+
const entry = (await configFor(workspace)).servers.get(server);
|
|
521
|
+
if (entry === undefined)
|
|
522
|
+
return { kind: "missing" };
|
|
523
|
+
if (entry.transport.kind !== "http")
|
|
524
|
+
return { kind: "invalid", message: `${server} runs a command and has no sign-in.` };
|
|
525
|
+
const key = oauthKey2(server, entry.transport.url);
|
|
526
|
+
await input.secrets.delete(key);
|
|
527
|
+
restartSignedIn(key);
|
|
528
|
+
return { kind: "updated" };
|
|
529
|
+
},
|
|
530
|
+
async add(workspace, server, config, scope) {
|
|
531
|
+
const path = filesFor(workspace).find((file) => file.scope === scope)?.path;
|
|
532
|
+
if (path === undefined)
|
|
533
|
+
return UNTRUSTED;
|
|
534
|
+
if (!isJson(config))
|
|
535
|
+
return { kind: "invalid", message: "The server's config is not JSON." };
|
|
536
|
+
return edit(workspace, path, server, (servers) => ({ ...servers, [server]: config }), "introduced-issues");
|
|
537
|
+
},
|
|
538
|
+
async remove(workspace, server, scope) {
|
|
539
|
+
const path = filesFor(workspace).find((file) => file.scope === scope)?.path;
|
|
540
|
+
if (path === undefined)
|
|
541
|
+
return UNTRUSTED;
|
|
542
|
+
return edit(workspace, path, server, (servers) => {
|
|
543
|
+
if (!(server in servers))
|
|
544
|
+
return null;
|
|
545
|
+
const { [server]: _removed, ...rest } = servers;
|
|
546
|
+
return rest;
|
|
547
|
+
}, "none");
|
|
548
|
+
},
|
|
549
|
+
async close() {
|
|
550
|
+
closed = true;
|
|
551
|
+
const live = [...pools.values()];
|
|
552
|
+
const links = live.flatMap((pool) => [...pool.links.values()]);
|
|
553
|
+
for (const pool of live)
|
|
554
|
+
shut(pool);
|
|
555
|
+
await Promise.all(links.map((link) => link.close()));
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function serverState(entry, state) {
|
|
560
|
+
if (state.kind !== "connected")
|
|
561
|
+
return state;
|
|
562
|
+
return {
|
|
563
|
+
kind: "connected",
|
|
564
|
+
tools: state.tools.map((tool) => ({
|
|
565
|
+
name: tool.name,
|
|
566
|
+
description: tool.description ?? tool.title ?? tool.name,
|
|
567
|
+
exposure: toolExposure2(entry, tool.name)
|
|
568
|
+
}))
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function notConnected(server, state) {
|
|
572
|
+
if (state.kind === "failed")
|
|
573
|
+
return `${server} is not connected: ${state.message}`;
|
|
574
|
+
if (state.kind === "needs-sign-in")
|
|
575
|
+
return `${server} needs the user to sign in again before its tools work.`;
|
|
576
|
+
return `${server} is reconnecting; try the call again shortly.`;
|
|
577
|
+
}
|
|
578
|
+
function poolKey(workspace) {
|
|
579
|
+
return JSON.stringify([workspace.id, workspace.root, workspace.trusted]);
|
|
580
|
+
}
|
|
581
|
+
function linkKey(entry) {
|
|
582
|
+
return JSON.stringify([entry.transport, entry.timeoutMs]);
|
|
583
|
+
}
|
|
584
|
+
function errorMessage(error) {
|
|
585
|
+
return error instanceof Error ? error.message : String(error);
|
|
586
|
+
}
|
|
587
|
+
function failedLogin(reason) {
|
|
588
|
+
return {
|
|
589
|
+
async* steps() {
|
|
590
|
+
yield { kind: "failed", reason };
|
|
591
|
+
},
|
|
592
|
+
answer: async () => {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export { createMcpHub2 };
|