From de67b677d84f67b3b5da6babf3852d5e4eb0ea13 Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Sun, 28 Jun 2026 08:48:00 +0300 Subject: [PATCH] =?UTF-8?q?docs(spec):=20amend=20Spec=20049=20=E2=80=94=20?= =?UTF-8?q?legalize=20server=5Fquarantined=20status=20(six-value=20taxonom?= =?UTF-8?q?y)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns Spec 049 with PR #778, which adds a sixth disabled-tool status, server_quarantined, surfaced by a dedicated quarantined-tool discovery pass (quarantined tools are deliberately excluded from the search index as a TPA defense). Spec 049 pinned the taxonomy to exactly five values and assumed all locked tools live in the index, so #778's behavior was correct but undocumented. - FR-004: five -> six values; server_quarantined assigned by the discovery pass (not the classifier), name-only, description/schema withheld; config-denied tools skipped by the pass. - FR-003: note the name-only exception for quarantined entries. - Assumptions: quarantined tools are excluded from the index and enumerated from authoritative quarantine state. - contracts/mcp-deltas.md: add server_quarantined to the status enum + example response shape and remediation. - design doc taxonomy: five -> six, with the server_quarantined explanation. Related #778 --- ...gent-discoverable-disabled-tools-design.md | 14 ++++++++- .../contracts/mcp-deltas.md | 17 ++++++++-- .../spec.md | 31 +++++++++++++++---- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/docs/superpowers/specs/2026-05-18-agent-discoverable-disabled-tools-design.md b/docs/superpowers/specs/2026-05-18-agent-discoverable-disabled-tools-design.md index 9fa8a8afa..e8cec447e 100644 --- a/docs/superpowers/specs/2026-05-18-agent-discoverable-disabled-tools-design.md +++ b/docs/superpowers/specs/2026-05-18-agent-discoverable-disabled-tools-design.md @@ -91,7 +91,8 @@ not exercised. ### 4.3 Status taxonomy & classification -`status` is one of five values. Classification order is **first match wins**: +`status` is one of six values. The first five are classifier-assigned to +index-discoverable tools; classification order is **first match wins**: | Order | Condition | `status` | `remediation` text (emitted once if present) | |------|-----------|----------|----------------------------------------------| @@ -105,6 +106,17 @@ not exercised. *wrong* remediation (e.g. telling the user to toggle a UI switch for a config-locked tool). The four happy-path states stay clean. +The sixth value, `server_quarantined`, is **not** classifier-assigned: quarantined +tools are deliberately absent from the search index (their untrusted descriptions +are withheld as a Tool Poisoning Attack defense), so the index loop cannot reach +them. A dedicated discovery pass enumerates them from authoritative quarantine +state and emits **name-only** locked entries — a tool on a quarantined server gets +`server_quarantined`; a tool-level pending/changed approval on a trusted server +re-uses `pending_approval`. A tool also denied by operator config is skipped +(approval could not make it callable). Remediation for `server_quarantined`: "Its +server is quarantined for security review. Its tools cannot be called until the +user reviews and approves the server in the mcpproxy UI or system tray." + ### 4.4 Reactive triggers (discoverability) Option C's only weakness is the agent not knowing the flag exists. Closed two ways: diff --git a/specs/049-agent-discoverable-disabled-tools/contracts/mcp-deltas.md b/specs/049-agent-discoverable-disabled-tools/contracts/mcp-deltas.md index e14cecbd2..24fd69b49 100644 --- a/specs/049-agent-discoverable-disabled-tools/contracts/mcp-deltas.md +++ b/specs/049-agent-discoverable-disabled-tools/contracts/mcp-deltas.md @@ -24,14 +24,27 @@ Tool description gains exactly one sentence (FR-014): "tools": [ /* callable results — UNCHANGED order/shape (FR-002, FR-006) */ ], "disabled": [ // NEW, after callable, ≤ min(limit,10) { "name": "delete_repo", "server": "github", - "description": "Delete a repository", "status": "disabled_by_config" } + "description": "Delete a repository", "status": "disabled_by_config" }, + // Quarantined-tool discovery pass: name-only, description/schema withheld + // (TPA defense); `name` is the ":" key. Prepended ahead of + // index-derived locked entries so the min(limit,10) cap can't drop them. + { "name": "github:rotate_keys", "server": "github", + "status": "server_quarantined" } ], "remediation": { // NEW, once, only present statuses - "disabled_by_config": "Locked by operator policy in mcp_config.json (enabled_tools/disabled_tools). The user cannot enable this from the UI; ask the operator to change the server config." + "disabled_by_config": "Locked by operator policy in mcp_config.json (enabled_tools/disabled_tools). The user cannot enable this from the UI; ask the operator to change the server config.", + "server_quarantined": "Its server is quarantined for security review. Its tools cannot be called until the user reviews and approves the server in the mcpproxy UI or system tray." } } ``` +`status` enum (FR-004): `disabled_by_config`, `disabled_by_user`, +`pending_approval`, `server_disabled`, `disabled_unknown`, `server_quarantined`. +The first five are classifier-assigned to index-discoverable tools; the last is +assigned by the quarantined-tool discovery pass (also re-using `pending_approval` +for tool-level pending/changed approvals), which surfaces name-only entries from +authoritative quarantine state because quarantined tools are not in the index. + ### Response delta (0 callable results, ≥1 locked match, flag OFF) — FR-009 A one-line note added to the existing result text: diff --git a/specs/049-agent-discoverable-disabled-tools/spec.md b/specs/049-agent-discoverable-disabled-tools/spec.md index 40943f086..b5287a57b 100644 --- a/specs/049-agent-discoverable-disabled-tools/spec.md +++ b/specs/049-agent-discoverable-disabled-tools/spec.md @@ -120,11 +120,23 @@ callable one carries none. fields, no reordering). - **FR-003**: Each returned locked tool MUST carry a lean shape: name, owning server, the existing one-line description, and a single `status` value. -- **FR-004**: `status` MUST be one of exactly five values — + Exception: a `server_quarantined` entry (and any tool surfaced by the + quarantined-tool discovery pass) withholds the description and schema, so it + carries name + owning server + `status` only. +- **FR-004**: `status` MUST be one of exactly six values — `disabled_by_config`, `disabled_by_user`, `pending_approval`, - `server_disabled`, `disabled_unknown` — assigned by fixed first-match - precedence in that order (server-off, then config, then user-disabled, then - pending approval, else unknown). + `server_disabled`, `disabled_unknown`, `server_quarantined`. The first five + are assigned to index-discoverable tools by fixed first-match precedence in + that order (server-off, then config, then user-disabled, then pending + approval, else unknown). `server_quarantined` is assigned separately by the + quarantined-tool discovery pass (not the classifier), because quarantined + tools are deliberately absent from the search index (see Assumptions): it + covers a tool on a quarantined server (status `server_quarantined`) and a + tool-level pending/changed approval on a trusted server (status + `pending_approval`), with description and schema withheld to avoid exposing a + Tool Poisoning Attack payload. A tool also denied by operator config + (`enabled_tools`/`disabled_tools`) is skipped by this pass rather than + surfaced, since approval could not make it callable. - **FR-005**: The response MUST include a single remediation map emitted once, containing only the keys for statuses actually present in the response; no per-tool remediation text. @@ -154,7 +166,8 @@ callable one carries none. ### Key Entities *(include if feature involves data)* - **Locked tool entry**: A discovered tool that is not callable — name, server, - one-line description, and one `status` of the five-value set. + one-line description (withheld for quarantined entries), and one `status` of + the six-value set. - **Status**: The single machine-branchable reason a tool is not callable, mapped 1:1 to a remediation class. - **Remediation map**: Response-level mapping from each present status to one @@ -185,7 +198,13 @@ callable one carries none. - Locked tools are present in the existing search index (verified during brainstorming: indexing does not filter by callability; filtering is - request-time only). + request-time only). Exception: quarantined tools — both on a quarantined + server and tool-level pending/changed approvals — are deliberately excluded + from the search index so their untrusted descriptions cannot be ranked or + exposed (Tool Poisoning Attack defense). They are therefore not reachable via + the index loop and are instead enumerated from authoritative quarantine state + by a dedicated discovery pass that emits name-only `server_quarantined` / + `pending_approval` entries. - The config-vs-user discriminator introduced by PR #468 is available and is reused unchanged as the authoritative config-denial signal. - "Limit" refers to the discovery request's existing result-limit parameter.