You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix MCP HTTP transport for PHP-FPM/Apache: Auto-register clients + OAuth compliance
## Critical Transport Architecture Fix
This commit resolves two fundamental issues that prevented MCP HTTP transport
from working correctly with traditional PHP servers (PHP-FPM, Apache) while
maintaining compatibility with long-running processes (ReactPHP).
### 🔧 Problem 1: Client Registration Lost Between Requests
**Root Cause:**
Each HTTP request in PHP-FPM/Apache runs in a separate PHP process, causing:
- SSE stream (GET /sse) creates LaravelHttpTransport instance php-mcp#1
- POST requests (/message) create LaravelHttpTransport instance php-mcp#2
- Client registered in instance php-mcp#1 is not available in instance php-mcp#2
- Result: "Client not actively managed by this transport" errors
**Solution:**
Auto-registration in `LaravelHttpTransport::sendToClientAsync()`:
- Automatically emit `client_connected` event for unknown clients
- Ensures clients are active before processing messages
- Maintains backward compatibility with ReactPHP (no-op for already active clients)
### 🔧 Problem 2: OAuth Standard Compliance
**Root Cause:**
Original package used non-standard parameter naming that violates OAuth spec:
- Used: `clientId` (camelCase)
- OAuth Standard: `client_id` (snake_case)
- Real MCP clients (Claude Desktop) send `client_id` parameter
**Solution:**
- Updated SSE endpoint to accept `client_id` query parameter
- Consistent `client_id` usage in all log messages
- Maintains fallback to session ID if no `client_id` provided
### 🔧 Problem 3: Service Provider Architecture
**Root Cause:**
Multiple controller instantiations called `server->listen()` repeatedly, causing:
- Event handlers registered multiple times
- Transport state inconsistencies
- Memory leaks and performance issues
**Solution:**
- Moved `server->listen()` to McpServiceProvider singleton registration
- Removed redundant `server->listen()` calls from controller constructor
- Proper logger injection in service provider
## 🧪 Tested Scenarios
✅ **PHP-FPM/Apache (Separate Processes)**
- Each request gets clean transport instance
- Auto-registration ensures client connectivity
- No shared state issues
✅ **ReactPHP (Long-Running Process)**
- Existing clients remain active
- Auto-registration is no-op for active clients
- No performance impact
✅ **Claude Desktop Integration**
- Recognizes all MCP tools correctly
- Proper `client_id` parameter handling
- SSE stream maintains connection
## 🔍 Technical Details
**Modified Files:**
- `src/Transports/LaravelHttpTransport.php`: Auto-registration logic
- `src/Http/Controllers/McpController.php`: OAuth compliance + service provider cleanup
- `src/McpServiceProvider.php`: Centralized transport initialization
**Key Changes:**
1. Auto-registration in `sendToClientAsync()` when client not found
2. SSE endpoint accepts `client_id` query parameter
3. Service provider handles transport listening lifecycle
4. Consistent `client_id` logging throughout
## 🚀 Impact
**Before:** MCP HTTP transport only worked with ReactPHP
**After:** Works with all PHP server configurations
**Deployment:** Zero breaking changes - existing code continues to work
**Performance:** Minimal overhead (~1 isset() check per message)
This fix enables MCP HTTP transport to work reliably in production PHP
environments while maintaining the existing API and functionality.
Fixes issues with:
- Traditional PHP-FPM deployments
- Apache mod_php configurations
- Docker containers with nginx+php-fpm
- Shared hosting environments
- Any setup where each HTTP request runs in separate PHP process
Co-authored-by: Claude (Anthropic) <[email protected]>
0 commit comments