protocol_droid/application/server/
live.rs1use std::sync::{Arc, Mutex, PoisonError, Weak};
28
29use arc_swap::ArcSwap;
30
31use crate::application::registry::store::{Published, RegistryStore};
32use crate::application::registry::{Fanout, Subscriber, SubscriberSet};
33use crate::application::server::builder::{PromptHandler, ResourceHandler, ToolHandler};
34use crate::application::server::watch::Watchers;
35use crate::application::session::DynFrameSink;
36use crate::domain::registry::{
37 Primitive, PrimitiveId, PrimitiveKind, Prompt, Registered, Resource, ResourceTemplate, Tool,
38};
39use crate::domain::{Feature, FeatureProfile};
40
41#[derive(Clone)]
46pub(crate) struct ToolEntry {
47 pub(crate) name: PrimitiveId,
48 pub(crate) handler: ToolHandler,
49}
50
51impl core::fmt::Debug for ToolEntry {
52 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53 f.debug_struct("ToolEntry")
54 .field("name", &self.name)
55 .finish_non_exhaustive()
56 }
57}
58
59#[derive(Clone, Default)]
65pub(crate) struct Handlers {
66 pub(crate) tools: Vec<ToolEntry>,
67 pub(crate) resources: Vec<(PrimitiveId, ResourceHandler)>,
68 pub(crate) prompts: Vec<(PrimitiveId, PromptHandler)>,
69}
70
71pub(crate) struct Live {
73 registry: RegistryStore,
74 handlers: ArcSwap<Handlers>,
75 subscribers: SubscriberSet,
76 watchers: Watchers,
77 writer: Mutex<()>,
84}
85
86impl Live {
87 pub(crate) fn new(registry: RegistryStore, handlers: Handlers) -> Self {
88 Self {
89 registry,
90 handlers: ArcSwap::from_pointee(handlers),
91 subscribers: SubscriberSet::new(),
92 watchers: Watchers::default(),
93 writer: Mutex::new(()),
94 }
95 }
96
97 pub(crate) const fn registry(&self) -> &RegistryStore {
98 &self.registry
99 }
100
101 pub(crate) fn tool_count(&self) -> usize {
102 self.handlers.load().tools.len()
103 }
104
105 pub(crate) fn tool(&self, name: &str) -> Option<ToolHandler> {
106 self.handlers
107 .load()
108 .tools
109 .iter()
110 .find(|entry| entry.name.as_str() == name)
111 .map(|entry| Arc::clone(&entry.handler))
112 }
113
114 pub(crate) fn reader(&self, uri: &str) -> Option<ResourceHandler> {
115 self.handlers
116 .load()
117 .resources
118 .iter()
119 .find(|(registered, _)| registered.as_str() == uri)
120 .map(|(_, handler)| Arc::clone(handler))
121 }
122
123 pub(crate) fn getter(&self, name: &str) -> Option<PromptHandler> {
124 self.handlers
125 .load()
126 .prompts
127 .iter()
128 .find(|(registered, _)| registered.as_str() == name)
129 .map(|(_, handler)| Arc::clone(handler))
130 }
131
132 fn swap_handlers(&self, edit: impl FnOnce(&mut Handlers)) {
133 let mut next = Handlers::clone(&self.handlers.load());
134 edit(&mut next);
135 self.handlers.store(Arc::new(next));
136 }
137
138 pub(crate) fn add(
140 &self,
141 registered: Registered,
142 edit: impl FnOnce(&mut Handlers),
143 ) -> Published {
144 let _writer = self.writer.lock().unwrap_or_else(PoisonError::into_inner);
145 self.swap_handlers(edit);
146 self.registry.register(registered)
147 }
148
149 pub(crate) fn remove(
151 &self,
152 kind: PrimitiveKind,
153 id: &PrimitiveId,
154 edit: impl FnOnce(&mut Handlers),
155 ) -> Published {
156 let _writer = self.writer.lock().unwrap_or_else(PoisonError::into_inner);
157 let published = self.registry.unregister(kind, id);
158 self.swap_handlers(edit);
159 published
160 }
161
162 async fn announce(&self, published: &Published) -> Fanout {
163 self.subscribers
164 .announce(published.snapshot(), published.changed())
165 .await
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum ListKind {
177 Tools,
179 Resources,
181 Prompts,
183}
184
185impl ListKind {
186 pub const ALL: [Self; 3] = [Self::Tools, Self::Resources, Self::Prompts];
188
189 #[must_use]
191 pub const fn method(self) -> &'static str {
192 match self {
193 Self::Tools => "notifications/tools/list_changed",
194 Self::Resources => "notifications/resources/list_changed",
195 Self::Prompts => "notifications/prompts/list_changed",
196 }
197 }
198
199 #[must_use]
201 pub const fn feature(self) -> Feature {
202 match self {
203 Self::Tools => Feature::ToolsListChanged,
204 Self::Resources => Feature::ResourcesListChanged,
205 Self::Prompts => Feature::PromptsListChanged,
206 }
207 }
208
209 fn subscriber(
210 self,
211 frame: &str,
212 profile: FeatureProfile,
213 sink: Arc<dyn DynFrameSink>,
214 ) -> Subscriber {
215 let primary = match self {
216 Self::Tools => PrimitiveKind::Tool,
217 Self::Resources => PrimitiveKind::Resource,
218 Self::Prompts => PrimitiveKind::Prompt,
219 };
220 let subscriber =
221 Subscriber::new(primary, self.feature(), self.method(), frame, profile, sink);
222 if self == Self::Resources {
223 subscriber.also_for(PrimitiveKind::ResourceTemplate)
224 } else {
225 subscriber
226 }
227 }
228
229 fn frame(self, meta: Option<&serde_json::Value>) -> String {
235 let mut frame = serde_json::json!({
236 "jsonrpc": "2.0",
237 "method": self.method(),
238 });
239 if let Some(meta) = meta
240 && let Some(object) = frame.as_object_mut()
241 {
242 object.insert("params".to_owned(), serde_json::json!({ "_meta": meta }));
243 }
244 frame.to_string()
245 }
246}
247
248pub struct ListSubscription {
256 live: Weak<Live>,
257 sink: Arc<dyn DynFrameSink>,
258}
259
260impl core::fmt::Debug for ListSubscription {
261 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
262 f.debug_struct("ListSubscription").finish_non_exhaustive()
263 }
264}
265
266impl Drop for ListSubscription {
267 fn drop(&mut self) {
268 if let Some(live) = self.live.upgrade() {
269 live.subscribers.remove_by_sink(&self.sink);
270 live.watchers.forget(&self.sink);
271 }
272 }
273}
274
275#[derive(Clone)]
312pub struct ServerHandle {
313 live: Arc<Live>,
314}
315
316impl core::fmt::Debug for ServerHandle {
317 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
318 f.debug_struct("ServerHandle")
319 .field("tool_count", &self.live.tool_count())
320 .finish_non_exhaustive()
321 }
322}
323
324impl ServerHandle {
325 pub(crate) const fn new(live: Arc<Live>) -> Self {
326 Self { live }
327 }
328
329 pub async fn add_tool(&self, tool: Tool, handler: ToolHandler) -> Fanout {
331 self.add_restricted_tool(Registered::from(Primitive::from(tool)), handler)
332 .await
333 }
334
335 pub async fn add_restricted_tool(
340 &self,
341 registered: Registered,
342 handler: ToolHandler,
343 ) -> Fanout {
344 let name = match registered.primitive() {
345 Primitive::Tool(tool) => Some(tool.name.clone()),
346 _ => None,
347 };
348 let published = self.live.add(registered, |table| {
349 if let Some(name) = name {
350 table.tools.retain(|entry| entry.name != name);
351 table.tools.push(ToolEntry { name, handler });
352 }
353 });
354 self.live.announce(&published).await
355 }
356
357 pub async fn remove_tool(&self, name: &str) -> Fanout {
359 let id = PrimitiveId::from(name);
360 let published = self.live.remove(PrimitiveKind::Tool, &id, |table| {
361 table.tools.retain(|entry| entry.name != id);
362 });
363 self.live.announce(&published).await
364 }
365
366 pub async fn add_resource(&self, resource: Resource, reader: ResourceHandler) -> Fanout {
368 let uri = resource.uri.clone();
369 let published = self
370 .live
371 .add(Registered::from(Primitive::from(resource)), |table| {
372 table.resources.retain(|(registered, _)| *registered != uri);
373 table.resources.push((uri, reader));
374 });
375 self.live.announce(&published).await
376 }
377
378 pub async fn remove_resource(&self, uri: &str) -> Fanout {
380 let id = PrimitiveId::from(uri);
381 let published = self.live.remove(PrimitiveKind::Resource, &id, |table| {
382 table.resources.retain(|(registered, _)| *registered != id);
383 });
384 self.live.announce(&published).await
385 }
386
387 pub async fn add_resource_template(&self, template: ResourceTemplate) -> Fanout {
389 let published = self
390 .live
391 .add(Registered::from(Primitive::from(template)), |_| {});
392 self.live.announce(&published).await
393 }
394
395 pub async fn remove_resource_template(&self, uri_template: &str) -> Fanout {
397 let id = PrimitiveId::from(uri_template);
398 let published = self
399 .live
400 .remove(PrimitiveKind::ResourceTemplate, &id, |_| {});
401 self.live.announce(&published).await
402 }
403
404 pub async fn add_prompt(&self, prompt: Prompt, getter: PromptHandler) -> Fanout {
406 let name = prompt.name.clone();
407 let published = self
408 .live
409 .add(Registered::from(Primitive::from(prompt)), |table| {
410 table.prompts.retain(|(registered, _)| *registered != name);
411 table.prompts.push((name, getter));
412 });
413 self.live.announce(&published).await
414 }
415
416 pub async fn remove_prompt(&self, name: &str) -> Fanout {
418 let id = PrimitiveId::from(name);
419 let published = self.live.remove(PrimitiveKind::Prompt, &id, |table| {
420 table.prompts.retain(|(registered, _)| *registered != id);
421 });
422 self.live.announce(&published).await
423 }
424
425 #[must_use]
432 pub fn subscribe(
433 &self,
434 sink: &Arc<dyn DynFrameSink>,
435 profile: FeatureProfile,
436 kinds: &[ListKind],
437 meta: Option<&serde_json::Value>,
438 ) -> ListSubscription {
439 for kind in kinds {
440 self.live.subscribers.add(kind.subscriber(
441 &kind.frame(meta),
442 profile,
443 Arc::clone(sink),
444 ));
445 }
446 ListSubscription {
447 live: Arc::downgrade(&self.live),
448 sink: Arc::clone(sink),
449 }
450 }
451
452 pub async fn notify_resource_updated(&self, uri: &str) -> Fanout {
459 self.live.watchers.notify(uri).await
460 }
461
462 pub fn watch_resource(
467 &self,
468 sink: &Arc<dyn DynFrameSink>,
469 uri: &str,
470 meta: Option<&serde_json::Value>,
471 ) {
472 self.live.watchers.watch(sink, uri, meta);
473 }
474
475 pub fn unwatch_resource(&self, sink: &Arc<dyn DynFrameSink>, uri: &str) {
477 self.live.watchers.unwatch(sink, uri);
478 }
479
480 #[must_use]
482 pub fn watcher_count(&self) -> usize {
483 self.live.watchers.len()
484 }
485
486 #[must_use]
492 pub fn is_listed(&self, kind: PrimitiveKind, id: &str) -> bool {
493 self.live
494 .registry
495 .snapshot()
496 .contains(kind, &PrimitiveId::from(id))
497 }
498
499 #[must_use]
501 pub fn subscriber_count(&self) -> usize {
502 self.live.subscribers.len()
503 }
504}
505
506#[cfg(test)]
507mod tests;