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.
The messaging system consists of several components:
- Client-side message handling - MaggClient with MessageHandler support
- Server-side message coordination - ProxyFastMCP with message routing
- Message forwarding - Transparent proxy of notifications from backend servers
- Message aggregation - Coordination of notifications from multiple servers
from magg import MaggClient, MaggMessageHandler
import mcp.types
# Create a custom message handler
def on_tool_changed(notification: mcp.types.ToolListChangedNotification):
print(f"Tools changed!")
def on_progress(notification: mcp.types.ProgressNotification):
print(f"Progress: {notification.progress}/{notification.total}")
# Create handler with callbacks
handler = MaggMessageHandler(
on_tool_list_changed=on_tool_changed,
on_progress=on_progress
)
# Create client with message handling
client = MaggClient(
"http://localhost:8000",
message_handler=handler
)
async with client:
# All notifications from backend servers will be forwarded to your handler
tools = await client.list_tools()from magg.messaging import MaggMessageHandler
import mcp.types
class MyMessageHandler(MaggMessageHandler):
def __init__(self):
super().__init__()
self.tool_cache = []
async def on_tool_list_changed(
self,
notification: mcp.types.ToolListChangedNotification
):
print("Tool list changed - clearing cache")
self.tool_cache.clear()
async def on_resource_list_changed(
self,
notification: mcp.types.ResourceListChangedNotification
):
print("Resource list changed")
async def on_progress(
self,
notification: mcp.types.ProgressNotification
):
print(f"Progress: {notification.progress}")
# Use custom handler
handler = MyMessageHandler()
client = MaggClient("http://localhost:8000", message_handler=handler)The messaging system supports all standard MCP notifications:
ToolListChangedNotification- Tool inventory changesResourceListChangedNotification- Resource inventory changesPromptListChangedNotification- Prompt inventory changesResourceUpdatedNotification- Individual resource updatesProgressNotification- Progress updates from long-running operationsLoggingMessageNotification- Log messages from serversCancelledNotification- Request cancellation notifications
Magg's server automatically sets up message forwarding when mounting backend servers:
# In ServerManager.mount_server():
# 1. Create BackendMessageHandler for each server
# 2. Connect Client with message_handler
# 3. Mount server with ProxyFastMCP.mount_backend_server()
# 4. All notifications automatically forwarded to clientsThe ServerMessageCoordinator handles:
- Deduplication - Prevents duplicate notifications
- Aggregation - Combines notifications from multiple servers
- Routing - Sends notifications to appropriate client handlers
- State tracking - Maintains notification state for debugging
# Access message coordinator for debugging
coordinator = magg_server.mcp.message_coordinator
state = await coordinator.get_notification_state()
print(f"Servers with tool changes: {state.get('tool_changes', set())}")MaggClient automatically forwards the message handler to the underlying FastMCP Client:
# In MaggClient.__init__():
super().__init__(
transport,
message_handler=message_handler, # Passed through to FastMCP
# ... other args
)Each backend server gets its own message handler:
# In ServerManager.mount_server():
message_handler = BackendMessageHandler(
server_id=server.name,
coordinator=self.mcp.message_coordinator
)
client = Client(transport, message_handler=message_handler)- Backend server sends notification (e.g., tool list changed)
- BackendMessageHandler receives notification
- ServerMessageCoordinator processes and routes notification
- MessageRouter forwards to registered client handlers
- Client MessageHandler receives and processes notification
from magg.messaging import MessageRouter
router = MessageRouter()
# Register handler for specific server
await router.register_handler(my_handler, server_id="weather-server")
# Register global handler for all servers
await router.register_handler(global_handler, server_id=None)From server-side tools using Context:
from fastmcp.server.context import Context
@server.tool
async def my_tool(ctx: Context) -> str:
# Do some work that changes available tools
# ...
# Manually trigger tool list change notification
await ctx.send_tool_list_changed()
return "Tools updated"Message handlers should be robust:
class RobustMessageHandler(MaggMessageHandler):
async def on_tool_list_changed(self, notification):
try:
# Handle notification
await self.process_tool_change(notification)
except Exception as e:
logger.error(f"Error handling tool change: {e}")
# Don't re-raise - prevents breaking message flow- Async handlers - All message handlers are async to prevent blocking
- Concurrent processing - Multiple handlers called concurrently with
asyncio.gather - Error isolation - Handler exceptions don't affect other handlers or message flow
- Minimal overhead - Message routing adds minimal latency to notifications
Existing Magg clients continue to work unchanged. To add message handling:
# Before
client = MaggClient("http://localhost:8000")
# After
handler = MaggMessageHandler(on_tool_list_changed=my_callback)
client = MaggClient("http://localhost:8000", message_handler=handler)No server-side changes are required - message forwarding is automatically enabled for all mounted servers.