local source change events to mcp notifs

This commit is contained in:
lda
2026-05-13 18:10:31 +07:00 Verified
parent f3bb63389f
commit 7723d8558e
8 changed files with 267 additions and 32 deletions
+13
View File
@@ -0,0 +1,13 @@
from .mapping import map_event_to_notifications
from .sink import (
FastMcpContextNotificationSink,
NotificationSink,
RecordingNotificationSink,
)
__all__ = [
"FastMcpContextNotificationSink",
"NotificationSink",
"RecordingNotificationSink",
"map_event_to_notifications",
]
+35
View File
@@ -0,0 +1,35 @@
from __future__ import annotations
import mcp.types as mcp_types
from wf_mcp.events import McpEvent
def map_event_to_notifications(event: McpEvent) -> list[mcp_types.ServerNotification]:
"""Project broker-local events into MCP server notifications.
The event bus is intentionally protocol-neutral. This function is the
boundary where local capability changes become official MCP notification
payloads that a FastMCP transport can later send to connected clients.
"""
notification = _list_changed_notification(event)
if notification is None:
return []
return [mcp_types.ServerNotification(notification)]
def _list_changed_notification(
event: McpEvent,
) -> (
mcp_types.ToolListChangedNotification
| mcp_types.ResourceListChangedNotification
| mcp_types.PromptListChangedNotification
| None
):
if event.kind == "tools_changed":
return mcp_types.ToolListChangedNotification()
if event.kind == "resources_changed":
return mcp_types.ResourceListChangedNotification()
if event.kind == "prompts_changed":
return mcp_types.PromptListChangedNotification()
return None
+49
View File
@@ -0,0 +1,49 @@
from __future__ import annotations
from typing import Protocol
import mcp.types as mcp_types
from wf_mcp.events import McpEvent
from .mapping import map_event_to_notifications
class NotificationSink(Protocol):
"""Consumes local events and emits or records MCP notifications."""
def __call__(self, event: McpEvent) -> None: ...
class RecordingNotificationSink:
"""Test sink that records MCP notifications projected from local events."""
def __init__(self) -> None:
self._notifications: list[mcp_types.ServerNotification] = []
def __call__(self, event: McpEvent) -> None:
self._notifications.extend(map_event_to_notifications(event))
def list_notifications(self) -> list[mcp_types.ServerNotification]:
"""Return a defensive copy of projected notifications."""
return list(self._notifications)
class FastMcpNotificationContext(Protocol):
"""Small protocol for the FastMCP context method we need."""
async def send_notification(
self,
notification: mcp_types.ServerNotificationType,
) -> None: ...
class FastMcpContextNotificationSink:
"""Send projected local events through a request-scoped FastMCP context."""
def __init__(self, context: FastMcpNotificationContext) -> None:
self._context = context
async def send_event(self, event: McpEvent) -> None:
for notification in map_event_to_notifications(event):
await self._context.send_notification(notification.root)
+15 -3
View File
@@ -2,9 +2,11 @@ from __future__ import annotations
from typing import Any
from fastmcp import FastMCP
from fastmcp import Context, FastMCP
from ..admin_surface import ProxyAdminRuntime, TransparentAdminHandlers
from ..events import make_event
from ..notifications import FastMcpContextNotificationSink
def create_proxy_admin_server(
@@ -44,8 +46,10 @@ def create_proxy_admin_server(
"Reload the config file and remount enabled upstream MCP connections."
),
)
async def reload_config() -> dict[str, Any]:
return handlers.reload_config()
async def reload_config(ctx: Context) -> dict[str, Any]:
result = handlers.reload_config()
await _send_reload_notifications(ctx)
return result
@admin.tool(
title="List Proxy Tools",
@@ -134,3 +138,11 @@ def create_proxy_admin_server(
return handlers.remove_connection(connection_id)
return admin
async def _send_reload_notifications(ctx: Context) -> None:
"""Notify the current client that reload may have changed visible capabilities."""
sink = FastMcpContextNotificationSink(ctx)
await sink.send_event(make_event("tools_changed"))
await sink.send_event(make_event("resources_changed"))
await sink.send_event(make_event("prompts_changed"))