#!/usr/bin/env python3
"""
Demonstration of MCP Resource Change Notifications.
This script demonstrates how the Vultr MCP server now sends resource change
notifications to clients when tools modify resources.
Usage:
python examples/resource_notification_demo.py
"""
import asyncio
from typing import Any, Dict, List
from unittest.mock import AsyncMock, Mock
# Simulate the MCP notification flow
class MockMCPClient:
"""Mock MCP client that subscribes to resource changes."""
def __init__(self, name: str):
self.name = name
self.subscribed_resources: List[str] = []
self.received_notifications: List[Dict[str, Any]] = []
def subscribe_to_resource(self, resource_uri: str):
"""Subscribe to resource change notifications."""
self.subscribed_resources.append(resource_uri)
print(f"π‘ {self.name} subscribed to: {resource_uri}")
def receive_notification(self, notification_type: str, resource_uri: str):
"""Receive a resource change notification."""
self.received_notifications.append({
"type": notification_type,
"resource": resource_uri,
"timestamp": asyncio.get_event_loop().time()
})
print(f"π {self.name} received notification: {notification_type} for {resource_uri}")
class MockFastMCPContext:
"""Mock FastMCP Context that sends notifications to subscribed clients."""
def __init__(self, clients: List[MockMCPClient]):
self.clients = clients
self.notification_count = 0
async def send_resource_list_changed(self):
"""Send resource list changed notification to all subscribed clients."""
self.notification_count += 1
# In a real FastMCP setup, this would send MCP protocol messages
# Here we simulate the notification delivery
for client in self.clients:
# Notify all subscribed clients about resource changes
for resource_uri in client.subscribed_resources:
client.receive_notification("resources/list_changed", resource_uri)
print(f"β‘ FastMCP sent resource_list_changed notification (#{self.notification_count})")
class MockVultrClient:
"""Mock Vultr API client for demonstration."""
def __init__(self):
self.domains: List[Dict[str, Any]] = []
self.records: Dict[str, List[Dict[str, Any]]] = {}
async def create_domain(self, domain: str, ip: str, dns_sec: str = "disabled"):
"""Mock domain creation."""
domain_data = {
"domain": domain,
"ip": ip,
"dns_sec": dns_sec,
"date_created": "2024-01-01",
}
self.domains.append(domain_data)
self.records[domain] = []
print(f"π Created domain: {domain} -> {ip}")
return domain_data
async def create_record(self, domain: str, record_type: str, name: str, data: str, ttl: int = 300, priority: int = None):
"""Mock DNS record creation."""
record_data = {
"id": f"record-{len(self.records.get(domain, []))}",
"type": record_type,
"name": name,
"data": data,
"ttl": ttl,
"priority": priority
}
if domain not in self.records:
self.records[domain] = []
self.records[domain].append(record_data)
print(f"π Created {record_type} record: {name}.{domain} -> {data}")
return record_data
async def delete_domain(self, domain: str):
"""Mock domain deletion."""
self.domains = [d for d in self.domains if d["domain"] != domain]
if domain in self.records:
del self.records[domain]
print(f"ποΈ Deleted domain: {domain}")
# Import our notification manager
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from src.mcp_vultr.notification_manager import NotificationManager
async def demonstrate_dns_operations_with_notifications():
"""Demonstrate DNS operations with resource change notifications."""
print("π Starting Resource Change Notification Demonstration")
print("=" * 60)
# Set up mock clients
claude_desktop = MockMCPClient("Claude Desktop")
api_client = MockMCPClient("API Client")
# Clients subscribe to different resources
claude_desktop.subscribe_to_resource("domains://list")
claude_desktop.subscribe_to_resource("domains://example.com/records")
api_client.subscribe_to_resource("domains://list")
# Set up mock context and Vultr client
ctx = MockFastMCPContext([claude_desktop, api_client])
vultr_client = MockVultrClient()
print("\\nπ§ Mock DNS Tools with Notifications")
print("-" * 40)
# Mock DNS tools that use our notification system
async def create_domain_with_notifications(domain: str, ip: str, dns_sec: str = "disabled"):
"""Mock create_domain tool with notifications."""
result = await vultr_client.create_domain(domain, ip, dns_sec)
await NotificationManager.notify_dns_changes(
ctx=ctx, operation="create_domain", domain=domain, debug_enabled=True
)
return result
async def create_record_with_notifications(domain: str, record_type: str, name: str, data: str):
"""Mock create_record tool with notifications."""
result = await vultr_client.create_record(domain, record_type, name, data)
await NotificationManager.notify_dns_changes(
ctx=ctx, operation="create_record", domain=domain, debug_enabled=True
)
return result
async def delete_domain_with_notifications(domain: str):
"""Mock delete_domain tool with notifications."""
await vultr_client.delete_domain(domain)
await NotificationManager.notify_dns_changes(
ctx=ctx, operation="delete_domain", domain=domain, debug_enabled=True
)
return {"status": "success", "message": f"Domain {domain} deleted"}
print("\\nπ Executing DNS Operations")
print("-" * 40)
# Simulate user operations
print("\\n1οΈβ£ User creates a domain:")
await create_domain_with_notifications("example.com", "1.2.3.4")
print("\\n2οΈβ£ User adds DNS records:")
await create_record_with_notifications("example.com", "A", "www", "1.2.3.4")
await create_record_with_notifications("example.com", "MX", "@", "mail.example.com")
print("\\n3οΈβ£ User creates another domain:")
await create_domain_with_notifications("test.org", "5.6.7.8")
print("\\n4οΈβ£ User deletes a domain:")
await delete_domain_with_notifications("test.org")
print("\\nπ Notification Summary")
print("-" * 40)
print(f"Total FastMCP notifications sent: {ctx.notification_count}")
print("\\nπ± Claude Desktop received:")
for i, notification in enumerate(claude_desktop.received_notifications, 1):
print(f" {i}. {notification['type']} for {notification['resource']}")
print("\\nπ₯οΈ API Client received:")
for i, notification in enumerate(api_client.received_notifications, 1):
print(f" {i}. {notification['type']} for {notification['resource']}")
print("\\n⨠Benefits of Resource Change Notifications:")
print("-" * 50)
print("β’ Claude Desktop UI updates automatically when domains/records change")
print("β’ API clients stay synchronized with server state")
print("β’ No need for manual refresh or polling")
print("β’ Real-time collaboration between multiple MCP clients")
print("β’ Better user experience with immediate feedback")
print("\\nπ― Implementation Impact:")
print("-" * 50)
print("β’ DNS module now has Context parameters on all modify operations")
print("β’ NotificationManager provides systematic notification patterns")
print("β’ FastMCP handles the MCP protocol details automatically")
print("β’ Pattern can be systematically applied to all 16+ modules")
async def demonstrate_notification_architecture():
"""Show the overall notification architecture."""
print("\\nποΈ MCP Resource Change Notification Architecture")
print("=" * 60)
print("""
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Claude Code β β API Client β β Other Client β
β (MCP) β β (MCP) β β (MCP) β
βββββββββββ¬ββββββββ βββββββββββ¬ββββββββ βββββββββββ¬ββββββββ
β β β
β resources/subscribe β β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββ
β FastMCP Server β
β βββββββββββββββββββββββββββββββββββ β
β β Vultr MCP Tools β β
β β β’ create_domain(ctx: Context) β β
β β β’ create_record(ctx: Context) β β
β β β’ delete_domain(ctx: Context) β β
β β β’ ... β β
β βββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββ β
β β NotificationManager β β
β β β’ notify_dns_changes() β β
β β β’ notify_instance_changes() β β
β β β’ OPERATION_RESOURCE_MAP β β
β βββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββ β
β β ctx.send_resource_list_changedβ β
β βββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββ
β MCP Protocol Notifications β
β notifications/resources/list_changed β
βββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β UI Updates β β Cache Refresh β β State Sync β
β Automatically β β Automatically β β Automatically β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
""")
print("Key Components:")
print("β’ Tools accept Context parameter for notification capability")
print("β’ NotificationManager maps operations to affected resources")
print("β’ FastMCP sends MCP protocol notifications to subscribed clients")
print("β’ Clients receive real-time updates and refresh their views")
def show_implementation_rollout_plan():
"""Show the systematic rollout plan for all modules."""
print("\\nπ Systematic Implementation Rollout Plan")
print("=" * 60)
modules_priority = [
("dns.py", "β
COMPLETED", "High - Most commonly used"),
("instances.py", "β³ Next", "High - Core compute resources"),
("ssh_keys.py", "β³ Next", "High - Used with instances"),
("firewall.py", "β³ Next", "High - Security-related"),
("load_balancer.py", "β³ Next", "Medium - Infrastructure"),
("kubernetes.py", "β³ Next", "Medium - Container orchestration"),
("block_storage.py", "β³ Next", "Medium - Storage resources"),
("container_registry.py", "β³ Next", "Medium - Container images"),
("object_storage.py", "β³ Next", "Medium - Object storage"),
("managed_databases.py", "β³ Next", "Medium - Database services"),
("vpcs.py", "β³ Next", "Medium - Networking"),
("reserved_ips.py", "β³ Next", "Medium - IP management"),
("users.py", "β³ Next", "Low - Admin functions"),
("subaccount.py", "β³ Next", "Low - Account management"),
("snapshots.py", "β³ Next", "Low - Backup operations"),
("backups.py", "β³ Next", "Low - Backup operations"),
]
print("\\nModule Implementation Priority:")
for module, status, priority in modules_priority:
print(f" {status} {module:<25} - {priority}")
print("\\nπ§ Implementation Pattern for Each Module:")
print("""
1. Add imports:
from fastmcp import Context
from .notification_manager import NotificationManager
2. Update tool signatures:
@mcp.tool
async def create_resource(param1: str, ctx: Context, param2: str = "default"):
3. Add notifications after operations:
result = await vultr_client.create_resource(...)
await NotificationManager.notify_resource_change(
ctx=ctx, operation="create_resource", resource_id=result["id"]
)
return result
4. Test with mock context to verify notifications
""")
print("\\nπ Expected Benefits After Full Rollout:")
print("β’ All 16 modules support real-time resource notifications")
print("β’ Claude Desktop provides seamless user experience")
print("β’ API clients stay synchronized automatically")
print("β’ Consistent notification patterns across all resources")
print("β’ Better developer experience with reactive UI updates")
async def main():
"""Main demonstration function."""
await demonstrate_dns_operations_with_notifications()
await demonstrate_notification_architecture()
show_implementation_rollout_plan()
print("\\nπ Demonstration Complete!")
print("\\nπ‘ Next Steps:")
print("1. Systematically apply this pattern to other modules")
print("2. Test with real Claude Desktop client")
print("3. Monitor notification performance and optimize if needed")
print("4. Document the pattern for other contributors")
if __name__ == "__main__":
asyncio.run(main())