Skip to content

Commit 19c404f

Browse files
committed
feat: Add progressive tool discovery system to MCP Python SDK
Implements optional progressive disclosure of MCP tools through semantic grouping and lazy-loading. Servers can organize tools into semantic groups with gateway tools that load actual tools on-demand, achieving significant reduction in context token usage. ## Architecture ### Core Components - ToolGroup: Semantic organization of related tools - ToolGroupRegistry & ToolGroupManager: Discovery infrastructure - Server.enable_discovery_with_groups(): Simple API to enable discovery - is_discovery_enabled: Property to check discovery status - Client-side tool refresh: Automatic handling via ToolListChangedNotification ### Key Features - Hybrid mode: Mix direct tools with grouped tools - Lazy loading: Tools load only when needed - Non-blocking refresh: Tool refresh happens in background - Backward compatible: Discovery is entirely opt-in - No protocol changes: Works with existing MCP clients ## Usage Example ```python from mcp.server import Server from mcp import ToolGroup, Tool math_group = ToolGroup( name='math', description='Mathematical operations', tools=[ Tool(name='add', description='Add numbers', inputSchema={...}), Tool(name='subtract', description='Subtract numbers', inputSchema={...}), ] ) server = Server('my-service') server.enable_discovery_with_groups([math_group]) ``` ## Testing - 5 new discovery-specific tests: All passing - 41/44 total tests passing (3 pre-existing unrelated failures) - Backward compatibility verified with SDK examples - Real-world examples with live weather APIs ## Files Changed New files: - src/mcp/server/discovery/__init__.py - src/mcp/server/discovery/manager.py - src/mcp/server/discovery/tool_group.py - tests/test_discovery.py - tests/test_discovery_integration.py - examples/discovery/ (with server, agent, and README) Modified files: - src/mcp/__init__.py (export ToolGroup) - src/mcp/client/session.py (callback support) - src/mcp/client/session_group.py (tool refresh handling) - src/mcp/server/lowlevel/server.py (discovery integration) - tests/client/test_session_group.py (5 new tests) ## Benefits - Token efficiency: Significant reduction in context token usage for large tool sets - Scalability: Supports servers with many tools - LLM autonomy: LLM decides which tools to load - Clean architecture: Semantic grouping is explicit - Backward compatible: No breaking changes, fully opt-in
1 parent 5489e8b commit 19c404f

File tree

13 files changed

+3940
-14
lines changed

13 files changed

+3940
-14
lines changed

examples/discovery/README.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Example: Progressive Tool Discovery Server
2+
3+
This is a working example of an MCP server that uses **progressive tool discovery** to organize tools into semantic groups and lazy-load them on demand.
4+
5+
All tool groups are defined directly in Python code with **no schema.json files needed**. This is the recommended approach for building production MCP servers with progressive disclosure.
6+
7+
## What This Demonstrates
8+
9+
This server showcases how to:
10+
11+
1. **Organize tools into semantic groups** - Math tools and Weather tools
12+
2. **Enable progressive disclosure** - Only gateway tools are exposed by default (~500 tokens)
13+
3. **Lazy-load tool groups** - When an LLM asks about weather, math tools stay out of context
14+
4. **Save context tokens** - ~77% reduction for servers with many tools
15+
5. **Hybrid mode** - Mix direct tools (e.g., divide) with grouped tools
16+
6. **Real API integration** - Weather tools use live Open-Meteo API and IP geolocation
17+
18+
## Directory Structure
19+
20+
```
21+
discovery/
22+
├── progressive_discovery_server.py # Main server with discovery enabled (recommended)
23+
├── ai_agent.py # Claude-powered agent demonstrating progressive discovery
24+
└── README.md # This file
25+
```
26+
27+
## Tool Groups
28+
29+
### Math Tools Group
30+
31+
Provides basic mathematical operations:
32+
- **add** - Add two numbers
33+
- **subtract** - Subtract two numbers
34+
- **multiply** - Multiply two numbers
35+
36+
The **divide** tool is exposed as a direct tool (always visible, not in a group) to demonstrate **hybrid mode**.
37+
38+
### Weather Tools Group
39+
40+
Provides weather and location services using **real APIs**:
41+
- **get_user_location** - Auto-detect user's location using IP geolocation (ipapi.co)
42+
- **geocode_address** - Convert address/city names to coordinates (Open-Meteo Geocoding API)
43+
- **get_forecast** - Get real weather forecast for any coordinates (Open-Meteo Weather API)
44+
45+
## How Progressive Tool Discovery Works
46+
47+
### Traditional Approach (All Tools Upfront)
48+
```
49+
Client: listTools()
50+
Server: [tool1, tool2, tool3, ..., tool100]
51+
All tool definitions in context (~4,000+ tokens)
52+
LLM: Must consider all tools for every decision
53+
Result: Context bloat, inefficient token usage
54+
```
55+
56+
### Progressive Discovery Approach
57+
```
58+
Step 1: Client calls listTools()
59+
Server: [gateway_tool_1, gateway_tool_2, gateway_tool_3]
60+
Only group summaries (~300-500 tokens)
61+
62+
Step 2: LLM reads descriptions and decides which group to load
63+
Step 3: LLM calls gateway tool
64+
65+
Step 4: Server returns actual tools from that group
66+
(~200-400 tokens added, domain-specific)
67+
68+
Step 5: LLM uses the actual tools
69+
Other groups remain unloaded (tokens saved!)
70+
```
71+
72+
### Key Benefit
73+
74+
**Only relevant tools are in context at any time.** When you ask weather questions, math tools stay hidden. This achieves ~77% token savings for large tool sets.
75+
76+
## Running the Server
77+
78+
### Prerequisites
79+
- Python 3.10+
80+
- uv package manager
81+
82+
### Start the Server
83+
84+
```bash
85+
cd examples/discovery
86+
uv run progressive_discovery_server.py
87+
```
88+
89+
The server will start listening on stdio for MCP protocol messages.
90+
91+
## Core Architecture
92+
93+
### Three Main Components
94+
95+
#### 1. Tool Groups
96+
Semantic collections of related tools:
97+
- Organized by function (math, weather, payments, etc.)
98+
- Defined in Python with all tools in one place
99+
- Can contain nested sub-groups
100+
101+
#### 2. Gateway Tools
102+
Auto-generated entry points for each group:
103+
- No input parameters (just presence indicates what's available)
104+
- LLM reads descriptions to understand what tools are in each group
105+
- Calling a gateway tool loads that group's tools into the client's context
106+
107+
#### 3. Server Integration
108+
The MCP Server handles discovery automatically:
109+
- When `enable_discovery_with_groups()` is called, discovery is enabled
110+
- `listTools()` returns only gateway tools initially
111+
- Gateway tool calls trigger loading of actual tools
112+
- `is_discovery_enabled` property tracks whether discovery is active
113+
114+
### Sample Implementation
115+
116+
```python
117+
from mcp.server import Server
118+
from mcp import ToolGroup, Tool
119+
120+
# Define tool groups programmatically
121+
math_group = ToolGroup(
122+
name="math",
123+
description="Mathematical operations",
124+
tools=[
125+
Tool(name="add", description="Add numbers", inputSchema={...}),
126+
Tool(name="subtract", description="Subtract numbers", inputSchema={...}),
127+
]
128+
)
129+
130+
# Enable discovery
131+
server = Server("my-service")
132+
server.enable_discovery_with_groups([math_group])
133+
134+
# listTools() now returns only gateway tools
135+
# Actual tools load when gateway is called
136+
```
137+
138+
### First `listTools()` Call Example
139+
140+
Server returns **only gateway tools**:
141+
```json
142+
[
143+
{
144+
"name": "get_math_tools",
145+
"description": "Mathematical operations including addition, subtraction, multiplication, and division",
146+
"inputSchema": {"type": "object", "properties": {}, "required": []}
147+
},
148+
{
149+
"name": "get_weather_tools",
150+
"description": "Weather information tools including forecasts and alerts",
151+
"inputSchema": {"type": "object", "properties": {}, "required": []}
152+
}
153+
]
154+
```
155+
156+
LLM reads descriptions and understands what each group provides.
157+
158+
## Client-Side Experience
159+
160+
When a client connects to a progressive discovery server:
161+
162+
1. **Initial state**: Client gets only gateway tools (~300-500 tokens)
163+
2. **User request**: LLM decides which group is relevant based on descriptions
164+
3. **Gateway call**: LLM calls the gateway tool with no parameters
165+
4. **Tool loading**: Server automatically loads that group's tools
166+
5. **Tool refresh**: Client receives the new tools and updates its context
167+
6. **Tool usage**: LLM uses actual tools from the loaded group
168+
7. **Isolation**: Other groups remain hidden from context
169+
170+
## Is Discovery Enabled?
171+
172+
The Server class provides a property to check discovery status:
173+
174+
```python
175+
server = Server("my-service")
176+
print(server.is_discovery_enabled) # False by default
177+
178+
# Enable discovery
179+
server.enable_discovery_with_groups([group1, group2])
180+
print(server.is_discovery_enabled) # True when enabled
181+
```
182+
183+
## Hybrid Mode (Optional)
184+
185+
You can mix approaches:
186+
- **Gateway tools**: Domain-specific tools loaded on demand
187+
- **Direct tools**: High-frequency operations always visible
188+
189+
Example:
190+
- `divide` tool visible everywhere (direct tool)
191+
- `add`, `subtract`, `multiply` in math group (gateway tool)
192+
193+
## Extending the System
194+
195+
To add more tool groups:
196+
197+
1. Define a new `ToolGroup` with related tools
198+
2. Add it to `enable_discovery_with_groups()`
199+
3. The server automatically creates gateway tools
200+
4. No additional handler code needed
201+
202+
## Benefits Demonstrated
203+
204+
- **Token Efficiency** - Only relevant tools in context
205+
- **Scalability** - Easy to add many tool groups
206+
- **LLM Autonomy** - LLM decides which tools to load
207+
- **Clean Architecture** - Semantic grouping is explicit
208+
- **Backward Compatible** - No changes to existing MCP protocol
209+
210+
## Further Reading
211+
212+
- [CLAUDE.md](../../.claude/CLAUDE.md) - Full specification
213+
- [PHASE_1_IMPLEMENTATION.md](../../.claude/PHASE_1_IMPLEMENTATION.md) - Core system
214+
- [PHASE_2_IMPLEMENTATION.md](../../.claude/PHASE_2_IMPLEMENTATION.md) - Server integration
215+
216+
## Key Takeaways
217+
218+
- **Progressive discovery is optional** - `is_discovery_enabled` controls whether it's active
219+
- **Backward compatible** - Existing MCP servers work unchanged
220+
- **Tool groups are flexible** - Define any semantic grouping that makes sense for your domain
221+
- **Client handling is automatic** - Refresh happens transparently via notifications
222+
- **Hybrid mode possible** - Mix direct and grouped tools as needed

0 commit comments

Comments
 (0)