README-SSE-Servlet.md
11.8 KB
Moqui MCP SSE Servlet
This document describes the new MCP SSE (Server-Sent Events) servlet implementation based on the official Java MCP SDK SSE Servlet approach.
Overview
The McpSseServlet implements the MCP SSE transport specification using the traditional Servlet API, providing:
- Asynchronous message handling using Servlet async support
-
Session management for multiple client connections
-
Two types of endpoints:
- SSE endpoint (
/sse) for server-to-client events - Message endpoint (
/mcp/message) for client-to-server requests
- SSE endpoint (
- Error handling and response formatting
- Graceful shutdown support
- Keep-alive pings to maintain connections
- Connection limits to prevent resource exhaustion
Architecture
Endpoint Structure
/sse - SSE endpoint for server-to-client events
/mcp/message - Message endpoint for client-to-server requests
Connection Flow
-
Client connects to
/sseendpoint - Server establishes SSE connection with unique session ID
-
Client sends JSON-RPC messages to
/mcp/message - Server processes messages and sends responses via SSE
- Keep-alive pings maintain connection health
Configuration
Servlet Configuration Parameters
| Parameter | Default | Description |
|---|---|---|
moqui-name |
- | Moqui webapp name (required) |
sseEndpoint |
/sse |
SSE endpoint path |
messageEndpoint |
/mcp/message |
Message endpoint path |
keepAliveIntervalSeconds |
30 |
Keep-alive ping interval |
maxConnections |
100 |
Maximum concurrent SSE connections |
Web.xml Configuration
<servlet>
<servlet-name>McpSseServlet</servlet-name>
<servlet-class>org.moqui.mcp.McpSseServlet</servlet-class>
<init-param>
<param-name>moqui-name</param-name>
<param-value>moqui-mcp-2</param-value>
</init-param>
<init-param>
<param-name>keepAliveIntervalSeconds</param-name>
<param-value>30</param-value>
</init-param>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>McpSseServlet</servlet-name>
<url-pattern>/sse/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>McpSseServlet</servlet-name>
<url-pattern>/mcp/message/*</url-pattern>
</servlet-mapping>
Usage Examples
Client Connection Flow
1. Establish SSE Connection
curl -N -H "Accept: text/event-stream" \
http://localhost:8080/sse
Response:
event: connect
data: {"type":"connected","sessionId":"uuid-123","timestamp":1234567890,"serverInfo":{"name":"Moqui MCP SSE Server","version":"2.0.0","protocolVersion":"2025-06-18"}}
event: ping
data: {"type":"ping","timestamp":1234567890,"connections":1}
2. Initialize MCP Session
curl -X POST http://localhost:8080/mcp/message \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "init-1",
"method": "initialize",
"params": {
"clientInfo": {
"name": "Test Client",
"version": "1.0.0"
}
}
}'
Response:
{
"jsonrpc": "2.0",
"id": "init-1",
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {"listChanged": true},
"resources": {"subscribe": true, "listChanged": true},
"logging": {},
"notifications": {}
},
"serverInfo": {
"name": "Moqui MCP SSE Server",
"version": "2.0.0"
},
"sessionId": "uuid-123"
}
}
3. List Available Tools
curl -X POST http://localhost:8080/mcp/message \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "tools-1",
"method": "tools/list",
"params": {}
}'
4. Call a Tool
curl -X POST http://localhost:8080/mcp/message \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "call-1",
"method": "tools/call",
"params": {
"name": "EntityFind",
"arguments": {
"entity": "moqui.security.UserAccount",
"fields": ["username", "emailAddress"],
"limit": 10
}
}
}'
JavaScript Client Example
class McpSseClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.sessionId = null;
this.eventSource = null;
this.messageId = 0;
}
async connect() {
// Establish SSE connection
this.eventSource = new EventSource(`${this.baseUrl}/sse`);
this.eventSource.addEventListener('connect', (event) => {
const data = JSON.parse(event.data);
console.log('Connected:', data);
});
this.eventSource.addEventListener('ping', (event) => {
const data = JSON.parse(event.data);
console.log('Ping:', data);
});
this.eventSource.addEventListener('initialized', (event) => {
const data = JSON.parse(event.data);
console.log('Server initialized:', data);
});
// Initialize MCP session
const initResponse = await this.sendMessage('initialize', {
clientInfo: {
name: 'JavaScript Client',
version: '1.0.0'
}
});
this.sessionId = initResponse.sessionId;
return initResponse;
}
async sendMessage(method, params = {}) {
const id = `msg-${++this.messageId}`;
const payload = {
jsonrpc: "2.0",
id: id,
method: method,
params: params
};
const response = await fetch(`${this.baseUrl}/mcp/message`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
return await response.json();
}
async listTools() {
return await this.sendMessage('tools/list');
}
async callTool(name, arguments) {
return await this.sendMessage('tools/call', {
name: name,
arguments: arguments
});
}
disconnect() {
if (this.eventSource) {
this.eventSource.close();
}
}
}
// Usage
const client = new McpSseClient('http://localhost:8080');
client.connect().then(() => {
console.log('MCP client connected');
// List tools
return client.listTools();
}).then(tools => {
console.log('Available tools:', tools);
// Call a tool
return client.callTool('EntityFind', {
entity: 'moqui.security.UserAccount',
limit: 5
});
}).then(result => {
console.log('Tool result:', result);
}).catch(error => {
console.error('MCP client error:', error);
});
Features
Session Management
- Unique session IDs generated for each connection
- Connection tracking with metadata (user agent, timestamps)
- Automatic cleanup on connection close/error
- Connection limits to prevent resource exhaustion
Event Types
| Event Type | Description | Data |
|---|---|---|
connect |
Initial connection established | Session info, server details |
ping |
Keep-alive ping | Timestamp, connection count |
initialized |
Server initialization notification | Session info, client details |
subscribed |
Subscription confirmation | Session, event type |
tool_result |
Tool execution result | Tool name, result data |
resource_update |
Resource change notification | Resource URI, change data |
Error Handling
- JSON-RPC 2.0 compliant error responses
- Connection error recovery with automatic cleanup
- Resource exhaustion protection with connection limits
- Graceful degradation on service failures
Security
- CORS support for cross-origin requests
- Moqui authentication integration with admin fallback
- Session isolation between clients
- Input validation for all requests
Monitoring
Connection Status
Get current server status:
curl http://localhost:8080/mcp/message
Response:
{
"serverInfo": {
"name": "Moqui MCP SSE Server",
"version": "2.0.0",
"protocolVersion": "2025-06-18"
},
"connections": {
"active": 3,
"max": 100
},
"endpoints": {
"sse": "/sse",
"message": "/mcp/message"
}
}
Logging
The servlet provides detailed logging for:
- Connection establishment/cleanup
- Message processing
- Error conditions
- Performance metrics
Log levels:
-
INFO: Connection events, message processing -
WARN: Connection errors, authentication issues -
ERROR: System errors, service failures -
DEBUG: Detailed request/response data
Comparison with Existing Servlet
| Feature | MoquiMcpServlet | McpSseServlet |
|---|---|---|
| Transport | HTTP POST/GET | SSE + HTTP |
| Async Support | Limited | Full async |
| Session Management | Basic | Advanced |
| Real-time Events | No | Yes |
| Connection Limits | No | Yes |
| Keep-alive | No | Yes |
| Standards Compliance | Custom | MCP SSE Spec |
Migration Guide
From MoquiMcpServlet to McpSseServlet
- Update web.xml to use the new servlet
- Update client code to handle SSE connections
-
Modify endpoints from
/mcp/rpcto/mcp/message - Add SSE handling for real-time events
- Update authentication if using custom auth
Client Migration
Old approach:
curl -X POST http://localhost:8080/mcp/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"ping","id":1}'
New approach:
# 1. Establish SSE connection
curl -N -H "Accept: text/event-stream" http://localhost:8080/sse &
# 2. Send messages
curl -X POST http://localhost:8080/mcp/message \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"ping","id":1}'
Troubleshooting
Common Issues
-
SSE Connection Fails
- Check async support is enabled in web.xml
- Verify servlet container supports Servlet 3.0+
- Check firewall/proxy settings
-
Messages Not Received
- Verify session ID is valid
- Check connection is still active
- Review server logs for errors
-
High Memory Usage
- Reduce
maxConnectionsparameter - Check for connection leaks
- Monitor session cleanup
- Reduce
-
Authentication Issues
- Verify Moqui security configuration
- Check admin user credentials
- Review webapp security settings
Debug Mode
Enable debug logging by adding to log4j2.xml:
<Logger name="org.moqui.mcp.McpSseServlet" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
Performance Considerations
Connection Scaling
- Default limit: 100 concurrent connections
- Memory usage: ~1KB per connection
- CPU overhead: Minimal for idle connections
- Network bandwidth: Low (keep-alive pings only)
Optimization Tips
- Adjust keep-alive interval based on network conditions
- Monitor connection counts and adjust limits
- Use connection pooling for high-frequency clients
- Implement backpressure for high-volume scenarios
Future Enhancements
Planned improvements:
- WebSocket support as alternative to SSE
- Message queuing for offline clients
- Load balancing support for multiple instances
- Advanced authentication with token-based auth
- Metrics and monitoring integration
- Message compression for large payloads