feat(macos): bring the MCP server to the 2026-07-28 spec - #369
Conversation
|
@coderabbitai review |
cff8f37 to
7aa6366
Compare
✅ Action performedReview finished.
|
📝 WalkthroughWalkthroughThe PR implements the MCP ChangesMCP protocol implementation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant MCPClient
participant MCPServer
participant MCPRequestContext
participant MCPResources
participant MCPTaskStore
MCPClient->>MCPServer: Send JSON-RPC request
MCPServer->>MCPRequestContext: Parse request metadata
MCPServer->>MCPResources: List or read resources
MCPServer->>MCPTaskStore: Start or poll asynchronous task
MCPServer-->>MCPClient: Return JSON-RPC result or error
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
macos/Tests/MCPConformanceTests.swift (1)
377-396: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe late-result assertion depends on a fixed 0.3 s delay.
After
gate.signal(), the worker finishes on another thread. The test asserts the status 0.3 s later. On a loaded runner the worker may finish after that point, so the test can pass without exercising the late-result path. A deterministic signal from the worker closure would make the check reliable.♻️ Signal from inside the work closure
+ let finished = expectation(description: "work returns after cancel") let record = store.start(label: "unit", progressToken: nil) { _ in gate.wait() + defer { finished.fulfill() } return .success(["content": []]) } XCTAssertTrue(store.cancel(record.taskId)) XCTAssertEqual(store.get(record.taskId)?.status, "cancelled") gate.signal() // let the work finish after the cancel - let stillCancelled = expectation(description: "stays cancelled") - DispatchQueue.global().asyncAfter(deadline: .now() + 0.3) { - XCTAssertEqual(store.get(record.taskId)?.status, "cancelled") - stillCancelled.fulfill() - } - wait(for: [stillCancelled], timeout: 5) + wait(for: [finished], timeout: 5) + XCTAssertEqual(store.get(record.taskId)?.status, "cancelled")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@macos/Tests/MCPConformanceTests.swift` around lines 377 - 396, Update testCancel_isTerminalAndSurvivesALateResult to add a completion signal owned by the worker closure, signal it immediately after the work completes, and wait for that signal after gate.signal() before asserting the task remains cancelled. Remove the fixed 0.3-second asyncAfter timing while preserving the terminal-status assertion and timeout protection.macos/scripts/mcp-conformance.py (2)
296-325: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo teardown runs if a check raises.
result()raisesAssertionErroron any error reply, andjson.loadsraises on a malformed payload. Neither path reaches lines 324-325, so the spawned servers stay running and the verdict block never prints. Wrap the body intry/finallyand close every client in thefinally, or register the clients withatexit.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@macos/scripts/mcp-conformance.py` around lines 296 - 325, Wrap the conformance test body that performs task polling, cancellation, and synchronous-tool checks in a try/finally block, and move both client teardowns (`tc.close()` and `c.close()`) into the finally clause. Ensure teardown runs when `tc.result()` or JSON parsing raises, while preserving the existing checks and verdict behavior.
61-68: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
readlinehas no timeout, so a hung server hangs the harness.
sendblocks onself.proc.stdout.readline()with no deadline. If the server stops responding, the script never returns and a CI job runs until the job-level timeout, with no output about which method hung. The task poll loop at lines 296-301 already has a 120 s deadline; the request path has none.Consider a watchdog around the read, for example
signal.alarmor a reader thread with a join timeout, and report the pending method name on expiry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@macos/scripts/mcp-conformance.py` around lines 61 - 68, Update the request path in send so self.proc.stdout.readline() cannot block indefinitely: add a per-request timeout using an appropriate watchdog mechanism, and raise a clear timeout error that includes the pending method name when the deadline expires. Preserve the existing server-closed handling and JSON response behavior for requests that complete.macos/Sources/MCPInputRequests.swift (1)
200-203: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueBind the descriptor once in
isListArgument.The function calls
missingArgument(tool:arguments:)twice for one decision. One binding is clearer and avoids a future divergence between the two calls.♻️ Proposed refactor
private static func isListArgument(tool: String, key: String) -> Bool { - Self.missingArgument(tool: tool, arguments: [:])?.isList == true - && Self.missingArgument(tool: tool, arguments: [:])?.key == key + guard let descriptor = Self.missingArgument(tool: tool, arguments: [:]) else { return false } + return descriptor.isList && descriptor.key == key }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@macos/Sources/MCPInputRequests.swift` around lines 200 - 203, Update isListArgument to bind the result of missingArgument(tool:arguments:) once, then evaluate both the isList and key conditions against that binding while preserving the current boolean behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/agent-tools.md`:
- Around line 159-162: Update the socketfilterfw command in the “What’s
listening?” documentation to use its absolute system path instead of relying on
PATH lookup; leave the other security-status commands unchanged.
In `@macos/scripts/mcp-conformance.py`:
- Around line 122-127: Make the conformance harness resilient to unexpected
server state: in macos/scripts/mcp-conformance.py lines 122-127, build a
name-to-tool map and use safe .get lookups so missing tools register failed
checks instead of raising; in lines 296-325, wrap the main script flow in
try/finally and close every Client in the finally block, ensuring the verdict
and FAIL list are printed while all spawned server processes are cleaned up.
- Line 12: Validate the required binary argument immediately after assigning BIN
in the script entry flow, and fail with a clear usage or missing-path message
when it is None before invoking subprocess.Popen. Preserve the existing
execution path for a provided binary.
In `@macos/Tests/MCPConformanceTests.swift`:
- Around line 168-175: Update testResourceRead_isPrivateAndShortLived to compare
the returned ttl against MCPProtocol.Cache.liveTTL instead of
MCPProtocol.Cache.digestTTL, preserving the existing private cache-scope
assertion.
---
Nitpick comments:
In `@macos/scripts/mcp-conformance.py`:
- Around line 296-325: Wrap the conformance test body that performs task
polling, cancellation, and synchronous-tool checks in a try/finally block, and
move both client teardowns (`tc.close()` and `c.close()`) into the finally
clause. Ensure teardown runs when `tc.result()` or JSON parsing raises, while
preserving the existing checks and verdict behavior.
- Around line 61-68: Update the request path in send so
self.proc.stdout.readline() cannot block indefinitely: add a per-request timeout
using an appropriate watchdog mechanism, and raise a clear timeout error that
includes the pending method name when the deadline expires. Preserve the
existing server-closed handling and JSON response behavior for requests that
complete.
In `@macos/Sources/MCPInputRequests.swift`:
- Around line 200-203: Update isListArgument to bind the result of
missingArgument(tool:arguments:) once, then evaluate both the isList and key
conditions against that binding while preserving the current boolean behavior.
In `@macos/Tests/MCPConformanceTests.swift`:
- Around line 377-396: Update testCancel_isTerminalAndSurvivesALateResult to add
a completion signal owned by the worker closure, signal it immediately after the
work completes, and wait for that signal after gate.signal() before asserting
the task remains cancelled. Remove the fixed 0.3-second asyncAfter timing while
preserving the terminal-status assertion and timeout protection.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 14e29179-ca94-49cf-9ff3-a8bb01852e0c
📒 Files selected for processing (12)
docs/agent-tools.mdmacos/Sources/MCP.swiftmacos/Sources/MCPInputRequests.swiftmacos/Sources/MCPProtocol.swiftmacos/Sources/MCPResources.swiftmacos/Sources/MCPServer.swiftmacos/Sources/MCPTasks.swiftmacos/Sources/MCPToolMetadata.swiftmacos/Tests/MCPConformanceTests.swiftmacos/Tests/MCPTests.swiftmacos/scripts/mcp-conformance.pyskills/burrow-system-tools/SKILL.md
| - **"What's listening?"** → `burrow_ports`. For "is anything insecure", `burrow_doctor` over MCP | ||
| does **not** cover SIP/Gatekeeper/FileVault/firewall — read those from the shell (`csrutil | ||
| status`, `spctl --status`, `fdesetup status`, `socketfilterfw --getglobalstate`) until the | ||
| tool fills them in. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
socketfilterfw needs its absolute path.
socketfilterfw is not on the default PATH. The command as written returns "command not found". Use the full path.
📝 Proposed fix
- status`, `spctl --status`, `fdesetup status`, `socketfilterfw --getglobalstate`) until the
- tool fills them in.
+ status`, `spctl --status`, `fdesetup status`,
+ `/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate`) until the tool fills
+ them in.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - **"What's listening?"** → `burrow_ports`. For "is anything insecure", `burrow_doctor` over MCP | |
| does **not** cover SIP/Gatekeeper/FileVault/firewall — read those from the shell (`csrutil | |
| status`, `spctl --status`, `fdesetup status`, `socketfilterfw --getglobalstate`) until the | |
| tool fills them in. | |
| - **"What's listening?"** → `burrow_ports`. For "is anything insecure", `burrow_doctor` over MCP | |
| does **not** cover SIP/Gatekeeper/FileVault/firewall — read those from the shell (`csrutil | |
| status`, `spctl --status`, `fdesetup status`, | |
| `/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate`) until the tool fills | |
| them in. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/agent-tools.md` around lines 159 - 162, Update the socketfilterfw
command in the “What’s listening?” documentation to use its absolute system path
instead of relying on PATH lookup; leave the other security-status commands
unchanged.
✅ Confirmed as addressed by @caezium
There was a problem hiding this comment.
Fixed in ba9a106 — confirmed socketfilterfw is not on the default PATH (which socketfilterfw finds nothing; it lives at /usr/libexec/ApplicationFirewall/socketfilterfw), so the documented command failed for anyone who pasted it. Now absolute. The other three status commands are all in /usr/bin or /usr/sbin and resolve fine, so they are unchanged.
There was a problem hiding this comment.
@caezium, confirmed. The absolute path fixes the documented firewall-status command. The other commands remain unchanged.
You are interacting with an AI system.
There was a problem hiding this comment.
There was a problem hiding this comment.
@caezium, acknowledged. The finding remains open until the re-applied change restores the absolute socketfilterfw path in docs/agent-tools.md.
You are interacting with an AI system.
| import sys | ||
| import time | ||
|
|
||
| BIN = sys.argv[1] if len(sys.argv) > 1 else None |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fail with a clear message when the binary path is missing.
If the script runs with no argument, BIN stays None. subprocess.Popen([None, "--mcp"]) then raises a TypeError that does not explain the real cause.
🐛 Proposed fix
-BIN = sys.argv[1] if len(sys.argv) > 1 else None
+if len(sys.argv) < 2:
+ sys.exit("usage: mcp-conformance.py <path-to-Burrow-binary>")
+BIN = sys.argv[1]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| BIN = sys.argv[1] if len(sys.argv) > 1 else None | |
| if len(sys.argv) < 2: | |
| sys.exit("usage: mcp-conformance.py <path-to-Burrow-binary>") | |
| BIN = sys.argv[1] |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@macos/scripts/mcp-conformance.py` at line 12, Validate the required binary
argument immediately after assigning BIN in the script entry flow, and fail with
a clear usage or missing-path message when it is None before invoking
subprocess.Popen. Preserve the existing execution path for a provided binary.
✅ Confirmed as addressed by @caezium
There was a problem hiding this comment.
Fixed in ba9a106. It now exits with usage text naming the script and the missing argument; before, None reached subprocess.Popen and raised a TypeError that identified neither. Verified by running it with no arguments.
There was a problem hiding this comment.
@caezium, acknowledged. The missing binary argument now produces a clear usage message instead of passing None to subprocess.Popen.
You are interacting with an AI system.
There was a problem hiding this comment.
There was a problem hiding this comment.
@caezium, acknowledged. The missing binary-argument validation is not present on the current branch, so this finding remains open.
Reapply the usage check after the pending commits land. Then rerun the harness with no arguments to verify the clear failure message.
You are interacting with an AI system.
| clean = next(t for t in tools if t["name"] == "burrow_clean") | ||
| check("burrow_clean destructiveHint", clean["annotations"].get("destructiveHint") is True) | ||
| report = next(t for t in tools if t["name"] == "burrow_report") | ||
| check("burrow_report has no outputSchema (markdown)", "outputSchema" not in report) | ||
| schema_count = sum(1 for t in tools if "outputSchema" in t) | ||
| check("27 tools declare outputSchema", schema_count == 27, schema_count) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Any unexpected server state kills the harness before it prints a verdict. The script has no defensive lookups and no top-level error handling, so a missing tool or an error reply produces a traceback, skips the FAIL list at lines 328-332, and leaves spawned server processes running.
macos/scripts/mcp-conformance.py#L122-L127: build a{name: tool}map and use.get(...)so an absent tool records a failure instead of raisingStopIteration.macos/scripts/mcp-conformance.py#L296-L325: wrap the script body intry/finallyand close everyClientin thefinally, so the verdict prints and no server process is left behind.
📍 Affects 1 file
macos/scripts/mcp-conformance.py#L122-L127(this comment)macos/scripts/mcp-conformance.py#L296-L325
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@macos/scripts/mcp-conformance.py` around lines 122 - 127, Make the
conformance harness resilient to unexpected server state: in
macos/scripts/mcp-conformance.py lines 122-127, build a name-to-tool map and use
safe .get lookups so missing tools register failed checks instead of raising; in
lines 296-325, wrap the main script flow in try/finally and close every Client
in the finally block, ensuring the verdict and FAIL list are printed while all
spawned server processes are cleaned up.
✅ Confirmed as addressed by @caezium
There was a problem hiding this comment.
Fixed in ba9a106, both halves.
Tool lookups go through a by_name dict with .get, so a server that omits a tool now FAILS that check and lets the run reach its verdict — previously next(...) raised StopIteration, meaning the one condition most worth reporting killed the harness instead of being reported.
The tasks section is wrapped in try/finally with both tc.close() and c.close() in the finally, so an unexpected payload can no longer leave Burrow processes running and swallow the verdict at the same time. tc is initialised to None before the try, so a failure inside Client() itself cannot turn the cleanup into a NameError.
There was a problem hiding this comment.
@caezium, confirmed. The changes address both parts of the finding.
The harness now records missing tools as failures. The try/finally cleanup also closes created clients when an unexpected response interrupts the task checks.
You are interacting with an AI system.
There was a problem hiding this comment.
There was a problem hiding this comment.
@caezium, acknowledged. The finding remains open because 10eafe8 reverted the fix from ba9a106.
Please re-apply the fix after the pending commits land. I will verify the updated branch then.
You are interacting with an AI system.
| func testResourceRead_isPrivateAndShortLived() throws { | ||
| let r = try result(call("resources/read", ["uri": "burrow://info"])) | ||
| XCTAssertEqual(r["cacheScope"] as? String, "private", | ||
| "anything describing this Mac must not be shared across contexts") | ||
| let ttl = try XCTUnwrap(r["ttlMs"] as? Int) | ||
| XCTAssertLessThanOrEqual(ttl, MCPProtocol.Cache.digestTTL, | ||
| "live data must not be cacheable for long") | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect the Cache constants and the TTL assigned to resources/read.
fd -t f 'MCPProtocol.swift' --exec ast-grep outline {} --items all
rg -n -C4 'digestTTL|Cache\.' --glob '*.swift'Repository: caezium/Burrow
Length of output: 417
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- MCPProtocol.swift cache-related declarations ---'
rg -n -C8 'enum Cache|struct Cache|digestTTL|ttlMs|cacheScope|resources/read' macos/Sources/MCPProtocol.swift macos/Tests/MCPConformanceTests.swift
printf '%s\n' '--- MCPProtocol.swift relevant sections ---'
sed -n '1,140p' macos/Sources/MCPProtocol.swift
printf '%s\n' '--- conformance test context ---'
sed -n '145,185p' macos/Tests/MCPConformanceTests.swiftRepository: caezium/Burrow
Length of output: 20071
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- all TTL and resources/read assignments ---'
rg -n -C6 'ttlMs|resources/read|resourceListTTL|liveTTL|digestTTL' macos --glob '*.swift'
printf '%s\n' '--- resource handlers and cacheable calls ---'
rg -n -C10 'cacheable\(|case "resources/read"|resourcesRead|burrow://info|burrow://history|burrow://processes' macos --glob '*.swift'Repository: caezium/Burrow
Length of output: 50370
Use MCPProtocol.Cache.liveTTL as the upper bound. burrow://info uses liveTTL; digestTTL would allow a 60-second TTL and would not detect a regression from 5 seconds to 60 seconds.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@macos/Tests/MCPConformanceTests.swift` around lines 168 - 175, Update
testResourceRead_isPrivateAndShortLived to compare the returned ttl against
MCPProtocol.Cache.liveTTL instead of MCPProtocol.Cache.digestTTL, preserving the
existing private cache-scope assertion.
✅ Confirmed as addressed by @caezium
There was a problem hiding this comment.
Fixed in ba9a106. burrow://info declares liveTTL (5s) in MCPResources.swift, but the assertion compared against digestTTL (60s) — a bound twelve times looser than the value under test, which would have held even if the resource regressed to caching live machine state for a full minute. Now pinned to liveTTL; the private cache-scope assertion is unchanged.
There was a problem hiding this comment.
@caezium, confirmed. The test now enforces the liveTTL limit for burrow://info and retains the private cache-scope check.
You are interacting with an AI system.
There was a problem hiding this comment.
There was a problem hiding this comment.
@caezium, acknowledged. The finding remains open until the re-applied test compares the burrow://info TTL with MCPProtocol.Cache.liveTTL. Recheck after the pending commits land.
You are interacting with an AI system.
10eafe8 to
919d02e
Compare
The server advertised protocolVersion 2024-11-05 and implemented four methods. This moves it to the stateless revision without dropping the clients that still expect the old handshake. Protocol core: server/discover, per-request _meta (protocol version, client capabilities, client info, log level), resultType on every result, serverInfo in every result's _meta, and the renumbered error codes (-32022 unsupported version, resource-not-found -32002 -> -32602). initialize still negotiates for pre-2026 clients, and ping / logging/setLevel are still answered rather than 404'd at them. Result quality: ttlMs + cacheScope on every cacheable list, deterministic tool ordering, annotations (the read-only/destructive split is now machine-readable instead of prose in a description), and outputSchema + structuredContent on all but burrow_report, which is Markdown and so declares neither. Tasks: the io.modelcontextprotocol/tasks extension for the nine long-running tools, opt-in on both sides. A multi-minute clean or analyze returns a handle and reports progress instead of blocking and then coming back as timed_out: true, which reads to a model like "nothing to clean". MRTR: a missing required argument becomes an input_required elicitation when the client can ask a human, rather than an argument error. It is deliberately not used as a confirmation gate — an agent answers its own elicitation, so MoActions.decide and the Settings opt-ins remain the only authority, pinned by tests. New surface: resources (10) + templates (3) over the read tools, five prompts encoding the tool orderings that avoid wrong answers, argument completion, and two tools that close real gaps — burrow_agent_audit (the audit trail was write-only) and burrow_anomalies (the detector had no agent surface). Not implemented, on purpose: Roots, Sampling and Logging (deprecated in this same revision), legacy HTTP+SSE, and Streamable HTTP — the last is gated on a security design, since opening a listener on a process that drives a privileged helper is not a transport swap. Verified with 36 new unit tests (1042 total, 0 failures) and a 93-check end-to-end harness driving the signed binary over stdio.
The unit tests cover the envelope; this drives the real signed binary over stdio the way a client does. Carries a hard rule in the safety section: it runs against the user's live defaults, so it must never pass confirm:true — the gate's refusal path is unit-tested against a scratch defaults suite instead.
The tool reference and the agent skill both claimed burrow_doctor returns SIP/Gatekeeper/FileVault/firewall, battery health and CPU load, and told agents to start there for security questions. It doesn't: callDoctor builds Doctor.Input without those facets, so Doctor.report omits the checks and the agent gets a result with the question silently missing. The GUI fills them in, which is why the docs read true. Says what the tool does return, and points security questions at the shell until the gap is closed (BUR-105).
The shipped skill was missing seven read-only tools that already existed — dupes, photos, orphans, sentinel, slim_check, net, rules_dryrun — so its low-on-disk pattern went straight from analyze to clean and skipped everything that actually finds the big wins. Adds those, the two new tools (agent_audit, anomalies), and the resource / prompt / task surface from the 2026-07-28 work. Corrects the doctor entry: it claimed SIP/Gatekeeper/FileVault/firewall and battery, none of which the MCP path fills in (BUR-105).
919d02e to
ebf81f8
Compare
Stacked on #367 — base is
bur-011-safe-release, so this diff is only the MCP work. Retarget tomainonce #367 merges.Closes the stdio half of BUR-103. The server advertised
protocolVersion: 2024-11-05and implemented four methods; this moves it to the stateless revision without dropping clients that still expect the old handshake.Protocol core
server/discover, per-request_meta(protocol version, client capabilities, client info, log level),resultTypeon every result,serverInfoin every result's_meta, and the renumbered error codes —-32022for an unsupported version (carrying the supported list so a client knows what to retry with), and resource-not-found moved-32002→-32602.initializestill negotiates down for pre-2026 clients, andping/logging/setLevelare still answered rather than 404'd at a client that predates their removal.Result quality
ttlMs+cacheScopeon every cacheable list, deterministic tool ordering, annotations (the read-only/destructive split is now machine-readable instead of prose inside a description string), andoutputSchema+structuredContenton 27 of 28 tools.burrow_reportdeclares neither on purpose — it returns Markdown, and a schema would be a promise the payload can't keep.Tasks
The
io.modelcontextprotocol/tasksextension for the nine long-running tools, opt-in on both sides. A multi-minute clean or analyze returns a handle and reports progress instead of blocking and then coming back astimed_out: true— which reads to a model like "nothing to clean" rather than "we gave up". The task queue is serial: two concurrent disk scans would only thrash.MRTR
A missing required argument becomes an
input_requiredelicitation when the client can ask a human, instead of an argument error. It is deliberately not used as a confirmation gate — an agent answers its own elicitation, soMoActions.decideand the Settings opt-ins remain the only authority.testMRTRAnswer_cannotUnlockAnUninstallanswers the elicitation and passesconfirm:true, and assertsran: false.New surface
10 resources + 3 templates over the existing read tools, five prompts encoding the tool orderings that avoid wrong answers, argument completion, and two tools that close real gaps:
burrow_agent_audit(the audit trail was write-only — the MCP process appended to it, the GUI could show it, no agent could read it back) andburrow_anomalies(the detector had no agent surface at all).Deliberately not implemented
subscriptions/listen— it replaces the HTTP GET endpoint, so it lands with the transport or not at all.Verification
MCPConformanceTests).macos/scripts/mcp-conformance.py— 93 checks driving the real signed binary over stdio: stateless flow, legacy flow, version rejection, cache hints, MRTR round trip and decline, full task lifecycle including cancel, resources/prompts/completion.That harness runs against the live defaults domain, so it carries a hard rule: it must never pass
confirm:true. The gate's refusal path is unit-tested against a scratch defaults suite instead.Not yet hand-tested through a real MCP client — pointing Claude Code at the new binary and watching
server/discover, a task poll, and an elicitation happen for real is still worth doing.Summary by CodeRabbit
New Features
Documentation
Tests