McpServerBuilder

Struct McpServerBuilder 

Source
pub struct McpServerBuilder { /* private fields */ }
Expand description

Builder for MCP servers

Implementations§

Source§

impl McpServerBuilder

Source

pub fn new() -> Self

Create a new builder

Source

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

Sets the server name for identification

Source

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

Sets the server version string

Source

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

Sets the human-readable server title

Source

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

Sets usage instructions for MCP clients

Source

pub fn tool<T: McpTool + 'static>(self, tool: T) -> Self

Registers a tool that clients can execute

Source

pub fn tool_fn<F, T>(self, func: F) -> Self
where F: Fn() -> T, T: McpTool + 'static,

Register a function tool created with #[mcp_tool] macro

This method provides a more intuitive way to register function tools. The #[mcp_tool] macro generates a constructor function with the same name as your async function, so you can use the function name directly.

§Example
use turul_mcp_server::prelude::*;
use std::collections::HashMap;

// Manual tool implementation without derive macros
#[derive(Clone, Default)]
struct AddTool;

// Implement all required traits for ToolDefinition
impl turul_mcp_builders::traits::HasBaseMetadata for AddTool {
    fn name(&self) -> &str { "add" }
    fn title(&self) -> Option<&str> { Some("Add Numbers") }
}

impl turul_mcp_builders::traits::HasDescription for AddTool {
    fn description(&self) -> Option<&str> {
        Some("Add two numbers together")
    }
}

impl turul_mcp_builders::traits::HasInputSchema for AddTool {
    fn input_schema(&self) -> &turul_mcp_protocol::ToolSchema {
        use turul_mcp_protocol::schema::JsonSchema;
        static SCHEMA: std::sync::OnceLock<turul_mcp_protocol::ToolSchema> = std::sync::OnceLock::new();
        SCHEMA.get_or_init(|| {
            let mut props = HashMap::new();
            props.insert("a".to_string(), JsonSchema::number().with_description("First number"));
            props.insert("b".to_string(), JsonSchema::number().with_description("Second number"));
            turul_mcp_protocol::ToolSchema::object()
                .with_properties(props)
                .with_required(vec!["a".to_string(), "b".to_string()])
        })
    }
}

impl turul_mcp_builders::traits::HasOutputSchema for AddTool {
    fn output_schema(&self) -> Option<&turul_mcp_protocol::ToolSchema> { None }
}

impl turul_mcp_builders::traits::HasAnnotations for AddTool {
    fn annotations(&self) -> Option<&turul_mcp_protocol::tools::ToolAnnotations> { None }
}

impl turul_mcp_builders::traits::HasToolMeta for AddTool {
    fn tool_meta(&self) -> Option<&HashMap<String, serde_json::Value>> { None }
}

#[async_trait]
impl McpTool for AddTool {
    async fn call(&self, args: serde_json::Value, _session: Option<SessionContext>)
        -> McpResult<turul_mcp_protocol::tools::CallToolResult> {
        let a = args.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
        let b = args.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
        let result = a + b;

        Ok(turul_mcp_protocol::tools::CallToolResult::success(vec![
            turul_mcp_protocol::ToolResult::text(format!("{} + {} = {}", a, b, result))
        ]))
    }
}

let server = McpServer::builder()
    .name("math-server")
    .tool_fn(|| AddTool::default()) // Function returns working tool instance
    .build()?;
Source

pub fn tools<T: McpTool + 'static, I: IntoIterator<Item = T>>( self, tools: I, ) -> Self

Registers multiple tools in a batch

Source

pub fn middleware(self, middleware: Arc<dyn McpMiddleware>) -> Self

Add middleware to the request/response processing chain

This method is additive - each call adds a new middleware to the stack. Middleware execute in the order they are registered (FIFO):

  • Before dispatch: First registered executes first (FIFO order)
  • After dispatch: First registered executes last (LIFO/reverse order)

Multiple middleware can be composed by calling this method multiple times. Middleware works identically across all transports (HTTP, Lambda, etc.).

§Behavior with Other Builder Methods
  • .test_mode(): Does NOT affect middleware - middleware always executes
  • Non-HTTP builds: Middleware is available but requires manual wiring
§Examples
§Single Middleware
use turul_mcp_server::prelude::*;
use async_trait::async_trait;
use std::sync::Arc;

struct LoggingMiddleware;

#[async_trait]
impl McpMiddleware for LoggingMiddleware {
    async fn before_dispatch(
        &self,
        ctx: &mut RequestContext<'_>,
        _session: Option<&dyn turul_mcp_session_storage::SessionView>,
        _injection: &mut SessionInjection,
    ) -> Result<(), MiddlewareError> {
        println!("Request: {}", ctx.method());
        Ok(())
    }
}

let server = McpServer::builder()
    .name("my-server")
    .middleware(Arc::new(LoggingMiddleware))
    .build()?;
§Multiple Middleware Composition
use turul_mcp_server::prelude::*;
use async_trait::async_trait;
use std::sync::Arc;
use serde_json::json;

// Execution order:
// Before dispatch: Auth → Logging → RateLimit
// After dispatch: RateLimit → Logging → Auth (reverse)
let server = McpServer::builder()
    .name("my-server")
    .middleware(Arc::new(AuthMiddleware))      // 1st before, 3rd after
    .middleware(Arc::new(LoggingMiddleware))   // 2nd before, 2nd after
    .middleware(Arc::new(RateLimitMiddleware)) // 3rd before, 1st after
    .build()?;
Source

pub fn resource<R: McpResource + 'static>(self, resource: R) -> Self

Register a resource with the server

Automatically detects if the resource URI contains template variables (e.g., {ticker}, {id}) and registers it as either a static resource or template resource accordingly. This eliminates the need to manually call .template_resource() for templated URIs.

§Examples
use turul_mcp_server::prelude::*;
use std::collections::HashMap;

// Manual resource implementation without derive macros
#[derive(Clone)]
struct ConfigResource {
    data: String,
}

// Implement all required traits for ResourceDefinition
impl turul_mcp_builders::traits::HasResourceMetadata for ConfigResource {
    fn name(&self) -> &str { "config" }
    fn title(&self) -> Option<&str> { Some("Configuration") }
}

impl turul_mcp_builders::traits::HasResourceDescription for ConfigResource {
    fn description(&self) -> Option<&str> {
        Some("Application configuration file")
    }
}

impl turul_mcp_builders::traits::HasResourceUri for ConfigResource {
    fn uri(&self) -> &str { "file:///config.json" }
}

impl turul_mcp_builders::traits::HasResourceMimeType for ConfigResource {
    fn mime_type(&self) -> Option<&str> { Some("application/json") }
}

impl turul_mcp_builders::traits::HasResourceSize for ConfigResource {
    fn size(&self) -> Option<u64> { Some(self.data.len() as u64) }
}

impl turul_mcp_builders::traits::HasResourceAnnotations for ConfigResource {
    fn annotations(&self) -> Option<&turul_mcp_protocol::meta::Annotations> { None }
}

impl turul_mcp_builders::traits::HasResourceMeta for ConfigResource {
    fn resource_meta(&self) -> Option<&HashMap<String, serde_json::Value>> { None }
}

#[async_trait]
impl McpResource for ConfigResource {
    async fn read(&self, _params: Option<serde_json::Value>, _session: Option<&SessionContext>)
        -> McpResult<Vec<turul_mcp_protocol::ResourceContent>> {
        Ok(vec![turul_mcp_protocol::ResourceContent::text(
            self.uri(),
            &self.data
        )])
    }
}

let config = ConfigResource {
    data: r#"{"debug": true, "port": 8080}"#.to_string(),
};

let server = McpServer::builder()
    .name("resource-server")
    .resource(config) // Working resource with actual data
    .build()?;
Source

pub fn resource_fn<F, R>(self, func: F) -> Self
where F: Fn() -> R, R: McpResource + 'static,

Register a function resource created with #[mcp_resource] macro

This method provides a more intuitive way to register function resources. The #[mcp_resource] macro generates a constructor function with the same name as your async function, so you can use the function name directly.

§Example
use turul_mcp_server::prelude::*;
use std::collections::HashMap;

// Manual resource implementation without derive macros
#[derive(Clone)]
struct DataResource {
    content: String,
}

// Implement all required traits for ResourceDefinition (same as resource() example)
impl turul_mcp_builders::traits::HasResourceMetadata for DataResource {
    fn name(&self) -> &str { "data" }
    fn title(&self) -> Option<&str> { Some("Data File") }
}

impl turul_mcp_builders::traits::HasResourceDescription for DataResource {
    fn description(&self) -> Option<&str> { Some("Sample data file") }
}

impl turul_mcp_builders::traits::HasResourceUri for DataResource {
    fn uri(&self) -> &str { "file:///data/sample.json" }
}

impl turul_mcp_builders::traits::HasResourceMimeType for DataResource {
    fn mime_type(&self) -> Option<&str> { Some("application/json") }
}

impl turul_mcp_builders::traits::HasResourceSize for DataResource {
    fn size(&self) -> Option<u64> { Some(self.content.len() as u64) }
}

impl turul_mcp_builders::traits::HasResourceAnnotations for DataResource {
    fn annotations(&self) -> Option<&turul_mcp_protocol::meta::Annotations> { None }
}

impl turul_mcp_builders::traits::HasResourceMeta for DataResource {
    fn resource_meta(&self) -> Option<&HashMap<String, serde_json::Value>> { None }
}

#[async_trait]
impl McpResource for DataResource {
    async fn read(&self, _params: Option<serde_json::Value>, _session: Option<&SessionContext>)
        -> McpResult<Vec<turul_mcp_protocol::ResourceContent>> {
        Ok(vec![turul_mcp_protocol::ResourceContent::text(
            self.uri(),
            &self.content
        )])
    }
}

let server = McpServer::builder()
    .name("data-server")
    .resource_fn(|| DataResource {
        content: r#"{"items": [1, 2, 3]}"#.to_string()
    }) // Function returns working resource instance
    .build()?;
Source

pub fn resources<R: McpResource + 'static, I: IntoIterator<Item = R>>( self, resources: I, ) -> Self

Register multiple resources

Source

pub fn template_resource<R: McpResource + 'static>( self, template: UriTemplate, resource: R, ) -> Self

Register a resource with explicit URI template support

Note: This method is now optional. The .resource() method automatically detects template URIs and handles them appropriately. Use this method only when you need explicit control over template parsing or want to add custom validators.

§Example
use turul_mcp_server::prelude::*;
use turul_mcp_server::uri_template::{UriTemplate, VariableValidator};
use std::collections::HashMap;

// Manual template resource implementation
#[derive(Clone)]
struct TemplateResource {
    base_path: String,
}

// Implement all required traits for ResourceDefinition
impl turul_mcp_builders::traits::HasResourceMetadata for TemplateResource {
    fn name(&self) -> &str { "template-data" }
    fn title(&self) -> Option<&str> { Some("Template Data") }
}

impl turul_mcp_builders::traits::HasResourceDescription for TemplateResource {
    fn description(&self) -> Option<&str> { Some("Template-based data resource") }
}

impl turul_mcp_builders::traits::HasResourceUri for TemplateResource {
    fn uri(&self) -> &str { "file:///data/{id}.json" }
}

impl turul_mcp_builders::traits::HasResourceMimeType for TemplateResource {
    fn mime_type(&self) -> Option<&str> { Some("application/json") }
}

impl turul_mcp_builders::traits::HasResourceSize for TemplateResource {
    fn size(&self) -> Option<u64> { None } // Size varies by template
}

impl turul_mcp_builders::traits::HasResourceAnnotations for TemplateResource {
    fn annotations(&self) -> Option<&turul_mcp_protocol::meta::Annotations> { None }
}

impl turul_mcp_builders::traits::HasResourceMeta for TemplateResource {
    fn resource_meta(&self) -> Option<&HashMap<String, serde_json::Value>> { None }
}

#[async_trait]
impl McpResource for TemplateResource {
    async fn read(&self, params: Option<serde_json::Value>, _session: Option<&SessionContext>)
        -> McpResult<Vec<turul_mcp_protocol::ResourceContent>> {
        let id = params
            .as_ref()
            .and_then(|p| p.get("id"))
            .and_then(|v| v.as_str())
            .unwrap_or("default");

        let content = format!(r#"{{"id": "{}", "data": "sample content for {}"}}"#, id, id);
        Ok(vec![turul_mcp_protocol::ResourceContent::text(
            &format!("file:///data/{}.json", id),
            &content
        )])
    }
}

let template = UriTemplate::new("file:///data/{id}.json")?
    .with_validator("id", VariableValidator::user_id());

let resource = TemplateResource {
    base_path: "/data".to_string(),
};

let server = McpServer::builder()
    .name("template-server")
    .template_resource(template, resource) // Working template resource
    .build()?;
Source

pub fn prompt<P: McpPrompt + 'static>(self, prompt: P) -> Self

Registers a prompt template for conversation generation

Source

pub fn prompts<P: McpPrompt + 'static, I: IntoIterator<Item = P>>( self, prompts: I, ) -> Self

Register multiple prompts

Source

pub fn elicitation<E: McpElicitation + 'static>(self, elicitation: E) -> Self

Register an elicitation provider with the server

Source

pub fn elicitations<E: McpElicitation + 'static, I: IntoIterator<Item = E>>( self, elicitations: I, ) -> Self

Register multiple elicitation providers

Source

pub fn sampling_provider<S: McpSampling + 'static>(self, sampling: S) -> Self

Register a sampling provider with the server

Source

pub fn sampling_providers<S: McpSampling + 'static, I: IntoIterator<Item = S>>( self, sampling: I, ) -> Self

Register multiple sampling providers

Source

pub fn completion_provider<C: McpCompletion + 'static>( self, completion: C, ) -> Self

Register a completion provider with the server

Source

pub fn completion_providers<C: McpCompletion + 'static, I: IntoIterator<Item = C>>( self, completions: I, ) -> Self

Register multiple completion providers

Source

pub fn logger<L: McpLogger + 'static>(self, logger: L) -> Self

Register a logger with the server

Source

pub fn loggers<L: McpLogger + 'static, I: IntoIterator<Item = L>>( self, loggers: I, ) -> Self

Register multiple loggers

Source

pub fn root_provider<R: McpRoot + 'static>(self, root: R) -> Self

Register a root provider with the server

Source

pub fn root_providers<R: McpRoot + 'static, I: IntoIterator<Item = R>>( self, roots: I, ) -> Self

Register multiple root providers

Source

pub fn notification_provider<N: McpNotification + 'static>( self, notification: N, ) -> Self

Register a notification provider with the server

Source

pub fn notification_providers<N: McpNotification + 'static, I: IntoIterator<Item = N>>( self, notifications: I, ) -> Self

Register multiple notification providers

Source

pub fn sampler<S: McpSampling + 'static>(self, sampling: S) -> Self

Register a sampler - convenient alias for sampling_provider Automatically uses “sampling/createMessage” method

Source

pub fn completer<C: McpCompletion + 'static>(self, completion: C) -> Self

Register a completer - convenient alias for completion_provider Automatically uses “completion/complete” method

Source

pub fn notification_type<N: McpNotification + 'static + Default>(self) -> Self

Register a notification by type - type determines method automatically This enables the .notification::<T>() pattern from universal-turul-mcp-server

Source

pub fn handler<H: McpHandler + 'static>(self, handler: H) -> Self

Register a handler with the server

Source

pub fn handlers<H: McpHandler + 'static, I: IntoIterator<Item = H>>( self, handlers: I, ) -> Self

Register multiple handlers

Source

pub fn with_completion(self) -> Self

Add completion support

Source

pub fn with_prompts(self) -> Self

Add prompts support

Source

pub fn with_resources(self) -> Self

Add resources support

Note: This method is now optional. The framework automatically calls this when resources are registered via .resource() or .template_resource(). You only need to call this explicitly if you want to enable resource capabilities without registering any resources.

Source

pub fn with_logging(self) -> Self

Add logging support

Source

pub fn with_roots(self) -> Self

Add roots support

Source

pub fn root(self, root: Root) -> Self

Add a single root directory

Source

pub fn with_sampling(self) -> Self

Add sampling support

Source

pub fn with_elicitation(self) -> Self

Add elicitation support with default mock provider

Source

pub fn with_elicitation_provider<P: ElicitationProvider + 'static>( self, provider: P, ) -> Self

Add elicitation support with custom provider

Source

pub fn with_notifications(self) -> Self

Add notifications support

Source

pub fn session_timeout_minutes(self, minutes: u64) -> Self

Configure session timeout (in minutes, default: 30)

Source

pub fn session_cleanup_interval_seconds(self, seconds: u64) -> Self

Configure session cleanup interval (in seconds, default: 60)

Source

pub fn strict_lifecycle(self, strict: bool) -> Self

Enable strict MCP lifecycle enforcement

When enabled, the server will reject all operations (tools, resources, etc.) until the client sends notifications/initialized after receiving the initialize response.

Default: false (lenient mode) - for compatibility with existing clients Production: consider true - for strict MCP spec compliance

§Example
use turul_mcp_server::McpServer;

let server = McpServer::builder()
    .name("strict-server")
    .version("1.0.0")
    .strict_lifecycle(true)  // Enable strict enforcement
    .build()?;
Source

pub fn with_strict_lifecycle(self) -> Self

Enable strict MCP lifecycle enforcement (convenience method)

Equivalent to .strict_lifecycle(true). Enables strict enforcement where all operations are rejected until notifications/initialized is received.

Source

pub fn test_mode(self) -> Self

Enable test mode - disables security middleware for test servers

In test mode, ResourcesReadHandler is created without security middleware, allowing custom URI schemes for testing (binary://, memory://, error://, etc.). Production servers should NOT use test mode as it bypasses security controls.

§Example
use turul_mcp_server::McpServer;

let server = McpServer::builder()
    .name("test-server")
    .version("1.0.0")
    .test_mode()  // Disable security for testing
    .with_resources()
    .build()?;
Source

pub fn with_long_sessions(self) -> Self

Configure sessions with recommended defaults for long-running sessions

Source

pub fn with_short_sessions(self) -> Self

Configure sessions with recommended defaults for short-lived sessions

Source

pub fn with_session_storage<S: SessionStorage<Error = SessionStorageError> + 'static>( self, storage: Arc<S>, ) -> Self

Configure session storage backend (defaults to InMemory if not specified)

Source

pub fn bind_address(self, addr: SocketAddr) -> Self

Set HTTP bind address (requires “http” feature)

Source

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

Set MCP endpoint path (requires “http” feature)

Source

pub fn cors(self, enable: bool) -> Self

Enable/disable CORS (requires “http” feature)

Source

pub fn sse(self, enable: bool) -> Self

Enable/disable SSE (requires “sse” feature)

Source

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

Build the MCP server

Trait Implementations§

Source§

impl Default for McpServerBuilder

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<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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
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 = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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
Source§

impl<T> ErasedDestructor for T
where T: 'static,