local source change events to mcp notifs
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import mcp.types as mcp_types
|
||||
|
||||
from wf_mcp.events import EventBus, make_event
|
||||
from wf_mcp.notifications import (
|
||||
FastMcpContextNotificationSink,
|
||||
RecordingNotificationSink,
|
||||
map_event_to_notifications,
|
||||
)
|
||||
|
||||
|
||||
class FakeFastMcpContext:
|
||||
def __init__(self) -> None:
|
||||
self.sent: list[mcp_types.ServerNotificationType] = []
|
||||
|
||||
async def send_notification(
|
||||
self,
|
||||
notification: mcp_types.ServerNotificationType,
|
||||
) -> None:
|
||||
self.sent.append(notification)
|
||||
|
||||
|
||||
def test_maps_capability_change_events_to_mcp_list_changed_notifications() -> None:
|
||||
tool_event = make_event("tools_changed", connection_id="demo.personal")
|
||||
resource_event = make_event("resources_changed", connection_id="demo.personal")
|
||||
prompt_event = make_event("prompts_changed", connection_id="demo.personal")
|
||||
|
||||
tool_notifications = map_event_to_notifications(tool_event)
|
||||
resource_notifications = map_event_to_notifications(resource_event)
|
||||
prompt_notifications = map_event_to_notifications(prompt_event)
|
||||
|
||||
assert isinstance(tool_notifications[0].root, mcp_types.ToolListChangedNotification)
|
||||
assert tool_notifications[0].root.method == "notifications/tools/list_changed"
|
||||
assert isinstance(
|
||||
resource_notifications[0].root,
|
||||
mcp_types.ResourceListChangedNotification,
|
||||
)
|
||||
assert resource_notifications[0].root.method == "notifications/resources/list_changed"
|
||||
assert isinstance(
|
||||
prompt_notifications[0].root,
|
||||
mcp_types.PromptListChangedNotification,
|
||||
)
|
||||
assert prompt_notifications[0].root.method == "notifications/prompts/list_changed"
|
||||
|
||||
|
||||
def test_ignores_events_that_do_not_have_an_mcp_notification_projection() -> None:
|
||||
event = make_event("workflow_artifact_saved", workflow_name="demo")
|
||||
|
||||
assert map_event_to_notifications(event) == []
|
||||
|
||||
|
||||
def test_recording_notification_sink_projects_events_from_event_bus() -> None:
|
||||
bus = EventBus()
|
||||
sink = RecordingNotificationSink()
|
||||
bus.subscribe(sink)
|
||||
|
||||
bus.publish(make_event("tools_changed", connection_id="demo.personal"))
|
||||
bus.publish(make_event("workflow_deployment_saved", workflow_name="demo"))
|
||||
bus.publish(make_event("prompts_changed", connection_id="demo.personal"))
|
||||
|
||||
notifications = sink.list_notifications()
|
||||
assert len(notifications) == 2
|
||||
assert notifications[0].root.method == "notifications/tools/list_changed"
|
||||
assert notifications[1].root.method == "notifications/prompts/list_changed"
|
||||
|
||||
|
||||
def test_fastmcp_context_notification_sink_sends_projected_notifications() -> None:
|
||||
context = FakeFastMcpContext()
|
||||
sink = FastMcpContextNotificationSink(context)
|
||||
|
||||
async def run() -> None:
|
||||
await sink.send_event(
|
||||
make_event("resources_changed", connection_id="demo.personal")
|
||||
)
|
||||
await sink.send_event(make_event("workflow_artifact_saved", workflow_name="demo"))
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
assert len(context.sent) == 1
|
||||
assert isinstance(context.sent[0], mcp_types.ResourceListChangedNotification)
|
||||
assert context.sent[0].method == "notifications/resources/list_changed"
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import mcp.types as mcp_types
|
||||
import pytest
|
||||
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
@@ -446,3 +447,37 @@ def test_transparent_proxy_admin_reload_remounts_connections() -> None:
|
||||
assert _structured(result) == {"echoed": "reloaded"}
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_transparent_proxy_admin_reload_sends_list_changed_notifications() -> None:
|
||||
tmp_path = local_temp_root() / "transparent_proxy_reload_notification_store"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"store_root": ".wf_mcp_store",
|
||||
"connections": [],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
config = load_broker_config(config_path)
|
||||
notifications: list[mcp_types.ServerNotification] = []
|
||||
|
||||
async def message_handler(message: object) -> None:
|
||||
if isinstance(message, mcp_types.ServerNotification):
|
||||
notifications.append(message)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_transparent_proxy_client(config, config_path=config_path)
|
||||
client._session_kwargs["message_handler"] = message_handler
|
||||
async with client:
|
||||
await client.call_tool("wf.admin.reload_config")
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
methods = [notification.root.method for notification in notifications]
|
||||
assert "notifications/tools/list_changed" in methods
|
||||
assert "notifications/resources/list_changed" in methods
|
||||
assert "notifications/prompts/list_changed" in methods
|
||||
|
||||
Reference in New Issue
Block a user