portals-mcp 1.3.0 → 1.3.1

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 CHANGED
@@ -52,6 +52,7 @@ 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 |
55
56
  | `query_room` | Query room data for specific items, logic, or structure |
56
57
  | `update_room_settings` | Modify name, description, image, privacy, loading screens |
57
58
 
@@ -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
- "gotchas": []
52
- },
53
- {
54
- "$type": "OnKeyReleasedEvent",
55
- "description": "Keyboard key released",
56
- "applicable_items": "any",
57
- "gotchas": []
58
- },
59
- {
60
- "$type": "ScoreTrigger",
61
- "description": "Variable value changes",
62
- "applicable_items": "any",
63
- "gotchas": ["Fires on ANY variable change — use FunctionEffector to filter by specific variable"]
64
- },
65
- {
66
- "$type": "OnItemCollectedEvent",
67
- "description": "Collectible item picked up",
68
- "applicable_items": ["GlbCollectable"],
69
- "gotchas": []
70
- },
71
- {
72
- "$type": "OnPlayerLoggedIn",
73
- "description": "Player joins room — primary 'game start' trigger",
74
- "applicable_items": "any",
75
- "gotchas": ["Fires once per player per room join. Use for init: variables, sounds, hide/show, equip."]
76
- },
77
- {
78
- "$type": "OnPlayerDied",
79
- "description": "Player health reaches 0",
80
- "applicable_items": "any",
81
- "gotchas": []
82
- },
83
- {
84
- "$type": "OnPlayerRevived",
85
- "description": "Player revives after death",
86
- "applicable_items": "any",
87
- "gotchas": []
88
- },
89
- {
90
- "$type": "OnPlayerMove",
91
- "description": "Player starts moving",
92
- "applicable_items": "any",
93
- "gotchas": []
94
- },
95
- {
96
- "$type": "OnPlayerStoppedMoving",
97
- "description": "Player stops moving",
98
- "applicable_items": "any",
99
- "gotchas": []
100
- },
101
- {
102
- "$type": "PlayerLeave",
103
- "description": "Player leaves room",
104
- "applicable_items": "any",
105
- "gotchas": []
106
- },
107
- {
108
- "$type": "OnMicrophoneUnmuted",
109
- "description": "Player unmutes microphone",
110
- "applicable_items": "any",
111
- "gotchas": []
112
- },
113
- {
114
- "$type": "OnTimerStopped",
115
- "description": "Named timer stopped via StopTimerEffect",
116
- "applicable_items": "any",
117
- "gotchas": ["Only fires when StopTimerEffect is used, not CancelTimerEffect"]
118
- },
119
- {
120
- "$type": "OnCountdownTimerFinished",
121
- "description": "Countdown timer reaches 0",
122
- "applicable_items": "any",
123
- "gotchas": []
124
- },
125
- {
126
- "$type": "OnAnimationStoppedEvent",
127
- "description": "PortalsAnimation finishes playing",
128
- "applicable_items": "any",
129
- "gotchas": ["Fires when a PortalsAnimation completes, not GLB embedded animations"]
130
- },
131
- {
132
- "$type": "OnItemClickEvent",
133
- "description": "Backpack/inventory item activated",
134
- "applicable_items": "any",
135
- "gotchas": []
136
- },
137
- {
138
- "$type": "SwapVolume",
139
- "description": "Swap volume trigger",
140
- "applicable_items": "any",
141
- "gotchas": []
142
- },
143
- {
144
- "$type": "OnDestroyedEvent",
145
- "description": "Destructible item destroyed",
146
- "applicable_items": ["Destructible"],
147
- "gotchas": []
148
- },
149
- {
150
- "$type": "OnGunEquippedTrigger",
151
- "description": "Player equips a gun",
152
- "applicable_items": ["Gun", "Shotgun"],
153
- "gotchas": []
154
- },
155
- {
156
- "$type": "ShotHitTrigger",
157
- "description": "Bullet hits a target",
158
- "applicable_items": ["Gun", "Shotgun"],
159
- "gotchas": []
160
- },
161
- {
162
- "$type": "GotKillTrigger",
163
- "description": "Player gets a kill with gun",
164
- "applicable_items": ["Gun", "Shotgun"],
165
- "gotchas": []
166
- },
167
- {
168
- "$type": "StartedAimingTrigger",
169
- "description": "Player aims down sights",
170
- "applicable_items": ["Gun", "Shotgun"],
171
- "gotchas": []
172
- },
173
- {
174
- "$type": "StoppedAimingTrigger",
175
- "description": "Player stops aiming",
176
- "applicable_items": ["Gun", "Shotgun"],
177
- "gotchas": []
178
- },
179
- {
180
- "$type": "OnGunTossedTrigger",
181
- "description": "Player drops/tosses gun",
182
- "applicable_items": ["Gun", "Shotgun"],
183
- "gotchas": []
184
- },
185
- {
186
- "$type": "OnVehicleEntered",
187
- "description": "Player enters vehicle",
188
- "applicable_items": ["Vehicle"],
189
- "gotchas": []
190
- },
191
- {
192
- "$type": "OnVehicleExited",
193
- "description": "Player exits vehicle",
194
- "applicable_items": ["Vehicle"],
195
- "gotchas": []
196
- },
197
- {
198
- "$type": "OnEnemyDied",
199
- "description": "Enemy NPC killed",
200
- "applicable_items": ["EnemyNPC"],
201
- "gotchas": []
202
- },
203
- {
204
- "$type": "OnTakeDamageTrigger",
205
- "description": "Enemy NPC took damage",
206
- "applicable_items": ["EnemyNPC"],
207
- "gotchas": []
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",
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
+ "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,61 @@
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
- - `apply_operations` — add, modify, remove items/logic/quests (auto-pulls fresh data, auto-validates)
14
- - `get_room_data` — download room data with auto-generated index
15
-
16
- ## Building Rooms
17
-
18
- Use `apply_operations` for all room building — from simple edits to complex 200+ item builds. Key operations:
19
-
20
- - **`add_item`** — place a single item
21
- - **`add_item_with_logic`** — place an item AND attach trigger/effect logic in one step
22
- - **`modify_item`** / **`remove_item`** edit or delete existing items
23
- - **`add_logic_task`** / **`clear_logic_tasks`** — wire interactions onto existing items
24
- - **`add_quest`** / **`remove_quest`** — manage quests
25
- - **`add_component`** use pre-built component patterns (use `lookup("components")` to discover available components)
26
-
27
- **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:{...}}]`
28
-
29
- For bulk builds, pass hundreds of operations in a single `apply_operations` call.
30
-
31
- ## Querying Room Data
32
-
33
- Use the `query_room` MCP tool to search items without loading the full room JSON:
34
- ```
35
- query_room(roomId: "...", types: ["GLB"], hasTriggers: true)
36
- query_room(roomId: "...", near: {x:10, y:0, z:15}, radius: 5)
37
- query_room(roomId: "...", search: "door")
38
- query_room(roomId: "...", parent: "12")
39
- ```
40
-
41
- ## Scripts (manual / Blender)
42
- | Script | Purpose | Usage |
43
- |--------|---------|-------|
44
- | `blender_to_portals.py` | Headless Blender export | `blender --background <file> --python tools/blender_to_portals.py -- <output>` |
45
- | `extract_glb_metadata.py` | Extract GLB dimensions/thumbnails (needs numpy, trimesh) | `python tools/extract_glb_metadata.py <glbs> <room-id>` |
46
- | `classify_modular_edges.py` | Classify modular kit edges (needs numpy, trimesh) | After extract |
47
- | `manifest_to_room_data.py` | Convert Blender manifest to snapshot | `python tools/manifest_to_room_data.py <export-dir> --cdn-urls <urls.json>` |
48
- | `search_recipes.py` | Search recipes (use MCP `search_recipes` tool instead) | — |
49
- | `build_recipe_manifest.py` | Regenerate recipe manifest | After adding/editing recipes |
50
- | `parse_cdn_upload.py` | Parse CDN upload results | After batch upload |
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
+ - `apply_operations` — add, modify, remove items/logic/quests (auto-pulls fresh data, auto-validates)
15
+ - `get_room_data` — download room data with auto-generated index
16
+
17
+ ## Building Rooms
18
+
19
+ Use `apply_operations` for all room building — from simple edits to complex 200+ item builds. Key operations:
20
+
21
+ - **`add_item`** — place a single item
22
+ - **`add_item_with_logic`** place an item AND attach trigger/effect logic in one step
23
+ - **`modify_item`** / **`remove_item`** — edit or delete existing items
24
+ - **`add_logic_task`** / **`clear_logic_tasks`** — wire interactions onto existing items
25
+ - **`add_quest`** / **`remove_quest`** manage quests
26
+ - **`add_component`** — use pre-built component patterns (use `lookup("components")` to discover available components)
27
+
28
+ **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:{...}}]`
29
+
30
+ For bulk builds, pass hundreds of operations in a single `apply_operations` call.
31
+
32
+ ## Querying Room Data
33
+
34
+ Use the `query_room` MCP tool to search items without loading the full room JSON:
35
+ ```
36
+
37
+ ## Auditing Keyboard Triggers
38
+
39
+ Use `simulate_key_input` before live testing `OnKeyPressedEvent` / `OnKeyReleasedEvent` logic:
40
+ ```
41
+ simulate_key_input(filePath: ".../snapshot.json", actions: [{type: "press", key: "Alpha1"}])
42
+ simulate_key_input(filePath: ".../snapshot.json", actions: [{type: "press", key: "LeftShift"}, {type: "press", key: "Alpha1"}])
43
+ ```
44
+
45
+ Keyboard trigger keys must use Portals/Unity key-code names such as `Alpha1`, `Space`, `Return`, `LeftShift`, and `LeftArrow`.
46
+ query_room(roomId: "...", types: ["GLB"], hasTriggers: true)
47
+ query_room(roomId: "...", near: {x:10, y:0, z:15}, radius: 5)
48
+ query_room(roomId: "...", search: "door")
49
+ query_room(roomId: "...", parent: "12")
50
+ ```
51
+
52
+ ## Scripts (manual / Blender)
53
+ | Script | Purpose | Usage |
54
+ |--------|---------|-------|
55
+ | `blender_to_portals.py` | Headless Blender export | `blender --background <file> --python tools/blender_to_portals.py -- <output>` |
56
+ | `extract_glb_metadata.py` | Extract GLB dimensions/thumbnails (needs numpy, trimesh) | `python tools/extract_glb_metadata.py <glbs> <room-id>` |
57
+ | `classify_modular_edges.py` | Classify modular kit edges (needs numpy, trimesh) | After extract |
58
+ | `manifest_to_room_data.py` | Convert Blender manifest to snapshot | `python tools/manifest_to_room_data.py <export-dir> --cdn-urls <urls.json>` |
59
+ | `search_recipes.py` | Search recipes (use MCP `search_recipes` tool instead) | — |
60
+ | `build_recipe_manifest.py` | Regenerate recipe manifest | After adding/editing recipes |
61
+ | `parse_cdn_upload.py` | Parse CDN upload results | After batch upload |