feat(host-agent): mount /mcp + surface MCP status in console
This commit is contained in:
@@ -24,6 +24,8 @@ from host_agent.devices import register_local_device, unregister_local_device
|
||||
from host_agent.history import ConsoleHistoryStore
|
||||
from host_agent.identity import HostIdentityStore
|
||||
from host_agent.local_account import LocalAccountStore
|
||||
from host_agent.mcp_lock import McpBusyTracker
|
||||
from host_agent.mcp_token import McpTokenStore
|
||||
from host_agent.status import AgentStatusTracker
|
||||
from host_agent.web.auth import (
|
||||
SessionManager,
|
||||
@@ -31,6 +33,7 @@ from host_agent.web.auth import (
|
||||
attempt_login,
|
||||
change_password,
|
||||
)
|
||||
from host_agent.web.mcp_auth import BearerAuthMiddleware
|
||||
from storage.device_config import DeviceConfigStore
|
||||
from storage.task_metadata import TaskMetadataStore
|
||||
from storage.timeline import Timeline
|
||||
@@ -223,6 +226,9 @@ def create_console_app(
|
||||
metadata_store: TaskMetadataStore | None = None,
|
||||
timeline: Timeline | None = None,
|
||||
executor: AssignmentExecutor | None = None,
|
||||
mcp_server: Any = None,
|
||||
mcp_token_store: McpTokenStore | None = None,
|
||||
mcp_busy_tracker: McpBusyTracker | None = None,
|
||||
) -> FastAPI:
|
||||
app = FastAPI(title="Host Agent Console")
|
||||
cookie_secure = config.console_bind_host not in _LOOPBACK_BIND_HOSTS
|
||||
@@ -231,6 +237,18 @@ def create_console_app(
|
||||
if cancel_task is None and host_client is not None:
|
||||
cancel_task = host_client.cancel_task
|
||||
submission_available = submit_self_task is not None
|
||||
mcp_mounted = mcp_server is not None and mcp_token_store is not None
|
||||
if mcp_mounted:
|
||||
from starlette.applications import Starlette
|
||||
from starlette.middleware import Middleware
|
||||
|
||||
mcp_asgi = mcp_server.streamable_http_app()
|
||||
authed = Starlette(
|
||||
routes=[],
|
||||
middleware=[Middleware(BearerAuthMiddleware, token_store=mcp_token_store)],
|
||||
)
|
||||
authed.router.mount("/", mcp_asgi)
|
||||
app.mount("/mcp", authed)
|
||||
|
||||
def _running_devices() -> list[dict[str, str]]:
|
||||
return [
|
||||
@@ -340,6 +358,10 @@ def create_console_app(
|
||||
for d in manager.list_devices()
|
||||
]
|
||||
texts = _dashboard_texts(snapshot=snapshot)
|
||||
mcp_endpoint = "/mcp" if mcp_mounted else None
|
||||
mcp_busy_devices = (
|
||||
mcp_busy_tracker.busy_device_ids() if mcp_busy_tracker is not None else []
|
||||
)
|
||||
return _render(
|
||||
"dashboard.html",
|
||||
title="Status",
|
||||
@@ -347,6 +369,8 @@ def create_console_app(
|
||||
identity=identity,
|
||||
devices=devices,
|
||||
config=config,
|
||||
mcp_endpoint=mcp_endpoint,
|
||||
mcp_busy_devices=mcp_busy_devices,
|
||||
**texts,
|
||||
)
|
||||
|
||||
@@ -375,7 +399,18 @@ def create_console_app(
|
||||
}
|
||||
for device in manager.list_devices()
|
||||
]
|
||||
return JSONResponse({"status": snapshot, "devices": devices})
|
||||
return JSONResponse(
|
||||
{
|
||||
"status": snapshot,
|
||||
"devices": devices,
|
||||
"mcp_endpoint": "/mcp" if mcp_mounted else None,
|
||||
"mcp_busy_devices": (
|
||||
mcp_busy_tracker.busy_device_ids()
|
||||
if mcp_busy_tracker is not None
|
||||
else []
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@app.get("/devices", response_class=HTMLResponse)
|
||||
async def devices_page(
|
||||
|
||||
@@ -20,6 +20,24 @@
|
||||
<p id="current-assignment">{{ assignment_text }}</p>
|
||||
<p id="current-progress">{{ progress_text }}</p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>MCP</h2>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>MCP</td>
|
||||
<td>
|
||||
{% if mcp_endpoint %}
|
||||
endpoint <code>{{ mcp_endpoint }}</code>;
|
||||
{% if mcp_busy_devices %}busy: {{ mcp_busy_devices|join(", ") }}{% else %}idle{% endif %}
|
||||
{% else %}
|
||||
not configured
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Devices</h2>
|
||||
<table>
|
||||
|
||||
Reference in New Issue
Block a user