|
| 1 | +# MCP Messaging and Notifications |
| 2 | + |
| 3 | +Magg provides comprehensive support for MCP messaging and notifications, enabling real-time communication between clients and backend servers. This feature leverages FastMCP's messaging capabilities to provide transparent message forwarding across multiple backend servers. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The messaging system consists of several components: |
| 8 | + |
| 9 | +1. **Client-side message handling** - MaggClient with MessageHandler support |
| 10 | +2. **Server-side message coordination** - ProxyFastMCP with message routing |
| 11 | +3. **Message forwarding** - Transparent proxy of notifications from backend servers |
| 12 | +4. **Message aggregation** - Coordination of notifications from multiple servers |
| 13 | + |
| 14 | +## Client-Side Usage |
| 15 | + |
| 16 | +### Basic Message Handling |
| 17 | + |
| 18 | +```python |
| 19 | +from magg import MaggClient, MaggMessageHandler |
| 20 | +import mcp.types |
| 21 | + |
| 22 | +# Create a custom message handler |
| 23 | +def on_tool_changed(notification: mcp.types.ToolListChangedNotification): |
| 24 | + print(f"Tools changed!") |
| 25 | + |
| 26 | +def on_progress(notification: mcp.types.ProgressNotification): |
| 27 | + print(f"Progress: {notification.progress}/{notification.total}") |
| 28 | + |
| 29 | +# Create handler with callbacks |
| 30 | +handler = MaggMessageHandler( |
| 31 | + on_tool_list_changed=on_tool_changed, |
| 32 | + on_progress=on_progress |
| 33 | +) |
| 34 | + |
| 35 | +# Create client with message handling |
| 36 | +client = MaggClient( |
| 37 | + "http://localhost:8000", |
| 38 | + message_handler=handler |
| 39 | +) |
| 40 | + |
| 41 | +async with client: |
| 42 | + # All notifications from backend servers will be forwarded to your handler |
| 43 | + tools = await client.list_tools() |
| 44 | +``` |
| 45 | + |
| 46 | +### Custom Message Handler Class |
| 47 | + |
| 48 | +```python |
| 49 | +from magg.messaging import MaggMessageHandler |
| 50 | +import mcp.types |
| 51 | + |
| 52 | +class MyMessageHandler(MaggMessageHandler): |
| 53 | + def __init__(self): |
| 54 | + super().__init__() |
| 55 | + self.tool_cache = [] |
| 56 | + |
| 57 | + async def on_tool_list_changed( |
| 58 | + self, |
| 59 | + notification: mcp.types.ToolListChangedNotification |
| 60 | + ): |
| 61 | + print("Tool list changed - clearing cache") |
| 62 | + self.tool_cache.clear() |
| 63 | + |
| 64 | + async def on_resource_list_changed( |
| 65 | + self, |
| 66 | + notification: mcp.types.ResourceListChangedNotification |
| 67 | + ): |
| 68 | + print("Resource list changed") |
| 69 | + |
| 70 | + async def on_progress( |
| 71 | + self, |
| 72 | + notification: mcp.types.ProgressNotification |
| 73 | + ): |
| 74 | + print(f"Progress: {notification.progress}") |
| 75 | + |
| 76 | +# Use custom handler |
| 77 | +handler = MyMessageHandler() |
| 78 | +client = MaggClient("http://localhost:8000", message_handler=handler) |
| 79 | +``` |
| 80 | + |
| 81 | +## Supported Notification Types |
| 82 | + |
| 83 | +The messaging system supports all standard MCP notifications: |
| 84 | + |
| 85 | +- **`ToolListChangedNotification`** - Tool inventory changes |
| 86 | +- **`ResourceListChangedNotification`** - Resource inventory changes |
| 87 | +- **`PromptListChangedNotification`** - Prompt inventory changes |
| 88 | +- **`ResourceUpdatedNotification`** - Individual resource updates |
| 89 | +- **`ProgressNotification`** - Progress updates from long-running operations |
| 90 | +- **`LoggingMessageNotification`** - Log messages from servers |
| 91 | +- **`CancelledNotification`** - Request cancellation notifications |
| 92 | + |
| 93 | +## Server-Side Architecture |
| 94 | + |
| 95 | +### Message Routing |
| 96 | + |
| 97 | +Magg's server automatically sets up message forwarding when mounting backend servers: |
| 98 | + |
| 99 | +```python |
| 100 | +# In ServerManager.mount_server(): |
| 101 | +# 1. Create BackendMessageHandler for each server |
| 102 | +# 2. Connect Client with message_handler |
| 103 | +# 3. Mount server with ProxyFastMCP.mount_backend_server() |
| 104 | +# 4. All notifications automatically forwarded to clients |
| 105 | +``` |
| 106 | + |
| 107 | +### Message Coordination |
| 108 | + |
| 109 | +The `ServerMessageCoordinator` handles: |
| 110 | + |
| 111 | +- **Deduplication** - Prevents duplicate notifications |
| 112 | +- **Aggregation** - Combines notifications from multiple servers |
| 113 | +- **Routing** - Sends notifications to appropriate client handlers |
| 114 | +- **State tracking** - Maintains notification state for debugging |
| 115 | + |
| 116 | +### Debugging Message Flow |
| 117 | + |
| 118 | +```python |
| 119 | +# Access message coordinator for debugging |
| 120 | +coordinator = magg_server.mcp.message_coordinator |
| 121 | +state = await coordinator.get_notification_state() |
| 122 | +print(f"Servers with tool changes: {state.get('tool_changes', set())}") |
| 123 | +``` |
| 124 | + |
| 125 | +## Implementation Details |
| 126 | + |
| 127 | +### Client Message Handler Registration |
| 128 | + |
| 129 | +MaggClient automatically forwards the message handler to the underlying FastMCP Client: |
| 130 | + |
| 131 | +```python |
| 132 | +# In MaggClient.__init__(): |
| 133 | +super().__init__( |
| 134 | + transport, |
| 135 | + message_handler=message_handler, # Passed through to FastMCP |
| 136 | + # ... other args |
| 137 | +) |
| 138 | +``` |
| 139 | + |
| 140 | +### Backend Server Message Forwarding |
| 141 | + |
| 142 | +Each backend server gets its own message handler: |
| 143 | + |
| 144 | +```python |
| 145 | +# In ServerManager.mount_server(): |
| 146 | +message_handler = BackendMessageHandler( |
| 147 | + server_id=server.name, |
| 148 | + coordinator=self.mcp.message_coordinator |
| 149 | +) |
| 150 | +client = Client(transport, message_handler=message_handler) |
| 151 | +``` |
| 152 | + |
| 153 | +### Message Flow |
| 154 | + |
| 155 | +1. **Backend server** sends notification (e.g., tool list changed) |
| 156 | +2. **BackendMessageHandler** receives notification |
| 157 | +3. **ServerMessageCoordinator** processes and routes notification |
| 158 | +4. **MessageRouter** forwards to registered client handlers |
| 159 | +5. **Client MessageHandler** receives and processes notification |
| 160 | + |
| 161 | +## Advanced Features |
| 162 | + |
| 163 | +### Server-Specific Handlers |
| 164 | + |
| 165 | +```python |
| 166 | +from magg.messaging import MessageRouter |
| 167 | + |
| 168 | +router = MessageRouter() |
| 169 | + |
| 170 | +# Register handler for specific server |
| 171 | +await router.register_handler(my_handler, server_id="weather-server") |
| 172 | + |
| 173 | +# Register global handler for all servers |
| 174 | +await router.register_handler(global_handler, server_id=None) |
| 175 | +``` |
| 176 | + |
| 177 | +### Manual Notification Sending |
| 178 | + |
| 179 | +From server-side tools using Context: |
| 180 | + |
| 181 | +```python |
| 182 | +from fastmcp.server.context import Context |
| 183 | + |
| 184 | +@server.tool |
| 185 | +async def my_tool(ctx: Context) -> str: |
| 186 | + # Do some work that changes available tools |
| 187 | + # ... |
| 188 | + |
| 189 | + # Manually trigger tool list change notification |
| 190 | + await ctx.send_tool_list_changed() |
| 191 | + |
| 192 | + return "Tools updated" |
| 193 | +``` |
| 194 | + |
| 195 | +## Error Handling |
| 196 | + |
| 197 | +Message handlers should be robust: |
| 198 | + |
| 199 | +```python |
| 200 | +class RobustMessageHandler(MaggMessageHandler): |
| 201 | + async def on_tool_list_changed(self, notification): |
| 202 | + try: |
| 203 | + # Handle notification |
| 204 | + await self.process_tool_change(notification) |
| 205 | + except Exception as e: |
| 206 | + logger.error(f"Error handling tool change: {e}") |
| 207 | + # Don't re-raise - prevents breaking message flow |
| 208 | +``` |
| 209 | + |
| 210 | +## Performance Considerations |
| 211 | + |
| 212 | +- **Async handlers** - All message handlers are async to prevent blocking |
| 213 | +- **Concurrent processing** - Multiple handlers called concurrently with `asyncio.gather` |
| 214 | +- **Error isolation** - Handler exceptions don't affect other handlers or message flow |
| 215 | +- **Minimal overhead** - Message routing adds minimal latency to notifications |
| 216 | + |
| 217 | +## Migration from Previous Versions |
| 218 | + |
| 219 | +Existing Magg clients continue to work unchanged. To add message handling: |
| 220 | + |
| 221 | +```python |
| 222 | +# Before |
| 223 | +client = MaggClient("http://localhost:8000") |
| 224 | + |
| 225 | +# After |
| 226 | +handler = MaggMessageHandler(on_tool_list_changed=my_callback) |
| 227 | +client = MaggClient("http://localhost:8000", message_handler=handler) |
| 228 | +``` |
| 229 | + |
| 230 | +No server-side changes are required - message forwarding is automatically enabled for all mounted servers. |
0 commit comments