pub struct Server { /* private fields */ }Expand description
MCP server implementation.
§Examples
use pmcp::{Server, ServerCapabilities, ToolHandler};
use async_trait::async_trait;
use serde_json::Value;
struct MyTool;
#[async_trait]
impl ToolHandler for MyTool {
async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
Ok(serde_json::json!({"result": "success"}))
}
}
let server = Server::builder()
.name("my-server")
.version("1.0.0")
.tool("my-tool", MyTool)
.build()?;
server.run_stdio().await?;Implementations§
Source§impl Server
impl Server
Sourcepub async fn handle_batch_request(
self: &Arc<Self>,
batch: BatchRequest,
) -> Result<BatchResponse>
pub async fn handle_batch_request( self: &Arc<Self>, batch: BatchRequest, ) -> Result<BatchResponse>
Handle a batch request.
Processes multiple JSON-RPC requests and returns their responses in the same order.
Source§impl Server
impl Server
Sourcepub fn has_prompt(&self, name: &str) -> bool
pub fn has_prompt(&self, name: &str) -> bool
Check if a prompt exists
Sourcepub fn get_prompt(&self, name: &str) -> Option<&Arc<dyn PromptHandler>>
pub fn get_prompt(&self, name: &str) -> Option<&Arc<dyn PromptHandler>>
Get a prompt handler by name.
Returns a borrowed reference to the registered prompt handler Arc,
or None if no prompt with that name has been registered. Callers
who need ownership can Arc::clone(...) the returned reference.
§Handler-level testing pattern
This accessor is the public API surface for the handler-level
integration testing pattern documented in the testing chapter of
the PMCP book: build a Server, retrieve the handler by name,
invoke .handle(...).await directly with a synthetic
RequestHandlerExtra. It exercises handler logic in isolation
without spinning up a transport.
§What this pattern skips
This pattern exercises handler logic only. The JSONRPC dispatch
path (Server::handle_request) is bypassed, so auth_provider,
tool_authorizer, and tool_middleware are not invoked. For
full-pipeline tests that exercise the security pipeline, drive a
real transport (stdio or streamable-http) with a pmcp::Client.
§Examples
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use pmcp::{PromptHandler, Server};
use pmcp::types::{GetPromptResult, PromptMessage, Content};
use pmcp::types::content::Role;
struct GreetingPrompt;
#[async_trait]
impl PromptHandler for GreetingPrompt {
async fn handle(
&self,
args: HashMap<String, String>,
_extra: pmcp::RequestHandlerExtra,
) -> pmcp::Result<GetPromptResult> {
let who = args.get("name").cloned().unwrap_or_else(|| "world".to_string());
Ok(GetPromptResult::new(
vec![PromptMessage::new(Role::User, Content::text(format!("Hello, {}!", who)))],
Some("Greeting prompt".to_string()),
))
}
}
let server = Server::builder()
.name("demo")
.version("0.1")
.prompt_arc("greet", Arc::new(GreetingPrompt))
.build()?;
let handler = server.get_prompt("greet").expect("registered above");
let mut args = HashMap::new();
args.insert("name".to_string(), "claude".to_string());
let result = handler
.handle(args, pmcp::RequestHandlerExtra::default())
.await?;
assert_eq!(result.messages.len(), 1);Sourcepub fn get_tool(&self, name: &str) -> Option<&Arc<dyn ToolHandler>>
pub fn get_tool(&self, name: &str) -> Option<&Arc<dyn ToolHandler>>
Get a tool handler by name.
Returns a borrowed reference to the registered tool handler Arc,
or None if no tool with that name has been registered. Callers
who need ownership can Arc::clone(...) the returned reference.
§Handler-level testing pattern
This accessor is the public API surface for the handler-level
integration testing pattern: build a Server, retrieve the
handler by name, invoke .handle(...).await directly with a
synthetic RequestHandlerExtra. It exercises handler logic in
isolation without spinning up a transport, which is the primary
shape downstream toolkit authors use to assert on a built
pmcp::Server’s registered handlers.
§What this pattern skips
This pattern exercises handler logic only. The JSONRPC dispatch
path (Server::handle_request) is bypassed, so auth_provider,
tool_authorizer, and tool_middleware are not invoked. For
full-pipeline tests that exercise the security pipeline, drive a
real transport (stdio or streamable-http) with a pmcp::Client.
§Examples
use std::sync::Arc;
use async_trait::async_trait;
use pmcp::{Server, ToolHandler};
use serde_json::Value;
struct EchoTool;
#[async_trait]
impl ToolHandler for EchoTool {
async fn handle(
&self,
args: Value,
_extra: pmcp::RequestHandlerExtra,
) -> pmcp::Result<Value> {
Ok(serde_json::json!({ "echoed": args }))
}
}
let server = Server::builder()
.name("demo")
.version("0.1")
.tool_arc("echo", Arc::new(EchoTool))
.build()?;
let handler = server.get_tool("echo").expect("registered above");
let result = handler
.handle(serde_json::json!({"msg": "hi"}), pmcp::RequestHandlerExtra::default())
.await?;
assert_eq!(result, serde_json::json!({"echoed": {"msg": "hi"}}));Sourcepub fn http_middleware(&self) -> Option<Arc<ServerHttpMiddlewareChain>>
Available on crate feature streamable-http only.
pub fn http_middleware(&self) -> Option<Arc<ServerHttpMiddlewareChain>>
streamable-http only.Get the HTTP middleware chain configured via ServerBuilder.
Returns the HTTP middleware chain that was set using
ServerBuilder::with_http_middleware(). This can be used when
creating a StreamableHttpServer.
§Examples
use pmcp::Server;
use pmcp::server::streamable_http_server::StreamableHttpServerConfig;
let server = Server::builder()
.name("my-server")
.version("1.0.0")
// ... with_http_middleware() called here
.build()?;
let config = StreamableHttpServerConfig {
http_middleware: server.http_middleware(),
..Default::default()
};Sourcepub fn get_auth_provider(&self) -> Option<Arc<dyn AuthProvider>>
pub fn get_auth_provider(&self) -> Option<Arc<dyn AuthProvider>>
Get the authentication provider configured via ServerBuilder.
Returns the authentication provider that was set using
ServerBuilder::auth_provider(). This can be used by transport
layers to validate incoming requests and extract auth context.
Sourcepub fn build_expansion_registries(
&self,
) -> (HashMap<Arc<str>, ToolInfo>, HashMap<Arc<str>, ResourceInfo>)
pub fn build_expansion_registries( &self, ) -> (HashMap<Arc<str>, ToolInfo>, HashMap<Arc<str>, ResourceInfo>)
Build tool and resource registries for workflow expansion.
Creates HashMap registries that can be used to build an ExpansionContext
for converting workflow prompts to protocol types. The registries are
automatically populated from all registered tools and resources.
Returns a tuple of (tools_map, resources_map) that can be used with
ExpansionContext.
§Examples
use pmcp::Server;
use pmcp::server::workflow::{InternalPromptMessage, ToolHandle, PromptContent, conversion::ExpansionContext};
use pmcp::types::Role;
let server = Server::builder()
.name("example-server")
.version("1.0.0")
.build()?;
// Build registries from registered tools/resources
let (tools, resources) = server.build_expansion_registries();
// Create expansion context
let ctx = ExpansionContext {
tools: &tools,
resources: &resources,
};
// Use it to convert workflow prompts to protocol types
let msg = InternalPromptMessage::new(
Role::System,
PromptContent::ToolHandle(ToolHandle::new("my_tool"))
);
let protocol_msg = msg.to_protocol(&ctx)?;Sourcepub async fn send_notification(&self, notification: ServerNotification)
pub async fn send_notification(&self, notification: ServerNotification)
Send a notification.
Sends a notification to the connected client. Notifications are one-way messages that don’t expect a response.
§Arguments
notification- The server notification to send
§Examples
use pmcp::{Server, ServerNotification, ProgressNotification, ProgressToken};
let server = Server::builder()
.name("example-server")
.version("1.0.0")
.build()?;
// Send a progress notification
let progress = ProgressNotification::new(
ProgressToken::String("task-123".to_string()),
50.0,
Some("Processing...".to_string()),
);
server.send_notification(ServerNotification::Progress(progress)).await;Sourcepub fn close_subscription_streams(&self)
pub fn close_subscription_streams(&self)
Gracefully close every open subscriptions/listen stream (HTTP-04).
Call this from a shutdown handler: each stream receives its
SubscriptionsListenResult
as the JSON-RPC response and is then ended. This is the ONLY one of the
three closure triggers that can send a terminal result — a client
disconnect cannot (the peer is gone) and the buffer-overflow policy cannot
(the buffer is full); both simply end the stream.
§Examples
let server = pmcp::Server::builder()
.name("example-server")
.version("1.0.0")
.build()?;
// ... on shutdown:
server.close_subscription_streams();Sourcepub async fn get_client_capabilities(&self) -> Option<ClientCapabilities>
pub async fn get_client_capabilities(&self) -> Option<ClientCapabilities>
Get client capabilities.
Returns the capabilities that the client declared during initialization. This can be used to check if the client supports specific features.
§Examples
use pmcp::Server;
let server = Server::builder()
.name("example-server")
.version("1.0.0")
.build()?;
// Check client capabilities after initialization
if let Some(capabilities) = server.get_client_capabilities().await {
if capabilities.sampling.is_some() {
println!("Client supports LLM sampling requests");
}
if capabilities.elicitation.is_some() {
println!("Client supports user input requests");
}
}§Returns
Some(ClientCapabilities)if the client has been initializedNoneif the client hasn’t initialized yet
Sourcepub async fn is_initialized(&self) -> bool
pub async fn is_initialized(&self) -> bool
Check if the server is initialized.
Returns true if the initialization handshake with a client has completed. The server must be initialized before it can process most requests.
§Examples
use pmcp::Server;
let server = Server::builder()
.name("example-server")
.version("1.0.0")
.build()?;
if server.is_initialized().await {
println!("Server is ready to handle requests");
} else {
println!("Waiting for client initialization");
}Sourcepub fn builder() -> ServerBuilder
pub fn builder() -> ServerBuilder
Create a new server builder.
Returns a ServerBuilder for configuring and constructing a new MCP server.
The builder pattern allows you to set server information, capabilities,
and register handlers before building the final server instance.
§Examples
use pmcp::{Server, ToolHandler};
use async_trait::async_trait;
use serde_json::Value;
struct HelloTool;
#[async_trait]
impl ToolHandler for HelloTool {
async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
Ok(serde_json::json!({"message": "Hello, World!"}))
}
}
let server = Server::builder()
.name("greeting-server")
.version("1.0.0")
.tool("hello", HelloTool{})
.build()?;Sourcepub async fn run_stdio(self) -> Result<()>
pub async fn run_stdio(self) -> Result<()>
Run the server with stdio transport.
Starts the server using stdin/stdout for communication. This is the standard way to run MCP servers as they communicate via JSON-RPC over stdio.
§Examples
use pmcp::{Server, ToolHandler};
use async_trait::async_trait;
use serde_json::Value;
struct EchoTool;
#[async_trait]
impl ToolHandler for EchoTool {
async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
Ok(args) // Echo the input
}
}
let server = Server::builder()
.name("echo-server")
.version("1.0.0")
.tool("echo", EchoTool{})
.build()?;
// This will run indefinitely, handling client requests
server.run_stdio().await?;§Security: task isolation
Stdio (and any transport that does not resolve a per-request
AuthContext) carries no authenticated principal, so every tasks/*
request on a task_store-backed server is
owned by the single "local" owner — there is NO per-user task isolation.
This is correct for single-user CLI use; for multi-tenant deployments use
an HTTP transport whose auth layer populates the OAuth subject.
§Errors
Returns an error if:
- The stdio transport fails to initialize
- Communication with the client fails
- The server encounters an unrecoverable error
Sourcepub async fn run<T: Transport + 'static>(self, transport: T) -> Result<()>
pub async fn run<T: Transport + 'static>(self, transport: T) -> Result<()>
Run the server with a custom transport.
Starts the server using a custom transport implementation.
This allows for different communication mechanisms beyond stdio,
such as TCP sockets, WebSockets, or other protocols.
§Arguments
transport- The transport implementation to use for communication
§Examples
use pmcp::{Server, StdioTransport, ToolHandler};
use async_trait::async_trait;
use serde_json::Value;
struct CalculatorTool;
#[async_trait]
impl ToolHandler for CalculatorTool {
async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
let a = args["a"].as_f64().unwrap_or(0.0);
let b = args["b"].as_f64().unwrap_or(0.0);
Ok(serde_json::json!({"result": a + b}))
}
}
let server = Server::builder()
.name("calculator-server")
.version("1.0.0")
.tool("add", CalculatorTool{})
.build()?;
let transport = StdioTransport::new();
server.run(transport).await?;§Security: task isolation
Per-user tasks/* isolation requires the transport to resolve a per-request
AuthContext carrying the OAuth subject. Transports without one (stdio and
the like) own every task under the single "local" owner — no per-user
isolation. Use an authenticating HTTP transport for multi-tenant task servers.
§Errors
Returns an error if:
- The transport fails to initialize or operate
- Communication with the client fails
- The server encounters an unrecoverable error
Sourcepub async fn register_root(
&self,
uri: impl Into<String>,
name: Option<String>,
) -> Result<impl FnOnce() + Send + 'static>
pub async fn register_root( &self, uri: impl Into<String>, name: Option<String>, ) -> Result<impl FnOnce() + Send + 'static>
Register a root directory or URI that the server has access to.
This method allows the server to announce to clients that it has access to specific file system roots or URIs. This is useful for resource handlers that need to expose filesystem access or other URI-based resources.
§Arguments
uri- The root URI to register (e.g.,file:///home/user/project)name- Optional human-readable name for the root
§Returns
An unregister function that can be called to remove the root registration.
§Examples
use pmcp::Server;
let server = Server::builder()
.name("file-server")
.version("1.0.0")
.build()?;
// Register a project root
let unregister = server.register_root(
"file:///home/user/project",
Some("My Project".to_string())
).await?;
// Later, unregister the root
unregister();Sourcepub async fn get_roots(&self) -> Vec<Root>
pub async fn get_roots(&self) -> Vec<Root>
Get the list of registered roots.
Returns a list of all currently registered root URIs and their associated names. Roots are directories or URIs that the server has announced access to.
§Returns
A vector of Root objects containing URI and optional name.
§Examples
use pmcp::Server;
let server = Server::builder()
.name("file-server")
.version("1.0.0")
.build()?;
// Register some roots
server.register_root("file:///home/user/project1", Some("Project 1".to_string())).await?;
server.register_root("file:///home/user/project2", None).await?;
// Get the list of roots
let roots = server.get_roots().await;
println!("Registered {} roots", roots.len());Sourcepub async fn subscribe_resource(
&self,
uri: String,
client_id: String,
) -> Result<()>
pub async fn subscribe_resource( &self, uri: String, client_id: String, ) -> Result<()>
Subscribe a client to resource updates.
This method allows the server to track which clients are interested in updates to specific resources. When a resource changes, the server can notify all subscribed clients.
§Arguments
uri- The resource URI to subscribe toclient_id- Identifier for the subscribing client
§Examples
use pmcp::Server;
let server = Server::builder()
.name("file-server")
.version("1.0.0")
.build()?;
// Subscribe client to resource updates
server.subscribe_resource(
"file:///project/file.txt".to_string(),
"client-123".to_string()
).await?;Sourcepub async fn cancel_request(
&self,
request_id: String,
reason: Option<String>,
) -> Result<()>
pub async fn cancel_request( &self, request_id: String, reason: Option<String>, ) -> Result<()>
Cancel a request that is currently being processed.
This method allows the server to cancel ongoing requests, which is useful for implementing request timeouts or client-requested cancellations.
§Arguments
request_id- The ID of the request to cancelreason- Optional reason for cancellation
§Examples
use pmcp::Server;
let server = Server::builder()
.name("cancel-server")
.version("1.0.0")
.build()?;
// Cancel a request
server.cancel_request(
"request-123".to_string(),
Some("User requested cancellation".to_string())
).await?;Sourcepub async fn unsubscribe_resource(
&self,
uri: String,
client_id: String,
) -> Result<()>
pub async fn unsubscribe_resource( &self, uri: String, client_id: String, ) -> Result<()>
Unsubscribe a client from resource updates.
This method removes a client’s subscription to a specific resource, so they will no longer receive notifications when that resource changes.
§Arguments
uri- The resource URI to unsubscribe fromclient_id- Identifier for the client to unsubscribe
§Examples
use pmcp::Server;
let server = Server::builder()
.name("file-server")
.version("1.0.0")
.build()?;
// Unsubscribe client from resource updates
server.unsubscribe_resource(
"file:///project/file.txt".to_string(),
"client-123".to_string()
).await?;