foodforthought-cli 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
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.
- ate/__init__.py +1 -1
- ate/bridge_server.py +622 -0
- ate/cli.py +2625 -242
- ate/compatibility.py +580 -0
- ate/generators/__init__.py +19 -0
- ate/generators/docker_generator.py +461 -0
- ate/generators/hardware_config.py +469 -0
- ate/generators/ros2_generator.py +617 -0
- ate/generators/skill_generator.py +783 -0
- ate/marketplace.py +524 -0
- ate/mcp_server.py +1341 -107
- ate/primitives.py +1016 -0
- ate/robot_setup.py +2222 -0
- ate/skill_schema.py +537 -0
- ate/telemetry/__init__.py +33 -0
- ate/telemetry/cli.py +455 -0
- ate/telemetry/collector.py +444 -0
- ate/telemetry/context.py +318 -0
- ate/telemetry/fleet_agent.py +419 -0
- ate/telemetry/formats/__init__.py +18 -0
- ate/telemetry/formats/hdf5_serializer.py +503 -0
- ate/telemetry/formats/mcap_serializer.py +457 -0
- ate/telemetry/types.py +334 -0
- foodforthought_cli-0.2.3.dist-info/METADATA +300 -0
- foodforthought_cli-0.2.3.dist-info/RECORD +44 -0
- foodforthought_cli-0.2.3.dist-info/top_level.txt +6 -0
- mechdog_labeled/__init__.py +3 -0
- mechdog_labeled/primitives.py +113 -0
- mechdog_labeled/servo_map.py +209 -0
- mechdog_output/__init__.py +3 -0
- mechdog_output/primitives.py +59 -0
- mechdog_output/servo_map.py +203 -0
- test_autodetect/__init__.py +3 -0
- test_autodetect/primitives.py +113 -0
- test_autodetect/servo_map.py +209 -0
- test_full_auto/__init__.py +3 -0
- test_full_auto/primitives.py +113 -0
- test_full_auto/servo_map.py +209 -0
- test_smart_detect/__init__.py +3 -0
- test_smart_detect/primitives.py +113 -0
- test_smart_detect/servo_map.py +209 -0
- foodforthought_cli-0.2.1.dist-info/METADATA +0 -151
- foodforthought_cli-0.2.1.dist-info/RECORD +0 -9
- foodforthought_cli-0.2.1.dist-info/top_level.txt +0 -1
- {foodforthought_cli-0.2.1.dist-info → foodforthought_cli-0.2.3.dist-info}/WHEEL +0 -0
- {foodforthought_cli-0.2.1.dist-info → foodforthought_cli-0.2.3.dist-info}/entry_points.txt +0 -0
ate/mcp_server.py
CHANGED
|
@@ -195,6 +195,158 @@ def get_robot_tools() -> List[Tool]:
|
|
|
195
195
|
]
|
|
196
196
|
|
|
197
197
|
|
|
198
|
+
def get_marketplace_tools() -> List[Tool]:
|
|
199
|
+
"""Unified marketplace tools (Phase 6)"""
|
|
200
|
+
return [
|
|
201
|
+
Tool(
|
|
202
|
+
name="ate_marketplace_robots",
|
|
203
|
+
description="List community robots from the unified marketplace. Includes both Artifex-published and imported robots.",
|
|
204
|
+
inputSchema={
|
|
205
|
+
"type": "object",
|
|
206
|
+
"properties": {
|
|
207
|
+
"search": {
|
|
208
|
+
"type": "string",
|
|
209
|
+
"description": "Search by name or description",
|
|
210
|
+
},
|
|
211
|
+
"category": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"enum": ["arm", "gripper", "mobile_base", "quadruped", "humanoid",
|
|
214
|
+
"dual_arm", "manipulator", "cobot", "drone", "custom"],
|
|
215
|
+
"description": "Filter by robot category",
|
|
216
|
+
},
|
|
217
|
+
"sort": {
|
|
218
|
+
"type": "string",
|
|
219
|
+
"enum": ["downloads", "rating", "recent", "name"],
|
|
220
|
+
"description": "Sort order",
|
|
221
|
+
"default": "downloads",
|
|
222
|
+
},
|
|
223
|
+
"limit": {
|
|
224
|
+
"type": "number",
|
|
225
|
+
"description": "Maximum results",
|
|
226
|
+
"default": 20,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
),
|
|
231
|
+
Tool(
|
|
232
|
+
name="ate_marketplace_robot",
|
|
233
|
+
description="Get detailed information about a specific robot from the marketplace, including URDF, links, joints, and parts.",
|
|
234
|
+
inputSchema={
|
|
235
|
+
"type": "object",
|
|
236
|
+
"properties": {
|
|
237
|
+
"robot_id": {
|
|
238
|
+
"type": "string",
|
|
239
|
+
"description": "Robot ID or slug",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
"required": ["robot_id"],
|
|
243
|
+
},
|
|
244
|
+
),
|
|
245
|
+
Tool(
|
|
246
|
+
name="ate_marketplace_components",
|
|
247
|
+
description="List components from the parts marketplace. Components can be grippers, sensors, actuators, etc.",
|
|
248
|
+
inputSchema={
|
|
249
|
+
"type": "object",
|
|
250
|
+
"properties": {
|
|
251
|
+
"search": {
|
|
252
|
+
"type": "string",
|
|
253
|
+
"description": "Search by name or description",
|
|
254
|
+
},
|
|
255
|
+
"type": {
|
|
256
|
+
"type": "string",
|
|
257
|
+
"enum": ["gripper", "end_effector", "sensor", "camera",
|
|
258
|
+
"actuator", "link", "base", "arm_segment", "custom"],
|
|
259
|
+
"description": "Filter by component type",
|
|
260
|
+
},
|
|
261
|
+
"sort": {
|
|
262
|
+
"type": "string",
|
|
263
|
+
"enum": ["downloads", "rating", "recent", "name"],
|
|
264
|
+
"description": "Sort order",
|
|
265
|
+
"default": "downloads",
|
|
266
|
+
},
|
|
267
|
+
"limit": {
|
|
268
|
+
"type": "number",
|
|
269
|
+
"description": "Maximum results",
|
|
270
|
+
"default": 20,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
),
|
|
275
|
+
Tool(
|
|
276
|
+
name="ate_marketplace_component",
|
|
277
|
+
description="Get detailed information about a specific component, including compatible robots and specifications.",
|
|
278
|
+
inputSchema={
|
|
279
|
+
"type": "object",
|
|
280
|
+
"properties": {
|
|
281
|
+
"component_id": {
|
|
282
|
+
"type": "string",
|
|
283
|
+
"description": "Component ID",
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
"required": ["component_id"],
|
|
287
|
+
},
|
|
288
|
+
),
|
|
289
|
+
Tool(
|
|
290
|
+
name="ate_skill_transfer_check",
|
|
291
|
+
description="Calculate skill transfer compatibility between robots. Shows which robots can receive skills from a source robot.",
|
|
292
|
+
inputSchema={
|
|
293
|
+
"type": "object",
|
|
294
|
+
"properties": {
|
|
295
|
+
"robot_id": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"description": "Source robot ID",
|
|
298
|
+
},
|
|
299
|
+
"direction": {
|
|
300
|
+
"type": "string",
|
|
301
|
+
"enum": ["from", "to"],
|
|
302
|
+
"description": "Direction: 'from' = skills from this robot can transfer to others",
|
|
303
|
+
"default": "from",
|
|
304
|
+
},
|
|
305
|
+
"min_score": {
|
|
306
|
+
"type": "number",
|
|
307
|
+
"description": "Minimum compatibility score (0.0-1.0)",
|
|
308
|
+
"default": 0.4,
|
|
309
|
+
},
|
|
310
|
+
"limit": {
|
|
311
|
+
"type": "number",
|
|
312
|
+
"description": "Maximum results",
|
|
313
|
+
"default": 10,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
"required": ["robot_id"],
|
|
317
|
+
},
|
|
318
|
+
),
|
|
319
|
+
Tool(
|
|
320
|
+
name="ate_robot_parts",
|
|
321
|
+
description="Get parts required by or compatible with a robot.",
|
|
322
|
+
inputSchema={
|
|
323
|
+
"type": "object",
|
|
324
|
+
"properties": {
|
|
325
|
+
"robot_id": {
|
|
326
|
+
"type": "string",
|
|
327
|
+
"description": "Robot ID",
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
"required": ["robot_id"],
|
|
331
|
+
},
|
|
332
|
+
),
|
|
333
|
+
Tool(
|
|
334
|
+
name="ate_component_robots",
|
|
335
|
+
description="Get robots that use or are compatible with a component.",
|
|
336
|
+
inputSchema={
|
|
337
|
+
"type": "object",
|
|
338
|
+
"properties": {
|
|
339
|
+
"component_id": {
|
|
340
|
+
"type": "string",
|
|
341
|
+
"description": "Component ID",
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
"required": ["component_id"],
|
|
345
|
+
},
|
|
346
|
+
),
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
|
|
198
350
|
def get_compatibility_tools() -> List[Tool]:
|
|
199
351
|
"""Skill compatibility and adaptation tools"""
|
|
200
352
|
return [
|
|
@@ -798,126 +950,939 @@ def get_test_tools() -> List[Tool]:
|
|
|
798
950
|
]
|
|
799
951
|
|
|
800
952
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
tools = []
|
|
805
|
-
tools.extend(get_repository_tools())
|
|
806
|
-
tools.extend(get_robot_tools())
|
|
807
|
-
tools.extend(get_compatibility_tools())
|
|
808
|
-
tools.extend(get_skill_tools())
|
|
809
|
-
tools.extend(get_parts_tools())
|
|
810
|
-
tools.extend(get_generate_tools())
|
|
811
|
-
tools.extend(get_workflow_tools())
|
|
812
|
-
tools.extend(get_team_tools())
|
|
813
|
-
tools.extend(get_data_tools())
|
|
814
|
-
tools.extend(get_deploy_tools())
|
|
815
|
-
tools.extend(get_test_tools())
|
|
816
|
-
return tools
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
# ============================================================================
|
|
820
|
-
# Tool Handlers
|
|
821
|
-
# ============================================================================
|
|
822
|
-
|
|
823
|
-
def capture_output(func, *args, **kwargs):
|
|
824
|
-
"""Capture printed output from a function"""
|
|
825
|
-
import io
|
|
826
|
-
import contextlib
|
|
827
|
-
|
|
828
|
-
f = io.StringIO()
|
|
829
|
-
with contextlib.redirect_stdout(f):
|
|
830
|
-
try:
|
|
831
|
-
result = func(*args, **kwargs)
|
|
832
|
-
except SystemExit:
|
|
833
|
-
pass # CLI functions may call sys.exit
|
|
834
|
-
return f.getvalue()
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
@server.call_tool()
|
|
838
|
-
async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
|
839
|
-
"""Handle tool calls"""
|
|
840
|
-
try:
|
|
841
|
-
# Repository tools
|
|
842
|
-
if name == "ate_init":
|
|
843
|
-
result = client.init(
|
|
844
|
-
arguments["name"],
|
|
845
|
-
arguments.get("description", ""),
|
|
846
|
-
arguments.get("visibility", "public"),
|
|
847
|
-
)
|
|
848
|
-
return [
|
|
849
|
-
TextContent(
|
|
850
|
-
type="text",
|
|
851
|
-
text=f"Repository created successfully!\nID: {result['repository']['id']}\nName: {result['repository']['name']}",
|
|
852
|
-
)
|
|
853
|
-
]
|
|
854
|
-
|
|
855
|
-
elif name == "ate_clone":
|
|
856
|
-
output = capture_output(
|
|
857
|
-
client.clone,
|
|
858
|
-
arguments["repo_id"],
|
|
859
|
-
arguments.get("target_dir")
|
|
860
|
-
)
|
|
861
|
-
return [TextContent(type="text", text=output or f"Repository cloned successfully")]
|
|
953
|
+
def get_compiler_tools() -> List[Tool]:
|
|
954
|
+
"""
|
|
955
|
+
Skill Compiler Tools - Transform skill.yaml specifications into deployable robot skill packages.
|
|
862
956
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
957
|
+
WORKFLOW FOR AI ASSISTANTS:
|
|
958
|
+
1. Use ate_list_primitives to discover available building blocks
|
|
959
|
+
2. Help user create skill.yaml with proper format
|
|
960
|
+
3. Use ate_validate_skill_spec to check for errors
|
|
961
|
+
4. Use ate_check_skill_compatibility to verify robot compatibility
|
|
962
|
+
5. Use ate_compile_skill to generate deployable code
|
|
963
|
+
6. Use ate_test_compiled_skill to test in dry-run or simulation
|
|
964
|
+
7. Use ate_publish_compiled_skill to share with community
|
|
870
965
|
|
|
871
|
-
|
|
872
|
-
|
|
966
|
+
See docs/SKILL_COMPILER.md for full documentation.
|
|
967
|
+
"""
|
|
968
|
+
return [
|
|
969
|
+
Tool(
|
|
970
|
+
name="ate_compile_skill",
|
|
971
|
+
description="""Compile a skill.yaml specification into deployable code.
|
|
972
|
+
|
|
973
|
+
WHEN TO USE: After creating and validating a skill.yaml file, use this to generate
|
|
974
|
+
executable Python code, ROS2 packages, or Docker containers.
|
|
975
|
+
|
|
976
|
+
TARGETS:
|
|
977
|
+
- python: Standalone Python package (default, simplest)
|
|
978
|
+
- ros2: ROS2-compatible package with launch files
|
|
979
|
+
- docker: Containerized deployment with Dockerfile
|
|
980
|
+
- all: Generate all formats
|
|
981
|
+
|
|
982
|
+
EXAMPLE:
|
|
983
|
+
{
|
|
984
|
+
"skill_path": "pick_and_place.skill.yaml",
|
|
985
|
+
"target": "python",
|
|
986
|
+
"output": "./dist/pick_and_place"
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
OUTPUT: Creates a directory with generated code, config files, and dependencies.""",
|
|
990
|
+
inputSchema={
|
|
991
|
+
"type": "object",
|
|
992
|
+
"properties": {
|
|
993
|
+
"skill_path": {
|
|
994
|
+
"type": "string",
|
|
995
|
+
"description": "Path to skill.yaml file (e.g., 'skills/pick_place.skill.yaml')",
|
|
996
|
+
},
|
|
997
|
+
"output": {
|
|
998
|
+
"type": "string",
|
|
999
|
+
"description": "Output directory (default: ./output). Will be created if doesn't exist.",
|
|
1000
|
+
},
|
|
1001
|
+
"target": {
|
|
1002
|
+
"type": "string",
|
|
1003
|
+
"enum": ["python", "ros2", "docker", "all"],
|
|
1004
|
+
"description": "Compilation target: python (simplest), ros2 (for ROS2 robots), docker (containerized), all",
|
|
1005
|
+
"default": "python",
|
|
1006
|
+
},
|
|
1007
|
+
"robot": {
|
|
1008
|
+
"type": "string",
|
|
1009
|
+
"description": "Optional: Path to robot URDF for hardware-specific config generation",
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
"required": ["skill_path"],
|
|
1013
|
+
},
|
|
1014
|
+
),
|
|
1015
|
+
Tool(
|
|
1016
|
+
name="ate_test_compiled_skill",
|
|
1017
|
+
description="""Test a compiled skill without deploying to a real robot.
|
|
873
1018
|
|
|
874
|
-
|
|
875
|
-
for repo in repos[:10]:
|
|
876
|
-
result_text += f"- {repo['name']} (ID: {repo['id']})\n"
|
|
877
|
-
if repo.get("description"):
|
|
878
|
-
result_text += f" {repo['description'][:100]}...\n"
|
|
1019
|
+
WHEN TO USE: After compiling a skill, verify it works correctly before deployment.
|
|
879
1020
|
|
|
880
|
-
|
|
1021
|
+
MODES:
|
|
1022
|
+
- dry-run: Traces execution without any robot (fastest, always available)
|
|
1023
|
+
- sim: Runs in simulation (requires simulation setup)
|
|
1024
|
+
- hardware: Runs on real robot (requires robot_port)
|
|
881
1025
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1026
|
+
EXAMPLE:
|
|
1027
|
+
{
|
|
1028
|
+
"skill_path": "./dist/pick_and_place",
|
|
1029
|
+
"mode": "dry-run",
|
|
1030
|
+
"params": {"speed": 0.3, "grip_force": 15.0}
|
|
1031
|
+
}
|
|
885
1032
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1033
|
+
OUTPUT: Execution trace showing each primitive call and its result.""",
|
|
1034
|
+
inputSchema={
|
|
1035
|
+
"type": "object",
|
|
1036
|
+
"properties": {
|
|
1037
|
+
"skill_path": {
|
|
1038
|
+
"type": "string",
|
|
1039
|
+
"description": "Path to compiled skill directory (output from ate_compile_skill)",
|
|
1040
|
+
},
|
|
1041
|
+
"mode": {
|
|
1042
|
+
"type": "string",
|
|
1043
|
+
"enum": ["sim", "dry-run", "hardware"],
|
|
1044
|
+
"description": "dry-run=trace only, sim=simulation, hardware=real robot",
|
|
1045
|
+
"default": "dry-run",
|
|
1046
|
+
},
|
|
1047
|
+
"robot_port": {
|
|
1048
|
+
"type": "string",
|
|
1049
|
+
"description": "Robot serial port (only for hardware mode, e.g., '/dev/ttyUSB0')",
|
|
1050
|
+
},
|
|
1051
|
+
"params": {
|
|
1052
|
+
"type": "object",
|
|
1053
|
+
"description": "Override skill parameters (e.g., {\"speed\": 0.5})",
|
|
1054
|
+
},
|
|
1055
|
+
},
|
|
1056
|
+
"required": ["skill_path"],
|
|
1057
|
+
},
|
|
1058
|
+
),
|
|
1059
|
+
Tool(
|
|
1060
|
+
name="ate_publish_compiled_skill",
|
|
1061
|
+
description="""Publish a compiled skill to the FoodForThought registry.
|
|
890
1062
|
|
|
891
|
-
|
|
1063
|
+
WHEN TO USE: When the skill is tested and ready to share with others.
|
|
892
1064
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
params["search"] = arguments["search"]
|
|
898
|
-
if arguments.get("category"):
|
|
899
|
-
params["category"] = arguments["category"]
|
|
900
|
-
params["limit"] = arguments.get("limit", 20)
|
|
1065
|
+
VISIBILITY:
|
|
1066
|
+
- public: Anyone can use this skill
|
|
1067
|
+
- private: Only you can see it
|
|
1068
|
+
- team: Shared with your team members
|
|
901
1069
|
|
|
902
|
-
|
|
903
|
-
|
|
1070
|
+
EXAMPLE:
|
|
1071
|
+
{
|
|
1072
|
+
"skill_path": "./dist/pick_and_place",
|
|
1073
|
+
"visibility": "public"
|
|
1074
|
+
}
|
|
904
1075
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1076
|
+
OUTPUT: Registry URL where the skill is published.""",
|
|
1077
|
+
inputSchema={
|
|
1078
|
+
"type": "object",
|
|
1079
|
+
"properties": {
|
|
1080
|
+
"skill_path": {
|
|
1081
|
+
"type": "string",
|
|
1082
|
+
"description": "Path to compiled skill directory to publish",
|
|
1083
|
+
},
|
|
1084
|
+
"visibility": {
|
|
1085
|
+
"type": "string",
|
|
1086
|
+
"enum": ["public", "private", "team"],
|
|
1087
|
+
"description": "Who can access this skill",
|
|
1088
|
+
"default": "public",
|
|
1089
|
+
},
|
|
1090
|
+
},
|
|
1091
|
+
"required": ["skill_path"],
|
|
1092
|
+
},
|
|
1093
|
+
),
|
|
1094
|
+
Tool(
|
|
1095
|
+
name="ate_check_skill_compatibility",
|
|
1096
|
+
description="""Check if a skill can run on a specific robot.
|
|
910
1097
|
|
|
911
|
-
|
|
1098
|
+
WHEN TO USE: Before compiling, verify the skill's hardware requirements
|
|
1099
|
+
match the target robot's capabilities (DOF, sensors, payload, etc.).
|
|
912
1100
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1101
|
+
CHECKS PERFORMED:
|
|
1102
|
+
- Arm DOF and reach requirements
|
|
1103
|
+
- Gripper type and force capabilities
|
|
1104
|
+
- Required sensors (cameras, F/T sensors)
|
|
1105
|
+
- Workspace bounds
|
|
916
1106
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1107
|
+
EXAMPLE:
|
|
1108
|
+
{
|
|
1109
|
+
"skill_path": "pick_and_place.skill.yaml",
|
|
1110
|
+
"robot_urdf": "robots/ur5/ur5.urdf"
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
OUTPUT: Compatibility report with score and list of issues/adaptations needed.""",
|
|
1114
|
+
inputSchema={
|
|
1115
|
+
"type": "object",
|
|
1116
|
+
"properties": {
|
|
1117
|
+
"skill_path": {
|
|
1118
|
+
"type": "string",
|
|
1119
|
+
"description": "Path to skill.yaml file",
|
|
1120
|
+
},
|
|
1121
|
+
"robot_urdf": {
|
|
1122
|
+
"type": "string",
|
|
1123
|
+
"description": "Path to robot URDF file",
|
|
1124
|
+
},
|
|
1125
|
+
"robot_ate_dir": {
|
|
1126
|
+
"type": "string",
|
|
1127
|
+
"description": "Alternative: Path to directory containing ate.yaml robot config",
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
"required": ["skill_path"],
|
|
1131
|
+
},
|
|
1132
|
+
),
|
|
1133
|
+
Tool(
|
|
1134
|
+
name="ate_list_primitives",
|
|
1135
|
+
description="""List available primitives (building blocks) for creating skills.
|
|
1136
|
+
|
|
1137
|
+
WHEN TO USE: When starting to create a skill, or when you need to know what
|
|
1138
|
+
operations are available for a specific hardware type.
|
|
1139
|
+
|
|
1140
|
+
CATEGORIES:
|
|
1141
|
+
- motion: Movement primitives (move_to_pose, move_linear, etc.)
|
|
1142
|
+
- gripper: Gripper actions (open_gripper, close_gripper)
|
|
1143
|
+
- sensing: Sensor reading (capture_image, read_force_torque)
|
|
1144
|
+
- wait: Timing and conditions (wait_time, wait_for_contact)
|
|
1145
|
+
- control: Control modes (set_control_mode, enable_compliance)
|
|
1146
|
+
|
|
1147
|
+
EXAMPLE - List all motion primitives:
|
|
1148
|
+
{
|
|
1149
|
+
"category": "motion"
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
EXAMPLE - List primitives that need a camera:
|
|
1153
|
+
{
|
|
1154
|
+
"hardware": "camera"
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
OUTPUT: List of primitives with their descriptions and parameters.""",
|
|
1158
|
+
inputSchema={
|
|
1159
|
+
"type": "object",
|
|
1160
|
+
"properties": {
|
|
1161
|
+
"category": {
|
|
1162
|
+
"type": "string",
|
|
1163
|
+
"enum": ["motion", "gripper", "sensing", "wait", "control", "all"],
|
|
1164
|
+
"description": "Filter by category (use 'all' to see everything)",
|
|
1165
|
+
"default": "all",
|
|
1166
|
+
},
|
|
1167
|
+
"hardware": {
|
|
1168
|
+
"type": "string",
|
|
1169
|
+
"description": "Filter by hardware type: arm, gripper, camera, force_torque_sensor",
|
|
1170
|
+
},
|
|
1171
|
+
},
|
|
1172
|
+
},
|
|
1173
|
+
),
|
|
1174
|
+
Tool(
|
|
1175
|
+
name="ate_get_primitive",
|
|
1176
|
+
description="""Get detailed information about a specific primitive.
|
|
1177
|
+
|
|
1178
|
+
WHEN TO USE: When you need to know the exact parameters and requirements
|
|
1179
|
+
for a primitive before using it in a skill.
|
|
1180
|
+
|
|
1181
|
+
COMMON PRIMITIVES:
|
|
1182
|
+
- move_to_pose: Move end-effector to a 7D pose [x,y,z,qx,qy,qz,qw]
|
|
1183
|
+
- move_to_joint_positions: Move to specific joint angles
|
|
1184
|
+
- open_gripper / close_gripper: Gripper control
|
|
1185
|
+
- wait_for_contact: Wait until force threshold is reached
|
|
1186
|
+
- capture_image: Take a camera image
|
|
1187
|
+
|
|
1188
|
+
EXAMPLE:
|
|
1189
|
+
{
|
|
1190
|
+
"name": "move_to_pose"
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
OUTPUT: Full primitive definition with all parameters, types, defaults, and hardware requirements.""",
|
|
1194
|
+
inputSchema={
|
|
1195
|
+
"type": "object",
|
|
1196
|
+
"properties": {
|
|
1197
|
+
"name": {
|
|
1198
|
+
"type": "string",
|
|
1199
|
+
"description": "Primitive name (e.g., 'move_to_pose', 'close_gripper', 'wait_for_contact')",
|
|
1200
|
+
},
|
|
1201
|
+
},
|
|
1202
|
+
"required": ["name"],
|
|
1203
|
+
},
|
|
1204
|
+
),
|
|
1205
|
+
Tool(
|
|
1206
|
+
name="ate_validate_skill_spec",
|
|
1207
|
+
description="""Validate a skill.yaml file without compiling.
|
|
1208
|
+
|
|
1209
|
+
WHEN TO USE: After creating or modifying a skill.yaml, check for errors before compiling.
|
|
1210
|
+
|
|
1211
|
+
CHECKS PERFORMED:
|
|
1212
|
+
- YAML syntax validity
|
|
1213
|
+
- Required fields (name, version, description, execution)
|
|
1214
|
+
- Parameter type validity
|
|
1215
|
+
- Primitive names exist in registry
|
|
1216
|
+
- Template expression syntax
|
|
1217
|
+
- Hardware requirement format
|
|
1218
|
+
|
|
1219
|
+
EXAMPLE:
|
|
1220
|
+
{
|
|
1221
|
+
"skill_path": "my_skill.skill.yaml"
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
OUTPUT:
|
|
1225
|
+
- If valid: Summary of skill (name, version, parameter count, etc.)
|
|
1226
|
+
- If invalid: List of errors with line numbers and fix suggestions.""",
|
|
1227
|
+
inputSchema={
|
|
1228
|
+
"type": "object",
|
|
1229
|
+
"properties": {
|
|
1230
|
+
"skill_path": {
|
|
1231
|
+
"type": "string",
|
|
1232
|
+
"description": "Path to skill.yaml file to validate",
|
|
1233
|
+
},
|
|
1234
|
+
},
|
|
1235
|
+
"required": ["skill_path"],
|
|
1236
|
+
},
|
|
1237
|
+
),
|
|
1238
|
+
]
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
def get_protocol_tools() -> List[Tool]:
|
|
1242
|
+
"""Protocol registry tools"""
|
|
1243
|
+
return [
|
|
1244
|
+
Tool(
|
|
1245
|
+
name="ate_protocol_list",
|
|
1246
|
+
description="List protocols from the FoodForThought protocol registry. Protocols document how to communicate with robot hardware (BLE, serial, WiFi, CAN, etc.)",
|
|
1247
|
+
inputSchema={
|
|
1248
|
+
"type": "object",
|
|
1249
|
+
"properties": {
|
|
1250
|
+
"robot_model": {
|
|
1251
|
+
"type": "string",
|
|
1252
|
+
"description": "Filter by robot model name (e.g., 'mechdog', 'ur5')",
|
|
1253
|
+
},
|
|
1254
|
+
"transport_type": {
|
|
1255
|
+
"type": "string",
|
|
1256
|
+
"enum": ["ble", "serial", "wifi", "can", "i2c", "spi", "mqtt", "ros2"],
|
|
1257
|
+
"description": "Filter by transport type",
|
|
1258
|
+
},
|
|
1259
|
+
"verified_only": {
|
|
1260
|
+
"type": "boolean",
|
|
1261
|
+
"description": "Show only community-verified protocols",
|
|
1262
|
+
"default": False,
|
|
1263
|
+
},
|
|
1264
|
+
"search": {
|
|
1265
|
+
"type": "string",
|
|
1266
|
+
"description": "Search in command format and discovery notes",
|
|
1267
|
+
},
|
|
1268
|
+
},
|
|
1269
|
+
},
|
|
1270
|
+
),
|
|
1271
|
+
Tool(
|
|
1272
|
+
name="ate_protocol_get",
|
|
1273
|
+
description="Get detailed protocol information including BLE characteristics, serial config, command schema, and associated primitive skills",
|
|
1274
|
+
inputSchema={
|
|
1275
|
+
"type": "object",
|
|
1276
|
+
"properties": {
|
|
1277
|
+
"protocol_id": {
|
|
1278
|
+
"type": "string",
|
|
1279
|
+
"description": "Protocol ID to fetch",
|
|
1280
|
+
},
|
|
1281
|
+
},
|
|
1282
|
+
"required": ["protocol_id"],
|
|
1283
|
+
},
|
|
1284
|
+
),
|
|
1285
|
+
Tool(
|
|
1286
|
+
name="ate_protocol_init",
|
|
1287
|
+
description="Initialize a new protocol template for a robot. Creates protocol.json and README with transport-specific fields to fill in",
|
|
1288
|
+
inputSchema={
|
|
1289
|
+
"type": "object",
|
|
1290
|
+
"properties": {
|
|
1291
|
+
"robot_model": {
|
|
1292
|
+
"type": "string",
|
|
1293
|
+
"description": "Robot model name (e.g., 'hiwonder-mechdog-pro')",
|
|
1294
|
+
},
|
|
1295
|
+
"transport_type": {
|
|
1296
|
+
"type": "string",
|
|
1297
|
+
"enum": ["ble", "serial", "wifi", "can", "i2c", "spi", "mqtt", "ros2"],
|
|
1298
|
+
"description": "Transport type for communication",
|
|
1299
|
+
},
|
|
1300
|
+
"output_dir": {
|
|
1301
|
+
"type": "string",
|
|
1302
|
+
"description": "Output directory for protocol files",
|
|
1303
|
+
"default": "./protocol",
|
|
1304
|
+
},
|
|
1305
|
+
},
|
|
1306
|
+
"required": ["robot_model", "transport_type"],
|
|
1307
|
+
},
|
|
1308
|
+
),
|
|
1309
|
+
Tool(
|
|
1310
|
+
name="ate_protocol_push",
|
|
1311
|
+
description="Upload a protocol definition to FoodForThought registry for community use",
|
|
1312
|
+
inputSchema={
|
|
1313
|
+
"type": "object",
|
|
1314
|
+
"properties": {
|
|
1315
|
+
"protocol_file": {
|
|
1316
|
+
"type": "string",
|
|
1317
|
+
"description": "Path to protocol.json file (default: ./protocol.json)",
|
|
1318
|
+
},
|
|
1319
|
+
},
|
|
1320
|
+
},
|
|
1321
|
+
),
|
|
1322
|
+
Tool(
|
|
1323
|
+
name="ate_protocol_scan_serial",
|
|
1324
|
+
description="Scan for available serial ports on the system. Useful for discovering connected robot hardware",
|
|
1325
|
+
inputSchema={
|
|
1326
|
+
"type": "object",
|
|
1327
|
+
"properties": {},
|
|
1328
|
+
},
|
|
1329
|
+
),
|
|
1330
|
+
Tool(
|
|
1331
|
+
name="ate_protocol_scan_ble",
|
|
1332
|
+
description="Scan for BLE devices in range. Useful for discovering robot devices before connecting",
|
|
1333
|
+
inputSchema={
|
|
1334
|
+
"type": "object",
|
|
1335
|
+
"properties": {},
|
|
1336
|
+
},
|
|
1337
|
+
),
|
|
1338
|
+
]
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
def get_primitive_tools() -> List[Tool]:
|
|
1342
|
+
"""Primitive skills tools"""
|
|
1343
|
+
return [
|
|
1344
|
+
Tool(
|
|
1345
|
+
name="ate_primitive_list",
|
|
1346
|
+
description="List primitive skills - tested atomic robot operations like 'tilt_forward', 'gripper_close', etc. with safe parameter ranges",
|
|
1347
|
+
inputSchema={
|
|
1348
|
+
"type": "object",
|
|
1349
|
+
"properties": {
|
|
1350
|
+
"robot_model": {
|
|
1351
|
+
"type": "string",
|
|
1352
|
+
"description": "Filter by robot model name",
|
|
1353
|
+
},
|
|
1354
|
+
"category": {
|
|
1355
|
+
"type": "string",
|
|
1356
|
+
"enum": ["body_pose", "arm", "gripper", "locomotion", "head", "sensing", "manipulation", "navigation"],
|
|
1357
|
+
"description": "Filter by primitive category",
|
|
1358
|
+
},
|
|
1359
|
+
"status": {
|
|
1360
|
+
"type": "string",
|
|
1361
|
+
"enum": ["experimental", "tested", "verified", "deprecated"],
|
|
1362
|
+
"description": "Filter by status",
|
|
1363
|
+
},
|
|
1364
|
+
"tested_only": {
|
|
1365
|
+
"type": "boolean",
|
|
1366
|
+
"description": "Show only tested/verified primitives",
|
|
1367
|
+
"default": False,
|
|
1368
|
+
},
|
|
1369
|
+
},
|
|
1370
|
+
},
|
|
1371
|
+
),
|
|
1372
|
+
Tool(
|
|
1373
|
+
name="ate_primitive_get",
|
|
1374
|
+
description="Get detailed primitive skill info including command template, tested parameters with safe ranges, timing, safety notes, and dependencies",
|
|
1375
|
+
inputSchema={
|
|
1376
|
+
"type": "object",
|
|
1377
|
+
"properties": {
|
|
1378
|
+
"primitive_id": {
|
|
1379
|
+
"type": "string",
|
|
1380
|
+
"description": "Primitive skill ID to fetch",
|
|
1381
|
+
},
|
|
1382
|
+
},
|
|
1383
|
+
"required": ["primitive_id"],
|
|
1384
|
+
},
|
|
1385
|
+
),
|
|
1386
|
+
Tool(
|
|
1387
|
+
name="ate_primitive_test",
|
|
1388
|
+
description="Submit a test result for a primitive skill. Contributes to reliability score and helps verify safe operation ranges",
|
|
1389
|
+
inputSchema={
|
|
1390
|
+
"type": "object",
|
|
1391
|
+
"properties": {
|
|
1392
|
+
"primitive_id": {
|
|
1393
|
+
"type": "string",
|
|
1394
|
+
"description": "Primitive skill ID to test",
|
|
1395
|
+
},
|
|
1396
|
+
"params": {
|
|
1397
|
+
"type": "string",
|
|
1398
|
+
"description": "Parameters used in test as JSON string (e.g., '{\"pitch\": 15}')",
|
|
1399
|
+
},
|
|
1400
|
+
"result": {
|
|
1401
|
+
"type": "string",
|
|
1402
|
+
"enum": ["pass", "fail", "partial"],
|
|
1403
|
+
"description": "Test result",
|
|
1404
|
+
},
|
|
1405
|
+
"notes": {
|
|
1406
|
+
"type": "string",
|
|
1407
|
+
"description": "Additional notes about the test",
|
|
1408
|
+
},
|
|
1409
|
+
"video_url": {
|
|
1410
|
+
"type": "string",
|
|
1411
|
+
"description": "URL to video recording of test",
|
|
1412
|
+
},
|
|
1413
|
+
},
|
|
1414
|
+
"required": ["primitive_id", "params", "result"],
|
|
1415
|
+
},
|
|
1416
|
+
),
|
|
1417
|
+
Tool(
|
|
1418
|
+
name="ate_primitive_deps_show",
|
|
1419
|
+
description="Show dependency graph for a primitive skill. Shows what primitives it depends on and what depends on it. Indicates deployment readiness",
|
|
1420
|
+
inputSchema={
|
|
1421
|
+
"type": "object",
|
|
1422
|
+
"properties": {
|
|
1423
|
+
"primitive_id": {
|
|
1424
|
+
"type": "string",
|
|
1425
|
+
"description": "Primitive skill ID",
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1428
|
+
"required": ["primitive_id"],
|
|
1429
|
+
},
|
|
1430
|
+
),
|
|
1431
|
+
Tool(
|
|
1432
|
+
name="ate_primitive_deps_add",
|
|
1433
|
+
description="Add a dependency to a primitive skill. Creates deployment gates to ensure required primitives are tested before deployment",
|
|
1434
|
+
inputSchema={
|
|
1435
|
+
"type": "object",
|
|
1436
|
+
"properties": {
|
|
1437
|
+
"primitive_id": {
|
|
1438
|
+
"type": "string",
|
|
1439
|
+
"description": "Primitive skill ID (the one that depends)",
|
|
1440
|
+
},
|
|
1441
|
+
"required_id": {
|
|
1442
|
+
"type": "string",
|
|
1443
|
+
"description": "Required primitive skill ID",
|
|
1444
|
+
},
|
|
1445
|
+
"dependency_type": {
|
|
1446
|
+
"type": "string",
|
|
1447
|
+
"enum": ["requires", "extends", "overrides", "optional"],
|
|
1448
|
+
"description": "Type of dependency",
|
|
1449
|
+
"default": "requires",
|
|
1450
|
+
},
|
|
1451
|
+
"min_status": {
|
|
1452
|
+
"type": "string",
|
|
1453
|
+
"enum": ["experimental", "tested", "verified"],
|
|
1454
|
+
"description": "Minimum required status for deployment",
|
|
1455
|
+
"default": "tested",
|
|
1456
|
+
},
|
|
1457
|
+
},
|
|
1458
|
+
"required": ["primitive_id", "required_id"],
|
|
1459
|
+
},
|
|
1460
|
+
),
|
|
1461
|
+
]
|
|
1462
|
+
|
|
1463
|
+
|
|
1464
|
+
def get_bridge_tools() -> List[Tool]:
|
|
1465
|
+
"""Robot bridge tools for interactive communication"""
|
|
1466
|
+
return [
|
|
1467
|
+
Tool(
|
|
1468
|
+
name="ate_bridge_scan_serial",
|
|
1469
|
+
description="Scan for available serial ports. Use this to discover connected robots before using bridge connect",
|
|
1470
|
+
inputSchema={
|
|
1471
|
+
"type": "object",
|
|
1472
|
+
"properties": {},
|
|
1473
|
+
"required": [],
|
|
1474
|
+
},
|
|
1475
|
+
),
|
|
1476
|
+
Tool(
|
|
1477
|
+
name="ate_bridge_scan_ble",
|
|
1478
|
+
description="Scan for BLE devices. Use this to discover bluetooth robots before using bridge connect",
|
|
1479
|
+
inputSchema={
|
|
1480
|
+
"type": "object",
|
|
1481
|
+
"properties": {},
|
|
1482
|
+
"required": [],
|
|
1483
|
+
},
|
|
1484
|
+
),
|
|
1485
|
+
Tool(
|
|
1486
|
+
name="ate_bridge_send",
|
|
1487
|
+
description="Send a single command to a robot and get the response. Useful for quick tests without opening a full interactive session",
|
|
1488
|
+
inputSchema={
|
|
1489
|
+
"type": "object",
|
|
1490
|
+
"properties": {
|
|
1491
|
+
"port": {
|
|
1492
|
+
"type": "string",
|
|
1493
|
+
"description": "Serial port (e.g., /dev/tty.usbserial-0001) or BLE address",
|
|
1494
|
+
},
|
|
1495
|
+
"command": {
|
|
1496
|
+
"type": "string",
|
|
1497
|
+
"description": "Command to send to the robot",
|
|
1498
|
+
},
|
|
1499
|
+
"transport": {
|
|
1500
|
+
"type": "string",
|
|
1501
|
+
"enum": ["serial", "ble"],
|
|
1502
|
+
"description": "Transport type",
|
|
1503
|
+
"default": "serial",
|
|
1504
|
+
},
|
|
1505
|
+
"baud_rate": {
|
|
1506
|
+
"type": "integer",
|
|
1507
|
+
"description": "Baud rate for serial connection",
|
|
1508
|
+
"default": 115200,
|
|
1509
|
+
},
|
|
1510
|
+
"wait": {
|
|
1511
|
+
"type": "number",
|
|
1512
|
+
"description": "Wait time for response in seconds",
|
|
1513
|
+
"default": 0.5,
|
|
1514
|
+
},
|
|
1515
|
+
},
|
|
1516
|
+
"required": ["port", "command"],
|
|
1517
|
+
},
|
|
1518
|
+
),
|
|
1519
|
+
Tool(
|
|
1520
|
+
name="ate_bridge_replay",
|
|
1521
|
+
description="Replay a recorded robot session. Useful for testing primitives or reproducing sequences",
|
|
1522
|
+
inputSchema={
|
|
1523
|
+
"type": "object",
|
|
1524
|
+
"properties": {
|
|
1525
|
+
"recording": {
|
|
1526
|
+
"type": "string",
|
|
1527
|
+
"description": "Path to recording JSON file",
|
|
1528
|
+
},
|
|
1529
|
+
"port": {
|
|
1530
|
+
"type": "string",
|
|
1531
|
+
"description": "Serial port or BLE address",
|
|
1532
|
+
},
|
|
1533
|
+
"transport": {
|
|
1534
|
+
"type": "string",
|
|
1535
|
+
"enum": ["serial", "ble"],
|
|
1536
|
+
"description": "Transport type",
|
|
1537
|
+
"default": "serial",
|
|
1538
|
+
},
|
|
1539
|
+
"baud_rate": {
|
|
1540
|
+
"type": "integer",
|
|
1541
|
+
"description": "Baud rate for serial connection",
|
|
1542
|
+
"default": 115200,
|
|
1543
|
+
},
|
|
1544
|
+
"speed": {
|
|
1545
|
+
"type": "number",
|
|
1546
|
+
"description": "Playback speed multiplier (1.0 = normal, 2.0 = 2x speed)",
|
|
1547
|
+
"default": 1.0,
|
|
1548
|
+
},
|
|
1549
|
+
},
|
|
1550
|
+
"required": ["recording", "port"],
|
|
1551
|
+
},
|
|
1552
|
+
),
|
|
1553
|
+
]
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
@server.list_tools()
|
|
1557
|
+
async def list_tools() -> List[Tool]:
|
|
1558
|
+
"""List all available MCP tools"""
|
|
1559
|
+
tools = []
|
|
1560
|
+
tools.extend(get_repository_tools())
|
|
1561
|
+
tools.extend(get_robot_tools())
|
|
1562
|
+
tools.extend(get_marketplace_tools()) # Phase 6: Unified marketplace
|
|
1563
|
+
tools.extend(get_compatibility_tools())
|
|
1564
|
+
tools.extend(get_skill_tools())
|
|
1565
|
+
tools.extend(get_protocol_tools())
|
|
1566
|
+
tools.extend(get_primitive_tools())
|
|
1567
|
+
tools.extend(get_bridge_tools())
|
|
1568
|
+
tools.extend(get_parts_tools())
|
|
1569
|
+
tools.extend(get_generate_tools())
|
|
1570
|
+
tools.extend(get_workflow_tools())
|
|
1571
|
+
tools.extend(get_team_tools())
|
|
1572
|
+
tools.extend(get_data_tools())
|
|
1573
|
+
tools.extend(get_deploy_tools())
|
|
1574
|
+
tools.extend(get_test_tools())
|
|
1575
|
+
tools.extend(get_compiler_tools())
|
|
1576
|
+
return tools
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
# ============================================================================
|
|
1580
|
+
# Tool Handlers
|
|
1581
|
+
# ============================================================================
|
|
1582
|
+
|
|
1583
|
+
def capture_output(func, *args, **kwargs):
|
|
1584
|
+
"""Capture printed output from a function"""
|
|
1585
|
+
import io
|
|
1586
|
+
import contextlib
|
|
1587
|
+
|
|
1588
|
+
f = io.StringIO()
|
|
1589
|
+
with contextlib.redirect_stdout(f):
|
|
1590
|
+
try:
|
|
1591
|
+
result = func(*args, **kwargs)
|
|
1592
|
+
except SystemExit:
|
|
1593
|
+
pass # CLI functions may call sys.exit
|
|
1594
|
+
return f.getvalue()
|
|
1595
|
+
|
|
1596
|
+
|
|
1597
|
+
@server.call_tool()
|
|
1598
|
+
async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
|
1599
|
+
"""Handle tool calls"""
|
|
1600
|
+
try:
|
|
1601
|
+
# Repository tools
|
|
1602
|
+
if name == "ate_init":
|
|
1603
|
+
result = client.init(
|
|
1604
|
+
arguments["name"],
|
|
1605
|
+
arguments.get("description", ""),
|
|
1606
|
+
arguments.get("visibility", "public"),
|
|
1607
|
+
)
|
|
1608
|
+
return [
|
|
1609
|
+
TextContent(
|
|
1610
|
+
type="text",
|
|
1611
|
+
text=f"Repository created successfully!\nID: {result['repository']['id']}\nName: {result['repository']['name']}",
|
|
1612
|
+
)
|
|
1613
|
+
]
|
|
1614
|
+
|
|
1615
|
+
elif name == "ate_clone":
|
|
1616
|
+
output = capture_output(
|
|
1617
|
+
client.clone,
|
|
1618
|
+
arguments["repo_id"],
|
|
1619
|
+
arguments.get("target_dir")
|
|
1620
|
+
)
|
|
1621
|
+
return [TextContent(type="text", text=output or f"Repository cloned successfully")]
|
|
1622
|
+
|
|
1623
|
+
elif name == "ate_list_repositories":
|
|
1624
|
+
params = {}
|
|
1625
|
+
if arguments.get("search"):
|
|
1626
|
+
params["search"] = arguments["search"]
|
|
1627
|
+
if arguments.get("robot_model"):
|
|
1628
|
+
params["robotModel"] = arguments["robot_model"]
|
|
1629
|
+
params["limit"] = arguments.get("limit", 20)
|
|
1630
|
+
|
|
1631
|
+
response = client._request("GET", "/repositories", params=params)
|
|
1632
|
+
repos = response.get("repositories", [])
|
|
1633
|
+
|
|
1634
|
+
result_text = f"Found {len(repos)} repositories:\n\n"
|
|
1635
|
+
for repo in repos[:10]:
|
|
1636
|
+
result_text += f"- {repo['name']} (ID: {repo['id']})\n"
|
|
1637
|
+
if repo.get("description"):
|
|
1638
|
+
result_text += f" {repo['description'][:100]}...\n"
|
|
1639
|
+
|
|
1640
|
+
return [TextContent(type="text", text=result_text)]
|
|
1641
|
+
|
|
1642
|
+
elif name == "ate_get_repository":
|
|
1643
|
+
response = client._request("GET", f"/repositories/{arguments['repo_id']}")
|
|
1644
|
+
repo = response.get("repository", {})
|
|
1645
|
+
|
|
1646
|
+
result_text = f"Repository: {repo.get('name', 'Unknown')}\n"
|
|
1647
|
+
result_text += f"ID: {repo.get('id', 'Unknown')}\n"
|
|
1648
|
+
result_text += f"Description: {repo.get('description', 'No description')}\n"
|
|
1649
|
+
result_text += f"Visibility: {repo.get('visibility', 'unknown')}\n"
|
|
1650
|
+
|
|
1651
|
+
return [TextContent(type="text", text=result_text)]
|
|
1652
|
+
|
|
1653
|
+
# Robot tools
|
|
1654
|
+
elif name == "ate_list_robots":
|
|
1655
|
+
params = {}
|
|
1656
|
+
if arguments.get("search"):
|
|
1657
|
+
params["search"] = arguments["search"]
|
|
1658
|
+
if arguments.get("category"):
|
|
1659
|
+
params["category"] = arguments["category"]
|
|
1660
|
+
params["limit"] = arguments.get("limit", 20)
|
|
1661
|
+
|
|
1662
|
+
response = client._request("GET", "/robots/profiles", params=params)
|
|
1663
|
+
robots = response.get("profiles", [])
|
|
1664
|
+
|
|
1665
|
+
result_text = f"Found {len(robots)} robot profiles:\n\n"
|
|
1666
|
+
for robot in robots[:10]:
|
|
1667
|
+
result_text += f"- {robot['modelName']} by {robot['manufacturer']} (ID: {robot['id']})\n"
|
|
1668
|
+
if robot.get("description"):
|
|
1669
|
+
result_text += f" {robot['description'][:100]}...\n"
|
|
1670
|
+
|
|
1671
|
+
return [TextContent(type="text", text=result_text)]
|
|
1672
|
+
|
|
1673
|
+
elif name == "ate_get_robot":
|
|
1674
|
+
response = client._request("GET", f"/robots/profiles/{arguments['robot_id']}")
|
|
1675
|
+
robot = response.get("profile", {})
|
|
1676
|
+
|
|
1677
|
+
result_text = f"Robot: {robot.get('modelName', 'Unknown')}\n"
|
|
1678
|
+
result_text += f"Manufacturer: {robot.get('manufacturer', 'Unknown')}\n"
|
|
1679
|
+
result_text += f"Category: {robot.get('category', 'Unknown')}\n"
|
|
1680
|
+
result_text += f"Description: {robot.get('description', 'No description')}\n"
|
|
1681
|
+
|
|
1682
|
+
return [TextContent(type="text", text=result_text)]
|
|
1683
|
+
|
|
1684
|
+
# Marketplace tools (Phase 6)
|
|
1685
|
+
elif name == "ate_marketplace_robots":
|
|
1686
|
+
params = {}
|
|
1687
|
+
if arguments.get("search"):
|
|
1688
|
+
params["q"] = arguments["search"]
|
|
1689
|
+
if arguments.get("category"):
|
|
1690
|
+
params["category"] = arguments["category"]
|
|
1691
|
+
if arguments.get("sort"):
|
|
1692
|
+
params["sortBy"] = arguments["sort"]
|
|
1693
|
+
params["limit"] = arguments.get("limit", 20)
|
|
1694
|
+
|
|
1695
|
+
response = client._request("GET", "/robots/unified", params=params)
|
|
1696
|
+
robots = response.get("robots", [])
|
|
1697
|
+
|
|
1698
|
+
if not robots:
|
|
1699
|
+
return [TextContent(type="text", text="No robots found matching your criteria.")]
|
|
1700
|
+
|
|
1701
|
+
result_text = f"Found {len(robots)} robots:\n\n"
|
|
1702
|
+
for robot in robots[:20]:
|
|
1703
|
+
result_text += f"- **{robot.get('name', 'Unknown')}** ({robot.get('manufacturer', 'Unknown')})\n"
|
|
1704
|
+
result_text += f" ID: {robot.get('id')} | Category: {robot.get('category')} | DOF: {robot.get('dof', 'N/A')}\n"
|
|
1705
|
+
if robot.get("description"):
|
|
1706
|
+
result_text += f" {robot['description'][:100]}...\n"
|
|
1707
|
+
result_text += "\n"
|
|
1708
|
+
|
|
1709
|
+
return [TextContent(type="text", text=result_text)]
|
|
1710
|
+
|
|
1711
|
+
elif name == "ate_marketplace_robot":
|
|
1712
|
+
robot_id = arguments["robot_id"]
|
|
1713
|
+
response = client._request("GET", f"/robots/unified/{robot_id}")
|
|
1714
|
+
robot = response.get("robot", {})
|
|
1715
|
+
|
|
1716
|
+
if not robot:
|
|
1717
|
+
return [TextContent(type="text", text=f"Robot not found: {robot_id}")]
|
|
1718
|
+
|
|
1719
|
+
result_text = f"# {robot.get('name', 'Unknown')}\n\n"
|
|
1720
|
+
result_text += f"**Manufacturer:** {robot.get('manufacturer', 'Unknown')}\n"
|
|
1721
|
+
result_text += f"**Category:** {robot.get('category', 'Unknown')}\n"
|
|
1722
|
+
result_text += f"**DOF:** {robot.get('dof', 'N/A')}\n"
|
|
1723
|
+
result_text += f"**Downloads:** {robot.get('downloads', 0)}\n\n"
|
|
1724
|
+
|
|
1725
|
+
if robot.get("description"):
|
|
1726
|
+
result_text += f"## Description\n{robot['description']}\n\n"
|
|
1727
|
+
|
|
1728
|
+
links = robot.get("links", [])
|
|
1729
|
+
if links:
|
|
1730
|
+
result_text += f"## Links ({len(links)})\n"
|
|
1731
|
+
for link in links[:5]:
|
|
1732
|
+
result_text += f"- {link.get('name', 'Unnamed')}\n"
|
|
1733
|
+
|
|
1734
|
+
joints = robot.get("joints", [])
|
|
1735
|
+
if joints:
|
|
1736
|
+
result_text += f"\n## Joints ({len(joints)})\n"
|
|
1737
|
+
for joint in joints[:5]:
|
|
1738
|
+
result_text += f"- {joint.get('name', 'Unnamed')}: {joint.get('type', 'unknown')}\n"
|
|
1739
|
+
|
|
1740
|
+
return [TextContent(type="text", text=result_text)]
|
|
1741
|
+
|
|
1742
|
+
elif name == "ate_marketplace_components":
|
|
1743
|
+
params = {}
|
|
1744
|
+
if arguments.get("search"):
|
|
1745
|
+
params["q"] = arguments["search"]
|
|
1746
|
+
if arguments.get("type"):
|
|
1747
|
+
params["type"] = arguments["type"]
|
|
1748
|
+
if arguments.get("sort"):
|
|
1749
|
+
params["sortBy"] = arguments["sort"]
|
|
1750
|
+
params["limit"] = arguments.get("limit", 20)
|
|
1751
|
+
|
|
1752
|
+
response = client._request("GET", "/components", params=params)
|
|
1753
|
+
components = response.get("components", [])
|
|
1754
|
+
|
|
1755
|
+
if not components:
|
|
1756
|
+
return [TextContent(type="text", text="No components found matching your criteria.")]
|
|
1757
|
+
|
|
1758
|
+
result_text = f"Found {len(components)} components:\n\n"
|
|
1759
|
+
for comp in components[:20]:
|
|
1760
|
+
verified = " ✓" if comp.get("verified") else ""
|
|
1761
|
+
result_text += f"- **{comp.get('name', 'Unknown')}** v{comp.get('version', '1.0')}{verified}\n"
|
|
1762
|
+
result_text += f" ID: {comp.get('id')} | Type: {comp.get('type')} | Downloads: {comp.get('downloads', 0)}\n"
|
|
1763
|
+
if comp.get("description"):
|
|
1764
|
+
result_text += f" {comp['description'][:80]}...\n"
|
|
1765
|
+
result_text += "\n"
|
|
1766
|
+
|
|
1767
|
+
return [TextContent(type="text", text=result_text)]
|
|
1768
|
+
|
|
1769
|
+
elif name == "ate_marketplace_component":
|
|
1770
|
+
component_id = arguments["component_id"]
|
|
1771
|
+
response = client._request("GET", f"/components/{component_id}")
|
|
1772
|
+
comp = response.get("component", {})
|
|
1773
|
+
|
|
1774
|
+
if not comp:
|
|
1775
|
+
return [TextContent(type="text", text=f"Component not found: {component_id}")]
|
|
1776
|
+
|
|
1777
|
+
verified = "Yes ✓" if comp.get("verified") else "No"
|
|
1778
|
+
result_text = f"# {comp.get('name', 'Unknown')}\n\n"
|
|
1779
|
+
result_text += f"**Type:** {comp.get('type', 'Unknown')}\n"
|
|
1780
|
+
result_text += f"**Version:** {comp.get('version', '1.0')}\n"
|
|
1781
|
+
result_text += f"**Verified:** {verified}\n"
|
|
1782
|
+
result_text += f"**Downloads:** {comp.get('downloads', 0)}\n\n"
|
|
1783
|
+
|
|
1784
|
+
if comp.get("description"):
|
|
1785
|
+
result_text += f"## Description\n{comp['description']}\n\n"
|
|
1786
|
+
|
|
1787
|
+
return [TextContent(type="text", text=result_text)]
|
|
1788
|
+
|
|
1789
|
+
elif name == "ate_skill_transfer_check":
|
|
1790
|
+
robot_id = arguments["robot_id"]
|
|
1791
|
+
direction = arguments.get("direction", "from")
|
|
1792
|
+
min_score = arguments.get("min_score", 0.4)
|
|
1793
|
+
limit = arguments.get("limit", 10)
|
|
1794
|
+
|
|
1795
|
+
params = {
|
|
1796
|
+
"direction": direction,
|
|
1797
|
+
"minScore": min_score,
|
|
1798
|
+
"limit": limit,
|
|
1799
|
+
}
|
|
1800
|
+
response = client._request("GET", f"/robots/unified/{robot_id}/skill-transfer", params=params)
|
|
1801
|
+
|
|
1802
|
+
if response.get("error"):
|
|
1803
|
+
return [TextContent(type="text", text=f"Error: {response['error']}")]
|
|
1804
|
+
|
|
1805
|
+
source = response.get("sourceRobot", {})
|
|
1806
|
+
results = response.get("results", [])
|
|
1807
|
+
|
|
1808
|
+
if not results:
|
|
1809
|
+
return [TextContent(type="text", text=f"No compatible robots found for skill transfer from {source.get('name', robot_id)}.")]
|
|
1810
|
+
|
|
1811
|
+
result_text = f"# Skill Transfer Compatibility for {source.get('name', 'Unknown')}\n\n"
|
|
1812
|
+
result_text += f"Direction: Skills can transfer **{direction}** this robot\n\n"
|
|
1813
|
+
|
|
1814
|
+
result_text += f"## Compatible Robots ({len(results)})\n\n"
|
|
1815
|
+
for item in results:
|
|
1816
|
+
robot = item.get("robot", {})
|
|
1817
|
+
scores = item.get("scores", {})
|
|
1818
|
+
adaptation = item.get("adaptationType", "unknown")
|
|
1819
|
+
result_text += f"- **{robot.get('name', 'Unknown')}** - {int(scores.get('overall', 0) * 100)}% ({adaptation})\n"
|
|
1820
|
+
result_text += f" Category: {robot.get('category')} | DOF: {robot.get('dof', 'N/A')}\n\n"
|
|
1821
|
+
|
|
1822
|
+
return [TextContent(type="text", text=result_text)]
|
|
1823
|
+
|
|
1824
|
+
elif name == "ate_robot_parts":
|
|
1825
|
+
robot_id = arguments["robot_id"]
|
|
1826
|
+
response = client._request("GET", f"/robots/unified/{robot_id}/parts")
|
|
1827
|
+
|
|
1828
|
+
if response.get("error"):
|
|
1829
|
+
return [TextContent(type="text", text=f"Error: {response['error']}")]
|
|
1830
|
+
|
|
1831
|
+
robot_name = response.get("robotName", robot_id)
|
|
1832
|
+
required = response.get("requiredParts", [])
|
|
1833
|
+
compatible = response.get("compatibleParts", [])
|
|
1834
|
+
|
|
1835
|
+
result_text = f"# Parts for {robot_name}\n\n"
|
|
1836
|
+
|
|
1837
|
+
if required:
|
|
1838
|
+
result_text += f"## Required Parts ({len(required)})\n"
|
|
1839
|
+
for req in required:
|
|
1840
|
+
comp = req.get("component", {})
|
|
1841
|
+
result_text += f"- **{comp.get('name', 'Unknown')}** ({comp.get('type', 'unknown')})\n"
|
|
1842
|
+
result_text += f" Quantity: {req.get('quantity', 1)} | Required: {'Yes' if req.get('required') else 'Optional'}\n\n"
|
|
1843
|
+
else:
|
|
1844
|
+
result_text += "## Required Parts\nNo required parts specified.\n\n"
|
|
1845
|
+
|
|
1846
|
+
if compatible:
|
|
1847
|
+
result_text += f"## Compatible Parts ({len(compatible)})\n"
|
|
1848
|
+
for compat in compatible[:10]:
|
|
1849
|
+
comp = compat.get("component", {})
|
|
1850
|
+
score = compat.get("compatibilityScore", 0)
|
|
1851
|
+
result_text += f"- **{comp.get('name', 'Unknown')}** ({comp.get('type', 'unknown')})\n"
|
|
1852
|
+
result_text += f" Compatibility: {int(score * 100)}%\n\n"
|
|
1853
|
+
|
|
1854
|
+
return [TextContent(type="text", text=result_text)]
|
|
1855
|
+
|
|
1856
|
+
elif name == "ate_component_robots":
|
|
1857
|
+
component_id = arguments["component_id"]
|
|
1858
|
+
response = client._request("GET", f"/components/{component_id}/compatible-robots")
|
|
1859
|
+
|
|
1860
|
+
if response.get("error"):
|
|
1861
|
+
return [TextContent(type="text", text=f"Error: {response['error']}")]
|
|
1862
|
+
|
|
1863
|
+
comp_name = response.get("componentName", component_id)
|
|
1864
|
+
required_by = response.get("requiredBy", [])
|
|
1865
|
+
compatible_with = response.get("compatibleWith", [])
|
|
1866
|
+
|
|
1867
|
+
result_text = f"# Robots for {comp_name}\n\n"
|
|
1868
|
+
|
|
1869
|
+
if required_by:
|
|
1870
|
+
result_text += f"## Required By ({len(required_by)} robots)\n"
|
|
1871
|
+
for req in required_by:
|
|
1872
|
+
robot = req.get("robot", {})
|
|
1873
|
+
result_text += f"- **{robot.get('name', 'Unknown')}** ({robot.get('category', 'unknown')})\n"
|
|
1874
|
+
result_text += f" Quantity: {req.get('quantity', 1)}\n\n"
|
|
1875
|
+
else:
|
|
1876
|
+
result_text += "## Required By\nNo robots require this component.\n\n"
|
|
1877
|
+
|
|
1878
|
+
if compatible_with:
|
|
1879
|
+
result_text += f"## Compatible With ({len(compatible_with)} robots)\n"
|
|
1880
|
+
for compat in compatible_with[:10]:
|
|
1881
|
+
robot = compat.get("robot", {})
|
|
1882
|
+
score = compat.get("compatibilityScore", 0)
|
|
1883
|
+
verified = " ✓" if compat.get("verified") else ""
|
|
1884
|
+
result_text += f"- **{robot.get('name', 'Unknown')}** ({robot.get('category', 'unknown')})\n"
|
|
1885
|
+
result_text += f" Compatibility: {int(score * 100)}%{verified}\n\n"
|
|
921
1886
|
|
|
922
1887
|
return [TextContent(type="text", text=result_text)]
|
|
923
1888
|
|
|
@@ -1151,6 +2116,275 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
|
|
1151
2116
|
)
|
|
1152
2117
|
return [TextContent(type="text", text=output)]
|
|
1153
2118
|
|
|
2119
|
+
# Protocol tools
|
|
2120
|
+
elif name == "ate_protocol_list":
|
|
2121
|
+
output = capture_output(
|
|
2122
|
+
client.protocol_list,
|
|
2123
|
+
arguments.get("robot_model"),
|
|
2124
|
+
arguments.get("transport_type"),
|
|
2125
|
+
arguments.get("verified_only", False),
|
|
2126
|
+
arguments.get("search")
|
|
2127
|
+
)
|
|
2128
|
+
return [TextContent(type="text", text=output)]
|
|
2129
|
+
|
|
2130
|
+
elif name == "ate_protocol_get":
|
|
2131
|
+
output = capture_output(
|
|
2132
|
+
client.protocol_get,
|
|
2133
|
+
arguments["protocol_id"]
|
|
2134
|
+
)
|
|
2135
|
+
return [TextContent(type="text", text=output)]
|
|
2136
|
+
|
|
2137
|
+
elif name == "ate_protocol_init":
|
|
2138
|
+
output = capture_output(
|
|
2139
|
+
client.protocol_init,
|
|
2140
|
+
arguments["robot_model"],
|
|
2141
|
+
arguments["transport_type"],
|
|
2142
|
+
arguments.get("output_dir", "./protocol")
|
|
2143
|
+
)
|
|
2144
|
+
return [TextContent(type="text", text=output)]
|
|
2145
|
+
|
|
2146
|
+
elif name == "ate_protocol_push":
|
|
2147
|
+
output = capture_output(
|
|
2148
|
+
client.protocol_push,
|
|
2149
|
+
arguments.get("protocol_file")
|
|
2150
|
+
)
|
|
2151
|
+
return [TextContent(type="text", text=output)]
|
|
2152
|
+
|
|
2153
|
+
elif name == "ate_protocol_scan_serial":
|
|
2154
|
+
output = capture_output(client.protocol_scan_serial)
|
|
2155
|
+
return [TextContent(type="text", text=output)]
|
|
2156
|
+
|
|
2157
|
+
elif name == "ate_protocol_scan_ble":
|
|
2158
|
+
output = capture_output(client.protocol_scan_ble)
|
|
2159
|
+
return [TextContent(type="text", text=output)]
|
|
2160
|
+
|
|
2161
|
+
# Primitive tools
|
|
2162
|
+
elif name == "ate_primitive_list":
|
|
2163
|
+
output = capture_output(
|
|
2164
|
+
client.primitive_list,
|
|
2165
|
+
arguments.get("robot_model"),
|
|
2166
|
+
arguments.get("category"),
|
|
2167
|
+
arguments.get("status"),
|
|
2168
|
+
arguments.get("tested_only", False)
|
|
2169
|
+
)
|
|
2170
|
+
return [TextContent(type="text", text=output)]
|
|
2171
|
+
|
|
2172
|
+
elif name == "ate_primitive_get":
|
|
2173
|
+
output = capture_output(
|
|
2174
|
+
client.primitive_get,
|
|
2175
|
+
arguments["primitive_id"]
|
|
2176
|
+
)
|
|
2177
|
+
return [TextContent(type="text", text=output)]
|
|
2178
|
+
|
|
2179
|
+
elif name == "ate_primitive_test":
|
|
2180
|
+
output = capture_output(
|
|
2181
|
+
client.primitive_test,
|
|
2182
|
+
arguments["primitive_id"],
|
|
2183
|
+
arguments["params"],
|
|
2184
|
+
arguments["result"],
|
|
2185
|
+
arguments.get("notes"),
|
|
2186
|
+
arguments.get("video_url")
|
|
2187
|
+
)
|
|
2188
|
+
return [TextContent(type="text", text=output)]
|
|
2189
|
+
|
|
2190
|
+
elif name == "ate_primitive_deps_show":
|
|
2191
|
+
output = capture_output(
|
|
2192
|
+
client.primitive_deps_show,
|
|
2193
|
+
arguments["primitive_id"]
|
|
2194
|
+
)
|
|
2195
|
+
return [TextContent(type="text", text=output)]
|
|
2196
|
+
|
|
2197
|
+
elif name == "ate_primitive_deps_add":
|
|
2198
|
+
output = capture_output(
|
|
2199
|
+
client.primitive_deps_add,
|
|
2200
|
+
arguments["primitive_id"],
|
|
2201
|
+
arguments["required_id"],
|
|
2202
|
+
arguments.get("dependency_type", "requires"),
|
|
2203
|
+
arguments.get("min_status", "tested")
|
|
2204
|
+
)
|
|
2205
|
+
return [TextContent(type="text", text=output)]
|
|
2206
|
+
|
|
2207
|
+
# Bridge tools
|
|
2208
|
+
elif name == "ate_bridge_scan_serial":
|
|
2209
|
+
output = capture_output(client.protocol_scan_serial)
|
|
2210
|
+
return [TextContent(type="text", text=output)]
|
|
2211
|
+
|
|
2212
|
+
elif name == "ate_bridge_scan_ble":
|
|
2213
|
+
output = capture_output(client.protocol_scan_ble)
|
|
2214
|
+
return [TextContent(type="text", text=output)]
|
|
2215
|
+
|
|
2216
|
+
elif name == "ate_bridge_send":
|
|
2217
|
+
output = capture_output(
|
|
2218
|
+
client.bridge_send,
|
|
2219
|
+
arguments["port"],
|
|
2220
|
+
arguments["command"],
|
|
2221
|
+
arguments.get("transport", "serial"),
|
|
2222
|
+
arguments.get("baud_rate", 115200),
|
|
2223
|
+
arguments.get("wait", 0.5)
|
|
2224
|
+
)
|
|
2225
|
+
return [TextContent(type="text", text=output if output else "Command sent (no response)")]
|
|
2226
|
+
|
|
2227
|
+
elif name == "ate_bridge_replay":
|
|
2228
|
+
output = capture_output(
|
|
2229
|
+
client.bridge_replay,
|
|
2230
|
+
arguments["recording"],
|
|
2231
|
+
arguments["port"],
|
|
2232
|
+
arguments.get("transport", "serial"),
|
|
2233
|
+
arguments.get("baud_rate", 115200),
|
|
2234
|
+
arguments.get("speed", 1.0)
|
|
2235
|
+
)
|
|
2236
|
+
return [TextContent(type="text", text=output)]
|
|
2237
|
+
|
|
2238
|
+
# Compiler tools
|
|
2239
|
+
elif name == "ate_compile_skill":
|
|
2240
|
+
output = capture_output(
|
|
2241
|
+
client.compile_skill,
|
|
2242
|
+
arguments["skill_path"],
|
|
2243
|
+
arguments.get("output", "./output"),
|
|
2244
|
+
arguments.get("target", "python"),
|
|
2245
|
+
arguments.get("robot"),
|
|
2246
|
+
arguments.get("ate_dir")
|
|
2247
|
+
)
|
|
2248
|
+
return [TextContent(type="text", text=output or "Skill compiled successfully")]
|
|
2249
|
+
|
|
2250
|
+
elif name == "ate_test_compiled_skill":
|
|
2251
|
+
output = capture_output(
|
|
2252
|
+
client.test_compiled_skill,
|
|
2253
|
+
arguments["skill_path"],
|
|
2254
|
+
arguments.get("mode", "dry-run"),
|
|
2255
|
+
arguments.get("robot_port"),
|
|
2256
|
+
arguments.get("params", {})
|
|
2257
|
+
)
|
|
2258
|
+
return [TextContent(type="text", text=output or "Skill test completed")]
|
|
2259
|
+
|
|
2260
|
+
elif name == "ate_publish_compiled_skill":
|
|
2261
|
+
output = capture_output(
|
|
2262
|
+
client.publish_compiled_skill,
|
|
2263
|
+
arguments["skill_path"],
|
|
2264
|
+
arguments.get("visibility", "public")
|
|
2265
|
+
)
|
|
2266
|
+
return [TextContent(type="text", text=output or "Skill published successfully")]
|
|
2267
|
+
|
|
2268
|
+
elif name == "ate_check_skill_compatibility":
|
|
2269
|
+
output = capture_output(
|
|
2270
|
+
client.check_skill_compatibility,
|
|
2271
|
+
arguments["skill_path"],
|
|
2272
|
+
arguments.get("robot_urdf"),
|
|
2273
|
+
arguments.get("robot_ate_dir")
|
|
2274
|
+
)
|
|
2275
|
+
return [TextContent(type="text", text=output or "Compatibility check completed")]
|
|
2276
|
+
|
|
2277
|
+
elif name == "ate_list_primitives":
|
|
2278
|
+
from ate.primitives import PRIMITIVE_REGISTRY, PrimitiveCategory
|
|
2279
|
+
|
|
2280
|
+
category = arguments.get("category", "all")
|
|
2281
|
+
hardware = arguments.get("hardware")
|
|
2282
|
+
|
|
2283
|
+
result_text = "# Available Primitives\n\n"
|
|
2284
|
+
|
|
2285
|
+
for prim_name, prim_def in PRIMITIVE_REGISTRY.items():
|
|
2286
|
+
# Filter by category
|
|
2287
|
+
if category != "all":
|
|
2288
|
+
cat_match = prim_def.get("category", "").lower() == category.lower()
|
|
2289
|
+
if not cat_match:
|
|
2290
|
+
continue
|
|
2291
|
+
|
|
2292
|
+
# Filter by hardware
|
|
2293
|
+
if hardware:
|
|
2294
|
+
req_hardware = prim_def.get("hardware", [])
|
|
2295
|
+
if hardware.lower() not in [h.lower() for h in req_hardware]:
|
|
2296
|
+
continue
|
|
2297
|
+
|
|
2298
|
+
result_text += f"## {prim_name}\n"
|
|
2299
|
+
result_text += f"**Category:** {prim_def.get('category', 'unknown')}\n"
|
|
2300
|
+
result_text += f"**Description:** {prim_def.get('description', 'No description')}\n"
|
|
2301
|
+
|
|
2302
|
+
# Parameters
|
|
2303
|
+
params = prim_def.get("parameters", {})
|
|
2304
|
+
if params:
|
|
2305
|
+
result_text += "**Parameters:**\n"
|
|
2306
|
+
for param_name, param_def in params.items():
|
|
2307
|
+
required = "required" if param_def.get("required", False) else "optional"
|
|
2308
|
+
result_text += f" - `{param_name}` ({param_def.get('type', 'any')}, {required}): {param_def.get('description', '')}\n"
|
|
2309
|
+
|
|
2310
|
+
# Hardware requirements
|
|
2311
|
+
hw_reqs = prim_def.get("hardware", [])
|
|
2312
|
+
if hw_reqs:
|
|
2313
|
+
result_text += f"**Hardware:** {', '.join(hw_reqs)}\n"
|
|
2314
|
+
|
|
2315
|
+
result_text += "\n"
|
|
2316
|
+
|
|
2317
|
+
return [TextContent(type="text", text=result_text)]
|
|
2318
|
+
|
|
2319
|
+
elif name == "ate_get_primitive":
|
|
2320
|
+
from ate.primitives import get_primitive
|
|
2321
|
+
|
|
2322
|
+
prim_name = arguments["name"]
|
|
2323
|
+
prim_def = get_primitive(prim_name)
|
|
2324
|
+
|
|
2325
|
+
if not prim_def:
|
|
2326
|
+
return [TextContent(type="text", text=f"Primitive not found: {prim_name}")]
|
|
2327
|
+
|
|
2328
|
+
result_text = f"# {prim_name}\n\n"
|
|
2329
|
+
result_text += f"**Category:** {prim_def.get('category', 'unknown')}\n"
|
|
2330
|
+
result_text += f"**Description:** {prim_def.get('description', 'No description')}\n\n"
|
|
2331
|
+
|
|
2332
|
+
# Parameters
|
|
2333
|
+
params = prim_def.get("parameters", {})
|
|
2334
|
+
if params:
|
|
2335
|
+
result_text += "## Parameters\n\n"
|
|
2336
|
+
for param_name, param_def in params.items():
|
|
2337
|
+
required = "✓ Required" if param_def.get("required", False) else "○ Optional"
|
|
2338
|
+
default = f", default: `{param_def.get('default')}`" if "default" in param_def else ""
|
|
2339
|
+
result_text += f"### `{param_name}`\n"
|
|
2340
|
+
result_text += f"- **Type:** {param_def.get('type', 'any')}\n"
|
|
2341
|
+
result_text += f"- **Status:** {required}{default}\n"
|
|
2342
|
+
result_text += f"- **Description:** {param_def.get('description', '')}\n\n"
|
|
2343
|
+
|
|
2344
|
+
# Hardware requirements
|
|
2345
|
+
hw_reqs = prim_def.get("hardware", [])
|
|
2346
|
+
if hw_reqs:
|
|
2347
|
+
result_text += f"## Hardware Requirements\n\n"
|
|
2348
|
+
for hw in hw_reqs:
|
|
2349
|
+
result_text += f"- {hw}\n"
|
|
2350
|
+
|
|
2351
|
+
# Return type
|
|
2352
|
+
result_text += f"\n## Returns\n\n`{prim_def.get('returns', 'bool')}`\n"
|
|
2353
|
+
|
|
2354
|
+
return [TextContent(type="text", text=result_text)]
|
|
2355
|
+
|
|
2356
|
+
elif name == "ate_validate_skill_spec":
|
|
2357
|
+
from ate.skill_schema import SkillSpecification
|
|
2358
|
+
|
|
2359
|
+
skill_path = arguments["skill_path"]
|
|
2360
|
+
|
|
2361
|
+
try:
|
|
2362
|
+
spec = SkillSpecification.from_yaml(skill_path)
|
|
2363
|
+
errors = spec.validate()
|
|
2364
|
+
|
|
2365
|
+
if errors:
|
|
2366
|
+
result_text = f"# Validation Failed\n\n"
|
|
2367
|
+
result_text += f"Found {len(errors)} error(s) in `{skill_path}`:\n\n"
|
|
2368
|
+
for error in errors:
|
|
2369
|
+
result_text += f"- ❌ {error}\n"
|
|
2370
|
+
return [TextContent(type="text", text=result_text)]
|
|
2371
|
+
|
|
2372
|
+
result_text = f"# Validation Passed ✓\n\n"
|
|
2373
|
+
result_text += f"**Skill:** {spec.name}\n"
|
|
2374
|
+
result_text += f"**Version:** {spec.version}\n"
|
|
2375
|
+
result_text += f"**Description:** {spec.description}\n\n"
|
|
2376
|
+
|
|
2377
|
+
# Summary
|
|
2378
|
+
result_text += "## Summary\n\n"
|
|
2379
|
+
result_text += f"- **Parameters:** {len(spec.parameters)}\n"
|
|
2380
|
+
result_text += f"- **Hardware Requirements:** {len(spec.hardware_requirements)}\n"
|
|
2381
|
+
result_text += f"- **Execution Steps:** {len(spec.execution)}\n"
|
|
2382
|
+
result_text += f"- **Success Criteria:** {len(spec.success_criteria)}\n"
|
|
2383
|
+
|
|
2384
|
+
return [TextContent(type="text", text=result_text)]
|
|
2385
|
+
except Exception as e:
|
|
2386
|
+
return [TextContent(type="text", text=f"# Validation Error\n\nFailed to parse skill specification:\n\n```\n{str(e)}\n```")]
|
|
2387
|
+
|
|
1154
2388
|
else:
|
|
1155
2389
|
return [
|
|
1156
2390
|
TextContent(
|