@agentconnect.md/daemon 1.0.0-rc.220 → 1.0.0-rc.222
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/dist/{acp-host-CeJJZbtg.js → acp-host-DE2r7uCO.js} +191 -16
- package/dist/acp-host-DE2r7uCO.js.map +1 -0
- package/dist/{agent-list-CoYaBcDx.js → agent-list-BwgY3Xqv.js} +2 -2
- package/dist/{agent-list-CoYaBcDx.js.map → agent-list-BwgY3Xqv.js.map} +1 -1
- package/dist/bridge-DXIVgEzf.js +726 -0
- package/dist/bridge-DXIVgEzf.js.map +1 -0
- package/dist/{chat-Bcpnzw3h.js → chat--hmAp3lB.js} +4 -3
- package/dist/chat--hmAp3lB.js.map +1 -0
- package/dist/index.js +5 -5
- package/dist/{load-agents-nWFFmN5-.js → load-agents-hq5NXMmx.js} +34 -6
- package/dist/load-agents-hq5NXMmx.js.map +1 -0
- package/dist/{login-B99oQNXR.js → login-EVa3UJRh.js} +4 -4
- package/dist/{login-B99oQNXR.js.map → login-EVa3UJRh.js.map} +1 -1
- package/dist/{run-foreground-DLxz75b4.js → run-foreground-B05rDr1z.js} +4101 -211
- package/dist/run-foreground-B05rDr1z.js.map +1 -0
- package/dist/run-foreground-Bcg0RA3e.js +2 -0
- package/dist/{schemas-DujcoO0q.js → schemas-CQkO9hC5.js} +27 -2
- package/dist/schemas-CQkO9hC5.js.map +1 -0
- package/dist/{bridge-DoUrmBw9.js → stdio-CwrB9yrX.js} +41 -727
- package/dist/stdio-CwrB9yrX.js.map +1 -0
- package/package.json +1 -1
- package/dist/acp-host-CeJJZbtg.js.map +0 -1
- package/dist/bridge-DoUrmBw9.js.map +0 -1
- package/dist/chat-Bcpnzw3h.js.map +0 -1
- package/dist/ipc-2Xbxe7uu.js +0 -35
- package/dist/ipc-2Xbxe7uu.js.map +0 -1
- package/dist/load-agents-nWFFmN5-.js.map +0 -1
- package/dist/run-foreground-DLxz75b4.js.map +0 -1
- package/dist/run-foreground-PidBMaVD.js +0 -2
- package/dist/schemas-DujcoO0q.js.map +0 -1
|
@@ -1,27 +1,53 @@
|
|
|
1
1
|
import { i as __require, o as __toESM, t as __commonJSMin } from "./rolldown-runtime-CE-6LUnI.js";
|
|
2
2
|
import { n as require_ms, t as require_src$2 } from "./src-DDtlVutj.js";
|
|
3
3
|
import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
4
|
-
import { a as
|
|
4
|
+
import { C as _coercedNumber, T as NEVER, a as array, d as literal, f as looseObject, i as any, m as object, o as boolean, p as number$1, t as ZodNumber, v as string, x as url$4 } from "./schemas-CQkO9hC5.js";
|
|
5
|
+
import { a as agentChildEnv, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-hq5NXMmx.js";
|
|
5
6
|
import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
|
|
6
|
-
import { _ as
|
|
7
|
+
import { _ as cachedRuntimeNames, a as initGitInjection, c as simpleGit, d as MAX_FRAME_BYTES, f as buildEnvelope, g as RESERVED_MCP_SERVER_NAME, h as MEMORY_INDEX, i as prepareWorkspace, m as encode, n as installedRuntimes, o as probeGitVersion, p as decodeEnvelope, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as normalizeGitUrl, v as cachedRuntimeVersions, y as resolveRuntimes } from "./acp-host-DE2r7uCO.js";
|
|
7
8
|
import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Dcsq6QJe.js";
|
|
8
|
-
import { n as encodeFrame, t as
|
|
9
|
+
import { B as isInitializedNotification, C as JSONRPCMessageSchema, D as ListResourceTemplatesResultSchema, E as ListPromptsResultSchema, F as ReadResourceResultSchema, G as safeParse, H as isJSONRPCResultResponse, I as ResourceListChangedNotificationSchema, K as decodeFrames, L as SUPPORTED_PROTOCOL_VERSIONS, N as McpError, O as ListResourcesResultSchema, P as PromptListChangedNotificationSchema, T as ListChangedOptionsBaseSchema, U as getObjectShape, V as isJSONRPCRequest, W as isZ4Schema, _ as EmptyResultSchema, a as AjvJsonSchemaValidator, d as CreateMessageRequestSchema, f as CreateMessageResultSchema, g as ElicitResultSchema, h as ElicitRequestSchema, i as assertToolsCallTaskCapability, j as ListToolsResultSchema, l as CallToolResultSchema, m as CreateTaskResultSchema, n as serializeMessage, o as Protocol, p as CreateMessageResultWithToolsSchema, q as encodeFrame, r as assertClientRequestTaskCapability, s as mergeCapabilities, t as ReadBuffer, u as CompleteResultSchema, v as ErrorCode, w as LATEST_PROTOCOL_VERSION, x as InitializeResultSchema, y as GetPromptResultSchema, z as ToolListChangedNotificationSchema } from "./stdio-CwrB9yrX.js";
|
|
9
10
|
import { URL as URL$1 } from "url";
|
|
10
11
|
import * as tls from "tls";
|
|
11
12
|
import * as net$2 from "net";
|
|
12
13
|
import * as sp from "node:path";
|
|
13
|
-
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
14
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
14
15
|
import * as http$6 from "http";
|
|
15
16
|
import { Agent } from "https";
|
|
16
17
|
import { chmodSync, existsSync, mkdirSync, mkdtempSync, promises, readFileSync, rmSync, stat, unwatchFile, watch, watchFile, writeFileSync } from "node:fs";
|
|
17
18
|
import { EventEmitter } from "node:events";
|
|
19
|
+
import process$1 from "node:process";
|
|
18
20
|
import { hostname, tmpdir, type } from "node:os";
|
|
19
21
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
20
|
-
import { Readable } from "node:stream";
|
|
22
|
+
import { PassThrough, Readable } from "node:stream";
|
|
21
23
|
import net from "node:net";
|
|
22
24
|
import { lstat, open, readdir, realpath, stat as stat$1 } from "node:fs/promises";
|
|
23
25
|
import { DatabaseSync } from "node:sqlite";
|
|
24
26
|
import assert from "assert";
|
|
27
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/compat.js
|
|
28
|
+
/** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
|
|
29
|
+
const ZodIssueCode = {
|
|
30
|
+
invalid_type: "invalid_type",
|
|
31
|
+
too_big: "too_big",
|
|
32
|
+
too_small: "too_small",
|
|
33
|
+
invalid_format: "invalid_format",
|
|
34
|
+
not_multiple_of: "not_multiple_of",
|
|
35
|
+
unrecognized_keys: "unrecognized_keys",
|
|
36
|
+
invalid_union: "invalid_union",
|
|
37
|
+
invalid_key: "invalid_key",
|
|
38
|
+
invalid_element: "invalid_element",
|
|
39
|
+
invalid_value: "invalid_value",
|
|
40
|
+
custom: "custom"
|
|
41
|
+
};
|
|
42
|
+
/** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
|
|
43
|
+
var ZodFirstPartyTypeKind;
|
|
44
|
+
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/coerce.js
|
|
47
|
+
function number(params) {
|
|
48
|
+
return _coercedNumber(ZodNumber, params);
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
25
51
|
//#region ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/constants.js
|
|
26
52
|
var require_constants$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
27
53
|
const BINARY_TYPES = [
|
|
@@ -5333,6 +5359,7 @@ function hostSpawnSig(a) {
|
|
|
5333
5359
|
reasoningEffort: a.reasoningEffort,
|
|
5334
5360
|
executionMode: a.executionMode,
|
|
5335
5361
|
fastMode: a.fastMode,
|
|
5362
|
+
permissionMode: a.permissionMode,
|
|
5336
5363
|
env: a.runtimeOverrides?.env
|
|
5337
5364
|
});
|
|
5338
5365
|
}
|
|
@@ -5395,7 +5422,8 @@ var LocalStore = class {
|
|
|
5395
5422
|
key TEXT PRIMARY KEY, agentId TEXT, platform TEXT, channel TEXT, thread TEXT,
|
|
5396
5423
|
acpSessionId TEXT, state TEXT, lastDeliveredTs TEXT, updatedAt INTEGER,
|
|
5397
5424
|
usage TEXT, muted INTEGER, triggeredBy TEXT, title TEXT, modelOverride TEXT,
|
|
5398
|
-
effortOverride TEXT, fastModeOverride INTEGER,
|
|
5425
|
+
effortOverride TEXT, permissionModeOverride TEXT, fastModeOverride INTEGER,
|
|
5426
|
+
outputModeOverride TEXT
|
|
5399
5427
|
);
|
|
5400
5428
|
-- Platform id → human display name (Slack channel/user names, daemon-resolved
|
|
5401
5429
|
-- and cached here so session read-back can label ids without a Slack call).
|
|
@@ -5442,10 +5470,11 @@ var LocalStore = class {
|
|
|
5442
5470
|
}
|
|
5443
5471
|
/** Add the `sessions.usage` (JSON token accounting), `sessions.muted` (`!stop`
|
|
5444
5472
|
* thread mute), `sessions.triggeredBy`, `sessions.title`, `sessions.modelOverride`,
|
|
5445
|
-
* `sessions.effortOverride
|
|
5446
|
-
* model / reasoning-effort /
|
|
5447
|
-
*
|
|
5448
|
-
*
|
|
5473
|
+
* `sessions.effortOverride`, `sessions.permissionModeOverride` and
|
|
5474
|
+
* `sessions.fastModeOverride` (the sticky per-session model / reasoning-effort /
|
|
5475
|
+
* permission-mode / fast-mode choices) columns to a pre-existing DB. CREATE TABLE IF
|
|
5476
|
+
* NOT EXISTS above adds them for fresh DBs but never alters an existing table, so
|
|
5477
|
+
* upgrade in place. No-op once the columns are present. */
|
|
5449
5478
|
migrateSessionUsage() {
|
|
5450
5479
|
const cols = this.db.prepare("PRAGMA table_info(sessions)").all();
|
|
5451
5480
|
if (!cols.some((c) => c.name === "usage")) this.db.exec("ALTER TABLE sessions ADD COLUMN usage TEXT");
|
|
@@ -5454,6 +5483,7 @@ var LocalStore = class {
|
|
|
5454
5483
|
if (!cols.some((c) => c.name === "title")) this.db.exec("ALTER TABLE sessions ADD COLUMN title TEXT");
|
|
5455
5484
|
if (!cols.some((c) => c.name === "modelOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN modelOverride TEXT");
|
|
5456
5485
|
if (!cols.some((c) => c.name === "effortOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN effortOverride TEXT");
|
|
5486
|
+
if (!cols.some((c) => c.name === "permissionModeOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN permissionModeOverride TEXT");
|
|
5457
5487
|
if (!cols.some((c) => c.name === "fastModeOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN fastModeOverride INTEGER");
|
|
5458
5488
|
if (!cols.some((c) => c.name === "outputModeOverride")) this.db.exec("ALTER TABLE sessions ADD COLUMN outputModeOverride TEXT");
|
|
5459
5489
|
}
|
|
@@ -5563,6 +5593,15 @@ var LocalStore = class {
|
|
|
5563
5593
|
setEffortOverride(key, effort) {
|
|
5564
5594
|
this.db.prepare("UPDATE sessions SET effortOverride = ? WHERE key = ?").run(effort, key);
|
|
5565
5595
|
}
|
|
5596
|
+
/** The session-scoped permission-mode override (set via status bars), or undefined if
|
|
5597
|
+
* the session runs on the agent's default. Sticky across turns and restarts. */
|
|
5598
|
+
getPermissionModeOverride(key) {
|
|
5599
|
+
return this.db.prepare("SELECT permissionModeOverride FROM sessions WHERE key = ?").get(key)?.permissionModeOverride ?? void 0;
|
|
5600
|
+
}
|
|
5601
|
+
/** Persist the session-scoped permission-mode override. No-op on an unknown key. */
|
|
5602
|
+
setPermissionModeOverride(key, mode) {
|
|
5603
|
+
this.db.prepare("UPDATE sessions SET permissionModeOverride = ? WHERE key = ?").run(mode, key);
|
|
5604
|
+
}
|
|
5566
5605
|
/** The session-scoped fast-mode override (set via the status-bar fast toggle), or
|
|
5567
5606
|
* undefined if the session runs on the agent's default. Stored as 0/1; sticky across
|
|
5568
5607
|
* turns and restarts, re-applied to the ACP session on each dispatch. */
|
|
@@ -5757,6 +5796,150 @@ function monotonicTs() {
|
|
|
5757
5796
|
return String(last);
|
|
5758
5797
|
}
|
|
5759
5798
|
//#endregion
|
|
5799
|
+
//#region src/agents/memory.ts
|
|
5800
|
+
/**
|
|
5801
|
+
* Agent memory — a DIRECTORY at the agent's ROOT (`<agent-root>/memory/`), OUTSIDE
|
|
5802
|
+
* the workspace so it survives a workspace reset / re-clone and is never committed
|
|
5803
|
+
* into a github-workspace repo.
|
|
5804
|
+
*
|
|
5805
|
+
* Index-plus-topics shape (the structure Claude Code's auto-memory uses): a lean
|
|
5806
|
+
* `MEMORY.md` index — injected into the prompt every session, so it must stay small
|
|
5807
|
+
* — plus any number of `<topic>.md` files the agent reads on demand. The agent
|
|
5808
|
+
* maintains it via the `readMemory` / `writeMemory` / `listMemory` MCP tools; the
|
|
5809
|
+
* daemon serves the same directory to the CP for the console.
|
|
5810
|
+
*
|
|
5811
|
+
* SECURITY: topic paths are agent/console-supplied, so every path is contained to
|
|
5812
|
+
* the memory dir — absolute paths and `..` escapes are rejected, and the resolved
|
|
5813
|
+
* path is re-checked so a symlink can't smuggle a target out. Flat by design (one
|
|
5814
|
+
* level): a topic is a file directly under `memory/`, no nested dirs.
|
|
5815
|
+
*/
|
|
5816
|
+
const MEMORY_DIRNAME = "memory";
|
|
5817
|
+
/** Max bytes of the index injected into the prompt each session — keeps a large
|
|
5818
|
+
* memory from swelling the context (mirrors Claude Code's 25 KB entrypoint cap). */
|
|
5819
|
+
const MAX_INDEX_INJECT_BYTES = 25e3;
|
|
5820
|
+
/** Hard cap on a single memory file. Injected/read every session, so an unbounded
|
|
5821
|
+
* file would swell the prompt and the wire; writes over this are rejected. */
|
|
5822
|
+
const MAX_MEMORY_FILE_BYTES = 256e3;
|
|
5823
|
+
/** Raised when a memory path escapes the memory dir. Surfaces as `BAD_PAYLOAD`. */
|
|
5824
|
+
var MemoryPathError = class extends Error {
|
|
5825
|
+
constructor(message) {
|
|
5826
|
+
super(message);
|
|
5827
|
+
this.name = "MemoryPathError";
|
|
5828
|
+
}
|
|
5829
|
+
};
|
|
5830
|
+
/** Raised when a write exceeds {@link MAX_MEMORY_FILE_BYTES}. `BAD_PAYLOAD`. */
|
|
5831
|
+
var MemoryTooLargeError = class extends Error {
|
|
5832
|
+
constructor(message) {
|
|
5833
|
+
super(message);
|
|
5834
|
+
this.name = "MemoryTooLargeError";
|
|
5835
|
+
}
|
|
5836
|
+
};
|
|
5837
|
+
/** Raised when an `ifMatchMtime` precondition fails (the file changed under the
|
|
5838
|
+
* writer — e.g. a console edit racing a newer agent write). Surfaces as CONFLICT. */
|
|
5839
|
+
var MemoryConflictError = class extends Error {
|
|
5840
|
+
constructor(message) {
|
|
5841
|
+
super(message);
|
|
5842
|
+
this.name = "MemoryConflictError";
|
|
5843
|
+
}
|
|
5844
|
+
};
|
|
5845
|
+
/** The memory directory for an agent, given its root dir (where agent.json lives). */
|
|
5846
|
+
function memoryDir(agentDir) {
|
|
5847
|
+
return join(agentDir, MEMORY_DIRNAME);
|
|
5848
|
+
}
|
|
5849
|
+
/** Resolve a memory-dir-relative path to an absolute one, contained to the dir.
|
|
5850
|
+
* Rejects absolute paths, `..` escapes, and any nested directory (flat only). */
|
|
5851
|
+
function resolveInMemoryDir(agentDir, relPath) {
|
|
5852
|
+
if (isAbsolute(relPath)) throw new MemoryPathError("absolute paths are not allowed");
|
|
5853
|
+
const dir = memoryDir(agentDir);
|
|
5854
|
+
const abs = resolve(dir, relPath);
|
|
5855
|
+
if (abs !== dir && !abs.startsWith(dir + sep)) throw new MemoryPathError("path escapes the memory dir");
|
|
5856
|
+
const rel = abs.slice(dir.length + 1);
|
|
5857
|
+
if (rel.includes(sep)) throw new MemoryPathError("memory is a flat directory (no subdirectories)");
|
|
5858
|
+
if (rel.length === 0) throw new MemoryPathError("a file name is required");
|
|
5859
|
+
return abs;
|
|
5860
|
+
}
|
|
5861
|
+
/** Seed `<agent-root>/memory/MEMORY.md` with a header if the dir/index is absent
|
|
5862
|
+
* (idempotent). Called at session start so a brand-new agent always has an index. */
|
|
5863
|
+
function ensureMemory(agentDir, agentName) {
|
|
5864
|
+
const dir = memoryDir(agentDir);
|
|
5865
|
+
mkdirSync(dir, { recursive: true });
|
|
5866
|
+
const index = join(dir, MEMORY_INDEX);
|
|
5867
|
+
if (!existsSync(index)) writeFileSync(index, `# ${agentName} memory\n\nYour long-term memory index. Keep it short — link out to topic files (e.g. \`[deploys](deploys.md)\`) for detail and read them on demand with the readMemory tool.\n`);
|
|
5868
|
+
}
|
|
5869
|
+
/** Read the memory index (`MEMORY.md`) for prompt injection, trimmed to the inject
|
|
5870
|
+
* cap so a large index can't blow up the prompt; '' when absent. */
|
|
5871
|
+
async function readIndex(agentDir) {
|
|
5872
|
+
const text = await readMemoryFile(agentDir, MEMORY_INDEX);
|
|
5873
|
+
if (Buffer.byteLength(text) <= 25e3) return text;
|
|
5874
|
+
return Buffer.from(text, "utf8").subarray(0, MAX_INDEX_INJECT_BYTES).toString("utf8") + "\n\n[…memory index truncated — trim MEMORY.md]";
|
|
5875
|
+
}
|
|
5876
|
+
/** Read a memory file's text; '' when it does not exist (never throws on ENOENT). */
|
|
5877
|
+
async function readMemoryFile(agentDir, relPath) {
|
|
5878
|
+
const abs = resolveInMemoryDir(agentDir, relPath);
|
|
5879
|
+
try {
|
|
5880
|
+
return await promises.readFile(abs, "utf8");
|
|
5881
|
+
} catch (err) {
|
|
5882
|
+
if (err.code === "ENOENT") return "";
|
|
5883
|
+
throw err;
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
/** Overwrite a memory file with `content` (creating the dir if needed). Atomic
|
|
5887
|
+
* (tmp + rename) so a concurrent read never sees a partial file.
|
|
5888
|
+
*
|
|
5889
|
+
* - Rejects content over {@link MAX_MEMORY_FILE_BYTES} ({@link MemoryTooLargeError}).
|
|
5890
|
+
* - `ifMatchMtime` (optimistic concurrency): when given, the current file's mtime
|
|
5891
|
+
* must equal it, else {@link MemoryConflictError} — so a console edit can't
|
|
5892
|
+
* clobber a newer write. A brand-new file (no mtime) matches `ifMatchMtime`
|
|
5893
|
+
* only when the caller passes none (or the empty string). */
|
|
5894
|
+
async function writeMemoryFile(agentDir, relPath, content, ifMatchMtime) {
|
|
5895
|
+
if (Buffer.byteLength(content) > 256e3) throw new MemoryTooLargeError(`memory file exceeds the ${MAX_MEMORY_FILE_BYTES}-byte limit`);
|
|
5896
|
+
const abs = resolveInMemoryDir(agentDir, relPath);
|
|
5897
|
+
if (ifMatchMtime) {
|
|
5898
|
+
let current = null;
|
|
5899
|
+
try {
|
|
5900
|
+
current = (await promises.stat(abs)).mtime.toISOString();
|
|
5901
|
+
} catch (err) {
|
|
5902
|
+
if (err.code !== "ENOENT") throw err;
|
|
5903
|
+
}
|
|
5904
|
+
if (current !== ifMatchMtime) throw new MemoryConflictError("the memory file changed since it was read; reload and retry");
|
|
5905
|
+
}
|
|
5906
|
+
await promises.mkdir(memoryDir(agentDir), { recursive: true });
|
|
5907
|
+
const tmp = `${abs}.tmp`;
|
|
5908
|
+
await promises.writeFile(tmp, content, "utf8");
|
|
5909
|
+
await promises.rename(tmp, abs);
|
|
5910
|
+
const st = await promises.stat(abs);
|
|
5911
|
+
return {
|
|
5912
|
+
size: st.size,
|
|
5913
|
+
mtime: st.mtime.toISOString()
|
|
5914
|
+
};
|
|
5915
|
+
}
|
|
5916
|
+
/** List the files in the memory dir (flat; index + topics), sorted with the index
|
|
5917
|
+
* first. Empty when the dir does not exist. `.tmp` write artifacts are skipped. */
|
|
5918
|
+
async function listMemory(agentDir) {
|
|
5919
|
+
const dir = memoryDir(agentDir);
|
|
5920
|
+
let dirents;
|
|
5921
|
+
try {
|
|
5922
|
+
dirents = await promises.readdir(dir, { withFileTypes: true });
|
|
5923
|
+
} catch (err) {
|
|
5924
|
+
if (err.code === "ENOENT") return [];
|
|
5925
|
+
throw err;
|
|
5926
|
+
}
|
|
5927
|
+
const files = [];
|
|
5928
|
+
for (const d of dirents) {
|
|
5929
|
+
if (!d.isFile() || d.name.endsWith(".tmp")) continue;
|
|
5930
|
+
try {
|
|
5931
|
+
const st = await promises.stat(join(dir, d.name));
|
|
5932
|
+
files.push({
|
|
5933
|
+
name: d.name,
|
|
5934
|
+
size: st.size,
|
|
5935
|
+
mtime: st.mtime.toISOString()
|
|
5936
|
+
});
|
|
5937
|
+
} catch {}
|
|
5938
|
+
}
|
|
5939
|
+
files.sort((a, b) => a.name === "MEMORY.md" ? -1 : b.name === "MEMORY.md" ? 1 : a.name.localeCompare(b.name));
|
|
5940
|
+
return files;
|
|
5941
|
+
}
|
|
5942
|
+
//#endregion
|
|
5760
5943
|
//#region src/session/attachment-block.ts
|
|
5761
5944
|
/**
|
|
5762
5945
|
* Turn one Slack attachment into the richest ACP content block the agent can
|
|
@@ -5833,6 +6016,7 @@ var SessionManager = class {
|
|
|
5833
6016
|
async handle(agentId, msg) {
|
|
5834
6017
|
const agent = this.deps.agentById(agentId);
|
|
5835
6018
|
if (!agent) throw new Error(`unknown agent ${agentId}`);
|
|
6019
|
+
ensureMemory(agent.dir, agent.name);
|
|
5836
6020
|
const { thread, ts: coordTs } = transcriptCoords(msg);
|
|
5837
6021
|
const ts = msg.platform === "webchat" ? monotonicTs() : coordTs;
|
|
5838
6022
|
const key = sessionKey(msg.platform, msg.channel, thread, agentId);
|
|
@@ -5873,20 +6057,20 @@ var SessionManager = class {
|
|
|
5873
6057
|
this.deps.store.upsertSession(rec);
|
|
5874
6058
|
} else if (host.hasSession?.(rec.acpSessionId) === false) {
|
|
5875
6059
|
const cwd = await prepareWorkspace(agent);
|
|
6060
|
+
const mcpServers = this.deps.mcpServersFor?.({
|
|
6061
|
+
agent,
|
|
6062
|
+
channel: msg.channel,
|
|
6063
|
+
thread
|
|
6064
|
+
}) ?? [];
|
|
5876
6065
|
let resumed = false;
|
|
5877
6066
|
if (host.loadSupported?.()) {
|
|
5878
6067
|
this.deps.store.setSessionState(key, "resuming", Date.now());
|
|
5879
6068
|
try {
|
|
5880
|
-
await host.loadSession(rec.acpSessionId, cwd, effortOverride);
|
|
6069
|
+
await host.loadSession(rec.acpSessionId, cwd, mcpServers, effortOverride);
|
|
5881
6070
|
resumed = true;
|
|
5882
6071
|
} catch {}
|
|
5883
6072
|
}
|
|
5884
6073
|
if (!resumed) {
|
|
5885
|
-
const mcpServers = this.deps.mcpServersFor?.({
|
|
5886
|
-
agent,
|
|
5887
|
-
channel: msg.channel,
|
|
5888
|
-
thread
|
|
5889
|
-
}) ?? [];
|
|
5890
6074
|
const acpSessionId = await host.newSession(cwd, mcpServers, effortOverride);
|
|
5891
6075
|
created = true;
|
|
5892
6076
|
rec = {
|
|
@@ -5925,6 +6109,13 @@ var SessionManager = class {
|
|
|
5925
6109
|
text: `${head}\n${ctxText}`
|
|
5926
6110
|
});
|
|
5927
6111
|
}
|
|
6112
|
+
if (created) {
|
|
6113
|
+
const index = (await readIndex(agent.dir)).trim();
|
|
6114
|
+
if (index) blocks.push({
|
|
6115
|
+
type: "text",
|
|
6116
|
+
text: `# Your memory\nThis is your memory INDEX from earlier sessions. Read a linked topic file with the \`readMemory\` tool when relevant; revise memory with \`writeMemory\`.\n\n${index}`
|
|
6117
|
+
});
|
|
6118
|
+
}
|
|
5928
6119
|
blocks.push({
|
|
5929
6120
|
type: "text",
|
|
5930
6121
|
text: msg.text
|
|
@@ -6093,8 +6284,34 @@ function guessMimeFromUrl(url) {
|
|
|
6093
6284
|
* missing connection — the caller turns that into an MCP `isError` result.
|
|
6094
6285
|
*/
|
|
6095
6286
|
async function executeTool(ctx, name, args, deps) {
|
|
6096
|
-
|
|
6097
|
-
|
|
6287
|
+
if (name === "listMemory" || name === "readMemory" || name === "writeMemory") {
|
|
6288
|
+
const dir = deps.memoryDirFor(ctx.agentId);
|
|
6289
|
+
if (!dir) throw new Error(`unknown agent ${ctx.agentId}`);
|
|
6290
|
+
try {
|
|
6291
|
+
if (name === "listMemory") return { files: await listMemory(dir) };
|
|
6292
|
+
if (name === "readMemory") {
|
|
6293
|
+
const path = optionalString(args, "path") ?? "MEMORY.md";
|
|
6294
|
+
return {
|
|
6295
|
+
path,
|
|
6296
|
+
content: await readMemoryFile(dir, path)
|
|
6297
|
+
};
|
|
6298
|
+
}
|
|
6299
|
+
const path = optionalString(args, "path") ?? "MEMORY.md";
|
|
6300
|
+
const { size, mtime } = await writeMemoryFile(dir, path, requireStringAllowEmpty(args, "content"));
|
|
6301
|
+
return {
|
|
6302
|
+
ok: true,
|
|
6303
|
+
path,
|
|
6304
|
+
size,
|
|
6305
|
+
mtime
|
|
6306
|
+
};
|
|
6307
|
+
} catch (err) {
|
|
6308
|
+
if (err instanceof MemoryPathError) throw new Error(`invalid memory path: ${err.message}`);
|
|
6309
|
+
if (err instanceof MemoryTooLargeError) throw new Error(err.message);
|
|
6310
|
+
throw err;
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
const gw = ctx.integrationId ? deps.gatewayFor(ctx.integrationId) : void 0;
|
|
6314
|
+
if (!gw) throw new Error(`no live Slack connection for integration ${ctx.integrationId ?? "(none)"}`);
|
|
6098
6315
|
switch (name) {
|
|
6099
6316
|
case "sendSlackMessage": {
|
|
6100
6317
|
const text = requireString(args, "text");
|
|
@@ -6150,159 +6367,3523 @@ async function executeTool(ctx, name, args, deps) {
|
|
|
6150
6367
|
text: `Downloaded ${bytes.byteLength} bytes of ${mimeType} (binary — not shown inline).`
|
|
6151
6368
|
}] };
|
|
6152
6369
|
}
|
|
6153
|
-
default: throw new Error(`unknown tool: ${name}`);
|
|
6370
|
+
default: throw new Error(`unknown tool: ${name}`);
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
function requireString(args, key) {
|
|
6374
|
+
const v = args[key];
|
|
6375
|
+
if (typeof v !== "string" || v.length === 0) throw new Error(`missing required string argument: ${key}`);
|
|
6376
|
+
return v;
|
|
6377
|
+
}
|
|
6378
|
+
/** Like `requireString` but accepts '' — for `updateMemory`, where an empty string
|
|
6379
|
+
* is a valid value (clear the memory). */
|
|
6380
|
+
function requireStringAllowEmpty(args, key) {
|
|
6381
|
+
const v = args[key];
|
|
6382
|
+
if (typeof v !== "string") throw new Error(`missing required string argument: ${key}`);
|
|
6383
|
+
return v;
|
|
6384
|
+
}
|
|
6385
|
+
function optionalString(args, key) {
|
|
6386
|
+
const v = args[key];
|
|
6387
|
+
if (v === void 0 || v === null) return void 0;
|
|
6388
|
+
if (typeof v !== "string") throw new Error(`argument ${key} must be a string`);
|
|
6389
|
+
return v;
|
|
6390
|
+
}
|
|
6391
|
+
//#endregion
|
|
6392
|
+
//#region src/mcp/control-server.ts
|
|
6393
|
+
/**
|
|
6394
|
+
* The daemon-hosted half of MCP. It owns all tool logic, the registry of live
|
|
6395
|
+
* sessions, and the Unix-domain socket the `mcp-bridge` subprocesses connect to.
|
|
6396
|
+
* "The MCP server is the daemon itself" — the bridge is only a stdio↔socket pipe.
|
|
6397
|
+
*/
|
|
6398
|
+
var McpControlServer = class {
|
|
6399
|
+
deps;
|
|
6400
|
+
server;
|
|
6401
|
+
sessions = /* @__PURE__ */ new Map();
|
|
6402
|
+
conns = /* @__PURE__ */ new Set();
|
|
6403
|
+
constructor(deps) {
|
|
6404
|
+
this.deps = deps;
|
|
6405
|
+
}
|
|
6406
|
+
/**
|
|
6407
|
+
* Register a session's tool set and return an opaque token. The token is
|
|
6408
|
+
* embedded in the bridge's env at `session/new`; the bridge presents it on
|
|
6409
|
+
* every IPC request so we can resolve the channel/thread/agent binding.
|
|
6410
|
+
*/
|
|
6411
|
+
register(ctx) {
|
|
6412
|
+
const token = randomBytes(18).toString("base64url");
|
|
6413
|
+
this.sessions.set(token, ctx);
|
|
6414
|
+
return token;
|
|
6415
|
+
}
|
|
6416
|
+
unregister(token) {
|
|
6417
|
+
this.sessions.delete(token);
|
|
6418
|
+
}
|
|
6419
|
+
async start() {
|
|
6420
|
+
if (this.server) return;
|
|
6421
|
+
const path = this.deps.socketPath;
|
|
6422
|
+
const dir = dirname(path);
|
|
6423
|
+
mkdirSync(dir, {
|
|
6424
|
+
recursive: true,
|
|
6425
|
+
mode: 448
|
|
6426
|
+
});
|
|
6427
|
+
try {
|
|
6428
|
+
chmodSync(dir, 448);
|
|
6429
|
+
} catch {}
|
|
6430
|
+
rmSync(path, { force: true });
|
|
6431
|
+
const server = net.createServer((socket) => this.onConnection(socket));
|
|
6432
|
+
this.server = server;
|
|
6433
|
+
await new Promise((resolve, reject) => {
|
|
6434
|
+
const onStartupError = (err) => reject(err);
|
|
6435
|
+
server.once("error", onStartupError);
|
|
6436
|
+
server.listen(path, () => {
|
|
6437
|
+
server.off("error", onStartupError);
|
|
6438
|
+
server.on("error", (err) => this.deps.log?.error(`mcp: control server error: ${err.message}`));
|
|
6439
|
+
resolve();
|
|
6440
|
+
});
|
|
6441
|
+
});
|
|
6442
|
+
try {
|
|
6443
|
+
chmodSync(path, 384);
|
|
6444
|
+
} catch {}
|
|
6445
|
+
this.deps.log?.info(`mcp: control socket listening at ${path}`);
|
|
6446
|
+
}
|
|
6447
|
+
onConnection(socket) {
|
|
6448
|
+
socket.setEncoding("utf8");
|
|
6449
|
+
this.conns.add(socket);
|
|
6450
|
+
let buf = "";
|
|
6451
|
+
socket.on("data", (chunk) => {
|
|
6452
|
+
buf += chunk;
|
|
6453
|
+
const { messages, rest } = decodeFrames(buf, (line) => this.deps.log?.debug(`mcp: dropping malformed frame: ${line.slice(0, 120)}`));
|
|
6454
|
+
buf = rest;
|
|
6455
|
+
for (const req of messages) this.handle(req, socket);
|
|
6456
|
+
});
|
|
6457
|
+
socket.on("error", (err) => this.deps.log?.debug(`mcp: socket error: ${err.message}`));
|
|
6458
|
+
socket.on("close", () => this.conns.delete(socket));
|
|
6459
|
+
}
|
|
6460
|
+
async handle(req, socket) {
|
|
6461
|
+
const reply = (res) => {
|
|
6462
|
+
if (!socket.destroyed) socket.write(encodeFrame(res));
|
|
6463
|
+
};
|
|
6464
|
+
const ctx = this.sessions.get(req.token);
|
|
6465
|
+
if (!ctx) return reply({
|
|
6466
|
+
id: req.id,
|
|
6467
|
+
ok: false,
|
|
6468
|
+
error: "unknown or expired session token"
|
|
6469
|
+
});
|
|
6470
|
+
try {
|
|
6471
|
+
if (req.op === "listTools") {
|
|
6472
|
+
reply({
|
|
6473
|
+
id: req.id,
|
|
6474
|
+
ok: true,
|
|
6475
|
+
result: { tools: ctx.tools }
|
|
6476
|
+
});
|
|
6477
|
+
return;
|
|
6478
|
+
}
|
|
6479
|
+
const result = await executeTool(ctx, req.name, req.args ?? {}, this.deps);
|
|
6480
|
+
reply({
|
|
6481
|
+
id: req.id,
|
|
6482
|
+
ok: true,
|
|
6483
|
+
result
|
|
6484
|
+
});
|
|
6485
|
+
} catch (err) {
|
|
6486
|
+
reply({
|
|
6487
|
+
id: req.id,
|
|
6488
|
+
ok: false,
|
|
6489
|
+
error: err.message
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
async stop() {
|
|
6494
|
+
this.sessions.clear();
|
|
6495
|
+
const server = this.server;
|
|
6496
|
+
if (!server) return;
|
|
6497
|
+
this.server = void 0;
|
|
6498
|
+
for (const s of this.conns) s.destroy();
|
|
6499
|
+
this.conns.clear();
|
|
6500
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
6501
|
+
rmSync(this.deps.socketPath, { force: true });
|
|
6502
|
+
}
|
|
6503
|
+
};
|
|
6504
|
+
//#endregion
|
|
6505
|
+
//#region src/mcp/inject.ts
|
|
6506
|
+
/**
|
|
6507
|
+
* Build the `mcpServers` entry that makes the agent harness spawn our stdio
|
|
6508
|
+
* bridge. We reuse the *current* interpreter + CLI entry (so it works under both
|
|
6509
|
+
* `tsx` in dev and plain `node` in prod) and invoke the hidden `mcp-bridge`
|
|
6510
|
+
* subcommand. The socket path and per-session token travel via env — they reach
|
|
6511
|
+
* only the harness-spawned subprocess, never the model.
|
|
6512
|
+
*/
|
|
6513
|
+
function buildMcpServers(opts) {
|
|
6514
|
+
return [{
|
|
6515
|
+
name: RESERVED_MCP_SERVER_NAME,
|
|
6516
|
+
command: process.execPath,
|
|
6517
|
+
args: [
|
|
6518
|
+
...process.execArgv,
|
|
6519
|
+
opts.cliEntry,
|
|
6520
|
+
"mcp-bridge"
|
|
6521
|
+
],
|
|
6522
|
+
env: [{
|
|
6523
|
+
name: "AC_MCP_ENDPOINT",
|
|
6524
|
+
value: opts.socketPath
|
|
6525
|
+
}, {
|
|
6526
|
+
name: "AC_MCP_TOKEN",
|
|
6527
|
+
value: opts.token
|
|
6528
|
+
}]
|
|
6529
|
+
}];
|
|
6530
|
+
}
|
|
6531
|
+
//#endregion
|
|
6532
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.js
|
|
6533
|
+
/**
|
|
6534
|
+
* Experimental client task features for MCP SDK.
|
|
6535
|
+
* WARNING: These APIs are experimental and may change without notice.
|
|
6536
|
+
*
|
|
6537
|
+
* @experimental
|
|
6538
|
+
*/
|
|
6539
|
+
/**
|
|
6540
|
+
* Experimental task features for MCP clients.
|
|
6541
|
+
*
|
|
6542
|
+
* Access via `client.experimental.tasks`:
|
|
6543
|
+
* ```typescript
|
|
6544
|
+
* const stream = client.experimental.tasks.callToolStream({ name: 'tool', arguments: {} });
|
|
6545
|
+
* const task = await client.experimental.tasks.getTask(taskId);
|
|
6546
|
+
* ```
|
|
6547
|
+
*
|
|
6548
|
+
* @experimental
|
|
6549
|
+
*/
|
|
6550
|
+
var ExperimentalClientTasks = class {
|
|
6551
|
+
constructor(_client) {
|
|
6552
|
+
this._client = _client;
|
|
6553
|
+
}
|
|
6554
|
+
/**
|
|
6555
|
+
* Calls a tool and returns an AsyncGenerator that yields response messages.
|
|
6556
|
+
* The generator is guaranteed to end with either a 'result' or 'error' message.
|
|
6557
|
+
*
|
|
6558
|
+
* This method provides streaming access to tool execution, allowing you to
|
|
6559
|
+
* observe intermediate task status updates for long-running tool calls.
|
|
6560
|
+
* Automatically validates structured output if the tool has an outputSchema.
|
|
6561
|
+
*
|
|
6562
|
+
* @example
|
|
6563
|
+
* ```typescript
|
|
6564
|
+
* const stream = client.experimental.tasks.callToolStream({ name: 'myTool', arguments: {} });
|
|
6565
|
+
* for await (const message of stream) {
|
|
6566
|
+
* switch (message.type) {
|
|
6567
|
+
* case 'taskCreated':
|
|
6568
|
+
* console.log('Tool execution started:', message.task.taskId);
|
|
6569
|
+
* break;
|
|
6570
|
+
* case 'taskStatus':
|
|
6571
|
+
* console.log('Tool status:', message.task.status);
|
|
6572
|
+
* break;
|
|
6573
|
+
* case 'result':
|
|
6574
|
+
* console.log('Tool result:', message.result);
|
|
6575
|
+
* break;
|
|
6576
|
+
* case 'error':
|
|
6577
|
+
* console.error('Tool error:', message.error);
|
|
6578
|
+
* break;
|
|
6579
|
+
* }
|
|
6580
|
+
* }
|
|
6581
|
+
* ```
|
|
6582
|
+
*
|
|
6583
|
+
* @param params - Tool call parameters (name and arguments)
|
|
6584
|
+
* @param resultSchema - Zod schema for validating the result (defaults to CallToolResultSchema)
|
|
6585
|
+
* @param options - Optional request options (timeout, signal, task creation params, etc.)
|
|
6586
|
+
* @returns AsyncGenerator that yields ResponseMessage objects
|
|
6587
|
+
*
|
|
6588
|
+
* @experimental
|
|
6589
|
+
*/
|
|
6590
|
+
async *callToolStream(params, resultSchema = CallToolResultSchema, options) {
|
|
6591
|
+
const clientInternal = this._client;
|
|
6592
|
+
const optionsWithTask = {
|
|
6593
|
+
...options,
|
|
6594
|
+
task: options?.task ?? (clientInternal.isToolTask(params.name) ? {} : void 0)
|
|
6595
|
+
};
|
|
6596
|
+
const stream = clientInternal.requestStream({
|
|
6597
|
+
method: "tools/call",
|
|
6598
|
+
params
|
|
6599
|
+
}, resultSchema, optionsWithTask);
|
|
6600
|
+
const validator = clientInternal.getToolOutputValidator(params.name);
|
|
6601
|
+
for await (const message of stream) {
|
|
6602
|
+
if (message.type === "result" && validator) {
|
|
6603
|
+
const result = message.result;
|
|
6604
|
+
if (!result.structuredContent && !result.isError) {
|
|
6605
|
+
yield {
|
|
6606
|
+
type: "error",
|
|
6607
|
+
error: new McpError(ErrorCode.InvalidRequest, `Tool ${params.name} has an output schema but did not return structured content`)
|
|
6608
|
+
};
|
|
6609
|
+
return;
|
|
6610
|
+
}
|
|
6611
|
+
if (result.structuredContent) try {
|
|
6612
|
+
const validationResult = validator(result.structuredContent);
|
|
6613
|
+
if (!validationResult.valid) {
|
|
6614
|
+
yield {
|
|
6615
|
+
type: "error",
|
|
6616
|
+
error: new McpError(ErrorCode.InvalidParams, `Structured content does not match the tool's output schema: ${validationResult.errorMessage}`)
|
|
6617
|
+
};
|
|
6618
|
+
return;
|
|
6619
|
+
}
|
|
6620
|
+
} catch (error) {
|
|
6621
|
+
if (error instanceof McpError) {
|
|
6622
|
+
yield {
|
|
6623
|
+
type: "error",
|
|
6624
|
+
error
|
|
6625
|
+
};
|
|
6626
|
+
return;
|
|
6627
|
+
}
|
|
6628
|
+
yield {
|
|
6629
|
+
type: "error",
|
|
6630
|
+
error: new McpError(ErrorCode.InvalidParams, `Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}`)
|
|
6631
|
+
};
|
|
6632
|
+
return;
|
|
6633
|
+
}
|
|
6634
|
+
}
|
|
6635
|
+
yield message;
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
/**
|
|
6639
|
+
* Gets the current status of a task.
|
|
6640
|
+
*
|
|
6641
|
+
* @param taskId - The task identifier
|
|
6642
|
+
* @param options - Optional request options
|
|
6643
|
+
* @returns The task status
|
|
6644
|
+
*
|
|
6645
|
+
* @experimental
|
|
6646
|
+
*/
|
|
6647
|
+
async getTask(taskId, options) {
|
|
6648
|
+
return this._client.getTask({ taskId }, options);
|
|
6649
|
+
}
|
|
6650
|
+
/**
|
|
6651
|
+
* Retrieves the result of a completed task.
|
|
6652
|
+
*
|
|
6653
|
+
* @param taskId - The task identifier
|
|
6654
|
+
* @param resultSchema - Zod schema for validating the result
|
|
6655
|
+
* @param options - Optional request options
|
|
6656
|
+
* @returns The task result
|
|
6657
|
+
*
|
|
6658
|
+
* @experimental
|
|
6659
|
+
*/
|
|
6660
|
+
async getTaskResult(taskId, resultSchema, options) {
|
|
6661
|
+
return this._client.getTaskResult({ taskId }, resultSchema, options);
|
|
6662
|
+
}
|
|
6663
|
+
/**
|
|
6664
|
+
* Lists tasks with optional pagination.
|
|
6665
|
+
*
|
|
6666
|
+
* @param cursor - Optional pagination cursor
|
|
6667
|
+
* @param options - Optional request options
|
|
6668
|
+
* @returns List of tasks with optional next cursor
|
|
6669
|
+
*
|
|
6670
|
+
* @experimental
|
|
6671
|
+
*/
|
|
6672
|
+
async listTasks(cursor, options) {
|
|
6673
|
+
return this._client.listTasks(cursor ? { cursor } : void 0, options);
|
|
6674
|
+
}
|
|
6675
|
+
/**
|
|
6676
|
+
* Cancels a running task.
|
|
6677
|
+
*
|
|
6678
|
+
* @param taskId - The task identifier
|
|
6679
|
+
* @param options - Optional request options
|
|
6680
|
+
*
|
|
6681
|
+
* @experimental
|
|
6682
|
+
*/
|
|
6683
|
+
async cancelTask(taskId, options) {
|
|
6684
|
+
return this._client.cancelTask({ taskId }, options);
|
|
6685
|
+
}
|
|
6686
|
+
/**
|
|
6687
|
+
* Sends a request and returns an AsyncGenerator that yields response messages.
|
|
6688
|
+
* The generator is guaranteed to end with either a 'result' or 'error' message.
|
|
6689
|
+
*
|
|
6690
|
+
* This method provides streaming access to request processing, allowing you to
|
|
6691
|
+
* observe intermediate task status updates for task-augmented requests.
|
|
6692
|
+
*
|
|
6693
|
+
* @param request - The request to send
|
|
6694
|
+
* @param resultSchema - Zod schema for validating the result
|
|
6695
|
+
* @param options - Optional request options (timeout, signal, task creation params, etc.)
|
|
6696
|
+
* @returns AsyncGenerator that yields ResponseMessage objects
|
|
6697
|
+
*
|
|
6698
|
+
* @experimental
|
|
6699
|
+
*/
|
|
6700
|
+
requestStream(request, resultSchema, options) {
|
|
6701
|
+
return this._client.requestStream(request, resultSchema, options);
|
|
6702
|
+
}
|
|
6703
|
+
};
|
|
6704
|
+
//#endregion
|
|
6705
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/client/index.js
|
|
6706
|
+
/**
|
|
6707
|
+
* Elicitation default application helper. Applies defaults to the data based on the schema.
|
|
6708
|
+
*
|
|
6709
|
+
* @param schema - The schema to apply defaults to.
|
|
6710
|
+
* @param data - The data to apply defaults to.
|
|
6711
|
+
*/
|
|
6712
|
+
function applyElicitationDefaults(schema, data) {
|
|
6713
|
+
if (!schema || data === null || typeof data !== "object") return;
|
|
6714
|
+
if (schema.type === "object" && schema.properties && typeof schema.properties === "object") {
|
|
6715
|
+
const obj = data;
|
|
6716
|
+
const props = schema.properties;
|
|
6717
|
+
for (const key of Object.keys(props)) {
|
|
6718
|
+
const propSchema = props[key];
|
|
6719
|
+
if (obj[key] === void 0 && Object.prototype.hasOwnProperty.call(propSchema, "default")) obj[key] = propSchema.default;
|
|
6720
|
+
if (obj[key] !== void 0) applyElicitationDefaults(propSchema, obj[key]);
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
if (Array.isArray(schema.anyOf)) {
|
|
6724
|
+
for (const sub of schema.anyOf) if (typeof sub !== "boolean") applyElicitationDefaults(sub, data);
|
|
6725
|
+
}
|
|
6726
|
+
if (Array.isArray(schema.oneOf)) {
|
|
6727
|
+
for (const sub of schema.oneOf) if (typeof sub !== "boolean") applyElicitationDefaults(sub, data);
|
|
6728
|
+
}
|
|
6729
|
+
}
|
|
6730
|
+
/**
|
|
6731
|
+
* Determines which elicitation modes are supported based on declared client capabilities.
|
|
6732
|
+
*
|
|
6733
|
+
* According to the spec:
|
|
6734
|
+
* - An empty elicitation capability object defaults to form mode support (backwards compatibility)
|
|
6735
|
+
* - URL mode is only supported if explicitly declared
|
|
6736
|
+
*
|
|
6737
|
+
* @param capabilities - The client's elicitation capabilities
|
|
6738
|
+
* @returns An object indicating which modes are supported
|
|
6739
|
+
*/
|
|
6740
|
+
function getSupportedElicitationModes(capabilities) {
|
|
6741
|
+
if (!capabilities) return {
|
|
6742
|
+
supportsFormMode: false,
|
|
6743
|
+
supportsUrlMode: false
|
|
6744
|
+
};
|
|
6745
|
+
const hasFormCapability = capabilities.form !== void 0;
|
|
6746
|
+
const hasUrlCapability = capabilities.url !== void 0;
|
|
6747
|
+
return {
|
|
6748
|
+
supportsFormMode: hasFormCapability || !hasFormCapability && !hasUrlCapability,
|
|
6749
|
+
supportsUrlMode: hasUrlCapability
|
|
6750
|
+
};
|
|
6751
|
+
}
|
|
6752
|
+
/**
|
|
6753
|
+
* An MCP client on top of a pluggable transport.
|
|
6754
|
+
*
|
|
6755
|
+
* The client will automatically begin the initialization flow with the server when connect() is called.
|
|
6756
|
+
*
|
|
6757
|
+
* To use with custom types, extend the base Request/Notification/Result types and pass them as type parameters:
|
|
6758
|
+
*
|
|
6759
|
+
* ```typescript
|
|
6760
|
+
* // Custom schemas
|
|
6761
|
+
* const CustomRequestSchema = RequestSchema.extend({...})
|
|
6762
|
+
* const CustomNotificationSchema = NotificationSchema.extend({...})
|
|
6763
|
+
* const CustomResultSchema = ResultSchema.extend({...})
|
|
6764
|
+
*
|
|
6765
|
+
* // Type aliases
|
|
6766
|
+
* type CustomRequest = z.infer<typeof CustomRequestSchema>
|
|
6767
|
+
* type CustomNotification = z.infer<typeof CustomNotificationSchema>
|
|
6768
|
+
* type CustomResult = z.infer<typeof CustomResultSchema>
|
|
6769
|
+
*
|
|
6770
|
+
* // Create typed client
|
|
6771
|
+
* const client = new Client<CustomRequest, CustomNotification, CustomResult>({
|
|
6772
|
+
* name: "CustomClient",
|
|
6773
|
+
* version: "1.0.0"
|
|
6774
|
+
* })
|
|
6775
|
+
* ```
|
|
6776
|
+
*/
|
|
6777
|
+
var Client = class extends Protocol {
|
|
6778
|
+
/**
|
|
6779
|
+
* Initializes this client with the given name and version information.
|
|
6780
|
+
*/
|
|
6781
|
+
constructor(_clientInfo, options) {
|
|
6782
|
+
super(options);
|
|
6783
|
+
this._clientInfo = _clientInfo;
|
|
6784
|
+
this._cachedToolOutputValidators = /* @__PURE__ */ new Map();
|
|
6785
|
+
this._cachedKnownTaskTools = /* @__PURE__ */ new Set();
|
|
6786
|
+
this._cachedRequiredTaskTools = /* @__PURE__ */ new Set();
|
|
6787
|
+
this._listChangedDebounceTimers = /* @__PURE__ */ new Map();
|
|
6788
|
+
this._capabilities = options?.capabilities ?? {};
|
|
6789
|
+
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator();
|
|
6790
|
+
if (options?.listChanged) this._pendingListChangedConfig = options.listChanged;
|
|
6791
|
+
}
|
|
6792
|
+
/**
|
|
6793
|
+
* Set up handlers for list changed notifications based on config and server capabilities.
|
|
6794
|
+
* This should only be called after initialization when server capabilities are known.
|
|
6795
|
+
* Handlers are silently skipped if the server doesn't advertise the corresponding listChanged capability.
|
|
6796
|
+
* @internal
|
|
6797
|
+
*/
|
|
6798
|
+
_setupListChangedHandlers(config) {
|
|
6799
|
+
if (config.tools && this._serverCapabilities?.tools?.listChanged) this._setupListChangedHandler("tools", ToolListChangedNotificationSchema, config.tools, async () => {
|
|
6800
|
+
return (await this.listTools()).tools;
|
|
6801
|
+
});
|
|
6802
|
+
if (config.prompts && this._serverCapabilities?.prompts?.listChanged) this._setupListChangedHandler("prompts", PromptListChangedNotificationSchema, config.prompts, async () => {
|
|
6803
|
+
return (await this.listPrompts()).prompts;
|
|
6804
|
+
});
|
|
6805
|
+
if (config.resources && this._serverCapabilities?.resources?.listChanged) this._setupListChangedHandler("resources", ResourceListChangedNotificationSchema, config.resources, async () => {
|
|
6806
|
+
return (await this.listResources()).resources;
|
|
6807
|
+
});
|
|
6808
|
+
}
|
|
6809
|
+
/**
|
|
6810
|
+
* Access experimental features.
|
|
6811
|
+
*
|
|
6812
|
+
* WARNING: These APIs are experimental and may change without notice.
|
|
6813
|
+
*
|
|
6814
|
+
* @experimental
|
|
6815
|
+
*/
|
|
6816
|
+
get experimental() {
|
|
6817
|
+
if (!this._experimental) this._experimental = { tasks: new ExperimentalClientTasks(this) };
|
|
6818
|
+
return this._experimental;
|
|
6819
|
+
}
|
|
6820
|
+
/**
|
|
6821
|
+
* Registers new capabilities. This can only be called before connecting to a transport.
|
|
6822
|
+
*
|
|
6823
|
+
* The new capabilities will be merged with any existing capabilities previously given (e.g., at initialization).
|
|
6824
|
+
*/
|
|
6825
|
+
registerCapabilities(capabilities) {
|
|
6826
|
+
if (this.transport) throw new Error("Cannot register capabilities after connecting to transport");
|
|
6827
|
+
this._capabilities = mergeCapabilities(this._capabilities, capabilities);
|
|
6828
|
+
}
|
|
6829
|
+
/**
|
|
6830
|
+
* Override request handler registration to enforce client-side validation for elicitation.
|
|
6831
|
+
*/
|
|
6832
|
+
setRequestHandler(requestSchema, handler) {
|
|
6833
|
+
const methodSchema = getObjectShape(requestSchema)?.method;
|
|
6834
|
+
if (!methodSchema) throw new Error("Schema is missing a method literal");
|
|
6835
|
+
let methodValue;
|
|
6836
|
+
if (isZ4Schema(methodSchema)) {
|
|
6837
|
+
const v4Schema = methodSchema;
|
|
6838
|
+
methodValue = (v4Schema._zod?.def)?.value ?? v4Schema.value;
|
|
6839
|
+
} else {
|
|
6840
|
+
const v3Schema = methodSchema;
|
|
6841
|
+
methodValue = v3Schema._def?.value ?? v3Schema.value;
|
|
6842
|
+
}
|
|
6843
|
+
if (typeof methodValue !== "string") throw new Error("Schema method literal must be a string");
|
|
6844
|
+
const method = methodValue;
|
|
6845
|
+
if (method === "elicitation/create") {
|
|
6846
|
+
const wrappedHandler = async (request, extra) => {
|
|
6847
|
+
const validatedRequest = safeParse(ElicitRequestSchema, request);
|
|
6848
|
+
if (!validatedRequest.success) {
|
|
6849
|
+
const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
|
6850
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid elicitation request: ${errorMessage}`);
|
|
6851
|
+
}
|
|
6852
|
+
const { params } = validatedRequest.data;
|
|
6853
|
+
params.mode = params.mode ?? "form";
|
|
6854
|
+
const { supportsFormMode, supportsUrlMode } = getSupportedElicitationModes(this._capabilities.elicitation);
|
|
6855
|
+
if (params.mode === "form" && !supportsFormMode) throw new McpError(ErrorCode.InvalidParams, "Client does not support form-mode elicitation requests");
|
|
6856
|
+
if (params.mode === "url" && !supportsUrlMode) throw new McpError(ErrorCode.InvalidParams, "Client does not support URL-mode elicitation requests");
|
|
6857
|
+
const result = await Promise.resolve(handler(request, extra));
|
|
6858
|
+
if (params.task) {
|
|
6859
|
+
const taskValidationResult = safeParse(CreateTaskResultSchema, result);
|
|
6860
|
+
if (!taskValidationResult.success) {
|
|
6861
|
+
const errorMessage = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
|
|
6862
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
|
6863
|
+
}
|
|
6864
|
+
return taskValidationResult.data;
|
|
6865
|
+
}
|
|
6866
|
+
const validationResult = safeParse(ElicitResultSchema, result);
|
|
6867
|
+
if (!validationResult.success) {
|
|
6868
|
+
const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
|
6869
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid elicitation result: ${errorMessage}`);
|
|
6870
|
+
}
|
|
6871
|
+
const validatedResult = validationResult.data;
|
|
6872
|
+
const requestedSchema = params.mode === "form" ? params.requestedSchema : void 0;
|
|
6873
|
+
if (params.mode === "form" && validatedResult.action === "accept" && validatedResult.content && requestedSchema) {
|
|
6874
|
+
if (this._capabilities.elicitation?.form?.applyDefaults) try {
|
|
6875
|
+
applyElicitationDefaults(requestedSchema, validatedResult.content);
|
|
6876
|
+
} catch {}
|
|
6877
|
+
}
|
|
6878
|
+
return validatedResult;
|
|
6879
|
+
};
|
|
6880
|
+
return super.setRequestHandler(requestSchema, wrappedHandler);
|
|
6881
|
+
}
|
|
6882
|
+
if (method === "sampling/createMessage") {
|
|
6883
|
+
const wrappedHandler = async (request, extra) => {
|
|
6884
|
+
const validatedRequest = safeParse(CreateMessageRequestSchema, request);
|
|
6885
|
+
if (!validatedRequest.success) {
|
|
6886
|
+
const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
|
|
6887
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid sampling request: ${errorMessage}`);
|
|
6888
|
+
}
|
|
6889
|
+
const { params } = validatedRequest.data;
|
|
6890
|
+
const result = await Promise.resolve(handler(request, extra));
|
|
6891
|
+
if (params.task) {
|
|
6892
|
+
const taskValidationResult = safeParse(CreateTaskResultSchema, result);
|
|
6893
|
+
if (!taskValidationResult.success) {
|
|
6894
|
+
const errorMessage = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
|
|
6895
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
|
|
6896
|
+
}
|
|
6897
|
+
return taskValidationResult.data;
|
|
6898
|
+
}
|
|
6899
|
+
const validationResult = safeParse(params.tools || params.toolChoice ? CreateMessageResultWithToolsSchema : CreateMessageResultSchema, result);
|
|
6900
|
+
if (!validationResult.success) {
|
|
6901
|
+
const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
|
|
6902
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid sampling result: ${errorMessage}`);
|
|
6903
|
+
}
|
|
6904
|
+
return validationResult.data;
|
|
6905
|
+
};
|
|
6906
|
+
return super.setRequestHandler(requestSchema, wrappedHandler);
|
|
6907
|
+
}
|
|
6908
|
+
return super.setRequestHandler(requestSchema, handler);
|
|
6909
|
+
}
|
|
6910
|
+
assertCapability(capability, method) {
|
|
6911
|
+
if (!this._serverCapabilities?.[capability]) throw new Error(`Server does not support ${capability} (required for ${method})`);
|
|
6912
|
+
}
|
|
6913
|
+
async connect(transport, options) {
|
|
6914
|
+
await super.connect(transport);
|
|
6915
|
+
if (transport.sessionId !== void 0) return;
|
|
6916
|
+
try {
|
|
6917
|
+
const result = await this.request({
|
|
6918
|
+
method: "initialize",
|
|
6919
|
+
params: {
|
|
6920
|
+
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
6921
|
+
capabilities: this._capabilities,
|
|
6922
|
+
clientInfo: this._clientInfo
|
|
6923
|
+
}
|
|
6924
|
+
}, InitializeResultSchema, options);
|
|
6925
|
+
if (result === void 0) throw new Error(`Server sent invalid initialize result: ${result}`);
|
|
6926
|
+
if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`);
|
|
6927
|
+
this._serverCapabilities = result.capabilities;
|
|
6928
|
+
this._serverVersion = result.serverInfo;
|
|
6929
|
+
if (transport.setProtocolVersion) transport.setProtocolVersion(result.protocolVersion);
|
|
6930
|
+
this._instructions = result.instructions;
|
|
6931
|
+
await this.notification({ method: "notifications/initialized" });
|
|
6932
|
+
if (this._pendingListChangedConfig) {
|
|
6933
|
+
this._setupListChangedHandlers(this._pendingListChangedConfig);
|
|
6934
|
+
this._pendingListChangedConfig = void 0;
|
|
6935
|
+
}
|
|
6936
|
+
} catch (error) {
|
|
6937
|
+
this.close();
|
|
6938
|
+
throw error;
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
/**
|
|
6942
|
+
* After initialization has completed, this will be populated with the server's reported capabilities.
|
|
6943
|
+
*/
|
|
6944
|
+
getServerCapabilities() {
|
|
6945
|
+
return this._serverCapabilities;
|
|
6946
|
+
}
|
|
6947
|
+
/**
|
|
6948
|
+
* After initialization has completed, this will be populated with information about the server's name and version.
|
|
6949
|
+
*/
|
|
6950
|
+
getServerVersion() {
|
|
6951
|
+
return this._serverVersion;
|
|
6952
|
+
}
|
|
6953
|
+
/**
|
|
6954
|
+
* After initialization has completed, this may be populated with information about the server's instructions.
|
|
6955
|
+
*/
|
|
6956
|
+
getInstructions() {
|
|
6957
|
+
return this._instructions;
|
|
6958
|
+
}
|
|
6959
|
+
assertCapabilityForMethod(method) {
|
|
6960
|
+
switch (method) {
|
|
6961
|
+
case "logging/setLevel":
|
|
6962
|
+
if (!this._serverCapabilities?.logging) throw new Error(`Server does not support logging (required for ${method})`);
|
|
6963
|
+
break;
|
|
6964
|
+
case "prompts/get":
|
|
6965
|
+
case "prompts/list":
|
|
6966
|
+
if (!this._serverCapabilities?.prompts) throw new Error(`Server does not support prompts (required for ${method})`);
|
|
6967
|
+
break;
|
|
6968
|
+
case "resources/list":
|
|
6969
|
+
case "resources/templates/list":
|
|
6970
|
+
case "resources/read":
|
|
6971
|
+
case "resources/subscribe":
|
|
6972
|
+
case "resources/unsubscribe":
|
|
6973
|
+
if (!this._serverCapabilities?.resources) throw new Error(`Server does not support resources (required for ${method})`);
|
|
6974
|
+
if (method === "resources/subscribe" && !this._serverCapabilities.resources.subscribe) throw new Error(`Server does not support resource subscriptions (required for ${method})`);
|
|
6975
|
+
break;
|
|
6976
|
+
case "tools/call":
|
|
6977
|
+
case "tools/list":
|
|
6978
|
+
if (!this._serverCapabilities?.tools) throw new Error(`Server does not support tools (required for ${method})`);
|
|
6979
|
+
break;
|
|
6980
|
+
case "completion/complete":
|
|
6981
|
+
if (!this._serverCapabilities?.completions) throw new Error(`Server does not support completions (required for ${method})`);
|
|
6982
|
+
break;
|
|
6983
|
+
case "initialize": break;
|
|
6984
|
+
case "ping": break;
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
assertNotificationCapability(method) {
|
|
6988
|
+
switch (method) {
|
|
6989
|
+
case "notifications/roots/list_changed":
|
|
6990
|
+
if (!this._capabilities.roots?.listChanged) throw new Error(`Client does not support roots list changed notifications (required for ${method})`);
|
|
6991
|
+
break;
|
|
6992
|
+
case "notifications/initialized": break;
|
|
6993
|
+
case "notifications/cancelled": break;
|
|
6994
|
+
case "notifications/progress": break;
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
assertRequestHandlerCapability(method) {
|
|
6998
|
+
if (!this._capabilities) return;
|
|
6999
|
+
switch (method) {
|
|
7000
|
+
case "sampling/createMessage":
|
|
7001
|
+
if (!this._capabilities.sampling) throw new Error(`Client does not support sampling capability (required for ${method})`);
|
|
7002
|
+
break;
|
|
7003
|
+
case "elicitation/create":
|
|
7004
|
+
if (!this._capabilities.elicitation) throw new Error(`Client does not support elicitation capability (required for ${method})`);
|
|
7005
|
+
break;
|
|
7006
|
+
case "roots/list":
|
|
7007
|
+
if (!this._capabilities.roots) throw new Error(`Client does not support roots capability (required for ${method})`);
|
|
7008
|
+
break;
|
|
7009
|
+
case "tasks/get":
|
|
7010
|
+
case "tasks/list":
|
|
7011
|
+
case "tasks/result":
|
|
7012
|
+
case "tasks/cancel":
|
|
7013
|
+
if (!this._capabilities.tasks) throw new Error(`Client does not support tasks capability (required for ${method})`);
|
|
7014
|
+
break;
|
|
7015
|
+
case "ping": break;
|
|
7016
|
+
}
|
|
7017
|
+
}
|
|
7018
|
+
assertTaskCapability(method) {
|
|
7019
|
+
assertToolsCallTaskCapability(this._serverCapabilities?.tasks?.requests, method, "Server");
|
|
7020
|
+
}
|
|
7021
|
+
assertTaskHandlerCapability(method) {
|
|
7022
|
+
if (!this._capabilities) return;
|
|
7023
|
+
assertClientRequestTaskCapability(this._capabilities.tasks?.requests, method, "Client");
|
|
7024
|
+
}
|
|
7025
|
+
async ping(options) {
|
|
7026
|
+
return this.request({ method: "ping" }, EmptyResultSchema, options);
|
|
7027
|
+
}
|
|
7028
|
+
async complete(params, options) {
|
|
7029
|
+
return this.request({
|
|
7030
|
+
method: "completion/complete",
|
|
7031
|
+
params
|
|
7032
|
+
}, CompleteResultSchema, options);
|
|
7033
|
+
}
|
|
7034
|
+
async setLoggingLevel(level, options) {
|
|
7035
|
+
return this.request({
|
|
7036
|
+
method: "logging/setLevel",
|
|
7037
|
+
params: { level }
|
|
7038
|
+
}, EmptyResultSchema, options);
|
|
7039
|
+
}
|
|
7040
|
+
async getPrompt(params, options) {
|
|
7041
|
+
return this.request({
|
|
7042
|
+
method: "prompts/get",
|
|
7043
|
+
params
|
|
7044
|
+
}, GetPromptResultSchema, options);
|
|
7045
|
+
}
|
|
7046
|
+
async listPrompts(params, options) {
|
|
7047
|
+
return this.request({
|
|
7048
|
+
method: "prompts/list",
|
|
7049
|
+
params
|
|
7050
|
+
}, ListPromptsResultSchema, options);
|
|
7051
|
+
}
|
|
7052
|
+
async listResources(params, options) {
|
|
7053
|
+
return this.request({
|
|
7054
|
+
method: "resources/list",
|
|
7055
|
+
params
|
|
7056
|
+
}, ListResourcesResultSchema, options);
|
|
7057
|
+
}
|
|
7058
|
+
async listResourceTemplates(params, options) {
|
|
7059
|
+
return this.request({
|
|
7060
|
+
method: "resources/templates/list",
|
|
7061
|
+
params
|
|
7062
|
+
}, ListResourceTemplatesResultSchema, options);
|
|
7063
|
+
}
|
|
7064
|
+
async readResource(params, options) {
|
|
7065
|
+
return this.request({
|
|
7066
|
+
method: "resources/read",
|
|
7067
|
+
params
|
|
7068
|
+
}, ReadResourceResultSchema, options);
|
|
7069
|
+
}
|
|
7070
|
+
async subscribeResource(params, options) {
|
|
7071
|
+
return this.request({
|
|
7072
|
+
method: "resources/subscribe",
|
|
7073
|
+
params
|
|
7074
|
+
}, EmptyResultSchema, options);
|
|
7075
|
+
}
|
|
7076
|
+
async unsubscribeResource(params, options) {
|
|
7077
|
+
return this.request({
|
|
7078
|
+
method: "resources/unsubscribe",
|
|
7079
|
+
params
|
|
7080
|
+
}, EmptyResultSchema, options);
|
|
7081
|
+
}
|
|
7082
|
+
/**
|
|
7083
|
+
* Calls a tool and waits for the result. Automatically validates structured output if the tool has an outputSchema.
|
|
7084
|
+
*
|
|
7085
|
+
* For task-based execution with streaming behavior, use client.experimental.tasks.callToolStream() instead.
|
|
7086
|
+
*/
|
|
7087
|
+
async callTool(params, resultSchema = CallToolResultSchema, options) {
|
|
7088
|
+
if (this.isToolTaskRequired(params.name)) throw new McpError(ErrorCode.InvalidRequest, `Tool "${params.name}" requires task-based execution. Use client.experimental.tasks.callToolStream() instead.`);
|
|
7089
|
+
const result = await this.request({
|
|
7090
|
+
method: "tools/call",
|
|
7091
|
+
params
|
|
7092
|
+
}, resultSchema, options);
|
|
7093
|
+
const validator = this.getToolOutputValidator(params.name);
|
|
7094
|
+
if (validator) {
|
|
7095
|
+
if (!result.structuredContent && !result.isError) throw new McpError(ErrorCode.InvalidRequest, `Tool ${params.name} has an output schema but did not return structured content`);
|
|
7096
|
+
if (result.structuredContent) try {
|
|
7097
|
+
const validationResult = validator(result.structuredContent);
|
|
7098
|
+
if (!validationResult.valid) throw new McpError(ErrorCode.InvalidParams, `Structured content does not match the tool's output schema: ${validationResult.errorMessage}`);
|
|
7099
|
+
} catch (error) {
|
|
7100
|
+
if (error instanceof McpError) throw error;
|
|
7101
|
+
throw new McpError(ErrorCode.InvalidParams, `Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}`);
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
return result;
|
|
7105
|
+
}
|
|
7106
|
+
isToolTask(toolName) {
|
|
7107
|
+
if (!this._serverCapabilities?.tasks?.requests?.tools?.call) return false;
|
|
7108
|
+
return this._cachedKnownTaskTools.has(toolName);
|
|
7109
|
+
}
|
|
7110
|
+
/**
|
|
7111
|
+
* Check if a tool requires task-based execution.
|
|
7112
|
+
* Unlike isToolTask which includes 'optional' tools, this only checks for 'required'.
|
|
7113
|
+
*/
|
|
7114
|
+
isToolTaskRequired(toolName) {
|
|
7115
|
+
return this._cachedRequiredTaskTools.has(toolName);
|
|
7116
|
+
}
|
|
7117
|
+
/**
|
|
7118
|
+
* Cache validators for tool output schemas.
|
|
7119
|
+
* Called after listTools() to pre-compile validators for better performance.
|
|
7120
|
+
*/
|
|
7121
|
+
cacheToolMetadata(tools) {
|
|
7122
|
+
this._cachedToolOutputValidators.clear();
|
|
7123
|
+
this._cachedKnownTaskTools.clear();
|
|
7124
|
+
this._cachedRequiredTaskTools.clear();
|
|
7125
|
+
for (const tool of tools) {
|
|
7126
|
+
if (tool.outputSchema) {
|
|
7127
|
+
const toolValidator = this._jsonSchemaValidator.getValidator(tool.outputSchema);
|
|
7128
|
+
this._cachedToolOutputValidators.set(tool.name, toolValidator);
|
|
7129
|
+
}
|
|
7130
|
+
const taskSupport = tool.execution?.taskSupport;
|
|
7131
|
+
if (taskSupport === "required" || taskSupport === "optional") this._cachedKnownTaskTools.add(tool.name);
|
|
7132
|
+
if (taskSupport === "required") this._cachedRequiredTaskTools.add(tool.name);
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
/**
|
|
7136
|
+
* Get cached validator for a tool
|
|
7137
|
+
*/
|
|
7138
|
+
getToolOutputValidator(toolName) {
|
|
7139
|
+
return this._cachedToolOutputValidators.get(toolName);
|
|
7140
|
+
}
|
|
7141
|
+
async listTools(params, options) {
|
|
7142
|
+
const result = await this.request({
|
|
7143
|
+
method: "tools/list",
|
|
7144
|
+
params
|
|
7145
|
+
}, ListToolsResultSchema, options);
|
|
7146
|
+
this.cacheToolMetadata(result.tools);
|
|
7147
|
+
return result;
|
|
7148
|
+
}
|
|
7149
|
+
/**
|
|
7150
|
+
* Set up a single list changed handler.
|
|
7151
|
+
* @internal
|
|
7152
|
+
*/
|
|
7153
|
+
_setupListChangedHandler(listType, notificationSchema, options, fetcher) {
|
|
7154
|
+
const parseResult = ListChangedOptionsBaseSchema.safeParse(options);
|
|
7155
|
+
if (!parseResult.success) throw new Error(`Invalid ${listType} listChanged options: ${parseResult.error.message}`);
|
|
7156
|
+
if (typeof options.onChanged !== "function") throw new Error(`Invalid ${listType} listChanged options: onChanged must be a function`);
|
|
7157
|
+
const { autoRefresh, debounceMs } = parseResult.data;
|
|
7158
|
+
const { onChanged } = options;
|
|
7159
|
+
const refresh = async () => {
|
|
7160
|
+
if (!autoRefresh) {
|
|
7161
|
+
onChanged(null, null);
|
|
7162
|
+
return;
|
|
7163
|
+
}
|
|
7164
|
+
try {
|
|
7165
|
+
const items = await fetcher();
|
|
7166
|
+
onChanged(null, items);
|
|
7167
|
+
} catch (e) {
|
|
7168
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
7169
|
+
onChanged(error, null);
|
|
7170
|
+
}
|
|
7171
|
+
};
|
|
7172
|
+
const handler = () => {
|
|
7173
|
+
if (debounceMs) {
|
|
7174
|
+
const existingTimer = this._listChangedDebounceTimers.get(listType);
|
|
7175
|
+
if (existingTimer) clearTimeout(existingTimer);
|
|
7176
|
+
const timer = setTimeout(refresh, debounceMs);
|
|
7177
|
+
this._listChangedDebounceTimers.set(listType, timer);
|
|
7178
|
+
} else refresh();
|
|
7179
|
+
};
|
|
7180
|
+
this.setNotificationHandler(notificationSchema, handler);
|
|
7181
|
+
}
|
|
7182
|
+
async sendRootsListChanged() {
|
|
7183
|
+
return this.notification({ method: "notifications/roots/list_changed" });
|
|
7184
|
+
}
|
|
7185
|
+
};
|
|
7186
|
+
//#endregion
|
|
7187
|
+
//#region ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js
|
|
7188
|
+
var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7189
|
+
module.exports = isexe;
|
|
7190
|
+
isexe.sync = sync;
|
|
7191
|
+
var fs$17 = __require("fs");
|
|
7192
|
+
function checkPathExt(path, options) {
|
|
7193
|
+
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
7194
|
+
if (!pathext) return true;
|
|
7195
|
+
pathext = pathext.split(";");
|
|
7196
|
+
if (pathext.indexOf("") !== -1) return true;
|
|
7197
|
+
for (var i = 0; i < pathext.length; i++) {
|
|
7198
|
+
var p = pathext[i].toLowerCase();
|
|
7199
|
+
if (p && path.substr(-p.length).toLowerCase() === p) return true;
|
|
7200
|
+
}
|
|
7201
|
+
return false;
|
|
7202
|
+
}
|
|
7203
|
+
function checkStat(stat, path, options) {
|
|
7204
|
+
if (!stat.isSymbolicLink() && !stat.isFile()) return false;
|
|
7205
|
+
return checkPathExt(path, options);
|
|
7206
|
+
}
|
|
7207
|
+
function isexe(path, options, cb) {
|
|
7208
|
+
fs$17.stat(path, function(er, stat) {
|
|
7209
|
+
cb(er, er ? false : checkStat(stat, path, options));
|
|
7210
|
+
});
|
|
7211
|
+
}
|
|
7212
|
+
function sync(path, options) {
|
|
7213
|
+
return checkStat(fs$17.statSync(path), path, options);
|
|
7214
|
+
}
|
|
7215
|
+
}));
|
|
7216
|
+
//#endregion
|
|
7217
|
+
//#region ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js
|
|
7218
|
+
var require_mode = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7219
|
+
module.exports = isexe;
|
|
7220
|
+
isexe.sync = sync;
|
|
7221
|
+
var fs$16 = __require("fs");
|
|
7222
|
+
function isexe(path, options, cb) {
|
|
7223
|
+
fs$16.stat(path, function(er, stat) {
|
|
7224
|
+
cb(er, er ? false : checkStat(stat, options));
|
|
7225
|
+
});
|
|
7226
|
+
}
|
|
7227
|
+
function sync(path, options) {
|
|
7228
|
+
return checkStat(fs$16.statSync(path), options);
|
|
7229
|
+
}
|
|
7230
|
+
function checkStat(stat, options) {
|
|
7231
|
+
return stat.isFile() && checkMode(stat, options);
|
|
7232
|
+
}
|
|
7233
|
+
function checkMode(stat, options) {
|
|
7234
|
+
var mod = stat.mode;
|
|
7235
|
+
var uid = stat.uid;
|
|
7236
|
+
var gid = stat.gid;
|
|
7237
|
+
var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
|
|
7238
|
+
var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
|
|
7239
|
+
var u = parseInt("100", 8);
|
|
7240
|
+
var g = parseInt("010", 8);
|
|
7241
|
+
var o = parseInt("001", 8);
|
|
7242
|
+
var ug = u | g;
|
|
7243
|
+
return mod & o || mod & g && gid === myGid || mod & u && uid === myUid || mod & ug && myUid === 0;
|
|
7244
|
+
}
|
|
7245
|
+
}));
|
|
7246
|
+
//#endregion
|
|
7247
|
+
//#region ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
|
|
7248
|
+
var require_isexe = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7249
|
+
__require("fs");
|
|
7250
|
+
var core;
|
|
7251
|
+
if (process.platform === "win32" || global.TESTING_WINDOWS) core = require_windows();
|
|
7252
|
+
else core = require_mode();
|
|
7253
|
+
module.exports = isexe;
|
|
7254
|
+
isexe.sync = sync;
|
|
7255
|
+
function isexe(path, options, cb) {
|
|
7256
|
+
if (typeof options === "function") {
|
|
7257
|
+
cb = options;
|
|
7258
|
+
options = {};
|
|
7259
|
+
}
|
|
7260
|
+
if (!cb) {
|
|
7261
|
+
if (typeof Promise !== "function") throw new TypeError("callback not provided");
|
|
7262
|
+
return new Promise(function(resolve, reject) {
|
|
7263
|
+
isexe(path, options || {}, function(er, is) {
|
|
7264
|
+
if (er) reject(er);
|
|
7265
|
+
else resolve(is);
|
|
7266
|
+
});
|
|
7267
|
+
});
|
|
7268
|
+
}
|
|
7269
|
+
core(path, options || {}, function(er, is) {
|
|
7270
|
+
if (er) {
|
|
7271
|
+
if (er.code === "EACCES" || options && options.ignoreErrors) {
|
|
7272
|
+
er = null;
|
|
7273
|
+
is = false;
|
|
7274
|
+
}
|
|
7275
|
+
}
|
|
7276
|
+
cb(er, is);
|
|
7277
|
+
});
|
|
7278
|
+
}
|
|
7279
|
+
function sync(path, options) {
|
|
7280
|
+
try {
|
|
7281
|
+
return core.sync(path, options || {});
|
|
7282
|
+
} catch (er) {
|
|
7283
|
+
if (options && options.ignoreErrors || er.code === "EACCES") return false;
|
|
7284
|
+
else throw er;
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
}));
|
|
7288
|
+
//#endregion
|
|
7289
|
+
//#region ../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
|
|
7290
|
+
var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7291
|
+
const isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
|
|
7292
|
+
const path$12 = __require("path");
|
|
7293
|
+
const COLON = isWindows ? ";" : ":";
|
|
7294
|
+
const isexe = require_isexe();
|
|
7295
|
+
const getNotFoundError = (cmd) => Object.assign(/* @__PURE__ */ new Error(`not found: ${cmd}`), { code: "ENOENT" });
|
|
7296
|
+
const getPathInfo = (cmd, opt) => {
|
|
7297
|
+
const colon = opt.colon || COLON;
|
|
7298
|
+
const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [...isWindows ? [process.cwd()] : [], ...(opt.path || process.env.PATH || "").split(colon)];
|
|
7299
|
+
const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
|
|
7300
|
+
const pathExt = isWindows ? pathExtExe.split(colon) : [""];
|
|
7301
|
+
if (isWindows) {
|
|
7302
|
+
if (cmd.indexOf(".") !== -1 && pathExt[0] !== "") pathExt.unshift("");
|
|
7303
|
+
}
|
|
7304
|
+
return {
|
|
7305
|
+
pathEnv,
|
|
7306
|
+
pathExt,
|
|
7307
|
+
pathExtExe
|
|
7308
|
+
};
|
|
7309
|
+
};
|
|
7310
|
+
const which = (cmd, opt, cb) => {
|
|
7311
|
+
if (typeof opt === "function") {
|
|
7312
|
+
cb = opt;
|
|
7313
|
+
opt = {};
|
|
7314
|
+
}
|
|
7315
|
+
if (!opt) opt = {};
|
|
7316
|
+
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
7317
|
+
const found = [];
|
|
7318
|
+
const step = (i) => new Promise((resolve, reject) => {
|
|
7319
|
+
if (i === pathEnv.length) return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
|
|
7320
|
+
const ppRaw = pathEnv[i];
|
|
7321
|
+
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
7322
|
+
const pCmd = path$12.join(pathPart, cmd);
|
|
7323
|
+
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
7324
|
+
resolve(subStep(p, i, 0));
|
|
7325
|
+
});
|
|
7326
|
+
const subStep = (p, i, ii) => new Promise((resolve, reject) => {
|
|
7327
|
+
if (ii === pathExt.length) return resolve(step(i + 1));
|
|
7328
|
+
const ext = pathExt[ii];
|
|
7329
|
+
isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
|
|
7330
|
+
if (!er && is) if (opt.all) found.push(p + ext);
|
|
7331
|
+
else return resolve(p + ext);
|
|
7332
|
+
return resolve(subStep(p, i, ii + 1));
|
|
7333
|
+
});
|
|
7334
|
+
});
|
|
7335
|
+
return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
|
|
7336
|
+
};
|
|
7337
|
+
const whichSync = (cmd, opt) => {
|
|
7338
|
+
opt = opt || {};
|
|
7339
|
+
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
7340
|
+
const found = [];
|
|
7341
|
+
for (let i = 0; i < pathEnv.length; i++) {
|
|
7342
|
+
const ppRaw = pathEnv[i];
|
|
7343
|
+
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
7344
|
+
const pCmd = path$12.join(pathPart, cmd);
|
|
7345
|
+
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
7346
|
+
for (let j = 0; j < pathExt.length; j++) {
|
|
7347
|
+
const cur = p + pathExt[j];
|
|
7348
|
+
try {
|
|
7349
|
+
if (isexe.sync(cur, { pathExt: pathExtExe })) if (opt.all) found.push(cur);
|
|
7350
|
+
else return cur;
|
|
7351
|
+
} catch (ex) {}
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
if (opt.all && found.length) return found;
|
|
7355
|
+
if (opt.nothrow) return null;
|
|
7356
|
+
throw getNotFoundError(cmd);
|
|
7357
|
+
};
|
|
7358
|
+
module.exports = which;
|
|
7359
|
+
which.sync = whichSync;
|
|
7360
|
+
}));
|
|
7361
|
+
//#endregion
|
|
7362
|
+
//#region ../../node_modules/.pnpm/path-key@3.1.1/node_modules/path-key/index.js
|
|
7363
|
+
var require_path_key = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7364
|
+
const pathKey = (options = {}) => {
|
|
7365
|
+
const environment = options.env || process.env;
|
|
7366
|
+
if ((options.platform || process.platform) !== "win32") return "PATH";
|
|
7367
|
+
return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
|
|
7368
|
+
};
|
|
7369
|
+
module.exports = pathKey;
|
|
7370
|
+
module.exports.default = pathKey;
|
|
7371
|
+
}));
|
|
7372
|
+
//#endregion
|
|
7373
|
+
//#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
|
|
7374
|
+
var require_resolveCommand = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7375
|
+
const path$11 = __require("path");
|
|
7376
|
+
const which = require_which();
|
|
7377
|
+
const getPathKey = require_path_key();
|
|
7378
|
+
function resolveCommandAttempt(parsed, withoutPathExt) {
|
|
7379
|
+
const env = parsed.options.env || process.env;
|
|
7380
|
+
const cwd = process.cwd();
|
|
7381
|
+
const hasCustomCwd = parsed.options.cwd != null;
|
|
7382
|
+
const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
|
|
7383
|
+
if (shouldSwitchCwd) try {
|
|
7384
|
+
process.chdir(parsed.options.cwd);
|
|
7385
|
+
} catch (err) {}
|
|
7386
|
+
let resolved;
|
|
7387
|
+
try {
|
|
7388
|
+
resolved = which.sync(parsed.command, {
|
|
7389
|
+
path: env[getPathKey({ env })],
|
|
7390
|
+
pathExt: withoutPathExt ? path$11.delimiter : void 0
|
|
7391
|
+
});
|
|
7392
|
+
} catch (e) {} finally {
|
|
7393
|
+
if (shouldSwitchCwd) process.chdir(cwd);
|
|
7394
|
+
}
|
|
7395
|
+
if (resolved) resolved = path$11.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
|
|
7396
|
+
return resolved;
|
|
7397
|
+
}
|
|
7398
|
+
function resolveCommand(parsed) {
|
|
7399
|
+
return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
|
|
7400
|
+
}
|
|
7401
|
+
module.exports = resolveCommand;
|
|
7402
|
+
}));
|
|
7403
|
+
//#endregion
|
|
7404
|
+
//#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js
|
|
7405
|
+
var require_escape = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7406
|
+
const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
|
|
7407
|
+
function escapeCommand(arg) {
|
|
7408
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
7409
|
+
return arg;
|
|
7410
|
+
}
|
|
7411
|
+
function escapeArgument(arg, doubleEscapeMetaChars) {
|
|
7412
|
+
arg = `${arg}`;
|
|
7413
|
+
arg = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"");
|
|
7414
|
+
arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
|
|
7415
|
+
arg = `"${arg}"`;
|
|
7416
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
7417
|
+
if (doubleEscapeMetaChars) arg = arg.replace(metaCharsRegExp, "^$1");
|
|
7418
|
+
return arg;
|
|
7419
|
+
}
|
|
7420
|
+
module.exports.command = escapeCommand;
|
|
7421
|
+
module.exports.argument = escapeArgument;
|
|
7422
|
+
}));
|
|
7423
|
+
//#endregion
|
|
7424
|
+
//#region ../../node_modules/.pnpm/shebang-regex@3.0.0/node_modules/shebang-regex/index.js
|
|
7425
|
+
var require_shebang_regex = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7426
|
+
module.exports = /^#!(.*)/;
|
|
7427
|
+
}));
|
|
7428
|
+
//#endregion
|
|
7429
|
+
//#region ../../node_modules/.pnpm/shebang-command@2.0.0/node_modules/shebang-command/index.js
|
|
7430
|
+
var require_shebang_command = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7431
|
+
const shebangRegex = require_shebang_regex();
|
|
7432
|
+
module.exports = (string = "") => {
|
|
7433
|
+
const match = string.match(shebangRegex);
|
|
7434
|
+
if (!match) return null;
|
|
7435
|
+
const [path, argument] = match[0].replace(/#! ?/, "").split(" ");
|
|
7436
|
+
const binary = path.split("/").pop();
|
|
7437
|
+
if (binary === "env") return argument;
|
|
7438
|
+
return argument ? `${binary} ${argument}` : binary;
|
|
7439
|
+
};
|
|
7440
|
+
}));
|
|
7441
|
+
//#endregion
|
|
7442
|
+
//#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
|
|
7443
|
+
var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7444
|
+
const fs$15 = __require("fs");
|
|
7445
|
+
const shebangCommand = require_shebang_command();
|
|
7446
|
+
function readShebang(command) {
|
|
7447
|
+
const size = 150;
|
|
7448
|
+
const buffer = Buffer.alloc(size);
|
|
7449
|
+
let fd;
|
|
7450
|
+
try {
|
|
7451
|
+
fd = fs$15.openSync(command, "r");
|
|
7452
|
+
fs$15.readSync(fd, buffer, 0, size, 0);
|
|
7453
|
+
fs$15.closeSync(fd);
|
|
7454
|
+
} catch (e) {}
|
|
7455
|
+
return shebangCommand(buffer.toString());
|
|
7456
|
+
}
|
|
7457
|
+
module.exports = readShebang;
|
|
7458
|
+
}));
|
|
7459
|
+
//#endregion
|
|
7460
|
+
//#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
|
|
7461
|
+
var require_parse$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7462
|
+
const path$10 = __require("path");
|
|
7463
|
+
const resolveCommand = require_resolveCommand();
|
|
7464
|
+
const escape = require_escape();
|
|
7465
|
+
const readShebang = require_readShebang();
|
|
7466
|
+
const isWin = process.platform === "win32";
|
|
7467
|
+
const isExecutableRegExp = /\.(?:com|exe)$/i;
|
|
7468
|
+
const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
|
|
7469
|
+
function detectShebang(parsed) {
|
|
7470
|
+
parsed.file = resolveCommand(parsed);
|
|
7471
|
+
const shebang = parsed.file && readShebang(parsed.file);
|
|
7472
|
+
if (shebang) {
|
|
7473
|
+
parsed.args.unshift(parsed.file);
|
|
7474
|
+
parsed.command = shebang;
|
|
7475
|
+
return resolveCommand(parsed);
|
|
7476
|
+
}
|
|
7477
|
+
return parsed.file;
|
|
7478
|
+
}
|
|
7479
|
+
function parseNonShell(parsed) {
|
|
7480
|
+
if (!isWin) return parsed;
|
|
7481
|
+
const commandFile = detectShebang(parsed);
|
|
7482
|
+
const needsShell = !isExecutableRegExp.test(commandFile);
|
|
7483
|
+
if (parsed.options.forceShell || needsShell) {
|
|
7484
|
+
const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
|
|
7485
|
+
parsed.command = path$10.normalize(parsed.command);
|
|
7486
|
+
parsed.command = escape.command(parsed.command);
|
|
7487
|
+
parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
|
|
7488
|
+
parsed.args = [
|
|
7489
|
+
"/d",
|
|
7490
|
+
"/s",
|
|
7491
|
+
"/c",
|
|
7492
|
+
`"${[parsed.command].concat(parsed.args).join(" ")}"`
|
|
7493
|
+
];
|
|
7494
|
+
parsed.command = process.env.comspec || "cmd.exe";
|
|
7495
|
+
parsed.options.windowsVerbatimArguments = true;
|
|
7496
|
+
}
|
|
7497
|
+
return parsed;
|
|
7498
|
+
}
|
|
7499
|
+
function parse(command, args, options) {
|
|
7500
|
+
if (args && !Array.isArray(args)) {
|
|
7501
|
+
options = args;
|
|
7502
|
+
args = null;
|
|
7503
|
+
}
|
|
7504
|
+
args = args ? args.slice(0) : [];
|
|
7505
|
+
options = Object.assign({}, options);
|
|
7506
|
+
const parsed = {
|
|
7507
|
+
command,
|
|
7508
|
+
args,
|
|
7509
|
+
options,
|
|
7510
|
+
file: void 0,
|
|
7511
|
+
original: {
|
|
7512
|
+
command,
|
|
7513
|
+
args
|
|
7514
|
+
}
|
|
7515
|
+
};
|
|
7516
|
+
return options.shell ? parsed : parseNonShell(parsed);
|
|
7517
|
+
}
|
|
7518
|
+
module.exports = parse;
|
|
7519
|
+
}));
|
|
7520
|
+
//#endregion
|
|
7521
|
+
//#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js
|
|
7522
|
+
var require_enoent = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7523
|
+
const isWin = process.platform === "win32";
|
|
7524
|
+
function notFoundError(original, syscall) {
|
|
7525
|
+
return Object.assign(/* @__PURE__ */ new Error(`${syscall} ${original.command} ENOENT`), {
|
|
7526
|
+
code: "ENOENT",
|
|
7527
|
+
errno: "ENOENT",
|
|
7528
|
+
syscall: `${syscall} ${original.command}`,
|
|
7529
|
+
path: original.command,
|
|
7530
|
+
spawnargs: original.args
|
|
7531
|
+
});
|
|
7532
|
+
}
|
|
7533
|
+
function hookChildProcess(cp, parsed) {
|
|
7534
|
+
if (!isWin) return;
|
|
7535
|
+
const originalEmit = cp.emit;
|
|
7536
|
+
cp.emit = function(name, arg1) {
|
|
7537
|
+
if (name === "exit") {
|
|
7538
|
+
const err = verifyENOENT(arg1, parsed);
|
|
7539
|
+
if (err) return originalEmit.call(cp, "error", err);
|
|
7540
|
+
}
|
|
7541
|
+
return originalEmit.apply(cp, arguments);
|
|
7542
|
+
};
|
|
7543
|
+
}
|
|
7544
|
+
function verifyENOENT(status, parsed) {
|
|
7545
|
+
if (isWin && status === 1 && !parsed.file) return notFoundError(parsed.original, "spawn");
|
|
7546
|
+
return null;
|
|
7547
|
+
}
|
|
7548
|
+
function verifyENOENTSync(status, parsed) {
|
|
7549
|
+
if (isWin && status === 1 && !parsed.file) return notFoundError(parsed.original, "spawnSync");
|
|
7550
|
+
return null;
|
|
7551
|
+
}
|
|
7552
|
+
module.exports = {
|
|
7553
|
+
hookChildProcess,
|
|
7554
|
+
verifyENOENT,
|
|
7555
|
+
verifyENOENTSync,
|
|
7556
|
+
notFoundError
|
|
7557
|
+
};
|
|
7558
|
+
}));
|
|
7559
|
+
//#endregion
|
|
7560
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/client/stdio.js
|
|
7561
|
+
var import_cross_spawn = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7562
|
+
const cp = __require("child_process");
|
|
7563
|
+
const parse = require_parse$2();
|
|
7564
|
+
const enoent = require_enoent();
|
|
7565
|
+
function spawn(command, args, options) {
|
|
7566
|
+
const parsed = parse(command, args, options);
|
|
7567
|
+
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
7568
|
+
enoent.hookChildProcess(spawned, parsed);
|
|
7569
|
+
return spawned;
|
|
7570
|
+
}
|
|
7571
|
+
function spawnSync(command, args, options) {
|
|
7572
|
+
const parsed = parse(command, args, options);
|
|
7573
|
+
const result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
|
|
7574
|
+
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed);
|
|
7575
|
+
return result;
|
|
7576
|
+
}
|
|
7577
|
+
module.exports = spawn;
|
|
7578
|
+
module.exports.spawn = spawn;
|
|
7579
|
+
module.exports.sync = spawnSync;
|
|
7580
|
+
module.exports._parse = parse;
|
|
7581
|
+
module.exports._enoent = enoent;
|
|
7582
|
+
})))(), 1);
|
|
7583
|
+
/**
|
|
7584
|
+
* Environment variables to inherit by default, if an environment is not explicitly given.
|
|
7585
|
+
*/
|
|
7586
|
+
const DEFAULT_INHERITED_ENV_VARS = process$1.platform === "win32" ? [
|
|
7587
|
+
"APPDATA",
|
|
7588
|
+
"HOMEDRIVE",
|
|
7589
|
+
"HOMEPATH",
|
|
7590
|
+
"LOCALAPPDATA",
|
|
7591
|
+
"PATH",
|
|
7592
|
+
"PROCESSOR_ARCHITECTURE",
|
|
7593
|
+
"SYSTEMDRIVE",
|
|
7594
|
+
"SYSTEMROOT",
|
|
7595
|
+
"TEMP",
|
|
7596
|
+
"USERNAME",
|
|
7597
|
+
"USERPROFILE",
|
|
7598
|
+
"PROGRAMFILES"
|
|
7599
|
+
] : [
|
|
7600
|
+
"HOME",
|
|
7601
|
+
"LOGNAME",
|
|
7602
|
+
"PATH",
|
|
7603
|
+
"SHELL",
|
|
7604
|
+
"TERM",
|
|
7605
|
+
"USER"
|
|
7606
|
+
];
|
|
7607
|
+
/**
|
|
7608
|
+
* Returns a default environment object including only environment variables deemed safe to inherit.
|
|
7609
|
+
*/
|
|
7610
|
+
function getDefaultEnvironment() {
|
|
7611
|
+
const env = {};
|
|
7612
|
+
for (const key of DEFAULT_INHERITED_ENV_VARS) {
|
|
7613
|
+
const value = process$1.env[key];
|
|
7614
|
+
if (value === void 0) continue;
|
|
7615
|
+
if (value.startsWith("()")) continue;
|
|
7616
|
+
env[key] = value;
|
|
7617
|
+
}
|
|
7618
|
+
return env;
|
|
7619
|
+
}
|
|
7620
|
+
/**
|
|
7621
|
+
* Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.
|
|
7622
|
+
*
|
|
7623
|
+
* This transport is only available in Node.js environments.
|
|
7624
|
+
*/
|
|
7625
|
+
var StdioClientTransport = class {
|
|
7626
|
+
constructor(server) {
|
|
7627
|
+
this._readBuffer = new ReadBuffer();
|
|
7628
|
+
this._stderrStream = null;
|
|
7629
|
+
this._serverParams = server;
|
|
7630
|
+
if (server.stderr === "pipe" || server.stderr === "overlapped") this._stderrStream = new PassThrough();
|
|
7631
|
+
}
|
|
7632
|
+
/**
|
|
7633
|
+
* Starts the server process and prepares to communicate with it.
|
|
7634
|
+
*/
|
|
7635
|
+
async start() {
|
|
7636
|
+
if (this._process) throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
7637
|
+
return new Promise((resolve, reject) => {
|
|
7638
|
+
this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
|
|
7639
|
+
env: {
|
|
7640
|
+
...getDefaultEnvironment(),
|
|
7641
|
+
...this._serverParams.env
|
|
7642
|
+
},
|
|
7643
|
+
stdio: [
|
|
7644
|
+
"pipe",
|
|
7645
|
+
"pipe",
|
|
7646
|
+
this._serverParams.stderr ?? "inherit"
|
|
7647
|
+
],
|
|
7648
|
+
shell: false,
|
|
7649
|
+
windowsHide: process$1.platform === "win32",
|
|
7650
|
+
cwd: this._serverParams.cwd
|
|
7651
|
+
});
|
|
7652
|
+
this._process.on("error", (error) => {
|
|
7653
|
+
reject(error);
|
|
7654
|
+
this.onerror?.(error);
|
|
7655
|
+
});
|
|
7656
|
+
this._process.on("spawn", () => {
|
|
7657
|
+
resolve();
|
|
7658
|
+
});
|
|
7659
|
+
this._process.on("close", (_code) => {
|
|
7660
|
+
this._process = void 0;
|
|
7661
|
+
this.onclose?.();
|
|
7662
|
+
});
|
|
7663
|
+
this._process.stdin?.on("error", (error) => {
|
|
7664
|
+
this.onerror?.(error);
|
|
7665
|
+
});
|
|
7666
|
+
this._process.stdout?.on("data", (chunk) => {
|
|
7667
|
+
this._readBuffer.append(chunk);
|
|
7668
|
+
this.processReadBuffer();
|
|
7669
|
+
});
|
|
7670
|
+
this._process.stdout?.on("error", (error) => {
|
|
7671
|
+
this.onerror?.(error);
|
|
7672
|
+
});
|
|
7673
|
+
if (this._stderrStream && this._process.stderr) this._process.stderr.pipe(this._stderrStream);
|
|
7674
|
+
});
|
|
7675
|
+
}
|
|
7676
|
+
/**
|
|
7677
|
+
* The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
|
|
7678
|
+
*
|
|
7679
|
+
* If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to
|
|
7680
|
+
* attach listeners before the start method is invoked. This prevents loss of any early
|
|
7681
|
+
* error output emitted by the child process.
|
|
7682
|
+
*/
|
|
7683
|
+
get stderr() {
|
|
7684
|
+
if (this._stderrStream) return this._stderrStream;
|
|
7685
|
+
return this._process?.stderr ?? null;
|
|
7686
|
+
}
|
|
7687
|
+
/**
|
|
7688
|
+
* The child process pid spawned by this transport.
|
|
7689
|
+
*
|
|
7690
|
+
* This is only available after the transport has been started.
|
|
7691
|
+
*/
|
|
7692
|
+
get pid() {
|
|
7693
|
+
return this._process?.pid ?? null;
|
|
7694
|
+
}
|
|
7695
|
+
processReadBuffer() {
|
|
7696
|
+
while (true) try {
|
|
7697
|
+
const message = this._readBuffer.readMessage();
|
|
7698
|
+
if (message === null) break;
|
|
7699
|
+
this.onmessage?.(message);
|
|
7700
|
+
} catch (error) {
|
|
7701
|
+
this.onerror?.(error);
|
|
7702
|
+
}
|
|
7703
|
+
}
|
|
7704
|
+
async close() {
|
|
7705
|
+
if (this._process) {
|
|
7706
|
+
const processToClose = this._process;
|
|
7707
|
+
this._process = void 0;
|
|
7708
|
+
const closePromise = new Promise((resolve) => {
|
|
7709
|
+
processToClose.once("close", () => {
|
|
7710
|
+
resolve();
|
|
7711
|
+
});
|
|
7712
|
+
});
|
|
7713
|
+
try {
|
|
7714
|
+
processToClose.stdin?.end();
|
|
7715
|
+
} catch {}
|
|
7716
|
+
await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2e3).unref())]);
|
|
7717
|
+
if (processToClose.exitCode === null) {
|
|
7718
|
+
try {
|
|
7719
|
+
processToClose.kill("SIGTERM");
|
|
7720
|
+
} catch {}
|
|
7721
|
+
await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2e3).unref())]);
|
|
7722
|
+
}
|
|
7723
|
+
if (processToClose.exitCode === null) try {
|
|
7724
|
+
processToClose.kill("SIGKILL");
|
|
7725
|
+
} catch {}
|
|
7726
|
+
}
|
|
7727
|
+
this._readBuffer.clear();
|
|
7728
|
+
}
|
|
7729
|
+
send(message) {
|
|
7730
|
+
return new Promise((resolve) => {
|
|
7731
|
+
if (!this._process?.stdin) throw new Error("Not connected");
|
|
7732
|
+
const json = serializeMessage(message);
|
|
7733
|
+
if (this._process.stdin.write(json)) resolve();
|
|
7734
|
+
else this._process.stdin.once("drain", resolve);
|
|
7735
|
+
});
|
|
7736
|
+
}
|
|
7737
|
+
};
|
|
7738
|
+
//#endregion
|
|
7739
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js
|
|
7740
|
+
/**
|
|
7741
|
+
* Normalizes HeadersInit to a plain Record<string, string> for manipulation.
|
|
7742
|
+
* Handles Headers objects, arrays of tuples, and plain objects.
|
|
7743
|
+
*/
|
|
7744
|
+
function normalizeHeaders(headers) {
|
|
7745
|
+
if (!headers) return {};
|
|
7746
|
+
if (headers instanceof Headers) return Object.fromEntries(headers.entries());
|
|
7747
|
+
if (Array.isArray(headers)) return Object.fromEntries(headers);
|
|
7748
|
+
return { ...headers };
|
|
7749
|
+
}
|
|
7750
|
+
/**
|
|
7751
|
+
* Creates a fetch function that includes base RequestInit options.
|
|
7752
|
+
* This ensures requests inherit settings like credentials, mode, headers, etc. from the base init.
|
|
7753
|
+
*
|
|
7754
|
+
* @param baseFetch - The base fetch function to wrap (defaults to global fetch)
|
|
7755
|
+
* @param baseInit - The base RequestInit to merge with each request
|
|
7756
|
+
* @returns A wrapped fetch function that merges base options with call-specific options
|
|
7757
|
+
*/
|
|
7758
|
+
function createFetchWithInit(baseFetch = fetch, baseInit) {
|
|
7759
|
+
if (!baseInit) return baseFetch;
|
|
7760
|
+
return async (url, init) => {
|
|
7761
|
+
return baseFetch(url, {
|
|
7762
|
+
...baseInit,
|
|
7763
|
+
...init,
|
|
7764
|
+
headers: init?.headers ? {
|
|
7765
|
+
...normalizeHeaders(baseInit.headers),
|
|
7766
|
+
...normalizeHeaders(init.headers)
|
|
7767
|
+
} : baseInit.headers
|
|
7768
|
+
});
|
|
7769
|
+
};
|
|
7770
|
+
}
|
|
7771
|
+
//#endregion
|
|
7772
|
+
//#region ../../node_modules/.pnpm/pkce-challenge@5.0.1/node_modules/pkce-challenge/dist/index.node.js
|
|
7773
|
+
let crypto$6;
|
|
7774
|
+
crypto$6 = globalThis.crypto?.webcrypto ?? globalThis.crypto ?? import("node:crypto").then((m) => m.webcrypto);
|
|
7775
|
+
/**
|
|
7776
|
+
* Creates an array of length `size` of random bytes
|
|
7777
|
+
* @param size
|
|
7778
|
+
* @returns Array of random ints (0 to 255)
|
|
7779
|
+
*/
|
|
7780
|
+
async function getRandomValues(size) {
|
|
7781
|
+
return (await crypto$6).getRandomValues(new Uint8Array(size));
|
|
7782
|
+
}
|
|
7783
|
+
/** Generate cryptographically strong random string
|
|
7784
|
+
* @param size The desired length of the string
|
|
7785
|
+
* @returns The random string
|
|
7786
|
+
*/
|
|
7787
|
+
async function random(size) {
|
|
7788
|
+
const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
|
|
7789
|
+
const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % 66;
|
|
7790
|
+
let result = "";
|
|
7791
|
+
while (result.length < size) {
|
|
7792
|
+
const randomBytes = await getRandomValues(size - result.length);
|
|
7793
|
+
for (const randomByte of randomBytes) if (randomByte < evenDistCutoff) result += mask[randomByte % 66];
|
|
7794
|
+
}
|
|
7795
|
+
return result;
|
|
7796
|
+
}
|
|
7797
|
+
/** Generate a PKCE challenge verifier
|
|
7798
|
+
* @param length Length of the verifier
|
|
7799
|
+
* @returns A random verifier `length` characters long
|
|
7800
|
+
*/
|
|
7801
|
+
async function generateVerifier(length) {
|
|
7802
|
+
return await random(length);
|
|
7803
|
+
}
|
|
7804
|
+
/** Generate a PKCE code challenge from a code verifier
|
|
7805
|
+
* @param code_verifier
|
|
7806
|
+
* @returns The base64 url encoded code challenge
|
|
7807
|
+
*/
|
|
7808
|
+
async function generateChallenge(code_verifier) {
|
|
7809
|
+
const buffer = await (await crypto$6).subtle.digest("SHA-256", new TextEncoder().encode(code_verifier));
|
|
7810
|
+
return btoa(String.fromCharCode(...new Uint8Array(buffer))).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "");
|
|
7811
|
+
}
|
|
7812
|
+
/** Generate a PKCE challenge pair
|
|
7813
|
+
* @param length Length of the verifer (between 43-128). Defaults to 43.
|
|
7814
|
+
* @returns PKCE challenge pair
|
|
7815
|
+
*/
|
|
7816
|
+
async function pkceChallenge(length) {
|
|
7817
|
+
if (!length) length = 43;
|
|
7818
|
+
if (length < 43 || length > 128) throw `Expected a length between 43 and 128. Received ${length}.`;
|
|
7819
|
+
const verifier = await generateVerifier(length);
|
|
7820
|
+
return {
|
|
7821
|
+
code_verifier: verifier,
|
|
7822
|
+
code_challenge: await generateChallenge(verifier)
|
|
7823
|
+
};
|
|
7824
|
+
}
|
|
7825
|
+
//#endregion
|
|
7826
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/auth.js
|
|
7827
|
+
/**
|
|
7828
|
+
* Reusable URL validation that disallows javascript: scheme
|
|
7829
|
+
*/
|
|
7830
|
+
const SafeUrlSchema = url$4().superRefine((val, ctx) => {
|
|
7831
|
+
if (!URL.canParse(val)) {
|
|
7832
|
+
ctx.addIssue({
|
|
7833
|
+
code: ZodIssueCode.custom,
|
|
7834
|
+
message: "URL must be parseable",
|
|
7835
|
+
fatal: true
|
|
7836
|
+
});
|
|
7837
|
+
return NEVER;
|
|
7838
|
+
}
|
|
7839
|
+
}).refine((url) => {
|
|
7840
|
+
const u = new URL(url);
|
|
7841
|
+
return u.protocol !== "javascript:" && u.protocol !== "data:" && u.protocol !== "vbscript:";
|
|
7842
|
+
}, { message: "URL cannot use javascript:, data:, or vbscript: scheme" });
|
|
7843
|
+
/**
|
|
7844
|
+
* RFC 9728 OAuth Protected Resource Metadata
|
|
7845
|
+
*/
|
|
7846
|
+
const OAuthProtectedResourceMetadataSchema = looseObject({
|
|
7847
|
+
resource: string().url(),
|
|
7848
|
+
authorization_servers: array(SafeUrlSchema).optional(),
|
|
7849
|
+
jwks_uri: string().url().optional(),
|
|
7850
|
+
scopes_supported: array(string()).optional(),
|
|
7851
|
+
bearer_methods_supported: array(string()).optional(),
|
|
7852
|
+
resource_signing_alg_values_supported: array(string()).optional(),
|
|
7853
|
+
resource_name: string().optional(),
|
|
7854
|
+
resource_documentation: string().optional(),
|
|
7855
|
+
resource_policy_uri: string().url().optional(),
|
|
7856
|
+
resource_tos_uri: string().url().optional(),
|
|
7857
|
+
tls_client_certificate_bound_access_tokens: boolean().optional(),
|
|
7858
|
+
authorization_details_types_supported: array(string()).optional(),
|
|
7859
|
+
dpop_signing_alg_values_supported: array(string()).optional(),
|
|
7860
|
+
dpop_bound_access_tokens_required: boolean().optional()
|
|
7861
|
+
});
|
|
7862
|
+
/**
|
|
7863
|
+
* RFC 8414 OAuth 2.0 Authorization Server Metadata
|
|
7864
|
+
*/
|
|
7865
|
+
const OAuthMetadataSchema = looseObject({
|
|
7866
|
+
issuer: string(),
|
|
7867
|
+
authorization_endpoint: SafeUrlSchema,
|
|
7868
|
+
token_endpoint: SafeUrlSchema,
|
|
7869
|
+
registration_endpoint: SafeUrlSchema.optional(),
|
|
7870
|
+
scopes_supported: array(string()).optional(),
|
|
7871
|
+
response_types_supported: array(string()),
|
|
7872
|
+
response_modes_supported: array(string()).optional(),
|
|
7873
|
+
grant_types_supported: array(string()).optional(),
|
|
7874
|
+
token_endpoint_auth_methods_supported: array(string()).optional(),
|
|
7875
|
+
token_endpoint_auth_signing_alg_values_supported: array(string()).optional(),
|
|
7876
|
+
service_documentation: SafeUrlSchema.optional(),
|
|
7877
|
+
revocation_endpoint: SafeUrlSchema.optional(),
|
|
7878
|
+
revocation_endpoint_auth_methods_supported: array(string()).optional(),
|
|
7879
|
+
revocation_endpoint_auth_signing_alg_values_supported: array(string()).optional(),
|
|
7880
|
+
introspection_endpoint: string().optional(),
|
|
7881
|
+
introspection_endpoint_auth_methods_supported: array(string()).optional(),
|
|
7882
|
+
introspection_endpoint_auth_signing_alg_values_supported: array(string()).optional(),
|
|
7883
|
+
code_challenge_methods_supported: array(string()).optional(),
|
|
7884
|
+
client_id_metadata_document_supported: boolean().optional()
|
|
7885
|
+
});
|
|
7886
|
+
/**
|
|
7887
|
+
* OpenID Connect Discovery metadata that may include OAuth 2.0 fields
|
|
7888
|
+
* This schema represents the real-world scenario where OIDC providers
|
|
7889
|
+
* return a mix of OpenID Connect and OAuth 2.0 metadata fields
|
|
7890
|
+
*/
|
|
7891
|
+
const OpenIdProviderDiscoveryMetadataSchema = object({
|
|
7892
|
+
...looseObject({
|
|
7893
|
+
issuer: string(),
|
|
7894
|
+
authorization_endpoint: SafeUrlSchema,
|
|
7895
|
+
token_endpoint: SafeUrlSchema,
|
|
7896
|
+
userinfo_endpoint: SafeUrlSchema.optional(),
|
|
7897
|
+
jwks_uri: SafeUrlSchema,
|
|
7898
|
+
registration_endpoint: SafeUrlSchema.optional(),
|
|
7899
|
+
scopes_supported: array(string()).optional(),
|
|
7900
|
+
response_types_supported: array(string()),
|
|
7901
|
+
response_modes_supported: array(string()).optional(),
|
|
7902
|
+
grant_types_supported: array(string()).optional(),
|
|
7903
|
+
acr_values_supported: array(string()).optional(),
|
|
7904
|
+
subject_types_supported: array(string()),
|
|
7905
|
+
id_token_signing_alg_values_supported: array(string()),
|
|
7906
|
+
id_token_encryption_alg_values_supported: array(string()).optional(),
|
|
7907
|
+
id_token_encryption_enc_values_supported: array(string()).optional(),
|
|
7908
|
+
userinfo_signing_alg_values_supported: array(string()).optional(),
|
|
7909
|
+
userinfo_encryption_alg_values_supported: array(string()).optional(),
|
|
7910
|
+
userinfo_encryption_enc_values_supported: array(string()).optional(),
|
|
7911
|
+
request_object_signing_alg_values_supported: array(string()).optional(),
|
|
7912
|
+
request_object_encryption_alg_values_supported: array(string()).optional(),
|
|
7913
|
+
request_object_encryption_enc_values_supported: array(string()).optional(),
|
|
7914
|
+
token_endpoint_auth_methods_supported: array(string()).optional(),
|
|
7915
|
+
token_endpoint_auth_signing_alg_values_supported: array(string()).optional(),
|
|
7916
|
+
display_values_supported: array(string()).optional(),
|
|
7917
|
+
claim_types_supported: array(string()).optional(),
|
|
7918
|
+
claims_supported: array(string()).optional(),
|
|
7919
|
+
service_documentation: string().optional(),
|
|
7920
|
+
claims_locales_supported: array(string()).optional(),
|
|
7921
|
+
ui_locales_supported: array(string()).optional(),
|
|
7922
|
+
claims_parameter_supported: boolean().optional(),
|
|
7923
|
+
request_parameter_supported: boolean().optional(),
|
|
7924
|
+
request_uri_parameter_supported: boolean().optional(),
|
|
7925
|
+
require_request_uri_registration: boolean().optional(),
|
|
7926
|
+
op_policy_uri: SafeUrlSchema.optional(),
|
|
7927
|
+
op_tos_uri: SafeUrlSchema.optional(),
|
|
7928
|
+
client_id_metadata_document_supported: boolean().optional()
|
|
7929
|
+
}).shape,
|
|
7930
|
+
...OAuthMetadataSchema.pick({ code_challenge_methods_supported: true }).shape
|
|
7931
|
+
});
|
|
7932
|
+
/**
|
|
7933
|
+
* OAuth 2.1 token response
|
|
7934
|
+
*/
|
|
7935
|
+
const OAuthTokensSchema = object({
|
|
7936
|
+
access_token: string(),
|
|
7937
|
+
id_token: string().optional(),
|
|
7938
|
+
token_type: string(),
|
|
7939
|
+
expires_in: number().optional(),
|
|
7940
|
+
scope: string().optional(),
|
|
7941
|
+
refresh_token: string().optional()
|
|
7942
|
+
}).strip();
|
|
7943
|
+
/**
|
|
7944
|
+
* OAuth 2.1 error response
|
|
7945
|
+
*/
|
|
7946
|
+
const OAuthErrorResponseSchema = object({
|
|
7947
|
+
error: string(),
|
|
7948
|
+
error_description: string().optional(),
|
|
7949
|
+
error_uri: string().optional()
|
|
7950
|
+
});
|
|
7951
|
+
/**
|
|
7952
|
+
* Optional version of SafeUrlSchema that allows empty string for retrocompatibility on tos_uri and logo_uri
|
|
7953
|
+
*/
|
|
7954
|
+
const OptionalSafeUrlSchema = SafeUrlSchema.optional().or(literal("").transform(() => void 0));
|
|
7955
|
+
/**
|
|
7956
|
+
* RFC 7591 OAuth 2.0 Dynamic Client Registration metadata
|
|
7957
|
+
*/
|
|
7958
|
+
const OAuthClientMetadataSchema = object({
|
|
7959
|
+
redirect_uris: array(SafeUrlSchema),
|
|
7960
|
+
token_endpoint_auth_method: string().optional(),
|
|
7961
|
+
grant_types: array(string()).optional(),
|
|
7962
|
+
response_types: array(string()).optional(),
|
|
7963
|
+
client_name: string().optional(),
|
|
7964
|
+
client_uri: SafeUrlSchema.optional(),
|
|
7965
|
+
logo_uri: OptionalSafeUrlSchema,
|
|
7966
|
+
scope: string().optional(),
|
|
7967
|
+
contacts: array(string()).optional(),
|
|
7968
|
+
tos_uri: OptionalSafeUrlSchema,
|
|
7969
|
+
policy_uri: string().optional(),
|
|
7970
|
+
jwks_uri: SafeUrlSchema.optional(),
|
|
7971
|
+
jwks: any().optional(),
|
|
7972
|
+
software_id: string().optional(),
|
|
7973
|
+
software_version: string().optional(),
|
|
7974
|
+
software_statement: string().optional()
|
|
7975
|
+
}).strip();
|
|
7976
|
+
/**
|
|
7977
|
+
* RFC 7591 OAuth 2.0 Dynamic Client Registration client information
|
|
7978
|
+
*/
|
|
7979
|
+
const OAuthClientInformationSchema = object({
|
|
7980
|
+
client_id: string(),
|
|
7981
|
+
client_secret: string().optional(),
|
|
7982
|
+
client_id_issued_at: number$1().optional(),
|
|
7983
|
+
client_secret_expires_at: number$1().optional()
|
|
7984
|
+
}).strip();
|
|
7985
|
+
/**
|
|
7986
|
+
* RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata)
|
|
7987
|
+
*/
|
|
7988
|
+
const OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge(OAuthClientInformationSchema);
|
|
7989
|
+
object({
|
|
7990
|
+
error: string(),
|
|
7991
|
+
error_description: string().optional()
|
|
7992
|
+
}).strip();
|
|
7993
|
+
object({
|
|
7994
|
+
token: string(),
|
|
7995
|
+
token_type_hint: string().optional()
|
|
7996
|
+
}).strip();
|
|
7997
|
+
//#endregion
|
|
7998
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/auth-utils.js
|
|
7999
|
+
/**
|
|
8000
|
+
* Utilities for handling OAuth resource URIs.
|
|
8001
|
+
*/
|
|
8002
|
+
/**
|
|
8003
|
+
* Converts a server URL to a resource URL by removing the fragment.
|
|
8004
|
+
* RFC 8707 section 2 states that resource URIs "MUST NOT include a fragment component".
|
|
8005
|
+
* Keeps everything else unchanged (scheme, domain, port, path, query).
|
|
8006
|
+
*/
|
|
8007
|
+
function resourceUrlFromServerUrl(url) {
|
|
8008
|
+
const resourceURL = typeof url === "string" ? new URL(url) : new URL(url.href);
|
|
8009
|
+
resourceURL.hash = "";
|
|
8010
|
+
return resourceURL;
|
|
8011
|
+
}
|
|
8012
|
+
/**
|
|
8013
|
+
* Checks if a requested resource URL matches a configured resource URL.
|
|
8014
|
+
* A requested resource matches if it has the same scheme, domain, port,
|
|
8015
|
+
* and its path starts with the configured resource's path.
|
|
8016
|
+
*
|
|
8017
|
+
* @param requestedResource The resource URL being requested
|
|
8018
|
+
* @param configuredResource The resource URL that has been configured
|
|
8019
|
+
* @returns true if the requested resource matches the configured resource, false otherwise
|
|
8020
|
+
*/
|
|
8021
|
+
function checkResourceAllowed({ requestedResource, configuredResource }) {
|
|
8022
|
+
const requested = typeof requestedResource === "string" ? new URL(requestedResource) : new URL(requestedResource.href);
|
|
8023
|
+
const configured = typeof configuredResource === "string" ? new URL(configuredResource) : new URL(configuredResource.href);
|
|
8024
|
+
if (requested.origin !== configured.origin) return false;
|
|
8025
|
+
if (requested.pathname.length < configured.pathname.length) return false;
|
|
8026
|
+
const requestedPath = requested.pathname.endsWith("/") ? requested.pathname : requested.pathname + "/";
|
|
8027
|
+
const configuredPath = configured.pathname.endsWith("/") ? configured.pathname : configured.pathname + "/";
|
|
8028
|
+
return requestedPath.startsWith(configuredPath);
|
|
8029
|
+
}
|
|
8030
|
+
//#endregion
|
|
8031
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/errors.js
|
|
8032
|
+
/**
|
|
8033
|
+
* Base class for all OAuth errors
|
|
8034
|
+
*/
|
|
8035
|
+
var OAuthError = class extends Error {
|
|
8036
|
+
constructor(message, errorUri) {
|
|
8037
|
+
super(message);
|
|
8038
|
+
this.errorUri = errorUri;
|
|
8039
|
+
this.name = this.constructor.name;
|
|
8040
|
+
}
|
|
8041
|
+
/**
|
|
8042
|
+
* Converts the error to a standard OAuth error response object
|
|
8043
|
+
*/
|
|
8044
|
+
toResponseObject() {
|
|
8045
|
+
const response = {
|
|
8046
|
+
error: this.errorCode,
|
|
8047
|
+
error_description: this.message
|
|
8048
|
+
};
|
|
8049
|
+
if (this.errorUri) response.error_uri = this.errorUri;
|
|
8050
|
+
return response;
|
|
8051
|
+
}
|
|
8052
|
+
get errorCode() {
|
|
8053
|
+
return this.constructor.errorCode;
|
|
8054
|
+
}
|
|
8055
|
+
};
|
|
8056
|
+
/**
|
|
8057
|
+
* Invalid request error - The request is missing a required parameter,
|
|
8058
|
+
* includes an invalid parameter value, includes a parameter more than once,
|
|
8059
|
+
* or is otherwise malformed.
|
|
8060
|
+
*/
|
|
8061
|
+
var InvalidRequestError = class extends OAuthError {};
|
|
8062
|
+
InvalidRequestError.errorCode = "invalid_request";
|
|
8063
|
+
/**
|
|
8064
|
+
* Invalid client error - Client authentication failed (e.g., unknown client, no client
|
|
8065
|
+
* authentication included, or unsupported authentication method).
|
|
8066
|
+
*/
|
|
8067
|
+
var InvalidClientError = class extends OAuthError {};
|
|
8068
|
+
InvalidClientError.errorCode = "invalid_client";
|
|
8069
|
+
/**
|
|
8070
|
+
* Invalid grant error - The provided authorization grant or refresh token is
|
|
8071
|
+
* invalid, expired, revoked, does not match the redirection URI used in the
|
|
8072
|
+
* authorization request, or was issued to another client.
|
|
8073
|
+
*/
|
|
8074
|
+
var InvalidGrantError = class extends OAuthError {};
|
|
8075
|
+
InvalidGrantError.errorCode = "invalid_grant";
|
|
8076
|
+
/**
|
|
8077
|
+
* Unauthorized client error - The authenticated client is not authorized to use
|
|
8078
|
+
* this authorization grant type.
|
|
8079
|
+
*/
|
|
8080
|
+
var UnauthorizedClientError = class extends OAuthError {};
|
|
8081
|
+
UnauthorizedClientError.errorCode = "unauthorized_client";
|
|
8082
|
+
/**
|
|
8083
|
+
* Unsupported grant type error - The authorization grant type is not supported
|
|
8084
|
+
* by the authorization server.
|
|
8085
|
+
*/
|
|
8086
|
+
var UnsupportedGrantTypeError = class extends OAuthError {};
|
|
8087
|
+
UnsupportedGrantTypeError.errorCode = "unsupported_grant_type";
|
|
8088
|
+
/**
|
|
8089
|
+
* Invalid scope error - The requested scope is invalid, unknown, malformed, or
|
|
8090
|
+
* exceeds the scope granted by the resource owner.
|
|
8091
|
+
*/
|
|
8092
|
+
var InvalidScopeError = class extends OAuthError {};
|
|
8093
|
+
InvalidScopeError.errorCode = "invalid_scope";
|
|
8094
|
+
/**
|
|
8095
|
+
* Access denied error - The resource owner or authorization server denied the request.
|
|
8096
|
+
*/
|
|
8097
|
+
var AccessDeniedError = class extends OAuthError {};
|
|
8098
|
+
AccessDeniedError.errorCode = "access_denied";
|
|
8099
|
+
/**
|
|
8100
|
+
* Server error - The authorization server encountered an unexpected condition
|
|
8101
|
+
* that prevented it from fulfilling the request.
|
|
8102
|
+
*/
|
|
8103
|
+
var ServerError = class extends OAuthError {};
|
|
8104
|
+
ServerError.errorCode = "server_error";
|
|
8105
|
+
/**
|
|
8106
|
+
* Temporarily unavailable error - The authorization server is currently unable to
|
|
8107
|
+
* handle the request due to a temporary overloading or maintenance of the server.
|
|
8108
|
+
*/
|
|
8109
|
+
var TemporarilyUnavailableError = class extends OAuthError {};
|
|
8110
|
+
TemporarilyUnavailableError.errorCode = "temporarily_unavailable";
|
|
8111
|
+
/**
|
|
8112
|
+
* Unsupported response type error - The authorization server does not support
|
|
8113
|
+
* obtaining an authorization code using this method.
|
|
8114
|
+
*/
|
|
8115
|
+
var UnsupportedResponseTypeError = class extends OAuthError {};
|
|
8116
|
+
UnsupportedResponseTypeError.errorCode = "unsupported_response_type";
|
|
8117
|
+
/**
|
|
8118
|
+
* Unsupported token type error - The authorization server does not support
|
|
8119
|
+
* the requested token type.
|
|
8120
|
+
*/
|
|
8121
|
+
var UnsupportedTokenTypeError = class extends OAuthError {};
|
|
8122
|
+
UnsupportedTokenTypeError.errorCode = "unsupported_token_type";
|
|
8123
|
+
/**
|
|
8124
|
+
* Invalid token error - The access token provided is expired, revoked, malformed,
|
|
8125
|
+
* or invalid for other reasons.
|
|
8126
|
+
*/
|
|
8127
|
+
var InvalidTokenError = class extends OAuthError {};
|
|
8128
|
+
InvalidTokenError.errorCode = "invalid_token";
|
|
8129
|
+
/**
|
|
8130
|
+
* Method not allowed error - The HTTP method used is not allowed for this endpoint.
|
|
8131
|
+
* (Custom, non-standard error)
|
|
8132
|
+
*/
|
|
8133
|
+
var MethodNotAllowedError = class extends OAuthError {};
|
|
8134
|
+
MethodNotAllowedError.errorCode = "method_not_allowed";
|
|
8135
|
+
/**
|
|
8136
|
+
* Too many requests error - Rate limit exceeded.
|
|
8137
|
+
* (Custom, non-standard error based on RFC 6585)
|
|
8138
|
+
*/
|
|
8139
|
+
var TooManyRequestsError = class extends OAuthError {};
|
|
8140
|
+
TooManyRequestsError.errorCode = "too_many_requests";
|
|
8141
|
+
/**
|
|
8142
|
+
* Invalid client metadata error - The client metadata is invalid.
|
|
8143
|
+
* (Custom error for dynamic client registration - RFC 7591)
|
|
8144
|
+
*/
|
|
8145
|
+
var InvalidClientMetadataError = class extends OAuthError {};
|
|
8146
|
+
InvalidClientMetadataError.errorCode = "invalid_client_metadata";
|
|
8147
|
+
/**
|
|
8148
|
+
* Insufficient scope error - The request requires higher privileges than provided by the access token.
|
|
8149
|
+
*/
|
|
8150
|
+
var InsufficientScopeError = class extends OAuthError {};
|
|
8151
|
+
InsufficientScopeError.errorCode = "insufficient_scope";
|
|
8152
|
+
/**
|
|
8153
|
+
* Invalid target error - The requested resource is invalid, missing, unknown, or malformed.
|
|
8154
|
+
* (Custom error for resource indicators - RFC 8707)
|
|
8155
|
+
*/
|
|
8156
|
+
var InvalidTargetError = class extends OAuthError {};
|
|
8157
|
+
InvalidTargetError.errorCode = "invalid_target";
|
|
8158
|
+
/**
|
|
8159
|
+
* A full list of all OAuthErrors, enabling parsing from error responses
|
|
8160
|
+
*/
|
|
8161
|
+
const OAUTH_ERRORS = {
|
|
8162
|
+
[InvalidRequestError.errorCode]: InvalidRequestError,
|
|
8163
|
+
[InvalidClientError.errorCode]: InvalidClientError,
|
|
8164
|
+
[InvalidGrantError.errorCode]: InvalidGrantError,
|
|
8165
|
+
[UnauthorizedClientError.errorCode]: UnauthorizedClientError,
|
|
8166
|
+
[UnsupportedGrantTypeError.errorCode]: UnsupportedGrantTypeError,
|
|
8167
|
+
[InvalidScopeError.errorCode]: InvalidScopeError,
|
|
8168
|
+
[AccessDeniedError.errorCode]: AccessDeniedError,
|
|
8169
|
+
[ServerError.errorCode]: ServerError,
|
|
8170
|
+
[TemporarilyUnavailableError.errorCode]: TemporarilyUnavailableError,
|
|
8171
|
+
[UnsupportedResponseTypeError.errorCode]: UnsupportedResponseTypeError,
|
|
8172
|
+
[UnsupportedTokenTypeError.errorCode]: UnsupportedTokenTypeError,
|
|
8173
|
+
[InvalidTokenError.errorCode]: InvalidTokenError,
|
|
8174
|
+
[MethodNotAllowedError.errorCode]: MethodNotAllowedError,
|
|
8175
|
+
[TooManyRequestsError.errorCode]: TooManyRequestsError,
|
|
8176
|
+
[InvalidClientMetadataError.errorCode]: InvalidClientMetadataError,
|
|
8177
|
+
[InsufficientScopeError.errorCode]: InsufficientScopeError,
|
|
8178
|
+
[InvalidTargetError.errorCode]: InvalidTargetError
|
|
8179
|
+
};
|
|
8180
|
+
//#endregion
|
|
8181
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js
|
|
8182
|
+
var UnauthorizedError = class extends Error {
|
|
8183
|
+
constructor(message) {
|
|
8184
|
+
super(message ?? "Unauthorized");
|
|
8185
|
+
}
|
|
8186
|
+
};
|
|
8187
|
+
function isClientAuthMethod(method) {
|
|
8188
|
+
return [
|
|
8189
|
+
"client_secret_basic",
|
|
8190
|
+
"client_secret_post",
|
|
8191
|
+
"none"
|
|
8192
|
+
].includes(method);
|
|
8193
|
+
}
|
|
8194
|
+
const AUTHORIZATION_CODE_RESPONSE_TYPE = "code";
|
|
8195
|
+
const AUTHORIZATION_CODE_CHALLENGE_METHOD = "S256";
|
|
8196
|
+
/**
|
|
8197
|
+
* Determines the best client authentication method to use based on server support and client configuration.
|
|
8198
|
+
*
|
|
8199
|
+
* Priority order (highest to lowest):
|
|
8200
|
+
* 1. client_secret_basic (if client secret is available)
|
|
8201
|
+
* 2. client_secret_post (if client secret is available)
|
|
8202
|
+
* 3. none (for public clients)
|
|
8203
|
+
*
|
|
8204
|
+
* @param clientInformation - OAuth client information containing credentials
|
|
8205
|
+
* @param supportedMethods - Authentication methods supported by the authorization server
|
|
8206
|
+
* @returns The selected authentication method
|
|
8207
|
+
*/
|
|
8208
|
+
function selectClientAuthMethod(clientInformation, supportedMethods) {
|
|
8209
|
+
const hasClientSecret = clientInformation.client_secret !== void 0;
|
|
8210
|
+
if ("token_endpoint_auth_method" in clientInformation && clientInformation.token_endpoint_auth_method && isClientAuthMethod(clientInformation.token_endpoint_auth_method) && (supportedMethods.length === 0 || supportedMethods.includes(clientInformation.token_endpoint_auth_method))) return clientInformation.token_endpoint_auth_method;
|
|
8211
|
+
if (supportedMethods.length === 0) return hasClientSecret ? "client_secret_basic" : "none";
|
|
8212
|
+
if (hasClientSecret && supportedMethods.includes("client_secret_basic")) return "client_secret_basic";
|
|
8213
|
+
if (hasClientSecret && supportedMethods.includes("client_secret_post")) return "client_secret_post";
|
|
8214
|
+
if (supportedMethods.includes("none")) return "none";
|
|
8215
|
+
return hasClientSecret ? "client_secret_post" : "none";
|
|
8216
|
+
}
|
|
8217
|
+
/**
|
|
8218
|
+
* Applies client authentication to the request based on the specified method.
|
|
8219
|
+
*
|
|
8220
|
+
* Implements OAuth 2.1 client authentication methods:
|
|
8221
|
+
* - client_secret_basic: HTTP Basic authentication (RFC 6749 Section 2.3.1)
|
|
8222
|
+
* - client_secret_post: Credentials in request body (RFC 6749 Section 2.3.1)
|
|
8223
|
+
* - none: Public client authentication (RFC 6749 Section 2.1)
|
|
8224
|
+
*
|
|
8225
|
+
* @param method - The authentication method to use
|
|
8226
|
+
* @param clientInformation - OAuth client information containing credentials
|
|
8227
|
+
* @param headers - HTTP headers object to modify
|
|
8228
|
+
* @param params - URL search parameters to modify
|
|
8229
|
+
* @throws {Error} When required credentials are missing
|
|
8230
|
+
*/
|
|
8231
|
+
function applyClientAuthentication(method, clientInformation, headers, params) {
|
|
8232
|
+
const { client_id, client_secret } = clientInformation;
|
|
8233
|
+
switch (method) {
|
|
8234
|
+
case "client_secret_basic":
|
|
8235
|
+
applyBasicAuth(client_id, client_secret, headers);
|
|
8236
|
+
return;
|
|
8237
|
+
case "client_secret_post":
|
|
8238
|
+
applyPostAuth(client_id, client_secret, params);
|
|
8239
|
+
return;
|
|
8240
|
+
case "none":
|
|
8241
|
+
applyPublicAuth(client_id, params);
|
|
8242
|
+
return;
|
|
8243
|
+
default: throw new Error(`Unsupported client authentication method: ${method}`);
|
|
8244
|
+
}
|
|
8245
|
+
}
|
|
8246
|
+
/**
|
|
8247
|
+
* Applies HTTP Basic authentication (RFC 6749 Section 2.3.1)
|
|
8248
|
+
*/
|
|
8249
|
+
function applyBasicAuth(clientId, clientSecret, headers) {
|
|
8250
|
+
if (!clientSecret) throw new Error("client_secret_basic authentication requires a client_secret");
|
|
8251
|
+
const credentials = btoa(`${clientId}:${clientSecret}`);
|
|
8252
|
+
headers.set("Authorization", `Basic ${credentials}`);
|
|
8253
|
+
}
|
|
8254
|
+
/**
|
|
8255
|
+
* Applies POST body authentication (RFC 6749 Section 2.3.1)
|
|
8256
|
+
*/
|
|
8257
|
+
function applyPostAuth(clientId, clientSecret, params) {
|
|
8258
|
+
params.set("client_id", clientId);
|
|
8259
|
+
if (clientSecret) params.set("client_secret", clientSecret);
|
|
8260
|
+
}
|
|
8261
|
+
/**
|
|
8262
|
+
* Applies public client authentication (RFC 6749 Section 2.1)
|
|
8263
|
+
*/
|
|
8264
|
+
function applyPublicAuth(clientId, params) {
|
|
8265
|
+
params.set("client_id", clientId);
|
|
8266
|
+
}
|
|
8267
|
+
/**
|
|
8268
|
+
* Parses an OAuth error response from a string or Response object.
|
|
8269
|
+
*
|
|
8270
|
+
* If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
|
|
8271
|
+
* and an instance of the appropriate OAuthError subclass will be returned.
|
|
8272
|
+
* If parsing fails, it falls back to a generic ServerError that includes
|
|
8273
|
+
* the response status (if available) and original content.
|
|
8274
|
+
*
|
|
8275
|
+
* @param input - A Response object or string containing the error response
|
|
8276
|
+
* @returns A Promise that resolves to an OAuthError instance
|
|
8277
|
+
*/
|
|
8278
|
+
async function parseErrorResponse(input) {
|
|
8279
|
+
const statusCode = input instanceof Response ? input.status : void 0;
|
|
8280
|
+
const body = input instanceof Response ? await input.text() : input;
|
|
8281
|
+
try {
|
|
8282
|
+
const { error, error_description, error_uri } = OAuthErrorResponseSchema.parse(JSON.parse(body));
|
|
8283
|
+
return new (OAUTH_ERRORS[error] || ServerError)(error_description || "", error_uri);
|
|
8284
|
+
} catch (error) {
|
|
8285
|
+
return new ServerError(`${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error}. Raw body: ${body}`);
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
8288
|
+
/**
|
|
8289
|
+
* Orchestrates the full auth flow with a server.
|
|
8290
|
+
*
|
|
8291
|
+
* This can be used as a single entry point for all authorization functionality,
|
|
8292
|
+
* instead of linking together the other lower-level functions in this module.
|
|
8293
|
+
*/
|
|
8294
|
+
async function auth(provider, options) {
|
|
8295
|
+
try {
|
|
8296
|
+
return await authInternal(provider, options);
|
|
8297
|
+
} catch (error) {
|
|
8298
|
+
if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
|
|
8299
|
+
await provider.invalidateCredentials?.("all");
|
|
8300
|
+
return await authInternal(provider, options);
|
|
8301
|
+
} else if (error instanceof InvalidGrantError) {
|
|
8302
|
+
await provider.invalidateCredentials?.("tokens");
|
|
8303
|
+
return await authInternal(provider, options);
|
|
8304
|
+
}
|
|
8305
|
+
throw error;
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) {
|
|
8309
|
+
const cachedState = await provider.discoveryState?.();
|
|
8310
|
+
let resourceMetadata;
|
|
8311
|
+
let authorizationServerUrl;
|
|
8312
|
+
let metadata;
|
|
8313
|
+
let effectiveResourceMetadataUrl = resourceMetadataUrl;
|
|
8314
|
+
if (!effectiveResourceMetadataUrl && cachedState?.resourceMetadataUrl) effectiveResourceMetadataUrl = new URL(cachedState.resourceMetadataUrl);
|
|
8315
|
+
if (cachedState?.authorizationServerUrl) {
|
|
8316
|
+
authorizationServerUrl = cachedState.authorizationServerUrl;
|
|
8317
|
+
resourceMetadata = cachedState.resourceMetadata;
|
|
8318
|
+
metadata = cachedState.authorizationServerMetadata ?? await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn });
|
|
8319
|
+
if (!resourceMetadata) try {
|
|
8320
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl: effectiveResourceMetadataUrl }, fetchFn);
|
|
8321
|
+
} catch {}
|
|
8322
|
+
if (metadata !== cachedState.authorizationServerMetadata || resourceMetadata !== cachedState.resourceMetadata) await provider.saveDiscoveryState?.({
|
|
8323
|
+
authorizationServerUrl: String(authorizationServerUrl),
|
|
8324
|
+
resourceMetadataUrl: effectiveResourceMetadataUrl?.toString(),
|
|
8325
|
+
resourceMetadata,
|
|
8326
|
+
authorizationServerMetadata: metadata
|
|
8327
|
+
});
|
|
8328
|
+
} else {
|
|
8329
|
+
const serverInfo = await discoverOAuthServerInfo(serverUrl, {
|
|
8330
|
+
resourceMetadataUrl: effectiveResourceMetadataUrl,
|
|
8331
|
+
fetchFn
|
|
8332
|
+
});
|
|
8333
|
+
authorizationServerUrl = serverInfo.authorizationServerUrl;
|
|
8334
|
+
metadata = serverInfo.authorizationServerMetadata;
|
|
8335
|
+
resourceMetadata = serverInfo.resourceMetadata;
|
|
8336
|
+
await provider.saveDiscoveryState?.({
|
|
8337
|
+
authorizationServerUrl: String(authorizationServerUrl),
|
|
8338
|
+
resourceMetadataUrl: effectiveResourceMetadataUrl?.toString(),
|
|
8339
|
+
resourceMetadata,
|
|
8340
|
+
authorizationServerMetadata: metadata
|
|
8341
|
+
});
|
|
8342
|
+
}
|
|
8343
|
+
const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
|
|
8344
|
+
const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
|
|
8345
|
+
let clientInformation = await Promise.resolve(provider.clientInformation());
|
|
8346
|
+
if (!clientInformation) {
|
|
8347
|
+
if (authorizationCode !== void 0) throw new Error("Existing OAuth client information is required when exchanging an authorization code");
|
|
8348
|
+
const supportsUrlBasedClientId = metadata?.client_id_metadata_document_supported === true;
|
|
8349
|
+
const clientMetadataUrl = provider.clientMetadataUrl;
|
|
8350
|
+
if (clientMetadataUrl && !isHttpsUrl(clientMetadataUrl)) throw new InvalidClientMetadataError(`clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: ${clientMetadataUrl}`);
|
|
8351
|
+
if (supportsUrlBasedClientId && clientMetadataUrl) {
|
|
8352
|
+
clientInformation = { client_id: clientMetadataUrl };
|
|
8353
|
+
await provider.saveClientInformation?.(clientInformation);
|
|
8354
|
+
} else {
|
|
8355
|
+
if (!provider.saveClientInformation) throw new Error("OAuth client information must be saveable for dynamic registration");
|
|
8356
|
+
const fullInformation = await registerClient(authorizationServerUrl, {
|
|
8357
|
+
metadata,
|
|
8358
|
+
clientMetadata: provider.clientMetadata,
|
|
8359
|
+
scope: resolvedScope,
|
|
8360
|
+
fetchFn
|
|
8361
|
+
});
|
|
8362
|
+
await provider.saveClientInformation(fullInformation);
|
|
8363
|
+
clientInformation = fullInformation;
|
|
8364
|
+
}
|
|
8365
|
+
}
|
|
8366
|
+
const nonInteractiveFlow = !provider.redirectUrl;
|
|
8367
|
+
if (authorizationCode !== void 0 || nonInteractiveFlow) {
|
|
8368
|
+
const tokens = await fetchToken(provider, authorizationServerUrl, {
|
|
8369
|
+
metadata,
|
|
8370
|
+
resource,
|
|
8371
|
+
authorizationCode,
|
|
8372
|
+
fetchFn
|
|
8373
|
+
});
|
|
8374
|
+
await provider.saveTokens(tokens);
|
|
8375
|
+
return "AUTHORIZED";
|
|
8376
|
+
}
|
|
8377
|
+
const tokens = await provider.tokens();
|
|
8378
|
+
if (tokens?.refresh_token) try {
|
|
8379
|
+
const newTokens = await refreshAuthorization(authorizationServerUrl, {
|
|
8380
|
+
metadata,
|
|
8381
|
+
clientInformation,
|
|
8382
|
+
refreshToken: tokens.refresh_token,
|
|
8383
|
+
resource,
|
|
8384
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
8385
|
+
fetchFn
|
|
8386
|
+
});
|
|
8387
|
+
await provider.saveTokens(newTokens);
|
|
8388
|
+
return "AUTHORIZED";
|
|
8389
|
+
} catch (error) {
|
|
8390
|
+
if (!(error instanceof OAuthError) || error instanceof ServerError) {} else throw error;
|
|
8391
|
+
}
|
|
8392
|
+
const state = provider.state ? await provider.state() : void 0;
|
|
8393
|
+
const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
|
|
8394
|
+
metadata,
|
|
8395
|
+
clientInformation,
|
|
8396
|
+
state,
|
|
8397
|
+
redirectUrl: provider.redirectUrl,
|
|
8398
|
+
scope: resolvedScope,
|
|
8399
|
+
resource
|
|
8400
|
+
});
|
|
8401
|
+
await provider.saveCodeVerifier(codeVerifier);
|
|
8402
|
+
await provider.redirectToAuthorization(authorizationUrl);
|
|
8403
|
+
return "REDIRECT";
|
|
8404
|
+
}
|
|
8405
|
+
/**
|
|
8406
|
+
* SEP-991: URL-based Client IDs
|
|
8407
|
+
* Validate that the client_id is a valid URL with https scheme
|
|
8408
|
+
*/
|
|
8409
|
+
function isHttpsUrl(value) {
|
|
8410
|
+
if (!value) return false;
|
|
8411
|
+
try {
|
|
8412
|
+
const url = new URL(value);
|
|
8413
|
+
return url.protocol === "https:" && url.pathname !== "/";
|
|
8414
|
+
} catch {
|
|
8415
|
+
return false;
|
|
8416
|
+
}
|
|
8417
|
+
}
|
|
8418
|
+
async function selectResourceURL(serverUrl, provider, resourceMetadata) {
|
|
8419
|
+
const defaultResource = resourceUrlFromServerUrl(serverUrl);
|
|
8420
|
+
if (provider.validateResourceURL) return await provider.validateResourceURL(defaultResource, resourceMetadata?.resource);
|
|
8421
|
+
if (!resourceMetadata) return;
|
|
8422
|
+
if (!checkResourceAllowed({
|
|
8423
|
+
requestedResource: defaultResource,
|
|
8424
|
+
configuredResource: resourceMetadata.resource
|
|
8425
|
+
})) throw new Error(`Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin)`);
|
|
8426
|
+
return new URL(resourceMetadata.resource);
|
|
8427
|
+
}
|
|
8428
|
+
/**
|
|
8429
|
+
* Extract resource_metadata, scope, and error from WWW-Authenticate header.
|
|
8430
|
+
*/
|
|
8431
|
+
function extractWWWAuthenticateParams(res) {
|
|
8432
|
+
const authenticateHeader = res.headers.get("WWW-Authenticate");
|
|
8433
|
+
if (!authenticateHeader) return {};
|
|
8434
|
+
const [type, scheme] = authenticateHeader.split(" ");
|
|
8435
|
+
if (type.toLowerCase() !== "bearer" || !scheme) return {};
|
|
8436
|
+
const resourceMetadataMatch = extractFieldFromWwwAuth(res, "resource_metadata") || void 0;
|
|
8437
|
+
let resourceMetadataUrl;
|
|
8438
|
+
if (resourceMetadataMatch) try {
|
|
8439
|
+
resourceMetadataUrl = new URL(resourceMetadataMatch);
|
|
8440
|
+
} catch {}
|
|
8441
|
+
const scope = extractFieldFromWwwAuth(res, "scope") || void 0;
|
|
8442
|
+
const error = extractFieldFromWwwAuth(res, "error") || void 0;
|
|
8443
|
+
return {
|
|
8444
|
+
resourceMetadataUrl,
|
|
8445
|
+
scope,
|
|
8446
|
+
error
|
|
8447
|
+
};
|
|
8448
|
+
}
|
|
8449
|
+
/**
|
|
8450
|
+
* Extracts a specific field's value from the WWW-Authenticate header string.
|
|
8451
|
+
*
|
|
8452
|
+
* @param response The HTTP response object containing the headers.
|
|
8453
|
+
* @param fieldName The name of the field to extract (e.g., "realm", "nonce").
|
|
8454
|
+
* @returns The field value
|
|
8455
|
+
*/
|
|
8456
|
+
function extractFieldFromWwwAuth(response, fieldName) {
|
|
8457
|
+
const wwwAuthHeader = response.headers.get("WWW-Authenticate");
|
|
8458
|
+
if (!wwwAuthHeader) return null;
|
|
8459
|
+
const pattern = new RegExp(`${fieldName}=(?:"([^"]+)"|([^\\s,]+))`);
|
|
8460
|
+
const match = wwwAuthHeader.match(pattern);
|
|
8461
|
+
if (match) return match[1] || match[2];
|
|
8462
|
+
return null;
|
|
8463
|
+
}
|
|
8464
|
+
/**
|
|
8465
|
+
* Looks up RFC 9728 OAuth 2.0 Protected Resource Metadata.
|
|
8466
|
+
*
|
|
8467
|
+
* If the server returns a 404 for the well-known endpoint, this function will
|
|
8468
|
+
* return `undefined`. Any other errors will be thrown as exceptions.
|
|
8469
|
+
*/
|
|
8470
|
+
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
|
|
8471
|
+
const response = await discoverMetadataWithFallback(serverUrl, "oauth-protected-resource", fetchFn, {
|
|
8472
|
+
protocolVersion: opts?.protocolVersion,
|
|
8473
|
+
metadataUrl: opts?.resourceMetadataUrl
|
|
8474
|
+
});
|
|
8475
|
+
if (!response || response.status === 404) {
|
|
8476
|
+
await response?.body?.cancel();
|
|
8477
|
+
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
|
|
8478
|
+
}
|
|
8479
|
+
if (!response.ok) {
|
|
8480
|
+
await response.body?.cancel();
|
|
8481
|
+
throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
|
|
8482
|
+
}
|
|
8483
|
+
return OAuthProtectedResourceMetadataSchema.parse(await response.json());
|
|
8484
|
+
}
|
|
8485
|
+
/**
|
|
8486
|
+
* Helper function to handle fetch with CORS retry logic
|
|
8487
|
+
*/
|
|
8488
|
+
async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
|
|
8489
|
+
try {
|
|
8490
|
+
return await fetchFn(url, { headers });
|
|
8491
|
+
} catch (error) {
|
|
8492
|
+
if (error instanceof TypeError) if (headers) return fetchWithCorsRetry(url, void 0, fetchFn);
|
|
8493
|
+
else return;
|
|
8494
|
+
throw error;
|
|
8495
|
+
}
|
|
8496
|
+
}
|
|
8497
|
+
/**
|
|
8498
|
+
* Constructs the well-known path for auth-related metadata discovery
|
|
8499
|
+
*/
|
|
8500
|
+
function buildWellKnownPath(wellKnownPrefix, pathname = "", options = {}) {
|
|
8501
|
+
if (pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
8502
|
+
return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
|
|
8503
|
+
}
|
|
8504
|
+
/**
|
|
8505
|
+
* Tries to discover OAuth metadata at a specific URL
|
|
8506
|
+
*/
|
|
8507
|
+
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
|
|
8508
|
+
return await fetchWithCorsRetry(url, { "MCP-Protocol-Version": protocolVersion }, fetchFn);
|
|
8509
|
+
}
|
|
8510
|
+
/**
|
|
8511
|
+
* Determines if fallback to root discovery should be attempted
|
|
8512
|
+
*/
|
|
8513
|
+
function shouldAttemptFallback(response, pathname) {
|
|
8514
|
+
return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
|
|
8515
|
+
}
|
|
8516
|
+
/**
|
|
8517
|
+
* Generic function for discovering OAuth metadata with fallback support
|
|
8518
|
+
*/
|
|
8519
|
+
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
|
|
8520
|
+
const issuer = new URL(serverUrl);
|
|
8521
|
+
const protocolVersion = opts?.protocolVersion ?? "2025-11-25";
|
|
8522
|
+
let url;
|
|
8523
|
+
if (opts?.metadataUrl) url = new URL(opts.metadataUrl);
|
|
8524
|
+
else {
|
|
8525
|
+
const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
|
|
8526
|
+
url = new URL(wellKnownPath, opts?.metadataServerUrl ?? issuer);
|
|
8527
|
+
url.search = issuer.search;
|
|
8528
|
+
}
|
|
8529
|
+
let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn);
|
|
8530
|
+
if (!opts?.metadataUrl && shouldAttemptFallback(response, issuer.pathname)) response = await tryMetadataDiscovery(new URL(`/.well-known/${wellKnownType}`, issuer), protocolVersion, fetchFn);
|
|
8531
|
+
return response;
|
|
8532
|
+
}
|
|
8533
|
+
/**
|
|
8534
|
+
* Builds a list of discovery URLs to try for authorization server metadata.
|
|
8535
|
+
* URLs are returned in priority order:
|
|
8536
|
+
* 1. OAuth metadata at the given URL
|
|
8537
|
+
* 2. OIDC metadata endpoints at the given URL
|
|
8538
|
+
*/
|
|
8539
|
+
function buildDiscoveryUrls(authorizationServerUrl) {
|
|
8540
|
+
const url = typeof authorizationServerUrl === "string" ? new URL(authorizationServerUrl) : authorizationServerUrl;
|
|
8541
|
+
const hasPath = url.pathname !== "/";
|
|
8542
|
+
const urlsToTry = [];
|
|
8543
|
+
if (!hasPath) {
|
|
8544
|
+
urlsToTry.push({
|
|
8545
|
+
url: new URL("/.well-known/oauth-authorization-server", url.origin),
|
|
8546
|
+
type: "oauth"
|
|
8547
|
+
});
|
|
8548
|
+
urlsToTry.push({
|
|
8549
|
+
url: new URL(`/.well-known/openid-configuration`, url.origin),
|
|
8550
|
+
type: "oidc"
|
|
8551
|
+
});
|
|
8552
|
+
return urlsToTry;
|
|
8553
|
+
}
|
|
8554
|
+
let pathname = url.pathname;
|
|
8555
|
+
if (pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
8556
|
+
urlsToTry.push({
|
|
8557
|
+
url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url.origin),
|
|
8558
|
+
type: "oauth"
|
|
8559
|
+
});
|
|
8560
|
+
urlsToTry.push({
|
|
8561
|
+
url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
|
|
8562
|
+
type: "oidc"
|
|
8563
|
+
});
|
|
8564
|
+
urlsToTry.push({
|
|
8565
|
+
url: new URL(`${pathname}/.well-known/openid-configuration`, url.origin),
|
|
8566
|
+
type: "oidc"
|
|
8567
|
+
});
|
|
8568
|
+
return urlsToTry;
|
|
8569
|
+
}
|
|
8570
|
+
/**
|
|
8571
|
+
* Discovers authorization server metadata with support for RFC 8414 OAuth 2.0 Authorization Server Metadata
|
|
8572
|
+
* and OpenID Connect Discovery 1.0 specifications.
|
|
8573
|
+
*
|
|
8574
|
+
* This function implements a fallback strategy for authorization server discovery:
|
|
8575
|
+
* 1. Attempts RFC 8414 OAuth metadata discovery first
|
|
8576
|
+
* 2. If OAuth discovery fails, falls back to OpenID Connect Discovery
|
|
8577
|
+
*
|
|
8578
|
+
* @param authorizationServerUrl - The authorization server URL obtained from the MCP Server's
|
|
8579
|
+
* protected resource metadata, or the MCP server's URL if the
|
|
8580
|
+
* metadata was not found.
|
|
8581
|
+
* @param options - Configuration options
|
|
8582
|
+
* @param options.fetchFn - Optional fetch function for making HTTP requests, defaults to global fetch
|
|
8583
|
+
* @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
|
|
8584
|
+
* @returns Promise resolving to authorization server metadata, or undefined if discovery fails
|
|
8585
|
+
*/
|
|
8586
|
+
async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = LATEST_PROTOCOL_VERSION } = {}) {
|
|
8587
|
+
const headers = {
|
|
8588
|
+
"MCP-Protocol-Version": protocolVersion,
|
|
8589
|
+
Accept: "application/json"
|
|
8590
|
+
};
|
|
8591
|
+
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
|
|
8592
|
+
for (const { url: endpointUrl, type } of urlsToTry) {
|
|
8593
|
+
const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
|
|
8594
|
+
if (!response)
|
|
8595
|
+
/**
|
|
8596
|
+
* CORS error occurred - don't throw as the endpoint may not allow CORS,
|
|
8597
|
+
* continue trying other possible endpoints
|
|
8598
|
+
*/
|
|
8599
|
+
continue;
|
|
8600
|
+
if (!response.ok) {
|
|
8601
|
+
await response.body?.cancel();
|
|
8602
|
+
if (response.status >= 400 && response.status < 500) continue;
|
|
8603
|
+
throw new Error(`HTTP ${response.status} trying to load ${type === "oauth" ? "OAuth" : "OpenID provider"} metadata from ${endpointUrl}`);
|
|
8604
|
+
}
|
|
8605
|
+
if (type === "oauth") return OAuthMetadataSchema.parse(await response.json());
|
|
8606
|
+
else return OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
|
|
8607
|
+
}
|
|
8608
|
+
}
|
|
8609
|
+
/**
|
|
8610
|
+
* Discovers the authorization server for an MCP server following
|
|
8611
|
+
* {@link https://datatracker.ietf.org/doc/html/rfc9728 | RFC 9728} (OAuth 2.0 Protected
|
|
8612
|
+
* Resource Metadata), with fallback to treating the server URL as the
|
|
8613
|
+
* authorization server.
|
|
8614
|
+
*
|
|
8615
|
+
* This function combines two discovery steps into one call:
|
|
8616
|
+
* 1. Probes `/.well-known/oauth-protected-resource` on the MCP server to find the
|
|
8617
|
+
* authorization server URL (RFC 9728).
|
|
8618
|
+
* 2. Fetches authorization server metadata from that URL (RFC 8414 / OpenID Connect Discovery).
|
|
8619
|
+
*
|
|
8620
|
+
* Use this when you need the authorization server metadata for operations outside the
|
|
8621
|
+
* {@linkcode auth} orchestrator, such as token refresh or token revocation.
|
|
8622
|
+
*
|
|
8623
|
+
* @param serverUrl - The MCP resource server URL
|
|
8624
|
+
* @param opts - Optional configuration
|
|
8625
|
+
* @param opts.resourceMetadataUrl - Override URL for the protected resource metadata endpoint
|
|
8626
|
+
* @param opts.fetchFn - Custom fetch function for HTTP requests
|
|
8627
|
+
* @returns Authorization server URL, metadata, and resource metadata (if available)
|
|
8628
|
+
*/
|
|
8629
|
+
async function discoverOAuthServerInfo(serverUrl, opts) {
|
|
8630
|
+
let resourceMetadata;
|
|
8631
|
+
let authorizationServerUrl;
|
|
8632
|
+
try {
|
|
8633
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl: opts?.resourceMetadataUrl }, opts?.fetchFn);
|
|
8634
|
+
if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) authorizationServerUrl = resourceMetadata.authorization_servers[0];
|
|
8635
|
+
} catch {}
|
|
8636
|
+
if (!authorizationServerUrl) authorizationServerUrl = String(new URL("/", serverUrl));
|
|
8637
|
+
const authorizationServerMetadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn: opts?.fetchFn });
|
|
8638
|
+
return {
|
|
8639
|
+
authorizationServerUrl,
|
|
8640
|
+
authorizationServerMetadata,
|
|
8641
|
+
resourceMetadata
|
|
8642
|
+
};
|
|
8643
|
+
}
|
|
8644
|
+
/**
|
|
8645
|
+
* Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
|
|
8646
|
+
*/
|
|
8647
|
+
async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource }) {
|
|
8648
|
+
let authorizationUrl;
|
|
8649
|
+
if (metadata) {
|
|
8650
|
+
authorizationUrl = new URL(metadata.authorization_endpoint);
|
|
8651
|
+
if (!metadata.response_types_supported.includes(AUTHORIZATION_CODE_RESPONSE_TYPE)) throw new Error(`Incompatible auth server: does not support response type ${AUTHORIZATION_CODE_RESPONSE_TYPE}`);
|
|
8652
|
+
if (metadata.code_challenge_methods_supported && !metadata.code_challenge_methods_supported.includes(AUTHORIZATION_CODE_CHALLENGE_METHOD)) throw new Error(`Incompatible auth server: does not support code challenge method ${AUTHORIZATION_CODE_CHALLENGE_METHOD}`);
|
|
8653
|
+
} else authorizationUrl = new URL("/authorize", authorizationServerUrl);
|
|
8654
|
+
const challenge = await pkceChallenge();
|
|
8655
|
+
const codeVerifier = challenge.code_verifier;
|
|
8656
|
+
const codeChallenge = challenge.code_challenge;
|
|
8657
|
+
authorizationUrl.searchParams.set("response_type", AUTHORIZATION_CODE_RESPONSE_TYPE);
|
|
8658
|
+
authorizationUrl.searchParams.set("client_id", clientInformation.client_id);
|
|
8659
|
+
authorizationUrl.searchParams.set("code_challenge", codeChallenge);
|
|
8660
|
+
authorizationUrl.searchParams.set("code_challenge_method", AUTHORIZATION_CODE_CHALLENGE_METHOD);
|
|
8661
|
+
authorizationUrl.searchParams.set("redirect_uri", String(redirectUrl));
|
|
8662
|
+
if (state) authorizationUrl.searchParams.set("state", state);
|
|
8663
|
+
if (scope) authorizationUrl.searchParams.set("scope", scope);
|
|
8664
|
+
if (scope?.includes("offline_access")) authorizationUrl.searchParams.append("prompt", "consent");
|
|
8665
|
+
if (resource) authorizationUrl.searchParams.set("resource", resource.href);
|
|
8666
|
+
return {
|
|
8667
|
+
authorizationUrl,
|
|
8668
|
+
codeVerifier
|
|
8669
|
+
};
|
|
8670
|
+
}
|
|
8671
|
+
/**
|
|
8672
|
+
* Prepares token request parameters for an authorization code exchange.
|
|
8673
|
+
*
|
|
8674
|
+
* This is the default implementation used by fetchToken when the provider
|
|
8675
|
+
* doesn't implement prepareTokenRequest.
|
|
8676
|
+
*
|
|
8677
|
+
* @param authorizationCode - The authorization code received from the authorization endpoint
|
|
8678
|
+
* @param codeVerifier - The PKCE code verifier
|
|
8679
|
+
* @param redirectUri - The redirect URI used in the authorization request
|
|
8680
|
+
* @returns URLSearchParams for the authorization_code grant
|
|
8681
|
+
*/
|
|
8682
|
+
function prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri) {
|
|
8683
|
+
return new URLSearchParams({
|
|
8684
|
+
grant_type: "authorization_code",
|
|
8685
|
+
code: authorizationCode,
|
|
8686
|
+
code_verifier: codeVerifier,
|
|
8687
|
+
redirect_uri: String(redirectUri)
|
|
8688
|
+
});
|
|
8689
|
+
}
|
|
8690
|
+
/**
|
|
8691
|
+
* Internal helper to execute a token request with the given parameters.
|
|
8692
|
+
* Used by exchangeAuthorization, refreshAuthorization, and fetchToken.
|
|
8693
|
+
*/
|
|
8694
|
+
async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequestParams, clientInformation, addClientAuthentication, resource, fetchFn }) {
|
|
8695
|
+
const tokenUrl = metadata?.token_endpoint ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl);
|
|
8696
|
+
const headers = new Headers({
|
|
8697
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
8698
|
+
Accept: "application/json"
|
|
8699
|
+
});
|
|
8700
|
+
if (resource) tokenRequestParams.set("resource", resource.href);
|
|
8701
|
+
if (addClientAuthentication) await addClientAuthentication(headers, tokenRequestParams, tokenUrl, metadata);
|
|
8702
|
+
else if (clientInformation) applyClientAuthentication(selectClientAuthMethod(clientInformation, metadata?.token_endpoint_auth_methods_supported ?? []), clientInformation, headers, tokenRequestParams);
|
|
8703
|
+
const response = await (fetchFn ?? fetch)(tokenUrl, {
|
|
8704
|
+
method: "POST",
|
|
8705
|
+
headers,
|
|
8706
|
+
body: tokenRequestParams
|
|
8707
|
+
});
|
|
8708
|
+
if (!response.ok) throw await parseErrorResponse(response);
|
|
8709
|
+
return OAuthTokensSchema.parse(await response.json());
|
|
8710
|
+
}
|
|
8711
|
+
/**
|
|
8712
|
+
* Exchange a refresh token for an updated access token.
|
|
8713
|
+
*
|
|
8714
|
+
* Supports multiple client authentication methods as specified in OAuth 2.1:
|
|
8715
|
+
* - Automatically selects the best authentication method based on server support
|
|
8716
|
+
* - Preserves the original refresh token if a new one is not returned
|
|
8717
|
+
*
|
|
8718
|
+
* @param authorizationServerUrl - The authorization server's base URL
|
|
8719
|
+
* @param options - Configuration object containing client info, refresh token, etc.
|
|
8720
|
+
* @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
|
|
8721
|
+
* @throws {Error} When token refresh fails or authentication is invalid
|
|
8722
|
+
*/
|
|
8723
|
+
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
|
|
8724
|
+
return {
|
|
8725
|
+
refresh_token: refreshToken,
|
|
8726
|
+
...await executeTokenRequest(authorizationServerUrl, {
|
|
8727
|
+
metadata,
|
|
8728
|
+
tokenRequestParams: new URLSearchParams({
|
|
8729
|
+
grant_type: "refresh_token",
|
|
8730
|
+
refresh_token: refreshToken
|
|
8731
|
+
}),
|
|
8732
|
+
clientInformation,
|
|
8733
|
+
addClientAuthentication,
|
|
8734
|
+
resource,
|
|
8735
|
+
fetchFn
|
|
8736
|
+
})
|
|
8737
|
+
};
|
|
8738
|
+
}
|
|
8739
|
+
/**
|
|
8740
|
+
* Unified token fetching that works with any grant type via provider.prepareTokenRequest().
|
|
8741
|
+
*
|
|
8742
|
+
* This function provides a single entry point for obtaining tokens regardless of the
|
|
8743
|
+
* OAuth grant type. The provider's prepareTokenRequest() method determines which grant
|
|
8744
|
+
* to use and supplies the grant-specific parameters.
|
|
8745
|
+
*
|
|
8746
|
+
* @param provider - OAuth client provider that implements prepareTokenRequest()
|
|
8747
|
+
* @param authorizationServerUrl - The authorization server's base URL
|
|
8748
|
+
* @param options - Configuration for the token request
|
|
8749
|
+
* @returns Promise resolving to OAuth tokens
|
|
8750
|
+
* @throws {Error} When provider doesn't implement prepareTokenRequest or token fetch fails
|
|
8751
|
+
*
|
|
8752
|
+
* @example
|
|
8753
|
+
* // Provider for client_credentials:
|
|
8754
|
+
* class MyProvider implements OAuthClientProvider {
|
|
8755
|
+
* prepareTokenRequest(scope) {
|
|
8756
|
+
* const params = new URLSearchParams({ grant_type: 'client_credentials' });
|
|
8757
|
+
* if (scope) params.set('scope', scope);
|
|
8758
|
+
* return params;
|
|
8759
|
+
* }
|
|
8760
|
+
* // ... other methods
|
|
8761
|
+
* }
|
|
8762
|
+
*
|
|
8763
|
+
* const tokens = await fetchToken(provider, authServerUrl, { metadata });
|
|
8764
|
+
*/
|
|
8765
|
+
async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
|
|
8766
|
+
const scope = provider.clientMetadata.scope;
|
|
8767
|
+
let tokenRequestParams;
|
|
8768
|
+
if (provider.prepareTokenRequest) tokenRequestParams = await provider.prepareTokenRequest(scope);
|
|
8769
|
+
if (!tokenRequestParams) {
|
|
8770
|
+
if (!authorizationCode) throw new Error("Either provider.prepareTokenRequest() or authorizationCode is required");
|
|
8771
|
+
if (!provider.redirectUrl) throw new Error("redirectUrl is required for authorization_code flow");
|
|
8772
|
+
tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, await provider.codeVerifier(), provider.redirectUrl);
|
|
8773
|
+
}
|
|
8774
|
+
const clientInformation = await provider.clientInformation();
|
|
8775
|
+
return executeTokenRequest(authorizationServerUrl, {
|
|
8776
|
+
metadata,
|
|
8777
|
+
tokenRequestParams,
|
|
8778
|
+
clientInformation: clientInformation ?? void 0,
|
|
8779
|
+
addClientAuthentication: provider.addClientAuthentication,
|
|
8780
|
+
resource,
|
|
8781
|
+
fetchFn
|
|
8782
|
+
});
|
|
8783
|
+
}
|
|
8784
|
+
/**
|
|
8785
|
+
* Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
|
|
8786
|
+
*
|
|
8787
|
+
* If `scope` is provided, it overrides `clientMetadata.scope` in the registration
|
|
8788
|
+
* request body. This allows callers to apply the Scope Selection Strategy (SEP-835)
|
|
8789
|
+
* consistently across both DCR and the subsequent authorization request.
|
|
8790
|
+
*/
|
|
8791
|
+
async function registerClient(authorizationServerUrl, { metadata, clientMetadata, scope, fetchFn }) {
|
|
8792
|
+
let registrationUrl;
|
|
8793
|
+
if (metadata) {
|
|
8794
|
+
if (!metadata.registration_endpoint) throw new Error("Incompatible auth server: does not support dynamic client registration");
|
|
8795
|
+
registrationUrl = new URL(metadata.registration_endpoint);
|
|
8796
|
+
} else registrationUrl = new URL("/register", authorizationServerUrl);
|
|
8797
|
+
const response = await (fetchFn ?? fetch)(registrationUrl, {
|
|
8798
|
+
method: "POST",
|
|
8799
|
+
headers: { "Content-Type": "application/json" },
|
|
8800
|
+
body: JSON.stringify({
|
|
8801
|
+
...clientMetadata,
|
|
8802
|
+
...scope !== void 0 ? { scope } : {}
|
|
8803
|
+
})
|
|
8804
|
+
});
|
|
8805
|
+
if (!response.ok) throw await parseErrorResponse(response);
|
|
8806
|
+
return OAuthClientInformationFullSchema.parse(await response.json());
|
|
8807
|
+
}
|
|
8808
|
+
//#endregion
|
|
8809
|
+
//#region ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
|
|
8810
|
+
var ParseError = class extends Error {
|
|
8811
|
+
constructor(message, options) {
|
|
8812
|
+
super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
|
|
8813
|
+
}
|
|
8814
|
+
};
|
|
8815
|
+
const LF = 10, CR = 13, SPACE = 32;
|
|
8816
|
+
function noop(_arg) {}
|
|
8817
|
+
function createParser(config) {
|
|
8818
|
+
if (typeof config == "function") throw new TypeError("`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?");
|
|
8819
|
+
const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
|
|
8820
|
+
let pendingFragmentsLength = 0, isFirstChunk = !0, id, data = "", dataLines = 0, eventType, terminated = !1;
|
|
8821
|
+
function feed(chunk) {
|
|
8822
|
+
if (terminated) throw new Error("Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.");
|
|
8823
|
+
if (isFirstChunk && (isFirstChunk = !1, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
|
|
8824
|
+
const trailing2 = processLines(chunk);
|
|
8825
|
+
trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
|
|
8826
|
+
return;
|
|
8827
|
+
}
|
|
8828
|
+
if (chunk.indexOf(`
|
|
8829
|
+
`) === -1 && chunk.indexOf("\r") === -1) {
|
|
8830
|
+
pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
|
|
8831
|
+
return;
|
|
8832
|
+
}
|
|
8833
|
+
pendingFragments.push(chunk);
|
|
8834
|
+
const input = pendingFragments.join("");
|
|
8835
|
+
pendingFragments.length = 0, pendingFragmentsLength = 0;
|
|
8836
|
+
const trailing = processLines(input);
|
|
8837
|
+
trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
|
|
8838
|
+
}
|
|
8839
|
+
function checkBufferSize() {
|
|
8840
|
+
maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = !0, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, { type: "max-buffer-size-exceeded" }))));
|
|
8841
|
+
}
|
|
8842
|
+
function processLines(chunk) {
|
|
8843
|
+
let searchIndex = 0;
|
|
8844
|
+
if (chunk.indexOf("\r") === -1) {
|
|
8845
|
+
let lfIndex = chunk.indexOf(`
|
|
8846
|
+
`, searchIndex);
|
|
8847
|
+
for (; lfIndex !== -1;) {
|
|
8848
|
+
if (searchIndex === lfIndex) {
|
|
8849
|
+
dataLines > 0 && onEvent({
|
|
8850
|
+
id,
|
|
8851
|
+
event: eventType,
|
|
8852
|
+
data
|
|
8853
|
+
}), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
|
|
8854
|
+
`, searchIndex);
|
|
8855
|
+
continue;
|
|
8856
|
+
}
|
|
8857
|
+
const firstCharCode = chunk.charCodeAt(searchIndex);
|
|
8858
|
+
if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
|
|
8859
|
+
const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
|
|
8860
|
+
if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
|
|
8861
|
+
onEvent({
|
|
8862
|
+
id,
|
|
8863
|
+
event: eventType,
|
|
8864
|
+
data: value
|
|
8865
|
+
}), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
|
|
8866
|
+
`, searchIndex);
|
|
8867
|
+
continue;
|
|
8868
|
+
}
|
|
8869
|
+
data = dataLines === 0 ? value : `${data}
|
|
8870
|
+
${value}`, dataLines++;
|
|
8871
|
+
} else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6, lfIndex) || void 0 : parseLine(chunk, searchIndex, lfIndex);
|
|
8872
|
+
searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
|
|
8873
|
+
`, searchIndex);
|
|
8874
|
+
}
|
|
8875
|
+
return chunk.slice(searchIndex);
|
|
8876
|
+
}
|
|
8877
|
+
for (; searchIndex < chunk.length;) {
|
|
8878
|
+
const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
|
|
8879
|
+
`, searchIndex);
|
|
8880
|
+
let lineEnd = -1;
|
|
8881
|
+
if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1) break;
|
|
8882
|
+
parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
|
|
8883
|
+
}
|
|
8884
|
+
return chunk.slice(searchIndex);
|
|
8885
|
+
}
|
|
8886
|
+
function parseLine(chunk, start, end) {
|
|
8887
|
+
if (start === end) {
|
|
8888
|
+
dispatchEvent();
|
|
8889
|
+
return;
|
|
8890
|
+
}
|
|
8891
|
+
const firstCharCode = chunk.charCodeAt(start);
|
|
8892
|
+
if (isDataPrefix(chunk, start, firstCharCode)) {
|
|
8893
|
+
const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
|
|
8894
|
+
data = dataLines === 0 ? value2 : `${data}
|
|
8895
|
+
${value2}`, dataLines++;
|
|
8896
|
+
return;
|
|
8897
|
+
}
|
|
8898
|
+
if (isEventPrefix(chunk, start, firstCharCode)) {
|
|
8899
|
+
eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
|
|
8900
|
+
return;
|
|
8901
|
+
}
|
|
8902
|
+
if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
|
|
8903
|
+
const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
|
|
8904
|
+
id = value2.includes("\0") ? void 0 : value2;
|
|
8905
|
+
return;
|
|
8906
|
+
}
|
|
8907
|
+
if (firstCharCode === 58) {
|
|
8908
|
+
if (onComment) {
|
|
8909
|
+
const line2 = chunk.slice(start, end);
|
|
8910
|
+
onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
|
|
8911
|
+
}
|
|
8912
|
+
return;
|
|
8913
|
+
}
|
|
8914
|
+
const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
|
|
8915
|
+
if (fieldSeparatorIndex === -1) {
|
|
8916
|
+
processField(line, "", line);
|
|
8917
|
+
return;
|
|
8918
|
+
}
|
|
8919
|
+
const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1;
|
|
8920
|
+
processField(field, line.slice(fieldSeparatorIndex + offset), line);
|
|
8921
|
+
}
|
|
8922
|
+
function processField(field, value, line) {
|
|
8923
|
+
switch (field) {
|
|
8924
|
+
case "event":
|
|
8925
|
+
eventType = value || void 0;
|
|
8926
|
+
break;
|
|
8927
|
+
case "data":
|
|
8928
|
+
data = dataLines === 0 ? value : `${data}
|
|
8929
|
+
${value}`, dataLines++;
|
|
8930
|
+
break;
|
|
8931
|
+
case "id":
|
|
8932
|
+
id = value.includes("\0") ? void 0 : value;
|
|
8933
|
+
break;
|
|
8934
|
+
case "retry":
|
|
8935
|
+
/^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(new ParseError(`Invalid \`retry\` value: "${value}"`, {
|
|
8936
|
+
type: "invalid-retry",
|
|
8937
|
+
value,
|
|
8938
|
+
line
|
|
8939
|
+
}));
|
|
8940
|
+
break;
|
|
8941
|
+
default:
|
|
8942
|
+
onError(new ParseError(`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`, {
|
|
8943
|
+
type: "unknown-field",
|
|
8944
|
+
field,
|
|
8945
|
+
value,
|
|
8946
|
+
line
|
|
8947
|
+
}));
|
|
8948
|
+
break;
|
|
8949
|
+
}
|
|
8950
|
+
}
|
|
8951
|
+
function dispatchEvent() {
|
|
8952
|
+
dataLines > 0 && onEvent({
|
|
8953
|
+
id,
|
|
8954
|
+
event: eventType,
|
|
8955
|
+
data
|
|
8956
|
+
}), id = void 0, data = "", dataLines = 0, eventType = void 0;
|
|
8957
|
+
}
|
|
8958
|
+
function reset(options = {}) {
|
|
8959
|
+
if (options.consume && pendingFragments.length > 0) {
|
|
8960
|
+
const incompleteLine = pendingFragments.join("");
|
|
8961
|
+
parseLine(incompleteLine, 0, incompleteLine.length);
|
|
8962
|
+
}
|
|
8963
|
+
isFirstChunk = !0, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = !1;
|
|
8964
|
+
}
|
|
8965
|
+
return {
|
|
8966
|
+
feed,
|
|
8967
|
+
reset
|
|
8968
|
+
};
|
|
8969
|
+
}
|
|
8970
|
+
function isDataPrefix(chunk, i, firstCharCode) {
|
|
8971
|
+
return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
|
|
8972
|
+
}
|
|
8973
|
+
function isEventPrefix(chunk, i, firstCharCode) {
|
|
8974
|
+
return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
|
|
8975
|
+
}
|
|
8976
|
+
//#endregion
|
|
8977
|
+
//#region ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/stream.js
|
|
8978
|
+
var EventSourceParserStream = class extends TransformStream {
|
|
8979
|
+
constructor({ onError, onRetry, onComment, maxBufferSize } = {}) {
|
|
8980
|
+
let parser;
|
|
8981
|
+
super({
|
|
8982
|
+
start(controller) {
|
|
8983
|
+
parser = createParser({
|
|
8984
|
+
onEvent: (event) => {
|
|
8985
|
+
controller.enqueue(event);
|
|
8986
|
+
},
|
|
8987
|
+
onError(error) {
|
|
8988
|
+
typeof onError == "function" && onError(error), (onError === "terminate" || error.type === "max-buffer-size-exceeded") && controller.error(error);
|
|
8989
|
+
},
|
|
8990
|
+
onRetry,
|
|
8991
|
+
onComment,
|
|
8992
|
+
maxBufferSize
|
|
8993
|
+
});
|
|
8994
|
+
},
|
|
8995
|
+
transform(chunk) {
|
|
8996
|
+
parser.feed(chunk);
|
|
8997
|
+
}
|
|
8998
|
+
});
|
|
8999
|
+
}
|
|
9000
|
+
};
|
|
9001
|
+
//#endregion
|
|
9002
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js
|
|
9003
|
+
const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = {
|
|
9004
|
+
initialReconnectionDelay: 1e3,
|
|
9005
|
+
maxReconnectionDelay: 3e4,
|
|
9006
|
+
reconnectionDelayGrowFactor: 1.5,
|
|
9007
|
+
maxRetries: 2
|
|
9008
|
+
};
|
|
9009
|
+
var StreamableHTTPError = class extends Error {
|
|
9010
|
+
constructor(code, message) {
|
|
9011
|
+
super(`Streamable HTTP error: ${message}`);
|
|
9012
|
+
this.code = code;
|
|
9013
|
+
}
|
|
9014
|
+
};
|
|
9015
|
+
/**
|
|
9016
|
+
* Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
|
|
9017
|
+
* It will connect to a server using HTTP POST for sending messages and HTTP GET with Server-Sent Events
|
|
9018
|
+
* for receiving messages.
|
|
9019
|
+
*/
|
|
9020
|
+
var StreamableHTTPClientTransport = class {
|
|
9021
|
+
constructor(url, opts) {
|
|
9022
|
+
this._hasCompletedAuthFlow = false;
|
|
9023
|
+
this._url = url;
|
|
9024
|
+
this._resourceMetadataUrl = void 0;
|
|
9025
|
+
this._scope = void 0;
|
|
9026
|
+
this._requestInit = opts?.requestInit;
|
|
9027
|
+
this._authProvider = opts?.authProvider;
|
|
9028
|
+
this._fetch = opts?.fetch;
|
|
9029
|
+
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
|
|
9030
|
+
this._sessionId = opts?.sessionId;
|
|
9031
|
+
this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
|
|
9032
|
+
}
|
|
9033
|
+
async _authThenStart() {
|
|
9034
|
+
if (!this._authProvider) throw new UnauthorizedError("No auth provider");
|
|
9035
|
+
let result;
|
|
9036
|
+
try {
|
|
9037
|
+
result = await auth(this._authProvider, {
|
|
9038
|
+
serverUrl: this._url,
|
|
9039
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
9040
|
+
scope: this._scope,
|
|
9041
|
+
fetchFn: this._fetchWithInit
|
|
9042
|
+
});
|
|
9043
|
+
} catch (error) {
|
|
9044
|
+
this.onerror?.(error);
|
|
9045
|
+
throw error;
|
|
9046
|
+
}
|
|
9047
|
+
if (result !== "AUTHORIZED") throw new UnauthorizedError();
|
|
9048
|
+
return await this._startOrAuthSse({ resumptionToken: void 0 });
|
|
9049
|
+
}
|
|
9050
|
+
async _commonHeaders() {
|
|
9051
|
+
const headers = {};
|
|
9052
|
+
if (this._authProvider) {
|
|
9053
|
+
const tokens = await this._authProvider.tokens();
|
|
9054
|
+
if (tokens) headers["Authorization"] = `Bearer ${tokens.access_token}`;
|
|
9055
|
+
}
|
|
9056
|
+
if (this._sessionId) headers["mcp-session-id"] = this._sessionId;
|
|
9057
|
+
if (this._protocolVersion) headers["mcp-protocol-version"] = this._protocolVersion;
|
|
9058
|
+
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
|
|
9059
|
+
return new Headers({
|
|
9060
|
+
...headers,
|
|
9061
|
+
...extraHeaders
|
|
9062
|
+
});
|
|
9063
|
+
}
|
|
9064
|
+
async _startOrAuthSse(options) {
|
|
9065
|
+
const { resumptionToken } = options;
|
|
9066
|
+
try {
|
|
9067
|
+
const headers = await this._commonHeaders();
|
|
9068
|
+
headers.set("Accept", "text/event-stream");
|
|
9069
|
+
if (resumptionToken) headers.set("last-event-id", resumptionToken);
|
|
9070
|
+
const response = await (this._fetch ?? fetch)(this._url, {
|
|
9071
|
+
method: "GET",
|
|
9072
|
+
headers,
|
|
9073
|
+
signal: this._abortController?.signal
|
|
9074
|
+
});
|
|
9075
|
+
if (!response.ok) {
|
|
9076
|
+
await response.body?.cancel();
|
|
9077
|
+
if (response.status === 401 && this._authProvider) return await this._authThenStart();
|
|
9078
|
+
if (response.status === 405) return;
|
|
9079
|
+
throw new StreamableHTTPError(response.status, `Failed to open SSE stream: ${response.statusText}`);
|
|
9080
|
+
}
|
|
9081
|
+
this._handleSseStream(response.body, options, true);
|
|
9082
|
+
} catch (error) {
|
|
9083
|
+
this.onerror?.(error);
|
|
9084
|
+
throw error;
|
|
9085
|
+
}
|
|
9086
|
+
}
|
|
9087
|
+
/**
|
|
9088
|
+
* Calculates the next reconnection delay using backoff algorithm
|
|
9089
|
+
*
|
|
9090
|
+
* @param attempt Current reconnection attempt count for the specific stream
|
|
9091
|
+
* @returns Time to wait in milliseconds before next reconnection attempt
|
|
9092
|
+
*/
|
|
9093
|
+
_getNextReconnectionDelay(attempt) {
|
|
9094
|
+
if (this._serverRetryMs !== void 0) return this._serverRetryMs;
|
|
9095
|
+
const initialDelay = this._reconnectionOptions.initialReconnectionDelay;
|
|
9096
|
+
const growFactor = this._reconnectionOptions.reconnectionDelayGrowFactor;
|
|
9097
|
+
const maxDelay = this._reconnectionOptions.maxReconnectionDelay;
|
|
9098
|
+
return Math.min(initialDelay * Math.pow(growFactor, attempt), maxDelay);
|
|
9099
|
+
}
|
|
9100
|
+
/**
|
|
9101
|
+
* Schedule a reconnection attempt using server-provided retry interval or backoff
|
|
9102
|
+
*
|
|
9103
|
+
* @param lastEventId The ID of the last received event for resumability
|
|
9104
|
+
* @param attemptCount Current reconnection attempt count for this specific stream
|
|
9105
|
+
*/
|
|
9106
|
+
_scheduleReconnection(options, attemptCount = 0) {
|
|
9107
|
+
const maxRetries = this._reconnectionOptions.maxRetries;
|
|
9108
|
+
if (attemptCount >= maxRetries) {
|
|
9109
|
+
this.onerror?.(/* @__PURE__ */ new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
|
|
9110
|
+
return;
|
|
9111
|
+
}
|
|
9112
|
+
const delay = this._getNextReconnectionDelay(attemptCount);
|
|
9113
|
+
this._reconnectionTimeout = setTimeout(() => {
|
|
9114
|
+
this._startOrAuthSse(options).catch((error) => {
|
|
9115
|
+
this.onerror?.(/* @__PURE__ */ new Error(`Failed to reconnect SSE stream: ${error instanceof Error ? error.message : String(error)}`));
|
|
9116
|
+
this._scheduleReconnection(options, attemptCount + 1);
|
|
9117
|
+
});
|
|
9118
|
+
}, delay);
|
|
9119
|
+
}
|
|
9120
|
+
_handleSseStream(stream, options, isReconnectable) {
|
|
9121
|
+
if (!stream) return;
|
|
9122
|
+
const { onresumptiontoken, replayMessageId } = options;
|
|
9123
|
+
let lastEventId;
|
|
9124
|
+
let hasPrimingEvent = false;
|
|
9125
|
+
let receivedResponse = false;
|
|
9126
|
+
const processStream = async () => {
|
|
9127
|
+
try {
|
|
9128
|
+
const reader = stream.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream({ onRetry: (retryMs) => {
|
|
9129
|
+
this._serverRetryMs = retryMs;
|
|
9130
|
+
} })).getReader();
|
|
9131
|
+
while (true) {
|
|
9132
|
+
const { value: event, done } = await reader.read();
|
|
9133
|
+
if (done) break;
|
|
9134
|
+
if (event.id) {
|
|
9135
|
+
lastEventId = event.id;
|
|
9136
|
+
hasPrimingEvent = true;
|
|
9137
|
+
onresumptiontoken?.(event.id);
|
|
9138
|
+
}
|
|
9139
|
+
if (!event.data) continue;
|
|
9140
|
+
if (!event.event || event.event === "message") try {
|
|
9141
|
+
const message = JSONRPCMessageSchema.parse(JSON.parse(event.data));
|
|
9142
|
+
if (isJSONRPCResultResponse(message)) {
|
|
9143
|
+
receivedResponse = true;
|
|
9144
|
+
if (replayMessageId !== void 0) message.id = replayMessageId;
|
|
9145
|
+
}
|
|
9146
|
+
this.onmessage?.(message);
|
|
9147
|
+
} catch (error) {
|
|
9148
|
+
this.onerror?.(error);
|
|
9149
|
+
}
|
|
9150
|
+
}
|
|
9151
|
+
if ((isReconnectable || hasPrimingEvent) && !receivedResponse && this._abortController && !this._abortController.signal.aborted) this._scheduleReconnection({
|
|
9152
|
+
resumptionToken: lastEventId,
|
|
9153
|
+
onresumptiontoken,
|
|
9154
|
+
replayMessageId
|
|
9155
|
+
}, 0);
|
|
9156
|
+
} catch (error) {
|
|
9157
|
+
this.onerror?.(/* @__PURE__ */ new Error(`SSE stream disconnected: ${error}`));
|
|
9158
|
+
if ((isReconnectable || hasPrimingEvent) && !receivedResponse && this._abortController && !this._abortController.signal.aborted) try {
|
|
9159
|
+
this._scheduleReconnection({
|
|
9160
|
+
resumptionToken: lastEventId,
|
|
9161
|
+
onresumptiontoken,
|
|
9162
|
+
replayMessageId
|
|
9163
|
+
}, 0);
|
|
9164
|
+
} catch (error) {
|
|
9165
|
+
this.onerror?.(/* @__PURE__ */ new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
|
|
9166
|
+
}
|
|
9167
|
+
}
|
|
9168
|
+
};
|
|
9169
|
+
processStream();
|
|
9170
|
+
}
|
|
9171
|
+
async start() {
|
|
9172
|
+
if (this._abortController) throw new Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
9173
|
+
this._abortController = new AbortController();
|
|
9174
|
+
}
|
|
9175
|
+
/**
|
|
9176
|
+
* Call this method after the user has finished authorizing via their user agent and is redirected back to the MCP client application. This will exchange the authorization code for an access token, enabling the next connection attempt to successfully auth.
|
|
9177
|
+
*/
|
|
9178
|
+
async finishAuth(authorizationCode) {
|
|
9179
|
+
if (!this._authProvider) throw new UnauthorizedError("No auth provider");
|
|
9180
|
+
if (await auth(this._authProvider, {
|
|
9181
|
+
serverUrl: this._url,
|
|
9182
|
+
authorizationCode,
|
|
9183
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
9184
|
+
scope: this._scope,
|
|
9185
|
+
fetchFn: this._fetchWithInit
|
|
9186
|
+
}) !== "AUTHORIZED") throw new UnauthorizedError("Failed to authorize");
|
|
9187
|
+
}
|
|
9188
|
+
async close() {
|
|
9189
|
+
if (this._reconnectionTimeout) {
|
|
9190
|
+
clearTimeout(this._reconnectionTimeout);
|
|
9191
|
+
this._reconnectionTimeout = void 0;
|
|
9192
|
+
}
|
|
9193
|
+
this._abortController?.abort();
|
|
9194
|
+
this.onclose?.();
|
|
9195
|
+
}
|
|
9196
|
+
async send(message, options) {
|
|
9197
|
+
try {
|
|
9198
|
+
const { resumptionToken, onresumptiontoken } = options || {};
|
|
9199
|
+
if (resumptionToken) {
|
|
9200
|
+
this._startOrAuthSse({
|
|
9201
|
+
resumptionToken,
|
|
9202
|
+
replayMessageId: isJSONRPCRequest(message) ? message.id : void 0
|
|
9203
|
+
}).catch((err) => this.onerror?.(err));
|
|
9204
|
+
return;
|
|
9205
|
+
}
|
|
9206
|
+
const headers = await this._commonHeaders();
|
|
9207
|
+
headers.set("content-type", "application/json");
|
|
9208
|
+
headers.set("accept", "application/json, text/event-stream");
|
|
9209
|
+
const init = {
|
|
9210
|
+
...this._requestInit,
|
|
9211
|
+
method: "POST",
|
|
9212
|
+
headers,
|
|
9213
|
+
body: JSON.stringify(message),
|
|
9214
|
+
signal: this._abortController?.signal
|
|
9215
|
+
};
|
|
9216
|
+
const response = await (this._fetch ?? fetch)(this._url, init);
|
|
9217
|
+
const sessionId = response.headers.get("mcp-session-id");
|
|
9218
|
+
if (sessionId) this._sessionId = sessionId;
|
|
9219
|
+
if (!response.ok) {
|
|
9220
|
+
const text = await response.text().catch(() => null);
|
|
9221
|
+
if (response.status === 401 && this._authProvider) {
|
|
9222
|
+
if (this._hasCompletedAuthFlow) throw new StreamableHTTPError(401, "Server returned 401 after successful authentication");
|
|
9223
|
+
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
|
|
9224
|
+
this._resourceMetadataUrl = resourceMetadataUrl;
|
|
9225
|
+
this._scope = scope;
|
|
9226
|
+
if (await auth(this._authProvider, {
|
|
9227
|
+
serverUrl: this._url,
|
|
9228
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
9229
|
+
scope: this._scope,
|
|
9230
|
+
fetchFn: this._fetchWithInit
|
|
9231
|
+
}) !== "AUTHORIZED") throw new UnauthorizedError();
|
|
9232
|
+
this._hasCompletedAuthFlow = true;
|
|
9233
|
+
return this.send(message);
|
|
9234
|
+
}
|
|
9235
|
+
if (response.status === 403 && this._authProvider) {
|
|
9236
|
+
const { resourceMetadataUrl, scope, error } = extractWWWAuthenticateParams(response);
|
|
9237
|
+
if (error === "insufficient_scope") {
|
|
9238
|
+
const wwwAuthHeader = response.headers.get("WWW-Authenticate");
|
|
9239
|
+
if (this._lastUpscopingHeader === wwwAuthHeader) throw new StreamableHTTPError(403, "Server returned 403 after trying upscoping");
|
|
9240
|
+
if (scope) this._scope = scope;
|
|
9241
|
+
if (resourceMetadataUrl) this._resourceMetadataUrl = resourceMetadataUrl;
|
|
9242
|
+
this._lastUpscopingHeader = wwwAuthHeader ?? void 0;
|
|
9243
|
+
if (await auth(this._authProvider, {
|
|
9244
|
+
serverUrl: this._url,
|
|
9245
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
9246
|
+
scope: this._scope,
|
|
9247
|
+
fetchFn: this._fetch
|
|
9248
|
+
}) !== "AUTHORIZED") throw new UnauthorizedError();
|
|
9249
|
+
return this.send(message);
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
|
|
9253
|
+
}
|
|
9254
|
+
this._hasCompletedAuthFlow = false;
|
|
9255
|
+
this._lastUpscopingHeader = void 0;
|
|
9256
|
+
if (response.status === 202) {
|
|
9257
|
+
await response.body?.cancel();
|
|
9258
|
+
if (isInitializedNotification(message)) this._startOrAuthSse({ resumptionToken: void 0 }).catch((err) => this.onerror?.(err));
|
|
9259
|
+
return;
|
|
9260
|
+
}
|
|
9261
|
+
const hasRequests = (Array.isArray(message) ? message : [message]).filter((msg) => "method" in msg && "id" in msg && msg.id !== void 0).length > 0;
|
|
9262
|
+
const contentType = response.headers.get("content-type");
|
|
9263
|
+
if (hasRequests) if (contentType?.includes("text/event-stream")) this._handleSseStream(response.body, { onresumptiontoken }, false);
|
|
9264
|
+
else if (contentType?.includes("application/json")) {
|
|
9265
|
+
const data = await response.json();
|
|
9266
|
+
const responseMessages = Array.isArray(data) ? data.map((msg) => JSONRPCMessageSchema.parse(msg)) : [JSONRPCMessageSchema.parse(data)];
|
|
9267
|
+
for (const msg of responseMessages) this.onmessage?.(msg);
|
|
9268
|
+
} else {
|
|
9269
|
+
await response.body?.cancel();
|
|
9270
|
+
throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
|
|
9271
|
+
}
|
|
9272
|
+
else await response.body?.cancel();
|
|
9273
|
+
} catch (error) {
|
|
9274
|
+
this.onerror?.(error);
|
|
9275
|
+
throw error;
|
|
9276
|
+
}
|
|
9277
|
+
}
|
|
9278
|
+
get sessionId() {
|
|
9279
|
+
return this._sessionId;
|
|
9280
|
+
}
|
|
9281
|
+
/**
|
|
9282
|
+
* Terminates the current session by sending a DELETE request to the server.
|
|
9283
|
+
*
|
|
9284
|
+
* Clients that no longer need a particular session
|
|
9285
|
+
* (e.g., because the user is leaving the client application) SHOULD send an
|
|
9286
|
+
* HTTP DELETE to the MCP endpoint with the Mcp-Session-Id header to explicitly
|
|
9287
|
+
* terminate the session.
|
|
9288
|
+
*
|
|
9289
|
+
* The server MAY respond with HTTP 405 Method Not Allowed, indicating that
|
|
9290
|
+
* the server does not allow clients to terminate sessions.
|
|
9291
|
+
*/
|
|
9292
|
+
async terminateSession() {
|
|
9293
|
+
if (!this._sessionId) return;
|
|
9294
|
+
try {
|
|
9295
|
+
const headers = await this._commonHeaders();
|
|
9296
|
+
const init = {
|
|
9297
|
+
...this._requestInit,
|
|
9298
|
+
method: "DELETE",
|
|
9299
|
+
headers,
|
|
9300
|
+
signal: this._abortController?.signal
|
|
9301
|
+
};
|
|
9302
|
+
const response = await (this._fetch ?? fetch)(this._url, init);
|
|
9303
|
+
await response.body?.cancel();
|
|
9304
|
+
if (!response.ok && response.status !== 405) throw new StreamableHTTPError(response.status, `Failed to terminate session: ${response.statusText}`);
|
|
9305
|
+
this._sessionId = void 0;
|
|
9306
|
+
} catch (error) {
|
|
9307
|
+
this.onerror?.(error);
|
|
9308
|
+
throw error;
|
|
9309
|
+
}
|
|
9310
|
+
}
|
|
9311
|
+
setProtocolVersion(version) {
|
|
9312
|
+
this._protocolVersion = version;
|
|
9313
|
+
}
|
|
9314
|
+
get protocolVersion() {
|
|
9315
|
+
return this._protocolVersion;
|
|
9316
|
+
}
|
|
9317
|
+
/**
|
|
9318
|
+
* Resume an SSE stream from a previous event ID.
|
|
9319
|
+
* Opens a GET SSE connection with Last-Event-ID header to replay missed events.
|
|
9320
|
+
*
|
|
9321
|
+
* @param lastEventId The event ID to resume from
|
|
9322
|
+
* @param options Optional callback to receive new resumption tokens
|
|
9323
|
+
*/
|
|
9324
|
+
async resumeStream(lastEventId, options) {
|
|
9325
|
+
await this._startOrAuthSse({
|
|
9326
|
+
resumptionToken: lastEventId,
|
|
9327
|
+
onresumptiontoken: options?.onresumptiontoken
|
|
9328
|
+
});
|
|
9329
|
+
}
|
|
9330
|
+
};
|
|
9331
|
+
//#endregion
|
|
9332
|
+
//#region ../../node_modules/.pnpm/eventsource@3.0.7/node_modules/eventsource/dist/index.js
|
|
9333
|
+
var ErrorEvent = class extends Event {
|
|
9334
|
+
/**
|
|
9335
|
+
* Constructs a new `ErrorEvent` instance. This is typically not called directly,
|
|
9336
|
+
* but rather emitted by the `EventSource` object when an error occurs.
|
|
9337
|
+
*
|
|
9338
|
+
* @param type - The type of the event (should be "error")
|
|
9339
|
+
* @param errorEventInitDict - Optional properties to include in the error event
|
|
9340
|
+
*/
|
|
9341
|
+
constructor(type, errorEventInitDict) {
|
|
9342
|
+
var _a, _b;
|
|
9343
|
+
super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0;
|
|
9344
|
+
}
|
|
9345
|
+
/**
|
|
9346
|
+
* Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance,
|
|
9347
|
+
* when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
|
|
9348
|
+
* we explicitly include the properties in the `inspect` method.
|
|
9349
|
+
*
|
|
9350
|
+
* This is automatically called by Node.js when you `console.log` an instance of this class.
|
|
9351
|
+
*
|
|
9352
|
+
* @param _depth - The current depth
|
|
9353
|
+
* @param options - The options passed to `util.inspect`
|
|
9354
|
+
* @param inspect - The inspect function to use (prevents having to import it from `util`)
|
|
9355
|
+
* @returns A string representation of the error
|
|
9356
|
+
*/
|
|
9357
|
+
[Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) {
|
|
9358
|
+
return inspect(inspectableError(this), options);
|
|
9359
|
+
}
|
|
9360
|
+
/**
|
|
9361
|
+
* Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance,
|
|
9362
|
+
* when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
|
|
9363
|
+
* we explicitly include the properties in the `inspect` method.
|
|
9364
|
+
*
|
|
9365
|
+
* This is automatically called by Deno when you `console.log` an instance of this class.
|
|
9366
|
+
*
|
|
9367
|
+
* @param inspect - The inspect function to use (prevents having to import it from `util`)
|
|
9368
|
+
* @param options - The options passed to `Deno.inspect`
|
|
9369
|
+
* @returns A string representation of the error
|
|
9370
|
+
*/
|
|
9371
|
+
[Symbol.for("Deno.customInspect")](inspect, options) {
|
|
9372
|
+
return inspect(inspectableError(this), options);
|
|
6154
9373
|
}
|
|
9374
|
+
};
|
|
9375
|
+
function syntaxError(message) {
|
|
9376
|
+
const DomException = globalThis.DOMException;
|
|
9377
|
+
return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message);
|
|
6155
9378
|
}
|
|
6156
|
-
function
|
|
6157
|
-
|
|
6158
|
-
if (typeof v !== "string" || v.length === 0) throw new Error(`missing required string argument: ${key}`);
|
|
6159
|
-
return v;
|
|
9379
|
+
function flattenError(err) {
|
|
9380
|
+
return err instanceof Error ? "errors" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(", ") : "cause" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`;
|
|
6160
9381
|
}
|
|
6161
|
-
function
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
9382
|
+
function inspectableError(err) {
|
|
9383
|
+
return {
|
|
9384
|
+
type: err.type,
|
|
9385
|
+
message: err.message,
|
|
9386
|
+
code: err.code,
|
|
9387
|
+
defaultPrevented: err.defaultPrevented,
|
|
9388
|
+
cancelable: err.cancelable,
|
|
9389
|
+
timeStamp: err.timeStamp
|
|
9390
|
+
};
|
|
6166
9391
|
}
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
var
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
9392
|
+
var __typeError = (msg) => {
|
|
9393
|
+
throw TypeError(msg);
|
|
9394
|
+
}, __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg), __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)), __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value), __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value), __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method), _readyState, _url, _redirectUrl, _withCredentials, _fetch, _reconnectInterval, _reconnectTimer, _lastEventId, _controller, _parser, _onError, _onMessage, _onOpen, _EventSource_instances, connect_fn, _onFetchResponse, _onFetchError, getRequestOptions_fn, _onEvent, _onRetryChange, failConnection_fn, scheduleReconnect_fn, _reconnect;
|
|
9395
|
+
var EventSource = class extends EventTarget {
|
|
9396
|
+
constructor(url, eventSourceInitDict) {
|
|
9397
|
+
var _a, _b;
|
|
9398
|
+
super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => {
|
|
9399
|
+
var _a2;
|
|
9400
|
+
__privateGet(this, _parser).reset();
|
|
9401
|
+
const { body, redirected, status, headers } = response;
|
|
9402
|
+
if (status === 204) {
|
|
9403
|
+
__privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close();
|
|
9404
|
+
return;
|
|
9405
|
+
}
|
|
9406
|
+
if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {
|
|
9407
|
+
__privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);
|
|
9408
|
+
return;
|
|
9409
|
+
}
|
|
9410
|
+
if (!(headers.get("content-type") || "").startsWith("text/event-stream")) {
|
|
9411
|
+
__privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid content type, expected \"text/event-stream\"", status);
|
|
9412
|
+
return;
|
|
9413
|
+
}
|
|
9414
|
+
if (__privateGet(this, _readyState) === this.CLOSED) return;
|
|
9415
|
+
__privateSet(this, _readyState, this.OPEN);
|
|
9416
|
+
const openEvent = new Event("open");
|
|
9417
|
+
if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) {
|
|
9418
|
+
__privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close();
|
|
9419
|
+
return;
|
|
9420
|
+
}
|
|
9421
|
+
const decoder = new TextDecoder(), reader = body.getReader();
|
|
9422
|
+
let open = !0;
|
|
9423
|
+
do {
|
|
9424
|
+
const { done, value } = await reader.read();
|
|
9425
|
+
value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = !1, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
|
|
9426
|
+
} while (open);
|
|
9427
|
+
}), __privateAdd(this, _onFetchError, (err) => {
|
|
9428
|
+
__privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
|
|
9429
|
+
}), __privateAdd(this, _onEvent, (event) => {
|
|
9430
|
+
typeof event.id == "string" && __privateSet(this, _lastEventId, event.id);
|
|
9431
|
+
const messageEvent = new MessageEvent(event.event || "message", {
|
|
9432
|
+
data: event.data,
|
|
9433
|
+
origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,
|
|
9434
|
+
lastEventId: event.id || ""
|
|
9435
|
+
});
|
|
9436
|
+
__privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);
|
|
9437
|
+
}), __privateAdd(this, _onRetryChange, (value) => {
|
|
9438
|
+
__privateSet(this, _reconnectInterval, value);
|
|
9439
|
+
}), __privateAdd(this, _reconnect, () => {
|
|
9440
|
+
__privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);
|
|
9441
|
+
});
|
|
9442
|
+
try {
|
|
9443
|
+
if (url instanceof URL) __privateSet(this, _url, url);
|
|
9444
|
+
else if (typeof url == "string") __privateSet(this, _url, new URL(url, getBaseURL()));
|
|
9445
|
+
else throw new Error("Invalid URL");
|
|
9446
|
+
} catch {
|
|
9447
|
+
throw syntaxError("An invalid or illegal string was specified");
|
|
9448
|
+
}
|
|
9449
|
+
__privateSet(this, _parser, createParser({
|
|
9450
|
+
onEvent: __privateGet(this, _onEvent),
|
|
9451
|
+
onRetry: __privateGet(this, _onRetryChange)
|
|
9452
|
+
})), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : !1), __privateMethod(this, _EventSource_instances, connect_fn).call(this);
|
|
6181
9453
|
}
|
|
6182
9454
|
/**
|
|
6183
|
-
*
|
|
6184
|
-
*
|
|
6185
|
-
*
|
|
9455
|
+
* Returns the state of this EventSource object's connection. It can have the values described below.
|
|
9456
|
+
*
|
|
9457
|
+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)
|
|
9458
|
+
*
|
|
9459
|
+
* Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,
|
|
9460
|
+
* defined in the TypeScript `dom` library.
|
|
9461
|
+
*
|
|
9462
|
+
* @public
|
|
6186
9463
|
*/
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
this.sessions.set(token, ctx);
|
|
6190
|
-
return token;
|
|
9464
|
+
get readyState() {
|
|
9465
|
+
return __privateGet(this, _readyState);
|
|
6191
9466
|
}
|
|
6192
|
-
|
|
6193
|
-
|
|
9467
|
+
/**
|
|
9468
|
+
* Returns the URL providing the event stream.
|
|
9469
|
+
*
|
|
9470
|
+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)
|
|
9471
|
+
*
|
|
9472
|
+
* @public
|
|
9473
|
+
*/
|
|
9474
|
+
get url() {
|
|
9475
|
+
return __privateGet(this, _url).href;
|
|
6194
9476
|
}
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
9477
|
+
/**
|
|
9478
|
+
* Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise.
|
|
9479
|
+
*
|
|
9480
|
+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)
|
|
9481
|
+
*/
|
|
9482
|
+
get withCredentials() {
|
|
9483
|
+
return __privateGet(this, _withCredentials);
|
|
9484
|
+
}
|
|
9485
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */
|
|
9486
|
+
get onerror() {
|
|
9487
|
+
return __privateGet(this, _onError);
|
|
9488
|
+
}
|
|
9489
|
+
set onerror(value) {
|
|
9490
|
+
__privateSet(this, _onError, value);
|
|
9491
|
+
}
|
|
9492
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */
|
|
9493
|
+
get onmessage() {
|
|
9494
|
+
return __privateGet(this, _onMessage);
|
|
9495
|
+
}
|
|
9496
|
+
set onmessage(value) {
|
|
9497
|
+
__privateSet(this, _onMessage, value);
|
|
9498
|
+
}
|
|
9499
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
|
|
9500
|
+
get onopen() {
|
|
9501
|
+
return __privateGet(this, _onOpen);
|
|
9502
|
+
}
|
|
9503
|
+
set onopen(value) {
|
|
9504
|
+
__privateSet(this, _onOpen, value);
|
|
9505
|
+
}
|
|
9506
|
+
addEventListener(type, listener, options) {
|
|
9507
|
+
const listen = listener;
|
|
9508
|
+
super.addEventListener(type, listen, options);
|
|
9509
|
+
}
|
|
9510
|
+
removeEventListener(type, listener, options) {
|
|
9511
|
+
const listen = listener;
|
|
9512
|
+
super.removeEventListener(type, listen, options);
|
|
9513
|
+
}
|
|
9514
|
+
/**
|
|
9515
|
+
* Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
|
|
9516
|
+
*
|
|
9517
|
+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
|
|
9518
|
+
*
|
|
9519
|
+
* @public
|
|
9520
|
+
*/
|
|
9521
|
+
close() {
|
|
9522
|
+
__privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0));
|
|
9523
|
+
}
|
|
9524
|
+
};
|
|
9525
|
+
_readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), connect_fn = function() {
|
|
9526
|
+
__privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError));
|
|
9527
|
+
}, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), getRequestOptions_fn = function() {
|
|
9528
|
+
var _a;
|
|
9529
|
+
const init = {
|
|
9530
|
+
mode: "cors",
|
|
9531
|
+
redirect: "follow",
|
|
9532
|
+
headers: {
|
|
9533
|
+
Accept: "text/event-stream",
|
|
9534
|
+
...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0
|
|
9535
|
+
},
|
|
9536
|
+
cache: "no-store",
|
|
9537
|
+
signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
|
|
9538
|
+
};
|
|
9539
|
+
return "window" in globalThis && (init.credentials = this.withCredentials ? "include" : "same-origin"), init;
|
|
9540
|
+
}, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), failConnection_fn = function(message, code) {
|
|
9541
|
+
var _a;
|
|
9542
|
+
__privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
|
|
9543
|
+
const errorEvent = new ErrorEvent("error", {
|
|
9544
|
+
code,
|
|
9545
|
+
message
|
|
9546
|
+
});
|
|
9547
|
+
(_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
|
|
9548
|
+
}, scheduleReconnect_fn = function(message, code) {
|
|
9549
|
+
var _a;
|
|
9550
|
+
if (__privateGet(this, _readyState) === this.CLOSED) return;
|
|
9551
|
+
__privateSet(this, _readyState, this.CONNECTING);
|
|
9552
|
+
const errorEvent = new ErrorEvent("error", {
|
|
9553
|
+
code,
|
|
9554
|
+
message
|
|
9555
|
+
});
|
|
9556
|
+
(_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
|
|
9557
|
+
}, _reconnect = /* @__PURE__ */ new WeakMap(), EventSource.CONNECTING = 0, EventSource.OPEN = 1, EventSource.CLOSED = 2;
|
|
9558
|
+
function getBaseURL() {
|
|
9559
|
+
const doc = "document" in globalThis ? globalThis.document : void 0;
|
|
9560
|
+
return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
|
|
9561
|
+
}
|
|
9562
|
+
//#endregion
|
|
9563
|
+
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js
|
|
9564
|
+
var SseError = class extends Error {
|
|
9565
|
+
constructor(code, message, event) {
|
|
9566
|
+
super(`SSE error: ${message}`);
|
|
9567
|
+
this.code = code;
|
|
9568
|
+
this.event = event;
|
|
9569
|
+
}
|
|
9570
|
+
};
|
|
9571
|
+
/**
|
|
9572
|
+
* Client transport for SSE: this will connect to a server using Server-Sent Events for receiving
|
|
9573
|
+
* messages and make separate POST requests for sending messages.
|
|
9574
|
+
* @deprecated SSEClientTransport is deprecated. Prefer to use StreamableHTTPClientTransport where possible instead. Note that because some servers are still using SSE, clients may need to support both transports during the migration period.
|
|
9575
|
+
*/
|
|
9576
|
+
var SSEClientTransport = class {
|
|
9577
|
+
constructor(url, opts) {
|
|
9578
|
+
this._url = url;
|
|
9579
|
+
this._resourceMetadataUrl = void 0;
|
|
9580
|
+
this._scope = void 0;
|
|
9581
|
+
this._eventSourceInit = opts?.eventSourceInit;
|
|
9582
|
+
this._requestInit = opts?.requestInit;
|
|
9583
|
+
this._authProvider = opts?.authProvider;
|
|
9584
|
+
this._fetch = opts?.fetch;
|
|
9585
|
+
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
|
|
9586
|
+
}
|
|
9587
|
+
async _authThenStart() {
|
|
9588
|
+
if (!this._authProvider) throw new UnauthorizedError("No auth provider");
|
|
9589
|
+
let result;
|
|
6203
9590
|
try {
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
await new Promise((resolve, reject) => {
|
|
6210
|
-
const onStartupError = (err) => reject(err);
|
|
6211
|
-
server.once("error", onStartupError);
|
|
6212
|
-
server.listen(path, () => {
|
|
6213
|
-
server.off("error", onStartupError);
|
|
6214
|
-
server.on("error", (err) => this.deps.log?.error(`mcp: control server error: ${err.message}`));
|
|
6215
|
-
resolve();
|
|
9591
|
+
result = await auth(this._authProvider, {
|
|
9592
|
+
serverUrl: this._url,
|
|
9593
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
9594
|
+
scope: this._scope,
|
|
9595
|
+
fetchFn: this._fetchWithInit
|
|
6216
9596
|
});
|
|
9597
|
+
} catch (error) {
|
|
9598
|
+
this.onerror?.(error);
|
|
9599
|
+
throw error;
|
|
9600
|
+
}
|
|
9601
|
+
if (result !== "AUTHORIZED") throw new UnauthorizedError();
|
|
9602
|
+
return await this._startOrAuth();
|
|
9603
|
+
}
|
|
9604
|
+
async _commonHeaders() {
|
|
9605
|
+
const headers = {};
|
|
9606
|
+
if (this._authProvider) {
|
|
9607
|
+
const tokens = await this._authProvider.tokens();
|
|
9608
|
+
if (tokens) headers["Authorization"] = `Bearer ${tokens.access_token}`;
|
|
9609
|
+
}
|
|
9610
|
+
if (this._protocolVersion) headers["mcp-protocol-version"] = this._protocolVersion;
|
|
9611
|
+
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
|
|
9612
|
+
return new Headers({
|
|
9613
|
+
...headers,
|
|
9614
|
+
...extraHeaders
|
|
6217
9615
|
});
|
|
6218
|
-
try {
|
|
6219
|
-
chmodSync(path, 384);
|
|
6220
|
-
} catch {}
|
|
6221
|
-
this.deps.log?.info(`mcp: control socket listening at ${path}`);
|
|
6222
9616
|
}
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
9617
|
+
_startOrAuth() {
|
|
9618
|
+
const fetchImpl = this?._eventSourceInit?.fetch ?? this._fetch ?? fetch;
|
|
9619
|
+
return new Promise((resolve, reject) => {
|
|
9620
|
+
this._eventSource = new EventSource(this._url.href, {
|
|
9621
|
+
...this._eventSourceInit,
|
|
9622
|
+
fetch: async (url, init) => {
|
|
9623
|
+
const headers = await this._commonHeaders();
|
|
9624
|
+
headers.set("Accept", "text/event-stream");
|
|
9625
|
+
const response = await fetchImpl(url, {
|
|
9626
|
+
...init,
|
|
9627
|
+
headers
|
|
9628
|
+
});
|
|
9629
|
+
if (response.status === 401 && response.headers.has("www-authenticate")) {
|
|
9630
|
+
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
|
|
9631
|
+
this._resourceMetadataUrl = resourceMetadataUrl;
|
|
9632
|
+
this._scope = scope;
|
|
9633
|
+
}
|
|
9634
|
+
return response;
|
|
9635
|
+
}
|
|
9636
|
+
});
|
|
9637
|
+
this._abortController = new AbortController();
|
|
9638
|
+
this._eventSource.onerror = (event) => {
|
|
9639
|
+
if (event.code === 401 && this._authProvider) {
|
|
9640
|
+
this._authThenStart().then(resolve, reject);
|
|
9641
|
+
return;
|
|
9642
|
+
}
|
|
9643
|
+
const error = new SseError(event.code, event.message, event);
|
|
9644
|
+
reject(error);
|
|
9645
|
+
this.onerror?.(error);
|
|
9646
|
+
};
|
|
9647
|
+
this._eventSource.onopen = () => {};
|
|
9648
|
+
this._eventSource.addEventListener("endpoint", (event) => {
|
|
9649
|
+
const messageEvent = event;
|
|
9650
|
+
try {
|
|
9651
|
+
this._endpoint = new URL(messageEvent.data, this._url);
|
|
9652
|
+
if (this._endpoint.origin !== this._url.origin) throw new Error(`Endpoint origin does not match connection origin: ${this._endpoint.origin}`);
|
|
9653
|
+
} catch (error) {
|
|
9654
|
+
reject(error);
|
|
9655
|
+
this.onerror?.(error);
|
|
9656
|
+
this.close();
|
|
9657
|
+
return;
|
|
9658
|
+
}
|
|
9659
|
+
resolve();
|
|
9660
|
+
});
|
|
9661
|
+
this._eventSource.onmessage = (event) => {
|
|
9662
|
+
const messageEvent = event;
|
|
9663
|
+
let message;
|
|
9664
|
+
try {
|
|
9665
|
+
message = JSONRPCMessageSchema.parse(JSON.parse(messageEvent.data));
|
|
9666
|
+
} catch (error) {
|
|
9667
|
+
this.onerror?.(error);
|
|
9668
|
+
return;
|
|
9669
|
+
}
|
|
9670
|
+
this.onmessage?.(message);
|
|
9671
|
+
};
|
|
6232
9672
|
});
|
|
6233
|
-
socket.on("error", (err) => this.deps.log?.debug(`mcp: socket error: ${err.message}`));
|
|
6234
|
-
socket.on("close", () => this.conns.delete(socket));
|
|
6235
9673
|
}
|
|
6236
|
-
async
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
9674
|
+
async start() {
|
|
9675
|
+
if (this._eventSource) throw new Error("SSEClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
9676
|
+
return await this._startOrAuth();
|
|
9677
|
+
}
|
|
9678
|
+
/**
|
|
9679
|
+
* Call this method after the user has finished authorizing via their user agent and is redirected back to the MCP client application. This will exchange the authorization code for an access token, enabling the next connection attempt to successfully auth.
|
|
9680
|
+
*/
|
|
9681
|
+
async finishAuth(authorizationCode) {
|
|
9682
|
+
if (!this._authProvider) throw new UnauthorizedError("No auth provider");
|
|
9683
|
+
if (await auth(this._authProvider, {
|
|
9684
|
+
serverUrl: this._url,
|
|
9685
|
+
authorizationCode,
|
|
9686
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
9687
|
+
scope: this._scope,
|
|
9688
|
+
fetchFn: this._fetchWithInit
|
|
9689
|
+
}) !== "AUTHORIZED") throw new UnauthorizedError("Failed to authorize");
|
|
9690
|
+
}
|
|
9691
|
+
async close() {
|
|
9692
|
+
this._abortController?.abort();
|
|
9693
|
+
this._eventSource?.close();
|
|
9694
|
+
this.onclose?.();
|
|
9695
|
+
}
|
|
9696
|
+
async send(message) {
|
|
9697
|
+
if (!this._endpoint) throw new Error("Not connected");
|
|
6246
9698
|
try {
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
9699
|
+
const headers = await this._commonHeaders();
|
|
9700
|
+
headers.set("content-type", "application/json");
|
|
9701
|
+
const init = {
|
|
9702
|
+
...this._requestInit,
|
|
9703
|
+
method: "POST",
|
|
9704
|
+
headers,
|
|
9705
|
+
body: JSON.stringify(message),
|
|
9706
|
+
signal: this._abortController?.signal
|
|
9707
|
+
};
|
|
9708
|
+
const response = await (this._fetch ?? fetch)(this._endpoint, init);
|
|
9709
|
+
if (!response.ok) {
|
|
9710
|
+
const text = await response.text().catch(() => null);
|
|
9711
|
+
if (response.status === 401 && this._authProvider) {
|
|
9712
|
+
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
|
|
9713
|
+
this._resourceMetadataUrl = resourceMetadataUrl;
|
|
9714
|
+
this._scope = scope;
|
|
9715
|
+
if (await auth(this._authProvider, {
|
|
9716
|
+
serverUrl: this._url,
|
|
9717
|
+
resourceMetadataUrl: this._resourceMetadataUrl,
|
|
9718
|
+
scope: this._scope,
|
|
9719
|
+
fetchFn: this._fetchWithInit
|
|
9720
|
+
}) !== "AUTHORIZED") throw new UnauthorizedError();
|
|
9721
|
+
return this.send(message);
|
|
9722
|
+
}
|
|
9723
|
+
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
|
|
6254
9724
|
}
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
result
|
|
6260
|
-
});
|
|
6261
|
-
} catch (err) {
|
|
6262
|
-
reply({
|
|
6263
|
-
id: req.id,
|
|
6264
|
-
ok: false,
|
|
6265
|
-
error: err.message
|
|
6266
|
-
});
|
|
9725
|
+
await response.body?.cancel();
|
|
9726
|
+
} catch (error) {
|
|
9727
|
+
this.onerror?.(error);
|
|
9728
|
+
throw error;
|
|
6267
9729
|
}
|
|
6268
9730
|
}
|
|
6269
|
-
|
|
6270
|
-
this.
|
|
6271
|
-
const server = this.server;
|
|
6272
|
-
if (!server) return;
|
|
6273
|
-
this.server = void 0;
|
|
6274
|
-
for (const s of this.conns) s.destroy();
|
|
6275
|
-
this.conns.clear();
|
|
6276
|
-
await new Promise((resolve) => server.close(() => resolve()));
|
|
6277
|
-
rmSync(this.deps.socketPath, { force: true });
|
|
9731
|
+
setProtocolVersion(version) {
|
|
9732
|
+
this._protocolVersion = version;
|
|
6278
9733
|
}
|
|
6279
9734
|
};
|
|
6280
9735
|
//#endregion
|
|
6281
|
-
//#region src/mcp/
|
|
9736
|
+
//#region src/mcp/mcp-prober.ts
|
|
9737
|
+
const DEFAULT_TIMEOUT_MS$1 = 15e3;
|
|
9738
|
+
const DEFAULT_CONCURRENCY$1 = 3;
|
|
9739
|
+
function envRecord(pairs) {
|
|
9740
|
+
return Object.fromEntries(pairs.map((e) => [e.name, e.value]));
|
|
9741
|
+
}
|
|
9742
|
+
function buildTransport(def) {
|
|
9743
|
+
switch (def.transport) {
|
|
9744
|
+
case "stdio": return new StdioClientTransport({
|
|
9745
|
+
command: def.command,
|
|
9746
|
+
args: def.args,
|
|
9747
|
+
env: {
|
|
9748
|
+
...getDefaultEnvironment(),
|
|
9749
|
+
...envRecord(def.env)
|
|
9750
|
+
},
|
|
9751
|
+
stderr: "ignore"
|
|
9752
|
+
});
|
|
9753
|
+
case "http": return new StreamableHTTPClientTransport(new URL(def.url), { requestInit: { headers: envRecord(def.headers) } });
|
|
9754
|
+
case "sse": return new SSEClientTransport(new URL(def.url), { requestInit: { headers: envRecord(def.headers) } });
|
|
9755
|
+
}
|
|
9756
|
+
}
|
|
9757
|
+
async function connectReal(name, def) {
|
|
9758
|
+
const client = new Client({
|
|
9759
|
+
name: "agentconnect",
|
|
9760
|
+
version: DAEMON_VERSION
|
|
9761
|
+
});
|
|
9762
|
+
await client.connect(buildTransport(def));
|
|
9763
|
+
return {
|
|
9764
|
+
listTools: async () => (await client.listTools()).tools.map((t) => t.name),
|
|
9765
|
+
serverVersion: () => client.getServerVersion()?.version,
|
|
9766
|
+
close: () => client.close()
|
|
9767
|
+
};
|
|
9768
|
+
}
|
|
6282
9769
|
/**
|
|
6283
|
-
*
|
|
6284
|
-
*
|
|
6285
|
-
* `
|
|
6286
|
-
*
|
|
6287
|
-
* only the harness-spawned subprocess, never the model.
|
|
9770
|
+
* Probe one configured MCP server. Never throws: any failure (spawn error, bad
|
|
9771
|
+
* URL, handshake failure, timeout) is captured in `{ ok: false, error }`. On
|
|
9772
|
+
* success, `tools` reflects whatever `tools/list` returned — an empty list is
|
|
9773
|
+
* not a failure.
|
|
6288
9774
|
*/
|
|
6289
|
-
function
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
9775
|
+
async function probeMcpServer(name, def, opts = {}) {
|
|
9776
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
|
|
9777
|
+
let timer;
|
|
9778
|
+
let timedOut = false;
|
|
9779
|
+
const timeout = new Promise((_, reject) => {
|
|
9780
|
+
timer = setTimeout(() => {
|
|
9781
|
+
timedOut = true;
|
|
9782
|
+
reject(/* @__PURE__ */ new Error(`probe timed out after ${timeoutMs}ms`));
|
|
9783
|
+
}, timeoutMs);
|
|
9784
|
+
});
|
|
9785
|
+
let conn;
|
|
9786
|
+
const run = async () => {
|
|
9787
|
+
conn = await (opts.connectFactory ?? connectReal)(name, def);
|
|
9788
|
+
return {
|
|
9789
|
+
tools: await conn.listTools(),
|
|
9790
|
+
version: conn.serverVersion()
|
|
9791
|
+
};
|
|
9792
|
+
};
|
|
9793
|
+
const pending = run();
|
|
9794
|
+
try {
|
|
9795
|
+
const { tools, version } = await Promise.race([pending, timeout]);
|
|
9796
|
+
opts.log?.info(`mcp-probe: ${name} ok (tools: ${tools.length ? tools.join(", ") : "none"})`);
|
|
9797
|
+
return {
|
|
9798
|
+
name,
|
|
9799
|
+
transport: def.transport,
|
|
9800
|
+
ok: true,
|
|
9801
|
+
tools,
|
|
9802
|
+
...version !== void 0 ? { version } : {}
|
|
9803
|
+
};
|
|
9804
|
+
} catch (err) {
|
|
9805
|
+
const error = err.message;
|
|
9806
|
+
opts.log?.warn(`mcp-probe: ${name} failed — ${error}`);
|
|
9807
|
+
return {
|
|
9808
|
+
name,
|
|
9809
|
+
transport: def.transport,
|
|
9810
|
+
ok: false,
|
|
9811
|
+
tools: [],
|
|
9812
|
+
error
|
|
9813
|
+
};
|
|
9814
|
+
} finally {
|
|
9815
|
+
if (timer) clearTimeout(timer);
|
|
9816
|
+
const closeConn = () => conn?.close().catch(() => {});
|
|
9817
|
+
if (timedOut) pending.then(closeConn, closeConn);
|
|
9818
|
+
else await closeConn();
|
|
9819
|
+
}
|
|
9820
|
+
}
|
|
9821
|
+
/**
|
|
9822
|
+
* Probe every configured MCP server with bounded concurrency. Result order is
|
|
9823
|
+
* not meaningful — callers key by `result.name`.
|
|
9824
|
+
*/
|
|
9825
|
+
async function probeAllMcpServers(defs, opts = {}) {
|
|
9826
|
+
const names = Object.keys(defs);
|
|
9827
|
+
if (names.length === 0) return [];
|
|
9828
|
+
const limit = Math.max(1, opts.concurrency ?? DEFAULT_CONCURRENCY$1);
|
|
9829
|
+
const results = [];
|
|
9830
|
+
let next = 0;
|
|
9831
|
+
const worker = async () => {
|
|
9832
|
+
for (let i = next++; i < names.length; i = next++) {
|
|
9833
|
+
const name = names[i];
|
|
9834
|
+
results.push(await probeMcpServer(name, defs[name], opts));
|
|
9835
|
+
}
|
|
9836
|
+
};
|
|
9837
|
+
await Promise.all(Array.from({ length: Math.min(limit, names.length) }, () => worker()));
|
|
9838
|
+
return results;
|
|
9839
|
+
}
|
|
9840
|
+
//#endregion
|
|
9841
|
+
//#region src/mcp/resolve-servers.ts
|
|
9842
|
+
/**
|
|
9843
|
+
* Resolve an agent's enabled MCP-server names against the daemon's configured
|
|
9844
|
+
* definitions, producing the ACP `McpServer` entries to attach at
|
|
9845
|
+
* session/new|load (after the daemon's own bridge entry).
|
|
9846
|
+
*
|
|
9847
|
+
* Skips (with a warn) rather than fails: an unknown name, the reserved bridge
|
|
9848
|
+
* name, and an http/sse server the agent's runtime is KNOWN not to accept
|
|
9849
|
+
* (`caps` from the runtime probe). When `caps` is undefined (runtime not probed
|
|
9850
|
+
* yet) http/sse entries are included optimistically — the runtime rejecting an
|
|
9851
|
+
* unsupported transport at session/new is a clearer failure than silently
|
|
9852
|
+
* withholding a server the user enabled.
|
|
9853
|
+
*/
|
|
9854
|
+
function resolveAgentMcpServers(opts) {
|
|
9855
|
+
const out = [];
|
|
9856
|
+
for (const name of opts.enabled) {
|
|
9857
|
+
if (name === "agentconnect") {
|
|
9858
|
+
opts.warn?.(`mcp: server name "${name}" is reserved for the daemon bridge — skipped`);
|
|
9859
|
+
continue;
|
|
9860
|
+
}
|
|
9861
|
+
const def = opts.defs[name];
|
|
9862
|
+
if (!def) {
|
|
9863
|
+
opts.warn?.(`mcp: server "${name}" is not configured on this daemon — skipped`);
|
|
9864
|
+
continue;
|
|
9865
|
+
}
|
|
9866
|
+
if (def.transport === "stdio") {
|
|
9867
|
+
out.push({
|
|
9868
|
+
name,
|
|
9869
|
+
command: def.command,
|
|
9870
|
+
args: def.args,
|
|
9871
|
+
env: def.env
|
|
9872
|
+
});
|
|
9873
|
+
continue;
|
|
9874
|
+
}
|
|
9875
|
+
if (opts.caps && !opts.caps[def.transport]) {
|
|
9876
|
+
opts.warn?.(`mcp: server "${name}" needs the ${def.transport} transport, which the runtime doesn't accept — skipped`);
|
|
9877
|
+
continue;
|
|
9878
|
+
}
|
|
9879
|
+
out.push({
|
|
9880
|
+
type: def.transport,
|
|
9881
|
+
name,
|
|
9882
|
+
url: def.url,
|
|
9883
|
+
headers: def.headers
|
|
9884
|
+
});
|
|
9885
|
+
}
|
|
9886
|
+
return out;
|
|
6306
9887
|
}
|
|
6307
9888
|
//#endregion
|
|
6308
9889
|
//#region src/mcp/tools.ts
|
|
@@ -6377,13 +9958,48 @@ const SLACK_TOOLS = [
|
|
|
6377
9958
|
}, ["url"])
|
|
6378
9959
|
}
|
|
6379
9960
|
];
|
|
6380
|
-
SLACK_TOOLS.map((t) => t.name);
|
|
6381
9961
|
/**
|
|
6382
|
-
* The
|
|
6383
|
-
*
|
|
9962
|
+
* The agent's long-term memory tools — available to EVERY agent regardless of
|
|
9963
|
+
* integrations. Memory is a directory (`<agent-root>/memory/`) with a `MEMORY.md`
|
|
9964
|
+
* index (injected into the prompt each session) plus `<topic>.md` files the agent
|
|
9965
|
+
* reads on demand. Keep the index short; move detail into topic files.
|
|
9966
|
+
*/
|
|
9967
|
+
const MEMORY_TOOLS = [
|
|
9968
|
+
{
|
|
9969
|
+
name: "listMemory",
|
|
9970
|
+
description: "List your memory files — the MEMORY.md index plus any topic files you have written. Use this to discover what you have stored before reading or updating it.",
|
|
9971
|
+
inputSchema: obj({})
|
|
9972
|
+
},
|
|
9973
|
+
{
|
|
9974
|
+
name: "readMemory",
|
|
9975
|
+
description: "Read one of your memory files. Omit `path` (or pass \"MEMORY.md\") to read the index; pass a topic file name (e.g. \"deploys.md\") to read that topic. The index is already shown to you at the start of each session — use this to pull the detail behind an index entry when it is relevant.",
|
|
9976
|
+
inputSchema: obj({ path: {
|
|
9977
|
+
type: "string",
|
|
9978
|
+
description: "Memory file name (e.g. \"deploys.md\"). Defaults to the MEMORY.md index."
|
|
9979
|
+
} })
|
|
9980
|
+
},
|
|
9981
|
+
{
|
|
9982
|
+
name: "writeMemory",
|
|
9983
|
+
description: "Save one of your memory files (whole-file overwrite). Omit `path` to write the MEMORY.md index; pass a topic file name (e.g. \"deploys.md\") for a topic. Keep the INDEX short — a scannable list that links to topic files (e.g. \"- [deploys](deploys.md) — how we ship\"); put the actual detail in the topic files. Use for durable facts across sessions: conventions, decisions, who to ask, things you had to re-learn. A flat directory — no subfolders in the path.",
|
|
9984
|
+
inputSchema: obj({
|
|
9985
|
+
path: {
|
|
9986
|
+
type: "string",
|
|
9987
|
+
description: "Memory file name (e.g. \"deploys.md\"). Defaults to the MEMORY.md index. No subdirectories."
|
|
9988
|
+
},
|
|
9989
|
+
content: {
|
|
9990
|
+
type: "string",
|
|
9991
|
+
description: "The full new file contents, in Markdown. Replaces the file."
|
|
9992
|
+
}
|
|
9993
|
+
}, ["content"])
|
|
9994
|
+
}
|
|
9995
|
+
];
|
|
9996
|
+
[...SLACK_TOOLS, ...MEMORY_TOOLS].map((t) => t.name);
|
|
9997
|
+
/**
|
|
9998
|
+
* The default MCP tool set for an agent. The memory tools are always present
|
|
9999
|
+
* (every agent has memory); Slack tools are gated by a Slack integration.
|
|
6384
10000
|
*/
|
|
6385
10001
|
function toolsForIntegrations(integrations) {
|
|
6386
|
-
const tools = [];
|
|
10002
|
+
const tools = [...MEMORY_TOOLS];
|
|
6387
10003
|
if (integrations.some((i) => i.platform === "slack")) tools.push(...SLACK_TOOLS);
|
|
6388
10004
|
return tools;
|
|
6389
10005
|
}
|
|
@@ -63012,6 +66628,7 @@ const STATUS_ACTION = {
|
|
|
63012
66628
|
manage: "ac_manage",
|
|
63013
66629
|
setModel: "ac_set_model",
|
|
63014
66630
|
setEffort: "ac_set_effort",
|
|
66631
|
+
setPermissionMode: "ac_set_permission_mode",
|
|
63015
66632
|
setFast: "ac_set_fast",
|
|
63016
66633
|
setOutput: "ac_set_output",
|
|
63017
66634
|
cancel: "ac_cancel",
|
|
@@ -63095,6 +66712,34 @@ function buildStatusModal(info, sessionKey, link) {
|
|
|
63095
66712
|
text: `*Model:* ${info.model}`
|
|
63096
66713
|
}
|
|
63097
66714
|
});
|
|
66715
|
+
const permissionModes = info.permissionModes ?? [];
|
|
66716
|
+
if (permissionModes.length > 0) {
|
|
66717
|
+
const opts = info.permissionMode && !permissionModes.includes(info.permissionMode) ? [info.permissionMode, ...permissionModes] : permissionModes;
|
|
66718
|
+
const option = (m) => ({
|
|
66719
|
+
text: {
|
|
66720
|
+
type: "plain_text",
|
|
66721
|
+
text: clampTo(m, 75)
|
|
66722
|
+
},
|
|
66723
|
+
value: m
|
|
66724
|
+
});
|
|
66725
|
+
blocks.push({
|
|
66726
|
+
type: "section",
|
|
66727
|
+
text: {
|
|
66728
|
+
type: "mrkdwn",
|
|
66729
|
+
text: "*Permission mode*"
|
|
66730
|
+
},
|
|
66731
|
+
accessory: {
|
|
66732
|
+
type: "static_select",
|
|
66733
|
+
action_id: STATUS_ACTION.setPermissionMode,
|
|
66734
|
+
placeholder: {
|
|
66735
|
+
type: "plain_text",
|
|
66736
|
+
text: "Permission mode"
|
|
66737
|
+
},
|
|
66738
|
+
...info.permissionMode ? { initial_option: option(info.permissionMode) } : {},
|
|
66739
|
+
options: opts.map(option)
|
|
66740
|
+
}
|
|
66741
|
+
});
|
|
66742
|
+
}
|
|
63098
66743
|
const efforts = info.efforts ?? [];
|
|
63099
66744
|
if (efforts.length > 0) {
|
|
63100
66745
|
const opts = info.effort && !efforts.includes(info.effort) ? [info.effort, ...efforts] : efforts;
|
|
@@ -63459,6 +67104,7 @@ var SlackConnection = class {
|
|
|
63459
67104
|
deps;
|
|
63460
67105
|
app;
|
|
63461
67106
|
queue;
|
|
67107
|
+
assistantDmThreads = /* @__PURE__ */ new Map();
|
|
63462
67108
|
botUserId = "";
|
|
63463
67109
|
/** The appToken this socket is keyed by (one socket per unique appToken). */
|
|
63464
67110
|
appToken;
|
|
@@ -63499,8 +67145,8 @@ var SlackConnection = class {
|
|
|
63499
67145
|
this.workspaceUrl = auth.url ?? "";
|
|
63500
67146
|
log?.debug(`slack: auth.test ok → bot user ${this.botUserId} (bot_id ${this.botId || "n/a"})`);
|
|
63501
67147
|
const deliver = (ev, kind) => {
|
|
63502
|
-
const msg = normalizeSlackEvent(ev, { traceId: this.deps.newTraceId() });
|
|
63503
|
-
log?.debug(`slack: inbound ${kind} ch=${msg.channel} user=${msg.sender.id} isBot=${msg.sender.isBot} isDm=${msg.isDm} mentions=[${msg.mentionedBots.join(",")}] text=${JSON.stringify(msg.text.slice(0, 80))}`);
|
|
67148
|
+
const msg = normalizeSlackEvent(this.withAssistantThread(ev), { traceId: this.deps.newTraceId() });
|
|
67149
|
+
log?.debug(`slack: inbound ${kind} ch=${msg.channel} thread=${msg.thread ?? "none"} user=${msg.sender.id} isBot=${msg.sender.isBot} isDm=${msg.isDm} mentions=[${msg.mentionedBots.join(",")}] text=${JSON.stringify(msg.text.slice(0, 80))}`);
|
|
63504
67150
|
this.deps.onMessage(msg);
|
|
63505
67151
|
};
|
|
63506
67152
|
this.app.message(async ({ message }) => {
|
|
@@ -63516,6 +67162,10 @@ var SlackConnection = class {
|
|
|
63516
67162
|
if (!ev.channel) return;
|
|
63517
67163
|
deliver(ev, "app_mention");
|
|
63518
67164
|
});
|
|
67165
|
+
this.app.event("assistant_thread_started", async ({ event }) => {
|
|
67166
|
+
const thread = this.rememberAssistantThread(event);
|
|
67167
|
+
if (thread) log?.debug(`slack: assistant thread started ch=${thread.channel} thread=${thread.threadTs}`);
|
|
67168
|
+
});
|
|
63519
67169
|
this.app.event("member_joined_channel", async ({ event }) => {
|
|
63520
67170
|
const ev = event;
|
|
63521
67171
|
if (ev.user !== this.botUserId) return;
|
|
@@ -63562,6 +67212,16 @@ var SlackConnection = class {
|
|
|
63562
67212
|
effort
|
|
63563
67213
|
});
|
|
63564
67214
|
});
|
|
67215
|
+
this.app.action(STATUS_ACTION.setPermissionMode, async ({ ack, action, body }) => {
|
|
67216
|
+
await ack();
|
|
67217
|
+
const sessionKey = body?.view?.private_metadata ?? action.block_id;
|
|
67218
|
+
const permissionMode = action.selected_option?.value;
|
|
67219
|
+
if (sessionKey && permissionMode) this.deps.onStatusAction?.({
|
|
67220
|
+
kind: "set-permission-mode",
|
|
67221
|
+
sessionKey,
|
|
67222
|
+
permissionMode
|
|
67223
|
+
});
|
|
67224
|
+
});
|
|
63565
67225
|
this.app.action(STATUS_ACTION.setFast, async ({ ack, action, body }) => {
|
|
63566
67226
|
await ack();
|
|
63567
67227
|
const sessionKey = body?.view?.private_metadata ?? action.block_id;
|
|
@@ -63597,6 +67257,25 @@ var SlackConnection = class {
|
|
|
63597
67257
|
await this.app.start();
|
|
63598
67258
|
log?.debug("slack: app.start resolved → socket established");
|
|
63599
67259
|
}
|
|
67260
|
+
rememberAssistantThread(ev) {
|
|
67261
|
+
const channel = ev.assistant_thread?.channel_id;
|
|
67262
|
+
const threadTs = ev.assistant_thread?.thread_ts;
|
|
67263
|
+
if (!channel || !threadTs) return null;
|
|
67264
|
+
this.assistantDmThreads.set(channel, threadTs);
|
|
67265
|
+
return {
|
|
67266
|
+
channel,
|
|
67267
|
+
threadTs
|
|
67268
|
+
};
|
|
67269
|
+
}
|
|
67270
|
+
withAssistantThread(ev) {
|
|
67271
|
+
if (ev.thread_ts || !ev.channel) return ev;
|
|
67272
|
+
if (ev.channel_type !== "im" && !ev.channel.startsWith("D")) return ev;
|
|
67273
|
+
const threadTs = this.assistantDmThreads.get(ev.channel);
|
|
67274
|
+
return threadTs ? {
|
|
67275
|
+
...ev,
|
|
67276
|
+
thread_ts: threadTs
|
|
67277
|
+
} : ev;
|
|
67278
|
+
}
|
|
63600
67279
|
async postMessage(channel, text, threadTs) {
|
|
63601
67280
|
return this.queue.enqueue(async () => {
|
|
63602
67281
|
return (await this.app.client.chat.postMessage({
|
|
@@ -64772,13 +68451,15 @@ async function probeRuntime(id, rt, cwd, opts = {}) {
|
|
|
64772
68451
|
const opt = await Promise.race([run(), timeout]);
|
|
64773
68452
|
const models = (opt?.models ?? []).filter((m) => m !== "default");
|
|
64774
68453
|
const acpProtocolVersion = host.acpProtocolVersion();
|
|
68454
|
+
const mcpCapabilities = host.mcpCapabilities?.() ?? void 0;
|
|
64775
68455
|
opts.log?.info(`probe: ${id} ok (models: ${models.length ? models.join(", ") : "none advertised"})`);
|
|
64776
68456
|
return {
|
|
64777
68457
|
runtime: id,
|
|
64778
68458
|
ok: true,
|
|
64779
68459
|
models,
|
|
64780
68460
|
currentModel: opt?.current,
|
|
64781
|
-
acpProtocolVersion
|
|
68461
|
+
acpProtocolVersion,
|
|
68462
|
+
mcpCapabilities
|
|
64782
68463
|
};
|
|
64783
68464
|
} catch (err) {
|
|
64784
68465
|
const error = err.message;
|
|
@@ -64852,6 +68533,50 @@ function makeLogger(level = "info") {
|
|
|
64852
68533
|
};
|
|
64853
68534
|
}
|
|
64854
68535
|
//#endregion
|
|
68536
|
+
//#region src/cp/wire-slice.ts
|
|
68537
|
+
/**
|
|
68538
|
+
* Pure helpers for slicing file bytes into a single WS frame — shared by the
|
|
68539
|
+
* workspace and memory readers so both honour the 256 KiB wire cap and never
|
|
68540
|
+
* split a UTF-8 character across slices.
|
|
68541
|
+
*/
|
|
68542
|
+
/** Encoded-payload ceiling, leaving headroom under MAX_FRAME_BYTES for the
|
|
68543
|
+
* envelope (id/ts/type/corr + fencing ext, well under 4 KiB). */
|
|
68544
|
+
const REPLY_BUDGET$1 = MAX_FRAME_BYTES - 4096;
|
|
68545
|
+
/** The encoded size of the payload the wire will carry (JSON.stringify matches
|
|
68546
|
+
* the codec's `encode`), measured in bytes. */
|
|
68547
|
+
function encodedBytes$1(payload) {
|
|
68548
|
+
return Buffer.byteLength(JSON.stringify(payload));
|
|
68549
|
+
}
|
|
68550
|
+
/** Largest index ≤ `len` that lands on a UTF-8 character boundary — i.e. never
|
|
68551
|
+
* cuts a multi-byte sequence. If the slice's trailing char is incomplete, the
|
|
68552
|
+
* boundary is placed before that char (its bytes belong to the next slice). */
|
|
68553
|
+
function utf8Boundary$1(buf, len) {
|
|
68554
|
+
if (len >= buf.length) len = buf.length;
|
|
68555
|
+
if (len <= 0) return 0;
|
|
68556
|
+
let start = len - 1;
|
|
68557
|
+
while (start > 0 && ((buf[start] ?? 0) & 192) === 128) start--;
|
|
68558
|
+
const lead = buf[start] ?? 0;
|
|
68559
|
+
const seqLen = lead < 128 ? 1 : (lead & 224) === 192 ? 2 : (lead & 240) === 224 ? 3 : (lead & 248) === 240 ? 4 : 1;
|
|
68560
|
+
return start + seqLen <= len ? len : start;
|
|
68561
|
+
}
|
|
68562
|
+
/** Shrink `content` (a utf8 view of `slice[0..end]`) until its JSON-escaped size
|
|
68563
|
+
* fits `REPLY_BUDGET`, keeping the cut on a UTF-8 boundary. Returns the final
|
|
68564
|
+
* `{ end, content }`. Control-byte-heavy text escapes ~6×, so the raw byte limit
|
|
68565
|
+
* is not enough on its own. */
|
|
68566
|
+
function fitToBudget(slice, end) {
|
|
68567
|
+
let content = slice.toString("utf8", 0, end);
|
|
68568
|
+
while (end > 0 && encodedBytes$1(content) > REPLY_BUDGET$1) {
|
|
68569
|
+
const factor = REPLY_BUDGET$1 / encodedBytes$1(content);
|
|
68570
|
+
const shrunk = Math.max(1, Math.floor(end * factor * .9));
|
|
68571
|
+
end = utf8Boundary$1(slice, Math.min(shrunk, end - 1));
|
|
68572
|
+
content = slice.toString("utf8", 0, end);
|
|
68573
|
+
}
|
|
68574
|
+
return {
|
|
68575
|
+
end,
|
|
68576
|
+
content
|
|
68577
|
+
};
|
|
68578
|
+
}
|
|
68579
|
+
//#endregion
|
|
64855
68580
|
//#region src/cp/workspace-reader.ts
|
|
64856
68581
|
/**
|
|
64857
68582
|
* `WorkspaceReader` — the read-only seam answering the CP's `workspace/list` and
|
|
@@ -64876,9 +68601,6 @@ function makeLogger(level = "info") {
|
|
|
64876
68601
|
*/
|
|
64877
68602
|
/** Bytes sniffed from the head of a file for binary (NUL byte) detection. */
|
|
64878
68603
|
const SNIFF_BYTES = 8192;
|
|
64879
|
-
/** Encoded-payload ceiling, leaving headroom under MAX_FRAME_BYTES for the
|
|
64880
|
-
* envelope (id/ts/type/corr + fencing ext, well under 4 KiB). */
|
|
64881
|
-
const REPLY_BUDGET$1 = MAX_FRAME_BYTES - 4096;
|
|
64882
68604
|
/** Path-containment / bad-request violation → `BAD_PAYLOAD` on the wire. */
|
|
64883
68605
|
var WorkspaceViolationError = class extends Error {
|
|
64884
68606
|
constructor(message) {
|
|
@@ -64886,29 +68608,12 @@ var WorkspaceViolationError = class extends Error {
|
|
|
64886
68608
|
this.name = "WorkspaceViolationError";
|
|
64887
68609
|
}
|
|
64888
68610
|
};
|
|
64889
|
-
function isErrno(err, code) {
|
|
68611
|
+
function isErrno$1(err, code) {
|
|
64890
68612
|
return err?.code === code;
|
|
64891
68613
|
}
|
|
64892
68614
|
function under(root, p) {
|
|
64893
68615
|
return p === root || p.startsWith(root + sp.sep);
|
|
64894
68616
|
}
|
|
64895
|
-
/** Largest index ≤ `len` that lands on a UTF-8 character boundary — i.e. never
|
|
64896
|
-
* cuts a multi-byte sequence. If the slice's trailing char is incomplete, the
|
|
64897
|
-
* boundary is placed before that char (its bytes belong to the next slice). */
|
|
64898
|
-
function utf8Boundary$1(buf, len) {
|
|
64899
|
-
if (len >= buf.length) len = buf.length;
|
|
64900
|
-
if (len <= 0) return 0;
|
|
64901
|
-
let start = len - 1;
|
|
64902
|
-
while (start > 0 && ((buf[start] ?? 0) & 192) === 128) start--;
|
|
64903
|
-
const lead = buf[start] ?? 0;
|
|
64904
|
-
const seqLen = lead < 128 ? 1 : (lead & 224) === 192 ? 2 : (lead & 240) === 224 ? 3 : (lead & 248) === 240 ? 4 : 1;
|
|
64905
|
-
return start + seqLen <= len ? len : start;
|
|
64906
|
-
}
|
|
64907
|
-
/** The encoded size of the payload the wire will carry (JSON.stringify matches
|
|
64908
|
-
* the codec's `encode`), measured in bytes. */
|
|
64909
|
-
function encodedBytes$1(payload) {
|
|
64910
|
-
return Buffer.byteLength(JSON.stringify(payload));
|
|
64911
|
-
}
|
|
64912
68617
|
function createWorkspaceReader(workspaceRootByAgent) {
|
|
64913
68618
|
function rootFor(agentId) {
|
|
64914
68619
|
const root = workspaceRootByAgent(agentId);
|
|
@@ -64930,7 +68635,7 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
64930
68635
|
realRoot: await promises.realpath(root)
|
|
64931
68636
|
};
|
|
64932
68637
|
} catch (err) {
|
|
64933
|
-
if (isErrno(err, "ENOENT")) return {
|
|
68638
|
+
if (isErrno$1(err, "ENOENT")) return {
|
|
64934
68639
|
resolved,
|
|
64935
68640
|
realRoot: null
|
|
64936
68641
|
};
|
|
@@ -64959,14 +68664,14 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
64959
68664
|
try {
|
|
64960
68665
|
target = await canonicalUnder(realRoot, resolved);
|
|
64961
68666
|
} catch (err) {
|
|
64962
|
-
if (isErrno(err, "ENOENT")) return notFound;
|
|
68667
|
+
if (isErrno$1(err, "ENOENT")) return notFound;
|
|
64963
68668
|
throw err;
|
|
64964
68669
|
}
|
|
64965
68670
|
let dirents;
|
|
64966
68671
|
try {
|
|
64967
68672
|
dirents = await promises.readdir(target, { withFileTypes: true });
|
|
64968
68673
|
} catch (err) {
|
|
64969
|
-
if (isErrno(err, "ENOENT") || isErrno(err, "ENOTDIR")) return notFound;
|
|
68674
|
+
if (isErrno$1(err, "ENOENT") || isErrno$1(err, "ENOTDIR")) return notFound;
|
|
64970
68675
|
throw err;
|
|
64971
68676
|
}
|
|
64972
68677
|
const entries = [];
|
|
@@ -65016,7 +68721,7 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
65016
68721
|
try {
|
|
65017
68722
|
st = await promises.lstat(resolved);
|
|
65018
68723
|
} catch (err) {
|
|
65019
|
-
if (isErrno(err, "ENOENT")) return notFound;
|
|
68724
|
+
if (isErrno$1(err, "ENOENT")) return notFound;
|
|
65020
68725
|
throw err;
|
|
65021
68726
|
}
|
|
65022
68727
|
if (!st.isFile()) throw new WorkspaceViolationError("not a regular file");
|
|
@@ -65046,13 +68751,9 @@ function createWorkspaceReader(workspaceRootByAgent) {
|
|
|
65046
68751
|
slice = buf.subarray(0, bytesRead);
|
|
65047
68752
|
}
|
|
65048
68753
|
let end = utf8Boundary$1(slice, slice.length);
|
|
65049
|
-
|
|
65050
|
-
|
|
65051
|
-
|
|
65052
|
-
const shrunk = Math.max(1, Math.floor(end * factor * .9));
|
|
65053
|
-
end = utf8Boundary$1(slice, Math.min(shrunk, end - 1));
|
|
65054
|
-
content = slice.toString("utf8", 0, end);
|
|
65055
|
-
}
|
|
68754
|
+
const fitted = fitToBudget(slice, end);
|
|
68755
|
+
end = fitted.end;
|
|
68756
|
+
const content = fitted.content;
|
|
65056
68757
|
const nextOffset = req.offset + end;
|
|
65057
68758
|
return {
|
|
65058
68759
|
agentId: req.agentId,
|
|
@@ -65083,6 +68784,103 @@ function compareEntries(a, b) {
|
|
|
65083
68784
|
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
65084
68785
|
}
|
|
65085
68786
|
//#endregion
|
|
68787
|
+
//#region src/cp/memory-reader.ts
|
|
68788
|
+
/**
|
|
68789
|
+
* `MemoryReader` — the seam answering the CP's `memory/list`, `memory/read` and
|
|
68790
|
+
* `memory/write` REQs for an agent's memory directory (`<agent-root>/memory/`).
|
|
68791
|
+
* The files live only on the daemon (§1/§12); reads stream single byte slices,
|
|
68792
|
+
* writes replace one whole file.
|
|
68793
|
+
*
|
|
68794
|
+
* Path containment lives in `agents/memory.ts` (`resolveInMemoryDir`): a topic
|
|
68795
|
+
* path that escapes the memory dir throws `MemoryPathError` → `BAD_PAYLOAD`. An
|
|
68796
|
+
* unknown agent is also `BAD_PAYLOAD`; a not-yet-created file/dir is data
|
|
68797
|
+
* (`exists:false`), not an error. Frame-size safety mirrors the workspace reader:
|
|
68798
|
+
* the read slice is bounded by the *encoded* reply size and ends on a UTF-8
|
|
68799
|
+
* boundary. Never log memory contents.
|
|
68800
|
+
*/
|
|
68801
|
+
/** Unknown-agent violation → `BAD_PAYLOAD` on the wire (path escapes surface as
|
|
68802
|
+
* MemoryPathError from agents/memory.ts, mapped the same way by the dispatcher). */
|
|
68803
|
+
var MemoryViolationError = class extends Error {
|
|
68804
|
+
constructor(message) {
|
|
68805
|
+
super(message);
|
|
68806
|
+
this.name = "MemoryViolationError";
|
|
68807
|
+
}
|
|
68808
|
+
};
|
|
68809
|
+
function isErrno(err, code) {
|
|
68810
|
+
return err?.code === code;
|
|
68811
|
+
}
|
|
68812
|
+
/** `agentDirByAgent` resolves an agent id → its root dir (which holds `memory/`),
|
|
68813
|
+
* or undefined for an unknown agent. */
|
|
68814
|
+
function createMemoryReader(agentDirByAgent) {
|
|
68815
|
+
function dirFor(agentId) {
|
|
68816
|
+
const dir = agentDirByAgent(agentId);
|
|
68817
|
+
if (!dir) throw new MemoryViolationError(`unknown agent "${agentId}"`);
|
|
68818
|
+
return dir;
|
|
68819
|
+
}
|
|
68820
|
+
return {
|
|
68821
|
+
async list(req) {
|
|
68822
|
+
const files = await listMemory(dirFor(req.agentId));
|
|
68823
|
+
return {
|
|
68824
|
+
agentId: req.agentId,
|
|
68825
|
+
exists: files.length > 0,
|
|
68826
|
+
entries: files
|
|
68827
|
+
};
|
|
68828
|
+
},
|
|
68829
|
+
async read(req) {
|
|
68830
|
+
const file = resolveInMemoryDir(dirFor(req.agentId), req.path);
|
|
68831
|
+
const notFound = {
|
|
68832
|
+
agentId: req.agentId,
|
|
68833
|
+
path: req.path,
|
|
68834
|
+
exists: false
|
|
68835
|
+
};
|
|
68836
|
+
let st;
|
|
68837
|
+
try {
|
|
68838
|
+
st = await promises.stat(file);
|
|
68839
|
+
} catch (err) {
|
|
68840
|
+
if (isErrno(err, "ENOENT")) return notFound;
|
|
68841
|
+
throw err;
|
|
68842
|
+
}
|
|
68843
|
+
if (!st.isFile()) return notFound;
|
|
68844
|
+
const size = st.size;
|
|
68845
|
+
const mtime = st.mtime.toISOString();
|
|
68846
|
+
const fh = await promises.open(file, "r");
|
|
68847
|
+
try {
|
|
68848
|
+
const want = Math.min(req.limit, Math.max(0, size - req.offset));
|
|
68849
|
+
let slice = Buffer.alloc(0);
|
|
68850
|
+
if (want > 0) {
|
|
68851
|
+
const buf = Buffer.alloc(want);
|
|
68852
|
+
const { bytesRead } = await fh.read(buf, 0, want, req.offset);
|
|
68853
|
+
slice = buf.subarray(0, bytesRead);
|
|
68854
|
+
}
|
|
68855
|
+
const { end, content } = fitToBudget(slice, utf8Boundary$1(slice, slice.length));
|
|
68856
|
+
const nextOffset = req.offset + end;
|
|
68857
|
+
return {
|
|
68858
|
+
agentId: req.agentId,
|
|
68859
|
+
path: req.path,
|
|
68860
|
+
exists: true,
|
|
68861
|
+
size,
|
|
68862
|
+
mtime,
|
|
68863
|
+
content,
|
|
68864
|
+
offset: req.offset,
|
|
68865
|
+
nextOffset,
|
|
68866
|
+
truncated: nextOffset < size
|
|
68867
|
+
};
|
|
68868
|
+
} finally {
|
|
68869
|
+
await fh.close();
|
|
68870
|
+
}
|
|
68871
|
+
},
|
|
68872
|
+
async write(req) {
|
|
68873
|
+
const { size, mtime } = await writeMemoryFile(dirFor(req.agentId), req.path, req.content, req.ifMatchMtime);
|
|
68874
|
+
return {
|
|
68875
|
+
agentId: req.agentId,
|
|
68876
|
+
path: req.path,
|
|
68877
|
+
size,
|
|
68878
|
+
mtime
|
|
68879
|
+
};
|
|
68880
|
+
}
|
|
68881
|
+
};
|
|
68882
|
+
}
|
|
68883
|
+
//#endregion
|
|
65086
68884
|
//#region src/cp/correlator.ts
|
|
65087
68885
|
/** Transport-free representation of a typed wire `error` REP. */
|
|
65088
68886
|
var WireError = class extends Error {
|
|
@@ -65277,7 +69075,7 @@ var CpClient = class {
|
|
|
65277
69075
|
this.heartbeatMs = ok.heartbeatSec > 0 ? ok.heartbeatSec * 1e3 : this.deps.heartbeatDefaultMs;
|
|
65278
69076
|
this.armHeartbeat();
|
|
65279
69077
|
this.deps.log.info(`cp: READY (epoch=${this.sessionEpoch}, routingEpoch=${this.routingEpoch})`);
|
|
65280
|
-
this.emitDaemonRuntimes(this.deps.runtimeProfiles());
|
|
69078
|
+
this.emitDaemonRuntimes(this.deps.runtimeProfiles(), this.deps.mcpServerFacts?.() ?? []);
|
|
65281
69079
|
this.deps.onReady?.();
|
|
65282
69080
|
}
|
|
65283
69081
|
/**
|
|
@@ -65287,9 +69085,12 @@ var CpClient = class {
|
|
|
65287
69085
|
* runtime list to exactly this snapshot, pruning runtimes that are no longer
|
|
65288
69086
|
* installed. No-op unless READY/DRAINING.
|
|
65289
69087
|
*/
|
|
65290
|
-
emitDaemonRuntimes(runtimes) {
|
|
69088
|
+
emitDaemonRuntimes(runtimes, mcpServers = []) {
|
|
65291
69089
|
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
65292
|
-
this.transport?.send(encode(buildEnvelope("facts/daemon-runtimes", {
|
|
69090
|
+
this.transport?.send(encode(buildEnvelope("facts/daemon-runtimes", {
|
|
69091
|
+
runtimes,
|
|
69092
|
+
mcpServers
|
|
69093
|
+
})));
|
|
65293
69094
|
}
|
|
65294
69095
|
/**
|
|
65295
69096
|
* Report a session's cumulative token usage (D→C `usage/report` EVT,
|
|
@@ -65547,6 +69348,15 @@ var CpClient = class {
|
|
|
65547
69348
|
case "workspace/gitpull":
|
|
65548
69349
|
this.deps.workspaceGit.pull(frame.payload.agentId).then((result) => this.reply(frame, "workspace/gitpull/result", result)).catch((err) => this.workspaceError(frame.id, "workspace/gitpull", err));
|
|
65549
69350
|
return;
|
|
69351
|
+
case "memory/list":
|
|
69352
|
+
this.deps.memoryReader.list(frame.payload).then((page) => this.reply(frame, "memory/list/page", page)).catch((err) => this.memoryError(frame.id, "memory/list", err));
|
|
69353
|
+
return;
|
|
69354
|
+
case "memory/read":
|
|
69355
|
+
this.deps.memoryReader.read(frame.payload).then((content) => this.reply(frame, "memory/read/content", content)).catch((err) => this.memoryError(frame.id, "memory/read", err));
|
|
69356
|
+
return;
|
|
69357
|
+
case "memory/write":
|
|
69358
|
+
this.deps.memoryReader.write(frame.payload).then((ok) => this.reply(frame, "memory/write/ok", ok)).catch((err) => this.memoryError(frame.id, "memory/write", err));
|
|
69359
|
+
return;
|
|
65550
69360
|
case "webchat/message":
|
|
65551
69361
|
if (!this.deps.onWebchatMessage) {
|
|
65552
69362
|
this.sendError(frame.id, "INTERNAL", "webchat not supported", false);
|
|
@@ -65567,6 +69377,9 @@ var CpClient = class {
|
|
|
65567
69377
|
case "webchat/set-effort":
|
|
65568
69378
|
this.deps.onWebchatSetEffort?.(frame.payload);
|
|
65569
69379
|
return;
|
|
69380
|
+
case "webchat/set-permission-mode":
|
|
69381
|
+
this.deps.onWebchatSetPermissionMode?.(frame.payload);
|
|
69382
|
+
return;
|
|
65570
69383
|
case "webchat/set-fast":
|
|
65571
69384
|
this.deps.onWebchatSetFast?.(frame.payload);
|
|
65572
69385
|
return;
|
|
@@ -65590,6 +69403,18 @@ var CpClient = class {
|
|
|
65590
69403
|
this.deps.log.warn(`cp: ${op} failed: ${err?.message}`);
|
|
65591
69404
|
this.sendError(corr, "INTERNAL", `${op} failed`, false);
|
|
65592
69405
|
}
|
|
69406
|
+
memoryError(corr, op, err) {
|
|
69407
|
+
if (err instanceof MemoryConflictError) {
|
|
69408
|
+
this.sendError(corr, "CONFLICT", `${op} failed: ${err.message}`, false);
|
|
69409
|
+
return;
|
|
69410
|
+
}
|
|
69411
|
+
if (err instanceof MemoryViolationError || err instanceof MemoryPathError || err instanceof MemoryTooLargeError) {
|
|
69412
|
+
this.sendError(corr, "BAD_PAYLOAD", `${op} failed: ${err.message}`, false);
|
|
69413
|
+
return;
|
|
69414
|
+
}
|
|
69415
|
+
this.deps.log.warn(`cp: ${op} failed: ${err?.message}`);
|
|
69416
|
+
this.sendError(corr, "INTERNAL", `${op} failed`, false);
|
|
69417
|
+
}
|
|
65593
69418
|
reply(req, type, payload) {
|
|
65594
69419
|
this.transport?.send(encode(buildEnvelope(type, payload, { corr: req.id })));
|
|
65595
69420
|
}
|
|
@@ -66040,6 +69865,8 @@ function applySpecFields(raw, spec, opts) {
|
|
|
66040
69865
|
if (spec.reasoningEffort !== void 0) raw.reasoningEffort = spec.reasoningEffort;
|
|
66041
69866
|
if (spec.executionMode !== void 0) raw.executionMode = spec.executionMode;
|
|
66042
69867
|
if (spec.fastMode !== void 0) raw.fastMode = spec.fastMode;
|
|
69868
|
+
if (spec.permissionMode !== void 0) raw.permissionMode = spec.permissionMode;
|
|
69869
|
+
if (spec.mcpServers !== void 0) raw.mcpServers = spec.mcpServers;
|
|
66043
69870
|
if (spec.outputMode !== void 0) {
|
|
66044
69871
|
const out = typeof raw.output === "object" && raw.output !== null ? raw.output : {};
|
|
66045
69872
|
out.mode = spec.outputMode;
|
|
@@ -82161,6 +85988,9 @@ var Daemon = class Daemon {
|
|
|
82161
85988
|
runtimeVersions = {};
|
|
82162
85989
|
runtimeModels = /* @__PURE__ */ new Map();
|
|
82163
85990
|
runtimeAcpVersions = /* @__PURE__ */ new Map();
|
|
85991
|
+
runtimeMcpCaps = /* @__PURE__ */ new Map();
|
|
85992
|
+
mcpServerDefs = {};
|
|
85993
|
+
mcpServerFacts = [];
|
|
82164
85994
|
probing = false;
|
|
82165
85995
|
lastProbeAtMs = 0;
|
|
82166
85996
|
cpClient;
|
|
@@ -82236,6 +86066,10 @@ var Daemon = class Daemon {
|
|
|
82236
86066
|
this.log.info(`runtimes ready: ${Object.keys(this.runtimes).join(", ") || "(none)"}`);
|
|
82237
86067
|
const skipped = Object.keys(resolvedRuntimes).filter((id) => !this.runtimes[id]);
|
|
82238
86068
|
if (skipped.length) this.log.info(`runtimes not installed (skipped): ${skipped.join(", ")}`);
|
|
86069
|
+
const { [RESERVED_MCP_SERVER_NAME]: reservedMcp, ...mcpServerDefs } = cfg.mcpServers ?? {};
|
|
86070
|
+
if (reservedMcp) this.log.warn(`mcp: config server name "${RESERVED_MCP_SERVER_NAME}" is reserved for the daemon bridge — ignored`);
|
|
86071
|
+
this.mcpServerDefs = mcpServerDefs;
|
|
86072
|
+
if (Object.keys(mcpServerDefs).length) this.log.info(`mcp servers configured: ${Object.keys(mcpServerDefs).join(", ")}`);
|
|
82239
86073
|
this.store = new LocalStore(statePath(root));
|
|
82240
86074
|
this.nameResolver = new SlackNameResolver((id, name) => {
|
|
82241
86075
|
this.store.setDisplayName(id, name, Date.now());
|
|
@@ -82267,6 +86101,7 @@ var Daemon = class Daemon {
|
|
|
82267
86101
|
log: this.log,
|
|
82268
86102
|
now: () => Date.now(),
|
|
82269
86103
|
gatewayFor: (integrationId) => this.connByIntegration.get(integrationId),
|
|
86104
|
+
memoryDirFor: (agentId) => this.agents.get(agentId)?.dir,
|
|
82270
86105
|
recordOutbound: (ctx, channel, thread, text, ts) => this.store.appendTranscript({
|
|
82271
86106
|
channel,
|
|
82272
86107
|
thread: thread ?? ctx.thread,
|
|
@@ -82284,21 +86119,30 @@ var Daemon = class Daemon {
|
|
|
82284
86119
|
hostFor: (agentId) => this.ensureHostAsync(agentId),
|
|
82285
86120
|
agentById: (id) => this.agents.get(id),
|
|
82286
86121
|
mcpServersFor: ({ agent, channel, thread }) => {
|
|
86122
|
+
const servers = [];
|
|
82287
86123
|
const tools = toolsForIntegrations(agent.integrations);
|
|
82288
86124
|
const integrationId = agent.integrations.find((i) => i.platform === "slack")?.id;
|
|
82289
|
-
if (tools.length
|
|
82290
|
-
|
|
82291
|
-
|
|
82292
|
-
|
|
82293
|
-
|
|
82294
|
-
|
|
82295
|
-
|
|
82296
|
-
|
|
82297
|
-
|
|
82298
|
-
|
|
82299
|
-
|
|
82300
|
-
|
|
82301
|
-
|
|
86125
|
+
if (tools.length > 0) {
|
|
86126
|
+
const token = this.mcp.register({
|
|
86127
|
+
agentId: agent.id,
|
|
86128
|
+
...integrationId ? { integrationId } : {},
|
|
86129
|
+
channel,
|
|
86130
|
+
thread,
|
|
86131
|
+
tools
|
|
86132
|
+
});
|
|
86133
|
+
servers.push(...buildMcpServers({
|
|
86134
|
+
socketPath: mcpSocketPath(root),
|
|
86135
|
+
token,
|
|
86136
|
+
cliEntry
|
|
86137
|
+
}));
|
|
86138
|
+
}
|
|
86139
|
+
servers.push(...resolveAgentMcpServers({
|
|
86140
|
+
enabled: agent.mcpServers,
|
|
86141
|
+
defs: this.mcpServerDefs,
|
|
86142
|
+
caps: this.runtimeMcpCaps.get(agent.runtime),
|
|
86143
|
+
warn: (m) => this.log.warn(m)
|
|
86144
|
+
}));
|
|
86145
|
+
return servers;
|
|
82302
86146
|
},
|
|
82303
86147
|
downloadAttachment: (agentId, att) => this.replyConnFor(agentId)?.downloadFile?.(att.sourceUrl, this.cfg.limits.maxAttachmentBytes) ?? Promise.resolve(null),
|
|
82304
86148
|
attachmentMaxBytes: cfg.limits.maxAttachmentBytes,
|
|
@@ -82631,6 +86475,7 @@ var Daemon = class Daemon {
|
|
|
82631
86475
|
},
|
|
82632
86476
|
configPrefs: {
|
|
82633
86477
|
model: agent.runtimeOverrides?.model,
|
|
86478
|
+
permissionMode: agent.permissionMode,
|
|
82634
86479
|
reasoningEffort: agent.reasoningEffort,
|
|
82635
86480
|
fastMode: agent.fastMode,
|
|
82636
86481
|
systemPrompt: agent.description
|
|
@@ -82653,6 +86498,10 @@ var Daemon = class Daemon {
|
|
|
82653
86498
|
}
|
|
82654
86499
|
this.seenMsgIds.add(msg.msgId);
|
|
82655
86500
|
if (this.seenMsgIds.size > 2e3) this.seenMsgIds.clear();
|
|
86501
|
+
if (this.ownBotIdentities().has(msg.sender.id)) {
|
|
86502
|
+
this.log.debug(`routing: dropping own bot echo ${msg.msgId}`);
|
|
86503
|
+
return;
|
|
86504
|
+
}
|
|
82656
86505
|
const command = parseCommand(msg.text);
|
|
82657
86506
|
if (command) {
|
|
82658
86507
|
this.handleCommand(command, msg);
|
|
@@ -82783,6 +86632,21 @@ var Daemon = class Daemon {
|
|
|
82783
86632
|
}
|
|
82784
86633
|
host.setSessionEffort(acpSessionId, effort).then(() => this.refreshStatusBarForKey(key)).catch((err) => this.log.warn(`set-effort failed: ${err.message}`));
|
|
82785
86634
|
}
|
|
86635
|
+
/** Switch a session's permission/approval mode by its local key. Records the sticky
|
|
86636
|
+
* override and applies it live via ACP `mode` when the runtime advertises one. */
|
|
86637
|
+
setPermissionModeByKey(key, permissionMode) {
|
|
86638
|
+
const rec = this.store.getSession(key);
|
|
86639
|
+
if (!rec) return;
|
|
86640
|
+
this.store.setPermissionModeOverride(key, permissionMode);
|
|
86641
|
+
this.log.info(`session ${key} permission-mode override → "${permissionMode}"`);
|
|
86642
|
+
const acpSessionId = rec.acpSessionId;
|
|
86643
|
+
const host = this.hosts.get(rec.agentId);
|
|
86644
|
+
if (!acpSessionId || !host?.hasSession(acpSessionId)) {
|
|
86645
|
+
this.refreshStatusBarForKey(key);
|
|
86646
|
+
return;
|
|
86647
|
+
}
|
|
86648
|
+
host.setSessionPermissionMode(acpSessionId, permissionMode).then(() => this.refreshStatusBarForKey(key)).catch((err) => this.log.warn(`set-permission-mode failed: ${err.message}`));
|
|
86649
|
+
}
|
|
82786
86650
|
/** Toggle a session's fast mode by its local key — the fast-mode counterpart of
|
|
82787
86651
|
* {@link setModelByKey}. Records the sticky override and applies it live when the
|
|
82788
86652
|
* current model offers a fast toggle. */
|
|
@@ -82823,6 +86687,10 @@ var Daemon = class Daemon {
|
|
|
82823
86687
|
handleWebchatSetEffort(m) {
|
|
82824
86688
|
this.setEffortByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.effort);
|
|
82825
86689
|
}
|
|
86690
|
+
/** Handle a webchat in-session permission-mode switch (C→D `webchat/set-permission-mode`). */
|
|
86691
|
+
handleWebchatSetPermissionMode(m) {
|
|
86692
|
+
this.setPermissionModeByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.permissionMode);
|
|
86693
|
+
}
|
|
82826
86694
|
/** Handle a webchat in-session fast-mode toggle (C→D `webchat/set-fast`). */
|
|
82827
86695
|
handleWebchatSetFast(m) {
|
|
82828
86696
|
this.setFastByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.fastMode);
|
|
@@ -82846,6 +86714,8 @@ var Daemon = class Daemon {
|
|
|
82846
86714
|
if (a.model) this.setModelByKey(a.sessionKey, a.model);
|
|
82847
86715
|
} else if (a.kind === "set-effort") {
|
|
82848
86716
|
if (a.effort) this.setEffortByKey(a.sessionKey, a.effort);
|
|
86717
|
+
} else if (a.kind === "set-permission-mode") {
|
|
86718
|
+
if (a.permissionMode) this.setPermissionModeByKey(a.sessionKey, a.permissionMode);
|
|
82849
86719
|
} else if (a.kind === "set-fast") {
|
|
82850
86720
|
if (a.fastMode !== void 0) this.setFastByKey(a.sessionKey, a.fastMode);
|
|
82851
86721
|
} else if (a.kind === "set-output") {
|
|
@@ -83084,6 +86954,8 @@ var Daemon = class Daemon {
|
|
|
83084
86954
|
const host = await this.ensureHostAsync(agentId);
|
|
83085
86955
|
const override = this.store.getModelOverride(key);
|
|
83086
86956
|
if (override) await host.setSessionModel(sessionId, override).catch((err) => this.log.debug(`model override "${override}" not applied: ${err.message}`));
|
|
86957
|
+
const permissionModeOverride = this.store.getPermissionModeOverride(key);
|
|
86958
|
+
if (permissionModeOverride) await host.setSessionPermissionMode(sessionId, permissionModeOverride).catch((err) => this.log.debug(`permission-mode override "${permissionModeOverride}" not applied: ${err.message}`));
|
|
83087
86959
|
const effortOverride = this.store.getEffortOverride(key);
|
|
83088
86960
|
if (effortOverride) await host.setSessionEffort(sessionId, effortOverride).catch((err) => this.log.debug(`effort override "${effortOverride}" not applied: ${err.message}`));
|
|
83089
86961
|
const fastOverride = this.store.getFastModeOverride(key);
|
|
@@ -83347,12 +87219,15 @@ var Daemon = class Daemon {
|
|
|
83347
87219
|
const host = this.hosts.get(agentId);
|
|
83348
87220
|
const model = host?.modelOptions?.();
|
|
83349
87221
|
const effort = host?.effortOptions?.();
|
|
87222
|
+
const permissionMode = host?.permissionModeOptions?.();
|
|
83350
87223
|
const fast = host?.fastModeOption?.();
|
|
83351
87224
|
const effortOverride = this.store.getEffortOverride(sessionKey);
|
|
87225
|
+
const permissionModeOverride = this.store.getPermissionModeOverride(sessionKey);
|
|
83352
87226
|
const fastOverride = this.store.getFastModeOverride(sessionKey);
|
|
83353
87227
|
return {
|
|
83354
87228
|
model: model?.current ?? agent?.runtimeOverrides?.model,
|
|
83355
87229
|
effort: effortOverride ?? effort?.current ?? agent?.reasoningEffort,
|
|
87230
|
+
permissionMode: permissionModeOverride ?? permissionMode?.current ?? agent?.permissionMode,
|
|
83356
87231
|
fastMode: fastOverride ?? fast?.current ?? agent?.fastMode,
|
|
83357
87232
|
contextUsed: usage.contextUsed,
|
|
83358
87233
|
contextSize: usage.contextSize,
|
|
@@ -83369,6 +87244,7 @@ var Daemon = class Daemon {
|
|
|
83369
87244
|
} : {},
|
|
83370
87245
|
...model?.models?.length ? { models: model.models } : {},
|
|
83371
87246
|
...effort?.efforts?.length ? { efforts: effort.efforts } : {},
|
|
87247
|
+
...permissionMode?.modes?.length ? { permissionModes: permissionMode.modes } : {},
|
|
83372
87248
|
...fast ? { fastModeAvailable: true } : {},
|
|
83373
87249
|
...acpSessionId ? { sessionId: acpSessionId } : {}
|
|
83374
87250
|
};
|
|
@@ -83961,6 +87837,7 @@ var Daemon = class Daemon {
|
|
|
83961
87837
|
features: []
|
|
83962
87838
|
}),
|
|
83963
87839
|
runtimeProfiles: () => Object.keys(this.runtimes).map((id) => this.runtimeProfileFor(id)),
|
|
87840
|
+
mcpServerFacts: () => this.mcpServerFacts,
|
|
83964
87841
|
onReady: () => {
|
|
83965
87842
|
this.probeRuntimesAndEmit();
|
|
83966
87843
|
for (const [integrationId, channels] of this.channelSnapshots) this.cpClient?.emitIntegrationChannels({
|
|
@@ -83995,10 +87872,12 @@ var Daemon = class Daemon {
|
|
|
83995
87872
|
sessionRead: createSessionReader(this.store, (agentId) => this.replyConnFor(agentId)?.workspaceUrl),
|
|
83996
87873
|
workspaceRead: createWorkspaceReader((id) => this.agents.get(id)?.workspace.path),
|
|
83997
87874
|
workspaceGit: createWorkspaceGit((id) => this.agents.get(id)?.workspace.path),
|
|
87875
|
+
memoryReader: createMemoryReader((id) => this.agents.get(id)?.dir),
|
|
83998
87876
|
onWebchatMessage: (m) => this.handleWebchatMessage(m),
|
|
83999
87877
|
onWebchatClose: (m) => this.handleWebchatClose(m),
|
|
84000
87878
|
onWebchatSetModel: (m) => this.handleWebchatSetModel(m),
|
|
84001
87879
|
onWebchatSetEffort: (m) => this.handleWebchatSetEffort(m),
|
|
87880
|
+
onWebchatSetPermissionMode: (m) => this.handleWebchatSetPermissionMode(m),
|
|
84002
87881
|
onWebchatSetFast: (m) => this.handleWebchatSetFast(m),
|
|
84003
87882
|
onWebchatCancel: (m) => this.handleWebchatCancel(m),
|
|
84004
87883
|
clock: systemClock,
|
|
@@ -84018,7 +87897,8 @@ var Daemon = class Daemon {
|
|
|
84018
87897
|
models: this.runtimeModels.get(id) ?? [],
|
|
84019
87898
|
acpSupport: "full",
|
|
84020
87899
|
acpProtocolVersion: this.runtimeAcpVersions.get(id),
|
|
84021
|
-
toolCalling: true
|
|
87900
|
+
toolCalling: true,
|
|
87901
|
+
mcpCapabilities: this.runtimeMcpCaps.get(id)
|
|
84022
87902
|
};
|
|
84023
87903
|
}
|
|
84024
87904
|
/** How long a completed probe sweep stays fresh — reconnects within this window
|
|
@@ -84026,8 +87906,10 @@ var Daemon = class Daemon {
|
|
|
84026
87906
|
static PROBE_TTL_MS = 5 * 6e4;
|
|
84027
87907
|
/**
|
|
84028
87908
|
* Probe every installed runtime in the background (launch → initialize →
|
|
84029
|
-
* session/new → read models → tear down),
|
|
84030
|
-
*
|
|
87909
|
+
* session/new → read models → tear down) and, concurrently, every configured
|
|
87910
|
+
* MCP server (MCP handshake + tools/list), then emit one `facts/daemon-runtimes`
|
|
87911
|
+
* snapshot once the whole sweep completes so the CP replaces its runtime +
|
|
87912
|
+
* MCP-server lists.
|
|
84031
87913
|
*
|
|
84032
87914
|
* Triggered on each CP (re)connect. Deduped while in flight; skipped (with a
|
|
84033
87915
|
* cached re-emit) when the last sweep is still fresh, so a reconnect storm can't
|
|
@@ -84037,25 +87919,33 @@ var Daemon = class Daemon {
|
|
|
84037
87919
|
async probeRuntimesAndEmit() {
|
|
84038
87920
|
if (this.opts.hostFactory && !this.opts.probeRuntimes) return;
|
|
84039
87921
|
const ids = Object.keys(this.runtimes);
|
|
84040
|
-
|
|
87922
|
+
const mcpNames = Object.keys(this.mcpServerDefs);
|
|
87923
|
+
if (ids.length === 0 && mcpNames.length === 0) return;
|
|
84041
87924
|
if (this.probing) return;
|
|
84042
87925
|
if (this.lastProbeAtMs > 0 && Date.now() - this.lastProbeAtMs < Daemon.PROBE_TTL_MS) {
|
|
84043
|
-
this.cpClient?.emitDaemonRuntimes(ids.map((id) => this.runtimeProfileFor(id)));
|
|
87926
|
+
this.cpClient?.emitDaemonRuntimes(ids.map((id) => this.runtimeProfileFor(id)), this.mcpServerFacts);
|
|
84044
87927
|
return;
|
|
84045
87928
|
}
|
|
84046
87929
|
this.probing = true;
|
|
84047
|
-
this.log.info(`probe: sweeping ${ids.length} runtime(s): ${ids.join(", ")}`);
|
|
87930
|
+
this.log.info(`probe: sweeping ${ids.length} runtime(s): ${ids.join(", ") || "(none)"}`);
|
|
87931
|
+
if (mcpNames.length) this.log.info(`probe: sweeping ${mcpNames.length} MCP server(s): ${mcpNames.join(", ")}`);
|
|
84048
87932
|
try {
|
|
84049
|
-
const
|
|
87933
|
+
const probe = this.opts.probeRuntimes ?? probeAllRuntimes;
|
|
87934
|
+
const probeMcp = this.opts.probeMcpServers ?? probeAllMcpServers;
|
|
87935
|
+
const [results, mcpFacts] = await Promise.all([probe(this.runtimes, { log: this.log }), probeMcp(this.mcpServerDefs, { log: this.log })]);
|
|
84050
87936
|
for (const r of results) {
|
|
84051
87937
|
this.runtimeModels.set(r.runtime, r.ok ? r.models : []);
|
|
84052
87938
|
if (r.ok && r.acpProtocolVersion !== void 0) this.runtimeAcpVersions.set(r.runtime, r.acpProtocolVersion);
|
|
84053
87939
|
else this.runtimeAcpVersions.delete(r.runtime);
|
|
87940
|
+
if (r.ok && r.mcpCapabilities) this.runtimeMcpCaps.set(r.runtime, r.mcpCapabilities);
|
|
87941
|
+
else this.runtimeMcpCaps.delete(r.runtime);
|
|
84054
87942
|
}
|
|
87943
|
+
this.mcpServerFacts = mcpFacts;
|
|
84055
87944
|
this.lastProbeAtMs = Date.now();
|
|
84056
87945
|
const okCount = results.filter((r) => r.ok).length;
|
|
84057
87946
|
this.log.info(`probe: sweep complete — ${okCount}/${results.length} runtime(s) reachable`);
|
|
84058
|
-
this.
|
|
87947
|
+
if (mcpFacts.length) this.log.info(`probe: ${mcpFacts.filter((s) => s.ok).length}/${mcpFacts.length} MCP server(s) reachable`);
|
|
87948
|
+
this.cpClient?.emitDaemonRuntimes(ids.map((id) => this.runtimeProfileFor(id)), this.mcpServerFacts);
|
|
84059
87949
|
} catch (err) {
|
|
84060
87950
|
this.log.warn(`probe: sweep failed: ${formatErr(err)}`);
|
|
84061
87951
|
} finally {
|
|
@@ -84115,4 +88005,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
84115
88005
|
//#endregion
|
|
84116
88006
|
export { ClientTransport as n, runForeground as t };
|
|
84117
88007
|
|
|
84118
|
-
//# sourceMappingURL=run-foreground-
|
|
88008
|
+
//# sourceMappingURL=run-foreground-B05rDr1z.js.map
|