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 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 config = McpServerConfig::new("127.0.0.1:8000".parse().unwrap(), "/mcp", "page")?;
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;
invoked?;
client_closed
}.await;
let serving_closed = server.shutdown().await; // Consumes and joins serving.
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;
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.
use std::sync::Arc;
use tools_kit::{Toolset, ToolsetBackend, mcp::{McpServer, McpServerConfig}};
let tools = Arc::new(Toolset::local(Vec::new())?);
let config = McpServerConfig::new("127.0.0.1:8000".parse().unwrap(), "/mcp", "page")?;
let result = McpServer::start(Arc::clone(&tools), config).await;
let stopped = match result {
Ok(server) => server.shutdown().await,
Err(error) => Err(error),
};
let source_closed = tools.shutdown().await;
stopped?;
source_closed?;§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.
§Examples
use std::sync::Arc;
use tools_kit::{Toolset, ToolsetBackend, mcp::{McpServer, McpServerConfig}};
let tools = Arc::new(Toolset::local(Vec::new())?);
let config = McpServerConfig::new("127.0.0.1:8000".parse().unwrap(), "/mcp", "page")?;
let server = McpServer::start(Arc::clone(&tools), config).await?;
let stopped = server.shutdown().await;
let source_closed = tools.shutdown().await;
stopped?;
source_closed?;