Add independent MCP PDF report delivery
This commit is contained in:
+90
-4
@@ -1,15 +1,19 @@
|
||||
"""RollCalc's minimal stdio MCP server."""
|
||||
"""RollCalc MCP server with stdio and Streamable HTTP transports."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
from mcp_pdf_delivery import McpReportNotFoundError, McpReportStore
|
||||
from rollcalc_mcp_tools import (
|
||||
analyze_transport_capacity_result,
|
||||
calculate_machine_max_product_length_result,
|
||||
calculate_material_weight_result,
|
||||
calculate_product_length_result,
|
||||
calculate_roll_diameter_result,
|
||||
generate_calculation_pdf_result,
|
||||
get_article_result,
|
||||
get_machine_result,
|
||||
check_production_feasibility_result,
|
||||
@@ -18,11 +22,49 @@ from rollcalc_mcp_tools import (
|
||||
from transport_calculation import TransportPresetKey
|
||||
|
||||
|
||||
async def download_report(request):
|
||||
"""Serve one MCP-host-local report by opaque temporary capability."""
|
||||
from starlette.responses import PlainTextResponse, Response
|
||||
|
||||
token = request.path_params["token"]
|
||||
try:
|
||||
pdf, report = McpReportStore().read(token)
|
||||
except McpReportNotFoundError:
|
||||
return PlainTextResponse("Report not found or expired", status_code=404)
|
||||
return Response(
|
||||
pdf,
|
||||
media_type="application/pdf",
|
||||
headers={
|
||||
"Content-Disposition": (
|
||||
f'attachment; filename="{quote(report.filename)}"'
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def selected_transport() -> str:
|
||||
"""Read and validate the transport selected for this MCP process."""
|
||||
transport = os.getenv("ROLLCALC_MCP_TRANSPORT", "stdio").strip().lower()
|
||||
if transport not in {"stdio", "streamable-http"}:
|
||||
raise ValueError(
|
||||
"ROLLCALC_MCP_TRANSPORT must be 'stdio' or 'streamable-http'"
|
||||
)
|
||||
return transport
|
||||
|
||||
|
||||
def create_server() -> Any:
|
||||
"""Create the stdio-only MCP server after the SDK is installed."""
|
||||
"""Create the transport-neutral RollCalc MCP application."""
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
server = FastMCP("RollCalc")
|
||||
server = FastMCP(
|
||||
"RollCalc",
|
||||
host=os.getenv("ROLLCALC_MCP_HOST", "127.0.0.1"),
|
||||
port=int(os.getenv("ROLLCALC_MCP_PORT", "8000")),
|
||||
)
|
||||
|
||||
@server.custom_route("/reports/{token}.pdf", methods=["GET"])
|
||||
async def mcp_report_download_route(request):
|
||||
return await download_report(request)
|
||||
|
||||
@server.tool()
|
||||
def get_article(
|
||||
@@ -145,6 +187,45 @@ def create_server() -> Any:
|
||||
production_site=production_site,
|
||||
)
|
||||
|
||||
@server.tool()
|
||||
def generate_calculation_pdf(
|
||||
roll_length_m: float | None = None,
|
||||
core_diameter_mm: float | None = None,
|
||||
thickness_mm: float | None = None,
|
||||
thickness_stddev_mm: float | None = None,
|
||||
article_number: str | None = None,
|
||||
article_name_hint: str | None = None,
|
||||
core_type: str | None = None,
|
||||
width_m: float | None = None,
|
||||
area_weight_g_m2: float | None = None,
|
||||
category: str | None = None,
|
||||
production_site: str | None = None,
|
||||
include_roll_weight: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""Create a PDF calculation report only when the user explicitly requests a PDF or report. Supply the original inputs from a completed calculate_roll_diameter request; never supply, infer, or copy calculated diameters, weights, warnings, or notes. The tool recalculates authoritatively and returns a short-lived browser URL hosted by the MCP service. Do not call this tool after ordinary calculations and do not offer it proactively."""
|
||||
if selected_transport() != "streamable-http":
|
||||
return {
|
||||
"status": "artifact_delivery_unavailable",
|
||||
"error": (
|
||||
"generate_calculation_pdf requires "
|
||||
"ROLLCALC_MCP_TRANSPORT=streamable-http"
|
||||
),
|
||||
}
|
||||
return generate_calculation_pdf_result(
|
||||
article_number=article_number,
|
||||
article_name_hint=article_name_hint,
|
||||
roll_length_m=roll_length_m,
|
||||
width_m=width_m,
|
||||
thickness_mm=thickness_mm,
|
||||
thickness_stddev_mm=thickness_stddev_mm,
|
||||
area_weight_g_m2=area_weight_g_m2,
|
||||
core_diameter_mm=core_diameter_mm,
|
||||
core_type=core_type,
|
||||
category=category,
|
||||
production_site=production_site,
|
||||
include_roll_weight=include_roll_weight,
|
||||
)
|
||||
|
||||
@server.tool()
|
||||
def analyze_transport_capacity(
|
||||
transport_preset: TransportPresetKey,
|
||||
@@ -167,5 +248,10 @@ def create_server() -> Any:
|
||||
return server
|
||||
|
||||
|
||||
def run_server() -> None:
|
||||
"""Run the selected FastMCP transport without changing tool definitions."""
|
||||
create_server().run(transport=selected_transport())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_server().run(transport="stdio")
|
||||
run_server()
|
||||
|
||||
Reference in New Issue
Block a user