Add MCP server flow: Remote URL + JSON tabs, Test connection

Explicit connect reports stderr tails; a 401 on an anonymous http probe becomes needs-sign-in with a one-click OAuth switch.
Test button probes any enabled server row without opening a session.
This commit is contained in:
Rohit C Prasad
2026-08-20 13:50:13 -07:00
parent 8e71256ede
commit 5ebaa376d7
8 changed files with 362 additions and 16 deletions
+82
View File
@@ -312,3 +312,85 @@ async def test_prepare_records_failure_status_and_session_notice(
assert [n for n, _ in drained] == ["sales-db"]
assert "boom: bad args" in (drained[0][1] or "")
assert manager.pop_mcp_failures("s1") == [] # one-shot
# -- explicit connect (UX-033: add → Test → fix, without opening a session) ------
@pytest.mark.asyncio
async def test_connect_mcp_failure_includes_stderr_tail(tmp_path, monkeypatch):
"""The Test button's connect path reports the same stderr evidence as the
session path — a crashing stdio server yields error + status=error."""
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
_write_json(
tmp_path / "state" / "mcp.json",
{
"mcpServers": {
"doomed": {
"command": "/bin/sh",
"args": ["-c", "echo 'usage: doomed --flag' >&2; exit 7"],
"enabled": True,
}
}
},
)
manager = SessionManager(data_dir=tmp_path / "data")
result = await manager.connect_mcp("doomed")
assert result["ok"] is False
assert "usage: doomed --flag" in result["error"]
listed = {s["name"]: s for s in manager.list_mcp()}
assert listed["doomed"]["status"] == "error"
assert listed["doomed"]["auth_hint"] is False
# Removing the server takes its stale failure state with it.
manager.delete_mcp("doomed")
assert manager._mcp_errors.get("doomed") is None
@pytest.mark.asyncio
async def test_connect_mcp_http_401_sets_auth_hint(tmp_path, monkeypatch):
"""An anonymous connect that hits 401 is reported as "needs sign-in" (the GUI
offers the OAuth switch), not as a raw HTTP error dump."""
import http.server
import threading
class _Deny(http.server.BaseHTTPRequestHandler):
def _deny(self):
self.send_response(401)
self.send_header("Content-Length", "0")
self.end_headers()
do_GET = do_POST = do_DELETE = _deny
def log_message(self, *args): # keep pytest output clean
pass
srv = http.server.ThreadingHTTPServer(("127.0.0.1", 0), _Deny)
threading.Thread(target=srv.serve_forever, daemon=True).start()
try:
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
_write_json(
tmp_path / "state" / "mcp.json",
{
"mcpServers": {
"guarded": {
"url": f"http://127.0.0.1:{srv.server_address[1]}/mcp",
"enabled": True,
}
}
},
)
manager = SessionManager(data_dir=tmp_path / "data")
result = await manager.connect_mcp("guarded")
assert result["ok"] is False
assert "sign in" in result["error"]
listed = {s["name"]: s for s in manager.list_mcp()}
assert listed["guarded"]["auth_hint"] is True
assert listed["guarded"]["status"] == "error"
finally:
srv.shutdown()
srv.server_close()