forked from modelcontextprotocol/python-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsubscriptions.py
More file actions
111 lines (83 loc) · 3.95 KB
/
Copy pathsubscriptions.py
File metadata and controls
111 lines (83 loc) · 3.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""Typed event vocabulary for `subscriptions/listen` (2026-07-28, SEP-2575), shared by server and client.
Every event is a level trigger ("this changed, refetch if you care"), so both sides bound buffers by dedupe.
"""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any
from mcp_types import (
NotificationParams,
PromptListChangedNotification,
ResourceListChangedNotification,
ResourceUpdatedNotification,
ResourceUpdatedNotificationParams,
ServerNotification,
SubscriptionFilter,
ToolListChangedNotification,
)
__all__ = [
"LISTEN_STREAM_METHODS",
"SUBSCRIPTION_ID_META_KEY",
"PromptsListChanged",
"ResourceUpdated",
"ResourcesListChanged",
"ServerEvent",
"ToolsListChanged",
"event_from_wire",
"event_matches",
"event_to_notification",
]
SUBSCRIPTION_ID_META_KEY = "io.modelcontextprotocol/subscriptionId"
"""The `_meta` key on every listen-stream frame; the value is the `subscriptions/listen` request's JSON-RPC id."""
@dataclass(frozen=True)
class ToolsListChanged:
"""The server's tool list changed."""
@dataclass(frozen=True)
class PromptsListChanged:
"""The server's prompt list changed."""
@dataclass(frozen=True)
class ResourcesListChanged:
"""The server's resource list changed."""
@dataclass(frozen=True)
class ResourceUpdated:
"""The resource at `uri` changed and may need to be read again."""
uri: str
ServerEvent = ToolsListChanged | PromptsListChanged | ResourcesListChanged | ResourceUpdated
"""An event a server publishes for delivery to listen subscribers."""
def event_to_notification(event: ServerEvent, meta: dict[str, Any]) -> ServerNotification:
"""Build the stamped wire notification for `event` (the server's direction)."""
if isinstance(event, ToolsListChanged):
return ToolListChangedNotification(params=NotificationParams(_meta=meta))
if isinstance(event, PromptsListChanged):
return PromptListChangedNotification(params=NotificationParams(_meta=meta))
if isinstance(event, ResourcesListChanged):
return ResourceListChangedNotification(params=NotificationParams(_meta=meta))
return ResourceUpdatedNotification(params=ResourceUpdatedNotificationParams(uri=event.uri, _meta=meta))
_LIST_CHANGED_EVENTS: dict[str, ServerEvent] = {
"notifications/tools/list_changed": ToolsListChanged(),
"notifications/prompts/list_changed": PromptsListChanged(),
"notifications/resources/list_changed": ResourcesListChanged(),
}
LISTEN_STREAM_METHODS: frozenset[str] = frozenset({*_LIST_CHANGED_EVENTS, "notifications/resources/updated"})
"""The notification methods that ride `subscriptions/listen` streams at 2026-07-28
(and, at that era, nowhere else): the change-notification vocabulary."""
def event_from_wire(method: str, params: Mapping[str, Any] | None) -> ServerEvent | None:
"""The event a raw listen-stream frame announces, or None if it carries none.
Takes the raw wire dict: the client demultiplexes before the typed notification parse."""
if (event := _LIST_CHANGED_EVENTS.get(method)) is not None:
return event
if method == "notifications/resources/updated":
uri = (params or {}).get("uri")
if isinstance(uri, str):
return ResourceUpdated(uri=uri)
return None
def event_matches(honored: SubscriptionFilter, uris: frozenset[str], event: ServerEvent) -> bool:
"""Whether `event` is within the stream's honored filter (`uris`: the honored resource subscriptions as a set).
The admission predicate both sides share: server delivery and client intake honor only what was acknowledged."""
if isinstance(event, ToolsListChanged):
return honored.tools_list_changed is True
if isinstance(event, PromptsListChanged):
return honored.prompts_list_changed is True
if isinstance(event, ResourcesListChanged):
return honored.resources_list_changed is True
return event.uri in uris