server_fastmcp.py 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. """FastMCP transport wrapper for virtuoso tool logic.
  2. This module keeps the existing Virtuoso tool implementations untouched
  3. and exposes them through the MCP SDK transport.
  4. """
  5. from fastapi import FastAPI
  6. from fastapi import HTTPException
  7. from pydantic import BaseModel
  8. from mcp.server.fastmcp import FastMCP
  9. from mcp.server.transport_security import TransportSecuritySettings
  10. from virtuoso_mcp import (
  11. TOOLS,
  12. TOOL_DOCS,
  13. VIRTUOSO_ENDPOINT,
  14. SPARQL_DEFAULT_LIMIT,
  15. SPARQL_MAX_LIMIT,
  16. ALLOW_EXAMPLE_LOAD,
  17. )
  18. mcp = FastMCP(
  19. "virtuoso",
  20. transport_security=TransportSecuritySettings(
  21. enable_dns_rebinding_protection=False
  22. ),
  23. )
  24. def _wrap_tool(tool_name: str):
  25. def _tool(input: dict | None = None):
  26. payload = input or {}
  27. return TOOLS[tool_name](payload)
  28. _tool.__name__ = f"tool_{tool_name}"
  29. _tool.__doc__ = TOOL_DOCS.get(tool_name, "")
  30. return _tool
  31. for _name in sorted(TOOLS.keys()):
  32. mcp.add_tool(
  33. _wrap_tool(_name),
  34. name=_name,
  35. description=TOOL_DOCS.get(_name, ""),
  36. )
  37. app = FastAPI(title="Virtuoso MCP Server")
  38. app.mount("/mcp", mcp.sse_app())
  39. class LegacyToolRequest(BaseModel):
  40. tool: str
  41. input: dict = {}
  42. @app.post("/rpc")
  43. def legacy_rpc(req: LegacyToolRequest):
  44. if req.tool not in TOOLS:
  45. raise HTTPException(status_code=400, detail=f"Unknown tool: {req.tool}")
  46. try:
  47. result = TOOLS[req.tool](req.input or {})
  48. return {
  49. "status": "ok",
  50. "tool": req.tool,
  51. "description": TOOL_DOCS.get(req.tool, ""),
  52. "result": result,
  53. }
  54. except HTTPException:
  55. raise
  56. except Exception as exc:
  57. raise HTTPException(status_code=500, detail=str(exc))
  58. @app.get("/")
  59. def root():
  60. return {
  61. "status": "MCP server running",
  62. "transport": "fastmcp+sse",
  63. "mount": "/mcp",
  64. "tools": sorted(TOOLS.keys()),
  65. "virtuoso": VIRTUOSO_ENDPOINT,
  66. "guardrails": {
  67. "default_limit": SPARQL_DEFAULT_LIMIT,
  68. "max_limit": SPARQL_MAX_LIMIT,
  69. "allow_example_load": ALLOW_EXAMPLE_LOAD,
  70. },
  71. }
  72. @app.get("/health")
  73. def health():
  74. return root()