Skip to content

Commit b15bfc9

Browse files
authored
Merge pull request #335 from smart-mcp-proxy/fix-routing-endpoint-tools
Give each routing endpoint its own focused tool set
2 parents f204be8 + 6b64e0b commit b15bfc9

31 files changed

Lines changed: 510 additions & 428 deletions

cmd/mcpproxy/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ import (
4141

4242
clioutput "github.com/smart-mcp-proxy/mcpproxy-go/internal/cli/output"
4343
"github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
44-
"github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi"
4544
"github.com/smart-mcp-proxy/mcpproxy-go/internal/experiments"
45+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/httpapi"
4646
"github.com/smart-mcp-proxy/mcpproxy-go/internal/logs"
4747
"github.com/smart-mcp-proxy/mcpproxy-go/internal/registries"
4848
"github.com/smart-mcp-proxy/mcpproxy-go/internal/server"
@@ -476,8 +476,9 @@ func runServer(cmd *cobra.Command, _ []string) error {
476476
zap.String("log_level", cmdLogLevel),
477477
zap.Bool("log_to_file", cmdLogToFile))
478478

479-
// Pass edition to httpapi for status endpoint
479+
// Pass edition and version to internal packages
480480
httpapi.SetEdition(Edition)
481+
server.SetMCPServerVersion(version)
481482

482483
// Override other settings from command line
483484
cfg.DebugSearch = cmdDebugSearch

cmd/mcpproxy/status_cmd.go

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,20 @@ import (
2121

2222
// StatusInfo holds the collected status data for display.
2323
type StatusInfo struct {
24-
State string `json:"state"`
25-
Edition string `json:"edition"`
26-
ListenAddr string `json:"listen_addr"`
27-
Uptime string `json:"uptime,omitempty"`
28-
UptimeSeconds float64 `json:"uptime_seconds,omitempty"`
29-
APIKey string `json:"api_key"`
30-
WebUIURL string `json:"web_ui_url"`
31-
RoutingMode string `json:"routing_mode"`
32-
Servers *ServerCounts `json:"servers,omitempty"`
33-
SocketPath string `json:"socket_path,omitempty"`
34-
ConfigPath string `json:"config_path,omitempty"`
35-
Version string `json:"version,omitempty"`
36-
TeamsInfo *TeamsStatusInfo `json:"teams,omitempty"`
24+
State string `json:"state"`
25+
Edition string `json:"edition"`
26+
ListenAddr string `json:"listen_addr"`
27+
Uptime string `json:"uptime,omitempty"`
28+
UptimeSeconds float64 `json:"uptime_seconds,omitempty"`
29+
APIKey string `json:"api_key"`
30+
WebUIURL string `json:"web_ui_url"`
31+
RoutingMode string `json:"routing_mode"`
32+
Endpoints map[string]string `json:"endpoints"`
33+
Servers *ServerCounts `json:"servers,omitempty"`
34+
SocketPath string `json:"socket_path,omitempty"`
35+
ConfigPath string `json:"config_path,omitempty"`
36+
Version string `json:"version,omitempty"`
37+
TeamsInfo *TeamsStatusInfo `json:"teams,omitempty"`
3738
}
3839

3940
// TeamsStatusInfo holds teams-specific status information.
@@ -218,6 +219,9 @@ func collectStatusFromDaemon(cfg *config.Config, socketPath, configPath string)
218219
info.WebUIURL = statusBuildWebUIURL(info.ListenAddr, cfg.APIKey)
219220
}
220221

222+
// Build MCP endpoint URLs
223+
info.Endpoints = statusBuildEndpoints(info.ListenAddr)
224+
221225
return info, nil
222226
}
223227

@@ -239,6 +243,7 @@ func collectStatusFromConfig(cfg *config.Config, socketPath, configPath string)
239243
APIKey: cfg.APIKey,
240244
WebUIURL: statusBuildWebUIURL(listenAddr, cfg.APIKey),
241245
RoutingMode: routingMode,
246+
Endpoints: statusBuildEndpoints(listenAddr),
242247
ConfigPath: configPath,
243248
}
244249

@@ -273,6 +278,21 @@ func statusMaskAPIKey(apiKey string) string {
273278
return apiKey[:4] + "****" + apiKey[len(apiKey)-4:]
274279
}
275280

281+
// statusBuildEndpoints constructs the MCP endpoint URLs map.
282+
func statusBuildEndpoints(listenAddr string) map[string]string {
283+
addr := listenAddr
284+
if strings.HasPrefix(addr, ":") {
285+
addr = "127.0.0.1" + addr
286+
}
287+
base := "http://" + addr
288+
return map[string]string{
289+
"default": base + "/mcp",
290+
"retrieve_tools": base + "/mcp/call",
291+
"direct": base + "/mcp/all",
292+
"code_execution": base + "/mcp/code",
293+
}
294+
}
295+
276296
// statusBuildWebUIURL constructs the Web UI URL with embedded API key.
277297
func statusBuildWebUIURL(listenAddr, apiKey string) string {
278298
addr := listenAddr
@@ -379,6 +399,23 @@ func printStatusTable(info *StatusInfo) {
379399
fmt.Printf(" %-12s %s\n", "Config:", info.ConfigPath)
380400
}
381401

402+
if info.Endpoints != nil {
403+
fmt.Println()
404+
fmt.Println("MCP Endpoints")
405+
if v, ok := info.Endpoints["default"]; ok {
406+
fmt.Printf(" %-16s %s (default, %s mode)\n", "/mcp", v, info.RoutingMode)
407+
}
408+
if v, ok := info.Endpoints["retrieve_tools"]; ok {
409+
fmt.Printf(" %-16s %s (retrieve + call tools)\n", "/mcp/call", v)
410+
}
411+
if v, ok := info.Endpoints["direct"]; ok {
412+
fmt.Printf(" %-16s %s (all tools, direct access)\n", "/mcp/all", v)
413+
}
414+
if v, ok := info.Endpoints["code_execution"]; ok {
415+
fmt.Printf(" %-16s %s (code execution)\n", "/mcp/code", v)
416+
}
417+
}
418+
382419
if info.TeamsInfo != nil {
383420
fmt.Println()
384421
fmt.Println("Server Edition")

docs/features/code-execution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ const user: User = { name: input.username };
233233
- TypeScript support uses type-stripping only (no type checking or semantic validation)
234234
- Valid JavaScript is also valid TypeScript
235235
- Transpilation errors return the `TRANSPILE_ERROR` error code with line/column information
236-
- See [Code Execution Overview](../code_execution/overview.md#typescript-support) for comprehensive TypeScript documentation
236+
- See `docs/code_execution/overview.md` in the repository for comprehensive TypeScript documentation
237237
238238
## Best Practices
239239

docs/features/routing-modes.md

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -52,36 +52,51 @@ This means you can point different AI clients at different endpoints. For exampl
5252

5353
### retrieve_tools (Default)
5454

55-
The default mode uses BM25 full-text search to help AI agents discover relevant tools without exposing the entire tool catalog. This is the most token-efficient mode.
55+
The default mode uses BM25 full-text search to help AI agents discover relevant tools without exposing the entire tool catalog. This approach — sometimes called "lazy tool loading" or "tool search" — is used by Anthropic's own MCP implementation and is the recommended pattern for large tool sets.
56+
57+
**Endpoint:** `/mcp/call`
5658

5759
**Tools exposed:**
58-
- `retrieve_tools` -- Search for tools by natural language query
59-
- `call_tool_read` -- Execute read-only tool calls
60-
- `call_tool_write` -- Execute write tool calls
61-
- `call_tool_destructive` -- Execute destructive tool calls
62-
- `upstream_servers` -- Manage upstream servers (if management enabled)
63-
- `code_execution` -- JavaScript orchestration (if enabled)
60+
- `retrieve_tools` — Search for tools by natural language query
61+
- `call_tool_read` — Execute read-only tool calls
62+
- `call_tool_write` — Execute write tool calls
63+
- `call_tool_destructive` — Execute destructive tool calls
64+
- `read_cache` — Access paginated responses
6465

6566
**How it works:**
6667
1. AI agent calls `retrieve_tools` with a natural language query
67-
2. MCPProxy returns matching tools ranked by BM25 relevance
68-
3. AI agent calls the appropriate `call_tool_*` variant with the tool name
68+
2. MCPProxy returns matching tools ranked by BM25 relevance, with `call_with` recommendations
69+
3. AI agent calls the appropriate `call_tool_*` variant with the exact tool name from results
70+
71+
**Pros:**
72+
- Massive token savings: only matched tools are sent to the model, not the full catalog
73+
- Scales to hundreds of tools across many servers without context window bloat
74+
- Intent-based permission control (read/write/destructive variants) enables granular IDE approval flows
75+
- Activity logging captures operation type and intent metadata for auditing
76+
77+
**Cons:**
78+
- Two-step workflow (search then call) adds one round-trip compared to direct mode
79+
- BM25 search quality depends on tool descriptions — poorly described tools may not surface
80+
- The AI agent must learn the retrieve-then-call pattern (most modern models handle this well)
6981

7082
**When to use:**
7183
- You have many upstream servers with dozens or hundreds of tools
72-
- Token usage is a concern (only tool metadata for matched tools is sent)
73-
- You want intent-based permission control (read/write/destructive variants)
84+
- Token usage is a concern (common with paid API usage)
85+
- You want intent-based permission control in IDE auto-approve settings
86+
- Production deployments where audit trails matter
7487

7588
### direct
7689

77-
Direct mode exposes every upstream tool directly to the AI agent. Each tool is named `serverName__toolName` (double underscore separator).
90+
Direct mode exposes every upstream tool directly to the AI agent. Each tool appears in the standard MCP `tools/list` with a `serverName__toolName` name. This is the simplest mode and the closest to how individual MCP servers work natively.
91+
92+
**Endpoint:** `/mcp/all`
7893

7994
**Tools exposed:**
8095
- Every tool from every connected, enabled, non-quarantined upstream server
8196
- Named as `serverName__toolName` (e.g., `github__create_issue`, `filesystem__read_file`)
8297

8398
**How it works:**
84-
1. AI agent sees all available tools in the tools list
99+
1. AI agent sees all available tools in `tools/list`
85100
2. AI agent calls tools directly by their `serverName__toolName` name
86101
3. MCPProxy routes the call to the correct upstream server
87102

@@ -94,31 +109,74 @@ Direct mode exposes every upstream tool directly to the AI agent. Each tool is n
94109
- Agent tokens with server restrictions are enforced (access denied if token lacks server access)
95110
- Permission levels are derived from tool annotations (read-only, destructive, etc.)
96111

112+
**Pros:**
113+
- Zero learning curve: tools work exactly like native MCP tools
114+
- Single round-trip: no search step needed, call any tool directly
115+
- Maximum compatibility: works with any MCP client without special handling
116+
- Tool annotations (readOnlyHint, destructiveHint) are preserved from upstream
117+
118+
**Cons:**
119+
- High token cost: all tool definitions are sent in every request context
120+
- Does not scale well beyond ~50 tools (context window fills up, model accuracy degrades)
121+
- No intent-based permission tiers (the model just calls tools)
122+
- All tools visible upfront increases attack surface for prompt injection
123+
97124
**When to use:**
98-
- You have a small number of upstream servers (fewer than 50 total tools)
99-
- You want maximum simplicity and compatibility
100-
- AI clients that work better with a flat tool list
125+
- Small setups with fewer than 50 total tools
126+
- Quick prototyping and testing
127+
- AI clients that don't support the retrieve-then-call pattern
128+
- CI/CD agents that know exactly which tools they need
101129

102130
### code_execution
103131

104-
Code execution mode is designed for multi-step orchestration workflows. It exposes the `code_execution` tool with an enhanced description that includes a catalog of all available upstream tools.
132+
Code execution mode is designed for multi-step orchestration workflows. Instead of making separate tool calls for each step, the AI agent writes JavaScript or TypeScript code that chains multiple tool calls together in a single request. This is inspired by patterns from OpenAI's code interpreter and similar "tool-as-code" approaches.
133+
134+
**Endpoint:** `/mcp/code`
105135

106136
**Tools exposed:**
107-
- `code_execution` -- Execute JavaScript/TypeScript that orchestrates upstream tools
108-
- `retrieve_tools` -- Search for tools (useful for discovery before writing code)
137+
- `code_execution` Execute JavaScript/TypeScript that orchestrates upstream tools (includes a catalog of all available tools in the description)
138+
- `retrieve_tools` Search for tools (instructs to use `code_execution`, not `call_tool_*`)
109139

110140
**How it works:**
111141
1. AI agent sees the `code_execution` tool with a listing of all available upstream tools
112142
2. AI agent writes JavaScript/TypeScript code that calls `call_tool(serverName, toolName, args)`
113-
3. MCPProxy executes the code in a sandboxed VM, routing tool calls to upstream servers
143+
3. MCPProxy executes the code in a sandboxed ES2020+ VM, routing tool calls to upstream servers
144+
4. Results are returned as a single response
145+
146+
**Pros:**
147+
- Minimal round-trips: complex multi-step workflows execute in one request
148+
- Full programming power: conditionals, loops, error handling, data transformation
149+
- TypeScript support with type safety (auto-transpiled via esbuild)
150+
- Sandboxed execution: no filesystem or network access, timeout enforcement
151+
- Tool catalog in description means no separate search step needed
152+
153+
**Cons:**
154+
- Requires the AI model to write correct JavaScript/TypeScript code
155+
- Debugging is harder: errors come from inside the sandbox, not from MCP tool calls
156+
- Higher latency per request (VM startup + multiple sequential tool calls)
157+
- Must be explicitly enabled (`"enable_code_execution": true`)
158+
- Not all AI models are equally good at writing code for tool orchestration
114159

115160
**When to use:**
116-
- Workflows that require chaining 2+ tool calls together
117-
- You want to minimize model round-trips
118-
- Complex conditional logic or data transformation between tool calls
161+
- Workflows that chain 2+ tool calls with data dependencies between them
162+
- Batch operations (e.g., "for each repo, check CI status and create issue if failing")
163+
- Complex conditional logic that would require many round-trips in other modes
164+
- Data transformation pipelines (fetch from one tool, transform, send to another)
119165

120166
**Note:** Code execution must be enabled in config (`"enable_code_execution": true`). If disabled, the `code_execution` tool appears but returns an error message directing the user to enable it.
121167

168+
## Choosing the Right Mode
169+
170+
| Factor | retrieve_tools | direct | code_execution |
171+
|--------|---------------|--------|----------------|
172+
| **Token cost** | Low (only matched tools) | High (all tools) | Medium (catalog in description) |
173+
| **Round-trips per task** | 2 (search + call) | 1 (direct call) | 1 (code handles all) |
174+
| **Max practical tools** | 500+ | ~50 | 500+ |
175+
| **Setup complexity** | None (default) | None | Requires enablement |
176+
| **Model requirements** | Any modern LLM | Any LLM | Code-capable LLM |
177+
| **Audit granularity** | High (intent metadata) | Medium (annotations) | Medium (code logged) |
178+
| **IDE auto-approve** | Per-variant rules | Per-tool rules | Single rule |
179+
122180
## Viewing Current Routing Mode
123181

124182
### CLI

0 commit comments

Comments
 (0)