AlexTransformer commited on
Commit
bfdead1
·
verified ·
1 Parent(s): 68dd217

Create server.py

Browse files
Files changed (1) hide show
  1. server.py +98 -0
server.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ import sys
4
+ import json
5
+ from fastapi import FastAPI, Request
6
+ from sse_starlette.sse import EventSourceResponse
7
+ from fastapi.responses import JSONResponse
8
+
9
+ app = FastAPI()
10
+
11
+ # 获取环境变量(在 Space Settings 中设置的那些)
12
+ # 注意:我们直接从环境读取,不需要在这里硬编码 Token
13
+ ENV_VARS = os.environ.copy()
14
+
15
+ # 设置 MCP 进程的运行命令
16
+ # 对应原本的: uvx --from paddleocr-mcp paddleocr_mcp
17
+ MCP_COMMAND = ["python", "-m", "paddleocr_mcp"]
18
+
19
+ # 存储活跃的进程 (简单的会话管理)
20
+ # 注意:这是一个简化的实现,适合单人或小团队使用
21
+ active_processes = {}
22
+
23
+ async def run_mcp_session(request: Request):
24
+ """
25
+ 为每个连接启动一个独立的 paddleocr-mcp 子进程
26
+ 并将其输出流式传输回客户端 (SSE)
27
+ """
28
+ process = await asyncio.create_subprocess_exec(
29
+ *MCP_COMMAND,
30
+ stdin=asyncio.subprocess.PIPE,
31
+ stdout=asyncio.subprocess.PIPE,
32
+ stderr=sys.stderr, # 将错误日志打印到控制台
33
+ env=ENV_VARS
34
+ )
35
+
36
+ # 使用 client_id 或简单的标记来存储进程,以便 POST 请求能找到它
37
+ # 在这个简单版本中,我们假设主要通过 SSE 接收输出,
38
+ # 实际的 MCP SSE 协议通常需要 session_id,这里为了简化演示通用性:
39
+ # 我们将最近的一个进程存为全局,这在单用户场景下是可行的。
40
+ # ⚠️ 生产环境建议使用更复杂的 Session 管理。
41
+ global active_processes
42
+ active_processes["default"] = process
43
+
44
+ async def event_generator():
45
+ try:
46
+ while True:
47
+ if await request.is_disconnected():
48
+ break
49
+
50
+ # 读取子进程的一行输出
51
+ line = await process.stdout.readline()
52
+ if not line:
53
+ break
54
+
55
+ # 解码并发送 SSE 事件
56
+ data = line.decode().strip()
57
+ if data:
58
+ yield {
59
+ "event": "message",
60
+ "data": data
61
+ }
62
+ except Exception as e:
63
+ print(f"Error in event stream: {e}")
64
+ finally:
65
+ # 清理进程
66
+ if process.returncode is None:
67
+ process.terminate()
68
+ await process.wait()
69
+
70
+ return EventSourceResponse(event_generator())
71
+
72
+ @app.get("/sse")
73
+ async def handle_sse(request: Request):
74
+ return await run_mcp_session(request)
75
+
76
+ @app.post("/messages")
77
+ async def handle_messages(request: Request):
78
+ """
79
+ 接收 Claude 发来的指令 (JSON-RPC),写入子进程的 stdin
80
+ """
81
+ if "default" not in active_processes or active_processes["default"].returncode is not None:
82
+ return JSONResponse(status_code=400, content={"error": "No active MCP session"})
83
+
84
+ process = active_processes["default"]
85
+
86
+ try:
87
+ body = await request.json()
88
+ # 将 JSON 转为字符串并写入子进程,必须加换行符
89
+ message_str = json.dumps(body) + "\n"
90
+ process.stdin.write(message_str.encode())
91
+ await process.stdin.drain()
92
+ return JSONResponse(content={"status": "accepted"})
93
+ except Exception as e:
94
+ return JSONResponse(status_code=500, content={"error": str(e)})
95
+
96
+ @app.get("/")
97
+ def health_check():
98
+ return {"status": "paddleocr-mcp-server is running"}