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 UV_COMPILE_BYTECODE=1
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
ENV MPLCONFIGDIR=/tmp/matplotlib
|
ENV MPLCONFIGDIR=/tmp/matplotlib
|
||||||
ENV MCNOAA_CHARTS_DIR=/tmp/charts
|
|
||||||
|
|
||||||
COPY --from=deps /app/.venv /app/.venv
|
COPY --from=deps /app/.venv /app/.venv
|
||||||
COPY src/ src/
|
COPY src/ src/
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from fastmcp import Context, FastMCP
|
from fastmcp import Context, FastMCP
|
||||||
from fastmcp.utilities.types import Image
|
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.charts import check_deps
|
||||||
from mcnoaa_tides.client import NOAAClient
|
from mcnoaa_tides.client import NOAAClient
|
||||||
@ -23,15 +22,16 @@ def register(mcp: FastMCP) -> None:
|
|||||||
hours: int = 48,
|
hours: int = 48,
|
||||||
include_observed: bool = True,
|
include_observed: bool = True,
|
||||||
format: Literal["png", "html"] = "png",
|
format: Literal["png", "html"] = "png",
|
||||||
) -> Image | str:
|
) -> Image | EmbeddedResource:
|
||||||
"""Generate a tide prediction chart with high/low markers.
|
"""Generate a tide prediction chart with high/low markers.
|
||||||
|
|
||||||
Creates a visual chart of tide predictions showing the water level curve
|
Creates a visual chart of tide predictions showing the water level curve
|
||||||
with high (H) and low (L) tide markers. Optionally overlays observed
|
with high (H) and low (L) tide markers. Optionally overlays observed
|
||||||
water levels as a dashed line for comparison.
|
water levels as a dashed line for comparison.
|
||||||
|
|
||||||
PNG format returns an inline image. HTML format saves an interactive
|
PNG format returns an inline image. HTML format returns an interactive
|
||||||
chart to artifacts/charts/ and returns the file path.
|
Plotly chart as an embedded resource (text/html) that the client can
|
||||||
|
save or render directly.
|
||||||
|
|
||||||
Requires mcnoaa-tides[viz] to be installed.
|
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
|
from mcnoaa_tides.charts.tides import render_tide_chart_html
|
||||||
|
|
||||||
html = render_tide_chart_html(predictions, observed, station_name)
|
html = render_tide_chart_html(predictions, observed, station_name)
|
||||||
path = _save_html(html, station_id, "tides")
|
return _html_resource(html, station_id, "tides")
|
||||||
return f"Interactive tide chart saved to: {path}"
|
|
||||||
|
|
||||||
@mcp.tool(tags={"visualization"}, annotations=_ANNOTATIONS)
|
@mcp.tool(tags={"visualization"}, annotations=_ANNOTATIONS)
|
||||||
async def visualize_conditions(
|
async def visualize_conditions(
|
||||||
@ -106,7 +105,7 @@ def register(mcp: FastMCP) -> None:
|
|||||||
station_id: str,
|
station_id: str,
|
||||||
hours: int = 24,
|
hours: int = 24,
|
||||||
format: Literal["png", "html"] = "png",
|
format: Literal["png", "html"] = "png",
|
||||||
) -> Image | str:
|
) -> Image | EmbeddedResource:
|
||||||
"""Generate a multi-panel marine conditions dashboard.
|
"""Generate a multi-panel marine conditions dashboard.
|
||||||
|
|
||||||
Creates a dashboard with up to 4 panels:
|
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.
|
Products unavailable at a station are simply omitted from the dashboard.
|
||||||
|
|
||||||
PNG format returns an inline image. HTML format saves an interactive
|
PNG format returns an inline image. HTML format returns an interactive
|
||||||
chart to artifacts/charts/ and returns the file path.
|
Plotly dashboard as an embedded resource (text/html) that the client
|
||||||
|
can save or render directly.
|
||||||
|
|
||||||
Requires mcnoaa-tides[viz] to be installed.
|
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
|
from mcnoaa_tides.charts.conditions import render_conditions_html
|
||||||
|
|
||||||
html = render_conditions_html(snapshot, station_name)
|
html = render_conditions_html(snapshot, station_name)
|
||||||
path = _save_html(html, station_id, "conditions")
|
return _html_resource(html, station_id, "conditions")
|
||||||
return f"Interactive conditions dashboard saved to: {path}"
|
|
||||||
|
|
||||||
|
|
||||||
def _save_html(html: str, station_id: str, chart_type: str) -> Path:
|
def _html_resource(html: str, station_id: str, chart_type: str) -> EmbeddedResource:
|
||||||
"""Save HTML chart and return the path.
|
"""Wrap an HTML chart as an MCP EmbeddedResource for inline delivery."""
|
||||||
|
return EmbeddedResource(
|
||||||
Uses $MCNOAA_CHARTS_DIR if set, otherwise falls back to
|
type="resource",
|
||||||
artifacts/charts/ (relative to cwd). The container sets
|
resource=TextResourceContents(
|
||||||
MCNOAA_CHARTS_DIR=/tmp/charts so the nobody user can write.
|
uri=f"chart://{chart_type}/{station_id}",
|
||||||
"""
|
mimeType="text/html",
|
||||||
import os
|
text=html,
|
||||||
|
),
|
||||||
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
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user