pub struct ServerBuilder { /* private fields */ }Expand description
Builder for creating servers.
Implementations§
Source§impl ServerBuilder
impl ServerBuilder
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new server builder.
Creates a new ServerBuilder with default capabilities and no handlers.
Use the builder methods to configure the server before calling build().
§Examples
use pmcp::ServerBuilder;
let builder = ServerBuilder::new();This is equivalent to using the default implementation:
use pmcp::ServerBuilder;
let builder = ServerBuilder::default();Sourcepub fn version(self, version: impl Into<String>) -> Self
pub fn version(self, version: impl Into<String>) -> Self
Set the server version.
The server version identifies this specific version of the MCP server. This is required and will be sent to clients during initialization.
§Arguments
version- The version string (e.g., “1.0.0”, “2.1.3-beta”)
§Examples
use pmcp::Server;
let server = Server::builder()
.name("data-processor")
.version("2.1.0")
.build()?;Sourcepub fn website_url(self, url: impl Into<String>) -> Self
pub fn website_url(self, url: impl Into<String>) -> Self
Set the website URL for the server implementation (MCP 2025-11-25).
Sourcepub fn with_icons(self, icons: Vec<IconInfo>) -> Self
pub fn with_icons(self, icons: Vec<IconInfo>) -> Self
Set icons for the server implementation (MCP 2025-11-25).
Sourcepub fn capabilities(self, capabilities: ServerCapabilities) -> Self
pub fn capabilities(self, capabilities: ServerCapabilities) -> Self
Set server capabilities.
Configures the capabilities that this server supports. Capabilities inform clients about which MCP features are available.
§Arguments
capabilities- The server capabilities to advertise
§Examples
use pmcp::{Server, ServerCapabilities, ToolCapabilities};
let mut capabilities = ServerCapabilities::default();
capabilities.tools = Some(ToolCapabilities {
list_changed: Some(true),
});
let server = Server::builder()
.name("advanced-server")
.version("1.0.0")
.capabilities(capabilities)
.build()?;Sourcepub fn tool(
self,
name: impl Into<String>,
handler: impl ToolHandler + 'static,
) -> Self
pub fn tool( self, name: impl Into<String>, handler: impl ToolHandler + 'static, ) -> Self
Add a tool handler.
Registers a tool that clients can call via the tools/call method. Tools are the primary way servers provide functionality to clients.
§Arguments
name- The name of the tool (used by clients to call it)handler- The handler implementation for this tool
§Examples
use pmcp::{Server, ToolHandler};
use async_trait::async_trait;
use serde_json::Value;
struct FileListTool;
#[async_trait]
impl ToolHandler for FileListTool {
async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
let path = args["path"].as_str().unwrap_or(".");
// List files in path...
Ok(serde_json::json!({"files": ["file1.txt", "file2.txt"]}))
}
}
let server = Server::builder()
.name("file-server")
.version("1.0.0")
.tool("list_files", FileListTool{})
.build()?;Sourcepub fn tool_arc(
self,
name: impl Into<String>,
handler: Arc<dyn ToolHandler>,
) -> Self
pub fn tool_arc( self, name: impl Into<String>, handler: Arc<dyn ToolHandler>, ) -> Self
Add a tool handler with an Arc.
This variant lets the caller share the handler Arc between the
builder and an external in-process handler map (e.g., a downstream
toolkit’s handler registry) without writing a delegating wrapper
shim. Behavior is otherwise identical to Self::tool: the first
registration auto-enables capabilities.tools.
Sourcepub fn mcp_server<T: McpServer>(self, server: T) -> Self
pub fn mcp_server<T: McpServer>(self, server: T) -> Self
Register all tools and prompts from an #[mcp_server] annotated type.
This is the ergonomic counterpart to individually registering tools and
prompts. The server instance provides shared state via &self to all
tool and prompt methods.
§Examples
use pmcp::ServerBuilder;
#[mcp_server]
impl MyServer {
#[mcp_tool(description = "Query data")]
async fn query(&self, args: QueryArgs) -> Result<Value> { /* ... */ }
#[mcp_prompt(description = "Generate query")]
async fn query_prompt(&self, args: PromptArgs) -> Result<GetPromptResult> { /* ... */ }
}
let server = MyServer { db };
let builder = ServerBuilder::new()
.name("my-server")
.mcp_server(server);Sourcepub fn tool_typed<T, F, Fut>(self, name: impl Into<String>, handler: F) -> Selfwhere
T: DeserializeOwned + JsonSchema + Send + Sync + 'static,
F: Fn(T, RequestHandlerExtra) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Value>> + Send + 'static,
Available on crate feature schema-generation only.
pub fn tool_typed<T, F, Fut>(self, name: impl Into<String>, handler: F) -> Selfwhere
T: DeserializeOwned + JsonSchema + Send + Sync + 'static,
F: Fn(T, RequestHandlerExtra) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Value>> + Send + 'static,
schema-generation only.Add a type-safe tool handler with automatic schema generation.
This method provides first-class support for creating tools with:
- Automatic JSON schema generation from Rust types
- Compile-time type safety
- Runtime validation
- Field descriptions from doc comments
§Example
use pmcp::ServerBuilder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct EchoArgs {
/// The message to echo
message: String,
/// Optional prefix
prefix: Option<String>,
}
let server = ServerBuilder::new()
.name("example")
.tool_typed("echo", |args: EchoArgs, _| {
Box::pin(async move {
let message = match args.prefix {
Some(p) => format!("{}: {}", p, args.message),
None => args.message,
};
Ok(serde_json::json!({ "message": message }))
})
})
.build();Sourcepub fn tool_typed_with_description<T, F, Fut>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: F,
) -> Selfwhere
T: DeserializeOwned + JsonSchema + Send + Sync + 'static,
F: Fn(T, RequestHandlerExtra) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Value>> + Send + 'static,
Available on crate feature schema-generation only.
pub fn tool_typed_with_description<T, F, Fut>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: F,
) -> Selfwhere
T: DeserializeOwned + JsonSchema + Send + Sync + 'static,
F: Fn(T, RequestHandlerExtra) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Value>> + Send + 'static,
schema-generation only.Add a type-safe tool handler with automatic schema generation and description.
This is a convenience overload that allows setting a description directly
without needing to chain .with_description().
§Example
use pmcp::ServerBuilder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct EchoArgs {
/// The message to echo
message: String,
/// Optional prefix
prefix: Option<String>,
}
let server = ServerBuilder::new()
.name("example")
.tool_typed_with_description(
"echo",
"Echoes back a message with an optional prefix",
|args: EchoArgs, _| {
Box::pin(async move {
let message = match args.prefix {
Some(p) => format!("{}: {}", p, args.message),
None => args.message,
};
Ok(serde_json::json!({ "message": message }))
})
}
);Sourcepub fn tool_typed_sync<T, F>(self, name: impl Into<String>, handler: F) -> Selfwhere
T: DeserializeOwned + JsonSchema + Send + Sync + 'static,
F: Fn(T, RequestHandlerExtra) -> Result<Value> + Send + Sync + 'static,
Available on crate feature schema-generation only.
pub fn tool_typed_sync<T, F>(self, name: impl Into<String>, handler: F) -> Selfwhere
T: DeserializeOwned + JsonSchema + Send + Sync + 'static,
F: Fn(T, RequestHandlerExtra) -> Result<Value> + Send + Sync + 'static,
schema-generation only.Add a synchronous type-safe tool handler with automatic schema generation.
Similar to tool_typed but for synchronous handlers.
§Example
use pmcp::ServerBuilder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct MathArgs {
/// First number
a: f64,
/// Second number
b: f64,
/// Operation to perform
op: String,
}
let server = ServerBuilder::new()
.name("example")
.tool_typed_sync("calculator", |args: MathArgs, _| {
let result = match args.op.as_str() {
"add" => args.a + args.b,
"subtract" => args.a - args.b,
"multiply" => args.a * args.b,
"divide" => args.a / args.b,
_ => return Err(pmcp::Error::Validation("Unknown operation".into())),
};
Ok(serde_json::json!({ "result": result }))
})
.build();Sourcepub fn tool_typed_sync_with_description<T, F>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: F,
) -> Selfwhere
T: DeserializeOwned + JsonSchema + Send + Sync + 'static,
F: Fn(T, RequestHandlerExtra) -> Result<Value> + Send + Sync + 'static,
Available on crate feature schema-generation only.
pub fn tool_typed_sync_with_description<T, F>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: F,
) -> Selfwhere
T: DeserializeOwned + JsonSchema + Send + Sync + 'static,
F: Fn(T, RequestHandlerExtra) -> Result<Value> + Send + Sync + 'static,
schema-generation only.Add a synchronous type-safe tool handler with automatic schema generation and description.
This is a convenience overload that allows setting a description directly
without needing to chain .with_description().
§Example
use pmcp::ServerBuilder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct MathArgs {
/// First number
a: f64,
/// Second number
b: f64,
/// Operation to perform
op: String,
}
let server = ServerBuilder::new()
.name("example")
.tool_typed_sync_with_description(
"calculator",
"Performs synchronous mathematical operations",
|args: MathArgs, _| {
let result = match args.op.as_str() {
"add" => args.a + args.b,
"subtract" => args.a - args.b,
"multiply" => args.a * args.b,
"divide" => args.a / args.b,
_ => return Err(pmcp::Error::Validation("Unknown operation".into())),
};
Ok(serde_json::json!({ "result": result }))
}
);Sourcepub fn tool_typed_with_output<TIn, TOut>(
self,
name: impl Into<String>,
handler: impl Fn(TIn, RequestHandlerExtra) -> Pin<Box<dyn Future<Output = Result<TOut>> + Send>> + Send + Sync + 'static,
) -> Selfwhere
TIn: DeserializeOwned + JsonSchema + Send + Sync + 'static,
TOut: Serialize + JsonSchema + Send + Sync + 'static,
Available on crate feature schema-generation only.
pub fn tool_typed_with_output<TIn, TOut>(
self,
name: impl Into<String>,
handler: impl Fn(TIn, RequestHandlerExtra) -> Pin<Box<dyn Future<Output = Result<TOut>> + Send>> + Send + Sync + 'static,
) -> Selfwhere
TIn: DeserializeOwned + JsonSchema + Send + Sync + 'static,
TOut: Serialize + JsonSchema + Send + Sync + 'static,
schema-generation only.Add a type-safe tool handler with both input and output typing.
This method provides full type safety for both input and output types, which is useful for testing, documentation, and API contracts. Note that output schemas are not part of the MCP protocol but can be valuable for development and integration testing.
§Type Parameters
TIn- Input type that implementsJsonSchema,Deserialize,Send,SyncTOut- Output type that implementsJsonSchema,Serialize,Send,Sync
§Example
use pmcp::{ServerBuilder, TypedToolWithOutput};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(JsonSchema, Deserialize)]
struct MathInput { a: f64, b: f64, op: String }
#[derive(JsonSchema, Serialize)]
struct MathOutput { result: f64, operation: String }
let server = ServerBuilder::new()
.name("example")
.tool_typed_with_output::<MathInput, MathOutput>("math", |args, _| {
Box::pin(async move {
let result = match args.op.as_str() {
"add" => args.a + args.b,
"subtract" => args.a - args.b,
_ => return Err(pmcp::Error::Validation("Unknown operation".into())),
};
Ok(MathOutput {
result,
operation: args.op,
})
})
});Sourcepub fn tool_typed_with_output_and_description<TIn, TOut>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: impl Fn(TIn, RequestHandlerExtra) -> Pin<Box<dyn Future<Output = Result<TOut>> + Send>> + Send + Sync + 'static,
) -> Selfwhere
TIn: DeserializeOwned + JsonSchema + Send + Sync + 'static,
TOut: Serialize + JsonSchema + Send + Sync + 'static,
Available on crate feature schema-generation only.
pub fn tool_typed_with_output_and_description<TIn, TOut>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: impl Fn(TIn, RequestHandlerExtra) -> Pin<Box<dyn Future<Output = Result<TOut>> + Send>> + Send + Sync + 'static,
) -> Selfwhere
TIn: DeserializeOwned + JsonSchema + Send + Sync + 'static,
TOut: Serialize + JsonSchema + Send + Sync + 'static,
schema-generation only.Add a type-safe tool handler with both input and output typing and description.
This is a convenience overload that allows setting a description directly
without needing to chain .with_description().
§Example
use pmcp::ServerBuilder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(JsonSchema, Deserialize)]
struct MathInput { a: f64, b: f64, op: String }
#[derive(JsonSchema, Serialize)]
struct MathOutput { result: f64, operation: String }
let server = ServerBuilder::new()
.name("example")
.tool_typed_with_output_and_description::<MathInput, MathOutput>(
"math",
"Performs basic mathematical operations on two numbers",
|args, _| {
Box::pin(async move {
let result = match args.op.as_str() {
"add" => args.a + args.b,
"subtract" => args.a - args.b,
_ => return Err(pmcp::Error::Validation("Unknown operation".into())),
};
Ok(MathOutput { result, operation: args.op })
})
}
);Sourcepub fn prompt(
self,
name: impl Into<String>,
handler: impl PromptHandler + 'static,
) -> Self
pub fn prompt( self, name: impl Into<String>, handler: impl PromptHandler + 'static, ) -> Self
Add a prompt handler.
Registers a prompt that clients can retrieve via the prompts/get method. Prompts provide templates that clients can use for various tasks.
§Arguments
name- The name of the prompt (used by clients to retrieve it)handler- The handler implementation for this prompt
§Examples
use pmcp::{Server, PromptHandler, GetPromptResult, PromptMessage, Content};
use async_trait::async_trait;
use std::collections::HashMap;
struct CodeReviewPrompt;
#[async_trait]
impl PromptHandler for CodeReviewPrompt {
async fn handle(&self, args: HashMap<String, String>, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<GetPromptResult> {
let language = args.get("language").map(|s| s.as_str()).unwrap_or("unknown");
Ok(GetPromptResult::new(
vec![PromptMessage::user(pmcp::Content::text(format!(
"Please review this {} code:",
language
)))],
Some(format!("Code review prompt for {}", language)),
))
}
}
let server = Server::builder()
.name("code-server")
.version("1.0.0")
.prompt("code_review", CodeReviewPrompt{})
.build()?;Sourcepub fn prompt_arc(
self,
name: impl Into<String>,
handler: Arc<dyn PromptHandler>,
) -> Self
pub fn prompt_arc( self, name: impl Into<String>, handler: Arc<dyn PromptHandler>, ) -> Self
Add a prompt handler with an Arc.
This variant lets the caller share the handler Arc between the
builder and an external in-process handler map (e.g., a downstream
toolkit’s handler registry) without writing a delegating wrapper
shim. Behavior is otherwise identical to Self::prompt: the first
registration auto-enables capabilities.prompts.
Sourcepub fn prompt_workflow(self, workflow: SequentialWorkflow) -> Result<Self>
pub fn prompt_workflow(self, workflow: SequentialWorkflow) -> Result<Self>
Register a workflow-based prompt with automatic validation.
This method validates the workflow before registration and converts it to a prompt handler. The workflow’s instructions become the prompt messages, and the workflow’s arguments become the prompt arguments.
§Arguments
workflow- The workflow definition to register as a prompt
§Errors
Returns an error if the workflow validation fails (e.g., undefined bindings, undefined prompt arguments, etc.).
§Examples
use pmcp::{Server, ServerBuilder};
use pmcp::server::workflow::{SequentialWorkflow, InternalPromptMessage};
use pmcp::types::Role;
let workflow = SequentialWorkflow::new(
"code_review_workflow",
"Review code with multiple steps"
)
.argument("code", "Code to review", true)
.instruction(InternalPromptMessage::new(
Role::System,
"You are a code reviewer. Review the provided code carefully."
));
let server = Server::builder()
.name("code-server")
.version("1.0.0")
.prompt_workflow(workflow)?
.build()?;Sourcepub fn resources(self, handler: impl ResourceHandler + 'static) -> Self
pub fn resources(self, handler: impl ResourceHandler + 'static) -> Self
Set the resource handler.
Registers a resource handler that provides access to server resources. Resources allow clients to read files, configurations, or other data.
§Arguments
handler- The resource handler implementation
§Examples
use pmcp::{Server, ResourceHandler, ReadResourceResult, ListResourcesResult, ResourceInfo};
use async_trait::async_trait;
struct FileResourceHandler;
#[async_trait]
impl ResourceHandler for FileResourceHandler {
async fn read(&self, uri: &str, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<ReadResourceResult> {
// Read file content...
Ok(ReadResourceResult::new(vec![pmcp::Content::text("File content here")]))
}
async fn list(&self, _cursor: Option<String>, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<ListResourcesResult> {
Ok(ListResourcesResult::new(vec![
pmcp::ResourceInfo::new("file://example.txt", "example.txt")
.with_description("Example file")
.with_mime_type("text/plain"),
]))
}
}
let server = Server::builder()
.name("file-server")
.version("1.0.0")
.resources(FileResourceHandler{})
.build()?;Sourcepub fn resources_arc(self, handler: Arc<dyn ResourceHandler>) -> Self
pub fn resources_arc(self, handler: Arc<dyn ResourceHandler>) -> Self
Set the resource handler with an Arc.
This variant lets the caller share the handler Arc between the
builder and an external in-process handler map without writing a
delegating wrapper. Behavior is otherwise identical to
Self::resources: the first registration auto-enables
capabilities.resources.
Sourcepub fn sampling(self, handler: impl SamplingHandler + 'static) -> Self
pub fn sampling(self, handler: impl SamplingHandler + 'static) -> Self
Set the sampling handler.
Registers a sampling handler that provides LLM functionality. This allows the server to act as a language model provider.
§Arguments
handler- The sampling handler implementation
§Examples
use pmcp::{Server, SamplingHandler, CreateMessageParams, CreateMessageResult};
use async_trait::async_trait;
struct MockLLM;
#[async_trait]
impl SamplingHandler for MockLLM {
async fn create_message(&self, params: CreateMessageParams, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<CreateMessageResult> {
// Process the messages and generate a response
Ok(CreateMessageResult::new(pmcp::Content::text("Generated response"), "mock-llm-v1")
.with_usage(pmcp::TokenUsage::new(10, 5, 15))
.with_stop_reason("end_of_text"))
}
}
let server = Server::builder()
.name("llm-server")
.version("1.0.0")
.sampling(MockLLM{})
.build()?;Sourcepub fn sampling_arc(self, handler: Arc<dyn SamplingHandler>) -> Self
pub fn sampling_arc(self, handler: Arc<dyn SamplingHandler>) -> Self
Set the sampling handler with an Arc.
This variant lets the caller share the handler Arc between the
builder and an external in-process handler map without writing a
delegating wrapper. Uses the donor’s if is_none capability
auto-enable so an explicit prior .capabilities(custom) is not
clobbered by a later _arc registration.
Sourcepub fn auth_provider(self, provider: impl AuthProvider + 'static) -> Self
pub fn auth_provider(self, provider: impl AuthProvider + 'static) -> Self
Build the server.
Constructs the final Server instance from the configured builder. This validates that required fields (name and version) are set.
§Examples
use pmcp::{Server, ToolHandler};
use async_trait::async_trait;
use serde_json::Value;
struct PingTool;
#[async_trait]
impl ToolHandler for PingTool {
async fn handle(&self, _args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
Ok(serde_json::json!({"response": "pong"}))
}
}
let server = Server::builder()
.name("ping-server")
.version("1.0.0")
.tool("ping", PingTool{})
.build()?;
// Server is now ready to run
// server.run_stdio().await?;Set the authentication provider.
Configures an authentication provider that will validate incoming requests. When set, the server will use this provider to authenticate requests before processing them.
§Arguments
provider- The authentication provider implementation
§Examples
use pmcp::{Server, auth::ProxyProvider};
let auth_provider = ProxyProvider::with_upstream("https://oauth.example.com");
let server = Server::builder()
.name("secure-server")
.version("1.0.0")
.auth_provider(auth_provider)
.build()?;Sourcepub fn auth_provider_arc(self, provider: Arc<dyn AuthProvider>) -> Self
pub fn auth_provider_arc(self, provider: Arc<dyn AuthProvider>) -> Self
Set the authentication provider with an Arc.
This variant lets the caller share the provider Arc between the
builder and an external in-process registry without writing a
delegating wrapper. Behavior is otherwise identical to
Self::auth_provider.
Set the tool authorizer.
Configures a tool authorizer for fine-grained access control. The authorizer determines which tools authenticated users can access based on their authentication context.
§Arguments
authorizer- The tool authorization implementation
§Examples
use pmcp::{Server, auth::ScopeBasedAuthorizer};
let authorizer = ScopeBasedAuthorizer::new()
.require_scopes("sensitive_tool", vec!["admin".to_string()])
.default_scopes(vec!["read".to_string()]);
let server = Server::builder()
.name("secure-server")
.version("1.0.0")
.tool_authorizer(authorizer)
.build()?;Set the tool authorizer with an Arc.
This variant lets the caller share the authorizer Arc between
the builder and an external in-process registry without writing a
delegating wrapper. Mirrors Self::tool_authorizer’s
protection-clearing semantics: if any prior protect_tool()
configurations exist, they are cleared and a tracing::warn! is
emitted under target "mcp.auth", since a custom authorizer
supersedes scope-based tool protections.
Sourcepub fn protect_tool(
self,
tool_name: impl Into<String>,
scopes: Vec<String>,
) -> Self
pub fn protect_tool( self, tool_name: impl Into<String>, scopes: Vec<String>, ) -> Self
Protect a specific tool with required scopes.
This is a convenience method that creates or updates a scope-based authorizer to require specific scopes for accessing the named tool.
§Arguments
tool_name- The name of the tool to protectscopes- The required scopes for accessing this tool
§Examples
use pmcp::Server;
let server = Server::builder()
.name("secure-server")
.version("1.0.0")
.protect_tool("delete_data", vec!["admin".to_string(), "write".to_string()])
.protect_tool("read_data", vec!["read".to_string()])
.build()?;Sourcepub fn tool_middleware(self, middleware: Arc<dyn ToolMiddleware>) -> Self
pub fn tool_middleware(self, middleware: Arc<dyn ToolMiddleware>) -> Self
Add tool middleware for cross-cutting concerns.
Tool middleware allows you to inject cross-cutting concerns into tool execution, such as OAuth token injection, logging, metrics, or request transformation. Middleware is executed in the order it’s added, both for request processing (before tool execution) and response processing (after tool execution).
This method brings middleware support to the high-level ServerBuilder API,
enabling developers to use both typed tool registration AND middleware without
dropping down to the lower-level ServerCoreBuilder API.
§Arguments
middleware- The middleware implementation to add to the chain
§Examples
§OAuth Token Injection Middleware
use pmcp::server::tool_middleware::{ToolMiddleware, ToolContext};
use pmcp::server::cancellation::RequestHandlerExtra;
use pmcp::Server;
use std::sync::Arc;
use async_trait::async_trait;
use serde_json::Value;
struct OAuthInjectionMiddleware;
#[async_trait]
impl ToolMiddleware for OAuthInjectionMiddleware {
async fn on_request(
&self,
_tool_name: &str,
_args: &mut Value,
extra: &mut RequestHandlerExtra,
_context: &ToolContext,
) -> pmcp::Result<()> {
// Extract OAuth token from auth_context and inject into metadata
if let Some(auth_ctx) = extra.auth_context() {
if let Some(token) = &auth_ctx.token {
extra.set_metadata("oauth_token".to_string(), token.clone());
}
}
Ok(())
}
}
let server = Server::builder()
.name("oauth-server")
.version("1.0.0")
.tool_middleware(Arc::new(OAuthInjectionMiddleware))
.build()?;§Combining with Typed Tools
use pmcp::Server;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct ListGamesArgs {
filter: Option<String>,
}
let server = Server::builder()
.name("game-server")
.version("1.0.0")
.tool_typed_with_description(
"list_games",
"List all available games",
|args: ListGamesArgs, extra| {
Box::pin(async move {
// Access OAuth token injected by middleware
let _token = extra.get_metadata("oauth_token");
Ok(serde_json::json!({"games": []}))
})
}
)
// .tool_middleware(Arc::new(oauth_middleware)) // Works with typed tools!
.build()?;§Middleware Execution Order
Multiple middleware are executed in FIFO order for requests and FIFO for responses:
Request: Middleware1 → Middleware2 → Tool Handler
Response: Tool Handler → Middleware1 → Middleware2Sourcepub fn with_observability(self, config: ObservabilityConfig) -> Self
pub fn with_observability(self, config: ObservabilityConfig) -> Self
Enable observability for this server.
This adds observability middleware that provides:
- Distributed tracing with trace/span IDs
- Request/response event logging
- Metrics emission (duration, count, errors)
The backend is automatically selected based on the configuration:
- “console” - Pretty or JSON output to stdout (development)
- “cloudwatch” - AWS
CloudWatchEMF format (production) - “null” - Discards all events (testing)
§Examples
use pmcp::Server;
use pmcp::server::observability::ObservabilityConfig;
// Development: console output with pretty printing
let server = Server::builder()
.name("my-server")
.version("1.0.0")
.with_observability(ObservabilityConfig::development())
.build()?;
// Production: CloudWatch with EMF metrics
let server = Server::builder()
.name("my-server")
.version("1.0.0")
.with_observability(ObservabilityConfig::production())
.build()?;
// Auto-detect environment (Lambda vs local)
let config = if std::env::var("AWS_LAMBDA_FUNCTION_NAME").is_ok() {
ObservabilityConfig::production()
} else {
ObservabilityConfig::development()
};
let server = Server::builder()
.name("my-server")
.version("1.0.0")
.with_observability(config)
.build()?;Sourcepub fn with_observability_backend(
self,
config: ObservabilityConfig,
backend: Arc<dyn ObservabilityBackend>,
) -> Self
pub fn with_observability_backend( self, config: ObservabilityConfig, backend: Arc<dyn ObservabilityBackend>, ) -> Self
Enable observability with a custom backend.
Use this when you need a custom backend implementation (e.g., Datadog, custom metrics).
§Examples
use pmcp::Server;
use pmcp::server::observability::{ObservabilityConfig, ObservabilityBackend};
use std::sync::Arc;
struct MyCustomBackend;
#[async_trait]
impl ObservabilityBackend for MyCustomBackend {
// ... custom implementation
}
let server = Server::builder()
.name("my-server")
.version("1.0.0")
.with_observability_backend(
ObservabilityConfig::development(),
Arc::new(MyCustomBackend),
)
.build()?;Sourcepub fn with_tool_description(
self,
tool_name: impl Into<String>,
description: impl Into<String>,
) -> Self
👎Deprecated since 1.6.0: Use tool_typed_with_description() and similar variants instead
pub fn with_tool_description( self, tool_name: impl Into<String>, description: impl Into<String>, ) -> Self
Use tool_typed_with_description() and similar variants instead
Add a description to a tool (Note: Limited support).
Important: Due to the immutable design of tool handlers, this method cannot retroactively add descriptions to already-registered tools.
Recommended: Use the *_with_description variants instead:
.tool_typed_with_description().tool_typed_sync_with_description().tool_typed_with_output_and_description()
This method is provided for API completeness but will log warnings when used, encouraging migration to the preferred approaches.
§Preferred Examples
use pmcp::ServerBuilder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
struct MathArgs { a: f64, b: f64 }
// Preferred: Use the direct description variants
let server = ServerBuilder::new()
.name("example")
.tool_typed_with_description(
"add",
"Adds two numbers together",
|args: MathArgs, _| {
Box::pin(async move {
Ok(serde_json::json!({ "result": args.a + args.b }))
})
}
)
.build();Sourcepub fn with_http_middleware(
self,
middleware: Arc<ServerHttpMiddlewareChain>,
) -> Self
Available on crate feature streamable-http only.
pub fn with_http_middleware( self, middleware: Arc<ServerHttpMiddlewareChain>, ) -> Self
streamable-http only.Configure HTTP middleware chain for StreamableHttpServer.
This is a convenience method that stores the HTTP middleware chain
so it can be retrieved later when creating a StreamableHttpServer.
§Arguments
middleware- The HTTP middleware chain
§Examples
use pmcp::Server;
use pmcp::server::http_middleware::{ServerHttpLoggingMiddleware, ServerHttpMiddlewareChain};
use std::sync::Arc;
let mut http_chain = ServerHttpMiddlewareChain::new();
http_chain.add(Arc::new(ServerHttpLoggingMiddleware::new()));
let server = Server::builder()
.name("my-server")
.version("1.0.0")
.with_http_middleware(Arc::new(http_chain))
.build()?;
// Later when creating StreamableHttpServer:
// let config = StreamableHttpServerConfig {
// http_middleware: server.http_middleware(),
// ..Default::default()
// };Sourcepub fn with_host_layer(self, host: HostType) -> Self
Available on crate feature mcp-apps only.
pub fn with_host_layer(self, host: HostType) -> Self
mcp-apps only.Add a host layer for MCP Apps metadata enrichment.
Host layers enrich tool _meta at build time with host-specific keys.
For example, HostType::ChatGpt adds openai/outputTemplate and
openai/widgetAccessible derived from the standard ui.resourceUri.
This is opt-in — standard MCP Apps hosts (Claude Desktop, etc.) work without any host layer. Duplicates are ignored.