Enable tool-search synthesis
This guide walks through enabling tool-search synthesis. Use it when:
- Your upstream MCP servers expose many tools (50+) and your agents waste tokens on tool definitions in every prompt
- The upstream doesn’t natively support a
tools/searchmethod - You want a single virtual
mcp_searchtool to appear intools/listinstead of the full upstream catalogue
Behavior change: synthesize mode is a behavioral override. Agents already coded against specific tool names will continue to call those names directly and will not benefit. Agents that read
tools/listand react to what they find will need to callmcp_searchfirst to discover tool names. See Tool-search tradeoffs before enabling in production.
Prerequisites
- mcpgw vNEXT
- One or more upstreams configured under
upstreams:
Steps
1. Enable synthesize mode
Edit mcpgw.yaml:
tool_search:
enabled: true
mode: synthesize
refresh_interval: 60s # how often to pull tools/list from each upstream
max_results: 20 # hard cap on results returned by mcp_search
tool_search.enabled: true with mode: synthesize is the only supported combination that changes gateway behavior. mode: passthrough (the default) is a no-op and preserves prior tools/list behavior.
2. Restart (mode is not hot-reloadable)
mcpgw reads tool_search.mode once at startup. SIGHUP refreshes the index but does not switch between passthrough and synthesize. Restart for any mode change.
# Docker
docker restart mcpgw
# systemd
systemctl restart mcpgw
# direct process (sends SIGTERM, process must restart via supervisor)
kill "$(pgrep mcpgw)"
3. Verify the index populated
On startup, the refresher attempts one initial tools/list call to each configured upstream. Check stderr or journald for the result:
- Failure: the log contains
toolindex: initial refresh failedwith anerr=field naming the cause. The gateway continues running butmcp_searchreturns empty results until a later refresh cycle succeeds. - Success: no log line is emitted for a successful refresh — the refresher is silent on the happy path.
To inspect the live index state, run mcpgw stats (if exposed in your deployment) or tail the process logs for subsequent toolindex: refresh failed warnings at the configured refresh_interval.
4. Verify tools/list returns the stub
curl -s -X POST https://gw.acme.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq
Expected response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "mcp_search",
"description": "Search available tools by keyword. Returns ranked matches with full definitions. Call this with {query: \"...\"} before tools/call when you don't know the exact tool name.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Keywords to search for in tool names and descriptions."
},
"limit": {
"type": "integer",
"description": "Maximum number of results to return."
}
},
"required": ["query"]
}
}
]
}
}
If you see the upstream’s full tool list instead, confirm tool_search.enabled: true and mode: synthesize in the running config, then restart.
5. Try a search
curl -s -X POST https://gw.acme.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"mcp_search","arguments":{"query":"read","limit":5}}}' | jq
Expected: a result.content[0].text containing a JSON array of matching tool definitions pulled from the index. Each element has the same shape as a single tool in a tools/list response — name, description, and inputSchema.
If the array is empty, see Troubleshooting below.
6. Audit log
Synthesized responses appear in audit.jsonl with action values synthesize_list and synthesize_search. Filter:
jq 'select(.action | startswith("synthesize"))' audit.jsonl
A synthesize_search line looks like:
{
"ts": "2026-05-09T14:23:01.123Z",
"session_id": "claude-desktop-3f2a",
"method": "tools/call",
"tool_name": "mcp_search",
"action": "synthesize_search",
"latency_ms": 1
}
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
tools/list returns the upstream’s full tool list | tool_search.enabled: false or mode: passthrough, or config was not reloaded | Confirm config; restart (SIGHUP alone does not switch modes) |
mcp_search returns an empty results array | Index hasn’t populated yet, or all upstream refreshes failed | Check logs for toolindex: initial refresh failed or toolindex: refresh failed; verify upstreams are reachable from the gateway host |
Audit shows synthesize_search but calls to discovered tools return no_route | The discovered tool belongs to an upstream not covered by your routes: config | Add a matching route entry, or set default_upstream to a catch-all upstream |
| Mode change doesn’t take effect after SIGHUP | SIGHUP refreshes only the index, not the routing policy | Restart for mode changes |
Agent ignores mcp_search and calls tool names directly | Agent was trained on or configured with explicit tool names | Those calls pass through to the upstream unchanged — synthesize mode does not intercept them. Token savings do not apply; see tradeoffs |
Disabling
Set tool_search.enabled: false and restart. The gateway reverts to forwarding tools/list directly to the upstream.
Related
- Tool-search tradeoffs — design rationale, keyword scoring limits, and when synthesize mode does not help
- Configuration reference: tool_search
- Audit log schema