transit branch T-1835/bugfix-mcp-get-endpoint-returns-404-instead-of-405 commits 5 files 6 touched lines +326 / -21

Pre-push review: T-1835/bugfix-mcp-get-endpoint-returns-404-instead-of-405

PR #207 at the exact requested head 1a6daf3dc8534a3f8c4b500eece7850403929aae, reviewed against base 06a8dda823ed73b07e3227a5ec18107d6f366142.

At a glance

  • Identity: local HEAD, tracking branch, and PR #207 head are all 1a6daf3dc8534a3f8c4b500eece7850403929aae; base is 06a8dda823ed73b07e3227a5ec18107d6f366142.
  • Validation: make lint passed; make test-quick passed; focused MCPServerRouteTests passed 7/7 with 0 failures.
  • Remote gates: exact-head claude-review CI succeeded at 06:34:52Z; latest clean qualifying review was posted at 06:34:45Z; GraphQL review-thread count is 0.
  • Scope: PR #206 is in the requested base and changes only date-filter files, changelog, and its bugfix report; no semantic overlap with this MCP route patch.

Verdict

Ready to push

No candidate blocker found. The worktree is clean and exactly matches the requested PR head; the base is the requested ancestor. Local lint, the full macOS quick suite, and the focused route suite passed. Exact-head CI is successful, the latest qualifying review is current, review threads are zero, and the normalized local/remote patches are identical.

Review findings

2 raised · 0 fixed · 2 skipped

Jump to findings →

Commits

Three-level explanation

What changed

The macOS MCP server now recognizes GET /mcp explicitly. Because Transit does not provide an SSE listening stream, it returns HTTP 405 with Allow: POST instead of Hummingbird's generic 404. The existing POST request path remains operational.

Why it matters

MCP clients can distinguish an unsupported method on a real endpoint from an unknown URL. The Origin and Host checks still reject untrusted browser-origin or DNS-rebinding requests.

Architecture

The router adds one exact-path GET handler and extracts the shared Origin/authority validation into isAllowedMCPRequest. Both GET and POST call it before GET response construction or POST body collection. The rest of POST decoding and MainActor dispatch is unchanged.

Trade-offs

The change is intentionally route-local rather than global middleware, so unrelated routes retain their 404 behavior. HEAD and OPTIONS remain framework 404s and are covered as invariants because the ticket is specifically about GET/SSE semantics.

Deep dive

The route-level tests exercise a real Hummingbird Router<BasicRequestContext> responder through NIO's EmbeddedChannel. They verify 405 plus Allow: POST, normal POST dispatch, both GET rejection paths, and unchanged HEAD/OPTIONS/unrelated-route fallbacks. The extracted validator preserves the prior argument order and rejection semantics, while keeping the DNS-rebinding check before request-body access on POST.

Patch integrity

The local base-to-head patch and GitHub PR patch have identical normalized content; only abbreviated versus full Git index hash metadata differs. PR #206's merged date-filter changes are already represented in the requested base and are outside this patch's six-file scope.

Important changes — detailed

MCPServer: add explicit GET /mcp method response

Transit/Transit/MCP/MCPServer.swift

Why it matters. Changes the protocol-visible behavior from an unmatched-route 404 to 405 with Allow: POST for the advertised MCP endpoint.

What to look at. MCPServer.swift:196-217

Takeaway. Register endpoint-specific method behavior at the router boundary when framework fallback status codes do not express protocol semantics.
Rationale. The MCP Streamable HTTP contract requires an explicit method response when no SSE listening stream is offered.

MCPServer: centralize Origin/Host validation

Transit/Transit/MCP/MCPServer.swift

Why it matters. Preserves the DNS-rebinding defense on the new route and avoids divergent copies of security-critical validation.

What to look at. MCPServer.swift:358-374

Takeaway. Keep transport security checks in one helper and invoke them before body access or dispatch in every route.
Rationale. The same validation must gate both GET probes and POST JSON-RPC requests.

Route tests: pin all seven method/path invariants

Transit/TransitTests/MCPServerRouteTests.swift

Why it matters. Provides direct regression coverage for the requested positive, security, compatibility, and fallback behaviors.

What to look at. MCPServerRouteTests.swift:16-103

Takeaway. Test routing behavior through the real router/responder rather than only testing handler logic in isolation.
Rationale. The defect was Hummingbird route matching, so integration at the router level is the relevant test boundary.

Docs and bugfix report: synchronize transport contract

specs/bugfixes/mcp-get-endpoint-returns-404-instead-of-405/report.md

Why it matters. Records the root cause, alternatives, test names, affected files, and verification evidence for future maintenance.

What to look at. report.md:1-156

Takeaway. Keep protocol behavior, durable implementation notes, changelog, and regression evidence synchronized with a transport fix.
Rationale. The report is the durable explanation for why GET is handled separately while POST remains the data path.

Key decisions

Use a route-local GET handler instead of global method middleware.

This limits the behavior change to /mcp. The unrelated-path 404 control remains unchanged, and no other endpoint inherits an Allow header or method policy.

Validate Origin/Host before returning the GET response.

This preserves the existing browser-origin and DNS-rebinding policy even for a method probe. A disallowed request receives the existing generic 403 rather than revealing the endpoint's supported methods.

Leave HEAD and OPTIONS as 404 fallback behavior.

The ticket is scoped to GET/SSE. The focused suite explicitly documents current Hummingbird behavior so a future route change cannot silently synthesize these methods.

Review findings

SeverityAreaFindingResolution
minorTest harness duplicationThe new route test has dispatch plumbing similar to existing MCP route tests.Skipped as non-blocking; it follows the existing per-file convention and does not affect correctness of this candidate.
minorOther unsupported methodsPUT, DELETE, and PATCH on /mcp remain framework 404s rather than 405s.Skipped as out of scope for T-1835, which specifically fixes GET/SSE endpoint semantics; HEAD and OPTIONS invariants are explicitly tested.

Per-file diffs

Click to expand.

CHANGELOG.md Modified +1 / -0
diff --git a/CHANGELOG.md b/CHANGELOG.mdindex bd359a9..50c4c81 100644--- a/CHANGELOG.md+++ b/CHANGELOG.md@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).  ### Fixed - T-1799: QueryTasksIntent absolute date filters now require exact ASCII `YYYY-MM-DD` values representing valid dates in the user's current calendar. Local `Calendar.current` parsing, non-lenient formatter round-trip validation, and regressions reject normalized invalid days/months, non-padded values, and non-ASCII input while preserving inclusive ranges and relative filters.+- MCP Streamable HTTP now returns `405 Method Not Allowed` with `Allow: POST` for `GET /mcp` when no SSE listening stream is offered, while preserving POST handling, origin validation, and unrelated-route 404 behavior (T-1835). - MCP `create_task`, `create_milestone`, `CreateTaskIntent`, and `CreateMilestoneIntent` now distinguish missing required fields from present non-string `name`/`type` values (T-1598). Numeric, boolean, array, object, and null inputs return field-specific `name must be a string` / `type must be a string` validation errors before mutation, while missing-field and invalid-string-type behavior remains unchanged. Symmetric regression coverage verifies all four create paths and confirms malformed requests create no records. - `QueryTasksIntent` now rejects explicit JSON `null` in nested `completionDate` and `lastStatusChangeDate` fields (`relative`, `from`, and `to`) before Codable decoding can erase presence. Malformed nested date-filter shapes retain the established `INVALID_INPUT` contract, omitted fields remain unchanged, and regressions cover both filters and no-broadening behavior (T-1644). - `Color.hexString` now rounds resolved RGB components to the nearest byte after finite-checking and clamping instead of truncating values just below byte boundaries (T-1807). Project colors no longer darken when an editor saves an unchanged color. Regression coverage exercises every `0...255` RGB value, deterministic sRGB resolved components immediately below each boundary, and the existing uppercase six-digit RGB/no-alpha format.
Transit/Transit/MCP/MCPServer.swift Modified +21 / -19
diff --git a/Transit/Transit/MCP/MCPServer.swift b/Transit/Transit/MCP/MCPServer.swiftindex 8f2784c..6cb4b68 100644--- a/Transit/Transit/MCP/MCPServer.swift+++ b/Transit/Transit/MCP/MCPServer.swift@@ -193,22 +193,19 @@ extension MCPServer {  extension MCPServer { -    /// Builds the Hummingbird router for the single `POST /mcp` endpoint.+    /// Builds the Hummingbird router for the MCP endpoint.+    /// POST carries JSON-RPC; GET is explicitly 405 because no SSE stream is offered.     /// `nonisolated` keeps transport construction independent of MainActor;-    /// route callbacks execute on Hummingbird/NIO and hop to MainActor only-    /// when dispatching through `MCPToolHandler`.+    /// route callbacks execute on Hummingbird/NIO and hop to MainActor when dispatching.     nonisolated static func makeRouter(handler: MCPToolHandler) -> Router<BasicRequestContext> {         let router = Router()+        router.get("mcp") { request, _ -> Response in+            guard Self.isAllowedMCPRequest(request) else { return forbiddenResponse() }+            return Response(status: .methodNotAllowed, headers: [.allow: "POST"])+        }         router.post("mcp") { request, _ -> Response in-            // DNS-rebinding defence required by the MCP Streamable HTTP-            // transport. This must stay the first statement in the route: an-            // untrusted caller's body is never read, decoded, or dispatched.-            if MCPOriginValidator.rejectionReason(-                origin: request.head.headerFields[.origin],-                authority: request.head.authority-            ) != nil {-                return forbiddenResponse()-            }+            // Validate origin before reading the body.+            guard Self.isAllowedMCPRequest(request) else { return forbiddenResponse() }              let body = try await request.body.collect(upTo: 1_048_576)             let data = Data(buffer: body)@@ -359,13 +356,18 @@ extension MCPServer {         )     } -    /// Transport-level rejection. Deliberately not a JSON-RPC error body: the-    /// request never entered a JSON-RPC session, and answering `200` with an-    /// error object would tell an attacker's page that the endpoint is live.-    ///-    /// The body is a fixed string rather than the validator's specific reason,-    /// so a probing script cannot learn whether it was the `Origin` or the-    /// `Host` check that tripped.+    /// Validates Origin and Host/authority before body access or dispatch.+    /// This MCP DNS-rebinding defence must remain first in every MCP route.+    nonisolated private static func isAllowedMCPRequest(_ request: Request) -> Bool {+        let origin = request.head.headerFields[.origin]+        return MCPOriginValidator.rejectionReason(+            origin: origin,+            authority: request.head.authority+        ) == nil+    }++    /// Transport-level rejection, not JSON-RPC: fixed plain text avoids+    /// revealing whether Origin or Host validation failed to a prober.     nonisolated private static func forbiddenResponse() -> Response {         Response(             status: .forbidden,
Transit/TransitTests/MCPServerRouteTests.swift Added +146 / -0
diff --git a/Transit/TransitTests/MCPServerRouteTests.swift b/Transit/TransitTests/MCPServerRouteTests.swiftnew file mode 100644index 0000000..ad5144d--- /dev/null+++ b/Transit/TransitTests/MCPServerRouteTests.swift@@ -0,0 +1,146 @@+#if os(macOS)+import Foundation+import HTTPTypes+import Hummingbird+import Logging+import NIOCore+import NIOEmbedded+import Testing+@testable import Transit++/// Regression tests for T-1835: the advertised MCP Streamable HTTP endpoint+/// must distinguish an unsupported method on `/mcp` from an unknown route.+///+/// MCP 2025-03-26 requires GET on the MCP endpoint to return 405 when the+/// server does not offer an SSE listening stream. Hummingbird's unmatched+/// route fallback previously returned 404 because only POST was registered.+@MainActor @Suite(.serialized)+struct MCPServerRouteTests {++    @Test func getMcpReturnsMethodNotAllowedWithPostAllowHeader() async throws {+        let env = try MCPTestHelpers.makeEnv()+        let response = try await respond(+            handler: env.handler,+            method: .get,+            path: "/mcp"+        )++        #expect(response.status == .methodNotAllowed)+        #expect(response.allow == "POST")+    }++    @Test func postMcpStillDispatchesNormally() async throws {+        let env = try MCPTestHelpers.makeEnv()+        let response = try await respond(+            handler: env.handler,+            method: .post,+            path: "/mcp",+            body: #"{"jsonrpc":"2.0","id":1,"method":"ping"}"#+        )++        #expect(response.status == .ok)+    }++    @Test func getMcpStillValidatesUntrustedOrigins() async throws {+        let env = try MCPTestHelpers.makeEnv()+        let response = try await respond(+            handler: env.handler,+            method: .get,+            path: "/mcp",+            origin: "https://evil.example.com"+        )++        #expect(response.status == .forbidden)+    }++    @Test func getMcpStillValidatesNonLoopbackHost() async throws {+        let env = try MCPTestHelpers.makeEnv()+        let response = try await respond(+            handler: env.handler,+            method: .get,+            path: "/mcp",+            host: "rebind.example.com:3141"+        )++        #expect(response.status == .forbidden)+    }++    @Test func headMcpRemainsNotFoundWithoutAutoGeneratedHeadRoute() async throws {+        let env = try MCPTestHelpers.makeEnv()+        let response = try await respond(+            handler: env.handler,+            method: .head,+            path: "/mcp"+        )++        #expect(response.status == .notFound)+        #expect(response.allow == nil)+    }++    @Test func optionsMcpRemainsNotFoundWithoutExplicitOptionsRoute() async throws {+        let env = try MCPTestHelpers.makeEnv()+        let response = try await respond(+            handler: env.handler,+            method: .options,+            path: "/mcp"+        )++        #expect(response.status == .notFound)+        #expect(response.allow == nil)+    }++    @Test func getOnUnrelatedPathRemainsNotFound() async throws {+        let env = try MCPTestHelpers.makeEnv()+        let response = try await respond(+            handler: env.handler,+            method: .get,+            path: "/not-mcp"+        )++        #expect(response.status == .notFound)+        #expect(response.allow == nil)+    }++    private struct RouteResponse {+        let status: HTTPResponse.Status+        let allow: String?+    }++    private func respond(+        handler: MCPToolHandler,+        method: HTTPRequest.Method,+        path: String,+        origin: String? = nil,+        host: String = "127.0.0.1:3141",+        body: String = ""+    ) async throws -> RouteResponse {+        let responder = MCPServer.makeRouter(handler: handler).buildResponder()+        var headers = HTTPFields()+        if let origin {+            headers[.origin] = origin+        }+        let request = Request(+            head: HTTPRequest(+                method: method,+                scheme: "http",+                authority: host,+                path: path,+                headerFields: headers+            ),+            body: RequestBody(buffer: ByteBuffer(string: body))+        )++        let channel = EmbeddedChannel()+        defer { _ = try? channel.finish() }+        let context = BasicRequestContext(+            source: ApplicationRequestContextSource(+                channel: channel, logger: Logger(label: "mcp-route-tests")+            )+        )++        let response = try await responder.respond(to: request, context: context)+        return RouteResponse(status: response.status, allow: response.headers[.allow])+    }+}++#endif
docs/agent-notes/mcp-server.md Modified +1 / -1
diff --git a/docs/agent-notes/mcp-server.md b/docs/agent-notes/mcp-server.mdindex 281ae07..cf7ed7c 100644--- a/docs/agent-notes/mcp-server.md+++ b/docs/agent-notes/mcp-server.md@@ -10,7 +10,7 @@ Embedded MCP server in the Transit macOS app using Hummingbird HTTP server. Expo Claude Code ←→ HTTP POST /mcp (localhost:3141) ←→ MCPServer ←→ MCPToolHandler ←→ TaskService/ProjectService/CommentService ←→ SwiftData ``` -- **Transport**: Streamable HTTP, single `POST /mcp` endpoint, JSON-RPC 2.0+- **Transport**: Streamable HTTP, JSON-RPC 2.0 over `POST /mcp`; `GET /mcp` is explicitly handled with HTTP 405 and `Allow: POST` because Transit does not offer an SSE listening stream - **HTTP server**: Hummingbird 2.x (SwiftNIO-based), binds to `127.0.0.1` only - **Lifecycle**: Opt-in via Settings toggle. Default port 3141. 
specs/bugfixes/mcp-get-endpoint-returns-404-instead-of-405/report.md Added +156 / -0
diff --git a/specs/bugfixes/mcp-get-endpoint-returns-404-instead-of-405/report.md b/specs/bugfixes/mcp-get-endpoint-returns-404-instead-of-405/report.mdnew file mode 100644index 0000000..84c7e7f--- /dev/null+++ b/specs/bugfixes/mcp-get-endpoint-returns-404-instead-of-405/report.md@@ -0,0 +1,156 @@+# Bugfix Report: MCP GET Endpoint Returns 404 Instead of 405++**Date:** 2026-08-02+**Status:** Fixed+**Transit ticket:** T-1835++## Description of the Issue++Transit advertises the MCP Streamable HTTP transport version `2025-03-26`, but+its Hummingbird router registers only `POST /mcp`. A client probing the MCP+endpoint with `GET /mcp` therefore reaches Hummingbird's unmatched-route+fallback and receives `404 Not Found`.++For Streamable HTTP, GET is the endpoint's optional server-to-client SSE+operation. When Transit does not offer an SSE listening stream, the MCP+endpoint must respond with `405 Method Not Allowed`, ideally including+`Allow: POST`, so a client can distinguish an unsupported endpoint method from+an unknown path.++**Reproduction steps:**+1. Enable the MCP server in Transit Settings.+2. Send `GET http://127.0.0.1:3141/mcp`.+3. Observe `404 Not Found` instead of `405 Method Not Allowed`.++**Impact:** MCP clients probing the advertised endpoint cannot distinguish an+unsupported SSE listening stream from a missing MCP endpoint. POST-based MCP+operation is unaffected.++## Investigation Summary++The investigation followed the systematic debugging workflow:++- **Symptoms examined:** The ticket reports `GET /mcp` returning 404. A+  route-level regression test reproduces that response on the real Hummingbird+  router.+- **Code inspected:** `Transit/Transit/MCP/MCPServer.swift` registers the+  single `POST /mcp` route; the POST closure performs origin validation,+  request decoding, and JSON-RPC dispatch. Existing lifecycle, origin, and+  batch tests establish the route test harness and unchanged behavior.+- **Hypotheses tested:** This is not a lifecycle or binding problem: the+  router can be exercised without starting a listener. It is not a JSON-RPC+  decoding problem: GET has no route match and never reaches the POST body+  handling. The supplied ticket identifies Hummingbird's+  `NotFoundResponder` as the source of the 404.++## Discovered Root Cause++`MCPServer.makeRouter(handler:)` registers `POST /mcp` but does not register a+GET handler for the same path. Hummingbird consequently treats the valid MCP+endpoint path as an unknown route for GET and returns its default 404 response.++**Defect type:** Missing route / protocol contract mismatch.++**Why it occurred:** The initial Streamable HTTP implementation modeled+Transit as POST-only and omitted the explicit method response required when no+SSE listening stream is offered.++**Contributing factors:** Hummingbird's unmatched-route fallback does not+encode the MCP endpoint's method semantics, so the distinction must be made by+the application router.++## Resolution for the Issue++**Changes made:**+- `Transit/Transit/MCP/MCPServer.swift:196-217` - registers an explicit+  `GET /mcp` route. It repeats the existing origin/authority validation and+  returns HTTP 405 with `Allow: POST` when the request is from an allowed+  local client. The existing POST route and its body/JSON-RPC dispatch path+  are unchanged.+- `Transit/TransitTests/MCPServerRouteTests.swift` - adds route-level tests+  for the 405 status, `Allow: POST` header, unchanged POST success, GET origin+  rejection, and unrelated-path 404 behavior.+- `docs/agent-notes/mcp-server.md` and+  `specs/mcp-server/implementation.md` - clarify that GET is explicitly+  handled as an unsupported SSE operation while POST remains the data path.++**Approach rationale:** The fix is deliberately at the Hummingbird router+boundary. Registering GET on the exact `/mcp` path lets Hummingbird distinguish+an unsupported method from an unknown path, while the `Allow` header tells MCP+clients that POST is the supported operation. Repeating origin validation+before returning 405 preserves the transport security policy for every MCP+request. No lifecycle or POST handler code needs to change.++**Alternatives considered:**+- **Leave GET unmatched and rely on Hummingbird's 404** - rejected because it+  violates the advertised Streamable HTTP `2025-03-26` endpoint contract and+  prevents clients from distinguishing method support from route absence.+- **Add a global method-not-allowed middleware** - rejected because the+  behavior is specific to the MCP endpoint and a route-local handler is the+  smallest change with no unrelated-path effects.+- **Return 405 without origin validation** - rejected because MCP origin+  validation applies to incoming transport requests; the new route must not+  create a browser-origin bypass.+++## Regression Test++**Test file:** `Transit/TransitTests/MCPServerRouteTests.swift`++**Test names:**+- `getMcpReturnsMethodNotAllowedWithPostAllowHeader`+- `postMcpStillDispatchesNormally`+- `getMcpStillValidatesUntrustedOrigins`+- `getMcpStillValidatesNonLoopbackHost`+- `headMcpRemainsNotFoundWithoutAutoGeneratedHeadRoute`+- `optionsMcpRemainsNotFoundWithoutExplicitOptionsRoute`+- `getOnUnrelatedPathRemainsNotFound`++**What it verifies:** The real Hummingbird router returns 405 and `Allow: POST`+for GET on `/mcp`, keeps POST dispatch working, retains origin and host rejection+on the new route, confirms unsupported HEAD/OPTIONS use the framework's 404+fallback, and continues returning 404 for an unrelated path.++**Run command:** `make test-quick`++## Affected Files++| File | Change |+|------|--------|+| `Transit/Transit/MCP/MCPServer.swift` | Explicit GET `/mcp` method response with origin validation. |+| `Transit/TransitTests/MCPServerRouteTests.swift` | New route-level regression coverage. |+| `docs/agent-notes/mcp-server.md` | MCP transport behavior and endpoint documentation. |+| `specs/mcp-server/implementation.md` | MCP implementation architecture documentation. |+| `specs/bugfixes/mcp-get-endpoint-returns-404-instead-of-405/report.md` | Investigation and resolution record. |+| `CHANGELOG.md` | Unreleased fixed-entry for T-1835. |++## Verification++**Automated:**+- [x] Regression tests fail before the fix, reproducing the incorrect 404 and missing endpoint method response.+- [x] `make test-quick` passes after the fix, including all existing macOS unit tests and the seven T-1835 route tests.+- [x] `make lint` passes in strict mode.+- [x] `make build-macos` passes for the affected macOS target.++**Manual verification:**+- [x] Route-level request dispatch confirms `GET /mcp` returns 405 with+  `Allow: POST`.+- [x] Route-level request dispatch confirms a normal `POST /mcp` ping still+  returns HTTP 200.+- [x] Route-level request dispatch confirms `GET /not-mcp` remains 404 without+  an `Allow` header.+++## Prevention++- Register explicit handlers for every HTTP method required by an advertised+  transport contract; do not rely on an unmatched-route fallback for method+  semantics.+- Keep route-level tests for both supported and intentionally unsupported+  methods, including the `Allow` header and an unrelated-path 404 control.++## Related++- Transit ticket T-1835+- MCP Streamable HTTP transport contract for `2025-03-26`:+  https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#listening-for-messages-from-the-server
specs/mcp-server/implementation.md Modified +1 / -1
diff --git a/specs/mcp-server/implementation.md b/specs/mcp-server/implementation.mdindex 2d0b79d..109243d 100644--- a/specs/mcp-server/implementation.md+++ b/specs/mcp-server/implementation.md@@ -44,7 +44,7 @@ All MCP code is behind `#if os(macOS)` guards. iOS builds are unaffected.  The architecture has three layers: -1. **Transport layer** (`MCPServer`): Hummingbird HTTP server with a single `POST /mcp` route. Runs in a `Task.detached` to keep `Application.run()` off the MainActor. Parses raw HTTP into `JSONRPCRequest`, dispatches to the handler, serialises the response back to HTTP.+1. **Transport layer** (`MCPServer`): Hummingbird HTTP server with JSON-RPC over `POST /mcp`; `GET /mcp` is explicitly handled with HTTP 405 and `Allow: POST` because Transit does not offer an SSE listening stream. Runs in a `Task.detached` to keep `Application.run()` off the MainActor. Parses raw HTTP into `JSONRPCRequest`, dispatches to the handler, serialises the response back to HTTP.  2. **Protocol layer** (`MCPToolHandler`): `@MainActor` class that owns references to `TaskService` and `ProjectService`. Dispatches JSON-RPC methods (`initialize`, `tools/list`, `tools/call`, `ping`) and translates tool arguments into service calls. Reuses `IntentHelpers` for JSON encoding and project lookup error mapping — same code path as the App Intents layer. 

Things to double-check

Remote identity and freshness

GitHub PR #207 reports the exact requested head/base. The exact-head claude-review check completed successfully at 06:34:52Z, after the latest qualifying clean review at 06:34:45Z.

Intervening PR #206

PR #206 is the commit at the requested base and changes only date-filter implementation/tests plus its changelog and report. It does not alter the MCP route files or route-test scope.