| """ |
| 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://<space-name>.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 |
|
|
| |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
|
|
| from mcp.server.fastmcp import FastMCP |
| from mcp.server.transport_security import TransportSecuritySettings |
|
|
| from mcp_server import mcp as _configured_mcp |
| from mcp_server import get_server |
|
|
|
|
| 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() |
| |
| |
| |
| 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 |
|
|
|
|
| |
| _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, |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|