Spaces:
Sleeping
Sleeping
Compliance: Hardened MCP SSE stream with manual CORS and relative paths
Browse files- backend/main.py +14 -10
backend/main.py
CHANGED
|
@@ -77,32 +77,36 @@ async def process_synthesis(text: str, key: str):
|
|
| 77 |
a = agent_action(record, t, k=key)
|
| 78 |
return {"triage": t, "action": a}
|
| 79 |
|
| 80 |
-
# ββ MCP Standard (Optimized Discovery) ββββββββββββββββββββββββββ
|
| 81 |
|
| 82 |
@app.api_route("/mcp", methods=["GET", "POST", "OPTIONS"])
|
| 83 |
async def mcp_discovery(req: Request):
|
| 84 |
-
"""Initial Discovery (Supports both GET/POST for platform validation)"""
|
| 85 |
if req.method == "OPTIONS": return Response(status_code=204)
|
|
|
|
| 86 |
return {
|
| 87 |
"transport": "sse",
|
| 88 |
-
"endpoint": "
|
| 89 |
-
"tools": [{"name": "analyze_clinical_record", "description": "Synthesize records."}]
|
| 90 |
}
|
| 91 |
|
| 92 |
@app.get("/mcp/sse")
|
| 93 |
async def mcp_sse_stream(req: Request):
|
| 94 |
-
"""
|
| 95 |
async def event_generator():
|
| 96 |
-
#
|
| 97 |
-
yield f"event: endpoint\ndata:
|
| 98 |
while True:
|
| 99 |
-
await asyncio.sleep(
|
| 100 |
yield ": keep-alive\n\n"
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
@app.post("/mcp/invoke")
|
| 104 |
async def mcp_invoke(req: Request, x_gemini_key: str = Header(None)):
|
| 105 |
-
"""JSON-RPC Invocation"""
|
| 106 |
try: body = await req.json()
|
| 107 |
except: return {"error": "Invalid JSON"}
|
| 108 |
|
|
|
|
| 77 |
a = agent_action(record, t, k=key)
|
| 78 |
return {"triage": t, "action": a}
|
| 79 |
|
| 80 |
+
# ββ MCP Standard (Optimized Discovery & Handshake) ββββββββββββββββββββββββββ
|
| 81 |
|
| 82 |
@app.api_route("/mcp", methods=["GET", "POST", "OPTIONS"])
|
| 83 |
async def mcp_discovery(req: Request):
|
|
|
|
| 84 |
if req.method == "OPTIONS": return Response(status_code=204)
|
| 85 |
+
# Discovery should point to a relative path for the SSE stream
|
| 86 |
return {
|
| 87 |
"transport": "sse",
|
| 88 |
+
"endpoint": "/mcp/sse",
|
| 89 |
+
"tools": [{"name": "analyze_clinical_record", "description": "Synthesize clinical records."}]
|
| 90 |
}
|
| 91 |
|
| 92 |
@app.get("/mcp/sse")
|
| 93 |
async def mcp_sse_stream(req: Request):
|
| 94 |
+
"""Handshake with manual CORS headers for SSE"""
|
| 95 |
async def event_generator():
|
| 96 |
+
# Immediate endpoint event with relative path
|
| 97 |
+
yield f"event: endpoint\ndata: /mcp/invoke\n\n"
|
| 98 |
while True:
|
| 99 |
+
await asyncio.sleep(20)
|
| 100 |
yield ": keep-alive\n\n"
|
| 101 |
+
|
| 102 |
+
response = StreamingResponse(event_generator(), media_type="text/event-stream")
|
| 103 |
+
response.headers["Access-Control-Allow-Origin"] = "*"
|
| 104 |
+
response.headers["Cache-Control"] = "no-cache"
|
| 105 |
+
response.headers["Connection"] = "keep-alive"
|
| 106 |
+
return response
|
| 107 |
|
| 108 |
@app.post("/mcp/invoke")
|
| 109 |
async def mcp_invoke(req: Request, x_gemini_key: str = Header(None)):
|
|
|
|
| 110 |
try: body = await req.json()
|
| 111 |
except: return {"error": "Invalid JSON"}
|
| 112 |
|