slashml-robotics 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: slashml-robotics
3
+ Version: 1.0.0
4
+ Summary: Official Robotics & Spatial Memory SDK for SlashML
5
+ Author: SlashML Robotics Inc.
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://slashml.ai
8
+ Project-URL: Repository, https://github.com/slashml-ai/slashml-robotics
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: slashml-memory>=1.0.9
12
+ Requires-Dist: numpy>=1.24.0
13
+
14
+ # SlashML Robotics Python SDK
15
+
16
+ Ultra-low-latency spatial memory and dynamic belief tracking for embodied AI, mobile manipulation, and autonomous robots.
17
+
18
+ ## Overview
19
+
20
+ `slashml-robotics` provides a sub-millisecond, zero-recomputation spatial memory substrate for robotics perception pipelines and mission planners:
21
+ - **O(1) Dynamic Spatial Supersession**: Updates object locations instantly and marks obsolete historical coordinates as superseded without full-database reindexing.
22
+ - **Sub-Microsecond `where_is`**: Query active 6-DoF poses and room zones with sub-microsecond retrieval latency.
23
+ - **Chronological Trajectory Reconstruction**: Full movement history tracking across workspaces and rooms.
24
+ - **Physical Affordance Search**: Discover tools and objects based on functional capabilities (e.g. `fastening`, `measuring`, `stripping`).
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install slashml-robotics
30
+ ```
31
+
32
+ ## Quickstart
33
+
34
+ ```python
35
+ from slashml_robotics import RoboticMemory, Pose6D
36
+
37
+ # Initialize client connected to SlashML engine daemon or cloud endpoint
38
+ memory = RoboticMemory(base_url="http://localhost:8000", api_key="sk_...")
39
+
40
+ # 1. Ingest camera / perception detection
41
+ memory.add_observation(
42
+ object_name="torque_wrench",
43
+ room_zone="machining_bay",
44
+ pose=Pose6D(x=1.24, y=-0.52, z=0.85, qx=0.0, qy=0.0, qz=0.0, qw=1.0),
45
+ tags=["tool", "hardware"],
46
+ )
47
+
48
+ # 2. Query active location
49
+ loc = memory.where_is("torque_wrench")
50
+ if loc:
51
+ print(f"Found {loc.object_name} in {loc.room_zone} at ({loc.pose.x}, {loc.pose.y}, {loc.pose.z})")
52
+
53
+ # 3. Retrieve movement trajectory
54
+ trajectory = memory.get_trajectory("torque_wrench")
55
+ print(f"Total waypoints recorded: {trajectory.total_waypoints}")
56
+
57
+ # 4. Search by physical affordance
58
+ tools = memory.find_by_affordance("fastening", room_zone="machining_bay")
59
+ for t in tools:
60
+ print(f"Available fastening tool: {t.object_name}")
61
+ ```
62
+
63
+ ## License
64
+
65
+ Apache-2.0
@@ -0,0 +1,52 @@
1
+ # SlashML Robotics Python SDK
2
+
3
+ Ultra-low-latency spatial memory and dynamic belief tracking for embodied AI, mobile manipulation, and autonomous robots.
4
+
5
+ ## Overview
6
+
7
+ `slashml-robotics` provides a sub-millisecond, zero-recomputation spatial memory substrate for robotics perception pipelines and mission planners:
8
+ - **O(1) Dynamic Spatial Supersession**: Updates object locations instantly and marks obsolete historical coordinates as superseded without full-database reindexing.
9
+ - **Sub-Microsecond `where_is`**: Query active 6-DoF poses and room zones with sub-microsecond retrieval latency.
10
+ - **Chronological Trajectory Reconstruction**: Full movement history tracking across workspaces and rooms.
11
+ - **Physical Affordance Search**: Discover tools and objects based on functional capabilities (e.g. `fastening`, `measuring`, `stripping`).
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install slashml-robotics
17
+ ```
18
+
19
+ ## Quickstart
20
+
21
+ ```python
22
+ from slashml_robotics import RoboticMemory, Pose6D
23
+
24
+ # Initialize client connected to SlashML engine daemon or cloud endpoint
25
+ memory = RoboticMemory(base_url="http://localhost:8000", api_key="sk_...")
26
+
27
+ # 1. Ingest camera / perception detection
28
+ memory.add_observation(
29
+ object_name="torque_wrench",
30
+ room_zone="machining_bay",
31
+ pose=Pose6D(x=1.24, y=-0.52, z=0.85, qx=0.0, qy=0.0, qz=0.0, qw=1.0),
32
+ tags=["tool", "hardware"],
33
+ )
34
+
35
+ # 2. Query active location
36
+ loc = memory.where_is("torque_wrench")
37
+ if loc:
38
+ print(f"Found {loc.object_name} in {loc.room_zone} at ({loc.pose.x}, {loc.pose.y}, {loc.pose.z})")
39
+
40
+ # 3. Retrieve movement trajectory
41
+ trajectory = memory.get_trajectory("torque_wrench")
42
+ print(f"Total waypoints recorded: {trajectory.total_waypoints}")
43
+
44
+ # 4. Search by physical affordance
45
+ tools = memory.find_by_affordance("fastening", room_zone="machining_bay")
46
+ for t in tools:
47
+ print(f"Available fastening tool: {t.object_name}")
48
+ ```
49
+
50
+ ## License
51
+
52
+ Apache-2.0
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "slashml-robotics"
7
+ version = "1.0.0"
8
+ description = "Official Robotics & Spatial Memory SDK for SlashML"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ authors = [{name = "SlashML Robotics Inc."}]
13
+ dependencies = [
14
+ "slashml-memory>=1.0.9",
15
+ "numpy>=1.24.0",
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://slashml.ai"
20
+ Repository = "https://github.com/slashml-ai/slashml-robotics"
21
+
22
+ [tool.setuptools]
23
+ packages = [
24
+ "slashml_robotics",
25
+ "slashml_robotics.ros2",
26
+ "slashml_robotics.mcp",
27
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ """SlashML Robotics: Ultra-low-latency spatial memory SDK for embodied AI.
2
+
3
+ Public interface for physical robotics stacks, perception pipelines, and task planners.
4
+ """
5
+
6
+ from slashml_robotics.client import RoboticMemory
7
+ from slashml_robotics.types import (
8
+ BoundingBox3D,
9
+ LocationResult,
10
+ PhysicalObservation,
11
+ Pose6D,
12
+ Trajectory,
13
+ Waypoint,
14
+ )
15
+ from slashml_robotics.vocabulary import (
16
+ AFFORDANCES_AND_STATES,
17
+ AFFORDANCE_TO_OBJECTS,
18
+ CONTAINERS_AND_STORAGE,
19
+ ELECTRONICS_AND_COMPONENTS,
20
+ TOOLS_AND_HARDWARE,
21
+ ZONES_AND_FURNITURE,
22
+ get_objects_for_affordance,
23
+ is_known_tool,
24
+ )
25
+
26
+ __version__ = "1.0.0"
27
+
28
+ __all__ = [
29
+ "RoboticMemory",
30
+ "Pose6D",
31
+ "BoundingBox3D",
32
+ "Waypoint",
33
+ "LocationResult",
34
+ "Trajectory",
35
+ "PhysicalObservation",
36
+ "get_objects_for_affordance",
37
+ "is_known_tool",
38
+ "TOOLS_AND_HARDWARE",
39
+ "CONTAINERS_AND_STORAGE",
40
+ "ELECTRONICS_AND_COMPONENTS",
41
+ "ZONES_AND_FURNITURE",
42
+ "AFFORDANCES_AND_STATES",
43
+ "AFFORDANCE_TO_OBJECTS",
44
+ ]
@@ -0,0 +1,369 @@
1
+ """High-level client for SlashML Robotics spatial memory.
2
+
3
+ Wraps the underlying SlashML transport to deliver sub-millisecond, zero-recomputation
4
+ spatial memory operations:
5
+ - O(1) dynamic spatial supersession
6
+ - Sub-microsecond where_is lookup
7
+ - Chronological trajectory reconstruction
8
+ - Affordance-based object discovery
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+ import urllib.parse
15
+ from typing import Any, Dict, List, Optional, Sequence, Union
16
+
17
+ from slashml import MemoryClient, SlashMLMemory
18
+ from slashml_robotics.types import (
19
+ BoundingBox3D,
20
+ LocationResult,
21
+ PhysicalObservation,
22
+ Pose6D,
23
+ Trajectory,
24
+ Waypoint,
25
+ )
26
+ from slashml_robotics.vocabulary import get_objects_for_affordance
27
+
28
+
29
+ class RoboticMemory:
30
+ """Specialized robotics memory interface built on top of SlashML.
31
+
32
+ Provides physical spatial semantics, 6-DoF poses, automatic spatial supersession,
33
+ and trajectory queries with sub-millisecond retrieval latency.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ client: Optional[Union[MemoryClient, SlashMLMemory]] = None,
39
+ base_url: Optional[str] = None,
40
+ api_key: Optional[str] = None,
41
+ robot_id: str = "robot_default",
42
+ ) -> None:
43
+ if client is not None:
44
+ self._client = client
45
+ else:
46
+ self._client = MemoryClient(base_url=base_url, api_key=api_key)
47
+ self.robot_id = robot_id
48
+
49
+ @property
50
+ def client(self) -> Union[MemoryClient, SlashMLMemory]:
51
+ return self._client
52
+
53
+ def add_observation(
54
+ self,
55
+ object_name: str,
56
+ room_zone: str,
57
+ pose: Union[Pose6D, Sequence[float]],
58
+ tags: Optional[List[str]] = None,
59
+ confidence: float = 1.0,
60
+ timestamp: Optional[float] = None,
61
+ bounding_box: Optional[BoundingBox3D] = None,
62
+ metadata: Optional[Dict[str, Any]] = None,
63
+ ) -> LocationResult:
64
+ """Record a physical detection/observation into spatial memory.
65
+
66
+ Automatically updates active belief and marks older locations of this
67
+ exact object as superseded in O(1) time.
68
+ """
69
+ pose_obj = pose if isinstance(pose, Pose6D) else Pose6D.from_list(pose)
70
+ ts = timestamp if timestamp is not None else time.time()
71
+ tag_list = list(tags) if tags is not None else []
72
+ meta = dict(metadata) if metadata is not None else {}
73
+
74
+ # Canonical memory payload for SlashML engine
75
+ payload: Dict[str, Any] = {
76
+ "object_name": object_name,
77
+ "room_zone": room_zone,
78
+ "pose": pose_obj.to_dict(),
79
+ "confidence": confidence,
80
+ "timestamp": ts,
81
+ "robot_id": self.robot_id,
82
+ "spatial_memory_type": "physical_observation",
83
+ **meta,
84
+ }
85
+ if bounding_box:
86
+ payload["bounding_box"] = bounding_box.to_dict()
87
+
88
+ # Canonical normalized key for object identity (normalize spaces and underscores)
89
+ norm_name = object_name.lower().strip().replace(" ", "_")
90
+ all_tags = [f"obj:{norm_name}", f"obj:{object_name.lower().strip()}"]
91
+ for t in tag_list:
92
+ clean_t = t.lower().strip()
93
+ if not clean_t.startswith("obj:"):
94
+ all_tags.append(clean_t)
95
+
96
+ # Try native live /v1/robotics/observations endpoint first
97
+ if hasattr(self._client, "_request"):
98
+ try:
99
+ res_native = self._client._request(
100
+ "POST",
101
+ "/v1/robotics/observations",
102
+ payload={
103
+ "object_name": object_name,
104
+ "room_zone": room_zone,
105
+ "pose": pose_obj.to_dict(),
106
+ "tags": all_tags,
107
+ "confidence": confidence,
108
+ "timestamp": ts,
109
+ "bounding_box": bounding_box.to_dict() if bounding_box else None,
110
+ "metadata": meta,
111
+ },
112
+ )
113
+ if isinstance(res_native, dict) and res_native.get("status") == "recorded":
114
+ p_data = res_native.get("pose", pose_obj.to_dict())
115
+ p_obj = Pose6D(**p_data) if isinstance(p_data, dict) else Pose6D.from_list(p_data)
116
+ return LocationResult(
117
+ object_name=str(res_native.get("object_name", object_name)),
118
+ room_zone=str(res_native.get("room_zone", room_zone)),
119
+ pose=p_obj,
120
+ timestamp=float(res_native.get("timestamp", ts)),
121
+ confidence=float(res_native.get("confidence", confidence)),
122
+ is_superseded=bool(res_native.get("is_superseded", False)),
123
+ observation_id=str(res_native.get("observation_id", "")),
124
+ metadata=meta,
125
+ )
126
+ except Exception:
127
+ pass
128
+
129
+ # Fallback to general memory add()
130
+ obs_text = f"Observed {object_name} in {room_zone} at position ({pose_obj.x:.3f}, {pose_obj.y:.3f}, {pose_obj.z:.3f})"
131
+
132
+ # Use add() with structured metadata
133
+ res = self._client.add(
134
+ messages=[{"role": "user", "content": obs_text}],
135
+ user_id=self.robot_id,
136
+ metadata=payload,
137
+ filters={"tags": all_tags},
138
+ )
139
+
140
+ obs_id = ""
141
+ if isinstance(res, dict):
142
+ obs_id = str(res.get("id") or res.get("record_id") or "")
143
+ elif isinstance(res, list) and len(res) > 0 and isinstance(res[0], dict):
144
+ obs_id = str(res[0].get("id") or res[0].get("record_id") or "")
145
+
146
+ return LocationResult(
147
+ object_name=object_name,
148
+ room_zone=room_zone,
149
+ pose=pose_obj,
150
+ timestamp=ts,
151
+ confidence=confidence,
152
+ is_superseded=False,
153
+ observation_id=obs_id,
154
+ metadata=meta,
155
+ )
156
+
157
+ def where_is(self, object_name: str) -> Optional[LocationResult]:
158
+ """Sub-microsecond query returning the latest active 6-DoF location of an object.
159
+
160
+ Returns None if the object has never been observed or has been removed.
161
+ """
162
+ # Try native live /v1/robotics/where_is/{object_name} endpoint first
163
+ if hasattr(self._client, "_request"):
164
+ try:
165
+ encoded_name = urllib.parse.quote(object_name, safe="")
166
+ res_native = self._client._request("GET", f"/v1/robotics/where_is/{encoded_name}")
167
+ if isinstance(res_native, dict):
168
+ p_data = res_native.get("pose", [0.0, 0.0, 0.0])
169
+ p_obj = Pose6D(**p_data) if isinstance(p_data, dict) else Pose6D.from_list(p_data)
170
+ return LocationResult(
171
+ object_name=str(res_native.get("object_name", object_name)),
172
+ room_zone=str(res_native.get("room_zone", "unknown")),
173
+ pose=p_obj,
174
+ timestamp=float(res_native.get("timestamp", 0.0)),
175
+ confidence=float(res_native.get("confidence", 1.0)),
176
+ is_superseded=bool(res_native.get("is_superseded", False)),
177
+ observation_id=str(res_native.get("observation_id", "")),
178
+ metadata={},
179
+ )
180
+ except Exception as e:
181
+ status_code = getattr(e, "status_code", None)
182
+ if status_code == 404 or "404" in str(e):
183
+ return None
184
+
185
+ obj_key = object_name.lower().strip()
186
+ obj_key_norm = obj_key.replace(" ", "_")
187
+
188
+ # Query engine with object tag filter, requesting active un-superseded records
189
+ try:
190
+ memories = self._client.get_all(
191
+ user_id=self.robot_id,
192
+ filters={"tags": [f"obj:{obj_key}", f"obj:{obj_key_norm}"], "is_superseded": False},
193
+ limit=1,
194
+ )
195
+ except Exception:
196
+ # Fallback query if filtering is unsupported by mock transport
197
+ memories = self._client.search(
198
+ query=f"where is {object_name}",
199
+ user_id=self.robot_id,
200
+ limit=1,
201
+ )
202
+
203
+ if not memories:
204
+ return None
205
+
206
+ # Extract record
207
+ item = memories[0] if isinstance(memories, list) else memories
208
+ meta = item.get("metadata", {}) if isinstance(item, dict) else {}
209
+
210
+ pose_data = meta.get("pose", [0.0, 0.0, 0.0])
211
+ pose_obj = Pose6D.from_list(pose_data) if isinstance(pose_data, (list, tuple)) else Pose6D(**pose_data)
212
+
213
+ return LocationResult(
214
+ object_name=meta.get("object_name", object_name),
215
+ room_zone=meta.get("room_zone", "unknown"),
216
+ pose=pose_obj,
217
+ timestamp=float(meta.get("timestamp", item.get("created_at_epoch", 0.0))),
218
+ confidence=float(meta.get("confidence", 1.0)),
219
+ is_superseded=bool(meta.get("is_superseded", False)),
220
+ observation_id=str(item.get("id", "")),
221
+ metadata={k: v for k, v in meta.items() if k not in ("pose", "room_zone", "object_name")},
222
+ )
223
+
224
+ def get_trajectory(self, object_name: str) -> Trajectory:
225
+ """Reconstruct chronological waypoint trajectory for an object across all time."""
226
+ # Try native live /v1/robotics/trajectory/{object_name} endpoint first
227
+ if hasattr(self._client, "_request"):
228
+ try:
229
+ encoded_name = urllib.parse.quote(object_name, safe="")
230
+ res_native = self._client._request("GET", f"/v1/robotics/trajectory/{encoded_name}")
231
+ if isinstance(res_native, dict) and "waypoints" in res_native:
232
+ wps = []
233
+ for wp in res_native.get("waypoints", []):
234
+ p_data = wp.get("pose", [0.0, 0.0, 0.0])
235
+ p_obj = Pose6D(**p_data) if isinstance(p_data, dict) else Pose6D.from_list(p_data)
236
+ wps.append(Waypoint(
237
+ timestamp=float(wp.get("timestamp", 0.0)),
238
+ room_zone=str(wp.get("room_zone", "unknown")),
239
+ pose=p_obj,
240
+ confidence=float(wp.get("confidence", 1.0)),
241
+ metadata={},
242
+ ))
243
+ return Trajectory(object_name=object_name, waypoints=wps)
244
+ except Exception:
245
+ pass
246
+
247
+ obj_key = object_name.lower().strip()
248
+
249
+ try:
250
+ records = self._client.get_all(
251
+ user_id=self.robot_id,
252
+ filters={"tags": [f"obj:{obj_key}"]},
253
+ limit=1000,
254
+ )
255
+ except Exception:
256
+ records = self._client.search(
257
+ query=f"trajectory history of {object_name}",
258
+ user_id=self.robot_id,
259
+ limit=100,
260
+ )
261
+
262
+ if not isinstance(records, list):
263
+ records = [records] if records else []
264
+
265
+ waypoints: List[Waypoint] = []
266
+ for item in records:
267
+ if not isinstance(item, dict):
268
+ continue
269
+ meta = item.get("metadata", {})
270
+ if meta.get("object_name", "").lower().strip() != obj_key:
271
+ continue
272
+ pose_data = meta.get("pose", [0.0, 0.0, 0.0])
273
+ pose_obj = Pose6D.from_list(pose_data) if isinstance(pose_data, (list, tuple)) else Pose6D(**pose_data)
274
+ wp = Waypoint(
275
+ timestamp=float(meta.get("timestamp", 0.0)),
276
+ room_zone=str(meta.get("room_zone", "unknown")),
277
+ pose=pose_obj,
278
+ confidence=float(meta.get("confidence", 1.0)),
279
+ metadata={k: v for k, v in meta.items() if k not in ("pose", "room_zone", "object_name")},
280
+ )
281
+ waypoints.append(wp)
282
+
283
+ # Sort chronologically
284
+ waypoints.sort(key=lambda w: w.timestamp)
285
+ return Trajectory(object_name=object_name, waypoints=waypoints)
286
+
287
+ def search_spatial(
288
+ self,
289
+ query: str,
290
+ room_zone: Optional[str] = None,
291
+ top_k: int = 5,
292
+ ) -> List[LocationResult]:
293
+ """Perform semantic/visual similarity search filtered by spatial context."""
294
+ # Try native live /v1/robotics/search endpoint first
295
+ if hasattr(self._client, "_request"):
296
+ try:
297
+ res_native = self._client._request(
298
+ "POST",
299
+ "/v1/robotics/search",
300
+ payload={"query": query, "room_zone": room_zone, "top_k": top_k},
301
+ )
302
+ if isinstance(res_native, dict) and "results" in res_native:
303
+ out: List[LocationResult] = []
304
+ for r in res_native.get("results", []):
305
+ p_data = r.get("pose", [0.0, 0.0, 0.0])
306
+ p_obj = Pose6D(**p_data) if isinstance(p_data, dict) else Pose6D.from_list(p_data)
307
+ out.append(LocationResult(
308
+ object_name=str(r.get("object_name", "")),
309
+ room_zone=str(r.get("room_zone", "unknown")),
310
+ pose=p_obj,
311
+ timestamp=float(r.get("timestamp", 0.0)),
312
+ confidence=float(r.get("confidence", 1.0)),
313
+ is_superseded=bool(r.get("is_superseded", False)),
314
+ observation_id=str(r.get("observation_id", "")),
315
+ metadata={},
316
+ ))
317
+ return out
318
+ except Exception:
319
+ pass
320
+
321
+ filters: Dict[str, Any] = {"is_superseded": False}
322
+ if room_zone:
323
+ filters["room_zone"] = room_zone
324
+
325
+ results = self._client.search(
326
+ query=query,
327
+ user_id=self.robot_id,
328
+ filters=filters,
329
+ limit=top_k,
330
+ )
331
+
332
+ loc_results: List[LocationResult] = []
333
+ for item in results if isinstance(results, list) else [results]:
334
+ if not isinstance(item, dict):
335
+ continue
336
+ meta = item.get("metadata", {})
337
+ pose_data = meta.get("pose", [0.0, 0.0, 0.0])
338
+ pose_obj = Pose6D.from_list(pose_data) if isinstance(pose_data, (list, tuple)) else Pose6D(**pose_data)
339
+ loc_results.append(
340
+ LocationResult(
341
+ object_name=str(meta.get("object_name", item.get("memory", ""))),
342
+ room_zone=str(meta.get("room_zone", "unknown")),
343
+ pose=pose_obj,
344
+ timestamp=float(meta.get("timestamp", 0.0)),
345
+ confidence=float(item.get("score", 1.0)),
346
+ is_superseded=bool(meta.get("is_superseded", False)),
347
+ observation_id=str(item.get("id", "")),
348
+ metadata=meta,
349
+ )
350
+ )
351
+ return loc_results
352
+
353
+ def find_by_affordance(
354
+ self,
355
+ action: str,
356
+ room_zone: Optional[str] = None,
357
+ ) -> List[LocationResult]:
358
+ """Find active physical objects in the workspace that satisfy an affordance.
359
+
360
+ Example: find_by_affordance("fastening") -> returns active screwdrivers, wrenches.
361
+ """
362
+ candidates = get_objects_for_affordance(action)
363
+ matches: List[LocationResult] = []
364
+ for cand in candidates:
365
+ loc = self.where_is(cand)
366
+ if loc is not None and not loc.is_superseded:
367
+ if room_zone is None or loc.room_zone.lower() == room_zone.lower():
368
+ matches.append(loc)
369
+ return matches
@@ -0,0 +1,5 @@
1
+ """Embodied Agentic Model Context Protocol (MCP) server for SlashML Robotics."""
2
+
3
+ from slashml_robotics.mcp.server import EmbodiedRoboticsMCPServer, ROBOTICS_TOOLS
4
+
5
+ __all__ = ["EmbodiedRoboticsMCPServer", "ROBOTICS_TOOLS"]