SalexAI commited on
Commit
cf66711
·
verified ·
1 Parent(s): 6956c0d

Create Server.py

Browse files
Files changed (1) hide show
  1. Server.py +100 -0
Server.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from uuid import uuid4
3
+ from datetime import datetime, timezone
4
+ from typing import Dict, Any, Optional, List
5
+
6
+ from fastapi import FastAPI, HTTPException
7
+ from pydantic import BaseModel
8
+ from mcp.server.fastmcp import FastMCP, Context
9
+
10
+ commands: Dict[str, Dict[str, Any]] = {}
11
+ queue: List[str] = []
12
+ queue_lock = asyncio.Lock()
13
+
14
+ def now_iso():
15
+ return datetime.now(timezone.utc).isoformat()
16
+
17
+ async def enqueue(command: str, args: Optional[Dict[str, Any]] = None):
18
+ cid = str(uuid4())
19
+ rec = {
20
+ "id": cid,
21
+ "command": command,
22
+ "args": args or {},
23
+ "status": "queued",
24
+ "result": None,
25
+ "error": None,
26
+ "created_at": now_iso(),
27
+ "updated_at": now_iso(),
28
+ "claimed_by": None,
29
+ }
30
+ async with queue_lock:
31
+ commands[cid] = rec
32
+ queue.append(cid)
33
+ return rec
34
+
35
+ mcp = FastMCP(
36
+ "Create3 Robot Bridge",
37
+ instructions="Queues robot commands for a local Create 3 runner.",
38
+ stateless_http=True,
39
+ )
40
+
41
+ @mcp.tool()
42
+ async def dock(ctx: Context) -> dict:
43
+ return {"ok": True, "queued": await enqueue("dock", {})}
44
+
45
+ @mcp.tool()
46
+ async def undock(ctx: Context) -> dict:
47
+ return {"ok": True, "queued": await enqueue("undock", {})}
48
+
49
+ @mcp.tool()
50
+ async def move_cm(cm: float, ctx: Context) -> dict:
51
+ return {"ok": True, "queued": await enqueue("move_cm", {"cm": cm})}
52
+
53
+ app = FastAPI()
54
+
55
+ class StatusUpdate(BaseModel):
56
+ status: str
57
+ result: Optional[Dict[str, Any]] = None
58
+ error: Optional[str] = None
59
+ robot_id: Optional[str] = None
60
+
61
+ @app.get("/")
62
+ async def root():
63
+ return {"ok": True, "mcp": "/mcp", "health": "/health"}
64
+
65
+ @app.get("/health")
66
+ async def health():
67
+ return {"ok": True, "time": now_iso()}
68
+
69
+ @app.get("/commands")
70
+ async def list_commands():
71
+ return {"ok": True, "items": list(commands.values())}
72
+
73
+ @app.post("/commands/next")
74
+ async def claim_next_command(robot_id: str):
75
+ async with queue_lock:
76
+ while queue:
77
+ cid = queue.pop(0)
78
+ cmd = commands.get(cid)
79
+ if not cmd or cmd["status"] != "queued":
80
+ continue
81
+ cmd["status"] = "running"
82
+ cmd["claimed_by"] = robot_id
83
+ cmd["updated_at"] = now_iso()
84
+ return {"ok": True, "item": cmd}
85
+ return {"ok": True, "item": None}
86
+
87
+ @app.post("/commands/{command_id}/status")
88
+ async def update_status(command_id: str, update: StatusUpdate):
89
+ cmd = commands.get(command_id)
90
+ if not cmd:
91
+ raise HTTPException(status_code=404, detail="Command not found")
92
+ cmd["status"] = update.status
93
+ cmd["result"] = update.result
94
+ cmd["error"] = update.error
95
+ if update.robot_id:
96
+ cmd["claimed_by"] = update.robot_id
97
+ cmd["updated_at"] = now_iso()
98
+ return {"ok": True}
99
+
100
+ app.mount("/mcp", mcp.streamable_http_app())