Skip to content

Commit 626743c

Browse files
committed
Expose strict-safe MCP tool names
1 parent b694a65 commit 626743c

14 files changed

Lines changed: 1370 additions & 283 deletions

README.md

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -158,34 +158,44 @@ In the GABP layer, your mod is the server and GABS is the client.
158158

159159
## Common MCP Tools
160160

161-
Most users only need a few tools at first:
162-
163-
- **`games.list`** - List configured game IDs
164-
- **`games.show`** - Show one saved game config
165-
- **`games.start`** - Start a game
166-
- **`games.stop`** - Stop a game gracefully
167-
- **`games.kill`** - Force stop a game
168-
- **`games.status`** - Check if a game is running
169-
- **`games.connect`** - Reconnect to a running game's mod bridge
170-
- **`games.tool_names`** - List mirrored game-specific tools after a mod connects
171-
- **`games.tool_detail`** - Show the schema for one mirrored tool
172-
- **`games.call_tool`** - Call a mirrored tool through the stable core surface
161+
Most users only need a few tools at first. Release builds expose strict-safe MCP
162+
tool names by default because some clients reject dots in tool names:
163+
164+
- **`games_list`** - List configured game IDs
165+
- **`games_show`** - Show one saved game config
166+
- **`games_start`** - Start a game
167+
- **`games_stop`** - Stop a game gracefully
168+
- **`games_kill`** - Force stop a game
169+
- **`games_status`** - Check if a game is running
170+
- **`games_connect`** - Reconnect to a running game's mod bridge
171+
- **`games_tool_names`** - List mirrored game-specific tools after a mod connects
172+
- **`games_tool_detail`** - Show the schema for one mirrored tool
173+
- **`games_call_tool`** - Call a connected game tool through the stable core surface
174+
175+
The older dotted names such as `games.list` and `games.call_tool` remain accepted
176+
as call aliases, but `tools/list` advertises strict-safe names unless you
177+
explicitly disable normalization in config.
173178

174179
For the full MCP surface and advanced behavior, see the
175180
[AI Integration Guide](docs/INTEGRATION.md).
176181

177182
## Game-Specific Tools from Mods
178183

179-
When a GABP-compatible mod connects, GABS mirrors the mod's canonical tool
180-
names into MCP-friendly names such as `minecraft.inventory.get` or
181-
`rimworld.crafting.build`.
184+
When a GABP-compatible mod connects, GABS mirrors the mod's canonical
185+
slash-delimited GABP tool names into strict-safe MCP names such as
186+
`minecraft_inventory_get` or `rimworld_crafting_build`.
187+
After `games_start`, tool mirroring can finish asynchronously so the first game
188+
command is not delayed by large `tools/list` responses. Use `games_call_tool`
189+
with a fully qualified slash or dotted alias when you want to issue the first
190+
command immediately; `games_tool_names` and direct mirrored MCP tools become
191+
available as soon as mirroring completes.
182192

183193
The usual discovery flow is:
184194
```
185195
AI: "Reconnect to RimWorld and show me its mod tools"
186-
GABS: games.connect {"gameId": "rimworld"}
187-
GABS: games.tool_names {"gameId": "rimworld", "brief": true}
188-
GABS: games.tool_detail {"tool": "rimworld.crafting.build"}
196+
GABS: games_connect {"gameId": "rimworld"}
197+
GABS: games_tool_names {"gameId": "rimworld", "brief": true}
198+
GABS: games_tool_detail {"tool": "rimworld_crafting_build"}
189199
```
190200

191201
Most users can ignore attention gating, resource mirroring, and protocol

docs/AI_CLIENT_SETUP.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ the `server` subcommand.
157157

158158
### OpenAI-Style Tool Calling Clients
159159

160-
Only do this if your client has strict OpenAI-style tool naming rules.
160+
This is enabled by default in current releases. Keep it enabled if your client
161+
has strict OpenAI- or Claude-style tool naming rules.
161162

162163
Enable tool normalization in `~/.gabs/config.json`:
163164

@@ -171,8 +172,9 @@ Enable tool normalization in `~/.gabs/config.json`:
171172
}
172173
```
173174

174-
This turns tool names like `minecraft.inventory.get` into
175-
`minecraft_inventory_get`.
175+
This turns tool names like `games.call_tool` and `minecraft.inventory.get` into
176+
`games_call_tool` and `minecraft_inventory_get`. Older dotted names remain
177+
accepted as call aliases.
176178

177179
See also:
178180

@@ -197,7 +199,7 @@ curl -X POST http://localhost:8080/mcp \
197199
"id": 1,
198200
"method": "tools/call",
199201
"params": {
200-
"name": "games.list",
202+
"name": "games_list",
201203
"arguments": {}
202204
}
203205
}'

docs/AI_DYNAMIC_TOOLS_FAQ.md

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,30 @@ GABS provides a compact two-step discovery flow specifically for this purpose:
1818

1919
```javascript
2020
// AI discovers compact names for a specific game
21-
const minecraftToolNames = await mcp.callTool("games.tool_names", {"gameId": "minecraft", "brief": true});
21+
const minecraftToolNames = await mcp.callTool("games_tool_names", {"gameId": "minecraft", "brief": true});
2222

2323
// AI inspects one candidate in detail
24-
const inventoryDetail = await mcp.callTool("games.tool_detail", {
25-
"tool": "minecraft.inventory.get"
24+
const inventoryDetail = await mcp.callTool("games_tool_detail", {
25+
"tool": "minecraft_inventory_get"
2626
});
2727

2828
// The richer compatibility listing still exists when needed
29-
const allGameTools = await mcp.callTool("games.tools", {});
29+
const allGameTools = await mcp.callTool("games_tools", {});
3030
```
3131

32-
This gives AI agents a predictable "discover names first, inspect details second" workflow, which is much more context-efficient than always dumping every schema up front. `games.tool_names` defaults to 50 names per page and can add one-line summaries in structured output with `brief: true`.
32+
This gives AI agents a predictable "discover names first, inspect details second" workflow, which is much more context-efficient than always dumping every schema up front. `games_tool_names` defaults to 50 names per page and can add one-line summaries in structured output with `brief: true`.
3333

3434
### 2. **Clear Namespacing Prevents Chaos**
3535
Even with many tools from multiple games, AI agents can stay oriented because:
3636

3737
```
3838
✅ Clear and Unambiguous:
39-
- minecraft.inventory.get
40-
- minecraft.world.place_block
41-
- rimworld.inventory.get
42-
- rimworld.crafting.build
43-
- valheim.inventory.get
44-
- valheim.building.construct
39+
- minecraft_inventory_get
40+
- minecraft_world_place_block
41+
- rimworld_inventory_get
42+
- rimworld_crafting_build
43+
- valheim_inventory_get
44+
- valheim_building_construct
4545
4646
❌ Confusing (what GABS avoids):
4747
- inventory.get (which game?)
@@ -68,7 +68,7 @@ up front.
6868
User: "Help me with my Minecraft server"
6969
7070
AI: Let me see what I can do with Minecraft...
71-
→ games.tool_names {"gameId": "minecraft", "brief": true}
71+
-> games_tool_names {"gameId": "minecraft", "brief": true}
7272
7373
AI: I can help you with:
7474
- Managing player inventories
@@ -81,15 +81,15 @@ AI: I can help you with:
8181
User: "Give me a diamond sword"
8282
8383
AI: I'll add that to your inventory...
84-
→ minecraft.inventory.set {"playerId": "steve", "item": "diamond_sword", "count": 1}
84+
-> minecraft_inventory_set {"playerId": "steve", "item": "diamond_sword", "count": 1}
8585
```
8686

8787
### Example 2: Multi-Game Awareness
8888
```
8989
User: "I'm running both Minecraft and RimWorld, help me manage both"
9090
9191
AI: Let me check what I can do with both games...
92-
→ games.tool_names {"brief": true}
92+
-> games_tool_names {"brief": true}
9393
9494
AI: Perfect! I can help you with:
9595
@@ -109,11 +109,11 @@ AI: Perfect! I can help you with:
109109
class GABSClient {
110110
async refreshToolsWhenNeeded() {
111111
// Check if games have changed state
112-
const currentGames = await this.mcp.callTool("games.status", {});
112+
const currentGames = await this.mcp.callTool("games_status", {});
113113

114114
if (this.gameStateChanged(currentGames)) {
115115
// Refresh compact tool names
116-
this.availableTools = await this.mcp.callTool("games.tool_names", {"brief": true});
116+
this.availableTools = await this.mcp.callTool("games_tool_names", {"brief": true});
117117
this.lastRefresh = Date.now();
118118
}
119119
}
@@ -128,13 +128,13 @@ The test suite covers the important discovery behaviors:
128128
```
129129
✅ AI starts with the stable core management surface
130130
✅ AI discovers mirrored tools after a game connects
131-
✅ AI uses games.tool_names and games.tool_detail to understand capabilities
131+
✅ AI uses games_tool_names and games_tool_detail to understand capabilities
132132
✅ AI manages multiple connected games without tool-name collisions
133133
✅ AI can recover after reconnects and session ownership changes
134134
```
135135

136136
### AI Discovery Patterns That Work:
137-
1. **Discovery-First**: Always check `games.tool_names` before attempting game actions
137+
1. **Discovery-First**: Always check `games_tool_names` before attempting game actions
138138
2. **Caching with Refresh**: Cache tools but refresh after game state changes
139139
3. **Intent-Based Filtering**: Filter large tool sets by user intent
140140
4. **Lazy Loading**: Only load tools for games the user is actually using
@@ -145,7 +145,7 @@ The test suite covers the important discovery behaviors:
145145
```javascript
146146
// Pattern 1: Always discover before acting
147147
async handleGameRequest(gameId, action) {
148-
const gameTools = await mcp.callTool("games.tool_names", {gameId, brief: true});
148+
const gameTools = await mcp.callTool("games_tool_names", {gameId, brief: true});
149149
const availableActions = parseToolsForCapabilities(gameTools);
150150

151151
if (availableActions.includes(action)) {
@@ -170,10 +170,10 @@ function organizeToolsForUser(allTools) {
170170
### ❌ Anti-Patterns to Avoid:
171171
```javascript
172172
// Don't cache tools indefinitely
173-
const toolsCache = await mcp.callTool("games.tool_names", {}); // DON'T cache forever
173+
const toolsCache = await mcp.callTool("games_tool_names", {}); // DON'T cache forever
174174

175175
// Don't assume tools exist without checking
176-
await mcp.callTool("minecraft.inventory.get", {...}); // Check games.tool_names first!
176+
await mcp.callTool("minecraft_inventory_get", {...}); // Check games_tool_names first!
177177

178178
// Don't overwhelm users with massive tool lists
179179
console.log("Here are all available tools..."); // Group by game instead!
@@ -184,10 +184,10 @@ console.log("Here are all available tools..."); // Group by game instead!
184184
### GABS Architecture Supports This:
185185
1. **MCP Compliance**: Standard JSON-RPC with proper tool metadata
186186
2. **Game Prefixing**: Automatic namespacing prevents conflicts
187-
3. **Discovery APIs**: `games.tool_names` and `games.tool_detail` provide structured exploration
187+
3. **Discovery APIs**: `games_tool_names` and `games_tool_detail` provide structured exploration
188188
4. **Status Awareness**: AI can check game state before tool usage
189189
5. **Mirror System**: Automatic GABP→MCP tool conversion
190-
6. **Session Ownership Guardrails**: Duplicate `games.start` and `games.connect`
190+
6. **Session Ownership Guardrails**: Duplicate `games_start` and `games_connect`
191191
requests return quickly instead of racing a second live GABS session
192192

193193
## What About Multiple GABS Sessions?
@@ -196,11 +196,11 @@ This matters in the real world because developers often have more than one AI
196196
session open.
197197

198198
- If one live GABS session already owns a running or starting game,
199-
`games.start` returns quickly instead of launching a duplicate process
200-
- `games.connect` also returns quickly instead of hanging on a competing bridge
199+
`games_start` returns quickly instead of launching a duplicate process
200+
- `games_connect` also returns quickly instead of hanging on a competing bridge
201201
connection
202-
- `games.status` can report that another GABS session owns the process
203-
- If takeover is intentional, `games.connect {"gameId": "rimworld",
202+
- `games_status` can report that another GABS session owns the process
203+
- If takeover is intentional, `games_connect {"gameId": "rimworld",
204204
"forceTakeover": true}` moves ownership to the current session
205205

206206
### Current State
@@ -226,7 +226,7 @@ The concern is valid: dynamic tool expansion can be confusing if the system does
226226
not provide structure. GABS provides that structure.
227227

228228
### Why It Works:
229-
1. **Predictable Discovery**: `games.tool_names` makes tool exploration systematic
229+
1. **Predictable Discovery**: `games_tool_names` makes tool exploration systematic
230230
2. **Clear Namespacing**: Game prefixes eliminate all ambiguity
231231
3. **Progressive Disclosure**: Tools appear as capabilities are needed, not all at once
232232
4. **Standard MCP**: AI agents already know how to handle MCP tool expansion
@@ -235,8 +235,8 @@ not provide structure. GABS provides that structure.
235235
### Real-World Outcome:
236236
AI agents working with GABS will naturally develop patterns like:
237237

238-
1. **Start with basics**: Use core `games.*` tools to manage games
239-
2. **Discover capabilities**: Use `games.tool_names`, then `games.tool_detail` for the few tools you want to inspect; fully qualified tool names let you omit `gameId` in the detail and call steps
238+
1. **Start with basics**: Use core `games_*` tools to manage games
239+
2. **Discover capabilities**: Use `games_tool_names`, then `games_tool_detail` for the few tools you want to inspect; fully qualified tool names let you omit `gameId` in the detail and call steps
240240
3. **Use clear names**: Always use game-prefixed tool names for clarity
241241
4. **Cache intelligently**: Refresh tool knowledge after game state changes
242242

docs/CONFIGURATION.md

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -234,20 +234,32 @@ use `games.connect` with `forceTakeover: true`.
234234

235235
## Tool Normalization Configuration
236236

237-
Only use this if your AI platform has strict tool naming rules, such as the
238-
OpenAI API.
237+
GABS exposes strict-safe MCP tool names by default. This keeps `tools/list`
238+
accepted by clients that reject dotted names, including Claude variants seen in
239+
the field. The configuration key keeps its historical name for compatibility.
239240

240241
### Tool Normalization Options
241242

242243
The `toolNormalization` section supports these options:
243244

244-
- **`enableOpenAINormalization`** (boolean): Enable/disable OpenAI-compatible normalization (default: `false`)
245-
- Replaces dots (.) with underscores (_) in tool names
245+
- **`enableOpenAINormalization`** (boolean): Enable/disable strict-safe MCP name normalization (default: `true` when `toolNormalization` is omitted)
246+
- Replaces dots, slashes, and other unsafe separators with underscores
246247
- Enforces 64-character length limit
247-
- Ensures tool names start with a letter
248248
- **`maxToolNameLength`** (integer): Maximum length for tool names (default: `64`)
249249
- **`preserveOriginalName`** (boolean): Store original name in tool description/metadata (default: `true`)
250250

251+
Set `enableOpenAINormalization` to `false` only when you intentionally need the
252+
old dotted MCP names in `tools/list`.
253+
254+
**Example transformations:**
255+
- `games.call_tool` -> `games_call_tool`
256+
- `minecraft.inventory.get` -> `minecraft_inventory_get`
257+
- GABP `rimbridge/ping` for game `rimworld` -> `rimworld_rimbridge_ping`
258+
259+
Call aliases remain backward compatible: `games_call_tool`, `games.call_tool`,
260+
qualified slash names, qualified dotted names, and discovered strict-safe names
261+
all resolve without underscore guessing.
262+
251263
### Example Configuration
252264

253265
```json
@@ -264,6 +276,8 @@ The `toolNormalization` section supports these options:
264276
}
265277
```
266278

279+
For complete details about tool normalization, see the [Tool Normalization Guide](OPENAI_TOOL_NORMALIZATION.md).
280+
267281
## Startup Timeout Configuration
268282

269283
If your game takes longer to appear in the process list or longer for its GABP
@@ -280,6 +294,10 @@ The `timeouts.startup` section supports these options:
280294
game's GABP server to become available before returning control to you
281295
(default: `60`)
282296

297+
`games_start` only waits for the GABP handshake. Mirroring the connected mod's
298+
full tool list can continue briefly in the background, so known startup commands
299+
can be sent immediately through `games_call_tool` while discovery tools refresh.
300+
283301
### Example Configuration
284302

285303
```json
@@ -297,19 +315,6 @@ The `timeouts.startup` section supports these options:
297315
}
298316
```
299317

300-
### When to Use OpenAI Normalization
301-
302-
Enable this feature when:
303-
- Using GABS with OpenAI's API directly
304-
- Your game mods use dotted tool names (e.g., `minecraft.inventory.get`)
305-
- You need strict OpenAI API compliance
306-
307-
**Example transformations:**
308-
- `minecraft.inventory.get``minecraft_inventory_get`
309-
- `rimworld.crafting.build``rimworld_crafting_build`
310-
311-
For complete details about tool normalization, see the [OpenAI Tool Normalization Guide](OPENAI_TOOL_NORMALIZATION.md).
312-
313318
## Improved Game Stopping
314319

315320
GABS can stop games more reliably when `stopProcessName` is set correctly.

0 commit comments

Comments
 (0)