Skip to main content

elicitation/plugin/
mod.rs

1//! Type-erased plugin interface for the elicitation tool registry.
2//!
3//! Each shadow crate (e.g., `elicit_reqwest`) provides a `Plugin` struct that
4//! implements [`ElicitPlugin`]. The [`PluginRegistry`](crate::PluginRegistry)
5//! collects these and serves them as a single MCP server.
6//!
7//! # Implementing a plugin
8//!
9//! **Simple path** — implement [`DescriptorPlugin`] and expose a slice of
10//! [`ToolDescriptor`]s built with [`make_descriptor`].  The blanket impl
11//! provides [`ElicitPlugin`] for free.
12//!
13//! **Full control** — implement [`ElicitPlugin`] directly.
14
15pub mod context;
16pub mod descriptor;
17pub mod descriptor_plugin;
18
19pub use context::PluginContext;
20pub use descriptor::{
21    PluginToolRegistration, ToolDescriptor, make_descriptor, make_descriptor_ctx,
22};
23pub use descriptor_plugin::DescriptorPlugin;
24
25use std::borrow::Cow;
26use std::sync::Arc;
27
28use futures::future::BoxFuture;
29use rmcp::{
30    ErrorData,
31    model::{CallToolRequestParams, CallToolResult, Tool},
32    service::RequestContext,
33};
34
35use crate::rmcp::RoleServer;
36
37/// Type-erased interface for a shadow-crate tool plugin.
38///
39/// # Object Safety
40///
41/// This trait is object-safe: all async methods return `BoxFuture`.
42///
43/// Prefer implementing [`DescriptorPlugin`] over this trait directly unless
44/// you need custom dispatch logic.
45pub trait ElicitPlugin: Send + Sync + 'static {
46    /// Human-readable plugin name, used as the namespace prefix.
47    ///
48    /// E.g. `"http"` produces tools named `http__get`, `http__post`, etc.
49    fn name(&self) -> &'static str;
50
51    /// List all tools provided by this plugin (without namespace prefix).
52    fn list_tools(&self) -> Vec<Tool>;
53
54    /// Dispatch a tool call to this plugin.
55    ///
56    /// `params.name` will already have the namespace prefix stripped by
57    /// `PluginRegistry` before this is called.
58    fn call_tool<'a>(
59        &'a self,
60        params: CallToolRequestParams,
61        ctx: RequestContext<RoleServer>,
62    ) -> BoxFuture<'a, Result<CallToolResult, ErrorData>>;
63}
64
65/// A type-erased, cheaply-cloneable plugin reference.
66pub type ArcPlugin = Arc<dyn ElicitPlugin>;
67
68/// Apply the namespace prefix to a tool name.
69///
70/// `"http"` + `"get"` → `"http__get"`.
71pub(crate) fn prefixed_name(prefix: &str, name: &str) -> Cow<'static, str> {
72    Cow::Owned(format!("{prefix}__{name}"))
73}
74
75/// Strip the namespace prefix from a tool name, returning the bare name.
76///
77/// `"http__get"` with prefix `"http"` → `"get"`.
78/// Returns `None` if the name does not start with `{prefix}__`.
79pub(crate) fn strip_prefix<'a>(prefix: &str, name: &'a str) -> Option<&'a str> {
80    let sep = format!("{prefix}__");
81    name.strip_prefix(sep.as_str())
82}