portals-mcp 1.3.0 → 1.3.2
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.
- package/README.md +2 -0
- package/dist/resources/index/items.json +3 -3
- package/dist/resources/index/triggers.json +217 -210
- package/dist/resources/python/README.md +68 -50
- package/dist/resources/python/lib/portals_core.py +5 -1
- package/dist/resources/python/lib/portals_effects.py +1680 -1675
- package/dist/resources/python/lib/portals_key_input.py +284 -0
- package/dist/resources/python/lib/portals_trigger_zone_input.py +293 -0
- package/dist/resources/python/tools/apply_ops.py +366 -365
- package/dist/resources/python/tools/simulate_key_input.py +80 -0
- package/dist/resources/python/tools/simulate_trigger_zone_input.py +81 -0
- package/dist/resources/python/tools/validate_room.py +43 -0
- package/dist/resources/ref/items/trigger-zone.json +59 -56
- package/dist/resources/ref/pitfalls.json +5 -0
- package/dist/resources/ref/room-data-skeleton.json +67 -67
- package/dist/resources/ref/systems/parent-child.json +1 -1
- package/dist/resources/reference/api-cheatsheet.md +4 -2
- package/dist/resources/reference/gotchas.md +2 -0
- package/dist/resources/reference/interactions.md +7 -3
- package/dist/resources/reference/items/gameplay.md +364 -362
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +163 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,8 @@ npx portals-mcp@latest
|
|
|
52
52
|
| `duplicate_room` | Clone a room with all data |
|
|
53
53
|
| `get_room_data` | Download room snapshot to temp JSON |
|
|
54
54
|
| `inspect_room_data` | Summarize room-data counts, item types, variables, triggers/actions, and warnings without dumping full JSON |
|
|
55
|
+
| `simulate_key_input` | Audit and simulate `OnKeyPressedEvent` / `OnKeyReleasedEvent` mappings against a snapshot |
|
|
56
|
+
| `simulate_trigger_zone_input` | Audit and simulate Trigger `pressBtn` / `keyCode` press-inside-zone behavior |
|
|
55
57
|
| `query_room` | Query room data for specific items, logic, or structure |
|
|
56
58
|
| `update_room_settings` | Modify name, description, image, privacy, loading screens |
|
|
57
59
|
|
|
@@ -113,13 +113,13 @@
|
|
|
113
113
|
"category": "gameplay",
|
|
114
114
|
"key_fields": {
|
|
115
115
|
"events": "array — legacy events (usually [])",
|
|
116
|
-
"pressBtn": "bool — require key press",
|
|
117
|
-
"keyCode": "string — key to press (X, H, E)",
|
|
116
|
+
"pressBtn": "bool — require key press while inside before firing OnEnterEvent tasks",
|
|
117
|
+
"keyCode": "string — InputHandler key to press (X, H, E, Alpha1)",
|
|
118
118
|
"cm": "string — custom press message",
|
|
119
119
|
"opacity": "float — editor opacity"
|
|
120
120
|
},
|
|
121
121
|
"valid_triggers": ["OnEnterEvent", "OnExitEvent", "OnKeyPressedEvent", "OnKeyReleasedEvent", "OnPlayerLoggedIn", "ScoreTrigger", "OnTimerStopped", "OnCountdownTimerFinished", "OnAnimationStoppedEvent", "OnPlayerDied", "OnPlayerRevived", "PlayerLeave"],
|
|
122
|
-
"notes": "Invisible during play. NEVER use OnClickEvent/OnHoverStart/OnHoverEnd on triggers.",
|
|
122
|
+
"notes": "Invisible during play. NEVER use OnClickEvent/OnHoverStart/OnHoverEnd on triggers. pressBtn:true gates OnEnterEvent until keyCode is pressed while inside; it is not global OnKeyPressedEvent.",
|
|
123
123
|
"spec_uri": "docs://ref/items/trigger-zone"
|
|
124
124
|
},
|
|
125
125
|
{
|
|
@@ -1,210 +1,217 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "0.1.0",
|
|
3
|
-
"description": "Compact lookup of all Portals triggers. applicable_items='any' means general trigger; specific prefabNames means item-specific.",
|
|
4
|
-
"triggers": [
|
|
5
|
-
{
|
|
6
|
-
"$type": "OnClickEvent",
|
|
7
|
-
"description": "Player clicks item",
|
|
8
|
-
"applicable_items": "any",
|
|
9
|
-
"gotchas": ["Requires VISIBLE item — NEVER use on Trigger cubes (invisible)"]
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"$type": "OnEnterEvent",
|
|
13
|
-
"description": "Player enters trigger zone",
|
|
14
|
-
"applicable_items": ["Trigger"],
|
|
15
|
-
"gotchas": ["ONLY works on Trigger cubes, not ResizableCubes or GLBs"]
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"$type": "OnExitEvent",
|
|
19
|
-
"description": "Player exits trigger zone",
|
|
20
|
-
"applicable_items": ["Trigger"],
|
|
21
|
-
"gotchas": ["ONLY works on Trigger cubes, not ResizableCubes or GLBs"]
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"$type": "OnCollideEvent",
|
|
25
|
-
"description": "Player collides with item",
|
|
26
|
-
"applicable_items": "any",
|
|
27
|
-
"gotchas": []
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"$type": "OnCollisionStoppedEvent",
|
|
31
|
-
"description": "Player stops colliding with item",
|
|
32
|
-
"applicable_items": "any",
|
|
33
|
-
"gotchas": []
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"$type": "OnHoverStartEvent",
|
|
37
|
-
"description": "Cursor enters item",
|
|
38
|
-
"applicable_items": "any",
|
|
39
|
-
"gotchas": ["Requires VISIBLE item — NEVER use on Trigger cubes (invisible)"]
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"$type": "OnHoverEndEvent",
|
|
43
|
-
"description": "Cursor leaves item",
|
|
44
|
-
"applicable_items": "any",
|
|
45
|
-
"gotchas": ["Requires VISIBLE item — NEVER use on Trigger cubes (invisible)"]
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"$type": "OnKeyPressedEvent",
|
|
49
|
-
"description": "Keyboard key pressed down",
|
|
50
|
-
"applicable_items": "any",
|
|
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
|
-
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"description": "Compact lookup of all Portals triggers. applicable_items='any' means general trigger; specific prefabNames means item-specific.",
|
|
4
|
+
"triggers": [
|
|
5
|
+
{
|
|
6
|
+
"$type": "OnClickEvent",
|
|
7
|
+
"description": "Player clicks item",
|
|
8
|
+
"applicable_items": "any",
|
|
9
|
+
"gotchas": ["Requires VISIBLE item — NEVER use on Trigger cubes (invisible)"]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$type": "OnEnterEvent",
|
|
13
|
+
"description": "Player enters trigger zone. If the Trigger item has pressBtn:true, this fires only after the player enters and presses the Trigger keyCode.",
|
|
14
|
+
"applicable_items": ["Trigger"],
|
|
15
|
+
"gotchas": ["ONLY works on Trigger cubes, not ResizableCubes or GLBs", "pressBtn:true gates this trigger by keyCode while inside the zone; use simulate_trigger_zone_input to audit."]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"$type": "OnExitEvent",
|
|
19
|
+
"description": "Player exits trigger zone",
|
|
20
|
+
"applicable_items": ["Trigger"],
|
|
21
|
+
"gotchas": ["ONLY works on Trigger cubes, not ResizableCubes or GLBs"]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"$type": "OnCollideEvent",
|
|
25
|
+
"description": "Player collides with item",
|
|
26
|
+
"applicable_items": "any",
|
|
27
|
+
"gotchas": []
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"$type": "OnCollisionStoppedEvent",
|
|
31
|
+
"description": "Player stops colliding with item",
|
|
32
|
+
"applicable_items": "any",
|
|
33
|
+
"gotchas": []
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"$type": "OnHoverStartEvent",
|
|
37
|
+
"description": "Cursor enters item",
|
|
38
|
+
"applicable_items": "any",
|
|
39
|
+
"gotchas": ["Requires VISIBLE item — NEVER use on Trigger cubes (invisible)"]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"$type": "OnHoverEndEvent",
|
|
43
|
+
"description": "Cursor leaves item",
|
|
44
|
+
"applicable_items": "any",
|
|
45
|
+
"gotchas": ["Requires VISIBLE item — NEVER use on Trigger cubes (invisible)"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"$type": "OnKeyPressedEvent",
|
|
49
|
+
"description": "Keyboard key pressed down",
|
|
50
|
+
"applicable_items": "any",
|
|
51
|
+
"params": {
|
|
52
|
+
"key": "required InputHandler key-code string, e.g. Alpha1, Space, Return, LeftShift",
|
|
53
|
+
"key2": "optional second key-code string for combinations; fires when either configured key goes down while the other is held"
|
|
54
|
+
},
|
|
55
|
+
"gotchas": ["Requires key. Use Unity/Portals key names, not DOM names: Alpha1 not 1/Digit1, Return not Enter, LeftArrow not ArrowLeft."]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"$type": "OnKeyReleasedEvent",
|
|
59
|
+
"description": "Keyboard key released",
|
|
60
|
+
"applicable_items": "any",
|
|
61
|
+
"params": {
|
|
62
|
+
"key": "required InputHandler key-code string, e.g. Alpha1, Space, Return, LeftShift"
|
|
63
|
+
},
|
|
64
|
+
"gotchas": ["Requires key. key2 combinations are only supported by OnKeyPressedEvent, not OnKeyReleasedEvent."]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"$type": "ScoreTrigger",
|
|
68
|
+
"description": "Variable value changes",
|
|
69
|
+
"applicable_items": "any",
|
|
70
|
+
"gotchas": ["Fires on ANY variable change — use FunctionEffector to filter by specific variable"]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"$type": "OnItemCollectedEvent",
|
|
74
|
+
"description": "Collectible item picked up",
|
|
75
|
+
"applicable_items": ["GlbCollectable"],
|
|
76
|
+
"gotchas": []
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"$type": "OnPlayerLoggedIn",
|
|
80
|
+
"description": "Player joins room — primary 'game start' trigger",
|
|
81
|
+
"applicable_items": "any",
|
|
82
|
+
"gotchas": ["Fires once per player per room join. Use for init: variables, sounds, hide/show, equip."]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"$type": "OnPlayerDied",
|
|
86
|
+
"description": "Player health reaches 0",
|
|
87
|
+
"applicable_items": "any",
|
|
88
|
+
"gotchas": []
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"$type": "OnPlayerRevived",
|
|
92
|
+
"description": "Player revives after death",
|
|
93
|
+
"applicable_items": "any",
|
|
94
|
+
"gotchas": []
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"$type": "OnPlayerMove",
|
|
98
|
+
"description": "Player starts moving",
|
|
99
|
+
"applicable_items": "any",
|
|
100
|
+
"gotchas": []
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"$type": "OnPlayerStoppedMoving",
|
|
104
|
+
"description": "Player stops moving",
|
|
105
|
+
"applicable_items": "any",
|
|
106
|
+
"gotchas": []
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"$type": "PlayerLeave",
|
|
110
|
+
"description": "Player leaves room",
|
|
111
|
+
"applicable_items": "any",
|
|
112
|
+
"gotchas": []
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"$type": "OnMicrophoneUnmuted",
|
|
116
|
+
"description": "Player unmutes microphone",
|
|
117
|
+
"applicable_items": "any",
|
|
118
|
+
"gotchas": []
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"$type": "OnTimerStopped",
|
|
122
|
+
"description": "Named timer stopped via StopTimerEffect",
|
|
123
|
+
"applicable_items": "any",
|
|
124
|
+
"gotchas": ["Only fires when StopTimerEffect is used, not CancelTimerEffect"]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"$type": "OnCountdownTimerFinished",
|
|
128
|
+
"description": "Countdown timer reaches 0",
|
|
129
|
+
"applicable_items": "any",
|
|
130
|
+
"gotchas": []
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"$type": "OnAnimationStoppedEvent",
|
|
134
|
+
"description": "PortalsAnimation finishes playing",
|
|
135
|
+
"applicable_items": "any",
|
|
136
|
+
"gotchas": ["Fires when a PortalsAnimation completes, not GLB embedded animations"]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"$type": "OnItemClickEvent",
|
|
140
|
+
"description": "Backpack/inventory item activated",
|
|
141
|
+
"applicable_items": "any",
|
|
142
|
+
"gotchas": []
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"$type": "SwapVolume",
|
|
146
|
+
"description": "Swap volume trigger",
|
|
147
|
+
"applicable_items": "any",
|
|
148
|
+
"gotchas": []
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"$type": "OnDestroyedEvent",
|
|
152
|
+
"description": "Destructible item destroyed",
|
|
153
|
+
"applicable_items": ["Destructible"],
|
|
154
|
+
"gotchas": []
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"$type": "OnGunEquippedTrigger",
|
|
158
|
+
"description": "Player equips a gun",
|
|
159
|
+
"applicable_items": ["Gun", "Shotgun"],
|
|
160
|
+
"gotchas": []
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"$type": "ShotHitTrigger",
|
|
164
|
+
"description": "Bullet hits a target",
|
|
165
|
+
"applicable_items": ["Gun", "Shotgun"],
|
|
166
|
+
"gotchas": []
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"$type": "GotKillTrigger",
|
|
170
|
+
"description": "Player gets a kill with gun",
|
|
171
|
+
"applicable_items": ["Gun", "Shotgun"],
|
|
172
|
+
"gotchas": []
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"$type": "StartedAimingTrigger",
|
|
176
|
+
"description": "Player aims down sights",
|
|
177
|
+
"applicable_items": ["Gun", "Shotgun"],
|
|
178
|
+
"gotchas": []
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"$type": "StoppedAimingTrigger",
|
|
182
|
+
"description": "Player stops aiming",
|
|
183
|
+
"applicable_items": ["Gun", "Shotgun"],
|
|
184
|
+
"gotchas": []
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"$type": "OnGunTossedTrigger",
|
|
188
|
+
"description": "Player drops/tosses gun",
|
|
189
|
+
"applicable_items": ["Gun", "Shotgun"],
|
|
190
|
+
"gotchas": []
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"$type": "OnVehicleEntered",
|
|
194
|
+
"description": "Player enters vehicle",
|
|
195
|
+
"applicable_items": ["Vehicle"],
|
|
196
|
+
"gotchas": []
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"$type": "OnVehicleExited",
|
|
200
|
+
"description": "Player exits vehicle",
|
|
201
|
+
"applicable_items": ["Vehicle"],
|
|
202
|
+
"gotchas": []
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"$type": "OnEnemyDied",
|
|
206
|
+
"description": "Enemy NPC killed",
|
|
207
|
+
"applicable_items": ["EnemyNPC"],
|
|
208
|
+
"gotchas": []
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"$type": "OnTakeDamageTrigger",
|
|
212
|
+
"description": "Enemy NPC took damage",
|
|
213
|
+
"applicable_items": ["EnemyNPC"],
|
|
214
|
+
"gotchas": []
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
}
|
|
@@ -1,50 +1,68 @@
|
|
|
1
|
-
# Portals Python Toolkit
|
|
2
|
-
|
|
3
|
-
The Python toolkit provides validation, room analysis, and query tools. It is bundled inside the MCP server.
|
|
4
|
-
|
|
5
|
-
## How Tools Run
|
|
6
|
-
|
|
7
|
-
**Automatic** — these run without you calling them:
|
|
8
|
-
- `validate_room.py` runs automatically before every `apply_operations` and `set_room_data` push. If validation fails, the push is blocked.
|
|
9
|
-
- `index_room.py` runs automatically after every `get_room_data` pull. The index file path is returned alongside the data file.
|
|
10
|
-
|
|
11
|
-
**Via dedicated MCP tools**:
|
|
12
|
-
- `query_room` — search items by type, position, triggers, text, parent, or quest reference
|
|
13
|
-
- `
|
|
14
|
-
- `
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **`
|
|
23
|
-
- **`
|
|
24
|
-
- **`
|
|
25
|
-
- **`
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
query_room
|
|
36
|
-
|
|
37
|
-
query_room(roomId: "...",
|
|
38
|
-
query_room(roomId: "...",
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
# Portals Python Toolkit
|
|
2
|
+
|
|
3
|
+
The Python toolkit provides validation, room analysis, and query tools. It is bundled inside the MCP server.
|
|
4
|
+
|
|
5
|
+
## How Tools Run
|
|
6
|
+
|
|
7
|
+
**Automatic** — these run without you calling them:
|
|
8
|
+
- `validate_room.py` runs automatically before every `apply_operations` and `set_room_data` push. If validation fails, the push is blocked.
|
|
9
|
+
- `index_room.py` runs automatically after every `get_room_data` pull. The index file path is returned alongside the data file.
|
|
10
|
+
|
|
11
|
+
**Via dedicated MCP tools**:
|
|
12
|
+
- `query_room` — search items by type, position, triggers, text, parent, or quest reference
|
|
13
|
+
- `simulate_key_input` — audit and simulate keyboard triggers against a snapshot
|
|
14
|
+
- `simulate_trigger_zone_input` — audit and simulate Trigger `pressBtn` / `keyCode` press-inside-zone behavior
|
|
15
|
+
- `apply_operations` — add, modify, remove items/logic/quests (auto-pulls fresh data, auto-validates)
|
|
16
|
+
- `get_room_data` — download room data with auto-generated index
|
|
17
|
+
|
|
18
|
+
## Building Rooms
|
|
19
|
+
|
|
20
|
+
Use `apply_operations` for all room building — from simple edits to complex 200+ item builds. Key operations:
|
|
21
|
+
|
|
22
|
+
- **`add_item`** — place a single item
|
|
23
|
+
- **`add_item_with_logic`** — place an item AND attach trigger/effect logic in one step
|
|
24
|
+
- **`modify_item`** / **`remove_item`** — edit or delete existing items
|
|
25
|
+
- **`add_logic_task`** / **`clear_logic_tasks`** — wire interactions onto existing items
|
|
26
|
+
- **`add_quest`** / **`remove_quest`** — manage quests
|
|
27
|
+
- **`add_component`** — use pre-built component patterns (use `lookup("components")` to discover available components)
|
|
28
|
+
|
|
29
|
+
**Cross-item references**: Use `"__ref:N"` to reference items created earlier in the same batch. Example: `[{op:"add_item", type:"ResizableCube", pos:[0,1,0]}, {op:"add_logic_task", id:"__ref:0", task:{...}}]`
|
|
30
|
+
|
|
31
|
+
For bulk builds, pass hundreds of operations in a single `apply_operations` call.
|
|
32
|
+
|
|
33
|
+
## Querying Room Data
|
|
34
|
+
|
|
35
|
+
Use the `query_room` MCP tool to search items without loading the full room JSON:
|
|
36
|
+
```
|
|
37
|
+
query_room(roomId: "...", types: ["GLB"], hasTriggers: true)
|
|
38
|
+
query_room(roomId: "...", near: {x:10, y:0, z:15}, radius: 5)
|
|
39
|
+
query_room(roomId: "...", search: "door")
|
|
40
|
+
query_room(roomId: "...", parent: "12")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Auditing Keyboard Triggers
|
|
44
|
+
|
|
45
|
+
Use `simulate_key_input` before live testing `OnKeyPressedEvent` / `OnKeyReleasedEvent` logic:
|
|
46
|
+
```
|
|
47
|
+
simulate_key_input(filePath: ".../snapshot.json", actions: [{type: "press", key: "Alpha1"}])
|
|
48
|
+
simulate_key_input(filePath: ".../snapshot.json", actions: [{type: "press", key: "LeftShift"}, {type: "press", key: "Alpha1"}])
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Keyboard trigger keys must use Portals/Unity key-code names such as `Alpha1`, `Space`, `Return`, `LeftShift`, and `LeftArrow`.
|
|
52
|
+
|
|
53
|
+
Use `simulate_trigger_zone_input` for Trigger cube `pressBtn` behavior. This is not a global `OnKeyPressedEvent`; it gates the Trigger's `OnEnterEvent` tasks until the player enters the zone and presses `keyCode`.
|
|
54
|
+
```
|
|
55
|
+
simulate_trigger_zone_input(filePath: ".../snapshot.json", actions: [{type: "enter", itemId: "12"}, {type: "press", key: "X"}])
|
|
56
|
+
simulate_trigger_zone_input(filePath: ".../snapshot.json", actions: [{type: "press", key: "X"}, {type: "enter", itemId: "12"}])
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Scripts (manual / Blender)
|
|
60
|
+
| Script | Purpose | Usage |
|
|
61
|
+
|--------|---------|-------|
|
|
62
|
+
| `blender_to_portals.py` | Headless Blender export | `blender --background <file> --python tools/blender_to_portals.py -- <output>` |
|
|
63
|
+
| `extract_glb_metadata.py` | Extract GLB dimensions/thumbnails (needs numpy, trimesh) | `python tools/extract_glb_metadata.py <glbs> <room-id>` |
|
|
64
|
+
| `classify_modular_edges.py` | Classify modular kit edges (needs numpy, trimesh) | After extract |
|
|
65
|
+
| `manifest_to_room_data.py` | Convert Blender manifest to snapshot | `python tools/manifest_to_room_data.py <export-dir> --cdn-urls <urls.json>` |
|
|
66
|
+
| `search_recipes.py` | Search recipes (use MCP `search_recipes` tool instead) | — |
|
|
67
|
+
| `build_recipe_manifest.py` | Regenerate recipe manifest | After adding/editing recipes |
|
|
68
|
+
| `parse_cdn_upload.py` | Parse CDN upload results | After batch upload |
|
|
@@ -16,6 +16,8 @@ Usage:
|
|
|
16
16
|
|
|
17
17
|
from typing import Dict, Tuple, Optional
|
|
18
18
|
|
|
19
|
+
from portals_key_input import validate_key_code
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
def create_base_item(
|
|
21
23
|
prefab_name: str,
|
|
@@ -350,12 +352,14 @@ def create_trigger(
|
|
|
350
352
|
Notes:
|
|
351
353
|
- Invisible during play (visible in build mode)
|
|
352
354
|
- Two trigger types: User Enter and User Exit
|
|
355
|
+
- If press_button=True, OnEnterEvent tasks fire after the player enters
|
|
356
|
+
the zone and presses key_code; this is separate from OnKeyPressedEvent.
|
|
353
357
|
- Add effects via Tasks array (see portals_effects.py)
|
|
354
358
|
"""
|
|
355
359
|
logic = {
|
|
356
360
|
"events": [],
|
|
357
361
|
"cm": message,
|
|
358
|
-
"keyCode": key_code,
|
|
362
|
+
"keyCode": validate_key_code(key_code, "key_code"),
|
|
359
363
|
"Tasks": [],
|
|
360
364
|
"ViewNodes": []
|
|
361
365
|
}
|