Skip to content

MCP facade: GET <mount>/mcp serves HTML to MCP clients; catalog entry ships placeholder endpoint & phantom tools #389

Description

@TonsOfFun

Part of an ActiveAgent + actionagent dashboard functional review (multi-agent, adversarially verified). Severity: 🟡 Minor.

GET <mount>/mcp falls through the SPA catch-all and returns 200 text/html (the dashboard page) to MCP clients instead of the spec-required text/event-stream or a 405. And the catalog's first-party activeagents entry hardcodes the literal placeholder <mount>/mcp as its endpoint and advertises tools (call_agent, list_agents) the facade doesn't actually expose.

Findings

GET /mcp returns 200 text/html (the dashboard SPA) to MCP clients instead of the spec-required text/event-stream or 405

  • Where: actionagent/config/routes.rb:143 · severity: minor · kind: contract · repo: activeagent
  • What breaks: The engine routes POST /mcp to the JSON-RPC facade (routes.rb:137) but the SPA catch-all (routes.rb:143, constraint only excludes '/api/' paths) serves GET /mcp as the dashboard HTML page — the same path deliberately doubles as the MCP Services view deep-link (Dashboard.jsx:58-62). Under the MCP Streamable HTTP transport (protocol 2025-03-26, which both facades advertise), a client MAY issue GET on the endpoint to open the server->client SSE stream, and a server that does not offer one MUST respond 405 Method Not Allowed; clients also DELETE the endpoint to end a session. The engine instead answers GET with 200 text/html (a full HTML document a client will attempt to parse as an event stream — worse than the app copy's plain 404) and DELETE with 404. Real SDK clients treat this as a stream error rather than the clean 'no stream offered' signal, and any operator curl-testing the documented endpoint gets the dashboard page, which reads as the facade being broken.
  • Evidence: Live: curl -H 'Accept: text/event-stream' http://localhost:3001/activeagents/mcp -> 200 text/html; charset=utf-8 (dashboard SPA); curl -X DELETE http://localhost:3001/activeagents/mcp -> 404. App comparison: GET http://localhost:3000/mcp -> 404 text/html (no SPA collision, but also not the spec's 405). Code: routes.rb:137 (post only), :143 (catch-all constraint !request.path.include?("/api/") lets /mcp through to dashboard#index); Dashboard.jsx:58-62 uses the same path as the MCP Services client-side route.
  • Suggested fix: In actionagent/config/routes.rb, before the catch-all: route get "mcp" and delete "mcp" to a small handler in api/mcp (or a lambda constraint on the catch-all) that renders dashboard#index only when the request's Accept prefers text/html, and otherwise responds head :method_not_allowed with Allow: POST (per Streamable HTTP 2025-03-26: GET without SSE support and unsupported DELETE both return 405). Optionally mirror match "mcp", via: [:get, :delete] => 405 in the platform app (app has no /mcp SPA view, so no negotiation needed there).

Catalog's first-party 'activeagents' entry serves the literal placeholder '/mcp' as its endpoint and advertises tools (call_agent, list_agents) the facade does not expose

  • Where: actionagent/app/services/action_agent/mcp_catalog.rb:153 · severity: minor · kind: dx · repo: activeagent
  • What breaks: MCPCatalog's activeagents entry hardcodes url: "/mcp" (mcp_catalog.rb:153) with a comment saying it is 'relative to wherever the engine is mounted' — but nothing ever substitutes the real mount path: present() passes it through, ToolDiscovery#server_row passes it through, and McpServersView's detail row renders server.command || server.url verbatim in the Endpoint code block (McpServersView.jsx:334, 341-345). A user expanding the first-party card to connect an MCP client is shown the un-substituted template string '/mcp' instead of e.g. '/activeagents/mcp'. The same entry's tool_hints ["call_agent", "list_agents"] (mcp_catalog.rb:159) surface in the card's 'Tools offered' row and in TOOL_HINTS attribution, but the facade's actual tools are run_ (mcp_controller.rb:112-131) — neither call_agent nor list_agents exists, so the card documents tools that can never be called and the hint table can misattribute a genuine agent-defined tool named call_agent/list_agents to this server.
  • Evidence: Live: curl http://localhost:3001/activeagents/api/mcp_servers/activeagents -> server.url == "/mcp", server.tools == ["call_agent","list_agents"], while the engine's real MCP tool surface (mcp_controller.rb tools_list) is run_/run___, confirmed on the identical app facade live: tools/list at :3000 returned only run_* names. Rendering path: McpServersView.jsx ServerDetail const connection = server.command || server.url (line 334) displayed in a block labeled Endpoint (lines 341-345).
  • Suggested fix: Substitute the mount path at serialization time: have Api::McpServersController (or MCPCatalog.all/find via a mount_path: argument) replace "" with the engine's actual mount point (e.g. ActionAgent::Engine.routes.find_script_name({}) or request.script_name + engine mount) before rendering, so the card shows e.g. "/activeagents/mcp". For tool_hints, drop them for the activeagents entry or replace with the real run_* naming note: keep "call_agent" out (it is already a builtin and ToolDiscovery gives builtins priority, making the hint dead weight) and remove "list_agents" (not an MCP tool and risks misattributing a genuine agent-defined tool of that name). The presented tools list for this first-party entry could instead be derived from the account's agents (run_) or omitted.

Verification

Each finding above was produced by a dedicated per-feature review agent, then confirmed by an independent adversarial verifier (all rated high-confidence; zero rejected in this set). File:line citations are against the current main/HEAD of each repo; many were reproduced live against a booted dashboard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions