@securecoms/agent-bridge 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/bin.js +1779 -145
- package/dist/bridge-agent.d.ts +286 -8
- package/dist/consumer-grants.d.ts +28 -0
- package/dist/grants/binding.d.ts +2 -1
- package/dist/grants/effective.d.ts +8 -0
- package/dist/grants/manifest.d.ts +14 -0
- package/dist/grants/mcp-catalog.d.ts +23 -0
- package/dist/grants/mcp-issuance.d.ts +34 -0
- package/dist/grants/mcp-policy.d.ts +82 -0
- package/dist/grants/mcp-section.d.ts +94 -0
- package/dist/grants/owner-turn.d.ts +40 -0
- package/dist/grants/tool-status.d.ts +79 -0
- package/dist/http-server.d.ts +1 -0
- package/dist/index.js +1774 -142
- package/dist/mcp-catalog-message.d.ts +79 -0
- package/dist/mcp-catalog-rate.d.ts +35 -0
- package/dist/reply-intent.d.ts +1 -1
- package/dist/service/runtime-config-dir.d.ts +55 -0
- package/dist/tool-status-sender.d.ts +71 -0
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +1 -1
- package/package.json +2 -1
package/dist/bridge-agent.d.ts
CHANGED
|
@@ -44,10 +44,12 @@ import { VOICE_CALL_START_MARKER } from "./voice/prompt-compiler.js";
|
|
|
44
44
|
import { type CallBufferTurn } from "./voice/call-buffer.js";
|
|
45
45
|
import { type VaultListNode, type VaultReadExpectations } from "./vault.js";
|
|
46
46
|
import type { AgentActionNodeType, AgentActionNodeStatus, AgentActionNodeDetail } from "@agentvault/connect-core";
|
|
47
|
-
import { removeManagedLlmSource, writeManagedFiles, writeManifestPolicy } from "./service/runtime-config-dir.js";
|
|
47
|
+
import { removeManagedLlmSource, writeManagedFiles, writeManifestPolicy, writeMcpPolicy } from "./service/runtime-config-dir.js";
|
|
48
48
|
import { type ReplyCredit } from "./reply-credit.js";
|
|
49
49
|
import { bindRuntimeConfig, type FeatureSet } from "./grants/binding.js";
|
|
50
50
|
import type { RuntimeReplyReceipt } from "./reply-receipts.js";
|
|
51
|
+
import { type McpCatalog } from "./grants/mcp-catalog.js";
|
|
52
|
+
import { type McpCatalogLister } from "./mcp-catalog-message.js";
|
|
51
53
|
import type { SearchProvider } from "./grants/search-providers.js";
|
|
52
54
|
export type AgentAudience = "owner_dm" | "team_room" | "partner_room" | "other_human_dm" | "agent_to_agent" | "unknown";
|
|
53
55
|
export type BridgeToolGrant =
|
|
@@ -169,6 +171,13 @@ export interface BridgeAuthorization {
|
|
|
169
171
|
* for a harness socket and the HTTP/SSE paths (review m-3).
|
|
170
172
|
*/
|
|
171
173
|
readonly memory_write?: true;
|
|
174
|
+
/**
|
|
175
|
+
* MCP client Task 16 (OQ-2 ruling, audit M-4): true ONLY on an owner-DM turn whose verified owner check read the DM's
|
|
176
|
+
* leaves as exactly the pinned owner and the agent (relayLeaf === false), and only while at least one `mcp:` grant is
|
|
177
|
+
* in tool_grants (renarrowManifestGrants and withMcpCapability drop it with the last one). The runtime offers an
|
|
178
|
+
* `mcp:` tool only with it. NOT a tool grant.
|
|
179
|
+
*/
|
|
180
|
+
readonly relay_free?: true;
|
|
172
181
|
}
|
|
173
182
|
/**
|
|
174
183
|
* Spec §8.2: what ws-server tells applyMemoryNote about the socket a
|
|
@@ -357,9 +366,10 @@ export declare const ROOM_VOICE_GUIDANCE_WITH_RESPONDER = "You are in a group vo
|
|
|
357
366
|
* `voiceDefaultResponder` (never a body-supplied claim).
|
|
358
367
|
*/
|
|
359
368
|
export declare function selectVoiceGuidance(isDm: boolean | undefined, voiceDefaultResponder: boolean | undefined): string;
|
|
360
|
-
/** Owner grants plan 4 Task 11: TEST SEAM for the refetch's writers and binder (call order, throws). Production omits it. */
|
|
361
369
|
export interface GrantWriters {
|
|
362
370
|
writeManifestPolicy?: typeof writeManifestPolicy;
|
|
371
|
+
/** MCP Task 16. */
|
|
372
|
+
writeMcpPolicy?: typeof writeMcpPolicy;
|
|
363
373
|
writeManagedFiles?: typeof writeManagedFiles;
|
|
364
374
|
bindRuntimeConfig?: typeof bindRuntimeConfig;
|
|
365
375
|
removeManagedLlmSource?: typeof removeManagedLlmSource;
|
|
@@ -372,6 +382,15 @@ export interface BridgeAgentDeps {
|
|
|
372
382
|
* UNIFIED_MEMORY_PART1 decides (switch.arch.test.ts pins both).
|
|
373
383
|
*/
|
|
374
384
|
unifiedMemoryPart1?: boolean;
|
|
385
|
+
/**
|
|
386
|
+
* MCP client Task 16: TEST SEAM ONLY. The catalog the manifest verifier and mcp-policy.json use. Production (bin.ts)
|
|
387
|
+
* never passes it, so the compiled MCP_CATALOG (Phase 1: {linear}, Task 22) decides what MCP is grantable.
|
|
388
|
+
*/
|
|
389
|
+
mcpCatalog?: McpCatalog;
|
|
390
|
+
/** PR-B2 audit M-2: TEST SEAM ONLY. The H-D delivered-turn record's bound (default MCP_DELIVERED_TRACK_MAX). */
|
|
391
|
+
mcpDeliveredTrackMax?: number;
|
|
392
|
+
/** PR-B2 follow-up test seam: the backoff of the mcp-policy.json write retry (the last value repeats). */
|
|
393
|
+
mcpPolicyRetryDelaysMs?: readonly number[];
|
|
375
394
|
/**
|
|
376
395
|
* Unified memory: TEST SEAM. Production builds the real worker from dist/memory-worker.js (Task 27 pins that bin.ts
|
|
377
396
|
* never passes it).
|
|
@@ -757,12 +776,9 @@ export declare class SendForbiddenError extends Error {
|
|
|
757
776
|
readonly channelId: string;
|
|
758
777
|
constructor(channelId: string);
|
|
759
778
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
* not internal there. Dropped. A SendForbiddenError, so both transports treat
|
|
764
|
-
* it as terminal (send_forbidden: the runtime acks, nothing redelivers).
|
|
765
|
-
*/
|
|
779
|
+
export declare class McpReplyWithheldError extends SendForbiddenError {
|
|
780
|
+
constructor(channelId: string);
|
|
781
|
+
}
|
|
766
782
|
export declare class RoomClassChangedSendError extends SendForbiddenError {
|
|
767
783
|
constructor(channelId: string);
|
|
768
784
|
}
|
|
@@ -1206,6 +1222,57 @@ export declare class BridgeAgent {
|
|
|
1206
1222
|
private readonly roomDefaultGrants;
|
|
1207
1223
|
/** Owner grants (spec §7.4): the two manifest switches, read once at construction. */
|
|
1208
1224
|
private readonly ownerDmManifestGrants;
|
|
1225
|
+
/**
|
|
1226
|
+
* MCP client Task 16 (OQ-16, coordinator ruling D-7): SECURECOMS_MCP_GRANTS, read ONCE at construction. "off" writes
|
|
1227
|
+
* kill_switch (deleting); any value other than unset/"on"/"off" is suspended (non-deleting, one content-free warn).
|
|
1228
|
+
*/
|
|
1229
|
+
private readonly mcpHostSwitch;
|
|
1230
|
+
/** MCP Task 16 test seam (BridgeAgentDeps.mcpCatalog); undefined in production, so MCP_CATALOG decides. */
|
|
1231
|
+
private readonly mcpCatalog;
|
|
1232
|
+
/**
|
|
1233
|
+
* MCP Task 16 (OQ-1; PR-B1 audit M-2): the two raw /files fields from the LAST fetch. Any failed or rejected fetch
|
|
1234
|
+
* sets `fetched: false` (no_config, non-deleting): the last values are never kept to keep an `ok` document alive.
|
|
1235
|
+
*/
|
|
1236
|
+
private mcpConfig;
|
|
1237
|
+
/**
|
|
1238
|
+
* MCP Task 16: the mcp-policy.json document this process last WROTE (the file the runtime reads), or null before the
|
|
1239
|
+
* first write and after a failed one (fail closed: issuance reads only a document known to be on disk).
|
|
1240
|
+
*/
|
|
1241
|
+
private mcpPolicyWritten;
|
|
1242
|
+
private mcpPolicyWrittenJson;
|
|
1243
|
+
/** Deletion-ack ruling §3: the manifest counter in force when `mcpPolicyWrittenJson` was written (never read live). */
|
|
1244
|
+
private mcpPolicyWrittenCounter;
|
|
1245
|
+
/** Ruling §3: newest counter whose written policy the runtime fully acted on. Memory only, monotonic, starts at 0. */
|
|
1246
|
+
private mcpDeletionsThrough;
|
|
1247
|
+
/** Ruling §3: the last `credentials_present` an accepted frame carried; null until one arrives. */
|
|
1248
|
+
private mcpCredentialsPresentValue;
|
|
1249
|
+
/** Ruling §3: per attached socket, whether its latest hello declared mcp_deletion_ack_v1 (harness noted false). */
|
|
1250
|
+
private readonly mcpDeletionAckSockets;
|
|
1251
|
+
/** Ruling §3 rate: the ack-derived report values in the last report posted (null until one is). */
|
|
1252
|
+
private lastReportedMcpAck;
|
|
1253
|
+
/** #1351 round 2 M-1: bumped on every byte-changing policy write (and on the inert document being put in force). */
|
|
1254
|
+
private policyWriteSeq;
|
|
1255
|
+
/** M-1: per ack-capable socket, the policyWriteSeq at the dispatch of each owner-DM turn, by message id (bounded). */
|
|
1256
|
+
private readonly mcpTurnSeq;
|
|
1257
|
+
/**
|
|
1258
|
+
* #1351 round 2 H-1: the ack values the REPORT may carry. An ack lands seconds after an owner-DM turn and a report
|
|
1259
|
+
* posted then would time the turn (record_report sets applied_at). So, as the tool-status sender: `open` holds a
|
|
1260
|
+
* change while its 15-minute UTC bucket is open, `closed` what the report carries, and the only post is from one
|
|
1261
|
+
* timer at the next bucket boundary plus jitter.
|
|
1262
|
+
*/
|
|
1263
|
+
private mcpAckOpen;
|
|
1264
|
+
private mcpAckClosed;
|
|
1265
|
+
private mcpAckTimer;
|
|
1266
|
+
/** PR-B2 follow-up: a failed write is retried on this backoff (the last value repeats) until one lands or close(). */
|
|
1267
|
+
private readonly mcpPolicyRetryDelaysMs;
|
|
1268
|
+
private mcpPolicyRetryTimer;
|
|
1269
|
+
private mcpPolicyRetryAttempt;
|
|
1270
|
+
/** MCP Task 18: the loopback catalog listers of the attached runtime sockets that declared mcp_client_v1 (ws-server.ts). */
|
|
1271
|
+
private readonly mcpCatalogListers;
|
|
1272
|
+
/** MCP Task 18 (OQ-15): per server 1 in flight, 3 / 60 s, 20 / hour; per agent 40 / hour. Memory only. */
|
|
1273
|
+
private readonly mcpCatalogRate;
|
|
1274
|
+
/** MCP Task 18: answers in progress (outside the ingest cycle). Observable by tests; close() does not wait on them. */
|
|
1275
|
+
private readonly mcpCatalogWork;
|
|
1209
1276
|
private readonly roomManifestGrants;
|
|
1210
1277
|
/**
|
|
1211
1278
|
* Audit M-B4: DM channels whose roster was epoch-fresh at THIS ingest
|
|
@@ -1214,6 +1281,32 @@ export declare class BridgeAgent {
|
|
|
1214
1281
|
private readonly ownerDmCycleFresh;
|
|
1215
1282
|
private toolGrantManifest;
|
|
1216
1283
|
private toolGrantCounter;
|
|
1284
|
+
/**
|
|
1285
|
+
* Tool status Phase 0 (spec §3.1, §3.2; plan Task 8): the latest owner-DM last-turn record, in memory only (no store
|
|
1286
|
+
* table: after a restart there is none until the next typed owner-DM turn). ADVISORY: nothing reads it to decide a
|
|
1287
|
+
* grant. Content-free: enums, booleans, one 15-minute bucket `at` (ruling M-2), one counter, built-in names from the
|
|
1288
|
+
* allowlist. This is the latest record, open bucket included; what leaves the host is the sender's closed record.
|
|
1289
|
+
*/
|
|
1290
|
+
private ownerDmLastTurn;
|
|
1291
|
+
/**
|
|
1292
|
+
* Test seams (Task 8; ruling M-2): the cause function, the bridge clock the record and the sender read, and the
|
|
1293
|
+
* sender's timer and jitter source.
|
|
1294
|
+
*/
|
|
1295
|
+
private toolStatusCause;
|
|
1296
|
+
private toolStatusNow;
|
|
1297
|
+
private toolStatusSetTimer;
|
|
1298
|
+
private toolStatusClearTimer;
|
|
1299
|
+
private toolStatusRandom;
|
|
1300
|
+
/**
|
|
1301
|
+
* Phase 0 Task 9 (spec §3.5), ruling M-2: the post of the record to the narrow route, only just after a 15-minute
|
|
1302
|
+
* UTC bucket boundary, and only a record whose bucket has closed.
|
|
1303
|
+
*/
|
|
1304
|
+
private readonly toolStatusSender;
|
|
1305
|
+
/**
|
|
1306
|
+
* MCP Phase 1 Task 14 (OQ-3 ruling item 3): the sticky R-3 flag, mirrored from the sealed store. Starts false and is
|
|
1307
|
+
* set true at start() by the store (or by a failed read, fail closed). Only applyToolGrantManifest changes it after.
|
|
1308
|
+
*/
|
|
1309
|
+
private mcpSeen;
|
|
1217
1310
|
private manifestState;
|
|
1218
1311
|
/** Failed owner-turn checks since the last successful runtime-config report (§7.7). */
|
|
1219
1312
|
private ownerVerifyFailures;
|
|
@@ -1579,6 +1672,26 @@ export declare class BridgeAgent {
|
|
|
1579
1672
|
*/
|
|
1580
1673
|
refetchRuntimeConfig(): Promise<void>;
|
|
1581
1674
|
private refetchRuntimeConfigOnce;
|
|
1675
|
+
/** MCP Task 16 (OQ-1): the bridge's in-force entitlement reading; not fetched (or failed) is suspended. */
|
|
1676
|
+
private mcpEntitlementInForce;
|
|
1677
|
+
/** PR-B1 audit M-2: forget the last /files fields and rewrite mcp-policy.json (no_config: no grants, no deletion). */
|
|
1678
|
+
private noteMcpConfigUnfetched;
|
|
1679
|
+
/** MCP Task 16: the live inputs of mcp-policy.json (the pinned manifest in force only). */
|
|
1680
|
+
private mcpPolicyInput;
|
|
1681
|
+
/**
|
|
1682
|
+
* MCP Task 16 (spec §12.1): recompute mcp-policy.json and write it when it changed. The written document is what MCP
|
|
1683
|
+
* issuance and release-time re-narrowing read, so it is cleared BEFORE the write and set only after it lands: a failed
|
|
1684
|
+
* write leaves no document in force here (no MCP grant), and the next trigger tries again. Never throws.
|
|
1685
|
+
*/
|
|
1686
|
+
private recomputeMcpPolicy;
|
|
1687
|
+
/** A document is in force: no pending retry, and the next failure starts the backoff from the first delay. */
|
|
1688
|
+
private clearMcpPolicyRetry;
|
|
1689
|
+
/**
|
|
1690
|
+
* PR-B2 follow-up: one pending retry at a time. The retry recomputes from the state THEN (never re-writes a stale
|
|
1691
|
+
* document); any other trigger that writes successfully meanwhile cancels it. Nothing is in force on the bridge side
|
|
1692
|
+
* until a write lands (mcpPolicyWritten stays null).
|
|
1693
|
+
*/
|
|
1694
|
+
private scheduleMcpPolicyRetry;
|
|
1582
1695
|
/**
|
|
1583
1696
|
* Plan 4 Task 11: the refetch under a manifest in force. Returns false when the binding failed (H-4),
|
|
1584
1697
|
* after the memory was applied; the caller then writes nothing else. Throws only from a file write, with
|
|
@@ -2043,6 +2156,41 @@ export declare class BridgeAgent {
|
|
|
2043
2156
|
* there so a retry can resend the exact bytes).
|
|
2044
2157
|
*/
|
|
2045
2158
|
private publishBody;
|
|
2159
|
+
/**
|
|
2160
|
+
* MCP Task 16 (round 2 H-D): message ids whose DELIVERED authorization (after release-time re-narrowing) carried an
|
|
2161
|
+
* `mcp:` grant, with their channel and whether the runtime acked them. Recorded at the buffer exit, the one place
|
|
2162
|
+
* every consumer's copy leaves from (over-recording a turn a non-capable consumer then received without the grant only
|
|
2163
|
+
* adds a check). Kept after the ack, so a late frame citing the turn is still checked. Bounded: the oldest ACKED entry
|
|
2164
|
+
* goes first.
|
|
2165
|
+
*/
|
|
2166
|
+
private readonly mcpDeliveredTurns;
|
|
2167
|
+
private readonly mcpDeliveredTrackMax;
|
|
2168
|
+
/**
|
|
2169
|
+
* Record `m` when its authorization carries an `mcp:` grant. Returns false ONLY when `m` is a new turn, the record is
|
|
2170
|
+
* at its bound and no entry is acked: the caller (the buffer exit) must then issue no mcp: grant (audit M-2, fail
|
|
2171
|
+
* closed). `rebuild` (PR-B2 audit C-1 (b): a restored runtime-inbox row, or a replayed turn) records unconditionally:
|
|
2172
|
+
* those grants were issued before, so the record may exceed its bound by at most the runtime inbox's size.
|
|
2173
|
+
*/
|
|
2174
|
+
private noteMcpDelivered;
|
|
2175
|
+
/**
|
|
2176
|
+
* H-D: whether a frame to `channelId` must pass the relay-free check: it answers a recorded MCP turn (any channel:
|
|
2177
|
+
* a frame of that turn sent elsewhere is checked against ITS channel), or it cites no turn while an MCP turn is
|
|
2178
|
+
* unacked in the same channel.
|
|
2179
|
+
*/
|
|
2180
|
+
private mcpReplyGuarded;
|
|
2181
|
+
/**
|
|
2182
|
+
* H-D, synchronous: read the channel's local leaves from the warm manager in one step and tie the read to the
|
|
2183
|
+
* envelope's SEALED group and epoch, so the leaf set is exactly the membership that can open these bytes. Owner and
|
|
2184
|
+
* agent only: pass. Anyone else: withhold. The tree moved past the seal on the first attempt: StaleEpochError, which
|
|
2185
|
+
* sendWithSelfHeal answers with its one converge-and-re-seal (the existing semantics); still unprovable on the retry,
|
|
2186
|
+
* or no warm manager, no pin, or any throw: withhold (fail closed). Read-only; never touches the group.
|
|
2187
|
+
*
|
|
2188
|
+
* Scope (PR-B2 audit item 5): this guards the `send` path (publishBody) only. `status` detail, the `action_*` run
|
|
2189
|
+
* frames, `/v1/send-attachment` and summary frames are NOT checked; they are safe for MCP turns only because the
|
|
2190
|
+
* runtime emits none of them (pinned by connect-agent-runtime/src/outbound-frames.arch.test.ts). A runtime that starts
|
|
2191
|
+
* emitting any of them needs this guard extended to that frame first.
|
|
2192
|
+
*/
|
|
2193
|
+
private assertMcpReplyRelayFree;
|
|
2046
2194
|
/**
|
|
2047
2195
|
* Audit M-B1 (controller ruling): a reply to a room-granted turn is sealed
|
|
2048
2196
|
* only at the epoch the room was classified at. On a move, the room is
|
|
@@ -3057,6 +3205,13 @@ export declare class BridgeAgent {
|
|
|
3057
3205
|
* never changes a grant in step 1. A save that throws keeps the old state.
|
|
3058
3206
|
*/
|
|
3059
3207
|
private applyToolGrantManifest;
|
|
3208
|
+
/**
|
|
3209
|
+
* MCP Task 14: the R-3 hold Task 17 filters on and Task 15 writes as `r3_hold`. True while the manifest in force signs
|
|
3210
|
+
* an owner_dm `mcp:` string, or while the sticky flag is set (an old client's re-sign cannot lift it, audit C-1).
|
|
3211
|
+
*/
|
|
3212
|
+
r3Hold(): boolean;
|
|
3213
|
+
/** Persist then mirror. Setting mirrors even on a failed write; clearing mirrors only after the write (fail closed). */
|
|
3214
|
+
private setMcpSeen;
|
|
3060
3215
|
private logManifest;
|
|
3061
3216
|
/** Classify one inbound turn at the agent's CURRENT epoch for its channel. */
|
|
3062
3217
|
private authorizeTurn;
|
|
@@ -3101,6 +3256,20 @@ export declare class BridgeAgent {
|
|
|
3101
3256
|
* once, outside server control, is an open design follow-up.
|
|
3102
3257
|
*/
|
|
3103
3258
|
private authorizationFor;
|
|
3259
|
+
/**
|
|
3260
|
+
* Tool status Phase 0 (spec §3.2, §3.5; plan Task 8; round 1 H-2, M-4; round 2 H-4): build the candidate record and
|
|
3261
|
+
* keep the previous one (and its `at`) when the change tuple is the same, so `at` means "last changed". Never throws
|
|
3262
|
+
* into the authorization path. Never logs a channel id, sender id or tool name.
|
|
3263
|
+
*/
|
|
3264
|
+
private noteOwnerDmTurn;
|
|
3265
|
+
/** R2-M1: the served manifest names another owner than the pin; the held record belongs to the old owner. */
|
|
3266
|
+
private dropOwnerDmLastTurn;
|
|
3267
|
+
/**
|
|
3268
|
+
* Phase 0 Task 9 (spec §3.5): POST the record, body = the record only, to the narrow agent-key route. The backend
|
|
3269
|
+
* resolves the identity from the device credential; no identity, channel or sender id is in the URL or the body.
|
|
3270
|
+
* Returns the status class only; never throws.
|
|
3271
|
+
*/
|
|
3272
|
+
private postToolStatus;
|
|
3104
3273
|
/**
|
|
3105
3274
|
* One content-free INFO line per authorization decision (every call site of
|
|
3106
3275
|
* authorizationFor stages exactly one runtime turn). It says WHY a turn got
|
|
@@ -3175,6 +3344,28 @@ export declare class BridgeAgent {
|
|
|
3175
3344
|
* client's own wait already renders "Summary unavailable" — so there is
|
|
3176
3345
|
* nothing to catch here besides the send itself.
|
|
3177
3346
|
*/
|
|
3347
|
+
/**
|
|
3348
|
+
* MCP client Task 18 (spec §8 item 2; audit M-3, H-3; OQ-2, OQ-15). Accept an `mcp_catalog_request` only when: its
|
|
3349
|
+
* body is exactly the §8 shape; it is a direct frame (never a voice channel's) in a channel the server marks as a DM
|
|
3350
|
+
* that IS the signed owner DM; verifyOwnerTurn says `verified` (the pinned owner's signature, the signed DM, the leaf
|
|
3351
|
+
* check, not a replay) with `relayLeaf === false`; mcp-policy.json as written is `ok` with this server `ok`; a runtime
|
|
3352
|
+
* socket that declared mcp_client_v1 is attached; and the rate limits admit it. The replay row is written HERE
|
|
3353
|
+
* (M-3: the request never reaches stageRuntimeInbox, where it is otherwise written), for every verified request, before
|
|
3354
|
+
* anything is answered; the ownerReplayPending entry is removed in `finally`. A failure to record answers nothing.
|
|
3355
|
+
* Never throws. The answer runs outside the ingest cycle, so the row is acked now.
|
|
3356
|
+
*/
|
|
3357
|
+
private acceptMcpCatalogRequest;
|
|
3358
|
+
/** The signed server's vendor and the bridge catalog row's issuer, when mcp-policy.json as WRITTEN is `ok` with it `ok`. */
|
|
3359
|
+
private mcpCatalogTarget;
|
|
3360
|
+
/** Ask the runtime, re-check, build (384 KB cap) and send. Never throws. */
|
|
3361
|
+
private answerMcpCatalogRequest;
|
|
3362
|
+
/**
|
|
3363
|
+
* The `mcp_catalog` reply: a system_event envelope with a body-level type (the sendSummary shape), on the shared
|
|
3364
|
+
* self-heal seam and the one publish chokepoint. H-D applies to EVERY attempt (it carries vendor-supplied tool
|
|
3365
|
+
* descriptions): after the seal, before the POST, the leaves at the SEALED group and epoch must be exactly the owner
|
|
3366
|
+
* and the agent, or the frame is withheld. One message id across the retry.
|
|
3367
|
+
*/
|
|
3368
|
+
private sendMcpCatalog;
|
|
3178
3369
|
private handleSummarize;
|
|
3179
3370
|
/**
|
|
3180
3371
|
* A thin adapter over the daemon's one-shot completion seam. Voice-enabled
|
|
@@ -3307,6 +3498,23 @@ export declare class BridgeAgent {
|
|
|
3307
3498
|
* audience that is still signed, else dropped. Only narrows; an unchanged message is returned by reference.
|
|
3308
3499
|
*/
|
|
3309
3500
|
private renarrowManifestGrants;
|
|
3501
|
+
/**
|
|
3502
|
+
* MCP Task 16 (audit H-2; round 2 LOW), after the base re-narrowing: a buffered `mcp:` grant survives release only
|
|
3503
|
+
* when, NOW, the turn is still a plain owner-DM turn in the signed DM channel, the host switch reads "on", the
|
|
3504
|
+
* entitlement reading is in force, the name is not in skills.disabled, the mcp-policy.json document last written
|
|
3505
|
+
* lists the server ok with the tool, and a FRESH synchronous read of the DM's local leaves holds exactly the pinned
|
|
3506
|
+
* owner and the agent (MlsGroupKeyProvider.leafSnapshotCached: one `state` object, no await; null fails closed). Any
|
|
3507
|
+
* other mcp: grant is dropped, never kept as a fallback. relay_free is dropped whenever no mcp: grant survives.
|
|
3508
|
+
* Read-only: never touches the MLS group. Only narrows; returns `m` by reference when nothing changes.
|
|
3509
|
+
*/
|
|
3510
|
+
private renarrowMcpGrants;
|
|
3511
|
+
/**
|
|
3512
|
+
* MCP Task 16 (OQ-2; audit H-2, round 2 H-D): a FRESH read of the channel's local leaf set holds exactly the pinned
|
|
3513
|
+
* owner and this agent. Synchronous (one manager `state`, so the leaves belong to one epoch); a cold manager or any
|
|
3514
|
+
* throw reads as NOT relay-free.
|
|
3515
|
+
*/
|
|
3516
|
+
private ownerDmRelayFreeNow;
|
|
3517
|
+
private renarrowManifestGrantsBase;
|
|
3310
3518
|
private takeBufferedRaw;
|
|
3311
3519
|
/**
|
|
3312
3520
|
* Phase 3 F2: per-channel ordered delivery for the /v1/ws runtime consumer.
|
|
@@ -3555,6 +3763,74 @@ export declare class BridgeAgent {
|
|
|
3555
3763
|
private lastReportedUtilityTools;
|
|
3556
3764
|
noteSearchProvidersSocket(token: object, declared: boolean): void;
|
|
3557
3765
|
clearSearchProvidersSocket(token: object): void;
|
|
3766
|
+
/** MCP Task 16 (spec §15): the same AND-over-sockets state for mcp_client_v1, kept per socket. */
|
|
3767
|
+
private readonly mcpClientSockets;
|
|
3768
|
+
noteMcpClientSocket(token: object, declared: boolean): void;
|
|
3769
|
+
clearMcpClientSocket(token: object): void;
|
|
3770
|
+
/** Deletion-ack ruling §3: ws-server notes every runtime hello's mcp_deletion_ack_v1 declaration (harness: false). */
|
|
3771
|
+
noteMcpDeletionAckSocket(token: object, declared: boolean): void;
|
|
3772
|
+
/** #1351 round 2 observation: with no ack-capable socket left (detached, or re-hello'd without the feature), the last
|
|
3773
|
+
* credentials value is no longer known: forget it, so a later socket does not inherit a stale value. */
|
|
3774
|
+
private forgetCredentialsIfNoAckSocket;
|
|
3775
|
+
clearMcpDeletionAckSocket(token: object): void;
|
|
3776
|
+
/** #1351 round 2 M-1: ws-server calls this when it sends an owner-DM turn to an ack-capable socket. Bounded. */
|
|
3777
|
+
noteMcpTurnDispatch(token: object, messageId: string): void;
|
|
3778
|
+
/** Ruling §3 / §5.3: the AND over attached sockets, false with none attached (as mcpClientCapable). */
|
|
3779
|
+
private mcpDeletionAckCapable;
|
|
3780
|
+
/**
|
|
3781
|
+
* Deletion-ack ruling (2026-10-31) §3 acceptance. ws-server has validated the frame shape and the socket (declared
|
|
3782
|
+
* mcp_deletion_ack_v1 and mcp_client_v1, no harness). `credentials_present` is taken as is. `applied_sha256` advances
|
|
3783
|
+
* `mcpDeletionsThrough` only when it is the SHA-256 of the bytes THIS bridge last wrote (JSON + newline) and still
|
|
3784
|
+
* holds in force, and that document's top state is one whose deletion set covers the signed removal; then to the
|
|
3785
|
+
* counter recorded at that write, never the live one. Anything else is ignored silently. Never throws.
|
|
3786
|
+
*/
|
|
3787
|
+
noteMcpDeletionAck(token: object, frame: {
|
|
3788
|
+
credentials_present: boolean;
|
|
3789
|
+
message_id?: string;
|
|
3790
|
+
applied_sha256?: string;
|
|
3791
|
+
}): void;
|
|
3792
|
+
/** H-1: a change of the ack values goes into the open bucket; the report sees it only once the bucket has closed. */
|
|
3793
|
+
private noteMcpAckChanged;
|
|
3794
|
+
private promoteMcpAck;
|
|
3795
|
+
/** H-1: one timer, at the first bucket boundary strictly after now plus a jitter in [0, 60 s), as the tool-status sender. */
|
|
3796
|
+
private scheduleMcpAckReport;
|
|
3797
|
+
/**
|
|
3798
|
+
* Ruling §5.3: the three keys. The two ack values come from the CLOSED bucket only (H-1), so no report, whatever
|
|
3799
|
+
* posts it, reveals an owner-DM turn before its 15-minute bucket has closed. `mcp_r3_hold` is not DM timing: live.
|
|
3800
|
+
*/
|
|
3801
|
+
private reportedMcpAck;
|
|
3802
|
+
private mcpAckSignature;
|
|
3803
|
+
/**
|
|
3804
|
+
* A SOCKET change (attach, detach, a hello) can change which keys the report carries; their values are the closed
|
|
3805
|
+
* ones, so this is not owner-DM timing and may use the capability refetch. An ACK never comes here (H-1): it goes
|
|
3806
|
+
* through noteMcpAckChanged and the bucket timer.
|
|
3807
|
+
*/
|
|
3808
|
+
private maybeReportMcpAck;
|
|
3809
|
+
/** MCP Task 18: ws-server registers (or, with null, removes) a socket's loopback catalog lister. */
|
|
3810
|
+
noteMcpCatalogLister(token: object, lister: McpCatalogLister | null): void;
|
|
3811
|
+
/**
|
|
3812
|
+
* MCP Task 19 (spec §12.3): "ok" when every attached runtime socket declared mcp_client_v1 (the AND, which harness
|
|
3813
|
+
* sockets hold false: a harness socket counts in the AND and never declares it); else "harness_consumer" when the
|
|
3814
|
+
* latest hello was a harness; else "missing".
|
|
3815
|
+
*/
|
|
3816
|
+
private reportedMcpCapability;
|
|
3817
|
+
/**
|
|
3818
|
+
* MCP Task 19 (spec §12.3): at most 8 `{server_id, vendor, state}` for the signed servers of the manifest in force,
|
|
3819
|
+
* from mcp-policy.json as WRITTEN. Only the states the bridge itself knows: top `kill_switch` and `not_entitled` give
|
|
3820
|
+
* that state per server; top `ok` gives `ok` for a server the bridge lists `ok` and `malformed` for one it refused.
|
|
3821
|
+
* Every other top state (no capability, suspended, no config, switch off, section absent, malformed section) gives no
|
|
3822
|
+
* row. The runtime-only states (needs_login, login_expired, definition_changed, server_version_unsupported,
|
|
3823
|
+
* server_unreachable) have no runtime-to-bridge report path in Phase 1. `vendor` is reported only when it is a key of
|
|
3824
|
+
* the bridge's compiled catalog; a signed vendor outside it gives no row. Never a tool name, URL or issuer.
|
|
3825
|
+
*/
|
|
3826
|
+
private reportedMcpServers;
|
|
3827
|
+
/**
|
|
3828
|
+
* MCP Task 16: every attached runtime socket declared mcp_client_v1; no socket attached means absent (false).
|
|
3829
|
+
* PR-B2 follow-up (documented): a HARNESS socket counts in this AND. ws-server notes every socket that said hello,
|
|
3830
|
+
* and a harness hello is always noted `false` (harnessClaimed never resets), so one attached harness connector holds
|
|
3831
|
+
* the capability false for files, the report and mcp-policy.json (top `no_capability`) while it is attached.
|
|
3832
|
+
*/
|
|
3833
|
+
mcpClientCapable(): boolean;
|
|
3558
3834
|
noteUtilityToolsSocket(token: object, declared: boolean): void;
|
|
3559
3835
|
clearUtilityToolsSocket(token: object): void;
|
|
3560
3836
|
/** U6: every attached runtime socket declared utility_tools_v1; no socket attached means absent (false). */
|
|
@@ -3576,6 +3852,8 @@ export declare class BridgeAgent {
|
|
|
3576
3852
|
* switches and the new capability, then a debounced refetch (one per burst) only when needed.
|
|
3577
3853
|
*/
|
|
3578
3854
|
private onCapabilityMaybeChanged;
|
|
3855
|
+
/** Debounced, with a max wait: a burst that never pauses still refetches at most maxWait after its first change. */
|
|
3856
|
+
private scheduleCapabilityRefetch;
|
|
3579
3857
|
/**
|
|
3580
3858
|
* A refetch re-renders skills.json (owner_dm_search_cap counts web_extract only with the capability, D39) and posts
|
|
3581
3859
|
* the report. Needed under a manifest in force, or when the reported value is stale. Never before the boot fetch.
|
|
@@ -28,6 +28,8 @@ export declare const MANIFEST_GRANTS_FEATURE = "manifest_grants_v1";
|
|
|
28
28
|
export declare const SEARCH_PROVIDERS_FEATURE = "search_providers_v2";
|
|
29
29
|
/** Weather/currency spec §8.1 (U6): the /v1/ws hello feature of a runtime that knows weather_forecast and currency_convert. Pinned to the runtime's by capability-parity.test.ts. */
|
|
30
30
|
export declare const UTILITY_TOOLS_FEATURE = "utility_tools_v1";
|
|
31
|
+
/** MCP client spec §15 (plan Task 16): the /v1/ws hello feature of a runtime that runs the read-only MCP client. Never a TOOL_FEATURE value. Pinned to the runtime's by capability-parity.test.ts. */
|
|
32
|
+
export declare const MCP_CLIENT_FEATURE = "mcp_client_v1";
|
|
31
33
|
export declare const CONSTANT_TOOL_NAMES: Readonly<Record<"owner_dm" | "team_room", readonly string[]>>;
|
|
32
34
|
/**
|
|
33
35
|
* Spec §7.9: a consumer that did not declare manifest_grants_v1 (runtime 0.12.2, a harness,
|
|
@@ -64,3 +66,29 @@ export declare function withSearchCapability(m: BridgeMessage, consumerHasFeatur
|
|
|
64
66
|
* policy file and TOOL_FEATURE. Returns `m` by reference when nothing drops; never mutates `m`.
|
|
65
67
|
*/
|
|
66
68
|
export declare function withUtilityCapability(m: BridgeMessage, consumerHasFeature: boolean): BridgeMessage;
|
|
69
|
+
/** The exact prefix of an owner-signed MCP grant name (`mcp:<server_id>/<tool>`). */
|
|
70
|
+
export declare const MCP_GRANT_PREFIX = "mcp:";
|
|
71
|
+
/**
|
|
72
|
+
* Spec §15 per-delivery MCP filter, on every delivery path after withUtilityCapability, judged by the consumer's OWN
|
|
73
|
+
* hello. An `mcp:` grant survives only for a consumer that declared mcp_client_v1, on a turn whose audience is
|
|
74
|
+
* `owner_dm` and that carries no relay_speaker mark (any value: the mark disqualifies, T-10). Otherwise every `mcp:`
|
|
75
|
+
* grant is dropped, marked or unmarked (a harness, an older runtime and every HTTP path never receive one, T-14).
|
|
76
|
+
* Every other grant and field is unchanged. Only narrows; returns `m` by reference when nothing drops; never mutates.
|
|
77
|
+
*/
|
|
78
|
+
export declare function withMcpCapability(m: BridgeMessage, consumerHasFeature: boolean): BridgeMessage;
|
|
79
|
+
/**
|
|
80
|
+
* R-3 "holds" keep set, the bridge copy (spec §10, audit M-1): an ALLOW-list, so a future built-in is withdrawn by
|
|
81
|
+
* default while the hold is on. Pinned equal to the runtime's (Task 12b) by consumer-grants.mcp.test.ts, and over
|
|
82
|
+
* MANIFEST_KNOWN_TOOLS.owner_dm it withdraws exactly fetch_url, web_extract, http_request and sql_query.
|
|
83
|
+
*/
|
|
84
|
+
export declare const R3_KEEP_TOOLS: ReadonlySet<string>;
|
|
85
|
+
/**
|
|
86
|
+
* Spec §10 R-3 "holds" filter, on every delivery path to every consumer. `hold` is the agent's R-3 hold
|
|
87
|
+
* (BridgeAgent.r3Hold(): the in-force manifest's signedMcpPresent, computed on the raw verified owner_dm array, or the
|
|
88
|
+
* sticky mcp_seen flag). It is message-independent on purpose: it never reads this message's grants (which
|
|
89
|
+
* withMcpCapability, entitlement, kill switches or skills.disabled may already have emptied), so no narrowing step can
|
|
90
|
+
* lift it (audit C-2, OQ-13). While it holds, every owner-DM turn (audience owner_dm, or the owner-relayed voice turn
|
|
91
|
+
* marked relay_speaker "owner") keeps only R3_KEEP_TOOLS and `mcp:` grants. Other audiences are unchanged. Only
|
|
92
|
+
* narrows; returns `m` by reference when nothing drops; never mutates.
|
|
93
|
+
*/
|
|
94
|
+
export declare function withR3Holds(m: BridgeMessage, hold: boolean): BridgeMessage;
|
package/dist/grants/binding.d.ts
CHANGED
|
@@ -69,7 +69,8 @@ export declare function legacyAssignToken(d: {
|
|
|
69
69
|
* unknown name in manifest-policy.json as an unreadable file and drops EVERY manifest grant, so a tool listed here is
|
|
70
70
|
* written to the policy file (and delivered) only when every attached runtime socket declared its feature.
|
|
71
71
|
*/
|
|
72
|
-
|
|
72
|
+
/** MCP Task 16: "mcp_client_v1" is a Feature (capabilityFeatures) but never a TOOL_FEATURE value (pinned by capability-parity.test.ts). */
|
|
73
|
+
export type Feature = "search_providers_v2" | "utility_tools_v1" | "mcp_client_v1";
|
|
73
74
|
export type FeatureSet = ReadonlySet<Feature>;
|
|
74
75
|
/** No socket attached, or none declared anything: fail closed. */
|
|
75
76
|
export declare const NO_FEATURES: FeatureSet;
|
|
@@ -39,9 +39,17 @@ export interface EffectiveInput {
|
|
|
39
39
|
};
|
|
40
40
|
readonly roomSearchCapped: boolean;
|
|
41
41
|
}
|
|
42
|
+
/** Phase 0 Task 6 (spec §3.2, round 2 M-12): why a signed tool was dropped owner-side, pushed at the line that drops it. */
|
|
43
|
+
export type WithheldWhy = "disabled_by_owner" | "no_endpoint" | "no_database";
|
|
44
|
+
export interface WithheldTool {
|
|
45
|
+
readonly tool: string;
|
|
46
|
+
readonly why: WithheldWhy;
|
|
47
|
+
}
|
|
42
48
|
export interface EffectiveGrant {
|
|
43
49
|
readonly grants: readonly ReadGrant[];
|
|
44
50
|
readonly source: GrantSource;
|
|
45
51
|
readonly withheldBySearchCap: boolean;
|
|
52
|
+
/** Signed tools the manifest branch dropped, in ceiling order, frozen. [] on every other branch. The team-room search cap is not an owner-side reason (it keeps withheldBySearchCap). */
|
|
53
|
+
readonly withheld: readonly WithheldTool[];
|
|
46
54
|
}
|
|
47
55
|
export declare function effectiveToolGrants(i: EffectiveInput): EffectiveGrant;
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { type SearchProvider } from "./search-providers.js";
|
|
10
10
|
import { type OwnerGrantPin } from "./pin.js";
|
|
11
|
+
import { type SignedMcp } from "./mcp-section.js";
|
|
12
|
+
import { type McpCatalog } from "./mcp-catalog.js";
|
|
11
13
|
export declare const TOOL_GRANTS_DOMAIN = "SECURECOMS-TOOL-GRANTS-V1:";
|
|
12
14
|
export declare const MANIFEST_STATES: readonly ["ok", "unchanged", "no_pin", "no_manifest", "malformed", "unsupported_algo", "bad_signature", "wrong_agent", "wrong_owner", "rollback", "unknown_entry", "kept_last_good"];
|
|
13
15
|
export type ManifestState = (typeof MANIFEST_STATES)[number];
|
|
@@ -33,6 +35,17 @@ export interface ToolGrantManifest {
|
|
|
33
35
|
readonly databases: Readonly<Record<string, SignedDatabase>>;
|
|
34
36
|
/** Plan 4 Task 7 (spec §8.1, §7.10): the signed values the config binding compares. Required. */
|
|
35
37
|
readonly config: SignedConfig;
|
|
38
|
+
/**
|
|
39
|
+
* MCP Phase 1 Task 14 (spec §12.2, §13a). `mcp` is the parsed signed section, or null when the key is absent or the
|
|
40
|
+
* section is malformed (`mcpKey` tells them apart, OQ-3 ruling). A malformed section never makes the manifest malformed.
|
|
41
|
+
*/
|
|
42
|
+
readonly mcp: SignedMcp | null;
|
|
43
|
+
readonly mcpKey: "absent" | "present";
|
|
44
|
+
/**
|
|
45
|
+
* R-3 raw predicate (audit C-2): some string in the signature-verified `grants.owner_dm` starts with `mcp:`, read after
|
|
46
|
+
* step 5 and BEFORE step 6, parseMcp, the ceiling and the catalog, so none of them can lift R-3.
|
|
47
|
+
*/
|
|
48
|
+
readonly signedMcpPresent: boolean;
|
|
36
49
|
}
|
|
37
50
|
export interface SignedEndpoint {
|
|
38
51
|
readonly baseUrl: string;
|
|
@@ -75,4 +88,5 @@ export declare function verifyToolGrantManifest(raw: unknown, ctx: {
|
|
|
75
88
|
pin: OwnerGrantPin;
|
|
76
89
|
agentIdentityId: string;
|
|
77
90
|
storedCounter: number | null;
|
|
91
|
+
mcpCatalog?: McpCatalog;
|
|
78
92
|
}): ManifestVerdict;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bridge's copy of the compiled MCP catalog (spec §11, §12.2 "the vendor and tool are in the bridge's catalog copy").
|
|
3
|
+
* Phase 1 holds exactly one row, Linear (Task 22; Eric 2026-10-30, "ship Linear only"), identical to the runtime's
|
|
4
|
+
* (`connect-agent-runtime/src/mcp/catalog.ts`) value for value. No vendor value is ever written from memory; each cites
|
|
5
|
+
* its source below. `mcp-catalog.parity.test.ts` and `mcp-lists.parity.test.ts` pin this copy to the runtime's byte for
|
|
6
|
+
* byte, so the two cannot drift. How a row is added, changed or withdrawn: docs/runbooks/mcp-catalog-update.md.
|
|
7
|
+
*/
|
|
8
|
+
/** The runtime's `CatalogRow`, field for field (OQ-6 ruling: the #1308 §11 fields exactly). */
|
|
9
|
+
export interface McpCatalogRow {
|
|
10
|
+
displayName?: string;
|
|
11
|
+
host: string;
|
|
12
|
+
readOnlyUrl: string;
|
|
13
|
+
readTools: readonly string[];
|
|
14
|
+
scopes: readonly string[];
|
|
15
|
+
redirectPort?: 47823;
|
|
16
|
+
issuer: string;
|
|
17
|
+
authorizationEndpoint: string;
|
|
18
|
+
tokenEndpoint: string;
|
|
19
|
+
revocationEndpoint?: string;
|
|
20
|
+
docs: Readonly<Record<string, string>>;
|
|
21
|
+
}
|
|
22
|
+
export type McpCatalog = Readonly<Record<string, McpCatalogRow>>;
|
|
23
|
+
export declare const MCP_CATALOG: McpCatalog;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client Phase 1, Task 16 (spec §12.2 "issued"; OQ-1, OQ-2 rulings; audit H-3, M-4): which `mcp:` grants an
|
|
3
|
+
* owner-DM turn is issued. Pure; the bridge calls it right after effectiveToolGrants, on the owner-DM branch only.
|
|
4
|
+
*
|
|
5
|
+
* issued = { mcp:s/t in manifest.grants.owner_dm (step 6 already applied the code ceiling and the catalog) }
|
|
6
|
+
* only when ALL of:
|
|
7
|
+
* effectiveToolGrants returned source "manifest" (so the turn was manifest-eligible: never a fallback);
|
|
8
|
+
* the owner verdict is `verified` with relayLeaf === false (OQ-2: exactly owner and agent leaves);
|
|
9
|
+
* the bridge's in-force entitlement reading is "entitled" (OQ-1);
|
|
10
|
+
* SECURECOMS_MCP_GRANTS reads "on" (D-7: off and suspended issue nothing);
|
|
11
|
+
* SECURECOMS_OWNER_DM_MANIFEST_GRANTS is on;
|
|
12
|
+
* the mcp-policy.json document the bridge last WROTE is top `ok` and lists the server `ok` with the tool;
|
|
13
|
+
* minus every name in skills.disabled.
|
|
14
|
+
*
|
|
15
|
+
* Fail closed throughout: an absent relayLeaf, an absent policy, a missing server or tool all issue nothing.
|
|
16
|
+
*/
|
|
17
|
+
import type { GrantSource, ReadGrant } from "./effective.js";
|
|
18
|
+
import type { ToolGrantManifest } from "./manifest.js";
|
|
19
|
+
import type { McpEntitlementReading, McpHostSwitch } from "./mcp-policy.js";
|
|
20
|
+
import type { OwnerTurnVerdict } from "./owner-turn.js";
|
|
21
|
+
import type { McpPolicyDoc } from "../service/runtime-config-dir.js";
|
|
22
|
+
export interface McpIssuanceInput {
|
|
23
|
+
readonly source: GrantSource;
|
|
24
|
+
readonly verdict: OwnerTurnVerdict | undefined;
|
|
25
|
+
readonly entitlement: McpEntitlementReading;
|
|
26
|
+
readonly hostSwitch: McpHostSwitch;
|
|
27
|
+
readonly ownerDmManifestSwitchOn: boolean;
|
|
28
|
+
readonly disabled: ReadonlySet<string>;
|
|
29
|
+
readonly manifest: ToolGrantManifest | null;
|
|
30
|
+
readonly policy: McpPolicyDoc | null;
|
|
31
|
+
}
|
|
32
|
+
/** True when the written policy lists `serverId` as `ok` with `tool` among its tools. */
|
|
33
|
+
export declare function mcpPolicyAllows(policy: McpPolicyDoc | null, serverId: string, tool: string): boolean;
|
|
34
|
+
export declare function mcpIssuance(i: McpIssuanceInput): readonly ReadGrant[];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client Phase 1, Task 15 (spec §6.5, §12.1; OQ-1 and OQ-3 rulings; round 2 H-B): the `mcp-policy.json` document.
|
|
3
|
+
*
|
|
4
|
+
* Pure, except mcpGrantsHostSwitch's one start-time warn. The bridge writes the result with writeMcpPolicy BEFORE it
|
|
5
|
+
* issues any MCP grant (Task 16 wires it). The runtime deletes a token only on `ok` for a server named in `remove`
|
|
6
|
+
* (and not listed), `not_entitled` or `kill_switch` (deletion.ts; a host typo is therefore `suspended`, never
|
|
7
|
+
* `kill_switch`). The rules that matter most here:
|
|
8
|
+
* - C-NEW-1 (2026-10-30): `remove` is copied ONLY from the signed `mcp.removed` of the manifest in force, and only into
|
|
9
|
+
* the `ok` document; every other document writes `remove: []`. Absence from `servers` never deletes, so a stale
|
|
10
|
+
* view re-signed by the owner cannot launder a deletion. No record of an earlier section is kept;
|
|
11
|
+
* - a server the bridge refuses for its own reasons stays LISTED as `refused`;
|
|
12
|
+
* - a verified manifest with NO `mcp` key is `section_absent`, never top `ok` with empty servers (OQ-3). An explicit
|
|
13
|
+
* signed `"mcp": {"servers": {}}` is top `ok` with empty servers and deletes nothing; `{"servers": {}, "removed": "*"}`
|
|
14
|
+
* is remove-all.
|
|
15
|
+
*
|
|
16
|
+
* `r3_hold` (= signedMcpPresent || mcp_seen) is in EVERY document, empty-servers ones included (round 2 H-B).
|
|
17
|
+
*
|
|
18
|
+
* Precedence, first match wins (the spec fixes no order; this one puts the host's own switch first, then what the bridge
|
|
19
|
+
* cannot know, then the backend's entitlement, then local switches, then the manifest, then the consumer):
|
|
20
|
+
* kill_switch | host suspended (D-7), no_config, not_entitled | suspended, switch_off, no_manifest, section_absent,
|
|
21
|
+
* malformed, no_capability, ok
|
|
22
|
+
*/
|
|
23
|
+
import type { ToolGrantManifest } from "./manifest.js";
|
|
24
|
+
import { type McpCatalog } from "./mcp-catalog.js";
|
|
25
|
+
import type { McpPolicyDoc } from "../service/runtime-config-dir.js";
|
|
26
|
+
import { type ServiceLog } from "../service-log.js";
|
|
27
|
+
/**
|
|
28
|
+
* Architect skew ruling (2026-10-30): this bridge declares, in EVERY hello reply (ws-server.ts), that it is the sole
|
|
29
|
+
* writer of mcp-policy.json: it refuses backend delivery of that name (runtime-config-dir.ts isLocalOnlyName). The
|
|
30
|
+
* runtime deletes tokens and offers MCP tools from the file only on a turn from a declaring socket (connect-agent-runtime
|
|
31
|
+
* data-plane.ts MCP_POLICY_WRITER_FEATURE). Unconditional: a statement about this build, not about the runtime or the
|
|
32
|
+
* switches (the host kill switch travels IN the file and must keep deleting). Never declare it from a build where the
|
|
33
|
+
* backend can write that name (ws-server.test.ts same-build row).
|
|
34
|
+
*/
|
|
35
|
+
export declare const MCP_POLICY_WRITER_FEATURE = "mcp_policy_writer_v1";
|
|
36
|
+
/**
|
|
37
|
+
* Deletion-ack ruling (2026-10-31) §3: declared in the hello reply and by the runtime in its hello. Only a socket that
|
|
38
|
+
* declared it (and mcp_client_v1, no harness) may send `mcp_deletion_ack`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const MCP_DELETION_ACK_FEATURE = "mcp_deletion_ack_v1";
|
|
41
|
+
/** Ruling §3: the top states whose deletion set covers the manifest's signed `removed` (ok), or everything on disk. */
|
|
42
|
+
export declare const MCP_ACK_STATES: ReadonlySet<string>;
|
|
43
|
+
export type McpEntitlementReading = "entitled" | "not_entitled" | "suspended";
|
|
44
|
+
/**
|
|
45
|
+
* OQ-1 ruling (ADR G). Only `true`/"entitled" is in force and only `false`/"not_entitled" is the deleting downgrade.
|
|
46
|
+
* Anything else (missing, unknown, disagreeing, non-boolean) is `suspended`: no grants, no deletion.
|
|
47
|
+
*/
|
|
48
|
+
export declare function mcpEntitlementReading(mcpEntitled: unknown, mcpCause: unknown): McpEntitlementReading;
|
|
49
|
+
/**
|
|
50
|
+
* Host kill switch SECURECOMS_MCP_GRANTS (OQ-16; coordinator ruling D-7). Tri-state, so a typo can never delete:
|
|
51
|
+
* unset or exactly "on" -> "on"; exactly "off" -> "off" (writes `kill_switch`, a deleting state);
|
|
52
|
+
* any other value -> "suspended" (writes top `suspended`: no grants, no deletion).
|
|
53
|
+
*/
|
|
54
|
+
export type McpHostSwitch = "on" | "off" | "suspended";
|
|
55
|
+
/**
|
|
56
|
+
* Read once at bridge start by the caller. A suspended value logs ONE content-free warn per call carrying the enum,
|
|
57
|
+
* never the raw value (it may be anything an operator typed).
|
|
58
|
+
*/
|
|
59
|
+
export declare function mcpGrantsHostSwitch(env: Readonly<Record<string, string | undefined>>, log?: ServiceLog): McpHostSwitch;
|
|
60
|
+
export interface McpPolicyInput {
|
|
61
|
+
/** mcpGrantsHostSwitch(), read once at bridge start. "off" writes `kill_switch`; "suspended" writes `suspended`. */
|
|
62
|
+
readonly hostSwitch: McpHostSwitch;
|
|
63
|
+
/** The last /runtime-config/files result: not yet fetched (or failed), or the two raw fields from a successful fetch. */
|
|
64
|
+
readonly config: {
|
|
65
|
+
readonly fetched: false;
|
|
66
|
+
} | {
|
|
67
|
+
readonly fetched: true;
|
|
68
|
+
readonly mcp_entitled: unknown;
|
|
69
|
+
readonly mcp_cause: unknown;
|
|
70
|
+
};
|
|
71
|
+
/** SECURECOMS_OWNER_DM_MANIFEST_GRANTS on. */
|
|
72
|
+
readonly ownerDmManifestSwitchOn: boolean;
|
|
73
|
+
/** The verified manifest in force, or null. */
|
|
74
|
+
readonly manifest: ToolGrantManifest | null;
|
|
75
|
+
/** `mcp_client_v1` on every attached runtime socket of the identity (absent with none). */
|
|
76
|
+
readonly capability: boolean;
|
|
77
|
+
/** The sticky flag from the sealed store (Task 14). */
|
|
78
|
+
readonly mcpSeen: boolean;
|
|
79
|
+
/** Test seam; the compiled bridge catalog by default. */
|
|
80
|
+
readonly catalog?: McpCatalog;
|
|
81
|
+
}
|
|
82
|
+
export declare function mcpPolicyFor(i: McpPolicyInput): McpPolicyDoc;
|