You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
opal mcp is unusable as a stdio MCP server. Two requirements stated:
The process must block serving stdio until stdin closes. Observed (over ssh): startup banners print, then the prompt returns immediately. Acceptance: timeout 5 uv run opal mcp >/dev/null; echo $? prints 124, not 0/1 instantly.
All human-readable output (banners, project/database lines, logging) goes to stderr; stdout carries JSON-RPC exclusively from byte zero. Acceptance: uv run opal mcp 1>/dev/null still shows the banner; 2>/dev/null shows nothing.
Investigation (2026-06-11, commit 13cdbc6; src/opal/mcp/server.py and src/opal/__main__.py are identical on devel and master)
Committed code passes both acceptance criteria on the reporter's machine:
Entry-point prints were moved to stderr in aa3d366 ("Every entry point states which database it resolved, and why"); run_server (src/opal/mcp/server.py:5002) banners likewise target stderr, and the stdio loop (async with stdio_server(): await server.run(...)) is awaited correctly. Note: the server is the low-level mcp.server.Server, not FastMCP. Versions: mcp 1.27.1, anyio 4.12.0.
Root cause of the observed failure
The instant exit reproduces only in the main checkout/home/lv154/code/opal, branch feature/risk-scenarios with uncommitted risk-module changes (#40). The rewritten src/opal/db/models/risk.py drops RiskStatus (replaced by RiskDisposition / RiskIssueRole), while src/opal/mcp/server.py:63 still does from opal.db.models.risk import RiskStatus:
Using project: Purple Orchid (/home/lv154/code/opal)
Database: sqlite:////home/lv154/code/opal/data/opal.db (opal.project.yaml auto-detected)
Traceback (most recent call last):
...
File ".../src/opal/mcp/server.py", line 63, in <module>
from opal.db.models.risk import RiskStatus
ImportError: cannot import name 'RiskStatus' from 'opal.db.models.risk'
Exit 1 immediately after the banner lines — matching the reported symptom. Blast radius of the WIP is wider than MCP: web/routes.py, api/routes/risks.py, and seed.py also import RiskStatus, so opal serve and opal seed are equally down on that checkout.
Disposition
Cannot duplicate on committed code — defect attributed to in-progress risk-module refactor (test article was the dirty feature/risk-scenarios working tree).
Corrective action: when the risk model lands, update the four RiskStatus importers (mcp/server.py, web/routes.py, api/routes/risks.py, seed.py) in the same change — scope of Development: Risks #40, not a separate fix.
Preventive action: add a stdio-contract regression test that spawns opal mcp as a subprocess and asserts (a) it stays alive while stdin is open, (b) stdout is byte-clean JSON-RPC through the initialize handshake, (c) it exits cleanly on stdin EOF.
Discrepancy / needed from reporter
The reported banner was OPAL MCP server | project: ..., which prints insiderun_server — i.e. after the import that fails above. If that exact line really appeared before the prompt returned (rather than Using project: Purple Orchid + traceback), the failing run was a different environment than this checkout: please provide host, cwd, git rev-parse HEAD + git status -s, and the unredirected stderr of the failing run.
Reported
opal mcpis unusable as a stdio MCP server. Two requirements stated:timeout 5 uv run opal mcp >/dev/null; echo $?prints124, not 0/1 instantly.uv run opal mcp 1>/dev/nullstill shows the banner;2>/dev/nullshows nothing.Investigation (2026-06-11, commit 13cdbc6;
src/opal/mcp/server.pyandsrc/opal/__main__.pyare identical ondevelandmaster)Committed code passes both acceptance criteria on the reporter's machine:
timeout 8 opal mcp >/dev/nulltimeout 8 opal mcpunder a pty (script -qec)timeout 8 opal mcp --project /home/lv154/code/opal(Purple Orchid)OPAL MCP server | project: Purple Orchidon stderrtimeout 10 opal mcp < /dev/nullEntry-point prints were moved to stderr in aa3d366 ("Every entry point states which database it resolved, and why");
run_server(src/opal/mcp/server.py:5002) banners likewise target stderr, and the stdio loop (async with stdio_server(): await server.run(...)) is awaited correctly. Note: the server is the low-levelmcp.server.Server, not FastMCP. Versions: mcp 1.27.1, anyio 4.12.0.Root cause of the observed failure
The instant exit reproduces only in the main checkout
/home/lv154/code/opal, branchfeature/risk-scenarioswith uncommitted risk-module changes (#40). The rewrittensrc/opal/db/models/risk.pydropsRiskStatus(replaced byRiskDisposition/RiskIssueRole), whilesrc/opal/mcp/server.py:63still doesfrom opal.db.models.risk import RiskStatus:Exit 1 immediately after the banner lines — matching the reported symptom. Blast radius of the WIP is wider than MCP:
web/routes.py,api/routes/risks.py, andseed.pyalso importRiskStatus, soopal serveandopal seedare equally down on that checkout.Disposition
Cannot duplicate on committed code — defect attributed to in-progress risk-module refactor (test article was the dirty
feature/risk-scenariosworking tree).RiskStatusimporters (mcp/server.py,web/routes.py,api/routes/risks.py,seed.py) in the same change — scope of Development: Risks #40, not a separate fix.opal mcpas a subprocess and asserts (a) it stays alive while stdin is open, (b) stdout is byte-clean JSON-RPC through the initialize handshake, (c) it exits cleanly on stdin EOF.Discrepancy / needed from reporter
The reported banner was
OPAL MCP server | project: ..., which prints insiderun_server— i.e. after the import that fails above. If that exact line really appeared before the prompt returned (rather thanUsing project: Purple Orchid+ traceback), the failing run was a different environment than this checkout: please provide host, cwd,git rev-parse HEAD+git status -s, and the unredirected stderr of the failing run.