Skip to content

Commit 683ba83

Browse files
niechenclaude
andcommitted
chore: resolve merge conflict with main branch
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
2 parents c1d0d28 + b8e57b9 commit 683ba83

File tree

23 files changed

+1702
-1104
lines changed

23 files changed

+1702
-1104
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# [2.2.0](https://github.com/pathintegral-institute/mcpm.sh/compare/v2.1.0...v2.2.0) (2025-07-09)
2+
3+
4+
### Features
5+
6+
* refactor commands to v2 structure and add HTTP server support ([#211](https://github.com/pathintegral-institute/mcpm.sh/issues/211)) ([6ebca95](https://github.com/pathintegral-institute/mcpm.sh/commit/6ebca959e631309f6f61db55e87bf8582f44d648))
7+
18
# [2.1.0](https://github.com/pathintegral-institute/mcpm.sh/compare/v2.0.0...v2.1.0) (2025-07-07)
29

310

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
# FastMCP Progress Notification Investigation
2+
3+
**Date**: July 8, 2025
4+
**Issue**: Progress notifications not working when FastMCP proxy uses HTTP-to-Stdio transport
5+
**Status**: Root cause identified, architectural limitation in FastMCP
6+
7+
## Executive Summary
8+
9+
Progress notifications work correctly when FastMCP proxy communicates with servers via HTTP-to-HTTP transport, but fail when using HTTP-to-Stdio transport. The issue is a context isolation problem in FastMCP's proxy implementation where progress tokens cannot be properly tracked across process boundaries.
10+
11+
## Issue Description
12+
13+
### Observed Behavior
14+
15+
When running a server through MCPM's FastMCP proxy:
16+
17+
**Working Case (HTTP → Proxy → HTTP):**
18+
- Progress notifications flow correctly from server to client
19+
- Progress tokens are preserved throughout the request lifecycle
20+
21+
**Failing Case (HTTP → Proxy → Stdio):**
22+
- Progress notifications are received by proxy but fail to forward to client
23+
- Error: `[CONTEXT] No progress token available, skipping notification`
24+
25+
### Error Logs
26+
27+
```
28+
2025-07-08 20:22:36,003 - timer.main - DEBUG - Sending progress notification: 10000/180000 ms
29+
[07/08/25 20:22:36] INFO FastMCP.fastmcp.server.proxy: received progress notification: progress=10000.0, total=180000.0, message=None
30+
DEBUG fastmcp.server.context: [CONTEXT] Reporting progress: progress=10000.0, total=180000.0, message=None
31+
DEBUG fastmcp.server.context: [CONTEXT] No progress token available, skipping notification
32+
```
33+
34+
## Technical Root Cause Analysis
35+
36+
### MCP Protocol Support
37+
38+
The MCP protocol **correctly supports** progress notifications across all transports:
39+
40+
1. **Progress Token Propagation**: MCP passes progress tokens in `_meta.progressToken` field
41+
2. **Request Metadata**: Found in `/mcp/types.py:43-50`:
42+
```python
43+
class RequestParams(BaseModel):
44+
class Meta(BaseModel):
45+
progressToken: ProgressToken | None = None
46+
"""
47+
If specified, the caller requests out-of-band progress notifications for
48+
this request (as represented by notifications/progress). The value of this
49+
parameter is an opaque token that will be attached to any subsequent
50+
notifications.
51+
"""
52+
```
53+
54+
3. **Session Implementation**: In `/mcp/shared/session.py:443+`, `send_request` properly sets progress tokens:
55+
```python
56+
# Set up progress token if progress callback is provided
57+
if progress_callback is not None:
58+
# Use request_id as progress token
59+
if "_meta" not in request_data["params"]:
60+
request_data["params"]["_meta"] = {}
61+
request_data["params"]["_meta"]["progressToken"] = request_id
62+
# Store the callback for this request
63+
self._progress_callbacks[request_id] = progress_callback
64+
```
65+
66+
### FastMCP Implementation Details
67+
68+
**Progress Handler Location**: `/fastmcp/server/proxy.py:514-539`
69+
```python
70+
@classmethod
71+
async def default_progress_handler(
72+
cls,
73+
progress: float,
74+
total: float | None,
75+
message: str | None,
76+
) -> None:
77+
"""
78+
A handler that forwards the progress notification from the remote server to the proxy's connected clients.
79+
"""
80+
ctx = get_context()
81+
logger.info("received progress notification: progress=%s, total=%s, message=%s", progress, total, message)
82+
await ctx.report_progress(progress, total, message)
83+
```
84+
85+
**Context Report Progress**: `/fastmcp/server/context.py:125-155`
86+
```python
87+
async def report_progress(
88+
self, progress: float, total: float | None = None, message: str | None = None
89+
) -> None:
90+
progress_token = (
91+
self.request_context.meta.progressToken
92+
if self.request_context.meta
93+
else None
94+
)
95+
96+
if progress_token is None:
97+
logger.debug("[CONTEXT] No progress token available, skipping notification")
98+
return
99+
100+
await self.session.send_progress_notification(
101+
progress_token=progress_token,
102+
progress=progress,
103+
total=total,
104+
message=message,
105+
related_request_id=self.request_id,
106+
)
107+
```
108+
109+
### Context Isolation Problem
110+
111+
**HTTP-to-HTTP Flow (✅ Working):**
112+
```
113+
Client Request (HTTP) → Proxy → HTTP Server
114+
├─ Request context with progressToken flows through HTTP session
115+
├─ Progress handler executes in same HTTP context
116+
└─ Progress notifications reference original progressToken
117+
```
118+
119+
**HTTP-to-Stdio Flow (❌ Broken):**
120+
```
121+
Client Request (HTTP) → Proxy → Stdio Subprocess
122+
├─ Request context with progressToken sent to subprocess
123+
├─ Subprocess runs in isolated process with separate context
124+
├─ Progress handler (`default_progress_handler`) runs in subprocess context
125+
├─ `get_context()` returns subprocess context (no original progressToken)
126+
└─ Progress notification dropped due to missing token
127+
```
128+
129+
### Key Technical Components
130+
131+
1. **Context Management**: `/fastmcp/server/dependencies.py:27-33`
132+
```python
133+
def get_context() -> Context:
134+
from fastmcp.server.context import _current_context
135+
context = _current_context.get()
136+
if context is None:
137+
raise RuntimeError("No active context found.")
138+
return context
139+
```
140+
141+
2. **Context Variables**: Uses Python `ContextVar` which is isolated per async task/process
142+
143+
3. **Stdio Transport**: `/fastmcp/client/transports.py:300+` - Creates subprocess with separate memory space
144+
145+
## Debug Implementation Added
146+
147+
### Debug Middleware
148+
149+
Created `MCPMDebugMiddleware` in `/src/mcpm/fastmcp_integration/middleware.py:19-198`:
150+
151+
- **Limitation Documented**: Middleware only intercepts messages flowing FROM clients TO servers
152+
- **Progress notifications flow FROM servers TO clients**, bypassing middleware pipeline
153+
- **Added debug flags** to `mcpm run --debug` and `mcpm profile run --debug`
154+
155+
### Enhanced Logging
156+
157+
Added debug logging in multiple layers:
158+
159+
1. **Proxy Progress Handler**: `/fastmcp/server/proxy.py:524+`
160+
2. **Context Report Progress**: `/fastmcp/server/context.py:134-147`
161+
3. **Debug context inspection** in progress handler
162+
163+
## Architectural Limitation
164+
165+
### Why This Is Hard to Fix
166+
167+
FastMCP's proxy architecture has a fundamental limitation:
168+
169+
1. **Process Isolation**: Stdio servers run in separate processes with isolated memory
170+
2. **Context Variables**: `_current_context` is per-process, not shared
171+
3. **Missing Mapping**: No mechanism to associate stdio server progress with original client requests
172+
173+
### Required Fix
174+
175+
To properly support HTTP-to-Stdio progress notifications, FastMCP would need:
176+
177+
1. **Request Tracking**: Maintain mapping between client requests and stdio subprocess instances
178+
2. **Token Preservation**: Store original progress tokens when spawning stdio requests
179+
3. **Context Injection**: Inject correct progress token when handling progress from stdio servers
180+
181+
Example fix architecture:
182+
```python
183+
class ProxyRequestTracker:
184+
def __init__(self):
185+
self._request_mapping: Dict[subprocess_id, ProgressToken] = {}
186+
187+
def track_request(self, subprocess_id: str, progress_token: ProgressToken):
188+
self._request_mapping[subprocess_id] = progress_token
189+
190+
def get_progress_token(self, subprocess_id: str) -> ProgressToken | None:
191+
return self._request_mapping.get(subprocess_id)
192+
```
193+
194+
## Current Workarounds
195+
196+
### For Users
197+
198+
1. **Use HTTP servers** when progress notifications are needed:
199+
```bash
200+
mcpm run server-name --http --port 8001
201+
```
202+
203+
2. **Debug with HTTP-to-HTTP** setup:
204+
```bash
205+
mcpm run timer --http --debug
206+
```
207+
208+
### For Development
209+
210+
1. **Enable comprehensive debug logging**:
211+
```bash
212+
export PYTHONUNBUFFERED=1
213+
export LOG_LEVEL=DEBUG
214+
mcpm run server-name --debug
215+
```
216+
217+
2. **Use enhanced logging** in FastMCP components (already added)
218+
219+
## Files Modified During Investigation
220+
221+
### MCPM Code Changes
222+
223+
1. **Debug Middleware**: `/src/mcpm/fastmcp_integration/middleware.py`
224+
- Added `MCPMDebugMiddleware` class with comprehensive logging
225+
- Handles all MCP operations: tools, resources, prompts, notifications
226+
- Safe serialization for complex objects
227+
228+
2. **Proxy Factory**: `/src/mcpm/fastmcp_integration/proxy.py`
229+
- Added `debug: bool` parameter to `MCPMProxyFactory`
230+
- Debug middleware added first in middleware chain when enabled
231+
- Updated convenience function `create_mcpm_proxy()`
232+
233+
3. **CLI Commands**:
234+
- `/src/mcpm/commands/run.py`: Added `--debug` flag
235+
- `/src/mcpm/commands/profile/run.py`: Added `--debug` flag
236+
- Debug parameter propagated through all execution paths
237+
238+
### FastMCP Library Changes (for debugging)
239+
240+
1. **Proxy Progress Handler**: `/fastmcp/server/proxy.py:524`
241+
- Fixed logging syntax error (string formatting)
242+
- Added comprehensive debug context inspection
243+
244+
2. **Context Report Progress**: `/fastmcp/server/context.py:134-147`
245+
- Added detailed debug logging for progress reporting
246+
- Logs progress token availability and notification sending
247+
248+
## Testing and Validation
249+
250+
### Confirmed Working Cases
251+
252+
- **HTTP-to-HTTP**: Progress notifications work correctly
253+
- **Local servers**: Progress works when not proxied
254+
- **Debug logging**: All layers now provide detailed debugging information
255+
256+
### Confirmed Failing Cases
257+
258+
- **HTTP-to-Stdio**: Progress notifications fail due to context isolation
259+
- **Any proxied stdio server**: Same issue across all stdio transport types
260+
261+
### Debug Output Examples
262+
263+
**Working HTTP-to-HTTP**:
264+
```
265+
[PROXY DEBUG] TOOL CALL: timer_tool
266+
[CONTEXT] Reporting progress: progress=10000.0, total=180000.0, message=None
267+
[CONTEXT] Sending progress notification with token=12345
268+
```
269+
270+
**Failing HTTP-to-Stdio**:
271+
```
272+
[PROXY DEBUG] TOOL CALL: timer_tool
273+
INFO: received progress notification: progress=10000.0, total=180000.0, message=None
274+
[CONTEXT] Reporting progress: progress=10000.0, total=180000.0, message=None
275+
[CONTEXT] No progress token available, skipping notification
276+
```
277+
278+
## Future Work Recommendations
279+
280+
### FastMCP Core Fix
281+
282+
The proper solution requires changes to FastMCP core:
283+
284+
1. **Enhance ProxyClient**: Maintain request-to-subprocess mapping
285+
2. **Modify default_progress_handler**: Inject correct progress token from mapping
286+
3. **Add request lifecycle tracking**: Clean up mappings when requests complete
287+
288+
### Alternative Approaches
289+
290+
1. **HTTP Wrapper Pattern**: Create HTTP servers that wrap stdio servers
291+
2. **Progress Relay Service**: Separate service to bridge context gaps
292+
3. **Enhanced Middleware**: Middleware that can intercept outgoing notifications
293+
294+
### Testing Strategy
295+
296+
1. **Unit tests** for request tracking components
297+
2. **Integration tests** for HTTP-to-Stdio progress scenarios
298+
3. **Performance testing** for overhead of request tracking
299+
300+
## References
301+
302+
### Key Files Investigated
303+
304+
- `/mcp/types.py`: MCP protocol definitions including progress tokens
305+
- `/mcp/shared/session.py`: Base session with progress token handling
306+
- `/mcp/client/session.py`: Client session implementation
307+
- `/fastmcp/server/proxy.py`: Proxy server and progress handlers
308+
- `/fastmcp/server/context.py`: Request context and progress reporting
309+
- `/fastmcp/server/dependencies.py`: Context management and retrieval
310+
- `/fastmcp/client/transports.py`: Transport implementations including stdio
311+
312+
### FastMCP Version
313+
314+
- **Version**: 2.10.2 (from `pyproject.toml`)
315+
- **Library location**: `.venv/lib/python3.13/site-packages/fastmcp/`
316+
317+
### Related Issues
318+
319+
This issue demonstrates a broader challenge in MCP proxy implementations: maintaining request context across different transport types. Similar issues may exist in other proxy scenarios where context isolation occurs.
320+
321+
---
322+
323+
**Investigation completed by**: Claude Code
324+
**Next steps**: Resume work on implementing FastMCP core fix or alternative workarounds
325+
**Priority**: Medium (affects progress notification functionality, workarounds available)

0 commit comments

Comments
 (0)