committed 08:15PM - 27 Jul 26 UTC
* feat(restapi): add optional MCP endpoint (/mcp) wrapping the REST API (#1761)
…
* feat(restapi): add optional MCP endpoint (/mcp) wrapping the REST API
Add an optional Model Context Protocol (MCP) endpoint at /mcp on the REST
API. It is generated from the existing FastAPI routes via FastMCP's
`from_fastapi`, so every REST endpoint (coverage, stations, values,
interpolate, summarize, stripes, alerts, ...) becomes an MCP tool, proxied
in-process and served over the streamable-HTTP transport in the same app.
- Enabled by the new `mcp` extra (`pip install wetterdienst[mcp]`), which
pulls in `wetterdienst[restapi]` plus fastmcp (>=3.4.4,<4). Without it, the
REST API is unchanged and the /mcp route is simply absent.
- The MCP routes are added to the existing app and the session-manager
lifespan is adopted, so the auto-generated docs routes are not duplicated
and the existing REST endpoints keep working with or without the lifespan.
- Installed in the Docker image (build + dev stages) so the hosted
wetterdienst.eobs.org instance serves /mcp as well.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(mcp): make the MCP tools usable by small models
Optimize the /mcp surface so even a cheap model completes a task like
"recent temperature at Hamburg-Fuhlsbüttel" in a couple of calls instead of
thrashing. Validated by driving the endpoint with Haiku agents: 8 calls (with
a workaround) before -> 3 clean calls after.
Changes (in a new wetterdienst.ui.mcp module):
- Attach a workflow `instructions` block to the server (find a station, then
query its values; DWD defaults; parameter syntax; how to read/narrow results).
- Give the tools clean names (`values` instead of `values_api_values_get`) and
hide the non-data endpoints (index, robots, health, version, auth).
- Write real tool descriptions via the endpoint docstrings (stations, values,
coverage) instead of "Wrap get_* to provide results via restapi.".
- Build the OpenAPIProvider directly with validate_output=False: the endpoints
return a raw JSON string but declare rich response_model unions, so FastMCP's
derived output schemas are wrong and rejected valid results with
"9.0 is not of type 'string'". A permissive schema fixes the `values` tool.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(readme): mention the optional MCP endpoint in features
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(frontend): proxy /mcp to the backend (streamable HTTP)
Add a Nitro server route that pass-through-proxies /mcp to the backend's MCP
endpoint, preserving all methods, headers and streaming (POST + SSE + the
mcp-session-id header) via h3's proxyRequest. h3 strips the `accept` header
when proxying, so it is re-added because the MCP transport requires
`Accept: application/json, text/event-stream`.
This makes the MCP endpoint reachable on the frontend origin
(e.g. https://wetterdienst.eobs.org/mcp), not just the backend port. Docs
updated to describe backend vs hosted URLs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(frontend): changelog entry for the /mcp proxy
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(restapi): harden MCP mounting (degrade on build error, compose lifespan)
Address code-review findings on the /mcp mount:
- Wrap the whole MCP build in try/except so a build/version error logs and
degrades to a plain REST API (no /mcp) instead of taking down the app
import; ModuleNotFoundError still cleanly means "[mcp] extra not installed".
- Compose the MCP session-manager lifespan with the app's existing lifespan
via combine_lifespans instead of replacing it, so current and future app
startup/shutdown still run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(mcp): close the in-process httpx client on shutdown; fix stale docstring
- Attach a FastMCP lifespan that closes the ASGI-backed httpx.AsyncClient on
server shutdown; that lifespan is composed into the REST app's lifespan when
/mcp is mounted, so the client no longer leaks past shutdown.
- Correct the module docstring: the tools are generated via OpenAPIProvider
(needed for validate_output=False), not FastMCP.from_fastapi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(frontend): make /mcp proxy handle sub-paths and normalize Accept
- Split the single exact-match /mcp route into a shared handler used by
routes/mcp/index.ts (/mcp) and routes/mcp/[...].ts (/mcp/**), forwarding the
incoming path and query verbatim so a trailing-slash /mcp/ (or any sub-path)
reaches the backend instead of 404ing at the frontend.
- The MCP streamable-HTTP transport rejects a `*/*` or missing Accept with 406;
pass through an SSE-aware Accept unchanged, otherwise send the required
`application/json, text/event-stream` pair.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>