Return HTML charts as EmbeddedResource instead of server-side files
HTML charts were being written to the server filesystem and returning a path the client couldn't access. Now returns the HTML content inline as an MCP EmbeddedResource with text/html MIME type, so the client receives the full interactive chart over the wire.
This commit is contained in:
parent
89cdeb0967
commit
ad17d72894
@ -15,7 +15,6 @@ WORKDIR /app
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
ENV MPLCONFIGDIR=/tmp/matplotlib
|
||||
ENV MCNOAA_CHARTS_DIR=/tmp/charts
|
||||
|
||||
COPY --from=deps /app/.venv /app/.venv
|
||||
COPY src/ src/
|
||||
|
||||
@ -2,12 +2,11 @@
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from fastmcp import Context, FastMCP
|
||||
from fastmcp.utilities.types import Image
|
||||
from mcp.types import ToolAnnotations
|
||||
from mcp.types import EmbeddedResource, TextResourceContents, ToolAnnotations
|
||||
|
||||
from mcnoaa_tides.charts import check_deps
|
||||
from mcnoaa_tides.client import NOAAClient
|
||||
@ -23,15 +22,16 @@ def register(mcp: FastMCP) -> None:
|
||||
hours: int = 48,
|
||||
include_observed: bool = True,
|
||||
format: Literal["png", "html"] = "png",
|
||||
) -> Image | str:
|
||||
) -> Image | EmbeddedResource:
|
||||
"""Generate a tide prediction chart with high/low markers.
|
||||
|
||||
Creates a visual chart of tide predictions showing the water level curve
|
||||
with high (H) and low (L) tide markers. Optionally overlays observed
|
||||
water levels as a dashed line for comparison.
|
||||
|
||||
PNG format returns an inline image. HTML format saves an interactive
|
||||
chart to artifacts/charts/ and returns the file path.
|
||||
PNG format returns an inline image. HTML format returns an interactive
|
||||
Plotly chart as an embedded resource (text/html) that the client can
|
||||
save or render directly.
|
||||
|
||||
Requires mcnoaa-tides[viz] to be installed.
|
||||
"""
|
||||
@ -97,8 +97,7 @@ def register(mcp: FastMCP) -> None:
|
||||
from mcnoaa_tides.charts.tides import render_tide_chart_html
|
||||
|
||||
html = render_tide_chart_html(predictions, observed, station_name)
|
||||
path = _save_html(html, station_id, "tides")
|
||||
return f"Interactive tide chart saved to: {path}"
|
||||
return _html_resource(html, station_id, "tides")
|
||||
|
||||
@mcp.tool(tags={"visualization"}, annotations=_ANNOTATIONS)
|
||||
async def visualize_conditions(
|
||||
@ -106,7 +105,7 @@ def register(mcp: FastMCP) -> None:
|
||||
station_id: str,
|
||||
hours: int = 24,
|
||||
format: Literal["png", "html"] = "png",
|
||||
) -> Image | str:
|
||||
) -> Image | EmbeddedResource:
|
||||
"""Generate a multi-panel marine conditions dashboard.
|
||||
|
||||
Creates a dashboard with up to 4 panels:
|
||||
@ -117,8 +116,9 @@ def register(mcp: FastMCP) -> None:
|
||||
|
||||
Products unavailable at a station are simply omitted from the dashboard.
|
||||
|
||||
PNG format returns an inline image. HTML format saves an interactive
|
||||
chart to artifacts/charts/ and returns the file path.
|
||||
PNG format returns an inline image. HTML format returns an interactive
|
||||
Plotly dashboard as an embedded resource (text/html) that the client
|
||||
can save or render directly.
|
||||
|
||||
Requires mcnoaa-tides[viz] to be installed.
|
||||
"""
|
||||
@ -184,23 +184,16 @@ def register(mcp: FastMCP) -> None:
|
||||
from mcnoaa_tides.charts.conditions import render_conditions_html
|
||||
|
||||
html = render_conditions_html(snapshot, station_name)
|
||||
path = _save_html(html, station_id, "conditions")
|
||||
return f"Interactive conditions dashboard saved to: {path}"
|
||||
return _html_resource(html, station_id, "conditions")
|
||||
|
||||
|
||||
def _save_html(html: str, station_id: str, chart_type: str) -> Path:
|
||||
"""Save HTML chart and return the path.
|
||||
|
||||
Uses $MCNOAA_CHARTS_DIR if set, otherwise falls back to
|
||||
artifacts/charts/ (relative to cwd). The container sets
|
||||
MCNOAA_CHARTS_DIR=/tmp/charts so the nobody user can write.
|
||||
"""
|
||||
import os
|
||||
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
||||
base = os.environ.get("MCNOAA_CHARTS_DIR", "artifacts/charts")
|
||||
out_dir = Path(base)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
path = out_dir / f"{station_id}_{chart_type}_{timestamp}.html"
|
||||
path.write_text(html, encoding="utf-8")
|
||||
return path
|
||||
def _html_resource(html: str, station_id: str, chart_type: str) -> EmbeddedResource:
|
||||
"""Wrap an HTML chart as an MCP EmbeddedResource for inline delivery."""
|
||||
return EmbeddedResource(
|
||||
type="resource",
|
||||
resource=TextResourceContents(
|
||||
uri=f"chart://{chart_type}/{station_id}",
|
||||
mimeType="text/html",
|
||||
text=html,
|
||||
),
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user