Skip to content

fix(dashboard): read the global mcp.json off the event loop - #24093

Merged
KiroCrewCaptain merged 1 commit into
kirodotdev:mainfrom
IngridMorstrad:fix/mcp-probe-global-read-off-loop
Oct 26, 2026
Merged

KiroCrewCaptain merged 1 commit into
kirodotdev:mainfrom
IngridMorstrad:fix/mcp-probe-global-read-off-loop

Conversation

@IngridMorstrad

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: Loading the MCP panel or running an MCP probe no longer stalls every other session while ~/.kiro/settings/mcp.json is read.

The background probe (_run_mcp_probe) and the GET /api/mcp, GET /api/mcp/active and POST /api/mcp/probe routes in src/kiro_crew/dashboard/handlers/mcp_probe.py each read the Kiro-global mcp.json with a bare read_text() inside the async def. On a slow or network-mounted home directory every session's turn waits on that read, and a long stall trips the loop watchdog (#3057, #1572). The sync-io gate lists all four (4 src/kiro_crew/dashboard/handlers/mcp_probe.py).

Why it matters

GET /api/mcp runs on every MCP panel load, so the stall hits anyone who opens the panel, not only people who probe. The Kiro Crew store read on the next line already runs in a thread; only the global file was left on the loop.

Not a goal

What changed (motivation → approach → change)

The global file was read on the loop at four sites, while the store read beside each already used asyncio.to_thread. A new sync helper, _read_global_mcps(), does the read and the parse and returns mcpServers, falling back to {} on the same FileNotFoundError / JSONDecodeError as before. All four sites now await asyncio.to_thread(_read_global_mcps). The helper is re-exported from the handlers/mcp.py facade like the module's other names. The mcp_probe.py line is removed from .github/sync-io-in-async-fs-baseline.txt with --update-baseline.

Backwards compatibility

Compatible: the four routes return the same payloads, and a missing or unparsable file still reads as {}. Only the thread doing the read changes.

Removes nothing: the only removal is a sync-io baseline line for calls that no longer exist.

Tests

test/test_mcp_global_read_off_loop.py swaps _GLOBAL_MCP_JSON for a file that records the thread that read it. It drives each of the four entry points and asserts that the read happened and did not run on the loop thread. It is modelled on test/test_mcp_probe_spawn_off_loop.py.

Proof: PROVEN test/test_mcp_global_read_off_loop.py::test_global_mcp_json_is_read_off_the_loop_thread[_run_mcp_probe], test/test_mcp_global_read_off_loop.py::test_global_mcp_json_is_read_off_the_loop_thread[api_mcp_servers], test/test_mcp_global_read_off_loop.py::test_global_mcp_json_is_read_off_the_loop_thread[api_mcp_active], test/test_mcp_global_read_off_loop.py::test_global_mcp_json_is_read_off_the_loop_thread[api_mcp_probe] (head e74f94b)

Local: 536 passed across the MCP handler, probe, quarantine, facade and sync-io gate suites. check_sync_io_in_async.py, black, isort, flake8, mypy src/kiro_crew, the comment-history and brand gates, and docs-lint.sh all pass.

Manual verification

N/A: unit coverage is sufficient. The change only moves a file read into a worker thread, and the test pins the thread for every entry point.

Related Issues

Closes #24092

Sibling sweep

Swept: git grep -n "_GLOBAL_MCP_JSON.read_text" -- src/ (classified by enclosing function with ast)
Sibling: src/kiro_crew/dashboard/handlers/mcp_probe.py -- changed here
Sibling: src/kiro_crew/dashboard/handlers/mcp.py -- not affected -- read-modify-writes under _get_mcp_lock (api_mcp_sync/toggle/toggle_tool/toggle_all/remove/server_detail); fixing them means threading the whole RMW, and they stay in the sync-io baseline (8)
Sibling: src/kiro_crew/dashboard/handlers/mcp_agent_sync.py -- not affected -- reads are inside sync helpers (_sync_mcp_to_agent_unlocked, _sync_mcp_to_agent_batch_unlocked), not an async def; the gate does not list them

Pattern harvest

Not generalizable: the sync-io-in-async gate and its baseline already catch this class; this PR only works one baselined file down.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

The background MCP probe and the GET /api/mcp, GET /api/mcp/active
and POST /api/mcp/probe routes each read ~/.kiro/settings/mcp.json
with a bare read_text() inside the async def. A slow or network home
directory stalled every session while the MCP panel loaded or a
probe ran.

One sync helper, _read_global_mcps(), now does the read, parse and
.get("mcpServers", {}) with the same FileNotFoundError /
JSONDecodeError fallback, and all four sites await it through
asyncio.to_thread. The sync-io baseline entry for mcp_probe.py is
gone.

Closes kirodotdev#24092
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: checking Automated validation is still running labels Oct 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Opus 5.5, fork) — ⏭️ skipped

Revision e74f94b76f9e4f9d2612ca84b080eab01c37662d touches no user-facing surface (no changes under website/ or committed screenshots), so the UX review was skipped. Does not block merge.

@github-actions

Copy link
Copy Markdown
Contributor

First Principles Review (Opus 5.5, fork) — ✅ PASS

Premise-level review of e74f94b76f9e4f9d2612ca84b080eab01c37662d via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

First-Principles-Verdict: PASS

Nothing blocks. This is a mechanism-level fix of a baselined sync-io debt. The seven remaining _GLOBAL_MCP_JSON.read_text sites in handlers/mcp.py sit under the RMW lock, and the frozen Not-a-goal excludes them with a stated reason.

What this change ships

Inventory (4 items) — 4 justified

Intent: keep a slow home directory from stalling every session while the MCP panel loads or a probe runs. This is a FIX.

  1. The MCP panel listing, the active list, the probe route and the background probe now read ~/.kiro/settings/mcp.json in a worker thread, with the same {} fallback — justified
  2. A new sync helper _read_global_mcps replaces four copies of the same read-and-parse block, and it is listed in the mcp.py facade's type-checker mirror — justified
  3. The mcp_probe.py row comes out of the sync-io baseline, so the gate now catches any regression in this file — justified
  4. A new test pins the reader thread for all four entry points — justified

[FIRST-PRINCIPLES-REVIEWED] e74f94b

Description read

[DESCRIPTION-READ] 934cda65c85c135f74e038e3bdf4c41b48fb79f4982a571449dc164b30134f30

That sha256 names the PR description this verdict read. Recompute it with .github/scripts/pr-description-capture.sh; if it differs, the description changed after this verdict was formed and any finding drawn from the description is unproven.

@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Opus 5.5, fork) — ✅ PASS

Design-level review of e74f94b76f9e4f9d2612ca84b080eab01c37662d via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

Design-Verdict: PASS

No design-level concerns. The change moves one blocking read into a worker thread, the same way the store read next to it already works, and it keeps the same fallback.

[DESIGN-REVIEWED] e74f94b

Description read

[DESCRIPTION-READ] 3e5f863f8ae3fbfcec46b3244bf87ff542cca6cfc3236f992c90240dcc288cca

That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

Copy link
Copy Markdown
Contributor

Opus 5.5 Review (fork) — ✅ no blocking findings

Reviewed e74f94b76f9e4f9d2612ca84b080eab01c37662d via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] e74f94b

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Oct 25, 2026
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

GPT 6.1 Review (fork) — ✅ no blocking findings

Reviewed e74f94b76f9e4f9d2612ca84b080eab01c37662d via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] e74f94b

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Oct 25, 2026
@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention labels Oct 25, 2026
@KiroCrewCaptain
KiroCrewCaptain enabled auto-merge (squash) October 26, 2026 00:03

@KiroCrewCaptain KiroCrewCaptain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier 1 auto-approve: fix (4 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, security checklist all-NO, no reviewer-lane findings, readiness signal fresh for this head SHA, declared category matches the diff. Category: bug fix that moves the four reads of the global mcp.json in the MCP probe and listing handlers into one helper called through a worker thread, drops the file from the sync-I/O baseline, and adds a test.

@KiroCrewCaptain
KiroCrewCaptain merged commit 053b6cf into kirodotdev:main Oct 26, 2026
144 of 146 checks passed
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dashboard: MCP probe and listing routes read the global mcp.json on the event loop

2 participants