Skip to main content

ServerBuilder

Struct ServerBuilder 

Source
pub struct ServerBuilder { /* private fields */ }
Available on non-WebAssembly only.
Expand description

Builder for creating servers.

Implementations§

Source§

impl ServerBuilder

Source

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();
Source

pub fn with_request_state_key(self, key: [u8; 32]) -> Self

Available on crate feature streamable-http only.

Configure the shared requestState minting key (Phase 113, HTTP-02, D-03).

With no call, the key is resolved from PMCP_REQUEST_STATE_KEY; when that variable is unset the server generates a per-process key and WARNs at build time (D-04). Calling this overrides the environment entirely, which is what makes deterministic integration tests and multiple differently-configured servers in one process possible.

The key must be shared byte-for-byte by every instance behind a load balancer that should be able to resume each other’s multi-round-trip requests.

Has no effect on a server that did not opt into the v2 (2026-07-28) era.

The parameter type is deliberately still [u8; 32]: the SDK owns the copy it takes, not the caller’s (D-113-P, T-113-121).

Source

pub fn with_request_state_previous_keys(self, keys: Vec<[u8; 32]>) -> Self

Available on crate feature streamable-http only.

Accept rotated-out requestState keys for VERIFICATION only.

Tokens minted under a listed key still verify, but new tokens are always minted under the current key — so a rotation does not strand in-flight continuations. With no call, only the current key is accepted.

Has no effect on a server that did not opt into the v2 (2026-07-28) era.

Source

pub fn with_request_state_ttl(self, ttl: Duration) -> Self

Available on crate feature streamable-http only.

Configure the requestState continuation lifetime (D-05).

With no call, the lifetime is PMCP_REQUEST_STATE_TTL_SECS if parseable, else 300 seconds. A builder value beats both.

Has no effect on a server that did not opt into the v2 (2026-07-28) era.

Source

pub fn with_supported_protocol_versions( self, versions: impl IntoIterator<Item = ProtocolVersion>, ) -> Self

Opt into a protocol-version accept-list (Phase 112, VERS-01/02; D-02/D-04).

The high-level Server twin of ServerCoreBuilder::with_supported_protocol_versions. With no call, the server is v1-only and behaves exactly as today. An empty accept-list falls back to the v1-only default (never all-reject).

Source

pub fn name(self, name: impl Into<String>) -> Self

Set the server name.

The server name identifies this MCP server implementation. This is required and will be sent to clients during initialization.

§Arguments
  • name - The name of the server
§Examples
use pmcp::Server;

let server = Server::builder()
    .name("file-manager")
    .version("1.0.0")
    .build()?;
Source

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()?;
Source

pub fn website_url(self, url: impl Into<String>) -> Self

Set the website URL for the server implementation (MCP 2025-11-25).

Source

pub fn with_icons(self, icons: Vec<IconInfo>) -> Self

Set icons for the server implementation (MCP 2025-11-25).

Source

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()?;
Source

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()?;
Source

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.

Source

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);
Source

pub fn tool_typed<T, F, Fut>(self, name: impl Into<String>, handler: F) -> Self
where 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.

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();
Source

pub fn tool_typed_with_description<T, F, Fut>( self, name: impl Into<String>, description: impl Into<String>, handler: F, ) -> Self
where 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.

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 }))
            })
        }
    );
Source

pub fn tool_typed_sync<T, F>(self, name: impl Into<String>, handler: F) -> Self
where T: DeserializeOwned + JsonSchema + Send + Sync + 'static, F: Fn(T, RequestHandlerExtra) -> Result<Value> + Send + Sync + 'static,

Available on crate feature 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();
Source

pub fn tool_typed_sync_with_description<T, F>( self, name: impl Into<String>, description: impl Into<String>, handler: F, ) -> Self
where T: DeserializeOwned + JsonSchema + Send + Sync + 'static, F: Fn(T, RequestHandlerExtra) -> Result<Value> + Send + Sync + 'static,

Available on crate feature 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 }))
        }
    );
Source

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, ) -> Self
where TIn: DeserializeOwned + JsonSchema + Send + Sync + 'static, TOut: Serialize + JsonSchema + Send + Sync + 'static,

Available on crate feature 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 implements JsonSchema, Deserialize, Send, Sync
  • TOut - Output type that implements JsonSchema, 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,
            })
        })
    });
Source

pub fn tool_with_result<TIn>( self, name: impl Into<String>, handler: impl Fn(TIn, RequestHandlerExtra) -> Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>> + Send + Sync + 'static, ) -> Self
where TIn: DeserializeOwned + JsonSchema + Send + Sync + 'static,

Available on crate feature schema-generation only.

Register a tool whose async closure returns a full CallToolResult the handler owns end-to-end, emitted to the wire VERBATIM.

This mirrors tool_typed_with_output but fixes the return type to CallToolResult, so a handler can attach task augmentation (CallToolResult::with_related_task(...)), custom _meta, structured content, or an error envelope in ONE call — no hand-written ToolHandler impl required. The input arg type TIn deserializes from the tool arguments exactly as with tool_typed.

§⚠️ BYPASS WARNING — the returned result is sent to the wire VERBATIM

The closure’s CallToolResult is routed through ToolOutput::Result and therefore BYPASSES response middleware — redaction, sanitization, and audit hooks (ToolMiddleware::on_response) DO NOT run — as well as text-wrapping and widget enrichment. The handler owns its OWN redaction and sanitization of both content and _meta, at the same trust level as returning a raw Value today (D-04a). Request middleware still runs before the handler, and handler errors still route through the normal error path.

To advertise a human-readable tool description in tools/list, use tool_with_result_and_description.

§Example
use pmcp::ServerBuilder;
use pmcp::types::CallToolResult;
use pmcp::types::tasks::TaskMetadata;
use pmcp::types::Content;
use schemars::JsonSchema;
use serde::Deserialize;

#[derive(JsonSchema, Deserialize)]
struct RunArgs { job: String }

let server = ServerBuilder::new()
    .name("example")
    .tool_with_result("start_job", |args: RunArgs, _extra| {
        Box::pin(async move {
            Ok(CallToolResult::new(vec![Content::text(
                format!("started {}", args.job),
            )])
            .with_related_task(TaskMetadata::new("t1")))
        })
    })
    .build();
Source

pub fn tool_with_result_and_description<TIn>( self, name: impl Into<String>, description: impl Into<String>, handler: impl Fn(TIn, RequestHandlerExtra) -> Pin<Box<dyn Future<Output = Result<CallToolResult>> + Send>> + Send + Sync + 'static, ) -> Self
where TIn: DeserializeOwned + JsonSchema + Send + Sync + 'static,

Available on crate feature schema-generation only.

tool_with_result WITH a human-readable description advertised in tools/list.

Identical to tool_with_result — including the BYPASS WARNING documented there (the returned CallToolResult goes to the wire VERBATIM and skips response middleware) — but also sets the tool description, mirroring tool_typed_with_description. A description materially improves LLM tool selection; prefer this overload over the description-less tool_with_result.

§Example
use pmcp::ServerBuilder;
use pmcp::types::CallToolResult;
use pmcp::types::tasks::TaskMetadata;
use pmcp::types::Content;
use schemars::JsonSchema;
use serde::Deserialize;

#[derive(JsonSchema, Deserialize)]
struct RunArgs { job: String }

let server = ServerBuilder::new()
    .name("example")
    .tool_with_result_and_description(
        "start_job",
        "Start a background export job and return its task handle",
        |args: RunArgs, _extra| {
            Box::pin(async move {
                Ok(CallToolResult::new(vec![Content::text(
                    format!("started {}", args.job),
                )])
                .with_related_task(TaskMetadata::new("t1")))
            })
        },
    )
    .build();
Source

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, ) -> Self
where TIn: DeserializeOwned + JsonSchema + Send + Sync + 'static, TOut: Serialize + JsonSchema + Send + Sync + 'static,

Available on crate feature 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 })
            })
        }
    );
Source

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()?;
Source

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.

Source

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()?;
Source

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()?;
Source

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.

Source

pub fn completions( self, provider: impl CompletionProviderTrait + 'static, ) -> Self

Set the completion provider backing completion/complete.

The twin of ServerCoreBuilder::completions — same name, same signature, same single-provider shape — so a provider registered through EITHER builder family reaches its own dispatcher. A slot on one family with the dispatch arm on the other’s server would be an unreachable seam that still answered the spec shape, which is exactly the false green this pair exists to prevent.

A SINGLE, server-wide provider (the Self::resources shape, not the name-keyed Self::prompt shape): the spec routes every completion/complete to one seam and passes the ref as data. The reference reaches the provider through CompletionRequest::context under the key ref/prompt or ref/resource.

Registering a provider auto-advertises capabilities.completions. Not registering one is NOT an error: completion/complete still answers {"completion": {"values": []}}.

§Examples
use pmcp::Server;
use pmcp::types::completable::StaticCompletionProvider;

let server = Server::builder()
    .name("completion-server")
    .version("1.0.0")
    .completions(StaticCompletionProvider::from_strings(vec![
        "alpha".to_string(),
        "beta".to_string(),
    ]))
    .build()?;
Source

pub fn completions_arc(self, provider: Arc<dyn CompletionProviderTrait>) -> Self

Set the completion provider with an Arc.

This variant lets the caller share the provider Arc with something outside the builder. Behavior is otherwise identical to Self::completions.

Source

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()?;
Source

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.

Source

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()?;
Source

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.

Source

pub fn tool_authorizer(self, authorizer: impl ToolAuthorizer + 'static) -> Self

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()?;
Source

pub fn tool_authorizer_arc(self, authorizer: Arc<dyn ToolAuthorizer>) -> Self

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.

Source

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 protect
  • scopes - 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()?;
Source

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 → Middleware2
Source

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 CloudWatch EMF 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()?;
Source

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()?;
Source

pub 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

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();
Source

pub fn with_http_middleware( self, middleware: Arc<ServerHttpMiddlewareChain>, ) -> Self

Available on crate feature 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()
// };
Source

pub fn with_host_layer(self, host: HostType) -> Self

Available on crate feature 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.

Source

pub fn task_store(self, store: Arc<dyn TaskStore>) -> Self

Build the server.

Constructs the final Server instance from the configured builder. This validates that required fields (name and version) are set.

§Errors

Register a TaskStore for MCP Tasks on the high-level HTTP-facing Server (RECOMMENDED tools-as-Tasks path).

This is the recommended, all-typed path for exposing a tool as an async MCP Task over the Server / StreamableHttpServer path: pair a task-capable TypedTool (marked with_task_support(TaskSupport::Required)) with a store here, and the SDK serves tasks/* typed from the store — you never hand-write tasks/* wire JSON, and the store mints the task id. For the legacy experimental router path, use Self::with_task_store (which takes a TaskRouter, NOT a TaskStore).

When a task store is registered, the server:

  • Auto-advertises ServerCapabilities.tasks (with list and cancel support) in initialize — the mere presence of a store flips the capability on, unless an explicit tasks capability was already configured (additive-only; an explicit value is preserved verbatim).
  • Handles the tasks/* surface via the store. The method set is ERA-DEPENDENT (Phase 114): v1 (2025-11-25) serves tasks/get, tasks/result, tasks/list and tasks/cancel; v2 (2026-07-28) serves tasks/get, tasks/update and tasks/cancel, and answers -32601 for the two retired methods
  • Resolves task owner from auth context. v1 falls back through OAuth subject → client ID → session ID; v2 has no session to fall back to and binds fail-closed on an auth-configured server (TASK-05, D-07)

A tool declaring TaskSupport::Required with NO store (or router) makes Self::build return an Err, rather than advertising a hollow tasks capability whose endpoints cannot work.

§Examples
use std::sync::Arc;
use pmcp::Server;
use pmcp::server::task_store::{InMemoryTaskStore, TaskStore};
use pmcp::server::typed_tool::TypedTool;
use pmcp::types::{TaskSupport, ToolExecution};

let task_tool = TypedTool::new_with_schema(
    "summarize",
    serde_json::json!({ "type": "object" }),
    |_args: serde_json::Value, _extra| {
        Box::pin(async { Ok(serde_json::json!({ "status": "completed" })) })
    },
)
.with_description("Summarize asynchronously as an MCP Task")
.with_execution(ToolExecution::new().with_task_support(TaskSupport::Required));

let store = Arc::new(InMemoryTaskStore::new()) as Arc<dyn TaskStore>;
let server = Server::builder()
    .name("my-server")
    .version("1.0.0")
    .tool("summarize", task_tool)
    .task_store(store) // presence of a store auto-advertises the `tasks` capability
    .build()?;
Source

pub fn with_task_store(self, router: Arc<dyn TaskRouter>) -> Self

Register a legacy experimental TaskRouter for MCP Tasks on the high-level Server.

NAMING NOTE: despite the with_task_store name, this setter accepts a TaskRouter (the legacy, experimental router-backed path), NOT a TaskStore. The setter for an actual TaskStore (the RECOMMENDED polling path) is Self::task_store. This carried-over naming mirrors ServerCoreBuilder::with_task_store; the API is additive-only, so the confusing pair is documented here rather than renamed.

Registering a router auto-configures the experimental.tasks capability from the router’s task_capabilities().

That advertisement is v1-only (Phase 114). experimental.tasks is the 2025-11-25 spelling; a v2 (2026-07-28) client never sees it, because project_capabilities_for_v2 strips both experimental and capabilities.tasks and v2 declares tasks through the extensions map key io.modelcontextprotocol/tasks instead (plan 114-05).

Source

pub fn suppress_double_wrap_check(self, name: impl Into<String>) -> Self

Opt a tool OUT of the TOUT-02 double-wrap tripwire (D-08).

The tripwire WARNs (every build) and debug_assert!-fails (debug/CI) when a tool returns a ToolOutput::Payload Value that STRUCTURALLY resembles an already-built CallToolResult (a non-empty content array of Content, or a _meta related-task envelope) — the silent double-wrap bug. Naming a tool here suppresses that check for it.

SUPPRESSION SHOULD BE RARE AND REVIEWED: it disables a safety tripwire for one tool whose LEGITIMATE payload happens to trip the heuristic. Prefer returning ToolOutput::Result so the handler owns the full envelope verbatim, rather than suppressing. Reach for this only when a tool genuinely produces a plain Value that mimics a result shape and cannot be restructured.

The same suppression set is carried into ServerCore, so both native dispatchers honor the opt-out identically (no drift).

Source

pub fn build(self) -> Result<Server>

Returns an error if:

  • The server name is not set
  • The server version is not set
  • A tool declares TaskSupport::Required but no TaskStore/TaskRouter backend is configured (see Self::task_store)

Trait Implementations§

Source§

impl Debug for ServerBuilder

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for ServerBuilder

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more