""" HTTP entry point for the fabric-config-oracle MCP server. This module exposes the same FastMCP server as `main.py`, but uses the **streamable HTTP** transport instead of stdio. It is designed to run behind a uvicorn ASGI server so the MCP can be hosted on platforms like Hugging Face Spaces, Fly.io, Railway, or any container host that exposes a public HTTP port. Run locally: uvicorn app:asgi_app --host 0.0.0.0 --port 7860 Or directly: python app.py Hugging Face Spaces: The Dockerfile in this repo starts `python app.py`. The Space's public URL (e.g. `https://.hf.space/mcp`) is the MCP endpoint clients configure in their `mcp.json`. Security note: The server is **fully open / public** by design. There is no bearer token, no OAuth, no DNS-rebinding protection. Anyone with the URL can call the tools. This is acceptable for a read-only oracle that only queries public upstream APIs (Mojang / Fabric Meta / Modrinth). """ from __future__ import annotations import logging import os import sys # Make sibling modules importable when running as `python app.py`. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from mcp.server.fastmcp import FastMCP # noqa: E402 from mcp.server.transport_security import TransportSecuritySettings # noqa: E402 from mcp_server import mcp as _configured_mcp # noqa: E402 from mcp_server import get_server # noqa: E402 logger = logging.getLogger("fabric-config-oracle.http") def build_http_app() -> "FastMCP": """ Return a FastMCP instance configured for public HTTP hosting. We rebuild the FastMCP server (rather than mutating the singleton from `mcp_server.py`) so the stdio entry point in `main.py` keeps its conservative localhost-only defaults. The HTTP variant: * binds to 0.0.0.0 (required by HF Spaces, Fly.io, Docker) * listens on port 7860 (HF Spaces convention) * exposes the MCP endpoint at `/mcp` (FastMCP default) * **disables DNS rebinding protection** — public hosts have arbitrary Host headers by design. * enables stateless HTTP mode so any client can POST a single JSON-RPC request without first opening a long-lived session. This is the friendliest mode for public MCP endpoints and matches the official `streamable-http` examples. * enables `json_response=True` so responses are returned as Content-Type: application/json rather than text/event-stream. Both are spec-compliant; JSON is simpler for clients that don't want to parse SSE. """ server = get_server() # Re-apply settings that matter for public hosting. We mutate the # existing Settings object so tools/resources registered on the # module-level `mcp` singleton keep working. server.settings.host = os.environ.get("FASTMCP_HOST", "0.0.0.0") server.settings.port = int(os.environ.get("FASTMCP_PORT", "7860")) server.settings.streamable_http_path = "/mcp" server.settings.stateless_http = True server.settings.json_response = True server.settings.transport_security = TransportSecuritySettings( enable_dns_rebinding_protection=False, allowed_hosts=[], allowed_origins=[], ) return server # Module-level ASGI app for `uvicorn app:asgi_app` invocation. _http_server = build_http_app() asgi_app = _http_server.streamable_http_app() def main() -> None: """Start the fabric-config-oracle MCP server over streamable HTTP.""" logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", stream=sys.stderr, ) host = _http_server.settings.host port = _http_server.settings.port logger.info( "Starting fabric-config-oracle MCP server (streamable HTTP) " "on http://%s:%s/mcp", host, port, ) import uvicorn uvicorn.run( asgi_app, host=host, port=port, log_level="info", access_log=False, # MCP traffic is verbose; suppress per-request logs. ) if __name__ == "__main__": main()