Skip to content

Serve the coach MCP from the main image at /mcp - #14

Merged
aderaaij merged 1 commit into
mainfrom
feat/mcp-in-image
Nov 12, 2026
Merged

aderaaij merged 1 commit into
mainfrom
feat/mcp-in-image

Conversation

@aderaaij

Copy link
Copy Markdown
Owner

Using the coach meant a second install: uv, a second .env with a token pasted in, a process to keep alive and a second port to expose. The Docker quick start mentioned none of it. The MCP now runs inside the backend process and is served at /mcp on the API's own port, so wherever the dashboard is reachable the coach is too, with nothing to configure.

What changes

  • Endpoint. backend/app/mcp_mount.py serves the MCP at /mcp and /mcp/. A request without a bearer token is a 401. MCP_ENABLED=false turns it off.
  • Same data path. Embedded, the MCP still calls the API over HTTP (loopback) with the caller's own token, so auth, user scoping and consent filtering are unchanged.
  • Package rename. mcp/app → mcp/training_mcp, because the backend process already owns app. This is most of the diff. Its import-time logging setup moved into main() so importing it doesn't reconfigure the host process.
  • Dependencies. The MCP is a path dependency of the backend, so the image runs it from backend/uv.lock. fastmcp and the MCP SDK are held to the versions the MCP's own suite tests; unconstrained, the resolver also moved Starlette to 1.x under the API.
  • Docs. The README's MCP section now leads with the built-in endpoint; running the MCP on its own is the optional path, with the uv prerequisite stated.

Worth reviewing

  • Dispatched, not mounted. A Starlette mount only matches /mcp/..., so a POST to the bare /mcp fell through to the GET-only SPA catch-all and came back 405.
  • The door checks only that a bearer token is present. The API verifies it on every call. A made-up token can open a session and list tool descriptions (the consent lookup fails open), but every data call fails.
  • Two lockfiles. The pins in backend/pyproject.toml have to move together with mcp/uv.lock.

Checked

  • Backend: 278 tests pass, 6 of them new (tests/test_mcp_mount.py). MCP: its 14 tests pass.
  • Local demo stack from the built image: a real MCP client listed 40 tools and made tool calls at /mcp; a bogus token got "rejected" on every data call; the dashboard and SPA routes still load.
  • On the home server, running this image: the same token returns the same 40 tools and 12 plans as the standalone MCP it replaces, and Claude Desktop works against the new address.

Upgrade note for standalone installs

uv run start is unaffected. An install that runs .venv/bin/start directly needs one uv sync after pulling.

Using the coach meant a second install: uv, a second .env with a token
pasted in, a process to keep alive and a second port to expose. None of
that was in the Docker quick start. The MCP now runs inside the backend
process and is served at /mcp on the API's own port, so wherever the
dashboard is reachable the coach is too, with nothing to configure.

It still talks to the API over HTTP (loopback) with the caller's own
token, so auth, user scoping and consent filtering stay one code path.
A request without a bearer token is a 401. MCP_ENABLED=false turns the
endpoint off.

/mcp is dispatched ahead of the router and not mounted: a Starlette
mount only matches /mcp/..., so a POST to the bare path fell through to
the GET-only SPA catch-all and came back 405.

The MCP's package is renamed app -> training_mcp, since the backend
process already owns `app`, and its import-time logging setup moved into
main(). It is a path dependency of the backend; fastmcp and the MCP SDK
are held to the versions the MCP's own suite tests, which also keeps
starlette where it was under the API.

Running the MCP on its own (stdio, or a separate HTTP port) still works
and is documented as the optional path.
@aderaaij
aderaaij merged commit f8ebcf3 into main Nov 12, 2026
3 checks passed
@aderaaij
aderaaij deleted the feat/mcp-in-image branch November 12, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant