You are reading the docs for rws 18.4 The latest release is 18.8. Read this page for the latest release.

MCP Resources

Expose readable data sources to AI agents via URI-addressed MCP resources.

Resources are named, URI-addressed data sources that AI agents can read. Unlike tools (which are invoked with arguments), resources are addressed by URI — the AI browses available resources via resources/list and fetches them with resources/read.

Registering a resource

use rust_web_server::mcp::{McpServer, McpContent};

let mcp = McpServer::new("my-server", "1.0")
    .resource(
        "rws:///config",        // URI (or URI template)
        "Server Config",        // human-readable name
        "Current server configuration as JSON",  // description
        |uri| {
            let config_json = r#"{"port":7878,"threads":4}"#;
            Ok(McpContent::json(config_json.to_string()))
        },
    );

URI templates

Use {variable} placeholders in the URI template to match any concrete URI that shares the same prefix up to the placeholder:

.resource(
    "file:///{path}",
    "Static File",
    "Read a static file by path",
    |uri| {
        // uri = "file:///public/index.html"
        let path = uri.strip_prefix("file:///").unwrap_or("");
        let content = std::fs::read_to_string(path)
            .map_err(|e| format!("Cannot read {path}: {e}"))?;
        Ok(McpContent::text(content))
    },
)

The matching rule: a template with a {variable} matches any URI that starts with the text before the {. A template without placeholders matches only the exact URI string.

URI template parsing

The handler receives the full concrete URI string — not a pre-parsed map of variable names to values. Extract variables from the URI string yourself using strip_prefix or similar.

Handler signature

Fn(&str) -> Result<McpContent, String>

The handler receives the concrete URI requested by the AI (e.g., "rws:///config" or "user://42"). Return Ok(McpContent) with the resource content, or Err(String) to signal that the resource could not be read.

McpContent for resources

use rust_web_server::mcp::McpContent;

// Plain text resource
McpContent::text("line1\nline2\nline3")

// JSON resource — mimeType set to application/json
McpContent::json(r#"{"key":"value"}"#)

// Binary resource — base64 data, sent as `blob` instead of `text` (18.0.0)
McpContent::blob(base64_encoded_png, "image/png")

Resource listing

The AI discovers available resources via resources/list (JSON-RPC method). Each resource is listed with its URI template, name, description, and mimeType: "text/plain". Register resources in the order you want them listed.

Resource reading

The AI reads a resource via resources/read (JSON-RPC method) by sending the concrete URI. The server matches it against registered templates and calls the first matching handler.

Example: expose server config as a resource

use rust_web_server::mcp::{McpServer, McpContent};
use std::env;

let mcp = McpServer::new("rws", "17")
    .resource(
        "rws:///config",
        "Server Config",
        "Live server configuration (port, threads, TLS, log format)",
        |_uri| {
            let port    = env::var("RWS_CONFIG_PORT").unwrap_or_else(|_| "7878".to_string());
            let threads = env::var("RWS_CONFIG_THREAD_COUNT").unwrap_or_else(|_| "4".to_string());
            let tls     = env::var("RWS_CONFIG_TLS_CERT_FILE").ok();
            let log_fmt = env::var("RWS_CONFIG_LOG_FORMAT").unwrap_or_else(|_| "combined".to_string());

            let json = format!(
                r#"{{"port":{port},"threads":{threads},"tls":{},"log_format":"{log_fmt}"}}"#,
                tls.map(|_| "true").unwrap_or("false"),
            );
            Ok(McpContent::json(json))
        },
    )
    .resource(
        "rws:///metrics",
        "Server Metrics",
        "Prometheus-format metrics snapshot",
        |_uri| {
            // Pull from global metrics — example only
            Ok(McpContent::text("# HELP rws_requests_total Total requests\n"))
        },
    );

Multiple resources

Chain .resource() calls — each registers a separate resource. Resources are matched in registration order:

McpServer::new("my-server", "1.0")
    .resource("config:///app",  "App Config",  "Application settings",  |_| Ok(McpContent::text("...")))
    .resource("config:///db",   "DB Config",   "Database settings",     |_| Ok(McpContent::text("...")))
    .resource("log:///{level}", "Log Stream",  "Recent log lines",      |uri| {
        let level = uri.strip_prefix("log:///").unwrap_or("info");
        Ok(McpContent::text(format!("Log level: {level}")))
    });

Edit this page on GitHub