-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_tool_registry.py
More file actions
1335 lines (1182 loc) · 74.1 KB
/
Copy path_tool_registry.py
File metadata and controls
1335 lines (1182 loc) · 74.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Canonical Tool Registry -- Single Source of Truth
Every MCP tool definition lives here. Both transports import from this module:
- stdio bridge (mcp_server.py) generates Tool() objects from TOOL_DEFS
- Streamable HTTP (mcp/tools.py) builds JSON tool definitions from TOOL_DEFS
Adding a new tool:
1. Add handler in the appropriate handlers_*.py file
2. Add entry to TOOL_DEFS in this file
3. Both transports pick it up automatically
No hou dependency. No imports from synapse.server or synapse.mcp.tools.
Only json and orjson (optional).
"""
import json
from typing import Any
try:
import orjson
def _dumps_str(obj) -> str:
return orjson.dumps(obj, option=orjson.OPT_SORT_KEYS).decode()
except ImportError:
def _dumps_str(obj) -> str:
return json.dumps(obj, sort_keys=True)
# =========================================================================
# Dispatch ID counter (He2025: sequential, not uuid4)
# =========================================================================
_call_counter = 0
def _next_call_id(tool_name: str) -> str:
global _call_counter
_call_counter += 1
return f"mcp-{tool_name}-{_call_counter}"
# =========================================================================
# Payload builders (transform MCP arguments to handler payload)
# =========================================================================
def _passthrough(_args: dict) -> dict:
return {}
def _identity(args: dict) -> dict:
return dict(args)
def _execute_python_payload(args: dict) -> dict:
p = {"content": args["code"]}
if "dry_run" in args:
p["dry_run"] = args["dry_run"]
if "atomic" in args:
p["atomic"] = args["atomic"]
return p
def _stage_info_payload(args: dict) -> dict:
p = {}
if "node" in args:
p["node"] = args["node"]
return p
def _decide_payload(args: dict) -> dict:
p = {"decision": args["decision"]}
if "reasoning" in args:
p["reasoning"] = args["reasoning"]
if "alternatives" in args:
alt = args["alternatives"]
p["alternatives"] = (
[a.strip() for a in alt.split(",") if a.strip()]
if isinstance(alt, str) else alt
)
return p
def _add_memory_payload(args: dict) -> dict:
p = {"content": args["content"]}
if "memory_type" in args:
p["memory_type"] = args["memory_type"]
if "tags" in args:
p["tags"] = args["tags"]
return p
def _filter_keys(keys):
"""Return a payload builder that passes through only the specified keys."""
def _builder(args: dict) -> dict:
return {k: args[k] for k in keys if k in args}
return _builder
def _network_explain_payload(args: dict) -> dict:
"""Rename root_path -> node for the network_explain handler."""
return {**{k: v for k, v in args.items() if k != "root_path"}, "node": args["root_path"]}
def _delete_node_payload(args: dict) -> dict:
return {"node": args["node"]}
# =========================================================================
# Schema helpers
# =========================================================================
_EMPTY_SCHEMA: dict = {"type": "object", "properties": {}, "required": []}
# =========================================================================
# Tool definitions
# =========================================================================
# Each entry: (name, command_type, payload_builder, description,
# inputSchema, read_only, destructive, idempotent)
TOOL_DEFS: list[tuple] = [
# -- Utility --
("synapse_ping", "ping", _passthrough,
"Check if Houdini/Synapse is connected and responding.",
_EMPTY_SCHEMA, True, False, True),
("synapse_health", "get_health", _passthrough,
"Get system health status including resilience layer.",
_EMPTY_SCHEMA, True, False, True),
# -- Scene --
("houdini_scene_info", "get_scene_info", _passthrough,
"Get current Houdini scene info: HIP file path, current frame, FPS, and frame range.",
_EMPTY_SCHEMA, True, False, True),
("houdini_get_selection", "get_selection", _passthrough,
"Get the currently selected nodes in Houdini.",
_EMPTY_SCHEMA, True, False, True),
# -- Node operations --
("houdini_create_node", "create_node", _identity,
"Create a new node in Houdini. Returns the path of the created node.",
{"type": "object", "properties": {
"parent": {"type": "string", "description": "Parent node path (e.g. '/obj')"},
"type": {"type": "string", "description": "Node type (e.g. 'geo', 'null')"},
"name": {"type": "string", "description": "Optional node name"},
}, "required": ["parent", "type"]},
False, True, False),
("houdini_delete_node", "delete_node", _delete_node_payload,
"Delete a node in Houdini by its path.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "Full path of the node to delete"},
}, "required": ["node"]},
False, True, False),
("houdini_connect_nodes", "connect_nodes", _identity,
"Connect the output of one node to the input of another.",
{"type": "object", "properties": {
"source": {"type": "string", "description": "Source node path (output from)"},
"target": {"type": "string", "description": "Target node path (input to)"},
"source_output": {"type": "integer", "description": "Source output index (default: 0)"},
"target_input": {"type": "integer", "description": "Target input index (default: 0)"},
}, "required": ["source", "target"]},
False, True, False),
# -- Parameters --
("houdini_get_parm", "get_parm",
_filter_keys(("node", "parm")),
"Read a parameter value from a Houdini node.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "Node path"},
"parm": {"type": "string", "description": "Parameter name"},
}, "required": ["node", "parm"]},
True, False, True),
("houdini_set_parm", "set_parm",
_filter_keys(("node", "parm", "value")),
"Set a parameter value on a Houdini node. "
"For USD/Solaris nodes, parameter names are encoded "
"(e.g. xn__inputsintensity_i0a not 'intensity'). "
"Use houdini_inspect_node first to discover exact names.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "Node path"},
"parm": {"type": "string", "description": "Parameter name"},
"value": {"description": "Value to set"},
}, "required": ["node", "parm", "value"]},
False, True, True),
# -- Execution --
("houdini_execute_python", "execute_python", _execute_python_payload,
"Execute Python code in Houdini's runtime environment. "
"ONE mutation per call. Wrapped in undo group -- automatic rollback on failure.",
{"type": "object", "properties": {
"code": {"type": "string", "description": "Python code to execute"},
"dry_run": {"type": "boolean", "description": "Syntax-check only (default: false)"},
"atomic": {"type": "boolean", "description": "Wrap in undo group (default: true)"},
}, "required": ["code"]},
False, True, False),
("houdini_execute_vex", "execute_vex", _identity,
"Execute VEX code by creating an Attribute Wrangle node.",
{"type": "object", "properties": {
"snippet": {"type": "string", "description": "VEX code snippet"},
"run_over": {"type": "string", "description": "Points, Primitives, Vertices, or Detail"},
"input_node": {"type": "string", "description": "Optional input geometry node path"},
}, "required": ["snippet"]},
False, True, False),
# -- USD/Solaris --
("houdini_stage_info", "get_stage_info", _stage_info_payload,
"Get USD stage information: prim list and types.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "Optional LOP node path"},
}, "required": []},
True, False, True),
("houdini_get_usd_attribute", "get_usd_attribute",
_filter_keys(("node", "prim_path", "attribute_name")),
"Read a USD attribute value from a prim on the stage.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node path (optional)"},
"prim_path": {"type": "string", "description": "USD prim path"},
"attribute_name": {"type": "string", "description": "USD attribute name"},
}, "required": ["prim_path", "attribute_name"]},
True, False, True),
("houdini_set_usd_attribute", "set_usd_attribute",
_filter_keys(("node", "prim_path", "attribute_name", "value")),
"Set a USD attribute on a prim.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"prim_path": {"type": "string", "description": "USD prim path"},
"attribute_name": {"type": "string", "description": "USD attribute name"},
"value": {"description": "Value to set"},
}, "required": ["prim_path", "attribute_name", "value"]},
False, True, False),
("houdini_create_usd_prim", "create_usd_prim",
_filter_keys(("node", "prim_path", "prim_type")),
"Create a USD prim on the stage.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"prim_path": {"type": "string", "description": "USD prim path to create"},
"prim_type": {"type": "string", "description": "USD prim type (default: Xform)"},
}, "required": ["prim_path"]},
False, True, False),
("houdini_modify_usd_prim", "modify_usd_prim",
_filter_keys(("node", "prim_path", "kind", "purpose", "active", "instanceable")),
"Modify USD prim metadata: kind, purpose, active state, or instanceable flag.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"prim_path": {"type": "string", "description": "USD prim path"},
"kind": {"type": "string", "description": "Model kind"},
"purpose": {"type": "string", "description": "Prim purpose"},
"active": {"type": "boolean", "description": "Whether the prim is active"},
"instanceable": {"type": "boolean", "description": "Set the prim's instanceable flag"},
}, "required": ["prim_path"]},
False, True, False),
# -- Viewport / Render --
("houdini_capture_viewport", "capture_viewport", _identity,
"Capture the Houdini viewport as an image.",
{"type": "object", "properties": {
"width": {"type": "integer", "description": "Width in pixels"},
"height": {"type": "integer", "description": "Height in pixels"},
"format": {"type": "string", "enum": ["jpeg", "png"], "description": "Image format"},
}, "required": []},
True, False, True),
("houdini_render", "render", _identity,
"Render a frame using Karma XPU, Karma CPU, Mantra, or any ROP node.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "ROP node path (auto-discovers if omitted)"},
"frame": {"type": "number", "description": "Frame to render"},
"width": {"type": "integer", "description": "Override resolution width"},
"height": {"type": "integer", "description": "Override resolution height"},
}, "required": []},
False, True, False),
("synapse_validate_frame", "validate_frame", _identity,
"Validate a rendered frame for quality issues: black frames, NaN, clipping, fireflies.",
{"type": "object", "properties": {
"image_path": {"type": "string", "description": "Path to rendered image"},
"checks": {"type": "array", "items": {"type": "string"}, "description": "Checks to run (default: all)"},
"thresholds": {"type": "object", "description": "Threshold overrides"},
}, "required": ["image_path"]},
True, False, True),
("synapse_configure_render_passes", "configure_render_passes", _identity,
"Configure render passes (AOVs) for Karma. Creates RenderVar prims for compositing. "
"Presets: beauty, diffuse, specular, emission, normal, depth, position, albedo, "
"crypto_material, crypto_object, motion, sss. Also accepts custom pass definitions.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"passes": {"type": "array", "items": {"type": "string"},
"description": "List of pass names (e.g. ['beauty', 'diffuse', 'normal', 'crypto_object'])"},
"clear_existing": {"type": "boolean", "description": "Clear existing render vars before adding new ones (default: false)"},
}, "required": ["passes"]},
False, True, False),
# -- Keyframe / Render Settings --
("houdini_set_keyframe", "set_keyframe", _identity,
"Set a keyframe on a node parameter at a specific frame.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "Node path"},
"parm": {"type": "string", "description": "Parameter name"},
"value": {"type": "number", "description": "Value to set"},
"frame": {"type": "number", "description": "Frame number"},
}, "required": ["node", "parm", "value"]},
False, True, False),
("houdini_render_settings", "render_settings", _identity,
"Read and optionally modify render settings on a ROP or Karma node.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "ROP or render settings node path"},
"settings": {"type": "object", "description": "Optional overrides"},
}, "required": ["node"]},
False, True, True),
# -- TOPs / PDG --
("houdini_wedge", "wedge", _identity,
"Run a TOPs/PDG wedge to explore parameter variations.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP network or wedge node path"},
"parm": {"type": "string", "description": "Parameter to wedge"},
"values": {"type": "array", "items": {"type": "number"}, "description": "Values to wedge over"},
}, "required": ["node"]},
False, True, False),
# -- TOPS / PDG (Phase 1) --
("tops_get_work_items", "tops_get_work_items", _identity,
"Get work items from a TOP node with optional state filtering.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node path"},
"state_filter": {"type": "string", "description": "Filter by state: all, cooked, failed, cooking, scheduled, uncooked, cancelled (default: all)"},
"include_attributes": {"type": "boolean", "description": "Include work item attributes (default: true)"},
"limit": {"type": "integer", "description": "Max items to return (default: 100)"},
}, "required": ["node"]},
True, False, True),
("tops_get_dependency_graph", "tops_get_dependency_graph", _identity,
"Get the dependency graph for a TOP network: nodes, types, work item counts, and edges.",
{"type": "object", "properties": {
"topnet_path": {"type": "string", "description": "TOP network path"},
"depth": {"type": "integer", "description": "Traversal depth (-1 for full, default: -1)"},
}, "required": ["topnet_path"]},
True, False, True),
("tops_get_cook_stats", "tops_get_cook_stats", _identity,
"Get cook statistics for a TOP node or network: work item counts by state and cook times.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node or network path"},
}, "required": ["node"]},
True, False, True),
("tops_cook_node", "tops_cook_node", _identity,
"Cook a TOP node. Supports blocking/non-blocking and generate-only modes.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node path"},
"generate_only": {"type": "boolean", "description": "Generate work items only, don't cook (default: false)"},
"blocking": {"type": "boolean", "description": "Wait for cook to complete (default: true)"},
"top_down": {"type": "boolean", "description": "Cook upstream nodes first (default: true)"},
}, "required": ["node"]},
False, True, False),
("tops_generate_items", "tops_generate_items", _identity,
"Generate work items for a TOP node without cooking. Preview what a node will produce.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node path"},
}, "required": ["node"]},
False, True, False),
# -- TOPS / PDG (Phase 2: Scheduler & Control) --
("tops_configure_scheduler", "tops_configure_scheduler", _identity,
"Configure the scheduler for a TOP network: type, max concurrent, working directory.",
{"type": "object", "properties": {
"topnet_path": {"type": "string", "description": "TOP network path"},
"scheduler_type": {"type": "string", "description": "Scheduler type (default: local)"},
"max_concurrent": {"type": "integer", "description": "Max concurrent processes"},
"working_dir": {"type": "string", "description": "PDG working directory"},
}, "required": ["topnet_path"]},
False, True, True),
("tops_cancel_cook", "tops_cancel_cook", _identity,
"Cancel an active cook on a TOP node or network.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node or network path"},
}, "required": ["node"]},
False, True, False),
("tops_dirty_node", "tops_dirty_node", _identity,
"Dirty a TOP node to clear cached results. Optionally dirty upstream nodes too.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node path"},
"dirty_upstream": {"type": "boolean", "description": "Also dirty upstream nodes (default: false)"},
}, "required": ["node"]},
False, True, True),
# -- TOPS / PDG (Phase 3: Advanced) --
("tops_setup_wedge", "tops_setup_wedge", _identity,
"Set up a Wedge TOP node for parameter variation exploration.",
{"type": "object", "properties": {
"topnet_path": {"type": "string", "description": "TOP network path"},
"wedge_name": {"type": "string", "description": "Name for the wedge node (default: wedge1)"},
"attributes": {"type": "array", "items": {"type": "object"}, "description": "List of {name, type, start, end, steps}"},
}, "required": ["topnet_path", "attributes"]},
False, True, False),
("tops_batch_cook", "tops_batch_cook", _identity,
"Cook multiple TOP nodes in sequence, collecting per-node results and aggregate stats.",
{"type": "object", "properties": {
"node_paths": {"type": "array", "items": {"type": "string"}, "description": "List of TOP node paths to cook"},
"blocking": {"type": "boolean", "description": "Wait for each cook (default: true)"},
"stop_on_error": {"type": "boolean", "description": "Stop on first error (default: true)"},
}, "required": ["node_paths"]},
False, True, False),
("tops_query_items", "tops_query_items", _identity,
"Query work items by attribute value with filter operators (eq, gt, lt, gte, lte, contains, regex).",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node path"},
"query_attribute": {"type": "string", "description": "Attribute name to filter on"},
"filter_op": {"type": "string", "enum": ["eq", "gt", "lt", "gte", "lte", "contains", "regex"], "description": "Filter operator (default: eq)"},
"filter_value": {"description": "Value to match against"},
}, "required": ["node", "query_attribute", "filter_value"]},
True, False, True),
# -- TOPS / PDG (Phase 4: Autonomous Operations) --
("tops_cook_and_validate", "tops_cook_and_validate", _identity,
"Cook a TOP node with automatic retry on failure. Self-healing: cook -> validate -> dirty -> retry.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node path"},
"max_retries": {"type": "integer", "description": "Max retry attempts on failure (default: 0)"},
"validate_states": {"type": "boolean", "description": "Check work item states after cook (default: true)"},
}, "required": ["node"]},
False, True, False),
("tops_diagnose", "tops_diagnose", _identity,
"Diagnose failures on a TOP node: inspect failed items, scheduler config, upstream deps, and suggestions.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node path"},
"include_scheduler": {"type": "boolean", "description": "Include scheduler info (default: true)"},
"include_dependencies": {"type": "boolean", "description": "Include upstream dependency check (default: true)"},
}, "required": ["node"]},
True, False, True),
("tops_pipeline_status", "tops_pipeline_status", _identity,
"Full health check for a TOP network: per-node status, aggregate stats, issues, and suggestions.",
{"type": "object", "properties": {
"topnet_path": {"type": "string", "description": "TOP network path"},
"include_items": {"type": "boolean", "description": "Include per-node work items (default: false)"},
}, "required": ["topnet_path"]},
True, False, True),
# -- TOPS / PDG (Phase 5: Streaming & Render Integration) --
("tops_monitor_stream", "tops_monitor_stream", _identity,
"Start, stop, or check status of event-driven TOPS cook monitoring. "
"Push-based alternative to polling -- registers PDG event callbacks that "
"track work_item_started/completed/failed, cook_progress, cook_complete events. "
"Use action='start' to begin, 'status' to check, 'stop' to end.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "TOP node or network path to monitor"},
"action": {"type": "string", "enum": ["start", "stop", "status"],
"description": "Action: start, stop, or status (default: start)"},
"monitor_id": {"type": "string", "description": "Monitor ID (required for stop/status, returned by start)"},
}, "required": ["node"]},
False, False, False),
("tops_render_sequence", "tops_render_sequence", _identity,
"Render a frame sequence via TOPS/PDG. Single-call interface for 'render frames 1-48'. "
"Validates stage, creates/reuses TOPS network, sets frame range, generates work items, "
"starts cook. Idempotent -- reuses existing network if one matches.",
{"type": "object", "properties": {
"start_frame": {"type": "integer", "description": "First frame to render"},
"end_frame": {"type": "integer", "description": "Last frame to render (inclusive)"},
"step": {"type": "integer", "description": "Frame step (default: 1)"},
"camera": {"type": "string", "description": "Camera USD prim path"},
"output_dir": {"type": "string", "description": "Output directory for rendered frames"},
"output_prefix": {"type": "string", "description": "Filename prefix (default: render)"},
"rop_node": {"type": "string", "description": "ROP node path (auto-discovers if omitted)"},
"topnet_path": {"type": "string", "description": "Existing TOP network to reuse"},
"pixel_samples": {"type": "integer", "description": "Override pixel samples"},
"resolution": {"type": "array", "items": {"type": "integer"},
"description": "Override resolution [width, height]"},
"blocking": {"type": "boolean", "description": "Wait for cook to complete (default: false)"},
}, "required": ["start_frame", "end_frame"]},
False, True, False),
("tops_multi_shot", "tops_multi_shot", _identity,
"Create a TOPS network for multi-shot rendering. Accepts a list of shot definitions "
"(name, frame range, camera, overrides), creates per-shot work items in a genericgenerator, "
"feeds into ropfetch for rendering, partitions results by shot name. "
"Returns a job_id for monitoring.",
{"type": "object", "properties": {
"shots": {"type": "array", "items": {"type": "object", "properties": {
"name": {"type": "string", "description": "Shot name (e.g. sq010_sh010)"},
"frame_start": {"type": "integer", "description": "First frame (default: 1001)"},
"frame_end": {"type": "integer", "description": "Last frame (default: 1048)"},
"camera": {"type": "string", "description": "Camera USD prim path"},
"overrides": {"type": "object", "description": "Shot-specific parameter overrides"},
}, "required": ["name"]}, "description": "List of shot definitions"},
"topnet_path": {"type": "string", "description": "Existing TOP network to reuse"},
"renderer": {"type": "string", "description": "Renderer (default: karma_xpu)"},
"output_dir": {"type": "string", "description": "Base output directory (default: $HIP/render)"},
"camera_pattern": {"type": "string", "description": "Camera path template (default: /cameras/{shot}_cam)"},
"rop_node": {"type": "string", "description": "ROP node path (auto-discovers if omitted)"},
"blocking": {"type": "boolean", "description": "Wait for cook to complete (default: false)"},
"encode_movie": {"type": "boolean", "description": "Add ffmpeg encode per shot (default: false)"},
}, "required": ["shots"]},
False, True, False),
# -- USD Scene Assembly --
("houdini_reference_usd", "reference_usd", _identity,
"Import a USD file into the stage via reference, payload, or sublayer. "
"Payload mode uses deferred loading for heavy assets. "
"For Karma rendering, sublayer is the most reliable import mode.",
{"type": "object", "properties": {
"file": {"type": "string", "description": "Path to USD file"},
"prim_path": {"type": "string", "description": "Target prim path (default: /)"},
"mode": {"type": "string", "enum": ["reference", "payload", "sublayer"],
"description": "Import mode: reference (default), payload (deferred load), or sublayer (most Karma-compatible)"},
"parent": {"type": "string", "description": "Parent LOP network path"},
"karma_visible": {"type": "boolean", "description": "Author purpose/kind on the referenced prim for Karma visibility (default: true)"},
"purpose": {"type": "string", "description": "USD purpose to author non-clobberingly (default: default)"},
"kind": {"type": "string", "description": "USD model kind to author non-clobberingly (default: component)"},
}, "required": ["file"]},
False, True, False),
("houdini_set_payload_loadstate", "set_payload_loadstate", _identity,
"Control USD payload load state and prim activation. Load/unload a payload by "
"prim path and/or toggle the prim's active flag. Use to defer-load or release "
"heavy referenced assets.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"prim_path": {"type": "string", "description": "USD prim path carrying the payload"},
"action": {"type": "string", "enum": ["load", "unload"],
"description": "Load or unload the payload (optional)"},
"active": {"type": "boolean", "description": "Set prim active/inactive (optional)"},
}, "required": ["prim_path"]},
False, True, False),
("houdini_create_point_instancer", "create_point_instancer", _identity,
"Author a UsdGeom.PointInstancer: scatter prototype prims across positions. "
"Minimal valid setup -- defines the instancer, sets the prototypes relationship, "
"protoIndices (defaults to zeros), and positions.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"prim_path": {"type": "string", "description": "USD prim path for the PointInstancer"},
"prototypes": {"type": "array", "items": {"type": "string"},
"description": "Prototype prim paths to instance"},
"positions": {"type": "array", "items": {"type": "array", "items": {"type": "number"}},
"description": "Instance positions as [[x,y,z], ...]"},
}, "required": ["prim_path"]},
False, True, False),
("houdini_shot_render_ready", "shot_render_ready", _identity,
"Composite orchestrator: get a shot render-ready in one call. Runs "
"create_textured_material -> solaris_assemble_chain -> safe_render in sequence, "
"threading outputs, and returns a per-step summary with any errors. Orchestrates "
"existing primitives -- does not re-implement them.",
{"type": "object", "properties": {
"diffuse_map": {"type": "string", "description": "Diffuse/albedo texture for the material step (optional)"},
"material_name": {"type": "string", "description": "Material name (optional)"},
"geo_pattern": {"type": "string", "description": "Geometry prim pattern to assign the material to (optional)"},
"parent": {"type": "string", "description": "LOP network path for assembly (default: /stage)"},
"rop_path": {"type": "string", "description": "Render ROP path (auto-discovers if omitted)"},
"width": {"type": "integer", "description": "Render width override"},
"height": {"type": "integer", "description": "Render height override"},
"skip_render": {"type": "boolean", "description": "Assemble only, skip the render step (default: false)"},
}, "required": []},
False, True, False),
("houdini_query_prims", "query_prims", _identity,
"Query USD stage prims with filtering by type, purpose, and name pattern. "
"Returns matching prims with their paths, types, and metadata.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node path. If omitted, uses current selection."},
"root_path": {"type": "string", "description": "USD prim path to start walking from (default: /)"},
"prim_type": {"type": "string", "description": "Filter by USD type name (e.g. 'Mesh', 'DomeLight', 'Material')"},
"purpose": {"type": "string", "description": "Filter by purpose (e.g. 'default', 'render', 'proxy', 'guide')"},
"name_pattern": {"type": "string", "description": "Regex or substring filter on prim name"},
"max_depth": {"type": "integer", "description": "Max traversal depth (default: 10)"},
"limit": {"type": "integer", "description": "Max prims to return (default: 100)"},
}, "required": []},
True, False, False),
("houdini_manage_variant_set", "manage_variant_set", _identity,
"Manage USD variant sets on a prim: list, create, or select variants. "
"Use 'list' to see existing variant sets, 'create' to add a new set "
"with named variants, or 'select' to switch the active variant.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node path. If omitted, uses current selection."},
"prim_path": {"type": "string", "description": "USD prim path to manage variants on"},
"action": {"type": "string", "enum": ["list", "create", "select"],
"description": "Action to perform (default: list)"},
"variant_set": {"type": "string", "description": "Variant set name (required for create/select)"},
"variants": {"type": "array", "items": {"type": "string"},
"description": "Variant names to create (required for create action)"},
"variant": {"type": "string", "description": "Variant to select (required for select action)"},
}, "required": ["prim_path"]},
False, True, True),
("houdini_manage_collection", "manage_collection", _identity,
"Manage USD collections on a prim for light linking, material assignment, "
"and grouping. Use 'list' to see existing collections, 'create' to make a "
"new collection with include/exclude paths, 'add'/'remove' to modify paths.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node path. If omitted, uses current selection."},
"prim_path": {"type": "string", "description": "USD prim path to manage collections on"},
"action": {"type": "string", "enum": ["list", "create", "add", "remove"],
"description": "Action to perform (default: list)"},
"collection_name": {"type": "string", "description": "Collection name (required for create/add/remove)"},
"paths": {"type": "array", "items": {"type": "string"},
"description": "Prim paths to include (required for create/add/remove)"},
"exclude_paths": {"type": "array", "items": {"type": "string"},
"description": "Prim paths to exclude (optional, create only)"},
"expansion_rule": {"type": "string", "enum": ["expandPrims", "expandPrimsAndProperties", "explicitOnly"],
"description": "Collection expansion rule (default: expandPrims)"},
}, "required": ["prim_path"]},
False, True, True),
("synapse_validate_ordering", "solaris_validate_ordering", _identity,
"Walk a LOP network backwards from the render node, detecting ambiguous "
"merge points where input order affects USD opinion strength. Flags "
"merge and sublayer LOPs with 2+ inputs as potential ordering issues.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "Starting node path (render ROP or Karma LOP). Auto-discovers if omitted."},
"max_depth": {"type": "integer", "description": "Maximum traversal depth (default: 50)"},
}, "required": []},
True, False, True),
("synapse_solaris_assemble_chain", "solaris_assemble_chain", _identity,
"Auto-wire unwired LOP nodes in /stage into the canonical Solaris chain. "
"Three modes: 'all' scans for unwired nodes and wires them in canonical "
"order (SOPCreate -> MaterialLibrary -> AssignMaterial -> Camera -> Lights "
"-> RenderProperties -> OUTPUT null). 'nodes' wires specific node paths. "
"'after' appends nodes after a given chain tail. Supports dry_run to "
"preview wiring without mutating the scene.",
{"type": "object", "properties": {
"mode": {"type": "string", "enum": ["all", "nodes", "after"],
"description": "Assembly mode: 'all' (scan unwired), 'nodes' (specific paths), 'after' (append). Default: 'all'."},
"parent": {"type": "string", "description": "LOP network path (default: /stage)"},
"nodes": {"type": "array", "items": {"type": "string"},
"description": "Node paths to wire (required for 'nodes' and 'after' modes)"},
"after": {"type": "string", "description": "Node path to append after (required for 'after' mode)"},
"sort": {"type": "boolean", "description": "Sort nodes by canonical Solaris order (default: true)"},
"dry_run": {"type": "boolean", "description": "Preview wiring plan without mutating (default: false)"},
"aov_passes": {"type": "array", "items": {"type": "string"},
"description": "Auto-configure render passes after wiring (e.g. ['beauty', 'diffuse', 'normal', 'depth'])"},
}, "required": []},
False, False, True),
("synapse_solaris_build_graph", "solaris_build_graph", _identity,
"Build a Solaris LOP network with arbitrary topology: merge nodes, "
"sublayer stacks, parallel streams. Specify nodes and connections as "
"a directed acyclic graph. Supports pre-built templates for common "
"patterns (multi_asset_merge, sublayer_stack, render_pass_split, "
"lighting_rig). Use assemble_chain for simple linear wiring.",
{"type": "object", "properties": {
"parent": {"type": "string", "description": "LOP network path (default: /stage)"},
"nodes": {"type": "array", "items": {"type": "object", "properties": {
"id": {"type": "string", "description": "Local graph ID for connections"},
"type": {"type": "string", "description": "LOP node type (e.g. merge, sopcreate)"},
"name": {"type": "string", "description": "Houdini node name (defaults to id)"},
"parms": {"type": "object", "description": "Parameter values to set"},
}, "required": ["id", "type"]}, "description": "Nodes to create"},
"connections": {"type": "array", "items": {"type": "object", "properties": {
"from": {"type": "string", "description": "Source node id"},
"to": {"type": "string", "description": "Target node id"},
"input": {"type": "integer", "description": "Target input index (default: 0). Order matters for merge/sublayer."},
"output": {"type": "integer", "description": "Source output index (default: 0)"},
}, "required": ["from", "to"]}, "description": "Connection wiring"},
"display_node": {"type": "string", "description": "Node id to set display flag (auto-detects if omitted)"},
"template": {"type": "string", "enum": ["multi_asset_merge", "sublayer_stack", "render_pass_split", "lighting_rig"],
"description": "Pre-built topology template (optional)"},
"template_params": {"type": "object", "description": "Parameters for template expansion"},
"dry_run": {"type": "boolean", "description": "Preview graph without creating (default: false)"},
}, "required": ["nodes", "connections"]},
False, False, True),
("houdini_configure_light_linking", "configure_light_linking", _identity,
"Configure light linking between lights and geometry via USD collections. "
"Control which geometry a light illuminates or casts shadows on. "
"Actions: 'include' (limit illumination), 'exclude' (block illumination), "
"'shadow_include'/'shadow_exclude' (shadow control), 'reset' (illuminate everything).",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node path. If omitted, uses current selection."},
"light_path": {"type": "string", "description": "USD prim path of the light"},
"action": {"type": "string",
"enum": ["include", "exclude", "shadow_include", "shadow_exclude", "reset"],
"description": "Light linking action (default: include)"},
"geo_paths": {"type": "array", "items": {"type": "string"},
"description": "Geometry prim paths to include/exclude (not needed for reset)"},
}, "required": ["light_path"]},
False, True, False),
# -- Materials --
("houdini_create_textured_material", "create_textured_material", _identity,
"Create a production MaterialX material with texture file inputs. "
"Supports diffuse, roughness, metalness, normal, opacity, and displacement maps. "
"Handles UDIM textures and UV coordinate wiring automatically. "
"Use this for textured lookdev; use create_material for simple solid colors.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"name": {"type": "string", "description": "Material name (default: textured_material)"},
"diffuse_map": {"type": "string", "description": "Path to diffuse/albedo texture file"},
"roughness_map": {"type": "string", "description": "Path to roughness texture file"},
"metalness_map": {"type": "string", "description": "Path to metalness texture file"},
"normal_map": {"type": "string", "description": "Path to normal map texture file"},
"displacement_map": {"type": "string", "description": "Path to displacement map texture file"},
"opacity_map": {"type": "string", "description": "Path to opacity/alpha texture file"},
"roughness": {"type": "number", "description": "Scalar roughness fallback if no texture (0-1)"},
"metalness": {"type": "number", "description": "Scalar metalness fallback if no texture (0-1)"},
"geo_pattern": {"type": "string", "description": "Optional geometry prim pattern to auto-assign material"},
}, "required": []},
False, True, False),
("houdini_create_material", "create_material", _identity,
"Create a material with a shader in the LOP network. Supports presets "
"(glass, mirror, rough_metal, polished_metal, skin, cloth, plastic, ceramic, wax, rubber) "
"and category-based organization. Explicit params override preset values.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"name": {"type": "string", "description": "Material name"},
"preset": {"type": "string", "description": "Material preset (glass, mirror, rough_metal, polished_metal, skin, cloth, plastic, ceramic, wax, rubber). Explicit params override preset values"},
"category": {"type": "string", "description": "Material category for organization (e.g. 'metal', 'cloth'). Creates /materials/{category}/{name} hierarchy"},
"shader_type": {"type": "string", "description": "Shader type (default: mtlxstandard_surface)"},
"base_color": {"type": "array", "items": {"type": "number"}, "description": "[r, g, b] 0-1"},
"metalness": {"type": "number", "description": "Metalness 0-1"},
"roughness": {"type": "number", "description": "Roughness 0-1"},
"opacity": {"type": "number", "description": "Opacity 0-1 (1=fully opaque)"},
"emission": {"type": "number", "description": "Emission weight 0-1"},
"emission_color": {"type": "array", "items": {"type": "number"}, "description": "Emission color [r, g, b] 0-1"},
"subsurface": {"type": "number", "description": "Subsurface scattering weight 0-1"},
"subsurface_color": {"type": "array", "items": {"type": "number"}, "description": "Subsurface color [r, g, b] 0-1"},
"transmission": {"type": "number", "description": "Transmission weight 0-1 (glass, liquids)"},
"coat": {"type": "number", "description": "Clearcoat weight 0-1 (car paint, varnish)"},
"coat_roughness": {"type": "number", "description": "Clearcoat roughness 0-1"},
"ior": {"type": "number", "description": "Index of refraction (glass=1.5, water=1.33, diamond=2.42)"},
}, "required": []},
False, True, False),
("houdini_assign_material", "assign_material", _identity,
"Assign a material to geometry prims.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node to wire after (optional)"},
"prim_pattern": {"type": "string", "description": "Geometry prim path or pattern"},
"material_path": {"type": "string", "description": "USD material path"},
}, "required": ["prim_pattern", "material_path"]},
False, True, False),
("houdini_read_material", "read_material", _identity,
"Read what material is assigned to a prim and its shader settings.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "LOP node (optional)"},
"prim_path": {"type": "string", "description": "USD prim to inspect"},
}, "required": ["prim_path"]},
True, False, True),
# -- Knowledge / RAG --
("synapse_knowledge_lookup", "knowledge_lookup",
_filter_keys(("query",)),
"Look up Houdini knowledge: parameter names, node types, workflow guides.",
{"type": "object", "properties": {
"query": {"type": "string", "description": "Natural language query"},
}, "required": ["query"]},
True, False, True),
# -- Introspection --
("synapse_inspect_selection", "inspect_selection", _identity,
"Inspect selected nodes: parameters, connections, geometry stats, input graph.",
{"type": "object", "properties": {
"depth": {"type": "integer", "description": "Input traversal depth (default: 1)"},
}, "required": []},
True, False, True),
("synapse_inspect_scene", "inspect_scene", _identity,
"Bird's-eye scene overview: node tree, context breakdown, warnings, sticky notes.",
{"type": "object", "properties": {
"root": {"type": "string", "description": "Starting node path (default: '/')"},
"max_depth": {"type": "integer", "description": "Traversal depth (default: 3)"},
"context_filter": {"type": "string", "description": "Filter by category (e.g. 'Sop')"},
}, "required": []},
True, False, True),
("synapse_inspect_node", "inspect_node", _identity,
"Deep-dive into a single node: all parameters, expressions, code, geometry, HDA info.",
{"type": "object", "properties": {
"node": {"type": "string", "description": "Full node path"},
"include_code": {"type": "boolean", "description": "Include VEX/Python code (default: true)"},
"include_geometry": {"type": "boolean", "description": "Include geometry attributes (default: true)"},
"include_expressions": {"type": "boolean", "description": "Include expressions (default: true)"},
}, "required": ["node"]},
True, False, True),
# -- Network Explain --
("houdini_network_explain", "network_explain", _network_explain_payload,
"Walk a Houdini node network and produce a structured explanation: data flow order, "
"detected workflow patterns (scatter, terrain, simulation, VDB, etc.), non-default "
"parameter values, and suggested parameters to promote for HDA interfaces.",
{"type": "object", "properties": {
"root_path": {"type": "string", "description": "Path to network root (e.g. '/obj/geo1')"},
"depth": {"type": "integer", "description": "How deep to traverse subnets (default: 2, max: 5)"},
"detail_level": {"type": "string", "enum": ["summary", "standard", "detailed"],
"description": "Level of detail (default: standard)"},
"include_parameters": {"type": "boolean", "description": "Include key non-default parameter values (default: true)"},
"include_expressions": {"type": "boolean", "description": "Include channel expressions (default: false)"},
"format": {"type": "string", "enum": ["prose", "structured", "help_card"],
"description": "Output format (default: structured)"},
}, "required": ["root_path"]},
True, False, True),
# -- Memory --
("synapse_context", "context", _passthrough,
"Get project context from Synapse memory.",
_EMPTY_SCHEMA, True, False, True),
("synapse_search", "search",
_filter_keys(("query",)),
"Search project memory for relevant entries.",
{"type": "object", "properties": {
"query": {"type": "string", "description": "Search query"},
}, "required": ["query"]},
True, False, True),
("synapse_recall", "recall",
_filter_keys(("query",)),
"Recall relevant memories for a given context or question.",
{"type": "object", "properties": {
"query": {"type": "string", "description": "Context or question"},
}, "required": ["query"]},
True, False, True),
("synapse_decide", "decide", _decide_payload,
"Record a decision in project memory with reasoning.",
{"type": "object", "properties": {
"decision": {"type": "string", "description": "The decision made"},
"reasoning": {"type": "string", "description": "Why this decision was made"},
"alternatives": {"type": "string", "description": "Alternatives considered"},
}, "required": ["decision"]},
False, False, False),
("synapse_add_memory", "add_memory", _add_memory_payload,
"Add a memory entry to the project.",
{"type": "object", "properties": {
"content": {"type": "string", "description": "Memory content to store"},
"memory_type": {"type": "string", "description": "Type (note, context, reference, task)"},
"tags": {"type": "array", "items": {"type": "string"}, "description": "Tags"},
}, "required": ["content"]},
False, False, True),
# -- Scene Memory (Living Memory) --
("synapse_project_setup", "project_setup", _identity,
"Call this FIRST in every session. Returns project memory, scene memory, "
"agent state, and evolution stage. Without this, you have no context.",
{"type": "object", "properties": {
"force_refresh": {"type": "boolean", "description": "Force re-read (default: false)"},
}, "required": []},
False, False, True),
("synapse_memory_write", "memory_write", _identity,
"Write a memory entry to scene or project memory.",
{"type": "object", "properties": {
"entry_type": {"type": "string", "description": "Type of memory entry"},
"content": {"type": "object", "description": "Entry content"},
"scope": {"type": "string", "enum": ["scene", "project", "both"], "description": "Where to write"},
}, "required": ["entry_type", "content"]},
False, False, False),
("synapse_memory_query", "memory_query", _identity,
"Query scene or project memory.",
{"type": "object", "properties": {
"query": {"type": "string", "description": "Search query"},
"scope": {"type": "string", "enum": ["scene", "project", "all"]},
"type_filter": {"type": "string"},
}, "required": ["query"]},
True, False, True),
("synapse_memory_status", "memory_status", _passthrough,
"Get memory system status: evolution stage, file sizes, session count.",
_EMPTY_SCHEMA, True, False, True),
("synapse_evolve_memory", "evolve_memory", _passthrough,
"Manually trigger memory evolution.",
{"type": "object", "properties": {
"scope": {"type": "string", "enum": ["scene", "project"]},
"target_stage": {"type": "string", "enum": ["charmeleon", "charizard"]},
"dry_run": {"type": "boolean", "description": "Preview without evolving (default: true)"},
}, "required": []},
False, False, False),
# -- HDA (Houdini Digital Asset) --
("houdini_hda_create", "hda_create", _identity,
"Convert a subnet into a Houdini Digital Asset (HDA). "
"Sets metadata (author, version), installs the .hda file. "
"The subnet must already exist -- use create_node to build it first.",
{"type": "object", "properties": {
"subnet_path": {"type": "string", "description": "Path to subnet node to convert"},
"operator_name": {"type": "string", "description": "Internal operator type name"},
"operator_label": {"type": "string", "description": "Human-readable label"},
"category": {"type": "string", "enum": ["Sop", "Object", "Driver", "Lop", "Top"],
"description": "Node category for the HDA"},
"version": {"type": "string", "description": "SemVer version (default: 1.0.0)"},
"save_path": {"type": "string", "description": "File path to save the .hda file"},
"min_inputs": {"type": "integer", "description": "Minimum inputs (default: 0)"},
"max_inputs": {"type": "integer", "description": "Maximum inputs (default: 1)"},
"icon": {"type": "string", "description": "Optional icon name"},
}, "required": ["subnet_path", "operator_name", "operator_label", "category", "save_path"]},
False, True, False),
("houdini_hda_promote_parm", "hda_promote_parm", _identity,
"Promote an internal node parameter to the HDA's top-level interface. "
"Idempotent -- re-promoting updates rather than duplicates.",
{"type": "object", "properties": {
"hda_path": {"type": "string", "description": "Path to the HDA instance node"},
"internal_node": {"type": "string", "description": "Relative path to internal node"},
"parm_name": {"type": "string", "description": "Parameter name on the internal node"},
"label": {"type": "string", "description": "Optional label override"},
"folder": {"type": "string", "description": "Optional folder/tab name"},
"callback": {"type": "string", "description": "Optional Python callback script"},
"conditions": {"type": "object", "description": "Optional visibility conditions"},
}, "required": ["hda_path", "internal_node", "parm_name"]},
False, True, True),
("houdini_hda_set_help", "hda_set_help", _identity,
"Set help documentation on an HDA. Generates Houdini wiki markup "
"from structured inputs: summary, description, per-parameter help, and tips.",
{"type": "object", "properties": {
"hda_path": {"type": "string", "description": "Path to the HDA instance node"},
"summary": {"type": "string", "description": "Short summary"},
"description": {"type": "string", "description": "Full description (wiki markup)"},
"parameters_help": {"type": "object", "description": "{parm_name: help_text}"},
"tips": {"type": "array", "items": {"type": "string"}, "description": "List of tips"},
"author": {"type": "string", "description": "Author name"},
}, "required": ["hda_path"]},
False, True, True),
("houdini_hda_package", "hda_package", _identity,
"High-level HDA orchestrator: create subnet, convert to HDA, promote parameters, "
"set help -- all in one call. Atomic undo group rolls back on failure.",
{"type": "object", "properties": {
"description": {"type": "string", "description": "What the HDA should do"},
"name": {"type": "string", "description": "Operator name"},
"category": {"type": "string", "enum": ["Sop", "Object", "Driver", "Lop", "Top"],
"description": "Node category"},
"save_path": {"type": "string", "description": "File path to save .hda"},
"inputs": {"type": "array", "items": {"type": "string"}, "description": "Input descriptions"},
"promoted_parms": {"type": "array", "items": {"type": "object"},
"description": "List of {node, parm, label} dicts"},
"nodes": {"type": "array", "items": {"type": "object", "properties": {
"type": {"type": "string", "description": "Node type to create"},
"name": {"type": "string", "description": "Node name"},
"parms": {"type": "object", "description": "Parameter values to set"},
}, "required": ["type"]}, "description": "Internal nodes to create before HDA conversion"},
"connections": {"type": "array", "items": {"type": "array", "items": {"type": "string"}},
"description": "Connection triples: [src_name, dst_name, dst_input_idx]. Use __input0 for subnet input"},
}, "required": ["description", "name", "category", "save_path"]},
False, True, False),
("houdini_hda_list", "hda_list", _passthrough,
"List all Synapse-authored HDAs currently loaded in Houdini. "
"Scans loaded HDA files for definitions with author=synapse metadata.",
{"type": "object", "properties": {}, "required": []},
True, False, False),
# -- Undo / Redo --
("houdini_undo", "undo", _passthrough,
"Undo the last Houdini operation. Steps back one undo level.",
_EMPTY_SCHEMA, False, True, False),
("houdini_redo", "redo", _passthrough,
"Redo the last undone Houdini operation. Steps forward one undo level.",
_EMPTY_SCHEMA, False, True, False),
# -- Batch --
("synapse_batch", "batch_commands", _identity,
"Execute multiple Synapse commands in a single round-trip.",
{"type": "object", "properties": {
"commands": {"type": "array", "items": {"type": "object"}, "description": "Commands to execute"},
"atomic": {"type": "boolean", "description": "Wrap in undo group (default: true)"},
"stop_on_error": {"type": "boolean", "description": "Stop on first error (default: false)"},
}, "required": ["commands"]},
False, True, False),
# -- Metrics / Stats --
("synapse_metrics", "get_metrics", _passthrough,
"Get Synapse metrics in Prometheus text format.",
_EMPTY_SCHEMA, True, False, True),
("synapse_router_stats", "router_stats", _passthrough,
"Get tier cascade routing statistics.",
_EMPTY_SCHEMA, True, False, True),
("synapse_list_recipes", "list_recipes", _passthrough,
"List all available recipes with names, descriptions, and trigger patterns.",
_EMPTY_SCHEMA, True, False, True),