[PR #1669] [CLOSED] feat(mcp): add operator tool selection #4739

Closed
opened 2026-10-31 00:54:33 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1669
Author: @pompiliodonatoapp
Created: 10/11/2026
Status: ❌ Closed

Base: main ← Head: feat/operator-tool-selection


📝 Commits (3)

  • 017a5cb feat(mcp): add operator tool selection
  • 72f5d7d fix(mcp): enforce operator effects and honor excluded doctor probes
  • 28c9b8d fix(mcp): decouple effect algebra fixtures and group doctor diagnostics

📊 Changes

15 files changed (+780 additions, -29 deletions)

View changed files

📝 docs/mcp-contracts.md (+43 -1)
📝 src/application/CapabilityInventory.ts (+4 -1)
📝 src/config.test.ts (+21 -0)
➕ src/config/mcpToolSelection.ts (+112 -0)
📝 src/config/parseConfig.ts (+5 -0)
📝 src/contracts/toolEffects.test.ts (+18 -3)
📝 src/contracts/toolEffects.ts (+1 -1)
➕ src/contracts/toolsets.ts (+131 -0)
📝 src/mcpDoctor.ts (+59 -22)
📝 src/server/EvidenceMcpServer.ts (+13 -1)
📝 src/server/createServer.ts (+15 -0)
📝 src/server/registerSessionStatusTool.ts (+1 -0)
📝 tests/boundary/mcp/mcpDoctor.test.ts (+28 -0)
➕ tests/boundary/mcp/toolSelectionMcp.test.ts (+142 -0)
➕ tests/conformance/contracts/mcpToolSelection.test.ts (+187 -0)

📄 Description

Summary

Operators can now narrow the MCP tool inventory at startup with
REA_MCP_TOOLSETS, REA_MCP_DENY_EFFECTS and REA_MCP_DENY_TOOLS. With none
set, behavior is unchanged: tools/list still returns the complete canonical
inventory (170 tools at this base).

No existing issue is linked. This is proposed as an enhancement. The
motivation is the documented static-context cost of the full inventory (for
example the Copilot CLI compaction_static_context_blocked failure described
in docs/installation.md) and least-privilege setups where a workspace should
not offer tools that run caller commands or control devices.

Problem and expected behavior

A workspace that only studies web and JavaScript artifacts currently advertises
native, Android device, firmware and process-capture tools as well. Expected:
an operator can register only the toolsets a workspace needs, and a removed
tool can be neither listed nor invoked.

Change and scope

  • src/contracts/toolsets.ts partitions the public inventory into ten
    toolsets: core, process, native, artifacts, evm, managed,
    android, firmware, web, javascript.
    • The partition follows the contract families, with purpose-based exceptions:
      • process capture, which runs caller commands, gets its own process
        toolset;
      • the Android and Apple graph projections go with their package toolsets;
      • inspect_analysis_view and reconstruction accounting go in core.
    • core is always retained.
  • src/config/mcpToolSelection.ts parses the three settings into one
    predicate.
    • Toolsets apply first, then denied effects, then denied tools.
    • Denied effects fail closed for tools without declared effects.
    • Unknown tool names in REA_MCP_DENY_TOOLS are rejected, so a typo never
      leaves a tool registered.
  • EvidenceMcpServer.registerTool removes a deselected tool right after
    registration, before any client connects. The SDK guards the list-change
    notification while disconnected.
  • parseConfig validates the selection at startup, so an invalid value exits
    with a message naming the setting. Previously an error raised inside the
    stdio factory would surface as a lost connection.
  • binary_session tool_availability omits deselected tools, and
    rea mcp doctor compares against the selected inventory.

Intentionally not included: per-call authorization, runtime toggling, and
filtering guided prompts (prompts that name a removed tool remain listed, as
documented).

Contract and boundary impact

  • Semantic owner and earliest changed stage: server composition
    (createServer → EvidenceMcpServer); configuration parsing.
  • CLI and MCP/tool-catalog contract: unchanged when unset. When set,
    tools/list advertises a subset, and tools/call on a removed tool returns
    JSON-RPC -32602 (unknown tool). The catalog digest still identifies the
    canonical catalog.
  • Provider, bridge, target-format, or platform compatibility: none.
  • Evidence, artifact, provenance, or reconstruction contract: none.
  • Process execution, authorization, cleanup, or containment impact:
    reduces the offered surface only; documented as not a sandbox.
  • Generated metadata, package, or installation impact: none
    (npm run docs:check passes).

Evidence and regression coverage

  • Tests added or updated:

    • tests/conformance/contracts/mcpToolSelection.test.ts (20 tests): the
      partition covers every public tool exactly once; grouping; parsing; fail
      closed; the documented example.
    • tests/boundary/mcp/toolSelectionMcp.test.ts (4 tests): tools/list
      subset; -32602 without reaching the handler; production composition;
      binary_session inventory equals the advertised tools.
    • src/config.test.ts: startup rejection naming each setting.
  • Base reproduction or other evidence: not applicable (feature).

  • User-visible output:

    $ REA_MCP_TOOLSETS=browser rea mcp
    REA configuration is invalid: REA_MCP_TOOLSETS must be a comma-separated list of toolsets: core, process, native, artifacts, evm, managed, android, firmware, web, javascript. The core toolset is always retained.
    (exit 1)
    
    $ REA_MCP_TOOLSETS=web,javascript,artifacts REA_MCP_DENY_TOOLS=capture_electron_scenario rea mcp doctor --json
    healthy: true; inventory:tools expected=56 observed=56 missing=none unexpected=none duplicates=none
    
  • Remaining proof gaps: the Linux CI lanes and real-browser/provider lanes
    were not run (see below).

Validation performed

All runs below were on Windows 11 x64 with Node 22.22.3.

On this branch (base 31bfff6f):

  • npm ci, npm run typecheck: passed.
  • npm run check:fast: passed (typecheck and lint, including module
    boundaries).
  • npm run verify:test-discovery: discovered all 797 test files exactly once.
  • npm run docs:check: passed.
  • Focused tests, 79 in all, passed:
    • the selection conformance and boundary tests;
    • src/config.test.ts;
    • CapabilityInventory.test.ts;
    • the compact input schema profile tests.
  • Built stdio server:
    • rea mcp doctor --json with
      REA_MCP_TOOLSETS=web,javascript,artifacts and
      REA_MCP_DENY_TOOLS=capture_electron_scenario reported healthy, 56/56
      tools. One cold first run after the build exceeded the doctor deadline in
      binary_session; the next runs were healthy.
    • An invalid REA_MCP_TOOLSETS value exits 1 with the message above.
  • Timing with the same client, two runs per mode:
    Mode Tools tools/list First binary_session Connect
    Full 170 2.8–3.2 s 21–24 s about 15–17 s
    Selected 56 1.4–1.7 s 15–16 s about 15–17 s

On the previous base ac9677bd, with the same commit content:

  • npm run test:fast: 3,869 passed, 59 failed. The failing files were re-run
    on a clean worktree of ac9677bd:
    • all fail identically there (missing Windows native manifest, ?/#
      filenames on NTFS, macOS-only providers);
    • except a timing-sensitive ProcessHarness case, which fails about 1 in 4
      runs on both revisions.
  • npx vitest run --project mcp-boundary: 326 passed, 10 failed.
    • The base fails the same serverIdentity, serverWorkflowMcp,
      sessionMcp, toolErrorSchemaDelivery, dylibResolutionMcp and
      electronMcp cases (Windows timeouts and symlinks).
    • contractPresentation and toolSchemaValidity pass after
      npm run test:prepare.
  • npm run verify:architecture-guards: passed.
  • npm run knip: reported only lint-staged (package.json and
    .husky/pre-commit), which this change does not touch.

The Linux CI lanes and the real browser/provider lanes were not run locally.

Compatibility, safety, and release

  • Breaking changes or migration steps: none; all settings are opt-in.
  • Real Hopper/Ghidra, browser, or OS coverage: Windows 11 only; Linux CI
    not run locally.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: narrowing only;
    documented that selection limits which tools a model can request and does
    not sandbox the remaining ones.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/1669 **Author:** [@pompiliodonatoapp](https://github.com/pompiliodonatoapp) **Created:** 10/11/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `feat/operator-tool-selection` --- ### 📝 Commits (3) - [`017a5cb`](https://github.com/morluto/rea/commit/017a5cb0477c26bf82cb004085629521752e0cf1) feat(mcp): add operator tool selection - [`72f5d7d`](https://github.com/morluto/rea/commit/72f5d7d27d8924464d2ce239b67de3ec3d7d2258) fix(mcp): enforce operator effects and honor excluded doctor probes - [`28c9b8d`](https://github.com/morluto/rea/commit/28c9b8de4b965043e755da13f82919b2e8b21132) fix(mcp): decouple effect algebra fixtures and group doctor diagnostics ### 📊 Changes **15 files changed** (+780 additions, -29 deletions) <details> <summary>View changed files</summary> 📝 `docs/mcp-contracts.md` (+43 -1) 📝 `src/application/CapabilityInventory.ts` (+4 -1) 📝 `src/config.test.ts` (+21 -0) ➕ `src/config/mcpToolSelection.ts` (+112 -0) 📝 `src/config/parseConfig.ts` (+5 -0) 📝 `src/contracts/toolEffects.test.ts` (+18 -3) 📝 `src/contracts/toolEffects.ts` (+1 -1) ➕ `src/contracts/toolsets.ts` (+131 -0) 📝 `src/mcpDoctor.ts` (+59 -22) 📝 `src/server/EvidenceMcpServer.ts` (+13 -1) 📝 `src/server/createServer.ts` (+15 -0) 📝 `src/server/registerSessionStatusTool.ts` (+1 -0) 📝 `tests/boundary/mcp/mcpDoctor.test.ts` (+28 -0) ➕ `tests/boundary/mcp/toolSelectionMcp.test.ts` (+142 -0) ➕ `tests/conformance/contracts/mcpToolSelection.test.ts` (+187 -0) </details> ### 📄 Description ## Summary Operators can now narrow the MCP tool inventory at startup with `REA_MCP_TOOLSETS`, `REA_MCP_DENY_EFFECTS` and `REA_MCP_DENY_TOOLS`. With none set, behavior is unchanged: `tools/list` still returns the complete canonical inventory (170 tools at this base). No existing issue is linked. This is proposed as an enhancement. The motivation is the documented static-context cost of the full inventory (for example the Copilot CLI `compaction_static_context_blocked` failure described in `docs/installation.md`) and least-privilege setups where a workspace should not offer tools that run caller commands or control devices. ## Problem and expected behavior A workspace that only studies web and JavaScript artifacts currently advertises native, Android device, firmware and process-capture tools as well. Expected: an operator can register only the toolsets a workspace needs, and a removed tool can be neither listed nor invoked. ## Change and scope - `src/contracts/toolsets.ts` partitions the public inventory into ten toolsets: `core`, `process`, `native`, `artifacts`, `evm`, `managed`, `android`, `firmware`, `web`, `javascript`. - The partition follows the contract families, with purpose-based exceptions: - process capture, which runs caller commands, gets its own `process` toolset; - the Android and Apple graph projections go with their package toolsets; - `inspect_analysis_view` and reconstruction accounting go in `core`. - `core` is always retained. - `src/config/mcpToolSelection.ts` parses the three settings into one predicate. - Toolsets apply first, then denied effects, then denied tools. - Denied effects fail closed for tools without declared effects. - Unknown tool names in `REA_MCP_DENY_TOOLS` are rejected, so a typo never leaves a tool registered. - `EvidenceMcpServer.registerTool` removes a deselected tool right after registration, before any client connects. The SDK guards the list-change notification while disconnected. - `parseConfig` validates the selection at startup, so an invalid value exits with a message naming the setting. Previously an error raised inside the stdio factory would surface as a lost connection. - `binary_session` `tool_availability` omits deselected tools, and `rea mcp doctor` compares against the selected inventory. Intentionally not included: per-call authorization, runtime toggling, and filtering guided prompts (prompts that name a removed tool remain listed, as documented). ## Contract and boundary impact - **Semantic owner and earliest changed stage:** server composition (`createServer` → `EvidenceMcpServer`); configuration parsing. - **CLI and MCP/tool-catalog contract:** unchanged when unset. When set, `tools/list` advertises a subset, and `tools/call` on a removed tool returns JSON-RPC `-32602` (unknown tool). The catalog digest still identifies the canonical catalog. - **Provider, bridge, target-format, or platform compatibility:** none. - **Evidence, artifact, provenance, or reconstruction contract:** none. - **Process execution, authorization, cleanup, or containment impact:** reduces the offered surface only; documented as not a sandbox. - **Generated metadata, package, or installation impact:** none (`npm run docs:check` passes). ## Evidence and regression coverage - **Tests added or updated:** - `tests/conformance/contracts/mcpToolSelection.test.ts` (20 tests): the partition covers every public tool exactly once; grouping; parsing; fail closed; the documented example. - `tests/boundary/mcp/toolSelectionMcp.test.ts` (4 tests): `tools/list` subset; `-32602` without reaching the handler; production composition; `binary_session` inventory equals the advertised tools. - `src/config.test.ts`: startup rejection naming each setting. - **Base reproduction or other evidence:** not applicable (feature). - **User-visible output:** ```text $ REA_MCP_TOOLSETS=browser rea mcp REA configuration is invalid: REA_MCP_TOOLSETS must be a comma-separated list of toolsets: core, process, native, artifacts, evm, managed, android, firmware, web, javascript. The core toolset is always retained. (exit 1) $ REA_MCP_TOOLSETS=web,javascript,artifacts REA_MCP_DENY_TOOLS=capture_electron_scenario rea mcp doctor --json healthy: true; inventory:tools expected=56 observed=56 missing=none unexpected=none duplicates=none ``` - **Remaining proof gaps:** the Linux CI lanes and real-browser/provider lanes were not run (see below). ## Validation performed All runs below were on Windows 11 x64 with Node 22.22.3. On this branch (base `31bfff6f`): - `npm ci`, `npm run typecheck`: passed. - `npm run check:fast`: passed (typecheck and lint, including module boundaries). - `npm run verify:test-discovery`: discovered all 797 test files exactly once. - `npm run docs:check`: passed. - Focused tests, 79 in all, passed: - the selection conformance and boundary tests; - `src/config.test.ts`; - `CapabilityInventory.test.ts`; - the compact input schema profile tests. - Built stdio server: - `rea mcp doctor --json` with `REA_MCP_TOOLSETS=web,javascript,artifacts` and `REA_MCP_DENY_TOOLS=capture_electron_scenario` reported healthy, 56/56 tools. One cold first run after the build exceeded the doctor deadline in `binary_session`; the next runs were healthy. - An invalid `REA_MCP_TOOLSETS` value exits 1 with the message above. - Timing with the same client, two runs per mode: | Mode | Tools | `tools/list` | First `binary_session` | Connect | | --- | --- | --- | --- | --- | | Full | 170 | 2.8–3.2 s | 21–24 s | about 15–17 s | | Selected | 56 | 1.4–1.7 s | 15–16 s | about 15–17 s | On the previous base `ac9677bd`, with the same commit content: - `npm run test:fast`: 3,869 passed, 59 failed. The failing files were re-run on a clean worktree of `ac9677bd`: - all fail identically there (missing Windows native manifest, `?`/`#` filenames on NTFS, macOS-only providers); - except a timing-sensitive `ProcessHarness` case, which fails about 1 in 4 runs on both revisions. - `npx vitest run --project mcp-boundary`: 326 passed, 10 failed. - The base fails the same `serverIdentity`, `serverWorkflowMcp`, `sessionMcp`, `toolErrorSchemaDelivery`, `dylibResolutionMcp` and `electronMcp` cases (Windows timeouts and symlinks). - `contractPresentation` and `toolSchemaValidity` pass after `npm run test:prepare`. - `npm run verify:architecture-guards`: passed. - `npm run knip`: reported only `lint-staged` (`package.json` and `.husky/pre-commit`), which this change does not touch. The Linux CI lanes and the real browser/provider lanes were not run locally. ## Compatibility, safety, and release - **Breaking changes or migration steps:** none; all settings are opt-in. - **Real Hopper/Ghidra, browser, or OS coverage:** Windows 11 only; Linux CI not run locally. - **Package or release metadata impact:** none. - **Security, privacy, process, or containment review:** narrowing only; documented that selection limits which tools a model can request and does not sandbox the remaining ones. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 00:54:33 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4739
No description provided.