Skip to content

Commit 25a1e10

Browse files
authored
feat(mcp): add server instructions to initialize response (#389)
Adds a configurable top-level `instructions` field surfaced in the MCP `initialize` response via `WithInstructions`. When unset, a routing-mode-aware built-in default explains the `retrieve_tools` → `call_tool_*` workflow and warns against using `search_servers` to find already-connected tools. - internal/config: new `Config.Instructions` field - internal/server/mcp.go: `defaultInstructions` + `resolveInstructions()`, wired into capabilities - Tests, regenerated OpenAPI artifacts, docs/configuration.md row Thanks to @badigit for the contribution.
1 parent a98ab6a commit 25a1e10

6 files changed

Lines changed: 77 additions & 1 deletion

File tree

docs/configuration.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,22 @@ All three modes are always available on dedicated endpoints regardless of config
756756

757757
See [Routing Modes](features/routing-modes.md) for complete details.
758758

759+
## Server Instructions
760+
761+
Text returned in the MCP `initialize` response to guide AI agents on how to use the proxy (e.g., use `retrieve_tools` to discover existing tools rather than `search_servers`).
762+
763+
```json
764+
{
765+
"instructions": "Use retrieve_tools to discover tools before assuming a capability is unavailable."
766+
}
767+
```
768+
769+
| Field | Type | Default | Description |
770+
|-------|------|---------|-------------|
771+
| `instructions` | string | _(built-in)_ | Custom instructions sent in the MCP `initialize` response. When empty, a built-in default explains the `retrieve_tools``call_tool_*` workflow and warns against using `search_servers` for existing tools. |
772+
773+
**Note:** Applied at startup / on the next client connect — editing this value does not hot-reload into already-connected MCP sessions.
774+
759775
---
760776

761777
## Tool-Level Quarantine

internal/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@ type Config struct {
226226
// Valid values: "retrieve_tools" (default), "direct", "code_execution"
227227
RoutingMode string `json:"routing_mode,omitempty" mapstructure:"routing-mode"`
228228

229+
// Instructions text returned in the MCP initialize response to guide AI agents.
230+
// When empty, a built-in default is used that explains retrieve_tools workflow.
231+
Instructions string `json:"instructions,omitempty" mapstructure:"instructions"`
232+
229233
// QuarantineEnabled controls whether quarantine is active. It gates two
230234
// things together:
231235
// 1. Server-level auto-quarantine for newly added servers (issue #370).

internal/server/mcp.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ const (
5858
operationListRegistries = "list_registries"
5959
operationSearchServers = "search_servers"
6060

61+
// defaultInstructions is returned in the MCP initialize response when no
62+
// custom instructions are configured. It guides AI agents on the correct
63+
// workflow for discovering and calling tools through the proxy.
64+
defaultInstructions = "This is mcpproxy-go, an MCP aggregator proxy that connects multiple upstream MCP servers and exposes their tools. " +
65+
"DISCOVERY: Use 'retrieve_tools' to search for tools by description across all connected upstream servers — do this before assuming a capability is unavailable. " +
66+
"CALLING: When 'call_tool_read', 'call_tool_write', and 'call_tool_destructive' are exposed, call the variant named by the 'call_with' field of each retrieve_tools result. " +
67+
"When 'code_execution' is exposed, you may instead orchestrate several discovered tools in a single step with JavaScript. " +
68+
"When upstream tools are listed directly (named 'server__tool'), just call them by name. " +
69+
"Do NOT use 'search_servers' to find existing tools — it searches EXTERNAL registries for adding NEW servers only. " +
70+
"Use 'upstream_servers' with operation 'list' to see currently connected servers and their status."
71+
6172
// Connection status constants
6273
statusError = "error"
6374
statusDisabled = "disabled"
@@ -67,6 +78,14 @@ const (
6778
messageConnectionCancelled = "Connection monitoring cancelled due to server shutdown"
6879
)
6980

81+
// resolveInstructions returns custom instructions if set, otherwise the built-in default.
82+
func resolveInstructions(custom string) string {
83+
if custom != "" {
84+
return custom
85+
}
86+
return defaultInstructions
87+
}
88+
7089
// proxyVersion holds the build version for MCP server identification.
7190
// Set via SetMCPServerVersion during startup.
7291
var proxyVersion = "1.0.0"
@@ -256,6 +275,9 @@ func NewMCPProxyServer(
256275
capabilities = append(capabilities, mcpserver.WithPromptCapabilities(true))
257276
}
258277

278+
// Add server instructions for AI agent guidance
279+
capabilities = append(capabilities, mcpserver.WithInstructions(resolveInstructions(config.Instructions)))
280+
259281
mcpServer := mcpserver.NewMCPServer(
260282
"mcpproxy-go",
261283
mcpServerVersion(),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package server
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestResolveInstructions_Default(t *testing.T) {
10+
result := resolveInstructions("")
11+
assert.Equal(t, defaultInstructions, result)
12+
}
13+
14+
func TestResolveInstructions_Custom(t *testing.T) {
15+
custom := "Use retrieve_tools to find tools. Custom instructions."
16+
result := resolveInstructions(custom)
17+
assert.Equal(t, custom, result)
18+
}
19+
20+
func TestDefaultInstructions_ContainsKeyTerms(t *testing.T) {
21+
assert.Contains(t, defaultInstructions, "retrieve_tools")
22+
assert.Contains(t, defaultInstructions, "search_servers")
23+
assert.Contains(t, defaultInstructions, "call_tool_read")
24+
assert.Contains(t, defaultInstructions, "upstream_servers")
25+
// Routing-mode-aware guidance: the default must also cover the
26+
// code_execution and direct (server__tool) modes (Spec 031).
27+
assert.Contains(t, defaultInstructions, "code_execution")
28+
assert.Contains(t, defaultInstructions, "server__tool")
29+
}

oas/docs.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

oas/swagger.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ components:
7575
so an unset key behaves exactly as before this feature (SC-005). Validated
7676
in Validate(): health-check ∈ {0} ∪ [5s,1h]; tool-discovery ∈ {0} ∪ [30s,24h].
7777
type: string
78+
instructions:
79+
description: |-
80+
Instructions text returned in the MCP initialize response to guide AI agents.
81+
When empty, a built-in default is used that explains retrieve_tools workflow.
82+
type: string
7883
intent_declaration:
7984
$ref: '#/components/schemas/config.IntentDeclarationConfig'
8085
listen:

0 commit comments

Comments
 (0)