diff --git a/backend/app/assistant_tools.py b/backend/app/assistant_tools.py index d12bdc4..79f8ef8 100644 --- a/backend/app/assistant_tools.py +++ b/backend/app/assistant_tools.py @@ -16,7 +16,26 @@ MAX_FILE_LINES = 120 MAX_SEARCH_RESULTS = 12 BLOCKED_NAMES = {".env", ".git", ".ssh", "id_rsa", "id_ed25519"} -DEFAULT_MCP_URL = "http://127.0.0.1:3000/mcp/" + + +def _local_mcp_url() -> str: + """Return this process's local MCP endpoint. + + ``orbit run`` may select a port above the requested one when it is already + occupied. It passes that selected port through ``ORBIT_PORT`` to keep + generated MCP configuration from pointing at the process that occupied it. + """ + configured_port = os.environ.get("ORBIT_PORT", "3000").strip() + try: + port = int(configured_port) + except ValueError: + port = 3000 + if not 1 <= port <= 65535: + port = 3000 + return f"http://127.0.0.1:{port}/mcp/" + + +DEFAULT_MCP_URL = _local_mcp_url() def _terminal_visible() -> bool: diff --git a/backend/tests/test_assistant_graph.py b/backend/tests/test_assistant_graph.py index 63b0e7d..2254f31 100644 --- a/backend/tests/test_assistant_graph.py +++ b/backend/tests/test_assistant_graph.py @@ -1,3 +1,4 @@ +from app import assistant_tools from app.assistant_graph import DEFAULT_MCP_URL, LocalMcpTools, OrbitAssistantGraph, build_assistant_prompt from app.providers import ModelSettings @@ -56,6 +57,15 @@ def test_assistant_prompt_uses_local_mcp_as_the_control_room_source_of_truth(): assert "Assistant: Earlier answer" in prompt +def test_local_mcp_url_uses_the_effective_server_port(monkeypatch): + monkeypatch.setenv("ORBIT_PORT", "3001") + + assert assistant_tools._local_mcp_url() == "http://127.0.0.1:3001/mcp/" + + monkeypatch.setenv("ORBIT_PORT", "not-a-port") + assert assistant_tools._local_mcp_url() == "http://127.0.0.1:3000/mcp/" + + def test_assistant_prompt_prioritizes_ui_tools_for_browser_ui_requests(): prompt = build_assistant_prompt("What screen is open?", [], "en", ui_enabled=True) diff --git a/bin/orbit.mjs b/bin/orbit.mjs index 4315166..f9b203a 100755 --- a/bin/orbit.mjs +++ b/bin/orbit.mjs @@ -67,6 +67,7 @@ if (!Number.isInteger(requestedPort) || requestedPort < 1 || requestedPort > 655 const port = await nextAvailablePort(requestedPort, host) const localUrl = `http://127.0.0.1:${port}` console.log(`OpenOrbit is starting. Open ${localUrl}`) +console.log(`MCP endpoint: ${localUrl}/mcp/`) if (port !== requestedPort) { console.log(`Port ${requestedPort} is in use; using ${port} instead.`) } @@ -76,7 +77,9 @@ if (process.env.ORBIT_PUBLIC_URL) { const server = spawn(venvPython, ['-m', 'uvicorn', 'app.main:app', '--app-dir', 'backend', '--host', host, '--port', port], { cwd: root, stdio: 'inherit', - env: { ...process.env, ...(appData ? { ORBIT_APP_DATA: appData } : {}) }, + // Keep the server's generated local MCP configuration aligned with the + // port selected above when the requested port was already occupied. + env: { ...process.env, ORBIT_PORT: String(port), ...(appData ? { ORBIT_APP_DATA: appData } : {}) }, }) process.on('SIGINT', () => server.kill('SIGINT')) process.on('SIGTERM', () => server.kill('SIGTERM'))