From 0b2eb713d2bdcd2625406a819a71051fc61e22b6 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Sat, 28 Feb 2026 17:14:16 +0100 Subject: [PATCH 1/3] Fix Smithery registration: handle HEAD /mcp/ and add MCP Server Card Smithery's scanner probes with HEAD requests which the mounted FastMCP app rejects with 405, causing "Initialization failed with status 502". Add explicit HEAD /mcp/ handler, MCP Server Card endpoint (/.well-known/mcp/server-card.json per SEP-1649), and include HEAD in CORS allow_methods. Co-Authored-By: Claude Opus 4.6 --- mcp_cloud/http_server.py | 59 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/mcp_cloud/http_server.py b/mcp_cloud/http_server.py index 43232ed3..25ea7bb1 100644 --- a/mcp_cloud/http_server.py +++ b/mcp_cloud/http_server.py @@ -195,7 +195,7 @@ def _append_cors_headers(request: Request, response: Response) -> Response: headers = response.headers headers.setdefault("Access-Control-Allow-Origin", allow_origin) - headers.setdefault("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + headers.setdefault("Access-Control-Allow-Methods", "GET, HEAD, POST, OPTIONS") request_headers = request.headers.get("access-control-request-headers") if request_headers: @@ -778,7 +778,7 @@ async def _lifespan(app: FastAPI): CORSMiddleware, allow_origins=CORS_ORIGINS, allow_credentials=False, - allow_methods=["GET", "POST", "OPTIONS"], + allow_methods=["GET", "HEAD", "POST", "OPTIONS"], allow_headers=["*"], # Allow any header (e.g. X-API-Key) for CORS preflight ) @@ -904,6 +904,20 @@ async def options_mcp() -> Response: return Response(status_code=200) +@app.head("/mcp/") +async def head_mcp_trailing_slash() -> Response: + """Handle HEAD /mcp/ for health-check probes (e.g. Smithery scanner). + + The mounted FastMCP Streamable HTTP app does not support HEAD and returns + 405. This explicit route intercepts the request so scanners get a clean + 200 instead of bouncing off the sub-app. + """ + return Response( + status_code=200, + headers={"Content-Type": "application/json"}, + ) + + @app.api_route("/mcp", methods=["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"]) async def redirect_mcp_no_trailing_slash() -> RedirectResponse: """Normalize '/mcp' to '/mcp/' so streamable HTTP requests avoid 405 mismatches.""" @@ -1022,6 +1036,7 @@ def root() -> dict[str, Any]: "tools": "/mcp/tools", "call": "/mcp/tools/call", "health": "/healthcheck", + "mcp_server_card": "/.well-known/mcp/server-card.json", "glama_connector": "/.well-known/glama.json", "download": f"/download/{{plan_id}}/{REPORT_FILENAME}", "llms_txt": "/llms.txt", @@ -1046,6 +1061,46 @@ def _llms_txt_path() -> str: return path +@app.get("/.well-known/mcp/server-card.json") +def mcp_server_card() -> dict[str, Any]: + """Serve MCP Server Card for discovery (SEP-1649). + + This allows registries like Smithery to discover the server's capabilities + without performing a full MCP handshake. + """ + return { + "$schema": "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json", + "version": "1.0", + "serverInfo": { + "name": "planexe-mcp-server", + "title": "PlanExe – AI Project Planning", + "version": "1.0.0", + }, + "description": ( + "MCP server that generates strategic project-plan drafts from a " + "natural-language prompt. Output is a self-contained interactive " + "HTML report with 20+ sections including executive summary, " + "interactive Gantt charts, risk analysis, SWOT, governance, " + "investor pitch, and adversarial stress-test sections." + ), + "documentationUrl": "https://github.com/PlanExeOrg/PlanExe", + "transport": { + "type": "streamable-http", + "endpoint": "/mcp", + }, + "capabilities": { + "tools": {}, + }, + "authentication": { + "required": AUTH_REQUIRED, + "schemes": ["bearer"], + }, + "tools": ["dynamic"], + "prompts": ["dynamic"], + "resources": ["dynamic"], + } + + @app.get("/.well-known/glama.json") def glama_connector_metadata() -> dict[str, Any]: """Serve Glama connector ownership metadata.""" From df2b37f3732775f6749e2904bec8c65165ffac01 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Sat, 28 Feb 2026 17:16:55 +0100 Subject: [PATCH 2/3] Document discovery/.well-known endpoints in mcp_cloud README Co-Authored-By: Claude Opus 4.6 --- mcp_cloud/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mcp_cloud/README.md b/mcp_cloud/README.md index 051ab7fd..1928d886 100644 --- a/mcp_cloud/README.md +++ b/mcp_cloud/README.md @@ -86,6 +86,13 @@ Use a UserApiKey from [home.planexe.org](https://home.planexe.org/), or set `PLA - `GET /docs` - OpenAPI documentation (Swagger UI) - `GET /robots.txt` - Crawler rules for public metadata discovery +### Discovery / `.well-known` Endpoints + +The `/.well-known/` prefix is an [IETF standard (RFC 8615)](https://www.rfc-editor.org/rfc/rfc8615) for machine-readable metadata. Automated systems (registries, crawlers, AI agents) fetch these to discover what the server offers without performing a full handshake. + +- **`GET /.well-known/mcp/server-card.json`** — MCP Server Card ([SEP-1649](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1649)). Lets MCP registries (Smithery, etc.) discover the server's name, description, transport type, capabilities, and auth requirements in a single JSON fetch — no MCP handshake needed. +- **`GET /.well-known/glama.json`** — Glama ownership verification. When registering at [glama.ai](https://glama.ai), their crawler fetches this to confirm the server maintainer (contains a maintainer email). + ### "SSE error" or "no Server-SSE stream" from the client Some MCP clients (e.g. OpenClaw/mcporter) connect by doing a **GET** to the server URL and expect a **Server-Sent Events (SSE)** stream (`Content-Type: text/event-stream`). That is the **Streamable HTTP** transport. This server mounts FastMCP at `/mcp`; **GET /mcp** returns a **307 redirect** to `/mcp/`, and the Streamable HTTP handshake may not match what the client expects, so the client reports "SSE error" or "could not fetch … no SSE stream". From a17e2b6d523f7f417105db0ef49a956ae9d02adb Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Sat, 28 Feb 2026 17:22:54 +0100 Subject: [PATCH 3/3] Fix documentationUrl in MCP Server Card to point to docs.planexe.org Co-Authored-By: Claude Opus 4.6 --- mcp_cloud/http_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp_cloud/http_server.py b/mcp_cloud/http_server.py index 25ea7bb1..3ee22055 100644 --- a/mcp_cloud/http_server.py +++ b/mcp_cloud/http_server.py @@ -1083,7 +1083,7 @@ def mcp_server_card() -> dict[str, Any]: "interactive Gantt charts, risk analysis, SWOT, governance, " "investor pitch, and adversarial stress-test sections." ), - "documentationUrl": "https://github.com/PlanExeOrg/PlanExe", + "documentationUrl": "https://docs.planexe.org/", "transport": { "type": "streamable-http", "endpoint": "/mcp",