@corenel/mcp 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,17 @@
1
- import type { AgentTool } from '@corenel/protocol';
1
+ import type { AgentTool, JsonSchema } from '@corenel/protocol';
2
2
  import type { McpRegistry } from './registry';
3
+ /** `McpTool.inputSchema` is `unknown` -- some servers send no schema at all,
4
+ * or something that isn't a JSON Schema object. Never pass that through as
5
+ * `undefined`: an `AgentTool` with no `parameters` is one a model can call
6
+ * with any arguments at all, which is worse than an (accurate) empty one. */
7
+ export declare function toParameters(inputSchema: unknown): JsonSchema;
8
+ /** MCP's `tools/call` result content is `unknown` by the time it reaches
9
+ * here (httpClient.ts passes the JSON-RPC `result.content` through as-is).
10
+ * The common shape per the MCP spec is an array of content blocks
11
+ * (`{ type: 'text', text }` and friends); join the text blocks the way the
12
+ * model expects a tool result to read, and fall back to JSON for anything
13
+ * that isn't that shape rather than losing the payload. */
14
+ export declare function formatContent(content: unknown): string;
3
15
  /** Build one `AgentTool` per registry entry, in the registry's order.
4
16
  *
5
17
  * A server/tool pair whose name can't be safely packed into a wire name
@@ -1 +1 @@
1
- {"version":3,"file":"asAgentTools.d.ts","sourceRoot":"","sources":["../src/asAgentTools.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoC9C;;;;;;;oDAOoD;AACpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,SAAS,EAAE,CAa/D"}
1
+ {"version":3,"file":"asAgentTools.d.ts","sourceRoot":"","sources":["../src/asAgentTools.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQ9C;;;8EAG8E;AAC9E,wBAAgB,YAAY,CAAC,WAAW,EAAE,OAAO,GAAG,UAAU,CAG7D;AAED;;;;;4DAK4D;AAC5D,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAWtD;AAED;;;;;;;oDAOoD;AACpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,SAAS,EAAE,CAa/D"}
@@ -6,7 +6,7 @@ function isPlainObject(v) {
6
6
  * or something that isn't a JSON Schema object. Never pass that through as
7
7
  * `undefined`: an `AgentTool` with no `parameters` is one a model can call
8
8
  * with any arguments at all, which is worse than an (accurate) empty one. */
9
- function toParameters(inputSchema) {
9
+ export function toParameters(inputSchema) {
10
10
  if (isPlainObject(inputSchema))
11
11
  return inputSchema;
12
12
  return { type: 'object', properties: {} };
@@ -17,7 +17,7 @@ function toParameters(inputSchema) {
17
17
  * (`{ type: 'text', text }` and friends); join the text blocks the way the
18
18
  * model expects a tool result to read, and fall back to JSON for anything
19
19
  * that isn't that shape rather than losing the payload. */
20
- function formatContent(content) {
20
+ export function formatContent(content) {
21
21
  if (typeof content === 'string')
22
22
  return content;
23
23
  if (Array.isArray(content)) {
@@ -1 +1 @@
1
- {"version":3,"file":"asAgentTools.js","sourceRoot":"","sources":["../src/asAgentTools.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEhF,SAAS,aAAa,CAAC,CAAU;IAC/B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;8EAG8E;AAC9E,SAAS,YAAY,CAAC,WAAoB;IACxC,IAAI,aAAa,CAAC,WAAW,CAAC;QAAE,OAAO,WAAW,CAAC;IACnD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;4DAK4D;AAC5D,SAAS,aAAa,CAAC,OAAgB;IACrC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAClC,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC;YAC9E,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;oDAOoD;AACpD,MAAM,UAAU,YAAY,CAAC,QAAqB;IAChD,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC;QAChD,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,KAAK,CACX,6BAA6B,MAAM,WAAW,IAAI,CAAC,IAAI,qCAAqC;gBAC1F,iFAAiF,CACpF,CAAC;YACF,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,QAAqB,EAAE,MAAc,EAAE,IAAa;IACvE,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,KAAK;QAChB,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,aAAa,IAAI,CAAC,IAAI,gBAAgB,MAAM,MAAM,OAAO,IAAI;QACtG,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC;QAC1C,OAAO,EAAE,IAAI;QACb,KAAK,CAAC,GAAG,CAAC,IAAI;YACZ,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC5D,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3C,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAClD,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"asAgentTools.js","sourceRoot":"","sources":["../src/asAgentTools.ts"],"names":[],"mappings":"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"}
package/dist/auth.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type McpCredentialSlot } from './config';
1
2
  import type { McpServer } from './types';
2
3
  /** True when `url` is an https URL on the ORIGIN of one of `bases` and its path
3
4
  * is a hosted-relay path (`.../mcp/hosted/<name>`). Origin, not suffix: a
@@ -31,5 +32,5 @@ export interface AuthProvider {
31
32
  * a stub without a real socket -- production callers (Task 6) pass none and
32
33
  * get the real network.
33
34
  */
34
- export declare function authProviderFor(stateDir: string, server: McpServer, fetchImpl?: typeof fetch, accountToken?: () => Promise<string | null>, relayBases?: () => Promise<readonly string[]>, devApiBases?: readonly string[]): AuthProvider;
35
+ export declare function authProviderFor(stateDir: string, server: McpServer, fetchImpl?: typeof fetch, accountToken?: () => Promise<string | null>, relayBases?: () => Promise<readonly string[]>, devApiBases?: readonly string[], slot?: McpCredentialSlot): AuthProvider;
35
36
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,SAAS,CAAC;AAS7D;;;;;;;;+DAQ+D;AAC/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,WAAW,GAAE,SAAS,MAAM,EAAO,GAAG,OAAO,CAUrH;AAOD,MAAM,WAAW,YAAY;IAC3B;;;8EAG0E;IAC1E,OAAO,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACtD;;;mEAG+D;IAC/D,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AASD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,EACjB,SAAS,GAAE,OAAO,KAAa,EAW/B,YAAY,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAoB,EAG7D,UAAU,GAAE,MAAM,OAAO,CAAC,SAAS,MAAM,EAAE,CAAkB,EAG7D,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,YAAY,CA0Dd"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAsE,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACtH,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,SAAS,CAAC;AAS7D;;;;;;;;+DAQ+D;AAC/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,WAAW,GAAE,SAAS,MAAM,EAAO,GAAG,OAAO,CAUrH;AAOD,MAAM,WAAW,YAAY;IAC3B;;;8EAG0E;IAC1E,OAAO,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACtD;;;mEAG+D;IAC/D,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AASD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,EACjB,SAAS,GAAE,OAAO,KAAa,EAW/B,YAAY,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAoB,EAG7D,UAAU,GAAE,MAAM,OAAO,CAAC,SAAS,MAAM,EAAE,CAAkB,EAG7D,WAAW,GAAE,SAAS,MAAM,EAAO,EAGnC,IAAI,GAAE,iBAA+B,GACpC,YAAY,CAuEd"}
package/dist/auth.js CHANGED
@@ -19,16 +19,16 @@
19
19
  * static header that was just rejected is pure latency, never a fix.
20
20
  *
21
21
  * This module owns the refresh (`oauth/tokens.ts`) and expiry decision, and
22
- * persists a refreshed credential back through `writeMcpCredential` before
22
+ * persists a refreshed credential back through `replaceMcpCredentialIfUnchanged` before
23
23
  * handing out its headers -- Task 6's client must never read or write
24
24
  * auth.json itself, or the two would drift the way daemon.json/auth.json
25
25
  * already drift when two writers touch one file without a merge (see
26
26
  * mcp/config.ts's module comment).
27
27
  */
28
- import { readMcpCredential, writeMcpCredential } from './config.js';
28
+ import { readMcpCredentialFor, replaceMcpCredentialIfUnchanged, SERVER_SLOT } from './config.js';
29
29
  import { discoverAuthMetadata } from './oauth/discovery.js';
30
30
  import { refresh as refreshOAuthToken, isExpired } from './oauth/tokens.js';
31
- import { DEFAULT_API_BASE } from '@corenel/protocol';
31
+ import { DEFAULT_API_BASE, AGENT_CREDENTIAL_MISSING } from '@corenel/protocol';
32
32
  function safeOrigin(url) {
33
33
  try {
34
34
  return new URL(url).origin;
@@ -98,19 +98,32 @@ accountToken = async () => null,
98
98
  relayBases = async () => [],
99
99
  /* The source-run dev profile's API base, when the daemon is on it (see
100
100
  * isCorenelRelayUrl). Empty in a production run. */
101
- devApiBases = []) {
101
+ devApiBases = [],
102
+ /* Whose credential (spec 2026-10-01 Part D). An agent slot reads only that
103
+ * agent's entry and never the server's own. */
104
+ slot = SERVER_SLOT) {
102
105
  async function doRefresh(cred) {
103
106
  if (!cred.refreshToken) {
107
+ /* An agent's sign-in is its owner's to redo; the server-slot command
108
+ * below would sign the DAEMON in, not the agent. */
109
+ if (slot.kind === 'agent') {
110
+ throw new Error(`${AGENT_CREDENTIAL_MISSING}: the agent credential for MCP server "${server.name}" has expired and has no refresh token; the agent's owner must sign the agent in again.`);
111
+ }
104
112
  throw new Error(`"${server.name}" has no refresh token on file -- run: corenel mcp login ${server.name}`);
105
113
  }
106
114
  const meta = await discoverAuthMetadata(cred.authServer, fetchImpl);
107
115
  const refreshed = await refreshOAuthToken(meta, cred, fetchImpl);
108
- await writeMcpCredential(stateDir, server.name, refreshed);
116
+ /* Written only if the stored credential is still the one refreshed: a
117
+ * clear or replace that landed while the refresh was in flight wins, and
118
+ * this refreshed token (of the old credential) is discarded. */
119
+ if (!(await replaceMcpCredentialIfUnchanged(stateDir, server.name, slot, cred, refreshed))) {
120
+ throw new Error(`The credential for MCP server "${server.name}" changed while it was being refreshed; retry the call.`);
121
+ }
109
122
  return refreshed;
110
123
  }
111
124
  return {
112
125
  async headers() {
113
- const cred = await readMcpCredential(stateDir, server.name);
126
+ const cred = await readMcpCredentialFor(stateDir, server.name, slot);
114
127
  /* A HOSTED server may legitimately have no credential yet: the relay is
115
128
  * still reachable with the account token alone, and the upstream 401 is
116
129
  * what carries the WWW-Authenticate that starts the OAuth flow. A direct
@@ -122,6 +135,9 @@ devApiBases = []) {
122
135
  if (server.hosted && !isCorenelRelayUrl(server.url, [DEFAULT_API_BASE, ...(await relayBases())], devApiBases)) {
123
136
  throw new Error(`"${server.name}" is marked hosted but ${safeOrigin(server.url)} is not a Corenel relay; refusing to send the account token there.`);
124
137
  }
138
+ if (!cred && slot.kind === 'agent') {
139
+ throw new Error(`${AGENT_CREDENTIAL_MISSING}: no agent credential is set for MCP server "${server.name}"`);
140
+ }
125
141
  if (!cred && !server.hosted) {
126
142
  throw new Error(`No credential stored for MCP server "${server.name}" -- run: corenel mcp login ${server.name} (or set-header, for a static token).`);
127
143
  }
@@ -138,7 +154,7 @@ devApiBases = []) {
138
154
  return account ? [...moved, { name: 'Authorization', value: `Bearer ${account}` }] : moved;
139
155
  },
140
156
  async onUnauthorized() {
141
- const cred = await readMcpCredential(stateDir, server.name);
157
+ const cred = await readMcpCredentialFor(stateDir, server.name, slot);
142
158
  // Nothing to refresh: no credential at all, or a static header --
143
159
  // retrying a header the server just rejected fixes nothing.
144
160
  if (!cred || cred.kind !== 'oauth')
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,SAAS,UAAU,CAAC,GAAW;IAC7B,IAAI,CAAC;QAAC,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,SAAS,CAAC;IAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;+DAQ+D;AAC/D,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,KAAwB,EAAE,cAAiC,EAAE;IAC1G,IAAI,CAAM,CAAC;IACX,IAAI,CAAC;QAAC,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;IACjD,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/D,MAAM,UAAU,GAAG,CAAC,CAAS,EAAW,EAAE;QACxC,IAAI,CAAC;YAAC,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;IACxE,CAAC,CAAC;IACF,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,CAAC,GAAG,KAAK,EAAE,GAAG,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAChF,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,IAAI,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9F,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACtC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACzD,OAAO,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,KAAK,CAAC;AAC/D,CAAC;AAsBD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,MAAiB,EACjB,YAA0B,KAAK;AAC/B;;;;;;;;;sBASsB;AACtB,eAA6C,KAAK,IAAI,EAAE,CAAC,IAAI;AAC7D;sEACsE;AACtE,aAA+C,KAAK,IAAI,EAAE,CAAC,EAAE;AAC7D;oDACoD;AACpD,cAAiC,EAAE;IAEnC,KAAK,UAAU,SAAS,CAAC,IAAwB;QAC/C,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,4DAA4D,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5G,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,UAAU,EAAE,SAAsC,CAAC,CAAC;QACjG,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAoC,CAAC,CAAC;QAC5F,MAAM,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC3D,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,KAAK,CAAC,OAAO;YACX,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5D;;;mFAGuE;YACvE;;;wCAG4B;YAC5B,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,UAAU,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,EAAE,CAAC;gBAC9G,MAAM,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,0BAA0B,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,oEAAoE,CAAC,CAAC;YACvJ,CAAC;YACD,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,wCAAwC,MAAM,CAAC,IAAI,+BAA+B,MAAM,CAAC,IAAI,uCAAuC,CAAC,CAAC;YACxJ,CAAC;YAED,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;gBAC3B,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO;oBACtC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAE/H,IAAI,CAAC,MAAM,CAAC,MAAM;gBAAE,OAAO,UAAU,CAAC;YAEtC;;mEAEuD;YACvD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAClC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,eAAe,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAC/F,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,YAAY,EAAE,CAAC;YACrC,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC7F,CAAC;QAED,KAAK,CAAC,cAAc;YAClB,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5D,kEAAkE;YAClE,4DAA4D;YAC5D,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,KAAK,CAAC;YACjD,IAAI,CAAC;gBACH,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;gBACtB,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -82,4 +82,33 @@ export declare function clearMcpCredential(stateDir: string, server: string): Pr
82
82
  * registered OAuth client. Called whenever a server is removed. */
83
83
  export declare function forgetMcpServerSecrets(stateDir: string, server: string): Promise<void>;
84
84
  export declare function writeMcpCredential(stateDir: string, server: string, cred: McpCredential): Promise<void>;
85
+ /** Whose credential a call uses: the server's own (auth.json `mcp`), or one
86
+ * team agent's (auth.json `agents[accessKey]`, spec 2026-10-01 Part D). An
87
+ * agent slot NEVER falls back to the server's: that would be the daemon's
88
+ * credential acting for the agent. */
89
+ export type McpCredentialSlot = {
90
+ kind: 'server';
91
+ } | {
92
+ kind: 'agent';
93
+ accessKey: string;
94
+ };
95
+ export declare const SERVER_SLOT: McpCredentialSlot;
96
+ export declare function readMcpCredentialFor(stateDir: string, server: string, slot: McpCredentialSlot): Promise<McpCredential | null>;
97
+ export declare function writeMcpCredentialFor(stateDir: string, server: string, slot: McpCredentialSlot, cred: McpCredential): Promise<void>;
98
+ /**
99
+ * Write `next` into `slot`'s credential for `server` ONLY if the stored one is
100
+ * still `expected`, in one read-modify-write (Task 9 review fix round 1).
101
+ *
102
+ * A token refresh reads a credential, goes to the network, and writes the
103
+ * result back. If the owner cleared or replaced the credential meanwhile, an
104
+ * unconditional write would bring the revoked one back and persist it. Returns
105
+ * false, writing nothing, when the slot is empty or holds something else.
106
+ * Reads strictly (docForClear): an unreadable auth.json throws, and a missing
107
+ * or corrupt one holds nothing to replace.
108
+ */
109
+ export declare function replaceMcpCredentialIfUnchanged(stateDir: string, server: string, slot: McpCredentialSlot, expected: McpCredential, next: McpCredential): Promise<boolean>;
110
+ /** Forget one agent's credential for one server. */
111
+ export declare function clearAgentCredential(stateDir: string, accessKey: string, server: string): Promise<void>;
112
+ /** Forget every credential of one agent (the agent was deleted). */
113
+ export declare function clearAgentCredentials(stateDir: string, accessKey: string): Promise<void>;
85
114
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAUxD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAyBzF;AAED;;gDAEgD;AAChD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3F;AAmDD;;;;uBAIuB;AACvB,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAMvG;AAED;;;;;GAKG;AACH,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;yEAEqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;sDAQkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAaD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAMzG;AAED;;+DAE+D;AAC/D,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjH;AAED;;gEAEgE;AAChE,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpF;AAED;;yDAEyD;AACzD,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxF;AAED;oEACoE;AACpE,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5F;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAa7G"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -20,10 +20,11 @@
20
20
  * replacing the whole file instead of merging into it would sign the user
21
21
  * out (or drop an unrelated server's credential) as a side effect of an
22
22
  * MCP write neither operation has any business touching. The other
23
- * direction of that same merge matters too: auth/store.ts's
24
- * `saveCredential` (every sign-in and token refresh) must preserve this
25
- * module's `mcp` section rather than overwrite the whole file with just
26
- * the user's own credential -- see the comment there.
23
+ * direction of that same merge matters too: @corenel/tools-node's
24
+ * `writeLoginCredential` (every sign-in) and `clearAllCredentials`
25
+ * (every sign-out) must preserve this module's `mcp`, `mcpClients` and
26
+ * `agents` sections rather than replace or delete the whole file -- see
27
+ * the comments there.
27
28
  *
28
29
  * 3. readMcpCredential holds a stored entry to the SAME "never partially
29
30
  * applied" standard as readMcpServers: `isUsableCredential` checks the
@@ -31,8 +32,8 @@
31
32
  * half-written credential reads as absent, not as a broken one handed to
32
33
  * a caller.
33
34
  */
34
- import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
35
- import { credentialFilePath, credentialDir } from '@corenel/tools-node';
35
+ import { readFile, mkdir } from 'node:fs/promises';
36
+ import { credentialFilePath, credentialDir, writeFilePrivateAtomic, readAuthDocForWrite, backupCorruptAuthDoc, withAuthDocLock } from '@corenel/tools-node';
36
37
  import { readDaemonConfig, readDaemonConfigLayered, writeDaemonConfig } from '@corenel/config';
37
38
  /** True for a value that is a usable {name,url} server entry -- the shape
38
39
  * `readMcpServers` requires of every element it keeps. */
@@ -95,7 +96,12 @@ export async function writeMcpServers(stateDir, servers) {
95
96
  }
96
97
  /** Read auth.json as a bag of top-level keys, never throwing -- a missing or
97
98
  * corrupt file reads as empty, exactly like readDaemonConfig treats
98
- * daemon.json, so a credential lookup can never crash the daemon. */
99
+ * daemon.json, so a credential lookup can never crash the daemon.
100
+ *
101
+ * READ-ONLY callers only. A writer must not use this: taking a file it could
102
+ * not read as empty and writing the document back drops every other section
103
+ * (`mcp`, `mcpClients`, `agents`, the sign-in). Writers use docForWrite or
104
+ * docForClear below. */
99
105
  async function readAuthJson(stateDir) {
100
106
  let raw;
101
107
  try {
@@ -114,6 +120,34 @@ async function readAuthJson(stateDir) {
114
120
  return {};
115
121
  }
116
122
  }
123
+ /** Every read-modify-write of auth.json below runs under this process's
124
+ * auth.json lock (@corenel/tools-node withAuthDocLock, shared with sign-in
125
+ * and sign-out), so two writers never read the same document and the second
126
+ * rename never drops the first one's change. Not re-entrant: a locked body
127
+ * never calls another locked writer. */
128
+ function locked(stateDir, fn) {
129
+ return withAuthDocLock(credentialFilePath(stateDir), fn);
130
+ }
131
+ /** auth.json for a writer that ADDS or REPLACES something: the same read a
132
+ * sign-in does (@corenel/tools-node readAuthDocForWrite). Missing is `{}`; a
133
+ * file that exists but cannot be read throws before anything is written; a
134
+ * corrupt one is backed up to `auth.json.bad-<ms>` and then reads as `{}`. */
135
+ async function docForWrite(stateDir) {
136
+ const path = credentialFilePath(stateDir);
137
+ const read = await readAuthDocForWrite(path, 'save');
138
+ if (read.kind === 'doc')
139
+ return read.doc;
140
+ if (read.kind === 'corrupt')
141
+ await backupCorruptAuthDoc(path, read.bytes);
142
+ return {};
143
+ }
144
+ /** auth.json for a writer that only REMOVES something: null when there is
145
+ * nothing to remove from (missing, or corrupt, which holds nothing usable and
146
+ * is left exactly as it is). Throws on a read error, like docForWrite. */
147
+ async function docForClear(stateDir) {
148
+ const read = await readAuthDocForWrite(credentialFilePath(stateDir), 'save');
149
+ return read.kind === 'doc' ? read.doc : null;
150
+ }
117
151
  function isMcpAuthSection(v) {
118
152
  return typeof v === 'object' && v !== null && !Array.isArray(v);
119
153
  }
@@ -189,84 +223,265 @@ export async function readMcpClient(stateDir, server) {
189
223
  * writeMcpCredential does -- same file, same 0600, same preservation of every
190
224
  * other key including ones this build knows nothing about. */
191
225
  export async function writeMcpClient(stateDir, server, client) {
192
- const doc = await readAuthJson(stateDir);
193
- const prev = doc.mcpClients;
194
- const section = (typeof prev === 'object' && prev !== null && !Array.isArray(prev))
195
- ? { ...prev }
196
- : {};
197
- section[server] = client;
198
- const next = { ...doc, mcpClients: section };
199
- const path = credentialFilePath(stateDir);
200
- await mkdir(credentialDir(stateDir), { recursive: true });
201
- await writeFile(path, `${JSON.stringify(next, null, 2)}
202
- `, { mode: 0o600 });
203
- try {
204
- await chmod(path, 0o600);
205
- }
206
- catch {
207
- /* best effort on platforms without POSIX modes, matching auth/store.ts */
208
- }
226
+ assertServerName(server);
227
+ await locked(stateDir, async () => {
228
+ const doc = await docForWrite(stateDir);
229
+ const prev = doc.mcpClients;
230
+ const section = (typeof prev === 'object' && prev !== null && !Array.isArray(prev))
231
+ ? { ...prev }
232
+ : {};
233
+ section[server] = client;
234
+ await writeAuthJson(stateDir, { ...doc, mcpClients: section });
235
+ });
209
236
  }
210
237
  /** Forget `server`'s registered client, so the next sign-in registers afresh.
211
238
  * Used when a server rejects the stored client_id -- the registration is gone
212
239
  * on their side and keeping it only reproduces the failure. */
213
240
  export async function clearMcpClient(stateDir, server) {
214
- const doc = await readAuthJson(stateDir);
215
- const prev = doc.mcpClients;
216
- if (typeof prev !== 'object' || prev === null || Array.isArray(prev))
217
- return;
218
- const section = { ...prev };
219
- if (!(server in section))
220
- return;
221
- delete section[server];
222
- const path = credentialFilePath(stateDir);
223
- await mkdir(credentialDir(stateDir), { recursive: true });
224
- await writeFile(path, `${JSON.stringify({ ...doc, mcpClients: section }, null, 2)}
225
- `, { mode: 0o600 });
226
- try {
227
- await chmod(path, 0o600);
228
- }
229
- catch { /* as above */ }
241
+ await locked(stateDir, async () => {
242
+ const doc = await docForClear(stateDir);
243
+ if (!doc)
244
+ return;
245
+ const prev = doc.mcpClients;
246
+ if (typeof prev !== 'object' || prev === null || Array.isArray(prev))
247
+ return;
248
+ const section = { ...prev };
249
+ if (!(server in section))
250
+ return;
251
+ delete section[server];
252
+ await writeAuthJson(stateDir, { ...doc, mcpClients: section });
253
+ });
230
254
  }
231
255
  /** Forget `server`'s stored credential. Credentials are keyed by NAME, so a
232
256
  * removed server's credential must go with it: otherwise a later add under
233
257
  * the same name -- pointing anywhere -- inherits it. */
234
258
  export async function clearMcpCredential(stateDir, server) {
235
- const doc = await readAuthJson(stateDir);
236
- if (!isMcpAuthSection(doc.mcp))
237
- return;
238
- const section = { ...doc.mcp };
239
- if (!(server in section))
240
- return;
241
- delete section[server];
242
- const path = credentialFilePath(stateDir);
243
- await mkdir(credentialDir(stateDir), { recursive: true });
244
- await writeFile(path, `${JSON.stringify({ ...doc, mcp: section }, null, 2)}
245
- `, { mode: 0o600 });
246
- try {
247
- await chmod(path, 0o600);
248
- }
249
- catch { /* best effort, as above */ }
259
+ await locked(stateDir, async () => {
260
+ const doc = await docForClear(stateDir);
261
+ if (!doc)
262
+ return;
263
+ if (!isMcpAuthSection(doc.mcp))
264
+ return;
265
+ const section = { ...doc.mcp };
266
+ if (!(server in section))
267
+ return;
268
+ delete section[server];
269
+ await writeAuthJson(stateDir, { ...doc, mcp: section });
270
+ });
250
271
  }
251
272
  /** Everything stored for `server` in auth.json: its credential and its
252
273
  * registered OAuth client. Called whenever a server is removed. */
253
274
  export async function forgetMcpServerSecrets(stateDir, server) {
254
275
  await clearMcpCredential(stateDir, server);
255
276
  await clearMcpClient(stateDir, server);
277
+ await clearServerFromAllAgents(stateDir, server);
256
278
  }
257
279
  export async function writeMcpCredential(stateDir, server, cred) {
258
- const doc = await readAuthJson(stateDir);
259
- const section = isMcpAuthSection(doc.mcp) ? { ...doc.mcp } : {};
260
- section[server] = cred;
261
- const next = { ...doc, mcp: section };
262
- const path = credentialFilePath(stateDir);
280
+ assertServerName(server);
281
+ await locked(stateDir, async () => {
282
+ const doc = await docForWrite(stateDir);
283
+ const section = isMcpAuthSection(doc.mcp) ? { ...doc.mcp } : {};
284
+ section[server] = cred;
285
+ await writeAuthJson(stateDir, { ...doc, mcp: section });
286
+ });
287
+ }
288
+ export const SERVER_SLOT = { kind: 'server' };
289
+ /** Bound on an access key used as a JSON key: it is wire-reachable. */
290
+ const MAX_ACCESS_KEY_LEN = 512;
291
+ /** `__proto__` as a key of a plain object sets its PROTOTYPE instead of an
292
+ * own property, so the write would vanish (JSON.stringify skips it) or, read
293
+ * back, resolve to Object.prototype. Refused wherever a wire-reachable string
294
+ * becomes a key in auth.json. */
295
+ const FORBIDDEN_KEY = '__proto__';
296
+ function assertAccessKey(accessKey) {
297
+ if (typeof accessKey !== 'string' || !accessKey || accessKey.length > MAX_ACCESS_KEY_LEN || accessKey === FORBIDDEN_KEY) {
298
+ throw new Error('invalid agent access key');
299
+ }
300
+ }
301
+ function assertServerName(server) {
302
+ if (typeof server !== 'string' || !server || server === FORBIDDEN_KEY) {
303
+ throw new Error('invalid MCP server name');
304
+ }
305
+ }
306
+ /** auth.json `agents`, or an empty object for absent or malformed. */
307
+ function agentsOf(doc) {
308
+ const s = doc.agents;
309
+ return typeof s === 'object' && s !== null && !Array.isArray(s) ? { ...s } : {};
310
+ }
311
+ /** The ONE auth.json writer in this module: a 0600 temp file beside it,
312
+ * renamed over it (@corenel/tools-node writeFilePrivateAtomic, the same
313
+ * publish a sign-in uses). A truncate-then-fill write is seen half-done by a
314
+ * concurrent reader, and a sign-in reading a torn file to keep these sections
315
+ * would read it as empty. */
316
+ async function writeAuthJson(stateDir, next) {
263
317
  await mkdir(credentialDir(stateDir), { recursive: true });
264
- await writeFile(path, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
265
318
  try {
266
- await chmod(path, 0o600);
319
+ await writeFilePrivateAtomic(credentialFilePath(stateDir), `${JSON.stringify(next, null, 2)}\n`);
267
320
  }
268
- catch {
269
- /* best effort on platforms without POSIX modes, matching auth/store.ts */
321
+ catch (err) {
322
+ /* Windows refuses a rename over a file another process holds open. The
323
+ * live file is untouched (the temp is cleaned up), so this is a retry,
324
+ * not a loss: say so, and keep the code for anything that branches on it. */
325
+ const code = typeof err === 'object' && err !== null ? err.code : undefined;
326
+ if (code === 'EPERM' || code === 'EBUSY') {
327
+ throw Object.assign(new Error(`auth.json is in use; retry (${code})`), { code });
328
+ }
329
+ throw err;
330
+ }
331
+ }
332
+ export async function readMcpCredentialFor(stateDir, server, slot) {
333
+ if (slot.kind === 'server')
334
+ return readMcpCredential(stateDir, server);
335
+ assertAccessKey(slot.accessKey);
336
+ assertServerName(server);
337
+ const agents = agentsOf(await readAuthJson(stateDir));
338
+ if (!Object.prototype.hasOwnProperty.call(agents, slot.accessKey))
339
+ return null;
340
+ const perAgent = agents[slot.accessKey];
341
+ if (!isMcpAuthSection(perAgent))
342
+ return null;
343
+ if (!Object.prototype.hasOwnProperty.call(perAgent, server))
344
+ return null;
345
+ const cred = perAgent[server];
346
+ return isUsableCredential(cred) ? cred : null;
347
+ }
348
+ export async function writeMcpCredentialFor(stateDir, server, slot, cred) {
349
+ if (slot.kind === 'server')
350
+ return writeMcpCredential(stateDir, server, cred);
351
+ assertAccessKey(slot.accessKey);
352
+ assertServerName(server);
353
+ const key = slot.accessKey;
354
+ await locked(stateDir, async () => {
355
+ const doc = await docForWrite(stateDir);
356
+ const agents = agentsOf(doc);
357
+ const prev = agents[key];
358
+ const perAgent = isMcpAuthSection(prev) ? { ...prev } : {};
359
+ perAgent[server] = cred;
360
+ agents[key] = perAgent;
361
+ await writeAuthJson(stateDir, { ...doc, agents });
362
+ });
363
+ }
364
+ /** True when `stored` is still the credential `expected` was read as: same
365
+ * kind and, for OAuth, the same refresh token (or access token when there is
366
+ * none); for headers, the same header list. */
367
+ function sameCredential(stored, expected) {
368
+ if (stored.kind === 'oauth' && expected.kind === 'oauth') {
369
+ return expected.refreshToken !== undefined
370
+ ? stored.refreshToken === expected.refreshToken
371
+ : stored.refreshToken === undefined && stored.accessToken === expected.accessToken;
372
+ }
373
+ if (stored.kind === 'headers' && expected.kind === 'headers') {
374
+ return JSON.stringify(stored.headers) === JSON.stringify(expected.headers);
375
+ }
376
+ return false;
377
+ }
378
+ /**
379
+ * Write `next` into `slot`'s credential for `server` ONLY if the stored one is
380
+ * still `expected`, in one read-modify-write (Task 9 review fix round 1).
381
+ *
382
+ * A token refresh reads a credential, goes to the network, and writes the
383
+ * result back. If the owner cleared or replaced the credential meanwhile, an
384
+ * unconditional write would bring the revoked one back and persist it. Returns
385
+ * false, writing nothing, when the slot is empty or holds something else.
386
+ * Reads strictly (docForClear): an unreadable auth.json throws, and a missing
387
+ * or corrupt one holds nothing to replace.
388
+ */
389
+ export async function replaceMcpCredentialIfUnchanged(stateDir, server, slot, expected, next) {
390
+ assertServerName(server);
391
+ if (slot.kind === 'agent')
392
+ assertAccessKey(slot.accessKey);
393
+ return locked(stateDir, () => replaceIfUnchangedLocked(stateDir, server, slot, expected, next));
394
+ }
395
+ async function replaceIfUnchangedLocked(stateDir, server, slot, expected, next) {
396
+ const doc = await docForClear(stateDir);
397
+ if (!doc)
398
+ return false;
399
+ if (slot.kind === 'server') {
400
+ if (!isMcpAuthSection(doc.mcp))
401
+ return false;
402
+ const section = { ...doc.mcp };
403
+ if (!Object.prototype.hasOwnProperty.call(section, server))
404
+ return false;
405
+ const stored = section[server];
406
+ if (!isUsableCredential(stored) || !sameCredential(stored, expected))
407
+ return false;
408
+ section[server] = next;
409
+ await writeAuthJson(stateDir, { ...doc, mcp: section });
410
+ return true;
411
+ }
412
+ const agents = agentsOf(doc);
413
+ if (!Object.prototype.hasOwnProperty.call(agents, slot.accessKey))
414
+ return false;
415
+ const prev = agents[slot.accessKey];
416
+ if (!isMcpAuthSection(prev) || !Object.prototype.hasOwnProperty.call(prev, server))
417
+ return false;
418
+ const perAgent = { ...prev };
419
+ const stored = perAgent[server];
420
+ if (!isUsableCredential(stored) || !sameCredential(stored, expected))
421
+ return false;
422
+ perAgent[server] = next;
423
+ agents[slot.accessKey] = perAgent;
424
+ await writeAuthJson(stateDir, { ...doc, agents });
425
+ return true;
426
+ }
427
+ /** Forget one agent's credential for one server. */
428
+ export async function clearAgentCredential(stateDir, accessKey, server) {
429
+ assertAccessKey(accessKey);
430
+ assertServerName(server);
431
+ await locked(stateDir, async () => {
432
+ const doc = await docForClear(stateDir);
433
+ if (!doc)
434
+ return;
435
+ const agents = agentsOf(doc);
436
+ const prev = agents[accessKey];
437
+ if (!isMcpAuthSection(prev) || !Object.prototype.hasOwnProperty.call(prev, server))
438
+ return;
439
+ const perAgent = { ...prev };
440
+ delete perAgent[server];
441
+ if (Object.keys(perAgent).length)
442
+ agents[accessKey] = perAgent;
443
+ else
444
+ delete agents[accessKey];
445
+ await writeAuthJson(stateDir, { ...doc, agents });
446
+ });
447
+ }
448
+ /** Forget every credential of one agent (the agent was deleted). */
449
+ export async function clearAgentCredentials(stateDir, accessKey) {
450
+ assertAccessKey(accessKey);
451
+ await locked(stateDir, async () => {
452
+ const doc = await docForClear(stateDir);
453
+ if (!doc)
454
+ return;
455
+ const agents = agentsOf(doc);
456
+ if (!Object.prototype.hasOwnProperty.call(agents, accessKey))
457
+ return;
458
+ delete agents[accessKey];
459
+ await writeAuthJson(stateDir, { ...doc, agents });
460
+ });
461
+ }
462
+ /** Forget `server` from every agent: a removed server's agent credentials go
463
+ * with it, for the same reason its own credential does. */
464
+ async function clearServerFromAllAgents(stateDir, server) {
465
+ await locked(stateDir, () => clearServerFromAllAgentsLocked(stateDir, server));
466
+ }
467
+ async function clearServerFromAllAgentsLocked(stateDir, server) {
468
+ const doc = await docForClear(stateDir);
469
+ if (!doc)
470
+ return;
471
+ const agents = agentsOf(doc);
472
+ let changed = false;
473
+ for (const [key, prev] of Object.entries(agents)) {
474
+ if (!isMcpAuthSection(prev) || !Object.prototype.hasOwnProperty.call(prev, server))
475
+ continue;
476
+ const perAgent = { ...prev };
477
+ delete perAgent[server];
478
+ if (Object.keys(perAgent).length)
479
+ agents[key] = perAgent;
480
+ else
481
+ delete agents[key];
482
+ changed = true;
270
483
  }
484
+ if (changed)
485
+ await writeAuthJson(stateDir, { ...doc, agents });
271
486
  }
272
487
  //# sourceMappingURL=config.js.map