foodforthought-cli 0.2.0__py3-none-any.whl → 0.2.1__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/mcp_server.py CHANGED
@@ -29,6 +29,7 @@ import json
29
29
  import os
30
30
  import sys
31
31
  from typing import Any, Dict, List, Optional
32
+ from pathlib import Path
32
33
 
33
34
  # Import the existing CLI client
34
35
  from ate.cli import ATEClient
@@ -64,13 +65,16 @@ server = Server("foodforthought")
64
65
  client = ATEClient()
65
66
 
66
67
 
67
- @server.list_tools()
68
- async def list_tools() -> List[Tool]:
69
- """List all available MCP tools"""
68
+ # ============================================================================
69
+ # Tool Definitions
70
+ # ============================================================================
71
+
72
+ def get_repository_tools() -> List[Tool]:
73
+ """Repository management tools"""
70
74
  return [
71
75
  Tool(
72
76
  name="ate_init",
73
- description="Initialize a new FoodforThought repository",
77
+ description="Initialize a new FoodforThought repository for robot skills",
74
78
  inputSchema={
75
79
  "type": "object",
76
80
  "properties": {
@@ -133,111 +137,708 @@ async def list_tools() -> List[Tool]:
133
137
  },
134
138
  ),
135
139
  Tool(
136
- name="ate_list_robots",
137
- description="List available robot profiles",
140
+ name="ate_get_repository",
141
+ description="Get details of a specific repository",
142
+ inputSchema={
143
+ "type": "object",
144
+ "properties": {
145
+ "repo_id": {
146
+ "type": "string",
147
+ "description": "Repository ID",
148
+ },
149
+ },
150
+ "required": ["repo_id"],
151
+ },
152
+ ),
153
+ ]
154
+
155
+
156
+ def get_robot_tools() -> List[Tool]:
157
+ """Robot profile tools"""
158
+ return [
159
+ Tool(
160
+ name="ate_list_robots",
161
+ description="List available robot profiles",
162
+ inputSchema={
163
+ "type": "object",
164
+ "properties": {
165
+ "search": {
166
+ "type": "string",
167
+ "description": "Search query",
168
+ },
169
+ "category": {
170
+ "type": "string",
171
+ "description": "Filter by category",
172
+ },
173
+ "limit": {
174
+ "type": "number",
175
+ "description": "Maximum number of results",
176
+ "default": 20,
177
+ },
178
+ },
179
+ },
180
+ ),
181
+ Tool(
182
+ name="ate_get_robot",
183
+ description="Get details of a specific robot profile",
184
+ inputSchema={
185
+ "type": "object",
186
+ "properties": {
187
+ "robot_id": {
188
+ "type": "string",
189
+ "description": "Robot profile ID",
190
+ },
191
+ },
192
+ "required": ["robot_id"],
193
+ },
194
+ ),
195
+ ]
196
+
197
+
198
+ def get_compatibility_tools() -> List[Tool]:
199
+ """Skill compatibility and adaptation tools"""
200
+ return [
201
+ Tool(
202
+ name="ate_check_transfer",
203
+ description="Check skill transfer compatibility between two robot models",
204
+ inputSchema={
205
+ "type": "object",
206
+ "properties": {
207
+ "source_robot": {
208
+ "type": "string",
209
+ "description": "Source robot model name",
210
+ },
211
+ "target_robot": {
212
+ "type": "string",
213
+ "description": "Target robot model name",
214
+ },
215
+ "skill_id": {
216
+ "type": "string",
217
+ "description": "Optional skill ID to check",
218
+ },
219
+ "min_score": {
220
+ "type": "number",
221
+ "description": "Minimum compatibility score threshold (0.0-1.0)",
222
+ "default": 0.0,
223
+ },
224
+ },
225
+ "required": ["source_robot", "target_robot"],
226
+ },
227
+ ),
228
+ Tool(
229
+ name="ate_adapt",
230
+ description="Generate adaptation plan for transferring skills between robots",
231
+ inputSchema={
232
+ "type": "object",
233
+ "properties": {
234
+ "source_robot": {
235
+ "type": "string",
236
+ "description": "Source robot model",
237
+ },
238
+ "target_robot": {
239
+ "type": "string",
240
+ "description": "Target robot model",
241
+ },
242
+ "repo_id": {
243
+ "type": "string",
244
+ "description": "Repository ID to adapt",
245
+ },
246
+ "analyze_only": {
247
+ "type": "boolean",
248
+ "description": "Only show compatibility analysis",
249
+ "default": True,
250
+ },
251
+ },
252
+ "required": ["source_robot", "target_robot"],
253
+ },
254
+ ),
255
+ ]
256
+
257
+
258
+ def get_skill_tools() -> List[Tool]:
259
+ """Skill data tools"""
260
+ return [
261
+ Tool(
262
+ name="ate_pull",
263
+ description="Pull skill data for training in various formats (JSON, RLDS, LeRobot)",
264
+ inputSchema={
265
+ "type": "object",
266
+ "properties": {
267
+ "skill_id": {
268
+ "type": "string",
269
+ "description": "Skill ID to pull",
270
+ },
271
+ "robot": {
272
+ "type": "string",
273
+ "description": "Filter by robot model",
274
+ },
275
+ "format": {
276
+ "type": "string",
277
+ "enum": ["json", "rlds", "lerobot"],
278
+ "description": "Output format",
279
+ "default": "json",
280
+ },
281
+ "output": {
282
+ "type": "string",
283
+ "description": "Output directory",
284
+ "default": "./data",
285
+ },
286
+ },
287
+ "required": ["skill_id"],
288
+ },
289
+ ),
290
+ Tool(
291
+ name="ate_upload",
292
+ description="Upload demonstration videos for community labeling",
293
+ inputSchema={
294
+ "type": "object",
295
+ "properties": {
296
+ "path": {
297
+ "type": "string",
298
+ "description": "Path to video file",
299
+ },
300
+ "robot": {
301
+ "type": "string",
302
+ "description": "Robot model in the video",
303
+ },
304
+ "task": {
305
+ "type": "string",
306
+ "description": "Task being demonstrated",
307
+ },
308
+ "project": {
309
+ "type": "string",
310
+ "description": "Project ID to associate with",
311
+ },
312
+ },
313
+ "required": ["path", "robot", "task"],
314
+ },
315
+ ),
316
+ ]
317
+
318
+
319
+ def get_parts_tools() -> List[Tool]:
320
+ """Hardware parts management tools"""
321
+ return [
322
+ Tool(
323
+ name="ate_parts_list",
324
+ description="List available hardware parts in the catalog",
325
+ inputSchema={
326
+ "type": "object",
327
+ "properties": {
328
+ "category": {
329
+ "type": "string",
330
+ "enum": ["gripper", "sensor", "actuator", "controller",
331
+ "end-effector", "camera", "lidar", "force-torque"],
332
+ "description": "Filter by part category",
333
+ },
334
+ "manufacturer": {
335
+ "type": "string",
336
+ "description": "Filter by manufacturer",
337
+ },
338
+ "search": {
339
+ "type": "string",
340
+ "description": "Search by name or part number",
341
+ },
342
+ },
343
+ },
344
+ ),
345
+ Tool(
346
+ name="ate_parts_check",
347
+ description="Check part compatibility requirements for a skill",
348
+ inputSchema={
349
+ "type": "object",
350
+ "properties": {
351
+ "skill_id": {
352
+ "type": "string",
353
+ "description": "Skill ID to check parts for",
354
+ },
355
+ },
356
+ "required": ["skill_id"],
357
+ },
358
+ ),
359
+ Tool(
360
+ name="ate_parts_require",
361
+ description="Add a part dependency to a skill",
362
+ inputSchema={
363
+ "type": "object",
364
+ "properties": {
365
+ "part_id": {
366
+ "type": "string",
367
+ "description": "Part ID to require",
368
+ },
369
+ "skill_id": {
370
+ "type": "string",
371
+ "description": "Skill ID",
372
+ },
373
+ "version": {
374
+ "type": "string",
375
+ "description": "Minimum version",
376
+ "default": "1.0.0",
377
+ },
378
+ "required": {
379
+ "type": "boolean",
380
+ "description": "Mark as required (not optional)",
381
+ "default": False,
382
+ },
383
+ },
384
+ "required": ["part_id", "skill_id"],
385
+ },
386
+ ),
387
+ Tool(
388
+ name="ate_deps_audit",
389
+ description="Audit and verify all dependencies are compatible for a skill",
390
+ inputSchema={
391
+ "type": "object",
392
+ "properties": {
393
+ "skill_id": {
394
+ "type": "string",
395
+ "description": "Skill ID (optional, uses current repo if not specified)",
396
+ },
397
+ },
398
+ },
399
+ ),
400
+ ]
401
+
402
+
403
+ def get_generate_tools() -> List[Tool]:
404
+ """Skill generation tools"""
405
+ return [
406
+ Tool(
407
+ name="ate_generate",
408
+ description="Generate skill scaffolding from a natural language task description",
409
+ inputSchema={
410
+ "type": "object",
411
+ "properties": {
412
+ "description": {
413
+ "type": "string",
414
+ "description": "Natural language task description (e.g., 'pick up box and place on pallet')",
415
+ },
416
+ "robot": {
417
+ "type": "string",
418
+ "description": "Target robot model",
419
+ "default": "ur5",
420
+ },
421
+ "output": {
422
+ "type": "string",
423
+ "description": "Output directory for generated files",
424
+ "default": "./new-skill",
425
+ },
426
+ },
427
+ "required": ["description"],
428
+ },
429
+ ),
430
+ ]
431
+
432
+
433
+ def get_workflow_tools() -> List[Tool]:
434
+ """Workflow composition tools"""
435
+ return [
436
+ Tool(
437
+ name="ate_workflow_validate",
438
+ description="Validate a workflow YAML file for skill composition",
439
+ inputSchema={
440
+ "type": "object",
441
+ "properties": {
442
+ "path": {
443
+ "type": "string",
444
+ "description": "Path to workflow YAML file",
445
+ },
446
+ },
447
+ "required": ["path"],
448
+ },
449
+ ),
450
+ Tool(
451
+ name="ate_workflow_run",
452
+ description="Run a skill workflow/pipeline",
453
+ inputSchema={
454
+ "type": "object",
455
+ "properties": {
456
+ "path": {
457
+ "type": "string",
458
+ "description": "Path to workflow YAML file",
459
+ },
460
+ "sim": {
461
+ "type": "boolean",
462
+ "description": "Run in simulation mode",
463
+ "default": True,
464
+ },
465
+ "dry_run": {
466
+ "type": "boolean",
467
+ "description": "Show execution plan without running",
468
+ "default": False,
469
+ },
470
+ },
471
+ "required": ["path"],
472
+ },
473
+ ),
474
+ Tool(
475
+ name="ate_workflow_export",
476
+ description="Export workflow to other formats (ROS2 launch, JSON)",
477
+ inputSchema={
478
+ "type": "object",
479
+ "properties": {
480
+ "path": {
481
+ "type": "string",
482
+ "description": "Path to workflow YAML file",
483
+ },
484
+ "format": {
485
+ "type": "string",
486
+ "enum": ["ros2", "json"],
487
+ "description": "Export format",
488
+ "default": "ros2",
489
+ },
490
+ "output": {
491
+ "type": "string",
492
+ "description": "Output file path",
493
+ },
494
+ },
495
+ "required": ["path"],
496
+ },
497
+ ),
498
+ ]
499
+
500
+
501
+ def get_team_tools() -> List[Tool]:
502
+ """Team collaboration tools"""
503
+ return [
504
+ Tool(
505
+ name="ate_team_create",
506
+ description="Create a new team for collaboration",
507
+ inputSchema={
508
+ "type": "object",
509
+ "properties": {
510
+ "name": {
511
+ "type": "string",
512
+ "description": "Team name",
513
+ },
514
+ "description": {
515
+ "type": "string",
516
+ "description": "Team description",
517
+ },
518
+ },
519
+ "required": ["name"],
520
+ },
521
+ ),
522
+ Tool(
523
+ name="ate_team_list",
524
+ description="List teams you belong to",
525
+ inputSchema={
526
+ "type": "object",
527
+ "properties": {},
528
+ },
529
+ ),
530
+ Tool(
531
+ name="ate_team_invite",
532
+ description="Invite a user to a team",
533
+ inputSchema={
534
+ "type": "object",
535
+ "properties": {
536
+ "email": {
537
+ "type": "string",
538
+ "description": "Email of user to invite",
539
+ },
540
+ "team": {
541
+ "type": "string",
542
+ "description": "Team slug",
543
+ },
544
+ "role": {
545
+ "type": "string",
546
+ "enum": ["owner", "admin", "member", "viewer"],
547
+ "description": "Role to assign",
548
+ "default": "member",
549
+ },
550
+ },
551
+ "required": ["email", "team"],
552
+ },
553
+ ),
554
+ Tool(
555
+ name="ate_team_share",
556
+ description="Share a skill with a team",
557
+ inputSchema={
558
+ "type": "object",
559
+ "properties": {
560
+ "skill_id": {
561
+ "type": "string",
562
+ "description": "Skill ID to share",
563
+ },
564
+ "team": {
565
+ "type": "string",
566
+ "description": "Team slug",
567
+ },
568
+ },
569
+ "required": ["skill_id", "team"],
570
+ },
571
+ ),
572
+ ]
573
+
574
+
575
+ def get_data_tools() -> List[Tool]:
576
+ """Dataset management tools"""
577
+ return [
578
+ Tool(
579
+ name="ate_data_upload",
580
+ description="Upload sensor data or demonstration logs for a skill",
581
+ inputSchema={
582
+ "type": "object",
583
+ "properties": {
584
+ "path": {
585
+ "type": "string",
586
+ "description": "Path to data directory or file",
587
+ },
588
+ "skill": {
589
+ "type": "string",
590
+ "description": "Associated skill ID",
591
+ },
592
+ "stage": {
593
+ "type": "string",
594
+ "enum": ["raw", "annotated", "skill-abstracted", "production"],
595
+ "description": "Data stage",
596
+ "default": "raw",
597
+ },
598
+ },
599
+ "required": ["path", "skill"],
600
+ },
601
+ ),
602
+ Tool(
603
+ name="ate_data_list",
604
+ description="List datasets for a skill",
605
+ inputSchema={
606
+ "type": "object",
607
+ "properties": {
608
+ "skill": {
609
+ "type": "string",
610
+ "description": "Filter by skill ID",
611
+ },
612
+ "stage": {
613
+ "type": "string",
614
+ "description": "Filter by data stage",
615
+ },
616
+ },
617
+ },
618
+ ),
619
+ Tool(
620
+ name="ate_data_promote",
621
+ description="Promote a dataset to the next stage in the pipeline",
622
+ inputSchema={
623
+ "type": "object",
624
+ "properties": {
625
+ "dataset_id": {
626
+ "type": "string",
627
+ "description": "Dataset ID",
628
+ },
629
+ "to_stage": {
630
+ "type": "string",
631
+ "enum": ["annotated", "skill-abstracted", "production"],
632
+ "description": "Target stage",
633
+ },
634
+ },
635
+ "required": ["dataset_id", "to_stage"],
636
+ },
637
+ ),
638
+ Tool(
639
+ name="ate_data_export",
640
+ description="Export a dataset in various formats",
641
+ inputSchema={
642
+ "type": "object",
643
+ "properties": {
644
+ "dataset_id": {
645
+ "type": "string",
646
+ "description": "Dataset ID",
647
+ },
648
+ "format": {
649
+ "type": "string",
650
+ "enum": ["json", "rlds", "lerobot", "hdf5"],
651
+ "description": "Export format",
652
+ "default": "rlds",
653
+ },
654
+ "output": {
655
+ "type": "string",
656
+ "description": "Output directory",
657
+ "default": "./export",
658
+ },
659
+ },
660
+ "required": ["dataset_id"],
661
+ },
662
+ ),
663
+ ]
664
+
665
+
666
+ def get_deploy_tools() -> List[Tool]:
667
+ """Deployment management tools"""
668
+ return [
669
+ Tool(
670
+ name="ate_deploy",
671
+ description="Deploy skills to a robot",
138
672
  inputSchema={
139
673
  "type": "object",
140
674
  "properties": {
141
- "search": {
675
+ "robot_type": {
142
676
  "type": "string",
143
- "description": "Search query",
677
+ "description": "Robot type to deploy to",
144
678
  },
145
- "category": {
679
+ "repo_id": {
146
680
  "type": "string",
147
- "description": "Filter by category",
148
- },
149
- "limit": {
150
- "type": "number",
151
- "description": "Maximum number of results",
152
- "default": 20,
681
+ "description": "Repository ID (uses current repo if not specified)",
153
682
  },
154
683
  },
684
+ "required": ["robot_type"],
155
685
  },
156
686
  ),
157
687
  Tool(
158
- name="ate_compatibility",
159
- description="Check skill compatibility between two robot models",
688
+ name="ate_deploy_config",
689
+ description="Deploy skills using a deployment configuration file (supports hybrid edge/cloud)",
160
690
  inputSchema={
161
691
  "type": "object",
162
692
  "properties": {
163
- "source_robot_id": {
693
+ "config_path": {
164
694
  "type": "string",
165
- "description": "Source robot ID",
695
+ "description": "Path to deploy.yaml configuration",
166
696
  },
167
- "target_robot_id": {
697
+ "target": {
168
698
  "type": "string",
169
- "description": "Target robot ID",
699
+ "description": "Target fleet or robot",
170
700
  },
171
- "repository_id": {
172
- "type": "string",
173
- "description": "Repository ID to check compatibility for",
701
+ "dry_run": {
702
+ "type": "boolean",
703
+ "description": "Show deployment plan without executing",
704
+ "default": False,
174
705
  },
175
706
  },
176
- "required": ["source_robot_id", "target_robot_id", "repository_id"],
707
+ "required": ["config_path", "target"],
177
708
  },
178
709
  ),
179
710
  Tool(
180
- name="ate_adapt",
181
- description="Generate adaptation plan for transferring skills between robots",
711
+ name="ate_deploy_status",
712
+ description="Check deployment status for a fleet or robot",
182
713
  inputSchema={
183
714
  "type": "object",
184
715
  "properties": {
185
- "source_robot_id": {
716
+ "target": {
186
717
  "type": "string",
187
- "description": "Source robot ID",
718
+ "description": "Target fleet or robot",
188
719
  },
189
- "target_robot_id": {
720
+ },
721
+ "required": ["target"],
722
+ },
723
+ ),
724
+ ]
725
+
726
+
727
+ def get_test_tools() -> List[Tool]:
728
+ """Testing and validation tools"""
729
+ return [
730
+ Tool(
731
+ name="ate_test",
732
+ description="Test skills in simulation (Gazebo, MuJoCo, PyBullet, Webots)",
733
+ inputSchema={
734
+ "type": "object",
735
+ "properties": {
736
+ "environment": {
190
737
  "type": "string",
191
- "description": "Target robot ID",
738
+ "enum": ["gazebo", "mujoco", "pybullet", "webots"],
739
+ "description": "Simulation environment",
740
+ "default": "pybullet",
192
741
  },
193
- "repository_id": {
742
+ "robot": {
194
743
  "type": "string",
195
- "description": "Repository ID to adapt",
744
+ "description": "Robot model to test with",
196
745
  },
197
- "analyze_only": {
746
+ "local": {
198
747
  "type": "boolean",
199
- "description": "Only show compatibility analysis",
748
+ "description": "Run simulation locally",
200
749
  "default": False,
201
750
  },
202
751
  },
203
- "required": ["source_robot_id", "target_robot_id", "repository_id"],
204
752
  },
205
753
  ),
206
754
  Tool(
207
- name="ate_get_repository",
208
- description="Get details of a specific repository",
755
+ name="ate_validate",
756
+ description="Run safety and compliance validation checks",
209
757
  inputSchema={
210
758
  "type": "object",
211
759
  "properties": {
212
- "repo_id": {
213
- "type": "string",
214
- "description": "Repository ID",
760
+ "checks": {
761
+ "type": "array",
762
+ "items": {"type": "string"},
763
+ "description": "Safety checks to run (collision, speed, workspace, force, all)",
764
+ "default": ["all"],
765
+ },
766
+ "strict": {
767
+ "type": "boolean",
768
+ "description": "Use strict validation (fail on warnings)",
769
+ "default": False,
215
770
  },
216
771
  },
217
- "required": ["repo_id"],
218
772
  },
219
773
  ),
220
774
  Tool(
221
- name="ate_get_robot",
222
- description="Get details of a specific robot profile",
775
+ name="ate_benchmark",
776
+ description="Run performance benchmarks on skills",
223
777
  inputSchema={
224
778
  "type": "object",
225
779
  "properties": {
226
- "robot_id": {
780
+ "type": {
227
781
  "type": "string",
228
- "description": "Robot profile ID",
782
+ "enum": ["speed", "accuracy", "robustness", "efficiency", "all"],
783
+ "description": "Benchmark type",
784
+ "default": "all",
785
+ },
786
+ "trials": {
787
+ "type": "number",
788
+ "description": "Number of trials",
789
+ "default": 10,
790
+ },
791
+ "compare": {
792
+ "type": "string",
793
+ "description": "Compare with baseline repository ID",
229
794
  },
230
795
  },
231
- "required": ["robot_id"],
232
796
  },
233
797
  ),
234
798
  ]
235
799
 
236
800
 
801
+ @server.list_tools()
802
+ async def list_tools() -> List[Tool]:
803
+ """List all available MCP tools"""
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
+
237
837
  @server.call_tool()
238
838
  async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
239
839
  """Handle tool calls"""
240
840
  try:
841
+ # Repository tools
241
842
  if name == "ate_init":
242
843
  result = client.init(
243
844
  arguments["name"],
@@ -252,18 +853,14 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
252
853
  ]
253
854
 
254
855
  elif name == "ate_clone":
255
- client.clone(
256
- arguments["repo_id"], arguments.get("target_dir")
856
+ output = capture_output(
857
+ client.clone,
858
+ arguments["repo_id"],
859
+ arguments.get("target_dir")
257
860
  )
258
- return [
259
- TextContent(
260
- type="text",
261
- text=f"Repository cloned successfully to {arguments.get('target_dir', 'current directory')}",
262
- )
263
- ]
861
+ return [TextContent(type="text", text=output or f"Repository cloned successfully")]
264
862
 
265
863
  elif name == "ate_list_repositories":
266
- # Build query params
267
864
  params = {}
268
865
  if arguments.get("search"):
269
866
  params["search"] = arguments["search"]
@@ -271,20 +868,30 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
271
868
  params["robotModel"] = arguments["robot_model"]
272
869
  params["limit"] = arguments.get("limit", 20)
273
870
 
274
- # Make API request
275
871
  response = client._request("GET", "/repositories", params=params)
276
872
  repos = response.get("repositories", [])
277
873
 
278
874
  result_text = f"Found {len(repos)} repositories:\n\n"
279
- for repo in repos[:10]: # Limit to first 10
875
+ for repo in repos[:10]:
280
876
  result_text += f"- {repo['name']} (ID: {repo['id']})\n"
281
877
  if repo.get("description"):
282
878
  result_text += f" {repo['description'][:100]}...\n"
283
879
 
284
880
  return [TextContent(type="text", text=result_text)]
285
881
 
882
+ elif name == "ate_get_repository":
883
+ response = client._request("GET", f"/repositories/{arguments['repo_id']}")
884
+ repo = response.get("repository", {})
885
+
886
+ result_text = f"Repository: {repo.get('name', 'Unknown')}\n"
887
+ result_text += f"ID: {repo.get('id', 'Unknown')}\n"
888
+ result_text += f"Description: {repo.get('description', 'No description')}\n"
889
+ result_text += f"Visibility: {repo.get('visibility', 'unknown')}\n"
890
+
891
+ return [TextContent(type="text", text=result_text)]
892
+
893
+ # Robot tools
286
894
  elif name == "ate_list_robots":
287
- # Build query params
288
895
  params = {}
289
896
  if arguments.get("search"):
290
897
  params["search"] = arguments["search"]
@@ -292,80 +899,257 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
292
899
  params["category"] = arguments["category"]
293
900
  params["limit"] = arguments.get("limit", 20)
294
901
 
295
- # Make API request
296
902
  response = client._request("GET", "/robots/profiles", params=params)
297
903
  robots = response.get("profiles", [])
298
904
 
299
905
  result_text = f"Found {len(robots)} robot profiles:\n\n"
300
- for robot in robots[:10]: # Limit to first 10
906
+ for robot in robots[:10]:
301
907
  result_text += f"- {robot['modelName']} by {robot['manufacturer']} (ID: {robot['id']})\n"
302
908
  if robot.get("description"):
303
909
  result_text += f" {robot['description'][:100]}...\n"
304
910
 
305
911
  return [TextContent(type="text", text=result_text)]
306
912
 
307
- elif name == "ate_compatibility":
308
- response = client._request(
309
- "POST",
310
- "/skills/compatibility",
311
- json={
312
- "sourceRobotId": arguments["source_robot_id"],
313
- "targetRobotId": arguments["target_robot_id"],
314
- "repositoryId": arguments["repository_id"],
315
- },
316
- )
317
- compatibility = response.get("compatibility", {})
318
- score = compatibility.get("overallScore", 0) * 100
913
+ elif name == "ate_get_robot":
914
+ response = client._request("GET", f"/robots/profiles/{arguments['robot_id']}")
915
+ robot = response.get("profile", {})
319
916
 
320
- result_text = f"Compatibility Score: {score:.1f}%\n"
321
- result_text += f"Adaptation Type: {compatibility.get('adaptationType', 'unknown')}\n"
322
- result_text += f"Estimated Effort: {compatibility.get('estimatedEffort', 'unknown')}\n"
917
+ result_text = f"Robot: {robot.get('modelName', 'Unknown')}\n"
918
+ result_text += f"Manufacturer: {robot.get('manufacturer', 'Unknown')}\n"
919
+ result_text += f"Category: {robot.get('category', 'Unknown')}\n"
920
+ result_text += f"Description: {robot.get('description', 'No description')}\n"
323
921
 
324
922
  return [TextContent(type="text", text=result_text)]
325
923
 
924
+ # Compatibility tools
925
+ elif name == "ate_check_transfer":
926
+ output = capture_output(
927
+ client.check_transfer,
928
+ arguments.get("skill_id"),
929
+ arguments["source_robot"],
930
+ arguments["target_robot"],
931
+ arguments.get("min_score", 0.0)
932
+ )
933
+ return [TextContent(type="text", text=output)]
934
+
326
935
  elif name == "ate_adapt":
327
- response = client._request(
328
- "POST",
329
- "/skills/adapt",
330
- json={
331
- "sourceRobotId": arguments["source_robot_id"],
332
- "targetRobotId": arguments["target_robot_id"],
333
- "repositoryId": arguments["repository_id"],
334
- },
936
+ output = capture_output(
937
+ client.adapt,
938
+ arguments["source_robot"],
939
+ arguments["target_robot"],
940
+ arguments.get("repo_id"),
941
+ arguments.get("analyze_only", True)
335
942
  )
336
- plan = response.get("adaptationPlan", {})
337
- compatibility = response.get("compatibility", {})
943
+ return [TextContent(type="text", text=output)]
338
944
 
339
- result_text = "Adaptation Plan:\n\n"
340
- result_text += f"Overview: {plan.get('overview', 'No overview available')}\n\n"
945
+ # Skill tools
946
+ elif name == "ate_pull":
947
+ output = capture_output(
948
+ client.pull,
949
+ arguments["skill_id"],
950
+ arguments.get("robot"),
951
+ arguments.get("format", "json"),
952
+ arguments.get("output", "./data")
953
+ )
954
+ return [TextContent(type="text", text=output)]
341
955
 
342
- if compatibility:
343
- result_text += f"Compatibility Score: {compatibility.get('overallScore', 0) * 100:.1f}%\n"
344
- result_text += f"Adaptation Type: {compatibility.get('adaptationType', 'unknown')}\n"
956
+ elif name == "ate_upload":
957
+ output = capture_output(
958
+ client.upload,
959
+ arguments["path"],
960
+ arguments["robot"],
961
+ arguments["task"],
962
+ arguments.get("project")
963
+ )
964
+ return [TextContent(type="text", text=output)]
345
965
 
346
- return [TextContent(type="text", text=result_text)]
966
+ # Parts tools
967
+ elif name == "ate_parts_list":
968
+ output = capture_output(
969
+ client.parts_list,
970
+ arguments.get("category"),
971
+ arguments.get("manufacturer"),
972
+ arguments.get("search")
973
+ )
974
+ return [TextContent(type="text", text=output)]
347
975
 
348
- elif name == "ate_get_repository":
349
- response = client._request("GET", f"/repositories/{arguments['repo_id']}")
350
- repo = response.get("repository", {})
976
+ elif name == "ate_parts_check":
977
+ output = capture_output(
978
+ client.parts_check,
979
+ arguments["skill_id"]
980
+ )
981
+ return [TextContent(type="text", text=output)]
351
982
 
352
- result_text = f"Repository: {repo.get('name', 'Unknown')}\n"
353
- result_text += f"ID: {repo.get('id', 'Unknown')}\n"
354
- result_text += f"Description: {repo.get('description', 'No description')}\n"
355
- result_text += f"Visibility: {repo.get('visibility', 'unknown')}\n"
983
+ elif name == "ate_parts_require":
984
+ output = capture_output(
985
+ client.parts_require,
986
+ arguments["part_id"],
987
+ arguments["skill_id"],
988
+ arguments.get("version", "1.0.0"),
989
+ arguments.get("required", False)
990
+ )
991
+ return [TextContent(type="text", text=output)]
356
992
 
357
- return [TextContent(type="text", text=result_text)]
993
+ elif name == "ate_deps_audit":
994
+ output = capture_output(
995
+ client.deps_audit,
996
+ arguments.get("skill_id")
997
+ )
998
+ return [TextContent(type="text", text=output)]
358
999
 
359
- elif name == "ate_get_robot":
360
- response = client._request("GET", f"/robots/profiles/{arguments['robot_id']}")
361
- robot = response.get("profile", {})
1000
+ # Generate tools
1001
+ elif name == "ate_generate":
1002
+ output = capture_output(
1003
+ client.generate,
1004
+ arguments["description"],
1005
+ arguments.get("robot", "ur5"),
1006
+ arguments.get("output", "./new-skill")
1007
+ )
1008
+ return [TextContent(type="text", text=output)]
362
1009
 
363
- result_text = f"Robot: {robot.get('modelName', 'Unknown')}\n"
364
- result_text += f"Manufacturer: {robot.get('manufacturer', 'Unknown')}\n"
365
- result_text += f"Category: {robot.get('category', 'Unknown')}\n"
366
- result_text += f"Description: {robot.get('description', 'No description')}\n"
1010
+ # Workflow tools
1011
+ elif name == "ate_workflow_validate":
1012
+ output = capture_output(
1013
+ client.workflow_validate,
1014
+ arguments["path"]
1015
+ )
1016
+ return [TextContent(type="text", text=output)]
367
1017
 
368
- return [TextContent(type="text", text=result_text)]
1018
+ elif name == "ate_workflow_run":
1019
+ output = capture_output(
1020
+ client.workflow_run,
1021
+ arguments["path"],
1022
+ arguments.get("sim", True),
1023
+ arguments.get("dry_run", False)
1024
+ )
1025
+ return [TextContent(type="text", text=output)]
1026
+
1027
+ elif name == "ate_workflow_export":
1028
+ output = capture_output(
1029
+ client.workflow_export,
1030
+ arguments["path"],
1031
+ arguments.get("format", "ros2"),
1032
+ arguments.get("output")
1033
+ )
1034
+ return [TextContent(type="text", text=output)]
1035
+
1036
+ # Team tools
1037
+ elif name == "ate_team_create":
1038
+ output = capture_output(
1039
+ client.team_create,
1040
+ arguments["name"],
1041
+ arguments.get("description")
1042
+ )
1043
+ return [TextContent(type="text", text=output)]
1044
+
1045
+ elif name == "ate_team_list":
1046
+ output = capture_output(client.team_list)
1047
+ return [TextContent(type="text", text=output)]
1048
+
1049
+ elif name == "ate_team_invite":
1050
+ output = capture_output(
1051
+ client.team_invite,
1052
+ arguments["email"],
1053
+ arguments["team"],
1054
+ arguments.get("role", "member")
1055
+ )
1056
+ return [TextContent(type="text", text=output)]
1057
+
1058
+ elif name == "ate_team_share":
1059
+ output = capture_output(
1060
+ client.team_share,
1061
+ arguments["skill_id"],
1062
+ arguments["team"]
1063
+ )
1064
+ return [TextContent(type="text", text=output)]
1065
+
1066
+ # Data tools
1067
+ elif name == "ate_data_upload":
1068
+ output = capture_output(
1069
+ client.data_upload,
1070
+ arguments["path"],
1071
+ arguments["skill"],
1072
+ arguments.get("stage", "raw")
1073
+ )
1074
+ return [TextContent(type="text", text=output)]
1075
+
1076
+ elif name == "ate_data_list":
1077
+ output = capture_output(
1078
+ client.data_list,
1079
+ arguments.get("skill"),
1080
+ arguments.get("stage")
1081
+ )
1082
+ return [TextContent(type="text", text=output)]
1083
+
1084
+ elif name == "ate_data_promote":
1085
+ output = capture_output(
1086
+ client.data_promote,
1087
+ arguments["dataset_id"],
1088
+ arguments["to_stage"]
1089
+ )
1090
+ return [TextContent(type="text", text=output)]
1091
+
1092
+ elif name == "ate_data_export":
1093
+ output = capture_output(
1094
+ client.data_export,
1095
+ arguments["dataset_id"],
1096
+ arguments.get("format", "rlds"),
1097
+ arguments.get("output", "./export")
1098
+ )
1099
+ return [TextContent(type="text", text=output)]
1100
+
1101
+ # Deploy tools
1102
+ elif name == "ate_deploy":
1103
+ output = capture_output(
1104
+ client.deploy,
1105
+ arguments["robot_type"],
1106
+ arguments.get("repo_id")
1107
+ )
1108
+ return [TextContent(type="text", text=output)]
1109
+
1110
+ elif name == "ate_deploy_config":
1111
+ output = capture_output(
1112
+ client.deploy_config,
1113
+ arguments["config_path"],
1114
+ arguments["target"],
1115
+ arguments.get("dry_run", False)
1116
+ )
1117
+ return [TextContent(type="text", text=output)]
1118
+
1119
+ elif name == "ate_deploy_status":
1120
+ output = capture_output(
1121
+ client.deploy_status,
1122
+ arguments["target"]
1123
+ )
1124
+ return [TextContent(type="text", text=output)]
1125
+
1126
+ # Test tools
1127
+ elif name == "ate_test":
1128
+ output = capture_output(
1129
+ client.test,
1130
+ arguments.get("environment", "pybullet"),
1131
+ arguments.get("robot"),
1132
+ arguments.get("local", False)
1133
+ )
1134
+ return [TextContent(type="text", text=output)]
1135
+
1136
+ elif name == "ate_validate":
1137
+ output = capture_output(
1138
+ client.validate,
1139
+ arguments.get("checks", ["all"]),
1140
+ arguments.get("strict", False),
1141
+ None # files
1142
+ )
1143
+ return [TextContent(type="text", text=output)]
1144
+
1145
+ elif name == "ate_benchmark":
1146
+ output = capture_output(
1147
+ client.benchmark,
1148
+ arguments.get("type", "all"),
1149
+ arguments.get("trials", 10),
1150
+ arguments.get("compare")
1151
+ )
1152
+ return [TextContent(type="text", text=output)]
369
1153
 
370
1154
  else:
371
1155
  return [
@@ -384,6 +1168,10 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
384
1168
  ]
385
1169
 
386
1170
 
1171
+ # ============================================================================
1172
+ # Resources
1173
+ # ============================================================================
1174
+
387
1175
  @server.list_resources()
388
1176
  async def list_resources() -> List[Resource]:
389
1177
  """List available resources"""
@@ -400,6 +1188,30 @@ async def list_resources() -> List[Resource]:
400
1188
  description="Access robot profile details",
401
1189
  mimeType="application/json",
402
1190
  ),
1191
+ Resource(
1192
+ uri="skill://*",
1193
+ name="Skill",
1194
+ description="Access skill/artifact details",
1195
+ mimeType="application/json",
1196
+ ),
1197
+ Resource(
1198
+ uri="part://*",
1199
+ name="Hardware Part",
1200
+ description="Access hardware part details",
1201
+ mimeType="application/json",
1202
+ ),
1203
+ Resource(
1204
+ uri="workflow://*",
1205
+ name="Workflow",
1206
+ description="Access workflow definition",
1207
+ mimeType="application/yaml",
1208
+ ),
1209
+ Resource(
1210
+ uri="team://*",
1211
+ name="Team",
1212
+ description="Access team details",
1213
+ mimeType="application/json",
1214
+ ),
403
1215
  ]
404
1216
 
405
1217
 
@@ -414,17 +1226,33 @@ async def read_resource(uri: str) -> str:
414
1226
  robot_id = uri.replace("robot://", "")
415
1227
  response = client._request("GET", f"/robots/profiles/{robot_id}")
416
1228
  return json.dumps(response.get("profile", {}), indent=2)
1229
+ elif uri.startswith("skill://"):
1230
+ skill_id = uri.replace("skill://", "")
1231
+ response = client._request("GET", f"/skills/{skill_id}")
1232
+ return json.dumps(response.get("skill", {}), indent=2)
1233
+ elif uri.startswith("part://"):
1234
+ part_id = uri.replace("part://", "")
1235
+ response = client._request("GET", f"/parts/{part_id}")
1236
+ return json.dumps(response.get("part", {}), indent=2)
1237
+ elif uri.startswith("team://"):
1238
+ team_slug = uri.replace("team://", "")
1239
+ response = client._request("GET", f"/teams/{team_slug}")
1240
+ return json.dumps(response.get("team", {}), indent=2)
417
1241
  else:
418
1242
  raise ValueError(f"Unknown resource URI: {uri}")
419
1243
 
420
1244
 
1245
+ # ============================================================================
1246
+ # Prompts
1247
+ # ============================================================================
1248
+
421
1249
  @server.list_prompts()
422
1250
  async def list_prompts() -> List[Prompt]:
423
1251
  """List available prompts"""
424
1252
  return [
425
1253
  Prompt(
426
1254
  name="create_skill",
427
- description="Guided workflow for creating a new robot skill repository",
1255
+ description="Guided workflow for creating a new robot skill from scratch",
428
1256
  arguments=[
429
1257
  PromptArgument(
430
1258
  name="robot_model",
@@ -433,14 +1261,14 @@ async def list_prompts() -> List[Prompt]:
433
1261
  ),
434
1262
  PromptArgument(
435
1263
  name="task_description",
436
- description="Description of the skill/task",
1264
+ description="Natural language description of the skill/task",
437
1265
  required=True,
438
1266
  ),
439
1267
  ],
440
1268
  ),
441
1269
  Prompt(
442
1270
  name="adapt_skill",
443
- description="Guided workflow for adapting a skill between robots",
1271
+ description="Guided workflow for adapting a skill between different robots",
444
1272
  arguments=[
445
1273
  PromptArgument(
446
1274
  name="source_robot",
@@ -459,6 +1287,59 @@ async def list_prompts() -> List[Prompt]:
459
1287
  ),
460
1288
  ],
461
1289
  ),
1290
+ Prompt(
1291
+ name="setup_workflow",
1292
+ description="Create a multi-skill workflow/pipeline",
1293
+ arguments=[
1294
+ PromptArgument(
1295
+ name="task_description",
1296
+ description="Description of the overall task",
1297
+ required=True,
1298
+ ),
1299
+ PromptArgument(
1300
+ name="robot",
1301
+ description="Target robot model",
1302
+ required=True,
1303
+ ),
1304
+ ],
1305
+ ),
1306
+ Prompt(
1307
+ name="deploy_skill",
1308
+ description="Deploy a skill to production robots",
1309
+ arguments=[
1310
+ PromptArgument(
1311
+ name="skill_id",
1312
+ description="Skill ID to deploy",
1313
+ required=True,
1314
+ ),
1315
+ PromptArgument(
1316
+ name="target",
1317
+ description="Target fleet or robot",
1318
+ required=True,
1319
+ ),
1320
+ ],
1321
+ ),
1322
+ Prompt(
1323
+ name="debug_compatibility",
1324
+ description="Debug why a skill isn't transferring well between robots",
1325
+ arguments=[
1326
+ PromptArgument(
1327
+ name="source_robot",
1328
+ description="Source robot model",
1329
+ required=True,
1330
+ ),
1331
+ PromptArgument(
1332
+ name="target_robot",
1333
+ description="Target robot model",
1334
+ required=True,
1335
+ ),
1336
+ PromptArgument(
1337
+ name="skill_id",
1338
+ description="Skill ID having issues",
1339
+ required=True,
1340
+ ),
1341
+ ],
1342
+ ),
462
1343
  ]
463
1344
 
464
1345
 
@@ -469,32 +1350,195 @@ async def get_prompt(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
469
1350
  return [
470
1351
  TextContent(
471
1352
  type="text",
472
- text=f"""Create a new robot skill for {arguments.get('robot_model', 'your robot')}:
1353
+ text=f"""# Create a Robot Skill for {arguments.get('robot_model', 'your robot')}
1354
+
1355
+ ## Task: {arguments.get('task_description', 'Not specified')}
1356
+
1357
+ ### Steps:
473
1358
 
474
- 1. Initialize repository: Use ate_init with a descriptive name
475
- 2. Add your skill files (code, configs, documentation)
476
- 3. Commit changes: Use ate_commit with a meaningful message
477
- 4. Push to FoodforThought: Use ate_push
1359
+ 1. **Generate Scaffolding**
1360
+ Use `ate_generate` to create skill files from your task description:
1361
+ - This creates skill.yaml, main.py, test_skill.py, and README.md
478
1362
 
479
- Task: {arguments.get('task_description', 'Not specified')}
1363
+ 2. **Review Generated Files**
1364
+ - Check skill.yaml for correct parameters
1365
+ - Implement the TODO sections in main.py
1366
+
1367
+ 3. **Add Part Dependencies**
1368
+ Use `ate_parts_list` to find required hardware, then `ate_parts_require` to add dependencies
1369
+
1370
+ 4. **Test in Simulation**
1371
+ Use `ate_test` with environment="pybullet" to validate the skill
1372
+
1373
+ 5. **Run Safety Validation**
1374
+ Use `ate_validate` with checks=["collision", "speed", "workspace", "force"]
1375
+
1376
+ 6. **Upload Demonstrations**
1377
+ Use `ate_upload` to submit demo videos for community labeling
1378
+
1379
+ 7. **Check Transfer Compatibility**
1380
+ Use `ate_check_transfer` to see which other robots can use this skill
480
1381
  """,
481
1382
  )
482
1383
  ]
1384
+
483
1385
  elif name == "adapt_skill":
484
1386
  return [
485
1387
  TextContent(
486
1388
  type="text",
487
- text=f"""Adapt skill from {arguments.get('source_robot')} to {arguments.get('target_robot')}:
1389
+ text=f"""# Adapt Skill from {arguments.get('source_robot')} to {arguments.get('target_robot')}
1390
+
1391
+ ## Repository: {arguments.get('repository_id')}
1392
+
1393
+ ### Steps:
1394
+
1395
+ 1. **Check Compatibility**
1396
+ Use `ate_check_transfer` to get the compatibility score and adaptation type
1397
+
1398
+ 2. **Generate Adaptation Plan**
1399
+ Use `ate_adapt` to see what changes are needed:
1400
+ - Kinematic adaptations
1401
+ - Sensor mappings
1402
+ - Code modifications
1403
+
1404
+ 3. **Review Requirements**
1405
+ - Check if new parts are needed with `ate_parts_check`
1406
+ - Verify hardware compatibility
1407
+
1408
+ 4. **Apply Adaptations**
1409
+ Based on the adaptation type:
1410
+ - **Direct**: No changes needed
1411
+ - **Parametric**: Adjust configuration values
1412
+ - **Retrain**: Collect new demonstrations
1413
+ - **Manual**: Significant code changes required
1414
+
1415
+ 5. **Test Adapted Skill**
1416
+ Use `ate_test` with robot="{arguments.get('target_robot')}"
1417
+
1418
+ 6. **Validate Safety**
1419
+ Use `ate_validate` with strict=true for production deployment
1420
+ """,
1421
+ )
1422
+ ]
1423
+
1424
+ elif name == "setup_workflow":
1425
+ return [
1426
+ TextContent(
1427
+ type="text",
1428
+ text=f"""# Create Multi-Skill Workflow
1429
+
1430
+ ## Task: {arguments.get('task_description', 'Not specified')}
1431
+ ## Robot: {arguments.get('robot', 'Not specified')}
1432
+
1433
+ ### Steps:
1434
+
1435
+ 1. **Define Workflow Steps**
1436
+ Create a workflow.yaml file with your skill pipeline:
1437
+ ```yaml
1438
+ name: My Workflow
1439
+ version: 1.0.0
1440
+ robot:
1441
+ model: {arguments.get('robot', 'ur5')}
1442
+ steps:
1443
+ - id: step1
1444
+ skill: perception/detect-object
1445
+ - id: step2
1446
+ skill: manipulation/pick
1447
+ depends_on: [step1]
1448
+ ```
1449
+
1450
+ 2. **Validate Workflow**
1451
+ Use `ate_workflow_validate` to check for errors
1452
+
1453
+ 3. **Test in Simulation**
1454
+ Use `ate_workflow_run` with sim=true
1455
+
1456
+ 4. **Dry Run**
1457
+ Use `ate_workflow_run` with dry_run=true to see execution plan
1458
+
1459
+ 5. **Export for Production**
1460
+ Use `ate_workflow_export` with format="ros2" for ROS2 launch file
1461
+ """,
1462
+ )
1463
+ ]
1464
+
1465
+ elif name == "deploy_skill":
1466
+ return [
1467
+ TextContent(
1468
+ type="text",
1469
+ text=f"""# Deploy Skill to Production
1470
+
1471
+ ## Skill: {arguments.get('skill_id')}
1472
+ ## Target: {arguments.get('target')}
1473
+
1474
+ ### Pre-Deployment Checklist:
1475
+
1476
+ 1. **Audit Dependencies**
1477
+ Use `ate_deps_audit` to verify all parts are available
1478
+
1479
+ 2. **Run Validation**
1480
+ Use `ate_validate` with strict=true
1481
+
1482
+ 3. **Benchmark Performance**
1483
+ Use `ate_benchmark` to ensure acceptable performance
1484
+
1485
+ 4. **Check Deployment Config**
1486
+ Create deploy.yaml for hybrid edge/cloud deployment if needed
1487
+
1488
+ ### Deployment Steps:
1489
+
1490
+ 1. **Dry Run**
1491
+ Use `ate_deploy_config` with dry_run=true to preview
1492
+
1493
+ 2. **Deploy**
1494
+ Use `ate_deploy` or `ate_deploy_config` to push to target
1495
+
1496
+ 3. **Monitor Status**
1497
+ Use `ate_deploy_status` to check deployment health
1498
+ """,
1499
+ )
1500
+ ]
1501
+
1502
+ elif name == "debug_compatibility":
1503
+ return [
1504
+ TextContent(
1505
+ type="text",
1506
+ text=f"""# Debug Skill Transfer Compatibility
488
1507
 
489
- 1. Check compatibility: Use ate_compatibility to see if adaptation is feasible
490
- 2. Generate adaptation plan: Use ate_adapt to get detailed adaptation instructions
491
- 3. Review the plan and apply necessary changes
492
- 4. Test the adapted skill
1508
+ ## Source: {arguments.get('source_robot')}
1509
+ ## Target: {arguments.get('target_robot')}
1510
+ ## Skill: {arguments.get('skill_id')}
493
1511
 
494
- Repository ID: {arguments.get('repository_id')}
1512
+ ### Diagnostic Steps:
1513
+
1514
+ 1. **Get Compatibility Score**
1515
+ Use `ate_check_transfer` to see overall compatibility
1516
+
1517
+ 2. **Check Score Breakdown**
1518
+ Look at individual scores:
1519
+ - Kinematic score: Joint configurations, workspace overlap
1520
+ - Sensor score: Camera, force/torque sensor compatibility
1521
+ - Compute score: Processing power requirements
1522
+
1523
+ 3. **Review Part Requirements**
1524
+ Use `ate_parts_check` to see required hardware for the skill
1525
+
1526
+ 4. **Compare Robot Profiles**
1527
+ Use `ate_get_robot` for both source and target to compare specs
1528
+
1529
+ 5. **Generate Adaptation Plan**
1530
+ Use `ate_adapt` to get specific recommendations
1531
+
1532
+ ### Common Issues:
1533
+
1534
+ - **Low Kinematic Score**: Check joint limits, reach, payload
1535
+ - **Low Sensor Score**: Missing cameras or sensors
1536
+ - **Low Compute Score**: Target robot has less processing power
1537
+ - **Impossible Transfer**: Fundamentally incompatible hardware
495
1538
  """,
496
1539
  )
497
1540
  ]
1541
+
498
1542
  else:
499
1543
  return [TextContent(type="text", text=f"Unknown prompt: {name}")]
500
1544
 
@@ -502,7 +1546,6 @@ Repository ID: {arguments.get('repository_id')}
502
1546
  async def main():
503
1547
  """Main entry point for MCP server"""
504
1548
  # Run the server using stdio transport
505
- # stdio_server() returns (stdin, stdout) streams
506
1549
  stdin, stdout = stdio_server()
507
1550
  await server.run(
508
1551
  stdin, stdout, server.create_initialization_options()
@@ -511,4 +1554,3 @@ async def main():
511
1554
 
512
1555
  if __name__ == "__main__":
513
1556
  asyncio.run(main())
514
-