@ai-ecoverse/slicc-agent 3.32.0 → 3.34.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/README.md +7 -2
- package/bin/agent +1 -1
- package/dist/main.js +17 -14
- package/dist/main.js.map +2 -2
- package/dist/mcp/config.js +2 -2
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/connection.js +2 -2
- package/dist/mcp/connection.js.map +1 -1
- package/dist/mcp/index.js +6 -0
- package/dist/mcp/index.js.map +2 -2
- package/dist/mcp/label.js +14 -0
- package/dist/mcp/label.js.map +7 -0
- package/dist/mcp/runtime.js +65 -4
- package/dist/mcp/runtime.js.map +2 -2
- package/dist/models/index.js +182 -0
- package/dist/models/index.js.map +7 -0
- package/dist/remote/events.js +2 -1
- package/dist/remote/events.js.map +2 -2
- package/dist/scoops/cli.js +2 -0
- package/dist/scoops/cli.js.map +2 -2
- package/dist/scoops/index.js +11 -2
- package/dist/scoops/index.js.map +2 -2
- package/dist/scoops/requests.js +1 -1
- package/dist/scoops/requests.js.map +2 -2
- package/dist/services.js.map +2 -2
- package/dist/settings.js +14 -2
- package/dist/settings.js.map +2 -2
- package/dist/spectrum/messages.js +2 -1
- package/dist/spectrum/messages.js.map +2 -2
- package/dist/types/mcp/index.d.ts +1 -1
- package/dist/types/mcp/label.d.ts +2 -0
- package/dist/types/mcp/runtime.d.ts +9 -1
- package/dist/types/models/index.d.ts +16 -0
- package/dist/types/scoops/cli.d.ts +1 -0
- package/dist/types/scoops/index.d.ts +1 -0
- package/dist/types/scoops/requests.d.ts +1 -1
- package/dist/types/services.d.ts +3 -1
- package/dist/types/settings.d.ts +2 -0
- package/package.json +2 -2
- package/packages/vfs-root/skills/agent/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -170,6 +170,11 @@ Each scoop is owned by a background durable task, its **anchor**, so Stop in its
|
|
|
170
170
|
- `agent --async …` (= `subagent spawn …`) starts a persistent scoop and prints its handle; only cones start them. `list [--agents]`, `status`, `rename`, `send [--follow-up]`, `wait [--timeout <s>] [--notify]` (30 s by default; `--notify` returns at once and brings the answers together in one `scoop-wait` lick) and `stop` manage them. From a scoop, `agent send parent "<note>"` posts a progress note to its cone as a lick, coalesced per scoop.
|
|
171
171
|
- Options: `--name`, `--agent <role>`, `--model <provider/model>`, `--thinking <level>` or `--effort low|medium|high|max`, `--tools <a,b>` (or `auto`/`full`), `--read-only <paths>`, `--system-prompt[-file]`. `--schema-b64`, `--tools output`, `--session`, `--resume`, `--image`, `--no-escalate`, `--minimal` and `--usage` answer "not supported in SLICC yet".
|
|
172
172
|
|
|
173
|
+
**The `models` command** is the same script as `agent`, installed as `models`, and reads the models and accounts the Settings picker shows (`createAgentSettings`):
|
|
174
|
+
- `models` (or `models list`) lists every model of a connected account, `*` marking the caller's current one, with its label and account; `models current` prints the current `provider/model`. Both take `--json`.
|
|
175
|
+
- `models use <provider/model> [--thinking <level>]` switches the caller's model the way the Settings picker does (`Conversation.configure`): a cone's or a scoop's own, or, in a terminal, the active cone's. An unknown id exits 1 naming the three closest ids; a model whose account isn't connected exits 1 and says to connect it in Settings.
|
|
176
|
+
- `models --help` prints the usage; an unknown verb exits 2 with it.
|
|
177
|
+
|
|
173
178
|
**Roles** are pi agent files: Markdown with a frontmatter subset (`name`, `description`, `tools`, `model`, `thinking`, `memory`, `inheritProjectContext`, `inheritGlobalContext`, …) and the role's prompt as the body, added to the scoop's instructions. The built-ins are `scout`, `worker`, `reviewer`, `oracle`, `delegate` and `gelatiere` (below). Later sources replace earlier ones by name: built-ins, then packages in the kernel's `node_modules` that name a folder in `pi-subagents.agents` or `pi.subagents.agents`, then `~/.pi/agent/agents/`. A project's `.pi/agents/` will count only in trusted folders, and folders can't be trusted yet. `~/.pi/agent/settings.json` can override roles (`subagents.agentOverrides`) and set the limits `subagents.maxLiveScoops` (16 live scoops per cone) and `subagents.maxPerTurn` (64 scoops started per cone turn), which the worker counts for both kinds.
|
|
174
179
|
|
|
175
180
|
**Isolation, for now.** The file tools of a scoop write only under its folder and `/tmp`, plus the cwd of a v6-form call, and read those, their cone's working folder (`/home` by default) and `--read-only` paths; paths are normalized, `..` included, before the check. bash isn't confined, and the scoop is told so: the guard is a guardrail, not a boundary. A scoop's commands share one process group (see Bash and files on slicc-kernel), and stopping the scoop signals it, together with any group recorded for the scoop on an older kernel.
|
|
@@ -267,7 +272,7 @@ MCP servers reachable over Streamable HTTP become native agent tools. The client
|
|
|
267
272
|
|
|
268
273
|
**Per agent.** Today every cone reads the one file in `/home` and gets every enabled server. A scoop doesn't read a file of its own: it inherits its cone's servers, and a role's `tools:` or `agent --tools` list narrows them. A list with no `mcp__` entry gets no MCP servers. A list with `mcp__docs__search` or `mcp__docs__*` keeps only those, and `codemode` is added so codemode tools stay reachable. The resource tools must be listed by name, and they reach only the servers the list names. A nested scoop without a list inherits its calling scoop's list. A `beforeTool` hook blocks the model's calls outside the list, and codemode scripts only get the tools on it. When kernel users give each cone its own home, each cone reads `~/.pi/agent/mcp.json` in that home, and scoops keep inheriting from their cone.
|
|
269
274
|
|
|
270
|
-
**Secrets.** `${NAME}` placeholders are filled from the agent's encrypted credential store, under `mcp:<server>|<hash of the URL>`, when the worker connects. A server whose secret is missing comes up as needing one, with a lick. Pointing a server at another URL leaves it without its secret. Header values never reach SQLite, the transcript, licks or `~/.pi/agent/mcp.log`, and stored secrets are scrubbed from error texts and server log lines.
|
|
275
|
+
**Secrets.** `${NAME}` placeholders are filled from the agent's encrypted credential store, under `mcp:<server>|<hash of the URL>`, when the worker connects. A server whose secret is missing comes up as needing one, with a lick. Pointing a server at another URL leaves it without its secret. Header values never reach SQLite, the transcript, licks or `~/.pi/agent/mcp.log`, and stored secrets are scrubbed from error texts and server log lines. **Settings › MCP servers** lists each server whose headers use a placeholder, under its own heading below Accounts, with the URL host. Connect takes the token, which fills every placeholder of a server that uses one name; a server whose headers use several different names can't be filled from Settings yet. The server then reconnects. A server that answers 401 shows as **Rejected**, with Reconnect, and Disconnect deletes the token. Tool cards and remote summaries label MCP calls `server · tool`, unless the name is ambiguous (a server name that starts or ends with `_`, or a name with more than two `__` separators); then they show the raw name.
|
|
271
276
|
|
|
272
277
|
**Tools.** Tools are named `mcp__<server>__<tool>` (pi's names: everything but letters, digits and `_` becomes `_`, cut to 64 characters with a hash suffix on a collision). Their exposure follows pi:
|
|
273
278
|
- `codemode` (the default): callable from codemode scripts as `tools.mcp__docs__search(args)`, which resolves to the whole `CallToolResult`, but not declared to the model. The `mcp_servers` section lists these servers with their description or the first line of the server's instructions. It also says that MCP results are data from the server, not instructions.
|
|
@@ -433,7 +438,7 @@ The built-in `sprinkles` skill teaches the files, the bridge and the command.
|
|
|
433
438
|
|
|
434
439
|
## The kernel
|
|
435
440
|
|
|
436
|
-
`@ai-ecoverse/slicc-kernel` is a peer dependency (`>=1.47.1 <2`, the first kernel with users and the H1 wasm commands; until 3.31.0 the floor was 1.26.3): the agent only calls `attachKernel`, the client end of the page kernel's port, so it must match the kernel the page runs. Seven chooses that version: slicc-bios pins the certified kernel next to the agent in `src/packages/agent/package.json`, and pnpm fills the peer from that pin. The devDependency is the lowest certified kernel the range allows (1.47.1), and the unit and integration tests run on it with the H1 wasm commands as devDependencies. A second CI job reruns the integration tests on the newest certified kernel (`NEWEST_CERTIFIED_KERNEL` in `.github/workflows/ci.yml`), with the wasm commands certified for it (`NEWEST_CERTIFIED_WASM`). Today the floor is 1.47.1 and the newest certified kernel 1.
|
|
441
|
+
`@ai-ecoverse/slicc-kernel` is a peer dependency (`>=1.47.1 <2`, the first kernel with users and the H1 wasm commands; until 3.31.0 the floor was 1.26.3): the agent only calls `attachKernel`, the client end of the page kernel's port, so it must match the kernel the page runs. Seven chooses that version: slicc-bios pins the certified kernel next to the agent in `src/packages/agent/package.json`, and pnpm fills the peer from that pin. The devDependency is the lowest certified kernel the range allows (1.47.1), and the unit and integration tests run on it with the H1 wasm commands as devDependencies. A second CI job reruns the integration tests on the newest certified kernel (`NEWEST_CERTIFIED_KERNEL` in `.github/workflows/ci.yml`), with the wasm commands certified for it (`NEWEST_CERTIFIED_WASM`). Today the floor is 1.47.1 and the newest certified kernel 1.54.9; the second job moves by hand to each newer certified kernel, so both ends of the range are tested. If the port protocol ever breaks within 1.x, a patch release narrows the upper bound. Renovate opens PRs for the kernel, `wasi-pnpm`, the `wasm-*` packages and `slicc-cdp` but never merges them, because they move only after the homescoop coordinator certifies them.
|
|
437
442
|
|
|
438
443
|
## Patched dependencies
|
|
439
444
|
|
package/bin/agent
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
dir=/var/lib/slicc/agent/requests
|
|
3
3
|
as=$(basename "$0")
|
|
4
|
-
case "$as" in subagent|sprinkle|memory|gelatiere|freezer) ;; *) as=agent ;; esac
|
|
4
|
+
case "$as" in subagent|sprinkle|memory|gelatiere|freezer|models) ;; *) as=agent ;; esac
|
|
5
5
|
mkdir -p "$dir/in" "$dir/out" 2>/dev/null
|
|
6
6
|
|
|
7
7
|
submit() {
|
package/dist/main.js
CHANGED
|
@@ -25,6 +25,7 @@ import { processGroups } from "./kernel/groups.js";
|
|
|
25
25
|
import { setupLicks } from "./licks/index.js";
|
|
26
26
|
import { setupMcp } from "./mcp/index.js";
|
|
27
27
|
import { setupMemory } from "./memory/index.js";
|
|
28
|
+
import { modelsCommand } from "./models/index.js";
|
|
28
29
|
import { transportFetch } from "./net.js";
|
|
29
30
|
import { sliccPrompt } from "./prompt.js";
|
|
30
31
|
import { identity } from "./scoops/identity.js";
|
|
@@ -46,6 +47,18 @@ import {
|
|
|
46
47
|
transportName
|
|
47
48
|
} from "./system.js";
|
|
48
49
|
import { kernelPort, serveConnections } from "./worker.js";
|
|
50
|
+
async function codemodeTools(client, options, native, extra) {
|
|
51
|
+
const settings = await client.fs.readFile(`${HOME}/.pi/agent/settings.json`).catch(() => void 0);
|
|
52
|
+
if (disabledBySettings(settings === void 0 ? void 0 : new TextDecoder().decode(settings)))
|
|
53
|
+
return [];
|
|
54
|
+
const sandbox = sandboxFactory(
|
|
55
|
+
options.codemodeWasm ?? (() => locateWasm(native, new URL(import.meta.url))),
|
|
56
|
+
options.codemodeWorker
|
|
57
|
+
);
|
|
58
|
+
return [
|
|
59
|
+
codemodeTool({ declared: CodingTools.tools, sandbox, extra })
|
|
60
|
+
];
|
|
61
|
+
}
|
|
49
62
|
async function start(scope, port, options) {
|
|
50
63
|
const attach = options.attach ?? attachKernel;
|
|
51
64
|
const client = await attach(await port);
|
|
@@ -71,21 +84,9 @@ async function start(scope, port, options) {
|
|
|
71
84
|
)
|
|
72
85
|
);
|
|
73
86
|
const memory = setupMemory(registry);
|
|
74
|
-
const settings = await client.fs.readFile(`${HOME}/.pi/agent/settings.json`).catch(() => void 0);
|
|
75
87
|
const coding = CodingTools.tools;
|
|
76
88
|
const mcp = setupMcp(registry);
|
|
77
|
-
const codemode =
|
|
78
|
-
settings === void 0 ? void 0 : new TextDecoder().decode(settings)
|
|
79
|
-
) ? [] : [
|
|
80
|
-
codemodeTool({
|
|
81
|
-
declared: coding,
|
|
82
|
-
sandbox: sandboxFactory(
|
|
83
|
-
options.codemodeWasm ?? (() => locateWasm(native, new URL(import.meta.url))),
|
|
84
|
-
options.codemodeWorker
|
|
85
|
-
),
|
|
86
|
-
extra: mcp.codemode
|
|
87
|
-
})
|
|
88
|
-
];
|
|
89
|
+
const codemode = await codemodeTools(client, options, native, mcp.codemode);
|
|
89
90
|
registry.install(codemodeExtension(codemode));
|
|
90
91
|
const licks = setupLicks(registry);
|
|
91
92
|
const scoops = setupScoops(registry, licks.licks, [...coding, ...codemode, ...memory.tools]);
|
|
@@ -165,6 +166,7 @@ async function start(scope, port, options) {
|
|
|
165
166
|
title: titler(models, settingsText(home, HOME))
|
|
166
167
|
});
|
|
167
168
|
licks.licks.guard(freezer.frozenTarget);
|
|
169
|
+
const settings = await createAgentSettings(models, credentials, providers, { mcp: mcpRuntime });
|
|
168
170
|
const runtime = await scoops.attach(
|
|
169
171
|
{
|
|
170
172
|
harness: agent.harness,
|
|
@@ -178,6 +180,7 @@ async function start(scope, port, options) {
|
|
|
178
180
|
memory: memoryRuntime.command,
|
|
179
181
|
gelatiere: gelatiere.command,
|
|
180
182
|
freezer: freezer.command,
|
|
183
|
+
models: modelsCommand({ agents: agent.agents, settings, catalog: models }),
|
|
181
184
|
mcp: mcp.direct,
|
|
182
185
|
...options.assets ? { assets: options.assets } : {}
|
|
183
186
|
},
|
|
@@ -199,7 +202,7 @@ async function start(scope, port, options) {
|
|
|
199
202
|
await memoryRuntime.reload(BACKGROUND_CONTEXT).catch(() => void 0);
|
|
200
203
|
const changes = attachChanges({ agents: agent.agents, env: home, reloadMs: CHANGES_MS });
|
|
201
204
|
return hostAgent(agent, {
|
|
202
|
-
settings
|
|
205
|
+
settings,
|
|
203
206
|
licks: { licks: licks.licks, sources },
|
|
204
207
|
scoops: { scoops: scoops.scoops, runtime },
|
|
205
208
|
skills: skillsRuntime,
|
package/dist/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["import { attachKernel } from '@ai-ecoverse/slicc-kernel';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { CredentialStore, Provider } from '@earendil-works/pi-ai';\nimport {\n createRegistry,\n type ModelRef,\n type Storage,\n type ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport { CodingTools } from '@earendil-works/pi-durable/tools';\nimport { type Agent, openAgent } from './agent.ts';\nimport { attachChanges, CHANGES_MS } from './changes/index.ts';\nimport {\n codemodeExtension,\n codemodeTool,\n disabledBySettings,\n locateWasm,\n sandboxFactory,\n} from './codemode/index.ts';\nimport { EncryptedCredentialStore } from './credentials.ts';\nimport { attachFreezer } from './freezer/index.ts';\nimport { settingsText, titler } from './freezer/title.ts';\nimport { lateGelatiere } from './gelatiere/index.ts';\nimport { type AgentHost, hostAgent } from './host.ts';\nimport { imageReads } from './images/index.ts';\nimport { createActivity } from './kernel/activity.ts';\nimport type { KernelClient } from './kernel/client.ts';\nimport { HOME, kernelEnvironment, SliccKernelEnv } from './kernel/env.ts';\nimport { processGroups } from './kernel/groups.ts';\nimport { setupLicks } from './licks/index.ts';\nimport { setupMcp } from './mcp/index.ts';\nimport { setupMemory } from './memory/index.ts';\nimport { type Transport, transportFetch } from './net.ts';\nimport { sliccPrompt } from './prompt.ts';\nimport { identity } from './scoops/identity.ts';\nimport { type Assets, packageAssets, type ScoopsRuntime, setupScoops } from './scoops/index.ts';\nimport {\n createAgentSettings,\n createSliccModels,\n DEFAULT_MODEL,\n sliccProviders,\n} from './settings.ts';\nimport { RELOAD_MS, setupSkills } from './skills/index.ts';\nimport { setupSprinkles } from './sprinkles/index.ts';\nimport { openOpfsSqliteStorage } from './sqlite.ts';\nimport {\n agentVersion,\n browserConnected,\n commandsOnPath,\n kernelBoot,\n transportName,\n} from './system.ts';\nimport { kernelPort, serveConnections, type WorkerScope } from './worker.ts';\n\nexport interface AgentWorkerScope extends WorkerScope {\n fetch: typeof fetch;\n location: { origin: string };\n}\n\nexport type KernelAttach = (port: MessagePort) => Promise<KernelClient & { transport: Transport }>;\n\nexport interface AgentWorkerOptions {\n model?: ModelRef;\n providers?: Provider[];\n attach?: KernelAttach;\n credentials?: () => Promise<CredentialStore>;\n storage?: () => Promise<Storage>;\n assets?: Assets;\n codemodeWasm?: () => Promise<WebAssembly.Module | undefined>;\n codemodeWorker?: string | URL;\n catalog?: string;\n}\n\nasync function start(\n scope: AgentWorkerScope,\n port: Promise<MessagePort>,\n options: AgentWorkerOptions\n): Promise<AgentHost> {\n const attach: KernelAttach = options.attach ?? attachKernel;\n const client = await attach(await port);\n const native = scope.fetch.bind(scope);\n scope.fetch = transportFetch(client.transport, scope.location.origin, native);\n const facts = {\n version: await agentVersion((url) => native(url)),\n commands: await commandsOnPath(client),\n transport: transportName(client.transport),\n browser: await browserConnected(client),\n };\n const credentials = await (options.credentials ?? (() => EncryptedCredentialStore.open()))();\n const providers = options.providers ?? sliccProviders();\n const models = createSliccModels(credentials, providers);\n const registry = createRegistry();\n registry.install(CodingTools);\n registry.install(sliccPrompt(facts));\n registry.install(\n imageReads(\n (CodingTools.tools as readonly ToolRegistration[]).find(\n (tool) => tool.name === 'read'\n ) as ToolRegistration\n )\n );\n const memory = setupMemory(registry);\n const
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,OAIK;AACP,SAAS,mBAAmB;AAC5B,SAAqB,iBAAiB;AACtC,SAAS,eAAe,kBAAkB;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gCAAgC;AACzC,SAAS,qBAAqB;AAC9B,SAAS,cAAc,cAAc;AACrC,SAAS,qBAAqB;AAC9B,SAAyB,iBAAiB;AAC1C,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAE/B,SAAS,MAAM,mBAAmB,sBAAsB;AACxD,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAyB,sBAAsB;AAC/C,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAsB,eAAmC,mBAAmB;AAC5E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAAW,mBAAmB;AACvC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAY,wBAA0C;AAqB/D,eAAe,MACb,OACA,MACA,SACoB;AACpB,QAAM,SAAuB,QAAQ,UAAU;AAC/C,QAAM,SAAS,MAAM,OAAO,MAAM,IAAI;AACtC,QAAM,SAAS,MAAM,MAAM,KAAK,KAAK;AACrC,QAAM,QAAQ,eAAe,OAAO,WAAW,MAAM,SAAS,QAAQ,MAAM;AAC5E,QAAM,QAAQ;AAAA,IACZ,SAAS,MAAM,aAAa,CAAC,QAAQ,OAAO,GAAG,CAAC;AAAA,IAChD,UAAU,MAAM,eAAe,MAAM;AAAA,IACrC,WAAW,cAAc,OAAO,SAAS;AAAA,IACzC,SAAS,MAAM,iBAAiB,MAAM;AAAA,EACxC;AACA,QAAM,cAAc,OAAO,QAAQ,gBAAgB,MAAM,yBAAyB,KAAK,IAAI;AAC3F,QAAM,YAAY,QAAQ,aAAa,eAAe;AACtD,QAAM,SAAS,kBAAkB,aAAa,SAAS;AACvD,QAAM,WAAW,eAAe;AAChC,WAAS,QAAQ,WAAW;AAC5B,WAAS,QAAQ,YAAY,KAAK,CAAC;AACnC,WAAS;AAAA,IACP;AAAA,MACG,YAAY,MAAsC;AAAA,QACjD,CAAC,SAAS,KAAK,SAAS;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACA,QAAM,SAAS,YAAY,QAAQ;AACnC,QAAM,
|
|
4
|
+
"sourcesContent": ["import { attachKernel } from '@ai-ecoverse/slicc-kernel';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { CredentialStore, Provider } from '@earendil-works/pi-ai';\nimport {\n createRegistry,\n type ModelRef,\n type Storage,\n type ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport { CodingTools } from '@earendil-works/pi-durable/tools';\nimport { type Agent, openAgent } from './agent.ts';\nimport { attachChanges, CHANGES_MS } from './changes/index.ts';\nimport {\n codemodeExtension,\n codemodeTool,\n disabledBySettings,\n locateWasm,\n sandboxFactory,\n} from './codemode/index.ts';\nimport { EncryptedCredentialStore } from './credentials.ts';\nimport { attachFreezer } from './freezer/index.ts';\nimport { settingsText, titler } from './freezer/title.ts';\nimport { lateGelatiere } from './gelatiere/index.ts';\nimport { type AgentHost, hostAgent } from './host.ts';\nimport { imageReads } from './images/index.ts';\nimport { createActivity } from './kernel/activity.ts';\nimport type { KernelClient } from './kernel/client.ts';\nimport { HOME, kernelEnvironment, SliccKernelEnv } from './kernel/env.ts';\nimport { processGroups } from './kernel/groups.ts';\nimport { setupLicks } from './licks/index.ts';\nimport { setupMcp } from './mcp/index.ts';\nimport { setupMemory } from './memory/index.ts';\nimport { modelsCommand } from './models/index.ts';\nimport { type Transport, transportFetch } from './net.ts';\nimport { sliccPrompt } from './prompt.ts';\nimport { identity } from './scoops/identity.ts';\nimport { type Assets, packageAssets, type ScoopsRuntime, setupScoops } from './scoops/index.ts';\nimport {\n createAgentSettings,\n createSliccModels,\n DEFAULT_MODEL,\n sliccProviders,\n} from './settings.ts';\nimport { RELOAD_MS, setupSkills } from './skills/index.ts';\nimport { setupSprinkles } from './sprinkles/index.ts';\nimport { openOpfsSqliteStorage } from './sqlite.ts';\nimport {\n agentVersion,\n browserConnected,\n commandsOnPath,\n kernelBoot,\n transportName,\n} from './system.ts';\nimport { kernelPort, serveConnections, type WorkerScope } from './worker.ts';\n\nexport interface AgentWorkerScope extends WorkerScope {\n fetch: typeof fetch;\n location: { origin: string };\n}\n\nexport type KernelAttach = (port: MessagePort) => Promise<KernelClient & { transport: Transport }>;\n\nexport interface AgentWorkerOptions {\n model?: ModelRef;\n providers?: Provider[];\n attach?: KernelAttach;\n credentials?: () => Promise<CredentialStore>;\n storage?: () => Promise<Storage>;\n assets?: Assets;\n codemodeWasm?: () => Promise<WebAssembly.Module | undefined>;\n codemodeWorker?: string | URL;\n catalog?: string;\n}\n\nasync function codemodeTools(\n client: KernelClient,\n options: AgentWorkerOptions,\n native: typeof fetch,\n extra: Parameters<typeof codemodeTool>[0]['extra']\n) {\n const settings = await client.fs\n .readFile(`${HOME}/.pi/agent/settings.json`)\n .catch(() => undefined);\n if (disabledBySettings(settings === undefined ? undefined : new TextDecoder().decode(settings)))\n return [];\n const sandbox = sandboxFactory(\n options.codemodeWasm ?? (() => locateWasm(native, new URL(import.meta.url))),\n options.codemodeWorker\n );\n return [\n codemodeTool({ declared: CodingTools.tools as readonly ToolRegistration[], sandbox, extra }),\n ];\n}\n\nasync function start(\n scope: AgentWorkerScope,\n port: Promise<MessagePort>,\n options: AgentWorkerOptions\n): Promise<AgentHost> {\n const attach: KernelAttach = options.attach ?? attachKernel;\n const client = await attach(await port);\n const native = scope.fetch.bind(scope);\n scope.fetch = transportFetch(client.transport, scope.location.origin, native);\n const facts = {\n version: await agentVersion((url) => native(url)),\n commands: await commandsOnPath(client),\n transport: transportName(client.transport),\n browser: await browserConnected(client),\n };\n const credentials = await (options.credentials ?? (() => EncryptedCredentialStore.open()))();\n const providers = options.providers ?? sliccProviders();\n const models = createSliccModels(credentials, providers);\n const registry = createRegistry();\n registry.install(CodingTools);\n registry.install(sliccPrompt(facts));\n registry.install(\n imageReads(\n (CodingTools.tools as readonly ToolRegistration[]).find(\n (tool) => tool.name === 'read'\n ) as ToolRegistration\n )\n );\n const memory = setupMemory(registry);\n const coding = CodingTools.tools as readonly ToolRegistration[];\n const mcp = setupMcp(registry);\n const codemode = await codemodeTools(client, options, native, mcp.codemode);\n registry.install(codemodeExtension(codemode));\n const licks = setupLicks(registry);\n const scoops = setupScoops(registry, licks.licks, [...coding, ...codemode, ...memory.tools]);\n const skills = setupSkills(registry, licks.licks);\n const activity = createActivity();\n const groups = processGroups(client);\n const model = options.model ?? DEFAULT_MODEL;\n let opened: Agent | undefined;\n const environment = kernelEnvironment(client, {\n activity,\n groups,\n identify: identity(() => opened?.agents, model),\n });\n const agent = await openAgent({\n models,\n model,\n storage: await (options.storage ?? (() => openOpfsSqliteStorage()))(),\n registry,\n env: environment,\n });\n opened = agent;\n const home = new SliccKernelEnv(client, { cwd: HOME });\n const sources = licks.attach(agent, { env: home, home: HOME, activity });\n await sources.start(BACKGROUND_CONTEXT);\n await sources.boot(\n { version: facts.version, boot: await kernelBoot(client) },\n BACKGROUND_CONTEXT\n );\n const skillsRuntime = await skills.attach(\n { env: home, home: HOME, assets: options.assets ?? packageAssets(), reloadMs: RELOAD_MS },\n BACKGROUND_CONTEXT\n );\n const mcpRuntime = await mcp.attach(\n {\n env: home,\n home: HOME,\n credentials,\n licks: licks.licks,\n fetch: scope.fetch,\n version: facts.version,\n reloadMs: RELOAD_MS,\n codemode: codemode.length > 0,\n cors: client.transport.traits?.crossOrigin === 'cors',\n },\n BACKGROUND_CONTEXT\n );\n const gelatiere = lateGelatiere();\n const sprinkles = await setupSprinkles().attach(\n {\n intercept: gelatiere.intercept,\n harness: agent.harness,\n agents: agent.agents,\n licks: licks.licks,\n env: home,\n assets: options.assets ?? packageAssets(),\n reloadMs: RELOAD_MS,\n },\n BACKGROUND_CONTEXT\n );\n let roles: ScoopsRuntime | undefined;\n const memoryRuntime = await memory.attach(\n {\n harness: agent.harness,\n agents: agent.agents,\n env: home,\n home: HOME,\n reloadMs: RELOAD_MS,\n roles: async (using) => roles?.roles(using),\n },\n BACKGROUND_CONTEXT\n );\n const freezer = attachFreezer({\n harness: agent.harness,\n agents: agent.agents,\n env: home,\n extract: memory.extract,\n title: titler(models, settingsText(home, HOME)),\n });\n licks.licks.guard(freezer.frozenTarget);\n const settings = await createAgentSettings(models, credentials, providers, { mcp: mcpRuntime });\n const runtime = await scoops.attach(\n {\n harness: agent.harness,\n agents: agent.agents,\n groups,\n env: home,\n home: HOME,\n alive: alive(client),\n reads: skillsRuntime.dirs,\n sprinkle: sprinkles.command,\n memory: memoryRuntime.command,\n gelatiere: gelatiere.command,\n freezer: freezer.command,\n models: modelsCommand({ agents: agent.agents, settings, catalog: models }),\n mcp: mcp.direct,\n ...(options.assets ? { assets: options.assets } : {}),\n },\n BACKGROUND_CONTEXT\n );\n roles = runtime;\n gelatiere.attach({\n agents: agent.agents,\n licks: licks.licks,\n env: home,\n home: HOME,\n scoops: scoops.scoops,\n roles: runtime.roles,\n assets: options.assets ?? packageAssets(),\n fetch: scope.fetch,\n skills: skillsRuntime.skills,\n ...(options.catalog ? { catalog: options.catalog } : {}),\n });\n await memoryRuntime.reload(BACKGROUND_CONTEXT).catch(() => undefined);\n const changes = attachChanges({ agents: agent.agents, env: home, reloadMs: CHANGES_MS });\n return hostAgent(agent, {\n settings,\n licks: { licks: licks.licks, sources },\n scoops: { scoops: scoops.scoops, runtime },\n skills: skillsRuntime,\n sprinkles,\n memory: memoryRuntime,\n freezer,\n changes,\n mcp: mcpRuntime,\n });\n}\n\nexport function alive(client: KernelClient) {\n return async (pid: number): Promise<boolean> => {\n if (!client.ps) return true;\n return (await client.ps()).some((process) => process.pid === pid);\n };\n}\n\nexport function runAgentWorker(\n scope: AgentWorkerScope,\n options: AgentWorkerOptions = {}\n): Promise<AgentHost> {\n const host = start(scope, kernelPort(scope), options);\n host.catch(() => undefined);\n serveConnections(scope, host);\n return host;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/mcp/config.js
CHANGED
|
@@ -56,7 +56,7 @@ function checkHeaders(name, headers) {
|
|
|
56
56
|
if (literalSecret(header, value))
|
|
57
57
|
return fail(
|
|
58
58
|
name,
|
|
59
|
-
`headers.${header} holds a literal secret. mcp.json is readable by every agent: write \${NAME} instead, and add the value in Settings \u203A
|
|
59
|
+
`headers.${header} holds a literal secret. mcp.json is readable by every agent: write \${NAME} instead, and add the value in Settings \u203A MCP servers`,
|
|
60
60
|
`headers.${header}`
|
|
61
61
|
);
|
|
62
62
|
}
|
|
@@ -101,7 +101,7 @@ function checkTransport(name, value) {
|
|
|
101
101
|
if (secret)
|
|
102
102
|
return fail(
|
|
103
103
|
name,
|
|
104
|
-
`${secret} holds a literal secret. Put it in a header as \${NAME} and add the value in Settings \u203A
|
|
104
|
+
`${secret} holds a literal secret. Put it in a header as \${NAME} and add the value in Settings \u203A MCP servers`,
|
|
105
105
|
"url"
|
|
106
106
|
);
|
|
107
107
|
return void 0;
|
package/dist/mcp/config.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/config.ts"],
|
|
4
|
-
"sourcesContent": ["import { hash } from '../licks/sources.ts';\n\nexport type McpExposure = 'codemode' | 'deferred' | 'direct' | 'hidden';\n\nexport interface McpServerConfig {\n url: string;\n headers?: Record<string, string>;\n exposure?: McpExposure;\n toolExposure?: Record<string, McpExposure>;\n enabled?: boolean;\n timeout?: number;\n description?: string;\n}\n\nexport interface McpServerEntry {\n name: string;\n config: McpServerConfig;\n}\n\nexport interface McpConfigProblem {\n server?: string;\n field?: string;\n message: string;\n}\n\nexport interface McpConfig {\n servers: McpServerEntry[];\n problems: McpConfigProblem[];\n}\n\nconst SERVER_NAME = /^[A-Za-z0-9_-]+$/;\nconst EXPOSURES: readonly string[] = ['codemode', 'deferred', 'direct', 'hidden'];\nconst ALIASES: Record<string, McpExposure> = { 'codemode-deferred': 'codemode' };\nconst ACCEPTED = new Set([\n 'type',\n 'url',\n 'headers',\n 'exposure',\n 'toolExposure',\n 'enabled',\n 'timeout',\n 'description',\n]);\nconst SECRET_HEADER = /authorization|cookie|token|key|secret|password|session/i;\nconst SECRET_PARAM = /token|key|secret|password|signature|sig|auth|code/i;\nconst PLACEHOLDER = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\nconst SCHEME = /^\\s*(?:bearer|basic|token|apikey|api-key)\\s+/i;\nconst MAX_TOOL_NAME = 64;\nexport const DEFAULT_TIMEOUT_SECONDS = 60;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction alias(value: unknown): unknown {\n return typeof value === 'string' ? (ALIASES[value] ?? value) : value;\n}\n\nfunction isExposure(value: unknown): value is McpExposure {\n return typeof value === 'string' && EXPOSURES.includes(value);\n}\n\nexport function placeholders(value: string): string[] {\n return [...value.matchAll(PLACEHOLDER)].map((match) => match[1] as string);\n}\n\nexport function literalSecret(header: string, value: string): boolean {\n if (!SECRET_HEADER.test(header)) return false;\n return value.replace(PLACEHOLDER, '').replace(SCHEME, '').trim() !== '';\n}\n\nfunction urlSecret(url: URL): string | undefined {\n if (url.username || url.password) return 'url';\n for (const name of url.searchParams.keys()) if (SECRET_PARAM.test(name)) return `url ?${name}`;\n return undefined;\n}\n\ntype Checked = McpServerConfig | McpConfigProblem;\n\nfunction fail(server: string, message: string, field?: string): McpConfigProblem {\n return { server, ...(field ? { field } : {}), message };\n}\n\nfunction checkHeaders(name: string, headers: unknown): McpConfigProblem | undefined {\n if (headers === undefined) return undefined;\n if (!isRecord(headers) || !Object.values(headers).every((value) => typeof value === 'string'))\n return fail(name, 'headers must map names to strings', 'headers');\n for (const [header, value] of Object.entries(headers as Record<string, string>)) {\n if (value.trim().startsWith('!'))\n return fail(\n name,\n `headers.${header} runs a command, which SLICC can't do`,\n `headers.${header}`\n );\n if (literalSecret(header, value))\n return fail(\n name,\n `headers.${header} holds a literal secret. mcp.json is readable by every agent: write \\${NAME} instead, and add the value in Settings \u203A
|
|
4
|
+
"sourcesContent": ["import { hash } from '../licks/sources.ts';\n\nexport type McpExposure = 'codemode' | 'deferred' | 'direct' | 'hidden';\n\nexport interface McpServerConfig {\n url: string;\n headers?: Record<string, string>;\n exposure?: McpExposure;\n toolExposure?: Record<string, McpExposure>;\n enabled?: boolean;\n timeout?: number;\n description?: string;\n}\n\nexport interface McpServerEntry {\n name: string;\n config: McpServerConfig;\n}\n\nexport interface McpConfigProblem {\n server?: string;\n field?: string;\n message: string;\n}\n\nexport interface McpConfig {\n servers: McpServerEntry[];\n problems: McpConfigProblem[];\n}\n\nconst SERVER_NAME = /^[A-Za-z0-9_-]+$/;\nconst EXPOSURES: readonly string[] = ['codemode', 'deferred', 'direct', 'hidden'];\nconst ALIASES: Record<string, McpExposure> = { 'codemode-deferred': 'codemode' };\nconst ACCEPTED = new Set([\n 'type',\n 'url',\n 'headers',\n 'exposure',\n 'toolExposure',\n 'enabled',\n 'timeout',\n 'description',\n]);\nconst SECRET_HEADER = /authorization|cookie|token|key|secret|password|session/i;\nconst SECRET_PARAM = /token|key|secret|password|signature|sig|auth|code/i;\nconst PLACEHOLDER = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\nconst SCHEME = /^\\s*(?:bearer|basic|token|apikey|api-key)\\s+/i;\nconst MAX_TOOL_NAME = 64;\nexport const DEFAULT_TIMEOUT_SECONDS = 60;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction alias(value: unknown): unknown {\n return typeof value === 'string' ? (ALIASES[value] ?? value) : value;\n}\n\nfunction isExposure(value: unknown): value is McpExposure {\n return typeof value === 'string' && EXPOSURES.includes(value);\n}\n\nexport function placeholders(value: string): string[] {\n return [...value.matchAll(PLACEHOLDER)].map((match) => match[1] as string);\n}\n\nexport function literalSecret(header: string, value: string): boolean {\n if (!SECRET_HEADER.test(header)) return false;\n return value.replace(PLACEHOLDER, '').replace(SCHEME, '').trim() !== '';\n}\n\nfunction urlSecret(url: URL): string | undefined {\n if (url.username || url.password) return 'url';\n for (const name of url.searchParams.keys()) if (SECRET_PARAM.test(name)) return `url ?${name}`;\n return undefined;\n}\n\ntype Checked = McpServerConfig | McpConfigProblem;\n\nfunction fail(server: string, message: string, field?: string): McpConfigProblem {\n return { server, ...(field ? { field } : {}), message };\n}\n\nfunction checkHeaders(name: string, headers: unknown): McpConfigProblem | undefined {\n if (headers === undefined) return undefined;\n if (!isRecord(headers) || !Object.values(headers).every((value) => typeof value === 'string'))\n return fail(name, 'headers must map names to strings', 'headers');\n for (const [header, value] of Object.entries(headers as Record<string, string>)) {\n if (value.trim().startsWith('!'))\n return fail(\n name,\n `headers.${header} runs a command, which SLICC can't do`,\n `headers.${header}`\n );\n if (literalSecret(header, value))\n return fail(\n name,\n `headers.${header} holds a literal secret. mcp.json is readable by every agent: write \\${NAME} instead, and add the value in Settings \u203A MCP servers`,\n `headers.${header}`\n );\n }\n return undefined;\n}\n\nfunction checkExposures(\n name: string,\n value: Record<string, unknown>\n): McpConfigProblem | undefined {\n const allowed = EXPOSURES.map((item) => `\"${item}\"`).join(', ');\n if (value.exposure !== undefined && !isExposure(value.exposure))\n return fail(name, `exposure must be one of ${allowed}`, 'exposure');\n if (value.toolExposure === undefined) return undefined;\n if (!isRecord(value.toolExposure))\n return fail(name, 'toolExposure must map tool names to exposures', 'toolExposure');\n for (const [tool, exposure] of Object.entries(value.toolExposure))\n if (!isExposure(exposure))\n return fail(name, `toolExposure \"${tool}\" must be one of ${allowed}`, `toolExposure.${tool}`);\n return undefined;\n}\n\nfunction checkFields(name: string, value: Record<string, unknown>): McpConfigProblem | undefined {\n if (value.enabled !== undefined && typeof value.enabled !== 'boolean')\n return fail(name, 'enabled must be a boolean', 'enabled');\n if (value.description !== undefined && typeof value.description !== 'string')\n return fail(name, 'description must be a string', 'description');\n if (value.timeout !== undefined && (typeof value.timeout !== 'number' || !(value.timeout > 0)))\n return fail(name, 'timeout must be a positive number of seconds', 'timeout');\n return undefined;\n}\n\nfunction checkTransport(\n name: string,\n value: Record<string, unknown>\n): McpConfigProblem | undefined {\n if (value.command !== undefined || value.type === 'stdio')\n return fail(name, 'stdio servers need slicc-kernel#68, so this one is skipped', 'command');\n if (value.type === 'sse')\n return fail(\n name,\n 'the legacy SSE transport is not supported; use the streamable HTTP URL',\n 'type'\n );\n if (value.type !== undefined && value.type !== 'http' && value.type !== 'streamable-http')\n return fail(name, 'type must be \"http\" or \"streamable-http\"', 'type');\n if (typeof value.url !== 'string') return fail(name, 'needs a \"url\" (streamable HTTP)', 'url');\n if (!URL.canParse(value.url) || !/^https?:$/.test(new URL(value.url).protocol))\n return fail(name, 'url must be an http or https URL', 'url');\n const secret = urlSecret(new URL(value.url));\n if (secret)\n return fail(\n name,\n `${secret} holds a literal secret. Put it in a header as \\${NAME} and add the value in Settings \u203A MCP servers`,\n 'url'\n );\n return undefined;\n}\n\nexport function validateServer(name: string, raw: unknown): Checked {\n if (!SERVER_NAME.test(name))\n return fail(name, `invalid server name \"${name}\" (use letters, digits, \"_\" and \"-\")`);\n if (!isRecord(raw)) return fail(name, 'must be an object');\n const value: Record<string, unknown> = { ...raw, exposure: alias(raw.exposure) };\n if (raw.exposure === undefined) delete value.exposure;\n if (isRecord(raw.toolExposure))\n value.toolExposure = Object.fromEntries(\n Object.entries(raw.toolExposure).map(([tool, exposure]) => [tool, alias(exposure)])\n );\n for (const key of Object.keys(value))\n if (key === 'oauth' || key === 'auth')\n return fail(name, `${key} is not supported in SLICC yet`, key);\n const problem =\n checkTransport(name, value) ??\n checkHeaders(name, value.headers) ??\n checkExposures(name, value) ??\n checkFields(name, value);\n if (problem) return problem;\n const config: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value))\n if (ACCEPTED.has(key) && key !== 'type') config[key] = entry;\n return config as unknown as McpServerConfig;\n}\n\nfunction canonical(name: string): string {\n return name.replace(/-/g, '_');\n}\n\nexport function parseMcpConfig(text: string | undefined): McpConfig {\n if (text === undefined || text.trim() === '') return { servers: [], problems: [] };\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (error) {\n return { servers: [], problems: [{ message: `not valid JSON: ${(error as Error).message}` }] };\n }\n if (!isRecord(parsed) || (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers)))\n return { servers: [], problems: [{ message: 'mcpServers must be an object' }] };\n const servers: McpServerEntry[] = [];\n const problems: McpConfigProblem[] = [];\n const seen = new Set<string>();\n for (const [name, raw] of Object.entries((parsed.mcpServers ?? {}) as Record<string, unknown>)) {\n const checked = validateServer(name, raw);\n if ('message' in checked) {\n problems.push(checked);\n continue;\n }\n if (seen.has(canonical(name))) {\n problems.push(fail(name, 'names that differ only in \"-\" and \"_\" count as the same server'));\n continue;\n }\n seen.add(canonical(name));\n servers.push({ name, config: checked });\n }\n return { servers, problems };\n}\n\nfunction patternRegExp(pattern: string): RegExp {\n const source = pattern\n .split('*')\n .map((part) => part.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*');\n return new RegExp(`^${source}$`);\n}\n\nexport function nameMatcher(entries: readonly string[]): (name: string) => boolean {\n const names = new Set(entries.filter((entry) => !entry.includes('*')));\n const patterns = entries.filter((entry) => entry.includes('*')).map(patternRegExp);\n return (name) => names.has(name) || patterns.some((pattern) => pattern.test(name));\n}\n\nexport function toolExposure(config: McpServerConfig, tool: string): McpExposure {\n const overrides = config.toolExposure ?? {};\n const exact = overrides[tool];\n if (exact !== undefined) return exact;\n for (const [pattern, exposure] of Object.entries(overrides))\n if (pattern.includes('*') && patternRegExp(pattern).test(tool)) return exposure;\n return config.exposure ?? 'codemode';\n}\n\nexport function mcpNamespace(server: string): string {\n return `mcp__${canonical(server)}`;\n}\n\nexport function mcpToolName(\n server: string,\n tool: string,\n taken: (name: string) => boolean = () => false\n): string {\n const name = `mcp__${server}__${tool}`.replace(/[^A-Za-z0-9_]/g, '_');\n if (name.length <= MAX_TOOL_NAME && !taken(name)) return name;\n const suffix = hash(`${server}\\0${tool}`).padStart(7, '0').slice(0, 7);\n return `${name.slice(0, MAX_TOOL_NAME - suffix.length - 1)}_${suffix}`;\n}\n\nexport function sameConfig(a: McpServerConfig, b: McpServerConfig): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n"],
|
|
5
5
|
"mappings": "AAAA,SAAS,YAAY;AA8BrB,MAAM,cAAc;AACpB,MAAM,YAA+B,CAAC,YAAY,YAAY,UAAU,QAAQ;AAChF,MAAM,UAAuC,EAAE,qBAAqB,WAAW;AAC/E,MAAM,WAAW,oBAAI,IAAI;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,MAAM,gBAAgB;AACtB,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,SAAS;AACf,MAAM,gBAAgB;AACf,MAAM,0BAA0B;AAEvC,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,MAAM,OAAyB;AACtC,SAAO,OAAO,UAAU,WAAY,QAAQ,KAAK,KAAK,QAAS;AACjE;AAEA,SAAS,WAAW,OAAsC;AACxD,SAAO,OAAO,UAAU,YAAY,UAAU,SAAS,KAAK;AAC9D;AAEO,SAAS,aAAa,OAAyB;AACpD,SAAO,CAAC,GAAG,MAAM,SAAS,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,MAAM,CAAC,CAAW;AAC3E;AAEO,SAAS,cAAc,QAAgB,OAAwB;AACpE,MAAI,CAAC,cAAc,KAAK,MAAM,EAAG,QAAO;AACxC,SAAO,MAAM,QAAQ,aAAa,EAAE,EAAE,QAAQ,QAAQ,EAAE,EAAE,KAAK,MAAM;AACvE;AAEA,SAAS,UAAU,KAA8B;AAC/C,MAAI,IAAI,YAAY,IAAI,SAAU,QAAO;AACzC,aAAW,QAAQ,IAAI,aAAa,KAAK,EAAG,KAAI,aAAa,KAAK,IAAI,EAAG,QAAO,QAAQ,IAAI;AAC5F,SAAO;AACT;AAIA,SAAS,KAAK,QAAgB,SAAiB,OAAkC;AAC/E,SAAO,EAAE,QAAQ,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAI,QAAQ;AACxD;AAEA,SAAS,aAAa,MAAc,SAAgD;AAClF,MAAI,YAAY,OAAW,QAAO;AAClC,MAAI,CAAC,SAAS,OAAO,KAAK,CAAC,OAAO,OAAO,OAAO,EAAE,MAAM,CAAC,UAAU,OAAO,UAAU,QAAQ;AAC1F,WAAO,KAAK,MAAM,qCAAqC,SAAS;AAClE,aAAW,CAAC,QAAQ,KAAK,KAAK,OAAO,QAAQ,OAAiC,GAAG;AAC/E,QAAI,MAAM,KAAK,EAAE,WAAW,GAAG;AAC7B,aAAO;AAAA,QACL;AAAA,QACA,WAAW,MAAM;AAAA,QACjB,WAAW,MAAM;AAAA,MACnB;AACF,QAAI,cAAc,QAAQ,KAAK;AAC7B,aAAO;AAAA,QACL;AAAA,QACA,WAAW,MAAM;AAAA,QACjB,WAAW,MAAM;AAAA,MACnB;AAAA,EACJ;AACA,SAAO;AACT;AAEA,SAAS,eACP,MACA,OAC8B;AAC9B,QAAM,UAAU,UAAU,IAAI,CAAC,SAAS,IAAI,IAAI,GAAG,EAAE,KAAK,IAAI;AAC9D,MAAI,MAAM,aAAa,UAAa,CAAC,WAAW,MAAM,QAAQ;AAC5D,WAAO,KAAK,MAAM,2BAA2B,OAAO,IAAI,UAAU;AACpE,MAAI,MAAM,iBAAiB,OAAW,QAAO;AAC7C,MAAI,CAAC,SAAS,MAAM,YAAY;AAC9B,WAAO,KAAK,MAAM,iDAAiD,cAAc;AACnF,aAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,MAAM,YAAY;AAC9D,QAAI,CAAC,WAAW,QAAQ;AACtB,aAAO,KAAK,MAAM,iBAAiB,IAAI,oBAAoB,OAAO,IAAI,gBAAgB,IAAI,EAAE;AAChG,SAAO;AACT;AAEA,SAAS,YAAY,MAAc,OAA8D;AAC/F,MAAI,MAAM,YAAY,UAAa,OAAO,MAAM,YAAY;AAC1D,WAAO,KAAK,MAAM,6BAA6B,SAAS;AAC1D,MAAI,MAAM,gBAAgB,UAAa,OAAO,MAAM,gBAAgB;AAClE,WAAO,KAAK,MAAM,gCAAgC,aAAa;AACjE,MAAI,MAAM,YAAY,WAAc,OAAO,MAAM,YAAY,YAAY,EAAE,MAAM,UAAU;AACzF,WAAO,KAAK,MAAM,gDAAgD,SAAS;AAC7E,SAAO;AACT;AAEA,SAAS,eACP,MACA,OAC8B;AAC9B,MAAI,MAAM,YAAY,UAAa,MAAM,SAAS;AAChD,WAAO,KAAK,MAAM,8DAA8D,SAAS;AAC3F,MAAI,MAAM,SAAS;AACjB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACF,MAAI,MAAM,SAAS,UAAa,MAAM,SAAS,UAAU,MAAM,SAAS;AACtE,WAAO,KAAK,MAAM,4CAA4C,MAAM;AACtE,MAAI,OAAO,MAAM,QAAQ,SAAU,QAAO,KAAK,MAAM,mCAAmC,KAAK;AAC7F,MAAI,CAAC,IAAI,SAAS,MAAM,GAAG,KAAK,CAAC,YAAY,KAAK,IAAI,IAAI,MAAM,GAAG,EAAE,QAAQ;AAC3E,WAAO,KAAK,MAAM,oCAAoC,KAAK;AAC7D,QAAM,SAAS,UAAU,IAAI,IAAI,MAAM,GAAG,CAAC;AAC3C,MAAI;AACF,WAAO;AAAA,MACL;AAAA,MACA,GAAG,MAAM;AAAA,MACT;AAAA,IACF;AACF,SAAO;AACT;AAEO,SAAS,eAAe,MAAc,KAAuB;AAClE,MAAI,CAAC,YAAY,KAAK,IAAI;AACxB,WAAO,KAAK,MAAM,wBAAwB,IAAI,sCAAsC;AACtF,MAAI,CAAC,SAAS,GAAG,EAAG,QAAO,KAAK,MAAM,mBAAmB;AACzD,QAAM,QAAiC,EAAE,GAAG,KAAK,UAAU,MAAM,IAAI,QAAQ,EAAE;AAC/E,MAAI,IAAI,aAAa,OAAW,QAAO,MAAM;AAC7C,MAAI,SAAS,IAAI,YAAY;AAC3B,UAAM,eAAe,OAAO;AAAA,MAC1B,OAAO,QAAQ,IAAI,YAAY,EAAE,IAAI,CAAC,CAAC,MAAM,QAAQ,MAAM,CAAC,MAAM,MAAM,QAAQ,CAAC,CAAC;AAAA,IACpF;AACF,aAAW,OAAO,OAAO,KAAK,KAAK;AACjC,QAAI,QAAQ,WAAW,QAAQ;AAC7B,aAAO,KAAK,MAAM,GAAG,GAAG,kCAAkC,GAAG;AACjE,QAAM,UACJ,eAAe,MAAM,KAAK,KAC1B,aAAa,MAAM,MAAM,OAAO,KAChC,eAAe,MAAM,KAAK,KAC1B,YAAY,MAAM,KAAK;AACzB,MAAI,QAAS,QAAO;AACpB,QAAM,SAAkC,CAAC;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK;AAC7C,QAAI,SAAS,IAAI,GAAG,KAAK,QAAQ,OAAQ,QAAO,GAAG,IAAI;AACzD,SAAO;AACT;AAEA,SAAS,UAAU,MAAsB;AACvC,SAAO,KAAK,QAAQ,MAAM,GAAG;AAC/B;AAEO,SAAS,eAAe,MAAqC;AAClE,MAAI,SAAS,UAAa,KAAK,KAAK,MAAM,GAAI,QAAO,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE;AACjF,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,SAAS,OAAO;AACd,WAAO,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE,SAAS,mBAAoB,MAAgB,OAAO,GAAG,CAAC,EAAE;AAAA,EAC/F;AACA,MAAI,CAAC,SAAS,MAAM,KAAM,OAAO,eAAe,UAAa,CAAC,SAAS,OAAO,UAAU;AACtF,WAAO,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE,SAAS,+BAA+B,CAAC,EAAE;AAChF,QAAM,UAA4B,CAAC;AACnC,QAAM,WAA+B,CAAC;AACtC,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAS,OAAO,cAAc,CAAC,CAA6B,GAAG;AAC9F,UAAM,UAAU,eAAe,MAAM,GAAG;AACxC,QAAI,aAAa,SAAS;AACxB,eAAS,KAAK,OAAO;AACrB;AAAA,IACF;AACA,QAAI,KAAK,IAAI,UAAU,IAAI,CAAC,GAAG;AAC7B,eAAS,KAAK,KAAK,MAAM,gEAAgE,CAAC;AAC1F;AAAA,IACF;AACA,SAAK,IAAI,UAAU,IAAI,CAAC;AACxB,YAAQ,KAAK,EAAE,MAAM,QAAQ,QAAQ,CAAC;AAAA,EACxC;AACA,SAAO,EAAE,SAAS,SAAS;AAC7B;AAEA,SAAS,cAAc,SAAyB;AAC9C,QAAM,SAAS,QACZ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,QAAQ,sBAAsB,MAAM,CAAC,EACxD,KAAK,IAAI;AACZ,SAAO,IAAI,OAAO,IAAI,MAAM,GAAG;AACjC;AAEO,SAAS,YAAY,SAAuD;AACjF,QAAM,QAAQ,IAAI,IAAI,QAAQ,OAAO,CAAC,UAAU,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AACrE,QAAM,WAAW,QAAQ,OAAO,CAAC,UAAU,MAAM,SAAS,GAAG,CAAC,EAAE,IAAI,aAAa;AACjF,SAAO,CAAC,SAAS,MAAM,IAAI,IAAI,KAAK,SAAS,KAAK,CAAC,YAAY,QAAQ,KAAK,IAAI,CAAC;AACnF;AAEO,SAAS,aAAa,QAAyB,MAA2B;AAC/E,QAAM,YAAY,OAAO,gBAAgB,CAAC;AAC1C,QAAM,QAAQ,UAAU,IAAI;AAC5B,MAAI,UAAU,OAAW,QAAO;AAChC,aAAW,CAAC,SAAS,QAAQ,KAAK,OAAO,QAAQ,SAAS;AACxD,QAAI,QAAQ,SAAS,GAAG,KAAK,cAAc,OAAO,EAAE,KAAK,IAAI,EAAG,QAAO;AACzE,SAAO,OAAO,YAAY;AAC5B;AAEO,SAAS,aAAa,QAAwB;AACnD,SAAO,QAAQ,UAAU,MAAM,CAAC;AAClC;AAEO,SAAS,YACd,QACA,MACA,QAAmC,MAAM,OACjC;AACR,QAAM,OAAO,QAAQ,MAAM,KAAK,IAAI,GAAG,QAAQ,kBAAkB,GAAG;AACpE,MAAI,KAAK,UAAU,iBAAiB,CAAC,MAAM,IAAI,EAAG,QAAO;AACzD,QAAM,SAAS,KAAK,GAAG,MAAM,KAAK,IAAI,EAAE,EAAE,SAAS,GAAG,GAAG,EAAE,MAAM,GAAG,CAAC;AACrE,SAAO,GAAG,KAAK,MAAM,GAAG,gBAAgB,OAAO,SAAS,CAAC,CAAC,IAAI,MAAM;AACtE;AAEO,SAAS,WAAW,GAAoB,GAA6B;AAC1E,SAAO,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC;AAC/C;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/mcp/connection.js
CHANGED
|
@@ -144,7 +144,7 @@ class McpServerConnection {
|
|
|
144
144
|
void this.#drop(client);
|
|
145
145
|
}
|
|
146
146
|
signInMessage() {
|
|
147
|
-
return `MCP server "${this.name}" rejected
|
|
147
|
+
return `MCP server "${this.name}" rejected its token (401). Update it in Settings \u203A MCP servers.`;
|
|
148
148
|
}
|
|
149
149
|
#mark(state, error) {
|
|
150
150
|
this.state = state;
|
|
@@ -279,7 +279,7 @@ class MissingSecret extends Error {
|
|
|
279
279
|
names;
|
|
280
280
|
constructor(names) {
|
|
281
281
|
super(
|
|
282
|
-
`needs ${names.map((name) => `\${${name}}`).join(", ")}: add the value in Settings \u203A
|
|
282
|
+
`needs ${names.map((name) => `\${${name}}`).join(", ")}: add the value in Settings \u203A MCP servers`
|
|
283
283
|
);
|
|
284
284
|
this.names = names;
|
|
285
285
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/connection.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n type CallToolResult,\n JSON_RPC_ERROR_CODES,\n type ListResourcesResult,\n type ListResourceTemplatesResult,\n McpAuthRequiredError,\n McpClient,\n McpError,\n McpHttpError,\n type McpRequestOptions,\n McpSessionExpiredError,\n type Tool as McpTool,\n type McpTransport,\n type ReadResourceResult,\n type Resource,\n type ResourceTemplate,\n} from '@earendil-works/pi-mcp';\nimport { DEFAULT_TIMEOUT_SECONDS, type McpServerEntry } from './config.ts';\n\nexport type ServerState =\n | 'connecting'\n | 'connected'\n | 'needs-auth'\n | 'needs-secret'\n | 'failed'\n | 'closed';\n\nexport type TransportFactory = (\n entry: McpServerEntry\n) => Promise<{ transport: McpTransport; secrets: string[] } | { missing: string[] }>;\n\nexport const CONNECT_RETRY_DELAYS_MS = [250, 1_000];\nexport const CLIENT_NAME = 'slicc';\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function isTransient(error: unknown): boolean {\n if (error instanceof McpHttpError)\n return (\n error.status === 408 || error.status === 429 || (error.status >= 500 && error.status !== 501)\n );\n return error instanceof TypeError;\n}\n\nexport function isMcpApp(item: { uri?: string; uriTemplate?: string; mimeType?: string }): boolean {\n const uri = item.uri ?? item.uriTemplate ?? '';\n return uri.startsWith('ui://') || /;\\s*profile\\s*=\\s*\"?mcp-app\"?/i.test(item.mimeType ?? '');\n}\n\nasync function withoutTemplates<T>(list: () => Promise<T>, empty: T): Promise<T> {\n try {\n return await list();\n } catch (error) {\n if (error instanceof McpError && error.code === JSON_RPC_ERROR_CODES.methodNotFound)\n return empty;\n throw error;\n }\n}\n\nconst sleep = (ms: number, signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true }\n );\n });\n\nexport interface ConnectionOptions {\n entry: McpServerEntry;\n version: string;\n transport: TransportFactory;\n onTools: (connection: McpServerConnection) => void;\n onChange?: (connection: McpServerConnection) => void;\n onLog?: (connection: McpServerConnection, params: unknown) => void;\n}\n\nexport class McpServerConnection {\n readonly entry: McpServerEntry;\n state: ServerState = 'connecting';\n error: string | undefined;\n missing: string[] = [];\n tools: McpTool[] = [];\n hasResources = false;\n resources: Resource[] = [];\n resourceTemplates: ResourceTemplate[] = [];\n instructions: string | undefined;\n secrets: string[] = [];\n #client: McpClient | undefined;\n #opening: Promise<McpClient> | undefined;\n readonly #shutdown = new AbortController();\n readonly #busy = new Map<McpClient, number>();\n #seen = false;\n readonly #expired = new Set<McpClient>();\n readonly #options: ConnectionOptions;\n\n constructor(options: ConnectionOptions) {\n this.entry = options.entry;\n this.#options = options;\n }\n\n get name(): string {\n return this.entry.name;\n }\n\n get timeoutMs(): number {\n return (this.entry.config.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000;\n }\n\n get live(): boolean {\n return this.state === 'connected' || (this.state === 'connecting' && this.#seen);\n }\n\n get closed(): boolean {\n return this.#shutdown.signal.aborted;\n }\n\n getClient(): Promise<McpClient> {\n if (this.closed) return Promise.reject(new Error(`MCP server \"${this.name}\" is shut down`));\n if (this.#client?.connectionState === 'connected') return Promise.resolve(this.#client);\n this.#opening ??= this.#open().finally(() => {\n this.#opening = undefined;\n });\n return this.#opening;\n }\n\n callTool(\n name: string,\n args: Record<string, unknown>,\n options: McpRequestOptions\n ): Promise<CallToolResult> {\n return this.#with((client) => client.callTool(name, args, options));\n }\n\n readResource(uri: string, options: McpRequestOptions): Promise<ReadResourceResult> {\n return this.#with((client) => client.readResource(uri, options), true);\n }\n\n resourcesPage(\n cursor: string | undefined,\n options: McpRequestOptions\n ): Promise<ListResourcesResult> {\n return this.#with((client) => client.listResourcesPage(cursor, options), true);\n }\n\n resourceTemplatesPage(\n cursor: string | undefined,\n options: McpRequestOptions\n ): Promise<ListResourceTemplatesResult> {\n return this.#with(\n (client) =>\n withoutTemplates(() => client.listResourceTemplatesPage(cursor, options), {\n resourceTemplates: [],\n }),\n true\n );\n }\n\n allResources(options: McpRequestOptions): Promise<Resource[]> {\n return this.#with((client) => client.listResources(options), true);\n }\n\n allResourceTemplates(options: McpRequestOptions): Promise<ResourceTemplate[]> {\n return this.#with(\n (client) => withoutTemplates(() => client.listResourceTemplates(options), []),\n true\n );\n }\n\n async #with<T>(run: (client: McpClient) => Promise<T>, readOnly = false): Promise<T> {\n for (let attempt = 1; ; attempt++) {\n const client = await this.getClient();\n this.#busy.set(client, (this.#busy.get(client) ?? 0) + 1);\n try {\n return await run(client);\n } catch (error) {\n if (readOnly && attempt === 1 && error instanceof McpHttpError && isTransient(error)) {\n await sleep(CONNECT_RETRY_DELAYS_MS[0] as number, this.#shutdown.signal);\n continue;\n }\n if (error instanceof McpSessionExpiredError && attempt === 1) {\n if (this.#client === client) {\n this.#client = undefined;\n this.#expired.add(client);\n }\n continue;\n }\n if (!(error instanceof McpAuthRequiredError)) throw error;\n await this.#drop(client);\n this.#mark('needs-auth');\n throw new Error(this.signInMessage());\n } finally {\n this.#release(client);\n }\n }\n }\n\n #release(client: McpClient): void {\n const left = (this.#busy.get(client) as number) - 1;\n this.#busy.set(client, left);\n if (left > 0 || !this.#expired.delete(client)) return;\n this.#busy.delete(client);\n void this.#drop(client);\n }\n\n signInMessage(): string {\n return `MCP server \"${this.name}\" rejected the request as unauthorized (401). Check its token in Settings \u203A Accounts.`;\n }\n\n #mark(state: ServerState, error?: string): void {\n this.state = state;\n this.error = error;\n this.#options.onChange?.(this);\n }\n\n async #drop(client: McpClient): Promise<void> {\n if (this.#client === client) this.#client = undefined;\n await client.close().catch(() => undefined);\n }\n\n async #open(): Promise<McpClient> {\n this.#mark('connecting');\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.#connectOnce();\n } catch (error) {\n const delay = CONNECT_RETRY_DELAYS_MS[attempt];\n if (this.closed || delay === undefined || !isTransient(error)) throw this.#failed(error);\n await sleep(delay, this.#shutdown.signal);\n if (this.closed) throw this.#failed(error);\n }\n }\n }\n\n async #connectOnce(): Promise<McpClient> {\n const made = await this.#options.transport(this.entry);\n if ('missing' in made) {\n this.missing = made.missing;\n throw new MissingSecret(made.missing);\n }\n this.secrets = made.secrets;\n const client = new McpClient({\n name: CLIENT_NAME,\n version: this.#options.version,\n requestTimeoutMs: this.timeoutMs,\n });\n const log = this.#options.onLog;\n if (log) client.onNotification('notifications/message', (params) => log(this, params));\n let closing: Promise<void> | undefined;\n const closeClient = () => {\n closing ??= client.close().catch(() => undefined);\n return closing;\n };\n this.#shutdown.signal.addEventListener('abort', closeClient, { once: true });\n try {\n await client.connect(made.transport);\n client.onNotification('notifications/tools/list_changed', () => {\n void this.#refreshTools(client);\n });\n client.onNotification('notifications/resources/list_changed', () => {\n void this.#refreshResources(client);\n });\n const hasResources = client.serverCapabilities?.resources !== undefined;\n const [tools, resources] = await Promise.all([\n client.serverCapabilities?.tools ? client.listTools() : [],\n hasResources ? this.#fetchResources(client) : { resources: [], resourceTemplates: [] },\n ]);\n if (this.closed) throw new Error('shut down while connecting');\n if (client.connectionState !== 'connected') throw new Error('connection closed during setup');\n this.#client = client;\n this.tools = tools;\n this.hasResources = hasResources;\n this.resources = resources.resources;\n this.resourceTemplates = resources.resourceTemplates;\n this.instructions = client.instructions?.trim() || undefined;\n this.missing = [];\n this.#seen = true;\n this.state = 'connected';\n this.error = undefined;\n this.#options.onTools(this);\n this.#options.onChange?.(this);\n return client;\n } catch (error) {\n await closeClient();\n throw error;\n } finally {\n this.#shutdown.signal.removeEventListener('abort', closeClient);\n }\n }\n\n async #fetchResources(client: McpClient) {\n const [resources, resourceTemplates] = await Promise.all([\n client.listResources().catch(() => [] as Resource[]),\n withoutTemplates(() => client.listResourceTemplates(), [] as ResourceTemplate[]).catch(\n () => [] as ResourceTemplate[]\n ),\n ]);\n return {\n resources: resources.filter((resource) => !isMcpApp(resource)),\n resourceTemplates: resourceTemplates.filter((template) => !isMcpApp(template)),\n };\n }\n\n #failed(error: unknown): Error {\n if (error instanceof MissingSecret) {\n this.#mark('needs-secret');\n return new Error(error.message);\n }\n if (error instanceof McpAuthRequiredError && !this.closed) {\n this.#mark('needs-auth');\n return new Error(this.signInMessage());\n }\n this.#mark(this.closed ? 'closed' : 'failed', errorMessage(error));\n return new Error(`MCP server \"${this.name}\" failed to connect: ${this.error}`);\n }\n\n async #refreshTools(client: McpClient): Promise<void> {\n try {\n const tools = await client.listTools();\n if (this.#client !== client || this.closed) return;\n this.tools = tools;\n this.#options.onTools(this);\n } catch (error) {\n this.error = `Failed to refresh tools: ${errorMessage(error)}`;\n }\n this.#options.onChange?.(this);\n }\n\n async #refreshResources(client: McpClient): Promise<void> {\n const { resources, resourceTemplates } = await this.#fetchResources(client);\n if (this.#client !== client || this.closed) return;\n this.resources = resources;\n this.resourceTemplates = resourceTemplates;\n this.#options.onTools(this);\n this.#options.onChange?.(this);\n }\n\n async close(): Promise<void> {\n this.#shutdown.abort();\n this.state = 'closed';\n this.#options.onChange?.(this);\n const client = this.#client;\n this.#client = undefined;\n await Promise.allSettled([client?.close(), this.#opening]);\n }\n}\n\nexport class MissingSecret extends Error {\n readonly names: string[];\n\n constructor(names: string[]) {\n super(\n `needs ${names.map((name) => `\\${${name}}`).join(', ')}: add the value in Settings \u203A Accounts`\n );\n this.names = names;\n }\n}\n"],
|
|
4
|
+
"sourcesContent": ["import {\n type CallToolResult,\n JSON_RPC_ERROR_CODES,\n type ListResourcesResult,\n type ListResourceTemplatesResult,\n McpAuthRequiredError,\n McpClient,\n McpError,\n McpHttpError,\n type McpRequestOptions,\n McpSessionExpiredError,\n type Tool as McpTool,\n type McpTransport,\n type ReadResourceResult,\n type Resource,\n type ResourceTemplate,\n} from '@earendil-works/pi-mcp';\nimport { DEFAULT_TIMEOUT_SECONDS, type McpServerEntry } from './config.ts';\n\nexport type ServerState =\n | 'connecting'\n | 'connected'\n | 'needs-auth'\n | 'needs-secret'\n | 'failed'\n | 'closed';\n\nexport type TransportFactory = (\n entry: McpServerEntry\n) => Promise<{ transport: McpTransport; secrets: string[] } | { missing: string[] }>;\n\nexport const CONNECT_RETRY_DELAYS_MS = [250, 1_000];\nexport const CLIENT_NAME = 'slicc';\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function isTransient(error: unknown): boolean {\n if (error instanceof McpHttpError)\n return (\n error.status === 408 || error.status === 429 || (error.status >= 500 && error.status !== 501)\n );\n return error instanceof TypeError;\n}\n\nexport function isMcpApp(item: { uri?: string; uriTemplate?: string; mimeType?: string }): boolean {\n const uri = item.uri ?? item.uriTemplate ?? '';\n return uri.startsWith('ui://') || /;\\s*profile\\s*=\\s*\"?mcp-app\"?/i.test(item.mimeType ?? '');\n}\n\nasync function withoutTemplates<T>(list: () => Promise<T>, empty: T): Promise<T> {\n try {\n return await list();\n } catch (error) {\n if (error instanceof McpError && error.code === JSON_RPC_ERROR_CODES.methodNotFound)\n return empty;\n throw error;\n }\n}\n\nconst sleep = (ms: number, signal: AbortSignal) =>\n new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true }\n );\n });\n\nexport interface ConnectionOptions {\n entry: McpServerEntry;\n version: string;\n transport: TransportFactory;\n onTools: (connection: McpServerConnection) => void;\n onChange?: (connection: McpServerConnection) => void;\n onLog?: (connection: McpServerConnection, params: unknown) => void;\n}\n\nexport class McpServerConnection {\n readonly entry: McpServerEntry;\n state: ServerState = 'connecting';\n error: string | undefined;\n missing: string[] = [];\n tools: McpTool[] = [];\n hasResources = false;\n resources: Resource[] = [];\n resourceTemplates: ResourceTemplate[] = [];\n instructions: string | undefined;\n secrets: string[] = [];\n #client: McpClient | undefined;\n #opening: Promise<McpClient> | undefined;\n readonly #shutdown = new AbortController();\n readonly #busy = new Map<McpClient, number>();\n #seen = false;\n readonly #expired = new Set<McpClient>();\n readonly #options: ConnectionOptions;\n\n constructor(options: ConnectionOptions) {\n this.entry = options.entry;\n this.#options = options;\n }\n\n get name(): string {\n return this.entry.name;\n }\n\n get timeoutMs(): number {\n return (this.entry.config.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1000;\n }\n\n get live(): boolean {\n return this.state === 'connected' || (this.state === 'connecting' && this.#seen);\n }\n\n get closed(): boolean {\n return this.#shutdown.signal.aborted;\n }\n\n getClient(): Promise<McpClient> {\n if (this.closed) return Promise.reject(new Error(`MCP server \"${this.name}\" is shut down`));\n if (this.#client?.connectionState === 'connected') return Promise.resolve(this.#client);\n this.#opening ??= this.#open().finally(() => {\n this.#opening = undefined;\n });\n return this.#opening;\n }\n\n callTool(\n name: string,\n args: Record<string, unknown>,\n options: McpRequestOptions\n ): Promise<CallToolResult> {\n return this.#with((client) => client.callTool(name, args, options));\n }\n\n readResource(uri: string, options: McpRequestOptions): Promise<ReadResourceResult> {\n return this.#with((client) => client.readResource(uri, options), true);\n }\n\n resourcesPage(\n cursor: string | undefined,\n options: McpRequestOptions\n ): Promise<ListResourcesResult> {\n return this.#with((client) => client.listResourcesPage(cursor, options), true);\n }\n\n resourceTemplatesPage(\n cursor: string | undefined,\n options: McpRequestOptions\n ): Promise<ListResourceTemplatesResult> {\n return this.#with(\n (client) =>\n withoutTemplates(() => client.listResourceTemplatesPage(cursor, options), {\n resourceTemplates: [],\n }),\n true\n );\n }\n\n allResources(options: McpRequestOptions): Promise<Resource[]> {\n return this.#with((client) => client.listResources(options), true);\n }\n\n allResourceTemplates(options: McpRequestOptions): Promise<ResourceTemplate[]> {\n return this.#with(\n (client) => withoutTemplates(() => client.listResourceTemplates(options), []),\n true\n );\n }\n\n async #with<T>(run: (client: McpClient) => Promise<T>, readOnly = false): Promise<T> {\n for (let attempt = 1; ; attempt++) {\n const client = await this.getClient();\n this.#busy.set(client, (this.#busy.get(client) ?? 0) + 1);\n try {\n return await run(client);\n } catch (error) {\n if (readOnly && attempt === 1 && error instanceof McpHttpError && isTransient(error)) {\n await sleep(CONNECT_RETRY_DELAYS_MS[0] as number, this.#shutdown.signal);\n continue;\n }\n if (error instanceof McpSessionExpiredError && attempt === 1) {\n if (this.#client === client) {\n this.#client = undefined;\n this.#expired.add(client);\n }\n continue;\n }\n if (!(error instanceof McpAuthRequiredError)) throw error;\n await this.#drop(client);\n this.#mark('needs-auth');\n throw new Error(this.signInMessage());\n } finally {\n this.#release(client);\n }\n }\n }\n\n #release(client: McpClient): void {\n const left = (this.#busy.get(client) as number) - 1;\n this.#busy.set(client, left);\n if (left > 0 || !this.#expired.delete(client)) return;\n this.#busy.delete(client);\n void this.#drop(client);\n }\n\n signInMessage(): string {\n return `MCP server \"${this.name}\" rejected its token (401). Update it in Settings \u203A MCP servers.`;\n }\n\n #mark(state: ServerState, error?: string): void {\n this.state = state;\n this.error = error;\n this.#options.onChange?.(this);\n }\n\n async #drop(client: McpClient): Promise<void> {\n if (this.#client === client) this.#client = undefined;\n await client.close().catch(() => undefined);\n }\n\n async #open(): Promise<McpClient> {\n this.#mark('connecting');\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.#connectOnce();\n } catch (error) {\n const delay = CONNECT_RETRY_DELAYS_MS[attempt];\n if (this.closed || delay === undefined || !isTransient(error)) throw this.#failed(error);\n await sleep(delay, this.#shutdown.signal);\n if (this.closed) throw this.#failed(error);\n }\n }\n }\n\n async #connectOnce(): Promise<McpClient> {\n const made = await this.#options.transport(this.entry);\n if ('missing' in made) {\n this.missing = made.missing;\n throw new MissingSecret(made.missing);\n }\n this.secrets = made.secrets;\n const client = new McpClient({\n name: CLIENT_NAME,\n version: this.#options.version,\n requestTimeoutMs: this.timeoutMs,\n });\n const log = this.#options.onLog;\n if (log) client.onNotification('notifications/message', (params) => log(this, params));\n let closing: Promise<void> | undefined;\n const closeClient = () => {\n closing ??= client.close().catch(() => undefined);\n return closing;\n };\n this.#shutdown.signal.addEventListener('abort', closeClient, { once: true });\n try {\n await client.connect(made.transport);\n client.onNotification('notifications/tools/list_changed', () => {\n void this.#refreshTools(client);\n });\n client.onNotification('notifications/resources/list_changed', () => {\n void this.#refreshResources(client);\n });\n const hasResources = client.serverCapabilities?.resources !== undefined;\n const [tools, resources] = await Promise.all([\n client.serverCapabilities?.tools ? client.listTools() : [],\n hasResources ? this.#fetchResources(client) : { resources: [], resourceTemplates: [] },\n ]);\n if (this.closed) throw new Error('shut down while connecting');\n if (client.connectionState !== 'connected') throw new Error('connection closed during setup');\n this.#client = client;\n this.tools = tools;\n this.hasResources = hasResources;\n this.resources = resources.resources;\n this.resourceTemplates = resources.resourceTemplates;\n this.instructions = client.instructions?.trim() || undefined;\n this.missing = [];\n this.#seen = true;\n this.state = 'connected';\n this.error = undefined;\n this.#options.onTools(this);\n this.#options.onChange?.(this);\n return client;\n } catch (error) {\n await closeClient();\n throw error;\n } finally {\n this.#shutdown.signal.removeEventListener('abort', closeClient);\n }\n }\n\n async #fetchResources(client: McpClient) {\n const [resources, resourceTemplates] = await Promise.all([\n client.listResources().catch(() => [] as Resource[]),\n withoutTemplates(() => client.listResourceTemplates(), [] as ResourceTemplate[]).catch(\n () => [] as ResourceTemplate[]\n ),\n ]);\n return {\n resources: resources.filter((resource) => !isMcpApp(resource)),\n resourceTemplates: resourceTemplates.filter((template) => !isMcpApp(template)),\n };\n }\n\n #failed(error: unknown): Error {\n if (error instanceof MissingSecret) {\n this.#mark('needs-secret');\n return new Error(error.message);\n }\n if (error instanceof McpAuthRequiredError && !this.closed) {\n this.#mark('needs-auth');\n return new Error(this.signInMessage());\n }\n this.#mark(this.closed ? 'closed' : 'failed', errorMessage(error));\n return new Error(`MCP server \"${this.name}\" failed to connect: ${this.error}`);\n }\n\n async #refreshTools(client: McpClient): Promise<void> {\n try {\n const tools = await client.listTools();\n if (this.#client !== client || this.closed) return;\n this.tools = tools;\n this.#options.onTools(this);\n } catch (error) {\n this.error = `Failed to refresh tools: ${errorMessage(error)}`;\n }\n this.#options.onChange?.(this);\n }\n\n async #refreshResources(client: McpClient): Promise<void> {\n const { resources, resourceTemplates } = await this.#fetchResources(client);\n if (this.#client !== client || this.closed) return;\n this.resources = resources;\n this.resourceTemplates = resourceTemplates;\n this.#options.onTools(this);\n this.#options.onChange?.(this);\n }\n\n async close(): Promise<void> {\n this.#shutdown.abort();\n this.state = 'closed';\n this.#options.onChange?.(this);\n const client = this.#client;\n this.#client = undefined;\n await Promise.allSettled([client?.close(), this.#opening]);\n }\n}\n\nexport class MissingSecret extends Error {\n readonly names: string[];\n\n constructor(names: string[]) {\n super(\n `needs ${names.map((name) => `\\${${name}}`).join(', ')}: add the value in Settings \u203A MCP servers`\n );\n this.names = names;\n }\n}\n"],
|
|
5
5
|
"mappings": "AAAA;AAAA,EAEE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,OAMK;AACP,SAAS,+BAAoD;AActD,MAAM,0BAA0B,CAAC,KAAK,GAAK;AAC3C,MAAM,cAAc;AAEpB,SAAS,aAAa,OAAwB;AACnD,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEO,SAAS,YAAY,OAAyB;AACnD,MAAI,iBAAiB;AACnB,WACE,MAAM,WAAW,OAAO,MAAM,WAAW,OAAQ,MAAM,UAAU,OAAO,MAAM,WAAW;AAE7F,SAAO,iBAAiB;AAC1B;AAEO,SAAS,SAAS,MAA0E;AACjG,QAAM,MAAM,KAAK,OAAO,KAAK,eAAe;AAC5C,SAAO,IAAI,WAAW,OAAO,KAAK,iCAAiC,KAAK,KAAK,YAAY,EAAE;AAC7F;AAEA,eAAe,iBAAoB,MAAwB,OAAsB;AAC/E,MAAI;AACF,WAAO,MAAM,KAAK;AAAA,EACpB,SAAS,OAAO;AACd,QAAI,iBAAiB,YAAY,MAAM,SAAS,qBAAqB;AACnE,aAAO;AACT,UAAM;AAAA,EACR;AACF;AAEA,MAAM,QAAQ,CAAC,IAAY,WACzB,IAAI,QAAc,CAAC,YAAY;AAC7B,QAAM,QAAQ,WAAW,SAAS,EAAE;AACpC,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AACJ,mBAAa,KAAK;AAClB,cAAQ;AAAA,IACV;AAAA,IACA,EAAE,MAAM,KAAK;AAAA,EACf;AACF,CAAC;AAWI,MAAM,oBAAoB;AAAA,EACtB;AAAA,EACT,QAAqB;AAAA,EACrB;AAAA,EACA,UAAoB,CAAC;AAAA,EACrB,QAAmB,CAAC;AAAA,EACpB,eAAe;AAAA,EACf,YAAwB,CAAC;AAAA,EACzB,oBAAwC,CAAC;AAAA,EACzC;AAAA,EACA,UAAoB,CAAC;AAAA,EACrB;AAAA,EACA;AAAA,EACS,YAAY,IAAI,gBAAgB;AAAA,EAChC,QAAQ,oBAAI,IAAuB;AAAA,EAC5C,QAAQ;AAAA,EACC,WAAW,oBAAI,IAAe;AAAA,EAC9B;AAAA,EAET,YAAY,SAA4B;AACtC,SAAK,QAAQ,QAAQ;AACrB,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,YAAoB;AACtB,YAAQ,KAAK,MAAM,OAAO,WAAW,2BAA2B;AAAA,EAClE;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK,UAAU,eAAgB,KAAK,UAAU,gBAAgB,KAAK;AAAA,EAC5E;AAAA,EAEA,IAAI,SAAkB;AACpB,WAAO,KAAK,UAAU,OAAO;AAAA,EAC/B;AAAA,EAEA,YAAgC;AAC9B,QAAI,KAAK,OAAQ,QAAO,QAAQ,OAAO,IAAI,MAAM,eAAe,KAAK,IAAI,gBAAgB,CAAC;AAC1F,QAAI,KAAK,SAAS,oBAAoB,YAAa,QAAO,QAAQ,QAAQ,KAAK,OAAO;AACtF,SAAK,aAAa,KAAK,MAAM,EAAE,QAAQ,MAAM;AAC3C,WAAK,WAAW;AAAA,IAClB,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,SACE,MACA,MACA,SACyB;AACzB,WAAO,KAAK,MAAM,CAAC,WAAW,OAAO,SAAS,MAAM,MAAM,OAAO,CAAC;AAAA,EACpE;AAAA,EAEA,aAAa,KAAa,SAAyD;AACjF,WAAO,KAAK,MAAM,CAAC,WAAW,OAAO,aAAa,KAAK,OAAO,GAAG,IAAI;AAAA,EACvE;AAAA,EAEA,cACE,QACA,SAC8B;AAC9B,WAAO,KAAK,MAAM,CAAC,WAAW,OAAO,kBAAkB,QAAQ,OAAO,GAAG,IAAI;AAAA,EAC/E;AAAA,EAEA,sBACE,QACA,SACsC;AACtC,WAAO,KAAK;AAAA,MACV,CAAC,WACC,iBAAiB,MAAM,OAAO,0BAA0B,QAAQ,OAAO,GAAG;AAAA,QACxE,mBAAmB,CAAC;AAAA,MACtB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,aAAa,SAAiD;AAC5D,WAAO,KAAK,MAAM,CAAC,WAAW,OAAO,cAAc,OAAO,GAAG,IAAI;AAAA,EACnE;AAAA,EAEA,qBAAqB,SAAyD;AAC5E,WAAO,KAAK;AAAA,MACV,CAAC,WAAW,iBAAiB,MAAM,OAAO,sBAAsB,OAAO,GAAG,CAAC,CAAC;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,MAAS,KAAwC,WAAW,OAAmB;AACnF,aAAS,UAAU,KAAK,WAAW;AACjC,YAAM,SAAS,MAAM,KAAK,UAAU;AACpC,WAAK,MAAM,IAAI,SAAS,KAAK,MAAM,IAAI,MAAM,KAAK,KAAK,CAAC;AACxD,UAAI;AACF,eAAO,MAAM,IAAI,MAAM;AAAA,MACzB,SAAS,OAAO;AACd,YAAI,YAAY,YAAY,KAAK,iBAAiB,gBAAgB,YAAY,KAAK,GAAG;AACpF,gBAAM,MAAM,wBAAwB,CAAC,GAAa,KAAK,UAAU,MAAM;AACvE;AAAA,QACF;AACA,YAAI,iBAAiB,0BAA0B,YAAY,GAAG;AAC5D,cAAI,KAAK,YAAY,QAAQ;AAC3B,iBAAK,UAAU;AACf,iBAAK,SAAS,IAAI,MAAM;AAAA,UAC1B;AACA;AAAA,QACF;AACA,YAAI,EAAE,iBAAiB,sBAAuB,OAAM;AACpD,cAAM,KAAK,MAAM,MAAM;AACvB,aAAK,MAAM,YAAY;AACvB,cAAM,IAAI,MAAM,KAAK,cAAc,CAAC;AAAA,MACtC,UAAE;AACA,aAAK,SAAS,MAAM;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,SAAS,QAAyB;AAChC,UAAM,OAAQ,KAAK,MAAM,IAAI,MAAM,IAAe;AAClD,SAAK,MAAM,IAAI,QAAQ,IAAI;AAC3B,QAAI,OAAO,KAAK,CAAC,KAAK,SAAS,OAAO,MAAM,EAAG;AAC/C,SAAK,MAAM,OAAO,MAAM;AACxB,SAAK,KAAK,MAAM,MAAM;AAAA,EACxB;AAAA,EAEA,gBAAwB;AACtB,WAAO,eAAe,KAAK,IAAI;AAAA,EACjC;AAAA,EAEA,MAAM,OAAoB,OAAsB;AAC9C,SAAK,QAAQ;AACb,SAAK,QAAQ;AACb,SAAK,SAAS,WAAW,IAAI;AAAA,EAC/B;AAAA,EAEA,MAAM,MAAM,QAAkC;AAC5C,QAAI,KAAK,YAAY,OAAQ,MAAK,UAAU;AAC5C,UAAM,OAAO,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EAC5C;AAAA,EAEA,MAAM,QAA4B;AAChC,SAAK,MAAM,YAAY;AACvB,aAAS,UAAU,KAAK,WAAW;AACjC,UAAI;AACF,eAAO,MAAM,KAAK,aAAa;AAAA,MACjC,SAAS,OAAO;AACd,cAAM,QAAQ,wBAAwB,OAAO;AAC7C,YAAI,KAAK,UAAU,UAAU,UAAa,CAAC,YAAY,KAAK,EAAG,OAAM,KAAK,QAAQ,KAAK;AACvF,cAAM,MAAM,OAAO,KAAK,UAAU,MAAM;AACxC,YAAI,KAAK,OAAQ,OAAM,KAAK,QAAQ,KAAK;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,eAAmC;AACvC,UAAM,OAAO,MAAM,KAAK,SAAS,UAAU,KAAK,KAAK;AACrD,QAAI,aAAa,MAAM;AACrB,WAAK,UAAU,KAAK;AACpB,YAAM,IAAI,cAAc,KAAK,OAAO;AAAA,IACtC;AACA,SAAK,UAAU,KAAK;AACpB,UAAM,SAAS,IAAI,UAAU;AAAA,MAC3B,MAAM;AAAA,MACN,SAAS,KAAK,SAAS;AAAA,MACvB,kBAAkB,KAAK;AAAA,IACzB,CAAC;AACD,UAAM,MAAM,KAAK,SAAS;AAC1B,QAAI,IAAK,QAAO,eAAe,yBAAyB,CAAC,WAAW,IAAI,MAAM,MAAM,CAAC;AACrF,QAAI;AACJ,UAAM,cAAc,MAAM;AACxB,kBAAY,OAAO,MAAM,EAAE,MAAM,MAAM,MAAS;AAChD,aAAO;AAAA,IACT;AACA,SAAK,UAAU,OAAO,iBAAiB,SAAS,aAAa,EAAE,MAAM,KAAK,CAAC;AAC3E,QAAI;AACF,YAAM,OAAO,QAAQ,KAAK,SAAS;AACnC,aAAO,eAAe,oCAAoC,MAAM;AAC9D,aAAK,KAAK,cAAc,MAAM;AAAA,MAChC,CAAC;AACD,aAAO,eAAe,wCAAwC,MAAM;AAClE,aAAK,KAAK,kBAAkB,MAAM;AAAA,MACpC,CAAC;AACD,YAAM,eAAe,OAAO,oBAAoB,cAAc;AAC9D,YAAM,CAAC,OAAO,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC3C,OAAO,oBAAoB,QAAQ,OAAO,UAAU,IAAI,CAAC;AAAA,QACzD,eAAe,KAAK,gBAAgB,MAAM,IAAI,EAAE,WAAW,CAAC,GAAG,mBAAmB,CAAC,EAAE;AAAA,MACvF,CAAC;AACD,UAAI,KAAK,OAAQ,OAAM,IAAI,MAAM,4BAA4B;AAC7D,UAAI,OAAO,oBAAoB,YAAa,OAAM,IAAI,MAAM,gCAAgC;AAC5F,WAAK,UAAU;AACf,WAAK,QAAQ;AACb,WAAK,eAAe;AACpB,WAAK,YAAY,UAAU;AAC3B,WAAK,oBAAoB,UAAU;AACnC,WAAK,eAAe,OAAO,cAAc,KAAK,KAAK;AACnD,WAAK,UAAU,CAAC;AAChB,WAAK,QAAQ;AACb,WAAK,QAAQ;AACb,WAAK,QAAQ;AACb,WAAK,SAAS,QAAQ,IAAI;AAC1B,WAAK,SAAS,WAAW,IAAI;AAC7B,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,YAAY;AAClB,YAAM;AAAA,IACR,UAAE;AACA,WAAK,UAAU,OAAO,oBAAoB,SAAS,WAAW;AAAA,IAChE;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB,QAAmB;AACvC,UAAM,CAAC,WAAW,iBAAiB,IAAI,MAAM,QAAQ,IAAI;AAAA,MACvD,OAAO,cAAc,EAAE,MAAM,MAAM,CAAC,CAAe;AAAA,MACnD,iBAAiB,MAAM,OAAO,sBAAsB,GAAG,CAAC,CAAuB,EAAE;AAAA,QAC/E,MAAM,CAAC;AAAA,MACT;AAAA,IACF,CAAC;AACD,WAAO;AAAA,MACL,WAAW,UAAU,OAAO,CAAC,aAAa,CAAC,SAAS,QAAQ,CAAC;AAAA,MAC7D,mBAAmB,kBAAkB,OAAO,CAAC,aAAa,CAAC,SAAS,QAAQ,CAAC;AAAA,IAC/E;AAAA,EACF;AAAA,EAEA,QAAQ,OAAuB;AAC7B,QAAI,iBAAiB,eAAe;AAClC,WAAK,MAAM,cAAc;AACzB,aAAO,IAAI,MAAM,MAAM,OAAO;AAAA,IAChC;AACA,QAAI,iBAAiB,wBAAwB,CAAC,KAAK,QAAQ;AACzD,WAAK,MAAM,YAAY;AACvB,aAAO,IAAI,MAAM,KAAK,cAAc,CAAC;AAAA,IACvC;AACA,SAAK,MAAM,KAAK,SAAS,WAAW,UAAU,aAAa,KAAK,CAAC;AACjE,WAAO,IAAI,MAAM,eAAe,KAAK,IAAI,wBAAwB,KAAK,KAAK,EAAE;AAAA,EAC/E;AAAA,EAEA,MAAM,cAAc,QAAkC;AACpD,QAAI;AACF,YAAM,QAAQ,MAAM,OAAO,UAAU;AACrC,UAAI,KAAK,YAAY,UAAU,KAAK,OAAQ;AAC5C,WAAK,QAAQ;AACb,WAAK,SAAS,QAAQ,IAAI;AAAA,IAC5B,SAAS,OAAO;AACd,WAAK,QAAQ,4BAA4B,aAAa,KAAK,CAAC;AAAA,IAC9D;AACA,SAAK,SAAS,WAAW,IAAI;AAAA,EAC/B;AAAA,EAEA,MAAM,kBAAkB,QAAkC;AACxD,UAAM,EAAE,WAAW,kBAAkB,IAAI,MAAM,KAAK,gBAAgB,MAAM;AAC1E,QAAI,KAAK,YAAY,UAAU,KAAK,OAAQ;AAC5C,SAAK,YAAY;AACjB,SAAK,oBAAoB;AACzB,SAAK,SAAS,QAAQ,IAAI;AAC1B,SAAK,SAAS,WAAW,IAAI;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,UAAU,MAAM;AACrB,SAAK,QAAQ;AACb,SAAK,SAAS,WAAW,IAAI;AAC7B,UAAM,SAAS,KAAK;AACpB,SAAK,UAAU;AACf,UAAM,QAAQ,WAAW,CAAC,QAAQ,MAAM,GAAG,KAAK,QAAQ,CAAC;AAAA,EAC3D;AACF;AAEO,MAAM,sBAAsB,MAAM;AAAA,EAC9B;AAAA,EAET,YAAY,OAAiB;AAC3B;AAAA,MACE,SAAS,MAAM,IAAI,CAAC,SAAS,MAAM,IAAI,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,IACxD;AACA,SAAK,QAAQ;AAAA,EACf;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/mcp/index.js
CHANGED
|
@@ -2,6 +2,12 @@ import { parseMcpConfig } from "./config.js";
|
|
|
2
2
|
import { allows, McpAllowDoc, mcpEntries, serverAllowed } from "./allow.js";
|
|
3
3
|
const loadRuntime = () => import("./runtime.js");
|
|
4
4
|
const idle = {
|
|
5
|
+
accounts: async () => [],
|
|
6
|
+
connect: async (id) => {
|
|
7
|
+
throw new Error(`${id} is not an MCP server that takes a token`);
|
|
8
|
+
},
|
|
9
|
+
disconnect: async () => void 0,
|
|
10
|
+
onChange: () => () => void 0,
|
|
5
11
|
status: () => [],
|
|
6
12
|
reload: async () => void 0,
|
|
7
13
|
close: async () => void 0
|
package/dist/mcp/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type {\n Agent,\n Registry,\n ToolExecutionApi,\n ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport type { CodemodeExtra } from './allow.ts';\nimport type { McpCatalog } from './catalog.ts';\nimport { parseMcpConfig } from './config.ts';\nimport type { McpAttach, McpRuntime } from './runtime.ts';\n\nexport { allows, type CodemodeExtra, McpAllowDoc, mcpEntries, serverAllowed } from './allow.ts';\nexport type { McpAttach, McpRuntime, ServerStatus } from './runtime.ts';\n\nexport type McpLoader = () => Promise<typeof import('./runtime.ts')>;\n\nexport const loadRuntime: McpLoader = () => import('./runtime.ts');\n\nexport interface McpSetup {\n catalog(): McpCatalog | undefined;\n direct(names: readonly string[]): ToolRegistration[];\n codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra>;\n attach(options: McpAttach, context: Context): Promise<McpRuntime>;\n}\n\nconst idle: McpRuntime = {\n status: () => [],\n reload: async () => undefined,\n close: async () => undefined,\n};\n\nasync function unavailable(options: McpAttach, error: unknown, context: Context) {\n const file = `${options.home}/.pi/agent/mcp.json`;\n const read = await options.env.readTextFile(file, context);\n if (!parseMcpConfig(read.ok ? read.value : undefined).servers.length) return;\n await options.licks\n .deliver(\n {\n channel: 'fswatch',\n source: 'mcp:~/.pi/agent/mcp.json',\n title: \"MCP servers can't start in this SLICC\",\n text: error instanceof Error ? error.message : String(error),\n body: \"The MCP client didn't load, so the servers in ~/.pi/agent/mcp.json stay off until SLICC is updated.\",\n target: 'cone',\n severity: 'warn',\n eventId: 'mcp\\nunavailable',\n },\n context\n )\n .catch(() => undefined);\n}\n\nexport function setupMcp(registry: Registry, load: McpLoader = loadRuntime): McpSetup {\n let catalog: McpCatalog | undefined;\n return {\n catalog: () => catalog,\n direct: (names) => catalog?.direct(names) ?? [],\n codemode: async (api, agent, context) =>\n catalog ? catalog.codemode(api, agent, context) : { tools: [], globals: [] },\n async attach(options, context) {\n let started: Awaited<ReturnType<Awaited<ReturnType<McpLoader>>['startMcp']>>;\n try {\n started = await (await load()).startMcp(registry, options, context);\n } catch (error) {\n await unavailable(options, error, context);\n return idle;\n }\n catalog = started.catalog;\n return started.runtime;\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AASA,SAAS,sBAAsB;AAG/B,SAAS,QAA4B,aAAa,YAAY,qBAAqB;AAK5E,MAAM,cAAyB,MAAM,OAAO,cAAc;AASjE,MAAM,OAAmB;AAAA,EACvB,QAAQ,MAAM,CAAC;AAAA,EACf,QAAQ,YAAY;AAAA,EACpB,OAAO,YAAY;AACrB;AAEA,eAAe,YAAY,SAAoB,OAAgB,SAAkB;AAC/E,QAAM,OAAO,GAAG,QAAQ,IAAI;AAC5B,QAAM,OAAO,MAAM,QAAQ,IAAI,aAAa,MAAM,OAAO;AACzD,MAAI,CAAC,eAAe,KAAK,KAAK,KAAK,QAAQ,MAAS,EAAE,QAAQ,OAAQ;AACtE,QAAM,QAAQ,MACX;AAAA,IACC;AAAA,MACE,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC3D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AAC1B;AAEO,SAAS,SAAS,UAAoB,OAAkB,aAAuB;AACpF,MAAI;AACJ,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,CAAC,UAAU,SAAS,OAAO,KAAK,KAAK,CAAC;AAAA,IAC9C,UAAU,OAAO,KAAK,OAAO,YAC3B,UAAU,QAAQ,SAAS,KAAK,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,IAC7E,MAAM,OAAO,SAAS,SAAS;AAC7B,UAAI;AACJ,UAAI;AACF,kBAAU,OAAO,MAAM,KAAK,GAAG,SAAS,UAAU,SAAS,OAAO;AAAA,MACpE,SAAS,OAAO;AACd,cAAM,YAAY,SAAS,OAAO,OAAO;AACzC,eAAO;AAAA,MACT;AACA,gBAAU,QAAQ;AAClB,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;",
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type {\n Agent,\n Registry,\n ToolExecutionApi,\n ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport type { CodemodeExtra } from './allow.ts';\nimport type { McpCatalog } from './catalog.ts';\nimport { parseMcpConfig } from './config.ts';\nimport type { McpAttach, McpRuntime } from './runtime.ts';\n\nexport { allows, type CodemodeExtra, McpAllowDoc, mcpEntries, serverAllowed } from './allow.ts';\nexport type { McpAccounts, McpAttach, McpRuntime, ServerStatus } from './runtime.ts';\n\nexport type McpLoader = () => Promise<typeof import('./runtime.ts')>;\n\nexport const loadRuntime: McpLoader = () => import('./runtime.ts');\n\nexport interface McpSetup {\n catalog(): McpCatalog | undefined;\n direct(names: readonly string[]): ToolRegistration[];\n codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra>;\n attach(options: McpAttach, context: Context): Promise<McpRuntime>;\n}\n\nconst idle: McpRuntime = {\n accounts: async () => [],\n connect: async (id) => {\n throw new Error(`${id} is not an MCP server that takes a token`);\n },\n disconnect: async () => undefined,\n onChange: () => () => undefined,\n status: () => [],\n reload: async () => undefined,\n close: async () => undefined,\n};\n\nasync function unavailable(options: McpAttach, error: unknown, context: Context) {\n const file = `${options.home}/.pi/agent/mcp.json`;\n const read = await options.env.readTextFile(file, context);\n if (!parseMcpConfig(read.ok ? read.value : undefined).servers.length) return;\n await options.licks\n .deliver(\n {\n channel: 'fswatch',\n source: 'mcp:~/.pi/agent/mcp.json',\n title: \"MCP servers can't start in this SLICC\",\n text: error instanceof Error ? error.message : String(error),\n body: \"The MCP client didn't load, so the servers in ~/.pi/agent/mcp.json stay off until SLICC is updated.\",\n target: 'cone',\n severity: 'warn',\n eventId: 'mcp\\nunavailable',\n },\n context\n )\n .catch(() => undefined);\n}\n\nexport function setupMcp(registry: Registry, load: McpLoader = loadRuntime): McpSetup {\n let catalog: McpCatalog | undefined;\n return {\n catalog: () => catalog,\n direct: (names) => catalog?.direct(names) ?? [],\n codemode: async (api, agent, context) =>\n catalog ? catalog.codemode(api, agent, context) : { tools: [], globals: [] },\n async attach(options, context) {\n let started: Awaited<ReturnType<Awaited<ReturnType<McpLoader>>['startMcp']>>;\n try {\n started = await (await load()).startMcp(registry, options, context);\n } catch (error) {\n await unavailable(options, error, context);\n return idle;\n }\n catalog = started.catalog;\n return started.runtime;\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,sBAAsB;AAG/B,SAAS,QAA4B,aAAa,YAAY,qBAAqB;AAK5E,MAAM,cAAyB,MAAM,OAAO,cAAc;AASjE,MAAM,OAAmB;AAAA,EACvB,UAAU,YAAY,CAAC;AAAA,EACvB,SAAS,OAAO,OAAO;AACrB,UAAM,IAAI,MAAM,GAAG,EAAE,0CAA0C;AAAA,EACjE;AAAA,EACA,YAAY,YAAY;AAAA,EACxB,UAAU,MAAM,MAAM;AAAA,EACtB,QAAQ,MAAM,CAAC;AAAA,EACf,QAAQ,YAAY;AAAA,EACpB,OAAO,YAAY;AACrB;AAEA,eAAe,YAAY,SAAoB,OAAgB,SAAkB;AAC/E,QAAM,OAAO,GAAG,QAAQ,IAAI;AAC5B,QAAM,OAAO,MAAM,QAAQ,IAAI,aAAa,MAAM,OAAO;AACzD,MAAI,CAAC,eAAe,KAAK,KAAK,KAAK,QAAQ,MAAS,EAAE,QAAQ,OAAQ;AACtE,QAAM,QAAQ,MACX;AAAA,IACC;AAAA,MACE,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC3D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AAC1B;AAEO,SAAS,SAAS,UAAoB,OAAkB,aAAuB;AACpF,MAAI;AACJ,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,CAAC,UAAU,SAAS,OAAO,KAAK,KAAK,CAAC;AAAA,IAC9C,UAAU,OAAO,KAAK,OAAO,YAC3B,UAAU,QAAQ,SAAS,KAAK,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,IAC7E,MAAM,OAAO,SAAS,SAAS;AAC7B,UAAI;AACJ,UAAI;AACF,kBAAU,OAAO,MAAM,KAAK,GAAG,SAAS,UAAU,SAAS,OAAO;AAAA,MACpE,SAAS,OAAO;AACd,cAAM,YAAY,SAAS,OAAO,OAAO;AACzC,eAAO;AAAA,MACT;AACA,gBAAU,QAAQ;AAClB,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function mcpLabel(name) {
|
|
2
|
+
const parts = name.split("__");
|
|
3
|
+
if (parts.length !== 3 || parts[0] !== "mcp") return void 0;
|
|
4
|
+
const [, server, tool] = parts;
|
|
5
|
+
if (!/^[A-Za-z0-9](?:.*[A-Za-z0-9])?$/.test(server) || !/^[A-Za-z0-9]/.test(tool))
|
|
6
|
+
return void 0;
|
|
7
|
+
return `${server} \xB7 ${tool}`;
|
|
8
|
+
}
|
|
9
|
+
const MCP_ACCOUNT = "mcp:";
|
|
10
|
+
export {
|
|
11
|
+
MCP_ACCOUNT,
|
|
12
|
+
mcpLabel
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=label.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/mcp/label.ts"],
|
|
4
|
+
"sourcesContent": ["export function mcpLabel(name: string): string | undefined {\n const parts = name.split('__');\n if (parts.length !== 3 || parts[0] !== 'mcp') return undefined;\n const [, server, tool] = parts as [string, string, string];\n if (!/^[A-Za-z0-9](?:.*[A-Za-z0-9])?$/.test(server) || !/^[A-Za-z0-9]/.test(tool))\n return undefined;\n return `${server} \u00B7 ${tool}`;\n}\n\nexport const MCP_ACCOUNT = 'mcp:';\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,SAAS,MAAkC;AACzD,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,MAAI,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM,MAAO,QAAO;AACrD,QAAM,CAAC,EAAE,QAAQ,IAAI,IAAI;AACzB,MAAI,CAAC,kCAAkC,KAAK,MAAM,KAAK,CAAC,eAAe,KAAK,IAAI;AAC9E,WAAO;AACT,SAAO,GAAG,MAAM,SAAM,IAAI;AAC5B;AAEO,MAAM,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/mcp/runtime.js
CHANGED
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
sameConfig
|
|
6
6
|
} from "./config.js";
|
|
7
7
|
import { McpServerConnection } from "./connection.js";
|
|
8
|
-
import { resolveHeaders, scrub } from "./credentials.js";
|
|
8
|
+
import { credentialKey, resolveHeaders, scrub, secretNames } from "./credentials.js";
|
|
9
|
+
import { MCP_ACCOUNT } from "./label.js";
|
|
9
10
|
import { patchSse } from "./sse.js";
|
|
10
11
|
const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
11
12
|
function configPath(home) {
|
|
@@ -38,8 +39,8 @@ function stateLick(connection, cors) {
|
|
|
38
39
|
if (connection.state === "needs-secret")
|
|
39
40
|
return {
|
|
40
41
|
...base,
|
|
41
|
-
title: `MCP server "${name}" needs a
|
|
42
|
-
text: `Its headers use ${connection.missing.map((item) => `\${${item}}`).join(", ")}. Add the value in Settings \u203A
|
|
42
|
+
title: `MCP server "${name}" needs a token`,
|
|
43
|
+
text: `Its headers use ${connection.missing.map((item) => `\${${item}}`).join(", ")}. Add the value in Settings \u203A MCP servers.`,
|
|
43
44
|
severity: "warn",
|
|
44
45
|
eventId: `${name}
|
|
45
46
|
secret
|
|
@@ -48,7 +49,7 @@ ${connection.missing.join(",")}`
|
|
|
48
49
|
if (connection.state === "needs-auth")
|
|
49
50
|
return {
|
|
50
51
|
...base,
|
|
51
|
-
title: `MCP server "${name}"
|
|
52
|
+
title: `MCP server "${name}" rejected its token`,
|
|
52
53
|
text: connection.signInMessage(),
|
|
53
54
|
severity: "warn",
|
|
54
55
|
eventId: `${name}
|
|
@@ -103,6 +104,52 @@ function serverLog(options, context) {
|
|
|
103
104
|
settled: () => logging
|
|
104
105
|
};
|
|
105
106
|
}
|
|
107
|
+
function mcpAccounts(catalog, credentials, restart, listeners) {
|
|
108
|
+
const find = (id) => {
|
|
109
|
+
const connection = catalog.connections.get(id.slice(MCP_ACCOUNT.length));
|
|
110
|
+
if (!id.startsWith(MCP_ACCOUNT) || !connection || !secretNames(connection.entry.config).length)
|
|
111
|
+
throw new Error(`${id} is not an MCP server that takes a token`);
|
|
112
|
+
return connection;
|
|
113
|
+
};
|
|
114
|
+
const keyOf = (connection) => credentialKey(connection.name, connection.entry.config.url);
|
|
115
|
+
return {
|
|
116
|
+
async accounts() {
|
|
117
|
+
const rows = [];
|
|
118
|
+
for (const connection of catalog.connections.values()) {
|
|
119
|
+
if (!secretNames(connection.entry.config).length) continue;
|
|
120
|
+
const stored = await credentials.read(await keyOf(connection)) !== void 0;
|
|
121
|
+
rows.push({
|
|
122
|
+
id: `${MCP_ACCOUNT}${connection.name}`,
|
|
123
|
+
provider: connection.name,
|
|
124
|
+
identity: new URL(connection.entry.config.url).host,
|
|
125
|
+
status: connection.state === "needs-auth" ? "expired" : stored ? "connected" : "disconnected",
|
|
126
|
+
auth: "api-key",
|
|
127
|
+
needs: null,
|
|
128
|
+
group: "mcp",
|
|
129
|
+
secret: "token"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return rows;
|
|
133
|
+
},
|
|
134
|
+
async connect(id, secret) {
|
|
135
|
+
const connection = find(id);
|
|
136
|
+
await credentials.modify(await keyOf(connection), async () => ({
|
|
137
|
+
type: "api_key",
|
|
138
|
+
key: secret
|
|
139
|
+
}));
|
|
140
|
+
restart(connection);
|
|
141
|
+
},
|
|
142
|
+
async disconnect(id) {
|
|
143
|
+
const connection = find(id);
|
|
144
|
+
await credentials.delete(await keyOf(connection));
|
|
145
|
+
restart(connection);
|
|
146
|
+
},
|
|
147
|
+
onChange(listener) {
|
|
148
|
+
listeners.add(listener);
|
|
149
|
+
return () => listeners.delete(listener);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
106
153
|
async function attachMcp(catalog, options, context) {
|
|
107
154
|
const { env, home } = options;
|
|
108
155
|
catalog.codemodeOn = options.codemode;
|
|
@@ -112,6 +159,10 @@ async function attachMcp(catalog, options, context) {
|
|
|
112
159
|
reported.add(lick.eventId);
|
|
113
160
|
await options.licks.deliver(lick, using).catch(() => void 0);
|
|
114
161
|
};
|
|
162
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
163
|
+
const notify = () => {
|
|
164
|
+
for (const listener of listeners) listener();
|
|
165
|
+
};
|
|
115
166
|
let initial = /* @__PURE__ */ new Set();
|
|
116
167
|
let first = true;
|
|
117
168
|
const log = serverLog(options, context);
|
|
@@ -138,6 +189,7 @@ async function attachMcp(catalog, options, context) {
|
|
|
138
189
|
void report(connectedLick(changed), context);
|
|
139
190
|
void report(stateLick(changed, options.cors), context);
|
|
140
191
|
if (changed.state !== "connected") catalog.rebuild();
|
|
192
|
+
notify();
|
|
141
193
|
}
|
|
142
194
|
});
|
|
143
195
|
catalog.connections.set(entry.name, connection);
|
|
@@ -161,6 +213,7 @@ async function attachMcp(catalog, options, context) {
|
|
|
161
213
|
first = false;
|
|
162
214
|
for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);
|
|
163
215
|
catalog.rebuild();
|
|
216
|
+
notify();
|
|
164
217
|
await Promise.all(closing);
|
|
165
218
|
};
|
|
166
219
|
await reload(context);
|
|
@@ -171,7 +224,14 @@ async function attachMcp(catalog, options, context) {
|
|
|
171
224
|
};
|
|
172
225
|
const watched = await env.watch([{ path: configPath(home) }], schedule, context);
|
|
173
226
|
const watcher = watched.ok ? watched.value : void 0;
|
|
227
|
+
const restart = (connection) => {
|
|
228
|
+
catalog.connections.delete(connection.name);
|
|
229
|
+
void connection.close();
|
|
230
|
+
open(connection.entry);
|
|
231
|
+
catalog.rebuild();
|
|
232
|
+
};
|
|
174
233
|
return {
|
|
234
|
+
...mcpAccounts(catalog, options.credentials, restart, listeners),
|
|
175
235
|
status: () => [...catalog.connections.values()].map((connection) => ({
|
|
176
236
|
name: connection.name,
|
|
177
237
|
state: connection.state,
|
|
@@ -202,6 +262,7 @@ export {
|
|
|
202
262
|
connectedLick,
|
|
203
263
|
logLine,
|
|
204
264
|
logPath,
|
|
265
|
+
mcpAccounts,
|
|
205
266
|
problemLick,
|
|
206
267
|
startMcp,
|
|
207
268
|
stateLick
|