pub struct McpServer { /* private fields */ }Expand description
Independently owned MCP server serving any concrete ToolsetBackend.
The server retains a shared reference to the caller’s toolset. Server shutdown never shuts down that toolset; the caller owns its cleanup separately. This example binds a real loopback endpoint and requires an available port. Keep the Tokio runtime driven until both server and toolset cleanup settle. Dropping the server or its shutdown observation signals its retained cleanup owner; only explicit toolset shutdown closes the separate executable source.
§Examples
use std::sync::Arc;
use tools_kit::{Toolset, ToolsetBackend, ToolHandlerBackend, ToolError};
use tools_kit::declaration::ToolName;
use tools_kit::execution::{ToolHandlerInput, ToolHandlerOutput, ToolInvocation};
use tools_kit::local::ToolRegistration;
use tools_kit::mcp::{McpServer, McpServerConfig, McpClientConfig, McpConnectionConfig};
// MCP requires object-shaped arguments; local typed tools may use scalars.
#[derive(serde::Deserialize, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
struct DoubleArgs { value: i64 }
#[derive(serde::Serialize, schemars::JsonSchema)]
struct DoubleResult { value: i64 }
let config = McpServerConfig::new("127.0.0.1:8000".parse().unwrap(), "/mcp", "page")?;
let source = Arc::new(Toolset::local(vec![ToolRegistration::typed(
"double", |input: DoubleArgs| async move {
let value = input.value.checked_mul(2).ok_or(ToolError::InvalidArguments {
tool: ToolName::new("double")?, message: "doubling would overflow i64".into(), issues: Vec::new(),
})?;
Ok(DoubleResult { value })
},
)?])?);
let server = McpServer::start(Arc::clone(&source), config).await;
let observed = async { match server {
Ok(server) => {
let client_work = async {
let client = Toolset::mcp(McpClientConfig::new(McpConnectionConfig::StreamableHttp {
endpoint: "http://127.0.0.1:8000/mcp".parse().unwrap(), headers: Default::default(),
})).await?;
let invoked = async {
let mut handler = client.tool_handler(ToolName::new("double")?).await?;
let output = handler.step(ToolHandlerInput::Invoke(ToolInvocation {
call_id: Some("client-call".into()), arguments: serde_json::json!({"value":21}),
})).await?;
let ToolHandlerOutput::Completed(result) = output else {
return Err(ToolError::Protocol { message: "unexpected interaction".into() });
};
assert!(!result.is_error);
assert_eq!(result.structured_content, Some(serde_json::json!({"value":42})));
Ok::<(), ToolError>(())
}.await;
let client_closed = client.shutdown().await;
if let Err(error) = &client_closed {
eprintln!("Cleanup failed: {error}");
}
invoked?;
client_closed
}.await;
let serving_closed = server.shutdown().await; // Consumes and joins serving.
if let Err(error) = &serving_closed {
eprintln!("Cleanup failed: {error}");
}
client_work?;
serving_closed?;
assert_eq!(source.list_tools().await?.len(), 1); // Source remains usable.
Ok(())
}
Err(error) => Err(error),
}
}.await;
let source_closed = source.shutdown().await;
if let Err(error) = &source_closed {
eprintln!("Cleanup failed: {error}");
}
observed?;
source_closed?;Implementations§
Source§impl McpServer
impl McpServer
Sourcepub async fn start<T: ToolsetBackend>(
toolset: Arc<T>,
config: McpServerConfig,
) -> Result<Self, ToolError>
pub async fn start<T: ToolsetBackend>( toolset: Arc<T>, config: McpServerConfig, ) -> Result<Self, ToolError>
Binds and starts the configured loopback endpoint eagerly.
A failed start leaves the caller’s toolset usable. Neither the toolset nor
its associated handler needs to implement Clone or use supplied handles.
This example binds a real loopback endpoint and requires an available port. Keep the Tokio runtime driven through both independently owned cleanup operations.
See McpServer for the complete client/server/source cleanup sequence.
§Errors
Returns ToolError::InvalidConfiguration for invalid server settings or ToolError::Transport when the endpoint cannot bind. Failed startup leaves source shutdown with the caller.
Sourcepub async fn shutdown(self) -> Result<(), ToolError>
pub async fn shutdown(self) -> Result<(), ToolError>
Seals admission and observes the retained server owner through cleanup.
A timeout reports unsettled cleanup; it does not abort the cleanup owner. This example binds a real loopback endpoint and requires an available port. Keep the Tokio runtime driven until both server and toolset cleanup settle. Dropping the server or its shutdown observation signals its retained cleanup owner; only explicit toolset shutdown closes the separate executable source.
§Errors
Returns the retained server cleanup failure, including timeout or handler-cleanup failure. A timeout leaves cleanup owned rather than aborting it.
See McpServer for the complete client/server/source cleanup sequence.