|
|
import os |
|
|
import asyncio |
|
|
import sys |
|
|
import json |
|
|
from fastapi import FastAPI, Request |
|
|
from sse_starlette.sse import EventSourceResponse |
|
|
from fastapi.responses import JSONResponse |
|
|
|
|
|
app = FastAPI() |
|
|
|
|
|
|
|
|
|
|
|
ENV_VARS = os.environ.copy() |
|
|
|
|
|
|
|
|
|
|
|
MCP_COMMAND = ["python", "-m", "paddleocr_mcp"] |
|
|
|
|
|
|
|
|
|
|
|
active_processes = {} |
|
|
|
|
|
async def run_mcp_session(request: Request): |
|
|
""" |
|
|
为每个连接启动一个独立的 paddleocr-mcp 子进程 |
|
|
并将其输出流式传输回客户端 (SSE) |
|
|
""" |
|
|
process = await asyncio.create_subprocess_exec( |
|
|
*MCP_COMMAND, |
|
|
stdin=asyncio.subprocess.PIPE, |
|
|
stdout=asyncio.subprocess.PIPE, |
|
|
stderr=sys.stderr, |
|
|
env=ENV_VARS |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
global active_processes |
|
|
active_processes["default"] = process |
|
|
|
|
|
async def event_generator(): |
|
|
try: |
|
|
while True: |
|
|
if await request.is_disconnected(): |
|
|
break |
|
|
|
|
|
|
|
|
line = await process.stdout.readline() |
|
|
if not line: |
|
|
break |
|
|
|
|
|
|
|
|
data = line.decode().strip() |
|
|
if data: |
|
|
yield { |
|
|
"event": "message", |
|
|
"data": data |
|
|
} |
|
|
except Exception as e: |
|
|
print(f"Error in event stream: {e}") |
|
|
finally: |
|
|
|
|
|
if process.returncode is None: |
|
|
process.terminate() |
|
|
await process.wait() |
|
|
|
|
|
return EventSourceResponse(event_generator()) |
|
|
|
|
|
@app.get("/sse") |
|
|
async def handle_sse(request: Request): |
|
|
return await run_mcp_session(request) |
|
|
|
|
|
@app.post("/messages") |
|
|
async def handle_messages(request: Request): |
|
|
""" |
|
|
接收 Claude 发来的指令 (JSON-RPC),写入子进程的 stdin |
|
|
""" |
|
|
if "default" not in active_processes or active_processes["default"].returncode is not None: |
|
|
return JSONResponse(status_code=400, content={"error": "No active MCP session"}) |
|
|
|
|
|
process = active_processes["default"] |
|
|
|
|
|
try: |
|
|
body = await request.json() |
|
|
|
|
|
message_str = json.dumps(body) + "\n" |
|
|
process.stdin.write(message_str.encode()) |
|
|
await process.stdin.drain() |
|
|
return JSONResponse(content={"status": "accepted"}) |
|
|
except Exception as e: |
|
|
return JSONResponse(status_code=500, content={"error": str(e)}) |
|
|
|
|
|
@app.get("/") |
|
|
def health_check(): |
|
|
return {"status": "paddleocr-mcp-server is running"} |