Spaces:
Sleeping
Sleeping
File size: 3,498 Bytes
8033e09 cd032a4 ece12b4 8033e09 ece12b4 156996b 8033e09 156996b 1fc9c16 156996b 8033e09 cd032a4 8033e09 cd032a4 ece12b4 156996b 1fc9c16 156996b 8033e09 156996b cd032a4 156996b 1fc9c16 8033e09 156996b 8033e09 1fc9c16 156996b 8033e09 ece12b4 156996b 8033e09 ece12b4 156996b 8033e09 ece12b4 156996b 8033e09 156996b 8033e09 ece12b4 156996b ece12b4 8033e09 156996b 8033e09 ece12b4 8033e09 cd032a4 156996b 8033e09 cd032a4 ece12b4 cd032a4 156996b 1fc9c16 eb8307e cd032a4 ece12b4 cd032a4 156996b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | import asyncio
from uuid import uuid4
from datetime import datetime, timezone
from typing import Dict, Any, Optional, List
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from mcp.server.fastmcp import FastMCP, Context
# ---------------------------
# In-memory queue
# ---------------------------
commands: Dict[str, Dict[str, Any]] = {}
queue: List[str] = []
queue_lock = asyncio.Lock()
def now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
async def enqueue(command: str, args: Optional[Dict[str, Any]] = None, source: str = "mcp") -> Dict[str, Any]:
cid = str(uuid4())
rec = {
"id": cid,
"command": command,
"args": args or {},
"source": source,
"status": "queued",
"result": None,
"error": None,
"created_at": now_iso(),
"updated_at": now_iso(),
"claimed_by": None,
}
async with queue_lock:
commands[cid] = rec
queue.append(cid)
return rec
# ---------------------------
# MCP server (3 tools only)
# ---------------------------
mcp = FastMCP(
"Create3 Robot Bridge",
instructions="Queues commands for a local Create 3 runner.",
stateless_http=True,
)
@mcp.tool()
async def dock(ctx: Context) -> dict:
rec = await enqueue("dock", {}, "mcp")
return {"ok": True, "queued": rec}
@mcp.tool()
async def undock(ctx: Context) -> dict:
rec = await enqueue("undock", {}, "mcp")
return {"ok": True, "queued": rec}
@mcp.tool()
async def move_cm(cm: float, ctx: Context) -> dict:
rec = await enqueue("move_cm", {"cm": cm}, "mcp")
return {"ok": True, "queued": rec}
# ---------------------------
# FastAPI app
# ---------------------------
app = FastAPI(title="Create3 MCP Bridge")
class StatusUpdate(BaseModel):
status: str
result: Optional[Dict[str, Any]] = None
error: Optional[str] = None
robot_id: Optional[str] = None
@app.get("/")
async def root():
return {"ok": True, "mcp": "/mcp", "health": "/health", "time": now_iso()}
@app.get("/health")
async def health():
return {
"ok": True,
"queued": sum(1 for c in commands.values() if c["status"] == "queued"),
"running": sum(1 for c in commands.values() if c["status"] == "running"),
"total": len(commands),
"time": now_iso(),
}
@app.get("/commands")
async def list_commands():
return {"ok": True, "items": sorted(commands.values(), key=lambda x: x["updated_at"], reverse=True)}
@app.post("/commands/next")
async def claim_next_command(robot_id: str):
async with queue_lock:
while queue:
cid = queue.pop(0)
cmd = commands.get(cid)
if not cmd or cmd["status"] != "queued":
continue
cmd["status"] = "running"
cmd["claimed_by"] = robot_id
cmd["updated_at"] = now_iso()
return {"ok": True, "item": cmd}
return {"ok": True, "item": None}
@app.post("/commands/{command_id}/status")
async def update_status(command_id: str, update: StatusUpdate):
cmd = commands.get(command_id)
if not cmd:
raise HTTPException(status_code=404, detail="Command not found")
cmd["status"] = update.status
cmd["result"] = update.result
cmd["error"] = update.error
if update.robot_id:
cmd["claimed_by"] = update.robot_id
cmd["updated_at"] = now_iso()
return {"ok": True}
# Mount MCP at /mcp
app.mount("/mcp", mcp.streamable_http_app()) |