wicked-crew 0.7.40 → 0.7.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/audit.d.ts +3 -0
- package/dist/api/audit.d.ts.map +1 -1
- package/dist/api/audit.js +2 -0
- package/dist/api/audit.js.map +1 -1
- package/dist/api/capture.d.ts +149 -0
- package/dist/api/capture.d.ts.map +1 -0
- package/dist/api/capture.js +330 -0
- package/dist/api/capture.js.map +1 -0
- package/dist/api/chat-citations.d.ts +174 -0
- package/dist/api/chat-citations.d.ts.map +1 -0
- package/dist/api/chat-citations.js +528 -0
- package/dist/api/chat-citations.js.map +1 -0
- package/dist/api/chat-transcripts.d.ts +20 -1
- package/dist/api/chat-transcripts.d.ts.map +1 -1
- package/dist/api/chat-transcripts.js +29 -0
- package/dist/api/chat-transcripts.js.map +1 -1
- package/dist/api/delivery-cache.d.ts +2 -2
- package/dist/api/delivery-freeze.d.ts +66 -0
- package/dist/api/delivery-freeze.d.ts.map +1 -0
- package/dist/api/delivery-freeze.js +104 -0
- package/dist/api/delivery-freeze.js.map +1 -0
- package/dist/api/delivery-index.d.ts +53 -9
- package/dist/api/delivery-index.d.ts.map +1 -1
- package/dist/api/delivery-index.js +115 -22
- package/dist/api/delivery-index.js.map +1 -1
- package/dist/api/demo.d.ts +90 -0
- package/dist/api/demo.d.ts.map +1 -0
- package/dist/api/demo.js +492 -0
- package/dist/api/demo.js.map +1 -0
- package/dist/api/diagnostics.d.ts +64 -1
- package/dist/api/diagnostics.d.ts.map +1 -1
- package/dist/api/diagnostics.js +129 -1
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/eval-sample.d.ts +6 -6
- package/dist/api/governance-steering.d.ts +2 -2
- package/dist/api/mcp.d.ts +282 -0
- package/dist/api/mcp.d.ts.map +1 -0
- package/dist/api/mcp.js +521 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/plan-schema.d.ts +56 -0
- package/dist/api/plan-schema.d.ts.map +1 -0
- package/dist/api/plan-schema.js +26 -0
- package/dist/api/plan-schema.js.map +1 -0
- package/dist/api/roster-standing.d.ts +15 -2
- package/dist/api/roster-standing.d.ts.map +1 -1
- package/dist/api/roster-standing.js +59 -3
- package/dist/api/roster-standing.js.map +1 -1
- package/dist/api/routes.d.ts +630 -51
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +738 -127
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-files.d.ts.map +1 -1
- package/dist/api/run-files.js +31 -2
- package/dist/api/run-files.js.map +1 -1
- package/dist/api/run-liveness.d.ts +41 -0
- package/dist/api/run-liveness.d.ts.map +1 -0
- package/dist/api/run-liveness.js +166 -0
- package/dist/api/run-liveness.js.map +1 -0
- package/dist/api/seat-health.d.ts +35 -0
- package/dist/api/seat-health.d.ts.map +1 -1
- package/dist/api/seat-health.js +92 -4
- package/dist/api/seat-health.js.map +1 -1
- package/dist/api/seat-probe.d.ts +141 -0
- package/dist/api/seat-probe.d.ts.map +1 -0
- package/dist/api/seat-probe.js +257 -0
- package/dist/api/seat-probe.js.map +1 -0
- package/dist/api/seat-record.d.ts +40 -0
- package/dist/api/seat-record.d.ts.map +1 -0
- package/dist/api/seat-record.js +218 -0
- package/dist/api/seat-record.js.map +1 -0
- package/dist/api/seat-signin.d.ts.map +1 -1
- package/dist/api/seat-signin.js +3 -1
- package/dist/api/seat-signin.js.map +1 -1
- package/dist/api/seat-standing.d.ts +25 -5
- package/dist/api/seat-standing.d.ts.map +1 -1
- package/dist/api/seat-standing.js +76 -9
- package/dist/api/seat-standing.js.map +1 -1
- package/dist/api/server.d.ts +34 -48
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +283 -107
- package/dist/api/server.js.map +1 -1
- package/dist/api/skills.d.ts +3 -1
- package/dist/api/skills.d.ts.map +1 -1
- package/dist/api/skills.js +7 -2
- package/dist/api/skills.js.map +1 -1
- package/dist/api/stall-watchdog.d.ts +34 -3
- package/dist/api/stall-watchdog.d.ts.map +1 -1
- package/dist/api/stall-watchdog.js +107 -8
- package/dist/api/stall-watchdog.js.map +1 -1
- package/dist/api/steering-landing.d.ts +6 -6
- package/dist/api/steering-landing.d.ts.map +1 -1
- package/dist/api/steering-landing.js +7 -9
- package/dist/api/steering-landing.js.map +1 -1
- package/dist/api/testing.d.ts +6 -4
- package/dist/api/testing.d.ts.map +1 -1
- package/dist/api/testing.js +82 -47
- package/dist/api/testing.js.map +1 -1
- package/dist/campaign/boot-resume.d.ts +34 -0
- package/dist/campaign/boot-resume.d.ts.map +1 -0
- package/dist/campaign/boot-resume.js +45 -0
- package/dist/campaign/boot-resume.js.map +1 -0
- package/dist/campaigns/rollup.d.ts +4 -1
- package/dist/campaigns/rollup.d.ts.map +1 -1
- package/dist/campaigns/rollup.js +6 -1
- package/dist/campaigns/rollup.js.map +1 -1
- package/dist/campaigns/routes.d.ts +2 -2
- package/dist/cli/bearer.d.ts +27 -1
- package/dist/cli/bearer.d.ts.map +1 -1
- package/dist/cli/bearer.js +62 -2
- package/dist/cli/bearer.js.map +1 -1
- package/dist/cli/governance.d.ts +11 -0
- package/dist/cli/governance.d.ts.map +1 -1
- package/dist/cli/governance.js +8 -2
- package/dist/cli/governance.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +140 -75
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts +11 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +40 -9
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/service.d.ts +78 -0
- package/dist/cli/service.d.ts.map +1 -0
- package/dist/cli/service.js +334 -0
- package/dist/cli/service.js.map +1 -0
- package/dist/core/adapter.d.ts +238 -12
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +619 -157
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/bridge-reaper.d.ts +4 -1
- package/dist/core/bridge-reaper.d.ts.map +1 -1
- package/dist/core/bridge-reaper.js +15 -1
- package/dist/core/bridge-reaper.js.map +1 -1
- package/dist/core/bus-notice.d.ts +27 -0
- package/dist/core/bus-notice.d.ts.map +1 -0
- package/dist/core/bus-notice.js +24 -0
- package/dist/core/bus-notice.js.map +1 -0
- package/dist/core/bus.d.ts +99 -0
- package/dist/core/bus.d.ts.map +1 -0
- package/dist/core/bus.js +176 -0
- package/dist/core/bus.js.map +1 -0
- package/dist/core/deliver-exclusions.d.ts +71 -0
- package/dist/core/deliver-exclusions.d.ts.map +1 -0
- package/dist/core/deliver-exclusions.js +110 -0
- package/dist/core/deliver-exclusions.js.map +1 -0
- package/dist/core/deliver-text.d.ts +61 -14
- package/dist/core/deliver-text.d.ts.map +1 -1
- package/dist/core/deliver-text.js +178 -30
- package/dist/core/deliver-text.js.map +1 -1
- package/dist/core/deliver-triage.d.ts +11 -2
- package/dist/core/deliver-triage.d.ts.map +1 -1
- package/dist/core/deliver-triage.js +19 -1
- package/dist/core/deliver-triage.js.map +1 -1
- package/dist/core/deliver.d.ts +161 -3
- package/dist/core/deliver.d.ts.map +1 -1
- package/dist/core/deliver.js +463 -25
- package/dist/core/deliver.js.map +1 -1
- package/dist/core/deliverable-floor.d.ts.map +1 -1
- package/dist/core/deliverable-floor.js +5 -7
- package/dist/core/deliverable-floor.js.map +1 -1
- package/dist/core/engine-bus.d.ts +40 -0
- package/dist/core/engine-bus.d.ts.map +1 -0
- package/dist/core/engine-bus.js +27 -0
- package/dist/core/engine-bus.js.map +1 -0
- package/dist/core/estate-mcp-client.d.ts +87 -5
- package/dist/core/estate-mcp-client.d.ts.map +1 -1
- package/dist/core/estate-mcp-client.js +90 -11
- package/dist/core/estate-mcp-client.js.map +1 -1
- package/dist/core/linked-issues.d.ts +46 -0
- package/dist/core/linked-issues.d.ts.map +1 -0
- package/dist/core/linked-issues.js +123 -0
- package/dist/core/linked-issues.js.map +1 -0
- package/dist/core/run-identity.d.ts +100 -0
- package/dist/core/run-identity.d.ts.map +1 -0
- package/dist/core/run-identity.js +178 -0
- package/dist/core/run-identity.js.map +1 -0
- package/dist/core/shutdown.d.ts +16 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +39 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/types.d.ts +15 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +4 -0
- package/dist/core/types.js.map +1 -1
- package/dist/interactive/bridge-pool.d.ts +67 -3
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +129 -13
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/bridge-root.d.ts +9 -0
- package/dist/interactive/bridge-root.d.ts.map +1 -1
- package/dist/interactive/bridge-root.js +11 -4
- package/dist/interactive/bridge-root.js.map +1 -1
- package/dist/interactive/chat-events.d.ts +14 -9
- package/dist/interactive/chat-events.d.ts.map +1 -1
- package/dist/interactive/chat-events.js +55 -57
- package/dist/interactive/chat-events.js.map +1 -1
- package/dist/interactive/council-outcome.d.ts +1 -0
- package/dist/interactive/council-outcome.d.ts.map +1 -1
- package/dist/interactive/council-outcome.js +33 -0
- package/dist/interactive/council-outcome.js.map +1 -1
- package/dist/interactive/draft-events.d.ts +36 -17
- package/dist/interactive/draft-events.d.ts.map +1 -1
- package/dist/interactive/draft-events.js +121 -92
- package/dist/interactive/draft-events.js.map +1 -1
- package/dist/interactive/draft-skill.d.ts +59 -0
- package/dist/interactive/draft-skill.d.ts.map +1 -1
- package/dist/interactive/draft-skill.js +233 -0
- package/dist/interactive/draft-skill.js.map +1 -1
- package/dist/interactive/edit-events.d.ts +1 -14
- package/dist/interactive/edit-events.d.ts.map +1 -1
- package/dist/interactive/edit-events.js +37 -70
- package/dist/interactive/edit-events.js.map +1 -1
- package/dist/interactive/proxy-routes.d.ts +2 -2
- package/dist/interactive/proxy-routes.d.ts.map +1 -1
- package/dist/interactive/proxy-routes.js +4 -13
- package/dist/interactive/proxy-routes.js.map +1 -1
- package/dist/interactive/ws-relay.d.ts +9 -10
- package/dist/interactive/ws-relay.d.ts.map +1 -1
- package/dist/interactive/ws-relay.js +23 -50
- package/dist/interactive/ws-relay.js.map +1 -1
- package/dist/mcp/broker.d.ts +88 -0
- package/dist/mcp/broker.d.ts.map +1 -0
- package/dist/mcp/broker.js +307 -0
- package/dist/mcp/broker.js.map +1 -0
- package/dist/mcp/call-records.d.ts +61 -0
- package/dist/mcp/call-records.d.ts.map +1 -0
- package/dist/mcp/call-records.js +135 -0
- package/dist/mcp/call-records.js.map +1 -0
- package/dist/mcp/classify.d.ts +45 -0
- package/dist/mcp/classify.d.ts.map +1 -0
- package/dist/mcp/classify.js +83 -0
- package/dist/mcp/classify.js.map +1 -0
- package/dist/mcp/discovery.d.ts +24 -0
- package/dist/mcp/discovery.d.ts.map +1 -0
- package/dist/mcp/discovery.js +78 -0
- package/dist/mcp/discovery.js.map +1 -0
- package/dist/mcp/invoke.d.ts +36 -0
- package/dist/mcp/invoke.d.ts.map +1 -0
- package/dist/mcp/invoke.js +80 -0
- package/dist/mcp/invoke.js.map +1 -0
- package/dist/mcp/policies.d.ts +110 -0
- package/dist/mcp/policies.d.ts.map +1 -0
- package/dist/mcp/policies.js +400 -0
- package/dist/mcp/policies.js.map +1 -0
- package/dist/mcp/probe.d.ts +66 -0
- package/dist/mcp/probe.d.ts.map +1 -0
- package/dist/mcp/probe.js +116 -0
- package/dist/mcp/probe.js.map +1 -0
- package/dist/mcp/redact.d.ts +34 -0
- package/dist/mcp/redact.d.ts.map +1 -0
- package/dist/mcp/redact.js +84 -0
- package/dist/mcp/redact.js.map +1 -0
- package/dist/mcp/registry-store.d.ts +69 -0
- package/dist/mcp/registry-store.d.ts.map +1 -0
- package/dist/mcp/registry-store.js +82 -0
- package/dist/mcp/registry-store.js.map +1 -0
- package/dist/mcp/registry.d.ts +147 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +509 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/rest.d.ts +111 -0
- package/dist/mcp/rest.d.ts.map +1 -0
- package/dist/mcp/rest.js +606 -0
- package/dist/mcp/rest.js.map +1 -0
- package/dist/mcp/secrets.d.ts +66 -0
- package/dist/mcp/secrets.d.ts.map +1 -0
- package/dist/mcp/secrets.js +180 -0
- package/dist/mcp/secrets.js.map +1 -0
- package/dist/mcp/tool-list.d.ts +25 -0
- package/dist/mcp/tool-list.d.ts.map +1 -0
- package/dist/mcp/tool-list.js +62 -0
- package/dist/mcp/tool-list.js.map +1 -0
- package/dist/mcp/upstream-error.d.ts +12 -0
- package/dist/mcp/upstream-error.d.ts.map +1 -0
- package/dist/mcp/upstream-error.js +14 -0
- package/dist/mcp/upstream-error.js.map +1 -0
- package/dist/mcp/usage.d.ts +31 -0
- package/dist/mcp/usage.d.ts.map +1 -0
- package/dist/mcp/usage.js +159 -0
- package/dist/mcp/usage.js.map +1 -0
- package/dist/presets/routes.d.ts +53 -0
- package/dist/presets/routes.d.ts.map +1 -0
- package/dist/presets/routes.js +115 -0
- package/dist/presets/routes.js.map +1 -0
- package/dist/projects/activity.d.ts +6 -18
- package/dist/projects/activity.d.ts.map +1 -1
- package/dist/projects/activity.js +17 -60
- package/dist/projects/activity.js.map +1 -1
- package/dist/projects/events.d.ts +6 -5
- package/dist/projects/events.d.ts.map +1 -1
- package/dist/projects/events.js +33 -58
- package/dist/projects/events.js.map +1 -1
- package/dist/projects/routes.d.ts.map +1 -1
- package/dist/projects/routes.js +19 -0
- package/dist/projects/routes.js.map +1 -1
- package/dist/projects/state-home-preflight.d.ts.map +1 -1
- package/dist/projects/state-home-preflight.js +7 -0
- package/dist/projects/state-home-preflight.js.map +1 -1
- package/dist/projects/state-home-registry.d.ts.map +1 -1
- package/dist/projects/state-home-registry.js +9 -1
- package/dist/projects/state-home-registry.js.map +1 -1
- package/dist/qe/acceptance.d.ts +28 -28
- package/dist/qe/acceptance.d.ts.map +1 -1
- package/dist/qe/acceptance.js +60 -61
- package/dist/qe/acceptance.js.map +1 -1
- package/dist/qe/test-sets.d.ts +3 -5
- package/dist/qe/test-sets.d.ts.map +1 -1
- package/dist/qe/test-sets.js +5 -7
- package/dist/qe/test-sets.js.map +1 -1
- package/dist/skills/installed.d.ts +51 -0
- package/dist/skills/installed.d.ts.map +1 -0
- package/dist/skills/installed.js +46 -0
- package/dist/skills/installed.js.map +1 -0
- package/dist/standing-orders/evaluator.d.ts +113 -0
- package/dist/standing-orders/evaluator.d.ts.map +1 -0
- package/dist/standing-orders/evaluator.js +225 -0
- package/dist/standing-orders/evaluator.js.map +1 -0
- package/dist/standing-orders/history.d.ts +58 -0
- package/dist/standing-orders/history.d.ts.map +1 -0
- package/dist/standing-orders/history.js +117 -0
- package/dist/standing-orders/history.js.map +1 -0
- package/dist/standing-orders/parse.d.ts +19 -0
- package/dist/standing-orders/parse.d.ts.map +1 -0
- package/dist/standing-orders/parse.js +75 -0
- package/dist/standing-orders/parse.js.map +1 -0
- package/dist/standing-orders/routes.d.ts +180 -0
- package/dist/standing-orders/routes.d.ts.map +1 -0
- package/dist/standing-orders/routes.js +95 -0
- package/dist/standing-orders/routes.js.map +1 -0
- package/dist/standing-orders/store.d.ts +154 -0
- package/dist/standing-orders/store.d.ts.map +1 -0
- package/dist/standing-orders/store.js +189 -0
- package/dist/standing-orders/store.js.map +1 -0
- package/dist/studio/assets/index-CvV5qITf.css +32 -0
- package/dist/studio/assets/index-jCOy6Ns7.js +568 -0
- package/dist/studio/index.html +3 -3
- package/dist/studio/testid-inventory.json +3274 -894
- package/dist/team/key.d.ts +5 -0
- package/dist/team/key.d.ts.map +1 -0
- package/dist/team/key.js +21 -0
- package/dist/team/key.js.map +1 -0
- package/dist/team/routes.d.ts +196 -0
- package/dist/team/routes.d.ts.map +1 -0
- package/dist/team/routes.js +274 -0
- package/dist/team/routes.js.map +1 -0
- package/dist/team/ws-relay.d.ts +39 -0
- package/dist/team/ws-relay.d.ts.map +1 -0
- package/dist/team/ws-relay.js +63 -0
- package/dist/team/ws-relay.js.map +1 -0
- package/endpoint-manifest.json +502 -6
- package/package.json +6 -6
- package/dist/interactive/demo-events.d.ts +0 -367
- package/dist/interactive/demo-events.d.ts.map +0 -1
- package/dist/interactive/demo-events.js +0 -1293
- package/dist/interactive/demo-events.js.map +0 -1
- package/dist/qe/gate-events.d.ts +0 -113
- package/dist/qe/gate-events.d.ts.map +0 -1
- package/dist/qe/gate-events.js +0 -172
- package/dist/qe/gate-events.js.map +0 -1
- package/dist/studio/assets/index-Bg7Z3BR-.css +0 -32
- package/dist/studio/assets/index-BvOFMjf9.js +0 -554
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MCP broker's call path (DES-MCP-TOOLS-001 §6; slice S3). A worker on any seat reaches an
|
|
3
|
+
* upstream MCP tool only through here (the garden shim, `wicked-garden run mcp call`, sends
|
|
4
|
+
* `POST /api/v1/mcp/call {token, subject, args}`), and every step below runs in order:
|
|
5
|
+
*
|
|
6
|
+
* 1-3. **Judge.** The subject is resolved in the registry (unknown, disabled, `gone` or changed
|
|
7
|
+
* tools go to the engine as `registered: false`, D-5), then core's `evaluateMcpCall` verifies
|
|
8
|
+
* the token, judges the call for the unit the token is bound to (D-1 phase role, policy,
|
|
9
|
+
* first use, the run's mode) and records the claim. `deny` and `ask` never reach an upstream.
|
|
10
|
+
* 4. **Budget.** At most {@link DEFAULT_MCP_CALL_BUDGET} invoked calls per unit attempt
|
|
11
|
+
* (`WICKED_MCP_CALL_BUDGET` overrides it).
|
|
12
|
+
* 5. **Breaker.** After {@link BREAKER_THRESHOLD} consecutive upstream failures a tool is refused
|
|
13
|
+
* for {@link BREAKER_OPEN_MS}; then one call is let through, and one more failure reopens it.
|
|
14
|
+
* 6. **Invoke** with a deadline. The upstream's secret is resolved here, and only here. A `rest`
|
|
15
|
+
* tool (slice S5a, `rest.ts`) sends only its allowlisted arguments, and only to its pinned
|
|
16
|
+
* base URL: a request that would leave it is refused unsent, recorded as `guard_error`
|
|
17
|
+
* (`rest_host_escape`, I6).
|
|
18
|
+
* 7. **Retry** READ tools only, at most {@link MAX_READ_RETRIES} times, and only on transport
|
|
19
|
+
* errors, 429 and 5xx. A write or destructive tool is never retried: a second attempt could
|
|
20
|
+
* apply the write twice.
|
|
21
|
+
* 8. **Post.** The result is scrubbed (the exact secrets injected on this call, then credential
|
|
22
|
+
* field names and value shapes, D-2), then core's `evaluateMcpOutput` judges it; a deny
|
|
23
|
+
* withholds it. A crash anywhere here is `guard_error`: refused, fail closed.
|
|
24
|
+
* 9. **Record** (D-3). Every judged call is written to `calls.ndjson`; a call whose record cannot
|
|
25
|
+
* be written is refused and its result withheld. The record then rides `/ws`
|
|
26
|
+
* (`mcpCallCompleted`) and the bus (`wicked.crew.mcp_call.completed`).
|
|
27
|
+
*
|
|
28
|
+
* A call that ran and then could not be recorded HAS happened upstream; refusing it withholds the
|
|
29
|
+
* result from the worker, it does not undo the side effect. The record is written after the call
|
|
30
|
+
* because the span it records (timing, status, bytes) only exists then.
|
|
31
|
+
*/
|
|
32
|
+
import type { McpCallBody, McpCallOutcome, McpCallRecord, McpCallResponse } from '../core/types.js';
|
|
33
|
+
import type { McpEngineGate } from '../core/adapter.js';
|
|
34
|
+
import type { McpCallRecordSink } from './call-records.js';
|
|
35
|
+
import { type Invoker } from './invoke.js';
|
|
36
|
+
import type { McpRegistry } from './registry.js';
|
|
37
|
+
/** The bus event each call record rides. */
|
|
38
|
+
export declare const MCP_CALL_COMPLETED = "wicked.crew.mcp_call.completed";
|
|
39
|
+
export declare const DEFAULT_MCP_CALL_BUDGET = 200;
|
|
40
|
+
export declare const BREAKER_THRESHOLD = 5;
|
|
41
|
+
export declare const BREAKER_OPEN_MS = 60000;
|
|
42
|
+
export declare const MAX_READ_RETRIES = 2;
|
|
43
|
+
/** The HTTP status each outcome is answered with. */
|
|
44
|
+
export declare const OUTCOME_STATUS: Record<McpCallOutcome, number>;
|
|
45
|
+
/** A call the broker answered: the HTTP status and the body. */
|
|
46
|
+
export interface McpBrokerAnswer {
|
|
47
|
+
status: number;
|
|
48
|
+
body: McpCallResponse | {
|
|
49
|
+
error: string;
|
|
50
|
+
code: 'invalid_token' | 'bad_request';
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export interface McpBrokerDeps {
|
|
54
|
+
registry: Pick<McpRegistry, 'resolveCall' | 'callSecret'>;
|
|
55
|
+
/** The engine's gate, read per call; `null` = the linked engine has none (every call refused). */
|
|
56
|
+
engine: () => McpEngineGate | null;
|
|
57
|
+
records: McpCallRecordSink;
|
|
58
|
+
invoke?: Invoker;
|
|
59
|
+
/** Called with every record once it is written (`/ws` + bus). Never throws into the call. */
|
|
60
|
+
publish?: (record: McpCallRecord) => void | Promise<void>;
|
|
61
|
+
now?: () => number;
|
|
62
|
+
sleep?: (ms: number) => Promise<void>;
|
|
63
|
+
budgetPerUnit?: number;
|
|
64
|
+
timeoutMs?: number;
|
|
65
|
+
log?: (message: string) => void;
|
|
66
|
+
}
|
|
67
|
+
export declare function budgetFromEnv(env?: NodeJS.ProcessEnv): number;
|
|
68
|
+
export declare class McpBroker {
|
|
69
|
+
private readonly deps;
|
|
70
|
+
private readonly invoke;
|
|
71
|
+
private readonly now;
|
|
72
|
+
private readonly sleep;
|
|
73
|
+
private readonly budget;
|
|
74
|
+
private readonly timeoutMs;
|
|
75
|
+
private readonly used;
|
|
76
|
+
private readonly breakers;
|
|
77
|
+
constructor(deps: McpBrokerDeps);
|
|
78
|
+
call(body: McpCallBody): Promise<McpBrokerAnswer>;
|
|
79
|
+
private record;
|
|
80
|
+
/**
|
|
81
|
+
* Answer a call that does not hand a result back, after recording it. A refusal is a refusal
|
|
82
|
+
* whether or not its record lands; a failed write is logged, and the answer keeps its outcome
|
|
83
|
+
* (a deny is never re-labelled `guard_error`, I5).
|
|
84
|
+
*/
|
|
85
|
+
private refuse;
|
|
86
|
+
private publish;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=broker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker.d.ts","sourceRoot":"","sources":["../../src/mcp/broker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,KAAK,EACV,WAAW,EAEX,cAAc,EACd,aAAa,EACb,eAAe,EAGhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA6D,KAAK,OAAO,EAA2B,MAAM,aAAa,CAAC;AAE/H,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAGjD,4CAA4C;AAC5C,eAAO,MAAM,kBAAkB,mCAAmC,CAAC;AACnE,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,eAAe,QAAS,CAAC;AACtC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AA2BlC,qDAAqD;AACrD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CASzD,CAAC;AAEF,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,eAAe,GAAG,aAAa,CAAA;KAAE,CAAC;CAClF;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,aAAa,GAAG,YAAY,CAAC,CAAC;IAC1D,kGAAkG;IAClG,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAAC;IACnC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAkBD,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAI1E;AAED,qBAAa,SAAS;IASR,OAAO,CAAC,QAAQ,CAAC,IAAI;IARjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;gBAE/B,IAAI,EAAE,aAAa;IAQ1C,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC;IA0JvD,OAAO,CAAC,MAAM;IAmCd;;;;OAIG;YACW,MAAM;IAkCpB,OAAO,CAAC,OAAO;CAShB"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MCP broker's call path (DES-MCP-TOOLS-001 §6; slice S3). A worker on any seat reaches an
|
|
3
|
+
* upstream MCP tool only through here (the garden shim, `wicked-garden run mcp call`, sends
|
|
4
|
+
* `POST /api/v1/mcp/call {token, subject, args}`), and every step below runs in order:
|
|
5
|
+
*
|
|
6
|
+
* 1-3. **Judge.** The subject is resolved in the registry (unknown, disabled, `gone` or changed
|
|
7
|
+
* tools go to the engine as `registered: false`, D-5), then core's `evaluateMcpCall` verifies
|
|
8
|
+
* the token, judges the call for the unit the token is bound to (D-1 phase role, policy,
|
|
9
|
+
* first use, the run's mode) and records the claim. `deny` and `ask` never reach an upstream.
|
|
10
|
+
* 4. **Budget.** At most {@link DEFAULT_MCP_CALL_BUDGET} invoked calls per unit attempt
|
|
11
|
+
* (`WICKED_MCP_CALL_BUDGET` overrides it).
|
|
12
|
+
* 5. **Breaker.** After {@link BREAKER_THRESHOLD} consecutive upstream failures a tool is refused
|
|
13
|
+
* for {@link BREAKER_OPEN_MS}; then one call is let through, and one more failure reopens it.
|
|
14
|
+
* 6. **Invoke** with a deadline. The upstream's secret is resolved here, and only here. A `rest`
|
|
15
|
+
* tool (slice S5a, `rest.ts`) sends only its allowlisted arguments, and only to its pinned
|
|
16
|
+
* base URL: a request that would leave it is refused unsent, recorded as `guard_error`
|
|
17
|
+
* (`rest_host_escape`, I6).
|
|
18
|
+
* 7. **Retry** READ tools only, at most {@link MAX_READ_RETRIES} times, and only on transport
|
|
19
|
+
* errors, 429 and 5xx. A write or destructive tool is never retried: a second attempt could
|
|
20
|
+
* apply the write twice.
|
|
21
|
+
* 8. **Post.** The result is scrubbed (the exact secrets injected on this call, then credential
|
|
22
|
+
* field names and value shapes, D-2), then core's `evaluateMcpOutput` judges it; a deny
|
|
23
|
+
* withholds it. A crash anywhere here is `guard_error`: refused, fail closed.
|
|
24
|
+
* 9. **Record** (D-3). Every judged call is written to `calls.ndjson`; a call whose record cannot
|
|
25
|
+
* be written is refused and its result withheld. The record then rides `/ws`
|
|
26
|
+
* (`mcpCallCompleted`) and the bus (`wicked.crew.mcp_call.completed`).
|
|
27
|
+
*
|
|
28
|
+
* A call that ran and then could not be recorded HAS happened upstream; refusing it withholds the
|
|
29
|
+
* result from the worker, it does not undo the side effect. The record is written after the call
|
|
30
|
+
* because the span it records (timing, status, bytes) only exists then.
|
|
31
|
+
*/
|
|
32
|
+
import { randomBytes } from 'node:crypto';
|
|
33
|
+
import { parseMcpSubject } from './classify.js';
|
|
34
|
+
import { classifyUpstreamError, invokeMcpTool, MCP_CALL_TIMEOUT_MS } from './invoke.js';
|
|
35
|
+
import { scrubResult } from './redact.js';
|
|
36
|
+
import { scrubSecrets } from './secrets.js';
|
|
37
|
+
/** The bus event each call record rides. */
|
|
38
|
+
export const MCP_CALL_COMPLETED = 'wicked.crew.mcp_call.completed';
|
|
39
|
+
export const DEFAULT_MCP_CALL_BUDGET = 200;
|
|
40
|
+
export const BREAKER_THRESHOLD = 5;
|
|
41
|
+
export const BREAKER_OPEN_MS = 60_000;
|
|
42
|
+
export const MAX_READ_RETRIES = 2;
|
|
43
|
+
/** Units whose budget counters are kept; the oldest is dropped first. */
|
|
44
|
+
const BUDGET_UNITS_MAX = 4096;
|
|
45
|
+
/** The HTTP status each outcome is answered with. */
|
|
46
|
+
export const OUTCOME_STATUS = {
|
|
47
|
+
ok: 200,
|
|
48
|
+
denied: 403,
|
|
49
|
+
withheld: 403,
|
|
50
|
+
pending_approval: 409,
|
|
51
|
+
budget_exhausted: 429,
|
|
52
|
+
breaker_open: 503,
|
|
53
|
+
upstream_error: 502,
|
|
54
|
+
guard_error: 500,
|
|
55
|
+
};
|
|
56
|
+
export function budgetFromEnv(env = process.env) {
|
|
57
|
+
const raw = env['WICKED_MCP_CALL_BUDGET'];
|
|
58
|
+
const n = raw === undefined ? NaN : Number(raw);
|
|
59
|
+
return Number.isInteger(n) && n > 0 ? n : DEFAULT_MCP_CALL_BUDGET;
|
|
60
|
+
}
|
|
61
|
+
export class McpBroker {
|
|
62
|
+
deps;
|
|
63
|
+
invoke;
|
|
64
|
+
now;
|
|
65
|
+
sleep;
|
|
66
|
+
budget;
|
|
67
|
+
timeoutMs;
|
|
68
|
+
used = new Map();
|
|
69
|
+
breakers = new Map();
|
|
70
|
+
constructor(deps) {
|
|
71
|
+
this.deps = deps;
|
|
72
|
+
this.invoke = deps.invoke ?? invokeMcpTool;
|
|
73
|
+
this.now = deps.now ?? Date.now;
|
|
74
|
+
this.sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
75
|
+
this.budget = deps.budgetPerUnit ?? budgetFromEnv();
|
|
76
|
+
this.timeoutMs = deps.timeoutMs ?? MCP_CALL_TIMEOUT_MS;
|
|
77
|
+
}
|
|
78
|
+
async call(body) {
|
|
79
|
+
const parts = parseMcpSubject(body.subject);
|
|
80
|
+
if (parts === null)
|
|
81
|
+
return { status: 400, body: { error: `subject must be mcp:<server>/<tool> (got ${JSON.stringify(body.subject)})`, code: 'bad_request' } };
|
|
82
|
+
const span = { start: this.now(), spanId: randomBytes(8).toString('hex'), subject: body.subject, verdict: null, kind: null, retries: 0, bytesOut: 0 };
|
|
83
|
+
// ── 1-3. Resolve and judge ──────────────────────────────────────────────────────────────
|
|
84
|
+
const gate = this.deps.engine();
|
|
85
|
+
if (gate === null) {
|
|
86
|
+
return this.refuse(span, 'guard_error', 'guard_error', 'broker', [], 'the linked engine has no MCP gate (wicked-core-ts predates evaluateMcpCall/evaluateMcpOutput)', 'upgrade the engine; no MCP call runs unjudged');
|
|
87
|
+
}
|
|
88
|
+
let target;
|
|
89
|
+
try {
|
|
90
|
+
target = await this.deps.registry.resolveCall(parts.server, parts.tool);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
return this.refuse(span, 'guard_error', 'guard_error', 'broker', [], `the MCP registry could not be read: ${message(err)}`, 'the operator fixes the registry in studio → MCP tools');
|
|
94
|
+
}
|
|
95
|
+
span.kind = target?.config.kind ?? null;
|
|
96
|
+
const call = {
|
|
97
|
+
server: parts.server,
|
|
98
|
+
tool: parts.tool,
|
|
99
|
+
args: body.args ?? {},
|
|
100
|
+
annotations: target?.annotations ?? null,
|
|
101
|
+
classOverride: target?.classOverride ?? null,
|
|
102
|
+
registered: target?.registered ?? false,
|
|
103
|
+
...(target !== null ? { kind: target.config.kind } : {}),
|
|
104
|
+
carrier: 'shim',
|
|
105
|
+
};
|
|
106
|
+
let verdict;
|
|
107
|
+
try {
|
|
108
|
+
verdict = JSON.parse(await gate.evaluateCall(JSON.stringify({ token: body.token, call })));
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const msg = message(err);
|
|
112
|
+
if (msg.startsWith('invalid_token'))
|
|
113
|
+
return { status: 401, body: { error: msg, code: 'invalid_token' } };
|
|
114
|
+
if (msg.startsWith('bad_request'))
|
|
115
|
+
return { status: 400, body: { error: msg, code: 'bad_request' } };
|
|
116
|
+
return this.refuse(span, 'guard_error', 'guard_error', 'engine', [], `the call could not be judged or recorded: ${msg}`, 'nothing ran; try again, and report it if it persists');
|
|
117
|
+
}
|
|
118
|
+
span.verdict = verdict;
|
|
119
|
+
if (verdict.decision === 'deny') {
|
|
120
|
+
return this.refuse(span, 'denied', 'deny', verdict.ruleIds[0] ?? 'policy', verdict.ruleIds, verdict.reason, verdict.remedy);
|
|
121
|
+
}
|
|
122
|
+
if (verdict.decision === 'ask') {
|
|
123
|
+
return this.refuse(span, 'pending_approval', 'ask', verdict.ruleIds[0] ?? 'policy', verdict.ruleIds, verdict.reason, verdict.remedy);
|
|
124
|
+
}
|
|
125
|
+
if (target === null || !target.registered) {
|
|
126
|
+
// The engine allowed a subject the registry does not hold as registered (unknown, disabled,
|
|
127
|
+
// `gone` or changed since its preview): never invoke it (D-5 holds even if the engine errs).
|
|
128
|
+
return this.refuse(span, 'guard_error', 'guard_error', 'broker', verdict.ruleIds, `${body.subject} is not a registered, enabled tool`, 'register it in studio → MCP tools');
|
|
129
|
+
}
|
|
130
|
+
// ── 4. Budget ───────────────────────────────────────────────────────────────────────────
|
|
131
|
+
const unitKey = `${verdict.unit.runId}:${verdict.unit.ord}:${verdict.unit.attempt}`;
|
|
132
|
+
const used = this.used.get(unitKey) ?? 0;
|
|
133
|
+
if (used >= this.budget) {
|
|
134
|
+
return this.refuse(span, 'budget_exhausted', 'deny', 'budget', [], `this unit has made its ${this.budget} MCP calls`, 'finish with what you have; the budget is per unit');
|
|
135
|
+
}
|
|
136
|
+
// ── 5. Breaker ──────────────────────────────────────────────────────────────────────────
|
|
137
|
+
const breaker = this.breakers.get(body.subject) ?? { failures: 0, openUntil: 0 };
|
|
138
|
+
if (breaker.openUntil > this.now()) {
|
|
139
|
+
const secs = Math.ceil((breaker.openUntil - this.now()) / 1000);
|
|
140
|
+
return this.refuse(span, 'breaker_open', 'deny', 'breaker', [], `${body.subject} failed ${BREAKER_THRESHOLD} times in a row; refused for ${secs} s more`, 'continue without it, or try again later');
|
|
141
|
+
}
|
|
142
|
+
// ── 6. The secret, resolved here and only here. A call refused at this step never reaches the
|
|
143
|
+
// upstream, so it spends no budget and does not move the breaker. ─────────────────────
|
|
144
|
+
let secret;
|
|
145
|
+
try {
|
|
146
|
+
const resolved = await this.deps.registry.callSecret(target.config);
|
|
147
|
+
if (resolved.missing) {
|
|
148
|
+
return this.refuse(span, 'upstream_error', 'allow', verdict.ruleIds[0] ?? 'policy', verdict.ruleIds, `${target.config.auth?.ref ?? 'the secret'} resolves to no secret; the call was not made unauthenticated`, 'the operator sets the secret in studio → MCP tools', 'secret_missing');
|
|
149
|
+
}
|
|
150
|
+
secret = resolved.secret;
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
return this.refuse(span, 'guard_error', 'guard_error', 'broker', verdict.ruleIds, `the upstream's secret could not be read (${message(err)}); nothing ran`, 'try again, and report it if it persists');
|
|
154
|
+
}
|
|
155
|
+
if (breaker.openUntil !== 0) {
|
|
156
|
+
// Half-open: this call is let through, and one more failure reopens the breaker.
|
|
157
|
+
breaker.openUntil = 0;
|
|
158
|
+
breaker.failures = BREAKER_THRESHOLD - 1;
|
|
159
|
+
}
|
|
160
|
+
this.used.delete(unitKey);
|
|
161
|
+
this.used.set(unitKey, used + 1);
|
|
162
|
+
while (this.used.size > BUDGET_UNITS_MAX)
|
|
163
|
+
this.used.delete(this.used.keys().next().value);
|
|
164
|
+
// ── 6-7. Invoke, retrying read tools on transient failures only ─────────────────────────
|
|
165
|
+
const secrets = secret === null ? [] : [secret];
|
|
166
|
+
let result = null;
|
|
167
|
+
for (let attempt = 0;; attempt++) {
|
|
168
|
+
try {
|
|
169
|
+
result = await this.invoke(target.config, secret, parts.tool, call.args, this.timeoutMs, target.rest);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
const failure = classifyUpstreamError(err);
|
|
174
|
+
if (failure.errorClass === 'boundary') {
|
|
175
|
+
// I6: a REST request that would leave its pinned host was refused before it was sent (or a
|
|
176
|
+
// redirect off the host was not followed). Not the tool's failure, so the breaker is not
|
|
177
|
+
// moved; recorded as a guard_error, which fails closed.
|
|
178
|
+
const reason = scrubResult(`${body.subject} was refused: ${failure.message}`, secrets);
|
|
179
|
+
return this.refuse(span, 'guard_error', 'guard_error', 'broker:rest-host-pin', verdict.ruleIds, reason, 'call it with arguments that keep the request on the API\'s own host and path', 'rest_host_escape');
|
|
180
|
+
}
|
|
181
|
+
if (verdict.class === 'read' && failure.retryable && attempt < MAX_READ_RETRIES) {
|
|
182
|
+
span.retries += 1;
|
|
183
|
+
await this.sleep(200 * (attempt + 1));
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
breaker.failures += 1;
|
|
187
|
+
if (breaker.failures >= BREAKER_THRESHOLD)
|
|
188
|
+
breaker.openUntil = this.now() + BREAKER_OPEN_MS;
|
|
189
|
+
this.breakers.set(body.subject, breaker);
|
|
190
|
+
const reason = scrubResult(`${body.subject} failed upstream (${failure.errorClass}): ${failure.message}`, secrets);
|
|
191
|
+
return this.refuse(span, 'upstream_error', 'allow', verdict.ruleIds[0] ?? 'policy', verdict.ruleIds, reason, 'the call may be retried later; a write may or may not have applied', failure.errorClass);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
breaker.failures = 0;
|
|
195
|
+
this.breakers.set(body.subject, breaker);
|
|
196
|
+
// ── 8. Post: scrub, then the output decision; a crash is guard_error ─────────────────────
|
|
197
|
+
let scrubbed;
|
|
198
|
+
let output;
|
|
199
|
+
try {
|
|
200
|
+
scrubbed = scrubResult(result, secrets);
|
|
201
|
+
const raw = JSON.stringify(scrubbed);
|
|
202
|
+
span.bytesOut = Buffer.byteLength(raw, 'utf8');
|
|
203
|
+
output = JSON.parse(await gate.evaluateOutput(JSON.stringify({ token: body.token, call, raw })));
|
|
204
|
+
if (output.decision !== 'allow' && output.decision !== 'deny')
|
|
205
|
+
throw new Error(`unknown output decision ${JSON.stringify(output.decision)}`);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
return this.refuse(span, 'guard_error', 'guard_error', 'engine:mcp-output', [], `the result could not be checked (${scrubSecrets(message(err), secrets)}); it is withheld`, 'the call ran; its result is not shown');
|
|
209
|
+
}
|
|
210
|
+
if (output.decision === 'deny') {
|
|
211
|
+
return this.refuse(span, 'withheld', 'deny', output.ruleIds[0] ?? 'policy', output.ruleIds, output.reason, output.remedy, null, output.claimId ?? null);
|
|
212
|
+
}
|
|
213
|
+
// ── 9. Record, then answer ──────────────────────────────────────────────────────────────
|
|
214
|
+
const isError = scrubbed['isError'] === true;
|
|
215
|
+
const record = this.record(span, 'ok', 'allow', verdict.ruleIds[0] ?? 'policy', verdict.ruleIds, isError ? 'tool_error' : null, verdict.claimId);
|
|
216
|
+
try {
|
|
217
|
+
await this.deps.records.append(record);
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
this.deps.log?.(`[mcp] the call record for ${body.subject} could not be written (${message(err)}); its result is withheld`);
|
|
221
|
+
return {
|
|
222
|
+
status: OUTCOME_STATUS.guard_error,
|
|
223
|
+
body: {
|
|
224
|
+
outcome: 'guard_error',
|
|
225
|
+
subject: body.subject,
|
|
226
|
+
callId: null,
|
|
227
|
+
ruleIds: [],
|
|
228
|
+
reason: 'the call could not be recorded (D-3); its result is withheld',
|
|
229
|
+
remedy: 'the call ran; its result is not shown',
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
this.publish(record);
|
|
234
|
+
return { status: 200, body: { outcome: 'ok', subject: body.subject, callId: span.spanId, result: scrubbed, ruleIds: verdict.ruleIds } };
|
|
235
|
+
}
|
|
236
|
+
record(span, outcome, decision, by, ruleIds, errorClass, claimId) {
|
|
237
|
+
const end = this.now();
|
|
238
|
+
const v = span.verdict;
|
|
239
|
+
return {
|
|
240
|
+
traceId: v?.unit.runId ?? null,
|
|
241
|
+
spanId: span.spanId,
|
|
242
|
+
parentSpanId: v === null ? null : `${v.unit.ord}:${v.unit.attempt}`,
|
|
243
|
+
name: 'mcp.tool.call',
|
|
244
|
+
start: new Date(span.start).toISOString(),
|
|
245
|
+
end: new Date(end).toISOString(),
|
|
246
|
+
ms: end - span.start,
|
|
247
|
+
status: { code: errorClass === null && outcome === 'ok' ? 'ok' : 'error', errorClass: errorClass ?? (outcome === 'ok' ? null : outcome) },
|
|
248
|
+
decision: { decision, by, ruleIds, claimId },
|
|
249
|
+
outcome,
|
|
250
|
+
attrs: {
|
|
251
|
+
'mcp.subject': span.subject,
|
|
252
|
+
'mcp.class': v?.class ?? null,
|
|
253
|
+
'mcp.kind': span.kind,
|
|
254
|
+
'wicked.seat': v?.unit.seat ?? null,
|
|
255
|
+
'wicked.phase': v?.unit.phase ?? null,
|
|
256
|
+
'wicked.carrier': 'shim',
|
|
257
|
+
'bytes.out': span.bytesOut,
|
|
258
|
+
retries: span.retries,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Answer a call that does not hand a result back, after recording it. A refusal is a refusal
|
|
264
|
+
* whether or not its record lands; a failed write is logged, and the answer keeps its outcome
|
|
265
|
+
* (a deny is never re-labelled `guard_error`, I5).
|
|
266
|
+
*/
|
|
267
|
+
async refuse(span, outcome, decision, by, ruleIds, reason, remedy, errorClass = null, claimId = span.verdict?.claimId ?? null) {
|
|
268
|
+
const record = this.record(span, outcome, decision, by, ruleIds, errorClass ?? outcome, claimId);
|
|
269
|
+
let recorded = true;
|
|
270
|
+
try {
|
|
271
|
+
await this.deps.records.append(record);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
recorded = false;
|
|
275
|
+
this.deps.log?.(`[mcp] the call record for ${span.subject} (${outcome}) could not be written: ${message(err)}`);
|
|
276
|
+
}
|
|
277
|
+
if (recorded)
|
|
278
|
+
this.publish(record);
|
|
279
|
+
const body = {
|
|
280
|
+
outcome,
|
|
281
|
+
subject: span.subject,
|
|
282
|
+
callId: recorded ? span.spanId : null,
|
|
283
|
+
ruleIds,
|
|
284
|
+
...(reason !== undefined ? { reason } : {}),
|
|
285
|
+
...(remedy !== undefined ? { remedy } : {}),
|
|
286
|
+
...(outcome === 'pending_approval' ? { pending_approval: span.subject } : {}),
|
|
287
|
+
...(outcome === 'denied' || outcome === 'withheld' ? { denied: true } : {}),
|
|
288
|
+
};
|
|
289
|
+
const status = outcome === 'upstream_error' && errorClass === 'timeout' ? 504 : OUTCOME_STATUS[outcome];
|
|
290
|
+
return { status, body };
|
|
291
|
+
}
|
|
292
|
+
publish(record) {
|
|
293
|
+
const publish = this.deps.publish;
|
|
294
|
+
if (publish === undefined)
|
|
295
|
+
return;
|
|
296
|
+
try {
|
|
297
|
+
void Promise.resolve(publish(record)).catch((err) => this.deps.log?.(`[mcp] publishing call ${record.spanId} failed: ${message(err)}`));
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
this.deps.log?.(`[mcp] publishing call ${record.spanId} failed: ${message(err)}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function message(err) {
|
|
305
|
+
return err instanceof Error ? err.message : String(err);
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=broker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broker.js","sourceRoot":"","sources":["../../src/mcp/broker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP call records at rest (DES-MCP-TOOLS-001 §5, D-3): `<state home>/mcp/calls.ndjson`, one OTel
|
|
3
|
+
* span-shaped record per brokered call. Arguments and results are never captured.
|
|
4
|
+
*
|
|
5
|
+
* The broker writes a record for every call it judged, whatever the outcome, and a call whose
|
|
6
|
+
* record cannot be written is refused (`guard_error`): its result is withheld. Appends are
|
|
7
|
+
* serialized on one promise chain so two concurrent calls never interleave a line.
|
|
8
|
+
*
|
|
9
|
+
* The usage slice (S7) reads the records back on the same chain. A read first folds every record
|
|
10
|
+
* that started more than {@link MCP_CALLS_RAW_DAYS} days ago into `calls-daily.ndjson` (one row per
|
|
11
|
+
* UTC day × subject × seat × decision: calls, errors, total ms) and rewrites `calls.ndjson` without
|
|
12
|
+
* them (write a temp file, then rename). The fold runs on demand, never on a timer.
|
|
13
|
+
*/
|
|
14
|
+
import type { McpCallDecision, McpCallRecord } from '../core/types.js';
|
|
15
|
+
export declare const MCP_CALLS_FILENAME = "calls.ndjson";
|
|
16
|
+
export declare const MCP_CALLS_DAILY_FILENAME = "calls-daily.ndjson";
|
|
17
|
+
/** Records stay raw this long; older ones are folded into the daily file. */
|
|
18
|
+
export declare const MCP_CALLS_RAW_DAYS = 30;
|
|
19
|
+
export interface McpCallRecordSink {
|
|
20
|
+
append(record: McpCallRecord): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/** One row of `calls-daily.ndjson`. */
|
|
23
|
+
export interface McpCallDailyRow {
|
|
24
|
+
day: string;
|
|
25
|
+
subject: string;
|
|
26
|
+
seat: string | null;
|
|
27
|
+
decision: McpCallDecision;
|
|
28
|
+
calls: number;
|
|
29
|
+
errors: number;
|
|
30
|
+
msTotal: number;
|
|
31
|
+
}
|
|
32
|
+
export interface McpCallRecordsRead {
|
|
33
|
+
records: McpCallRecord[];
|
|
34
|
+
/** Lines that did not parse as a call record (kept in the file, left out of every fold). */
|
|
35
|
+
skipped: number;
|
|
36
|
+
/** Records this read folded into the daily file. */
|
|
37
|
+
folded: number;
|
|
38
|
+
}
|
|
39
|
+
/** The records the usage fold reads. */
|
|
40
|
+
export interface McpCallRecordSource {
|
|
41
|
+
read(now: number): Promise<McpCallRecordsRead>;
|
|
42
|
+
}
|
|
43
|
+
/** A parsed line when it has the fields the usage fold reads; otherwise `null`. */
|
|
44
|
+
export declare function parseCallRecord(line: string): McpCallRecord | null;
|
|
45
|
+
/** Fold records into daily rows, sorted by day, subject, seat, decision. */
|
|
46
|
+
export declare function foldDaily(records: readonly McpCallRecord[]): McpCallDailyRow[];
|
|
47
|
+
export declare class McpCallRecordFile implements McpCallRecordSink, McpCallRecordSource {
|
|
48
|
+
private readonly dir;
|
|
49
|
+
private tail;
|
|
50
|
+
constructor(dir?: string);
|
|
51
|
+
get path(): string;
|
|
52
|
+
get dailyPath(): string;
|
|
53
|
+
append(record: McpCallRecord): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Every record of the last {@link MCP_CALLS_RAW_DAYS} days, after folding older ones into the
|
|
56
|
+
* daily file. Serialized with the appends, so no call's line is lost to the rewrite.
|
|
57
|
+
*/
|
|
58
|
+
read(now: number): Promise<McpCallRecordsRead>;
|
|
59
|
+
private serialize;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=call-records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-records.d.ts","sourceRoot":"","sources":["../../src/mcp/call-records.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGvE,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AACjD,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAC7D,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAKrC,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED,uCAAuC;AACvC,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAChD;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAoBlE;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,eAAe,EAAE,CAe9E;AAED,qBAAa,iBAAkB,YAAW,iBAAiB,EAAE,mBAAmB;IAGlE,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFhC,OAAO,CAAC,IAAI,CAAuC;gBAEtB,GAAG,GAAE,MAAsB;IAExD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5C;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAwC9C,OAAO,CAAC,SAAS;CAKlB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP call records at rest (DES-MCP-TOOLS-001 §5, D-3): `<state home>/mcp/calls.ndjson`, one OTel
|
|
3
|
+
* span-shaped record per brokered call. Arguments and results are never captured.
|
|
4
|
+
*
|
|
5
|
+
* The broker writes a record for every call it judged, whatever the outcome, and a call whose
|
|
6
|
+
* record cannot be written is refused (`guard_error`): its result is withheld. Appends are
|
|
7
|
+
* serialized on one promise chain so two concurrent calls never interleave a line.
|
|
8
|
+
*
|
|
9
|
+
* The usage slice (S7) reads the records back on the same chain. A read first folds every record
|
|
10
|
+
* that started more than {@link MCP_CALLS_RAW_DAYS} days ago into `calls-daily.ndjson` (one row per
|
|
11
|
+
* UTC day × subject × seat × decision: calls, errors, total ms) and rewrites `calls.ndjson` without
|
|
12
|
+
* them (write a temp file, then rename). The fold runs on demand, never on a timer.
|
|
13
|
+
*/
|
|
14
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { mcpStateDir } from './registry-store.js';
|
|
17
|
+
export const MCP_CALLS_FILENAME = 'calls.ndjson';
|
|
18
|
+
export const MCP_CALLS_DAILY_FILENAME = 'calls-daily.ndjson';
|
|
19
|
+
/** Records stay raw this long; older ones are folded into the daily file. */
|
|
20
|
+
export const MCP_CALLS_RAW_DAYS = 30;
|
|
21
|
+
const DAY_MS = 86_400_000;
|
|
22
|
+
const DECISIONS = new Set(['allow', 'ask', 'deny', 'guard_error']);
|
|
23
|
+
/** A parsed line when it has the fields the usage fold reads; otherwise `null`. */
|
|
24
|
+
export function parseCallRecord(line) {
|
|
25
|
+
let v;
|
|
26
|
+
try {
|
|
27
|
+
v = JSON.parse(line);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (typeof v !== 'object' || v === null)
|
|
33
|
+
return null;
|
|
34
|
+
const r = v;
|
|
35
|
+
const ok = typeof r.spanId === 'string' &&
|
|
36
|
+
typeof r.start === 'string' &&
|
|
37
|
+
Number.isFinite(Date.parse(r.start)) &&
|
|
38
|
+
typeof r.ms === 'number' &&
|
|
39
|
+
typeof r.status?.code === 'string' &&
|
|
40
|
+
DECISIONS.has(r.decision?.decision) &&
|
|
41
|
+
typeof r.attrs?.['mcp.subject'] === 'string' &&
|
|
42
|
+
(r.traceId === null || typeof r.traceId === 'string') &&
|
|
43
|
+
(r.parentSpanId === null || typeof r.parentSpanId === 'string');
|
|
44
|
+
return ok ? r : null;
|
|
45
|
+
}
|
|
46
|
+
/** Fold records into daily rows, sorted by day, subject, seat, decision. */
|
|
47
|
+
export function foldDaily(records) {
|
|
48
|
+
const rows = new Map();
|
|
49
|
+
for (const r of records) {
|
|
50
|
+
const day = new Date(Date.parse(r.start)).toISOString().slice(0, 10);
|
|
51
|
+
const seat = r.attrs['wicked.seat'] ?? null;
|
|
52
|
+
const key = JSON.stringify([day, r.attrs['mcp.subject'], seat, r.decision.decision]);
|
|
53
|
+
const row = rows.get(key) ?? { day, subject: r.attrs['mcp.subject'], seat, decision: r.decision.decision, calls: 0, errors: 0, msTotal: 0 };
|
|
54
|
+
row.calls += 1;
|
|
55
|
+
if (r.status.code === 'error')
|
|
56
|
+
row.errors += 1;
|
|
57
|
+
row.msTotal += r.ms;
|
|
58
|
+
rows.set(key, row);
|
|
59
|
+
}
|
|
60
|
+
return [...rows.values()].sort((a, b) => a.day.localeCompare(b.day) || a.subject.localeCompare(b.subject) || (a.seat ?? '').localeCompare(b.seat ?? '') || a.decision.localeCompare(b.decision));
|
|
61
|
+
}
|
|
62
|
+
export class McpCallRecordFile {
|
|
63
|
+
dir;
|
|
64
|
+
tail = Promise.resolve();
|
|
65
|
+
constructor(dir = mcpStateDir()) {
|
|
66
|
+
this.dir = dir;
|
|
67
|
+
}
|
|
68
|
+
get path() {
|
|
69
|
+
return join(this.dir, MCP_CALLS_FILENAME);
|
|
70
|
+
}
|
|
71
|
+
get dailyPath() {
|
|
72
|
+
return join(this.dir, MCP_CALLS_DAILY_FILENAME);
|
|
73
|
+
}
|
|
74
|
+
append(record) {
|
|
75
|
+
const line = `${JSON.stringify(record)}\n`;
|
|
76
|
+
return this.serialize(async () => {
|
|
77
|
+
await mkdir(this.dir, { recursive: true, mode: 0o700 });
|
|
78
|
+
await appendFile(this.path, line, { mode: 0o600 });
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Every record of the last {@link MCP_CALLS_RAW_DAYS} days, after folding older ones into the
|
|
83
|
+
* daily file. Serialized with the appends, so no call's line is lost to the rewrite.
|
|
84
|
+
*/
|
|
85
|
+
read(now) {
|
|
86
|
+
return this.serialize(async () => {
|
|
87
|
+
let text;
|
|
88
|
+
try {
|
|
89
|
+
text = await readFile(this.path, 'utf8');
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err.code === 'ENOENT')
|
|
93
|
+
return { records: [], skipped: 0, folded: 0 };
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
const cutoff = now - MCP_CALLS_RAW_DAYS * DAY_MS;
|
|
97
|
+
const keep = [];
|
|
98
|
+
const records = [];
|
|
99
|
+
const old = [];
|
|
100
|
+
let skipped = 0;
|
|
101
|
+
for (const line of text.split('\n')) {
|
|
102
|
+
if (line.trim() === '')
|
|
103
|
+
continue;
|
|
104
|
+
const r = parseCallRecord(line);
|
|
105
|
+
if (r === null) {
|
|
106
|
+
skipped += 1;
|
|
107
|
+
keep.push(line);
|
|
108
|
+
}
|
|
109
|
+
else if (Date.parse(r.start) < cutoff) {
|
|
110
|
+
old.push(r);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
keep.push(line);
|
|
114
|
+
records.push(r);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (old.length > 0) {
|
|
118
|
+
// The raw file is rewritten only after the daily rows are durable: a crash in between
|
|
119
|
+
// counts those records twice in the archive, never zero times.
|
|
120
|
+
const daily = foldDaily(old).map((row) => `${JSON.stringify(row)}\n`).join('');
|
|
121
|
+
await appendFile(this.dailyPath, daily, { mode: 0o600 });
|
|
122
|
+
const tmp = `${this.path}.${process.pid}.tmp`;
|
|
123
|
+
await writeFile(tmp, keep.map((l) => `${l}\n`).join(''), { mode: 0o600 });
|
|
124
|
+
await rename(tmp, this.path);
|
|
125
|
+
}
|
|
126
|
+
return { records, skipped, folded: old.length };
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
serialize(work) {
|
|
130
|
+
const next = this.tail.then(work);
|
|
131
|
+
this.tail = next.catch(() => undefined);
|
|
132
|
+
return next;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=call-records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-records.js","sourceRoot":"","sources":["../../src/mcp/call-records.ts"],"names":[],"mappings":"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"}
|