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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +24 -38
app.py CHANGED
@@ -5,12 +5,13 @@ from typing import Dict, Any, Optional, List
5
 
6
  from fastapi import FastAPI, HTTPException
7
  from pydantic import BaseModel, Field
 
8
 
9
  from mcp.server.fastmcp import FastMCP, Context
10
 
11
 
12
  # ============================================================
13
- # In-memory command queue (local dev)
14
  # ============================================================
15
  commands: Dict[str, Dict[str, Any]] = {}
16
  queue: List[str] = []
@@ -23,12 +24,12 @@ def now_iso() -> str:
23
 
24
  async def enqueue(command: str, args: Optional[Dict[str, Any]] = None, source: str = "mcp") -> Dict[str, Any]:
25
  cmd_id = str(uuid4())
26
- record = {
27
  "id": cmd_id,
28
  "command": command,
29
  "args": args or {},
30
  "source": source,
31
- "status": "queued", # queued | running | done | failed
32
  "result": None,
33
  "error": None,
34
  "created_at": now_iso(),
@@ -36,49 +37,45 @@ async def enqueue(command: str, args: Optional[Dict[str, Any]] = None, source: s
36
  "claimed_by": None,
37
  }
38
  async with queue_lock:
39
- commands[cmd_id] = record
40
  queue.append(cmd_id)
41
- return record
42
 
43
 
44
  # ============================================================
45
- # MCP server (SSE transport)
46
  # ============================================================
47
  mcp = FastMCP(
48
- "Create3 Local Bridge (SSE)",
49
- instructions=(
50
- "Tools queue robot commands for a local Create 3 runner. "
51
- "This MCP server does not control the robot directly."
52
- ),
53
  )
54
 
55
- # --- Exactly 3 tools ---
56
-
57
  @mcp.tool()
58
  async def dock(ctx: Context) -> dict:
59
- """Dock the Create 3."""
60
  rec = await enqueue("dock", {}, source="mcp")
61
  return {"ok": True, "queued": rec}
62
 
63
-
64
  @mcp.tool()
65
  async def undock(ctx: Context) -> dict:
66
- """Undock the Create 3."""
67
  rec = await enqueue("undock", {}, source="mcp")
68
  return {"ok": True, "queued": rec}
69
 
70
-
71
  @mcp.tool()
72
  async def move_cm(cm: float, ctx: Context) -> dict:
73
- """Move forward (negative cm moves backward)."""
74
  rec = await enqueue("move_cm", {"cm": cm}, source="mcp")
75
  return {"ok": True, "queued": rec}
76
 
77
 
78
  # ============================================================
79
- # FastAPI app + queue endpoints for local robot runner
80
  # ============================================================
81
- app = FastAPI(title="Create3 MCP SSE + Queue (Local)", version="1.0.0")
82
 
83
 
84
  class StatusUpdate(BaseModel):
@@ -92,8 +89,9 @@ class StatusUpdate(BaseModel):
92
  async def root():
93
  return {
94
  "ok": True,
95
- "mcp_sse_endpoint": "/sse",
96
- "note": "Point your MCP connector to http://localhost:8000/sse",
 
97
  "time": now_iso(),
98
  }
99
 
@@ -111,31 +109,22 @@ async def health():
111
 
112
  @app.get("/commands")
113
  async def list_commands(limit: int = 50):
114
- items = sorted(
115
- commands.values(),
116
- key=lambda x: x["updated_at"],
117
- reverse=True
118
- )[: max(1, min(limit, 200))]
119
  return {"ok": True, "items": items}
120
 
121
 
122
  @app.post("/commands/next")
123
  async def claim_next_command(robot_id: str):
124
- """Robot runner polls this to claim the next queued command."""
125
  async with queue_lock:
126
  while queue:
127
  cmd_id = queue.pop(0)
128
  cmd = commands.get(cmd_id)
129
- if not cmd:
130
  continue
131
- if cmd["status"] != "queued":
132
- continue
133
-
134
  cmd["status"] = "running"
135
  cmd["claimed_by"] = robot_id
136
  cmd["updated_at"] = now_iso()
137
  return {"ok": True, "item": cmd}
138
-
139
  return {"ok": True, "item": None}
140
 
141
 
@@ -144,7 +133,6 @@ async def update_status(command_id: str, update: StatusUpdate):
144
  cmd = commands.get(command_id)
145
  if not cmd:
146
  raise HTTPException(status_code=404, detail="Command not found")
147
-
148
  cmd["status"] = update.status
149
  cmd["result"] = update.result
150
  cmd["error"] = update.error
@@ -154,7 +142,5 @@ async def update_status(command_id: str, update: StatusUpdate):
154
  return {"ok": True}
155
 
156
 
157
- # ============================================================
158
- # Mount MCP SSE app at /sse
159
- # ============================================================
160
- app.mount("/sse", mcp.sse_app())
 
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] = []
 
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
  "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):
 
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
 
 
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"
125
  cmd["claimed_by"] = robot_id
126
  cmd["updated_at"] = now_iso()
127
  return {"ok": True, "item": cmd}
 
128
  return {"ok": True, "item": None}
129
 
130
 
 
133
  cmd = commands.get(command_id)
134
  if not cmd:
135
  raise HTTPException(status_code=404, detail="Command not found")
 
136
  cmd["status"] = update.status
137
  cmd["result"] = update.result
138
  cmd["error"] = update.error
 
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()))