SalexAI commited on
Commit
156996b
·
verified ·
1 Parent(s): 1fc9c16

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +28 -54
app.py CHANGED
@@ -4,32 +4,27 @@ 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, Field
8
- from starlette.routing import Mount
9
-
10
  from mcp.server.fastmcp import FastMCP, Context
11
 
12
-
13
- # ============================================================
14
  # In-memory queue
15
- # ============================================================
16
  commands: Dict[str, Dict[str, Any]] = {}
17
  queue: List[str] = []
18
  queue_lock = asyncio.Lock()
19
 
20
-
21
  def now_iso() -> str:
22
  return datetime.now(timezone.utc).isoformat()
23
 
24
-
25
  async def enqueue(command: str, args: Optional[Dict[str, Any]] = None, source: str = "mcp") -> Dict[str, Any]:
26
- cmd_id = str(uuid4())
27
  rec = {
28
- "id": cmd_id,
29
  "command": command,
30
  "args": args or {},
31
  "source": source,
32
- "status": "queued", # queued | running | done | failed
33
  "result": None,
34
  "error": None,
35
  "created_at": now_iso(),
@@ -37,64 +32,48 @@ async def enqueue(command: str, args: Optional[Dict[str, Any]] = None, source: s
37
  "claimed_by": None,
38
  }
39
  async with queue_lock:
40
- commands[cmd_id] = rec
41
- queue.append(cmd_id)
42
  return rec
43
 
44
-
45
- # ============================================================
46
- # MCP server (Streamable HTTP) — better for ChatGPT connector
47
- # ============================================================
48
  mcp = FastMCP(
49
  "Create3 Robot Bridge",
50
- instructions="Queues robot commands for a local Create 3 runner.",
51
- stateless_http=True, # good for hosted environments
52
- json_response=False, # keep MCP transport behavior, not plain JSON API
53
  )
54
 
55
- # Exactly 3 tools
56
  @mcp.tool()
57
  async def dock(ctx: Context) -> dict:
58
- """Queue a dock command for Create 3."""
59
- rec = await enqueue("dock", {}, source="mcp")
60
  return {"ok": True, "queued": rec}
61
 
62
  @mcp.tool()
63
  async def undock(ctx: Context) -> dict:
64
- """Queue an undock command for Create 3."""
65
- rec = await enqueue("undock", {}, source="mcp")
66
  return {"ok": True, "queued": rec}
67
 
68
  @mcp.tool()
69
  async def move_cm(cm: float, ctx: Context) -> dict:
70
- """Queue a move command in centimeters (negative moves backward)."""
71
- rec = await enqueue("move_cm", {"cm": cm}, source="mcp")
72
  return {"ok": True, "queued": rec}
73
 
74
-
75
- # ============================================================
76
- # FastAPI app + queue endpoints for local Create 3 client
77
- # ============================================================
78
- app = FastAPI(title="Create3 MCP + Queue", version="1.0.0")
79
-
80
 
81
  class StatusUpdate(BaseModel):
82
- status: str = Field(..., description="running | done | failed")
83
  result: Optional[Dict[str, Any]] = None
84
  error: Optional[str] = None
85
  robot_id: Optional[str] = None
86
 
87
-
88
  @app.get("/")
89
  async def root():
90
- return {
91
- "ok": True,
92
- "mcp_endpoint": "/mcp",
93
- "health": "/health",
94
- "note": "Use the ChatGPT MCP connector URL ending in /mcp",
95
- "time": now_iso(),
96
- }
97
-
98
 
99
  @app.get("/health")
100
  async def health():
@@ -106,19 +85,16 @@ async def health():
106
  "time": now_iso(),
107
  }
108
 
109
-
110
  @app.get("/commands")
111
- async def list_commands(limit: int = 50):
112
- items = sorted(commands.values(), key=lambda x: x["updated_at"], reverse=True)[: max(1, min(limit, 200))]
113
- return {"ok": True, "items": items}
114
-
115
 
116
  @app.post("/commands/next")
117
  async def claim_next_command(robot_id: str):
118
  async with queue_lock:
119
  while queue:
120
- cmd_id = queue.pop(0)
121
- cmd = commands.get(cmd_id)
122
  if not cmd or cmd["status"] != "queued":
123
  continue
124
  cmd["status"] = "running"
@@ -127,7 +103,6 @@ async def claim_next_command(robot_id: str):
127
  return {"ok": True, "item": cmd}
128
  return {"ok": True, "item": None}
129
 
130
-
131
  @app.post("/commands/{command_id}/status")
132
  async def update_status(command_id: str, update: StatusUpdate):
133
  cmd = commands.get(command_id)
@@ -141,6 +116,5 @@ async def update_status(command_id: str, update: StatusUpdate):
141
  cmd["updated_at"] = now_iso()
142
  return {"ok": True}
143
 
144
-
145
- # Mount MCP at /mcp (Streamable HTTP transport)
146
- app.router.routes.append(Mount("/mcp", app=mcp.streamable_http_app()))
 
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
+ # ---------------------------
 
11
  # In-memory queue
12
+ # ---------------------------
13
  commands: Dict[str, Dict[str, Any]] = {}
14
  queue: List[str] = []
15
  queue_lock = asyncio.Lock()
16
 
 
17
  def now_iso() -> str:
18
  return datetime.now(timezone.utc).isoformat()
19
 
 
20
  async def enqueue(command: str, args: Optional[Dict[str, Any]] = None, source: str = "mcp") -> Dict[str, Any]:
21
+ cid = str(uuid4())
22
  rec = {
23
+ "id": cid,
24
  "command": command,
25
  "args": args or {},
26
  "source": source,
27
+ "status": "queued",
28
  "result": None,
29
  "error": None,
30
  "created_at": now_iso(),
 
32
  "claimed_by": None,
33
  }
34
  async with queue_lock:
35
+ commands[cid] = rec
36
+ queue.append(cid)
37
  return rec
38
 
39
+ # ---------------------------
40
+ # MCP server (3 tools only)
41
+ # ---------------------------
 
42
  mcp = FastMCP(
43
  "Create3 Robot Bridge",
44
+ instructions="Queues commands for a local Create 3 runner.",
45
+ stateless_http=True,
 
46
  )
47
 
 
48
  @mcp.tool()
49
  async def dock(ctx: Context) -> dict:
50
+ rec = await enqueue("dock", {}, "mcp")
 
51
  return {"ok": True, "queued": rec}
52
 
53
  @mcp.tool()
54
  async def undock(ctx: Context) -> dict:
55
+ rec = await enqueue("undock", {}, "mcp")
 
56
  return {"ok": True, "queued": rec}
57
 
58
  @mcp.tool()
59
  async def move_cm(cm: float, ctx: Context) -> dict:
60
+ rec = await enqueue("move_cm", {"cm": cm}, "mcp")
 
61
  return {"ok": True, "queued": rec}
62
 
63
+ # ---------------------------
64
+ # FastAPI app
65
+ # ---------------------------
66
+ app = FastAPI(title="Create3 MCP Bridge")
 
 
67
 
68
  class StatusUpdate(BaseModel):
69
+ status: str
70
  result: Optional[Dict[str, Any]] = None
71
  error: Optional[str] = None
72
  robot_id: Optional[str] = None
73
 
 
74
  @app.get("/")
75
  async def root():
76
+ return {"ok": True, "mcp": "/mcp", "health": "/health", "time": now_iso()}
 
 
 
 
 
 
 
77
 
78
  @app.get("/health")
79
  async def health():
 
85
  "time": now_iso(),
86
  }
87
 
 
88
  @app.get("/commands")
89
+ async def list_commands():
90
+ return {"ok": True, "items": sorted(commands.values(), key=lambda x: x["updated_at"], reverse=True)}
 
 
91
 
92
  @app.post("/commands/next")
93
  async def claim_next_command(robot_id: str):
94
  async with queue_lock:
95
  while queue:
96
+ cid = queue.pop(0)
97
+ cmd = commands.get(cid)
98
  if not cmd or cmd["status"] != "queued":
99
  continue
100
  cmd["status"] = "running"
 
103
  return {"ok": True, "item": cmd}
104
  return {"ok": True, "item": None}
105
 
 
106
  @app.post("/commands/{command_id}/status")
107
  async def update_status(command_id: str, update: StatusUpdate):
108
  cmd = commands.get(command_id)
 
116
  cmd["updated_at"] = now_iso()
117
  return {"ok": True}
118
 
119
+ # Mount MCP at /mcp
120
+ app.mount("/mcp", mcp.streamable_http_app())