Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
39 kB · 1003 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004// Local connection records for MCP services. Separate from credentials (auth.json):// a record captures the verified connection state for a connectionId — the stable// alias the kernel dispatches through — plus the catalog serviceId it connects and// the endpoint the verification ran against. Tokens never live here.//// Writes use the same file-lock + read-modify-write pattern as auth storage: the// interactive client and the daemon both mutate this file, so every flush re-reads// the latest on-disk state under a proper-lockfile lock and applies only this// instance's pending operations. Verification results apply under a guard so a// stale probe can never mark a newer grant (or a logged-out connection) verified.
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync } from "node:fs";import { dirname } from "node:path";import lockfile from "proper-lockfile";import { realpathIfPresentSync, writeFileAtomicSync } from "../../utils/atomic-file.js";
export type McpConnectionRecordStatus = "connected" | "pending" | "error";
export interface McpConnectionRecord { /** Auth.json key suffix (`mcp:<connectionId>`) and the kernel dispatch id. */ connectionId: string; /** Catalog service id; deliberately distinct from connectionId (aliases remain possible). */ serviceId: string; /** Endpoint the record's verification ran against. */ endpoint: string; label: string; status: McpConnectionRecordStatus; createdAt: number; updatedAt: number; /** Epoch ms of the last successful handshake + tools/list. */ verifiedAt?: number; toolCount?: number; /** Fixed, safe failure category (never URLs or server-controlled text). */ lastError?: string; /** Opaque one-time ownership nonce for a pending reservation/login attempt. */ attemptId?: string;}
interface McpConnectionsFile { version: 1; connections: Record<string, McpConnectionRecord>;}
type PendingOp = | { kind: "upsert"; record: McpConnectionRecord } | { kind: "remove"; connectionId: string } | { kind: "verify"; record: McpConnectionRecord; /** Evaluated at flush time under the lock; a failed guard discards the result. */ isStillCurrent: () => boolean; expectedRecord: McpConnectionRecord | undefined; resolve: (committed: boolean) => void; } | { kind: "claimConnectionId"; connectionId: string; /** Opaque ownership nonce stamped onto the EXISTING record. */ attemptId: string; isStillCurrent?: (record: McpConnectionRecord) => boolean; resolve: (claimed: boolean) => void; } | { kind: "releaseClaim"; connectionId: string; /** Only OUR claim is released; the record and its status survive. */ attemptId: string; resolve: (released: boolean) => void; } | { kind: "reserve"; record: McpConnectionRecord; /** Cross-process atomic allocation: true when this store won the id. */ resolve: (won: boolean) => void; } | { kind: "removeReservation"; connectionId: string; /** Opaque ownership nonce: only OUR pending reservation is removed. */ attemptId: string; resolve: (removed: boolean) => void; } | { kind: "finalizeAttempt"; connectionId: string; /** Opaque ownership nonce: the credential commit lands only for ours. */ attemptId: string; /** * Runs under the file lock ONLY when ownership holds; returns the * committed record. Caller-side effects (credential moves) belong here * so they share the store's lock ordering with cancel/remove. */ commit: (record: McpConnectionRecord) => McpConnectionRecord; /** * Runs under the SAME lock when the record write fails after commit * ran, restoring caller-side effects (credential moves) so a failed * finalize leaves the account exactly as before — all-or-nothing. * Closures capture whatever they need to restore. */ compensate?: () => void; resolve: (result: McpFinalizeResult) => void; } | { kind: "removeAccountForProvider"; /** The raw MCP credential provider id the user selected. */ providerId: string; /** Disk-authoritative credential removal (throws on write failure). */ authStorage: { removeVerified: (provider: string) => boolean }; resolve: (result: McpRemoveAccountResult) => void; } | { kind: "removeAccount"; connectionId: string; /** * Credential cleanup under the SAME file lock as the record removal — * disconnects share the store->auth ordering with finalizeAttempt, so * a concurrent finalize can never leave an orphan credential behind. * Runs even when no record exists (credential-only logouts); returns * whether a credential was actually removed. */ authCleanup: (connectionId: string) => boolean; /** The generic logout route keeps completed records (honest unbound). */ preserveCompletedRecord?: boolean; resolve: (result: McpRemoveAccountResult) => void; };
/** The account + credential key a generic MCP logout acts on. */export interface McpAccountLogoutTarget { /** The account whose active attempt is cancelled / record preserved. */ connectionId: string; /** * The auth key whose credential is verified-removed: the staged key itself * for staged-key logouts, the account key otherwise. */ credentialKey: string;}
/** * Resolve the account behind an MCP credential id for the generic logout * route. EXACT ids win — a local service id may itself contain "--" — and a * staged key maps back ONLY through its actually recorded attempt nonce, * never an unconditional split on the first "--". */export function resolveMcpAccountLogoutTarget( providerId: string, records: ReadonlyArray<{ connectionId: string; status: string; attemptId?: string }>,): McpAccountLogoutTarget { const candidate = providerId.slice("mcp:".length); const accountKey = `mcp:${candidate}`; if (records.some((record) => record.connectionId === candidate) || !candidate.includes("--")) { return { connectionId: candidate, credentialKey: accountKey }; } const stagedMatch = records.find( (record) => record.attemptId !== undefined && `${record.connectionId}--${record.attemptId}` === candidate, ); if (stagedMatch) { // A nonce match is ALWAYS a LIVE attempt (a pending reservation or a // claimed reconnect on any-status record — successful finalizes CONSUME // the nonce, so no stale nonces survive on completed records): the // staged-key logout cancels it, clearing the ACTIVE nonce. return { connectionId: stagedMatch.connectionId, credentialKey: accountKey }; } return { connectionId: candidate, credentialKey: accountKey };}
/** * The shared one-op MCP account logout for the generic /logout route: * verified credential deletion AND pending-attempt cancellation in ONE * store-locked critical section (store->auth ordering), with the id resolved * from CURRENT state UNDER the lock — never a stale pre-lock snapshot. An * account-key logout cancels pending attempts and preserves completed * records; a staged-key logout cancels the attempt — INVALIDATING its nonce * so the in-flight login's denied path cannot delete the record — while * NEVER deleting the account record itself (another client's ordinary login * may hold the real key at any moment; deleting could orphan it). The * state-neutral outcome is "refused": no success claim, no Connected claim * from token presence. The route delegates BEFORE touching auth; a failed * verified removal fails the whole op and changes no record. */export function logoutMcpAccount( providerId: string, store: McpConnectionStore, authStorage: { removeVerified: (provider: string) => boolean },): Promise<McpRemoveAccountResult> { return store.removeAccountForProvider({ providerId, authStorage });}
const MAX_LAST_ERROR_LENGTH = 500;
function sanitizeRecord(raw: unknown, connectionId: string): McpConnectionRecord | undefined { if (!raw || typeof raw !== "object") return undefined; const value = raw as Partial<McpConnectionRecord>; const string = (input: unknown): string | undefined => (typeof input === "string" ? input : undefined); const status = value.status; if ( !string(value.connectionId) || !string(value.serviceId) || !string(value.endpoint) || !string(value.label) || (status !== "connected" && status !== "pending" && status !== "error") ) { return undefined; } const record: McpConnectionRecord = { connectionId: string(value.connectionId) ?? connectionId, serviceId: string(value.serviceId) ?? "", endpoint: string(value.endpoint) ?? "", label: string(value.label) ?? string(value.connectionId) ?? connectionId, status, createdAt: typeof value.createdAt === "number" ? value.createdAt : Date.now(), updatedAt: typeof value.updatedAt === "number" ? value.updatedAt : Date.now(), }; if (typeof value.verifiedAt === "number") record.verifiedAt = value.verifiedAt; if (typeof value.toolCount === "number") record.toolCount = value.toolCount; const lastError = string(value.lastError); if (lastError) record.lastError = lastError.slice(0, MAX_LAST_ERROR_LENGTH); const attemptId = string(value.attemptId); if (attemptId) record.attemptId = attemptId.slice(0, 100); return record;}
function parseRecords(raw: string | undefined): Map<string, McpConnectionRecord> { const records = new Map<string, McpConnectionRecord>(); if (!raw) return records; let parsed: unknown; try { parsed = JSON.parse(raw); } catch { return records; } const file = parsed as Partial<McpConnectionsFile>; if (!file || file.version !== 1 || typeof file.connections !== "object" || file.connections === null) { return records; } for (const [connectionId, value] of Object.entries(file.connections)) { const record = sanitizeRecord(value, connectionId); if (record && record.connectionId === connectionId) { records.set(connectionId, record); } } return records;}
function serializeRecords(records: ReadonlyMap<string, McpConnectionRecord>): string { const file: McpConnectionsFile = { version: 1, connections: Object.fromEntries([...records.entries()].sort(([left], [right]) => left.localeCompare(right))), }; return `${JSON.stringify(file, null, "\t")}\n`;}
/** File-backed store of MCP connection records; corrupt or missing files reset to empty. */
/** One-shot attempt operations: they carry a resolver and never requeue. */type OneShotOperation = Extract< PendingOp, { kind: | "verify" | "reserve" | "claimConnectionId" | "releaseClaim" | "removeReservation" | "finalizeAttempt" | "removeAccount" | "removeAccountForProvider"; }>;
function isOneShotOperation(operation: PendingOp): operation is OneShotOperation { return ( operation.kind === "verify" || operation.kind === "reserve" || operation.kind === "claimConnectionId" || operation.kind === "releaseClaim" || operation.kind === "removeReservation" || operation.kind === "finalizeAttempt" || operation.kind === "removeAccount" || operation.kind === "removeAccountForProvider" );}
export class McpConnectionStore { private recordsById = new Map<string, McpConnectionRecord>(); private pendingOps: PendingOp[] = []; private flushChain: Promise<void> = Promise.resolve();
private constructor(private readonly path: string) {}
static open(path: string): McpConnectionStore { const store = new McpConnectionStore(path); store.load(); return store; }
/** Re-read the file. Tolerates a missing or corrupt file by resetting to empty. */ load(): void { this.recordsById = parseRecords( existsSync(this.path) ? safeReadFileSync(realpathIfPresentSync(this.path)) : undefined, ); }
get(connectionId: string): McpConnectionRecord | undefined { return this.recordsById.get(connectionId); }
records(): readonly McpConnectionRecord[] { return [...this.recordsById.values()]; }
/** Queue an unconditional upsert; applied in memory immediately and on disk at flush. */ upsert(record: McpConnectionRecord): void { this.applyUpsert(this.recordsById, record); this.pendingOps.push({ kind: "upsert", record: { ...record } }); }
/** Queue an unconditional removal; applied in memory immediately and on disk at flush. */ remove(connectionId: string): void { this.recordsById.delete(connectionId); this.pendingOps.push({ kind: "remove", connectionId }); }
/** * Atomically reserve a NEW connection id across processes, under the store's * file lock, as a durable pending record written before any login starts. * The record must carry a fresh opaque attemptId nonce (the ownership token * for cancel and finalize). Resolves true only after the record write * COMMITS; false when the id already exists on disk or the write failed — * no ghost reservation is queued. The pending marker makes the id visible * to every other process's allocations and to reconnect flows. */ reserveConnectionId(record: McpConnectionRecord): Promise<boolean> { return new Promise<boolean>((resolve) => { let settled = false; const settle = (won: boolean): void => { if (settled) return; settled = true; resolve(won); }; this.pendingOps.push({ kind: "reserve", record: { ...record }, resolve: settle }); void this.flush().catch(() => settle(false)); }); }
/** * Claim an EXISTING account record for a guarded login (reconnect): OUR * nonce is stamped on the record (any status) under the file lock, so the * finalize owns it and cancel (releaseClaim) restores the record exactly. * One-shot: settles on I/O or lock failure, never requeues. */ claimConnectionId(options: { connectionId: string; attemptId: string; /** Revalidate exact-account admission under the store lock, before claiming. */ isStillCurrent?: (record: McpConnectionRecord) => boolean; }): Promise<boolean> { return new Promise<boolean>((resolve) => { let settled = false; const settle = (claimed: boolean): void => { if (settled) return; settled = true; resolve(claimed); }; this.pendingOps.push({ kind: "claimConnectionId", connectionId: options.connectionId, attemptId: options.attemptId, isStillCurrent: options.isStillCurrent, resolve: settle, }); void this.flush().catch(() => settle(false)); }); }
/** * Release OUR claim on an existing account record: only the nonce is * cleared — the record, its status, and any credential survive untouched * (a cancelled reconnect preserves the account exactly). * One-shot: settles on I/O or lock failure, never requeues. */ releaseClaim(options: { connectionId: string; attemptId: string }): Promise<boolean> { return new Promise<boolean>((resolve) => { let settled = false; const settle = (released: boolean): void => { if (settled) return; settled = true; resolve(released); }; this.pendingOps.push({ kind: "releaseClaim", connectionId: options.connectionId, attemptId: options.attemptId, resolve: settle, }); void this.flush().catch(() => settle(false)); }); }
/** * Remove OUR pending reservation after a cancelled or failed login. Ownership * is the opaque attempt nonce (same id, still pending, same attempt), so a * late callback can never remove — or resurrect — another account's record. */ removeReservation(connectionId: string, attemptId: string): Promise<boolean> { return new Promise<boolean>((resolve) => { let settled = false; const settle = (removed: boolean): void => { if (settled) return; settled = true; resolve(removed); }; this.pendingOps.push({ kind: "removeReservation", connectionId, attemptId, resolve: settle }); void this.flush().catch(() => settle(false)); }); }
/** * Remove an account ENTIRELY under the store's file lock: the record is * deleted and `authCleanup` (credential logout) runs inside the SAME locked * critical section, so disconnects share the store->auth lock ordering with * finalizeAttempt — a concurrent finalize can never re-add an orphan * credential after a disconnect removed the account. * * Outcomes: "removed" (record gone, write committed), "preserved" * (credential-only logout semantics: the completed record was kept while * the credential was removed — it shows the honest unbound state), * "credential-only" (no record existed; a credential-only integration * logged out), "logged-out" (the logout committed durably but the RECORD * write failed — the partial state is reported honestly and the logout is * never restored), "missing" (nothing existed), "failed" (nothing * committed: the cleanup threw or never ran). */ /** * The generic /logout route's MCP account logout: ONE store-locked op that * resolves the account id from CURRENT records, verifies the credential * removal (throwing on auth-file write failures), and acts on the CURRENT * record state. An account-key logout cancels PENDING attempts (deleting * their record) and PRESERVES completed records (honest unbound/Reconnect). * A staged-key logout CANCELS the attempt — invalidating its nonce — but * NEVER deletes the account record (another client's ordinary login may * hold the real key; deleting could orphan it) and resolves the * state-neutral "refused" outcome. */ removeAccountForProvider(options: { providerId: string; authStorage: { removeVerified: (provider: string) => boolean }; }): Promise<McpRemoveAccountResult> { return new Promise<McpRemoveAccountResult>((resolve) => { let settled = false; const settle = (result: McpRemoveAccountResult): void => { if (settled) return; settled = true; resolve(result); }; this.pendingOps.push({ kind: "removeAccountForProvider", providerId: options.providerId, authStorage: options.authStorage, resolve: settle, }); void this.flush().catch(() => settle("failed")); }); }
removeAccount(options: { connectionId: string; /** Returns whether a credential was actually removed. */ authCleanup: (connectionId: string) => boolean; /** * Credential-only logout semantics (the generic /logout route): pending * attempts are cancelled, completed records are PRESERVED. */ preserveCompletedRecord?: boolean; }): Promise<McpRemoveAccountResult> { return new Promise<McpRemoveAccountResult>((resolve) => { let settled = false; const settle = (result: McpRemoveAccountResult): void => { if (settled) return; settled = true; resolve(result); }; this.pendingOps.push({ kind: "removeAccount", connectionId: options.connectionId, authCleanup: options.authCleanup, ...(options.preserveCompletedRecord ? { preserveCompletedRecord: true } : {}), resolve: settle, }); void this.flush().catch(() => settle("failed")); }); }
/** * Guarded credential commit for a successful login attempt. `commit` runs * under the store's file lock ONLY when the record still carries OUR * attempt nonce (any record status — a claimed connected/error account * finalizes like a pending reservation) — the caller moves staged * credentials there, sharing this lock's ordering with cancel/remove. * A successful finalize CONSUMES the nonce itself, so a late callback can * never replay ownership. Resolves "committed" only after the record write * commits; "denied" when ownership was lost; "compensated"/"recovery- * required" when the write failed (see McpFinalizeResult) — the staged * credential then stays under the attempt key for the caller to discard. */ finalizeAttempt(options: { connectionId: string; attemptId: string; commit: (record: McpConnectionRecord) => McpConnectionRecord; /** Rolls caller-side effects back under the lock when the write fails. */ compensate?: () => void; }): Promise<McpFinalizeResult> { return new Promise<McpFinalizeResult>((resolve) => { let settled = false; const settle = (result: McpFinalizeResult): void => { if (settled) return; settled = true; resolve(result); }; this.pendingOps.push({ kind: "finalizeAttempt", connectionId: options.connectionId, attemptId: options.attemptId, commit: options.commit, ...(options.compensate ? { compensate: options.compensate } : {}), resolve: settle, }); void this.flush().catch(() => settle("denied")); }); }
/** * Queue a verification result behind a guard. The guard is re-evaluated at flush * time under the file lock, so a result computed against an old grant (or a * connection that has since been disconnected) is discarded instead of * resurrecting a stale record. */ queueVerifyResult( record: McpConnectionRecord, isStillCurrent: () => boolean, options?: { expectedRecord: McpConnectionRecord | undefined }, ): Promise<boolean> { const expected = options ? options.expectedRecord : this.get(record.connectionId); return new Promise((resolve) => { let settled = false; const settle = (committed: boolean): void => { if (settled) return; settled = true; resolve(committed); }; this.pendingOps.push({ kind: "verify", record: { ...record }, expectedRecord: expected ? { ...expected } : undefined, isStillCurrent, resolve: settle, }); }); }
/** * Persist pending mutations. Serialized within this instance; the file lock * orders us against other processes. Each flush re-reads the latest on-disk * state and applies only this instance's pending operations, so concurrent * writers cannot lose each other's records. */ flush(): Promise<void> { const run = async (): Promise<void> => { let lockCompromised = false; let lockCompromisedError: Error | undefined; // Splice THIS run's batch BEFORE acquiring the lock: a lock-acquisition // failure can then resolve one-shot attempt ops false and drop them — // no denied reservation or finalize can materialize on a later flush. // Ops queued while we wait for the lock stay in pendingOps and belong // to the NEXT flush; the requeue below keeps this batch ahead of them. const operations = this.pendingOps.splice(0); let release: Awaited<ReturnType<typeof lockfile.lock>>; try { mkdirSync(dirname(this.path), { recursive: true }); // Exclusive, non-truncating create. The previous exists-then-write-empty // could rename an empty file over records another process had just // written; "wx" only ever creates a brand-new inode, so a first writer // can never wipe anything. Content always comes from the locked // read-modify-write below, so the winner writes nothing here. try { closeSync(openSync(this.path, "wx", 0o600)); chmodSync(this.path, 0o600); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; } release = await lockfile.lock(realpathIfPresentSync(this.path), { retries: { retries: 10, factor: 2, minTimeout: 100, maxTimeout: 10000, randomize: true, }, stale: 30000, onCompromised: (error) => { lockCompromised = true; lockCompromisedError = error as Error; }, }); } catch (lockError) { // The lock never acquired: durable (idempotent) ops retry on the // next flush; one-shot attempt ops settle false and are dropped — // the caller already learned "no", so a later materialization // would be a ghost. this.pendingOps.unshift(...operations.filter((operation) => !isOneShotOperation(operation))); for (const operation of operations) { if (isOneShotOperation(operation)) { if (operation.kind === "finalizeAttempt") operation.resolve("denied"); else if (operation.kind === "removeAccount") operation.resolve("failed"); else if (operation.kind === "removeAccountForProvider") operation.resolve("failed"); else operation.resolve(false); } } throw lockError; } let committed = false; try { if (lockCompromised) throw lockCompromisedError ?? new Error("MCP connection store lock was compromised"); const records = parseRecords(safeReadFileSync(realpathIfPresentSync(this.path))); // One-shot attempt operations (reserve/remove/finalize/removeAccount) // resolve only AFTER the atomic write commits: a failed write means // no durable reservation/removal/finalize happened, so the caller // must NOT act on a ghost. Losing branches resolve false // immediately. Every one-shot op settles EXACTLY once — a throwing // callback, a failed write, or trailing ops in a failed batch can // never leave a promise hanging or a one-shot requeued. const deferred: Array<(committed: boolean) => void> = []; const oneShot = new Set<PendingOp>(); const appliedFinalizes: Array<{ operation: Extract<PendingOp, { kind: "finalizeAttempt" }>; rollback: () => void; }> = []; const committedFinalizes = new Set<PendingOp>(); try { for (const operation of operations) { if (operation.kind === "remove") { records.delete(operation.connectionId); } else if (operation.kind === "claimConnectionId") { oneShot.add(operation); try { const existing = records.get(operation.connectionId); if ( existing === undefined || existing.attemptId !== undefined || (operation.isStillCurrent !== undefined && !operation.isStillCurrent(existing)) ) { operation.resolve(false); } else { // Stamp OUR nonce on the existing record (any // status): the guarded reconnect owns it for the // finalize; cancel releases via releaseClaim. records.set(operation.connectionId, { ...existing, attemptId: operation.attemptId, }); deferred.push((didCommit) => operation.resolve(didCommit)); } } catch { operation.resolve(false); } } else if (operation.kind === "releaseClaim") { oneShot.add(operation); try { const existing = records.get(operation.connectionId); if ( existing === undefined || existing.attemptId === undefined || existing.attemptId !== operation.attemptId ) { operation.resolve(false); } else { // Release OUR claim only: the record, its // status, and any credential survive untouched. const { attemptId: _released, ...withoutClaim } = existing; records.set(operation.connectionId, withoutClaim); deferred.push((didCommit) => operation.resolve(didCommit)); } } catch { operation.resolve(false); } } else if (operation.kind === "upsert") { this.applyUpsert(records, operation.record); } else if (operation.kind === "reserve") { oneShot.add(operation); try { // Atomic under the file lock: exactly one cross-process // contender wins the id; the loser allocates another. if (records.has(operation.record.connectionId)) { operation.resolve(false); } else { this.applyUpsert(records, operation.record); deferred.push((didCommit) => operation.resolve(didCommit)); } } catch { operation.resolve(false); } } else if (operation.kind === "removeReservation") { oneShot.add(operation); try { const existing = records.get(operation.connectionId); if ( existing?.status === "pending" && existing.attemptId !== undefined && existing.attemptId === operation.attemptId ) { records.delete(operation.connectionId); deferred.push((didCommit) => operation.resolve(didCommit)); } else { // Not ours (completed, replaced, or gone): a late // callback never removes another account's record. operation.resolve(false); } } catch { operation.resolve(false); } } else if (operation.kind === "removeAccountForProvider") { oneShot.add(operation); try { // Resolve from CURRENT state under THIS lock: a // staged-key selection that raced a completing // finalize acts on what the lock sees — never a // pre-lock cache snapshot. const target = resolveMcpAccountLogoutTarget(operation.providerId, [...records.values()]); { const accountKey = `mcp:${target.connectionId}`; const stagedSelection = target.credentialKey !== accountKey; // Verified cleanup FIRST (store->auth): a // throwing auth write fails the whole op // and changes NO record. const credentialRemoved = operation.authStorage.removeVerified(target.credentialKey); if (stagedSelection) { // A staged-key logout CANCELS the attempt but // NEVER deletes the account record: another // client's ordinary login may hold the real // key at any moment, and a credential // snapshot is not atomic with it — deleting // the record could orphan that credential. // The record stays visible (honest pending / // unbound state); an explicit account Remove // deletes it later. const existing = records.get(target.connectionId); if (existing?.attemptId !== undefined) { // Invalidate the ACTIVE attempt nonce on ANY status // (pending marker or a claimed connected/error // account): the in-flight login can no longer // finalize, and its denied path can never delete // this preserved record. const { attemptId: _cancelled, ...withoutAttempt } = existing; records.set(target.connectionId, withoutAttempt); } // State-neutral outcome: the attempt is no // longer current; the account keeps whatever // state another login gave it. deferred.push((didCommit) => didCommit ? operation.resolve("refused") : // The staged credential removal committed but the // nonce invalidation failed to save: honest partial. operation.resolve(credentialRemoved ? "logged-out" : "failed"), ); } else { // Account-key logout: cancel PENDING attempts // (the record goes with the attempt), PRESERVE // completed records (honest unbound/Reconnect) — // clearing any ACTIVE claim nonce so an // in-flight reconnect cannot re-write the // credential the user just logged out. const existing = records.get(target.connectionId); const existed = existing !== undefined; const removeRecord = existed && existing.status === "pending"; if (removeRecord) { records.delete(target.connectionId); } else if (existing?.attemptId !== undefined) { const { attemptId: _cancelled, ...withoutAttempt } = existing; records.set(target.connectionId, withoutAttempt); } const outcome: McpRemoveAccountResult = removeRecord ? "removed" : existed ? "preserved" : credentialRemoved ? "credential-only" : "missing"; deferred.push((didCommit) => didCommit ? operation.resolve(outcome) : // The logout committed durably but the record save failed: // report the honest partial state, never restore. operation.resolve(credentialRemoved ? "logged-out" : "failed"), ); } } } catch { operation.resolve("failed"); } } else if (operation.kind === "removeAccount") { oneShot.add(operation); try { // Verified cleanup FIRST, under the lock: a throwing // auth-file write must NOT leave a deleted record // behind — the working record/pending attempt // SURVIVES a failed logout (nothing was // cancelled or claimed). Cleanup runs even when // no record exists (credential-only logouts). // The generic logout route preserves COMPLETED // records (they show the honest unbound state) // while cancelling PENDING attempts. const credentialRemoved = operation.authCleanup(operation.connectionId); const existing = records.get(operation.connectionId); const existed = existing !== undefined; const removeRecord = existed && (!operation.preserveCompletedRecord || existing.status === "pending"); if (removeRecord) { records.delete(operation.connectionId); } const outcome: McpRemoveAccountResult = removeRecord ? "removed" : existed ? "preserved" : credentialRemoved ? "credential-only" : "missing"; deferred.push((didCommit) => didCommit ? operation.resolve(outcome) : // The credential logout already committed durably: never claim // plain failure. "logged-out" reports the honest partial // state (credential gone, record save failed, retry to // finish); the logout is PRESERVED, never restored. operation.resolve(credentialRemoved ? "logged-out" : "failed"), ); } catch { operation.resolve("failed"); } } else if (operation.kind === "finalizeAttempt") { oneShot.add(operation); try { const existing = records.get(operation.connectionId); if (existing?.attemptId !== undefined && existing.attemptId === operation.attemptId) { // Ownership is the EXPLICIT nonce, on any record status: // a claimed connected/error account (guarded reconnect) // finalizes like a pending reservation. // Recovery is registered BEFORE any side effect: // a throwing or partial commit callback still // gets its rollback attempt under this lock. const rollback = operation.compensate ?? (() => {}); try { const committedRecord = operation.commit(existing); appliedFinalizes.push({ operation, rollback }); const { attemptId: _consumed, ...completedRecord } = committedRecord; this.applyUpsert(records, completedRecord); committedFinalizes.add(operation); } catch { // Partial commit: roll back immediately. try { rollback(); operation.resolve("compensated"); } catch { operation.resolve("recovery-required"); } } } else { // Ownership lost (removed, replaced by another // attempt, or completed elsewhere): the credential // commit must NOT land on this account. operation.resolve("denied"); } } catch { operation.resolve("recovery-required"); } } else { oneShot.add(operation); const current = records.get(operation.record.connectionId); if ( current?.attemptId === undefined && sameConnectionRecord(current, operation.expectedRecord) && operation.isStillCurrent() ) { this.applyUpsert(records, operation.record); deferred.push((didCommit) => operation.resolve(didCommit)); } else { operation.resolve(false); } } } writeFileAtomicSync(realpathIfPresentSync(this.path), serializeRecords(records), { mode: 0o600, }); if (lockCompromised) throw lockCompromisedError ?? new Error("MCP connection store lock was compromised"); this.recordsById = records; committed = true; for (const operation of committedFinalizes) { (operation as unknown as { resolve: (result: McpFinalizeResult) => void }).resolve("committed"); } for (const settle of deferred) settle(true); } finally { if (!committed) { // The write failed (or the lock was compromised): durable // records put the batch back at the FRONT — idempotent ops // retry cleanly — but one-shot attempt ops resolve false and // are dropped: no ghost reservation, no surprise finalize. this.pendingOps.unshift( ...operations.filter((operation) => !oneShot.has(operation) && operation.kind !== "verify"), ); // Finalizes whose commit callback ran get their rollback // under the SAME lock before release. A successful rollback // is honest all-or-nothing; a FAILING rollback surfaces // recovery-required instead of claiming the account is // unchanged. for (const applied of appliedFinalizes) { try { applied.rollback(); applied.operation.resolve("compensated"); } catch { applied.operation.resolve("recovery-required"); } } for (const operation of operations) { if (operation.kind === "verify") operation.resolve(false); } for (const settle of deferred) settle(false); } } } finally { if (lockCompromised) await release().catch(() => undefined); else await release(); } }; this.flushChain = this.flushChain.then(run, run); return this.flushChain; }
private applyUpsert(records: Map<string, McpConnectionRecord>, record: McpConnectionRecord): void { const now = Date.now(); const previous = records.get(record.connectionId); const next: McpConnectionRecord = { ...record, createdAt: previous?.createdAt ?? record.createdAt ?? now, updatedAt: now, lastError: record.lastError ? record.lastError.slice(0, MAX_LAST_ERROR_LENGTH) : undefined, }; if (next.status !== "connected") { delete next.verifiedAt; delete next.toolCount; } records.set(record.connectionId, next); }}
function sameConnectionRecord(left: McpConnectionRecord | undefined, right: McpConnectionRecord | undefined): boolean { if (left === undefined || right === undefined) return left === right; const fields: readonly (keyof McpConnectionRecord)[] = [ "connectionId", "serviceId", "endpoint", "label", "status", "createdAt", "updatedAt", "verifiedAt", "toolCount", "lastError", "attemptId", ]; return fields.every((field) => left[field] === right[field]);}
function safeReadFileSync(path: string): string | undefined { try { return readFileSync(path, "utf8"); } catch { return undefined; }}
/** * Outcome of an account-removal transaction. "removed": record + credential * both cleared under the lock. "credential-only": no record existed (legacy * grants, failed record saves) but the credential logout ran. "missing": * nothing to remove. "failed": the durable write failed — nothing committed. */export type McpRemoveAccountResult = | "removed" | "preserved" | "refused" | "credential-only" | "logged-out" | "missing" | "failed";
/** * Outcome of a guarded finalize. "committed": record write landed. "denied": * ownership lost, no side effects. "compensated": the write failed and the * rollback restored every side effect — honest all-or-nothing. "recovery- * required": the commit callback or the rollback itself threw; partial state * may remain and the caller must surface recovery, not claim success. */export type McpFinalizeResult = "committed" | "denied" | "compensated" | "recovery-required";