Skip to main content

error_handling/
error_handling.rs

1//! Example demonstrating proper error handling in MCP server
2//!
3//! This example shows how the server properly returns JSON-RPC errors
4//! for various error conditions like method not found, invalid params, etc.
5
6use async_trait::async_trait;
7use std::collections::HashMap;
8use tenx_mcp::{
9    error::{MCPError, Result},
10    schema::*,
11    server::{MCPServer, ToolHandler},
12    transport::{StdioTransport, Transport},
13};
14use tracing::info;
15
16/// A tool that always fails to demonstrate error handling
17struct FailingTool;
18
19#[async_trait]
20impl ToolHandler for FailingTool {
21    fn metadata(&self) -> Tool {
22        Tool {
23            name: "always_fail".to_string(),
24            description: Some("A tool that always fails for testing".to_string()),
25            input_schema: ToolInputSchema {
26                schema_type: "object".to_string(),
27                properties: None,
28                required: None,
29            },
30            annotations: None,
31        }
32    }
33
34    async fn execute(&self, _arguments: Option<serde_json::Value>) -> Result<Vec<Content>> {
35        Err(MCPError::tool_execution_failed(
36            "always_fail",
37            "This tool always fails for testing purposes",
38        ))
39    }
40}
41
42/// A tool that requires specific parameters
43struct StrictTool;
44
45#[async_trait]
46impl ToolHandler for StrictTool {
47    fn metadata(&self) -> Tool {
48        Tool {
49            name: "strict_tool".to_string(),
50            description: Some("A tool that requires specific parameters".to_string()),
51            input_schema: ToolInputSchema {
52                schema_type: "object".to_string(),
53                properties: Some({
54                    let mut props = HashMap::new();
55                    props.insert(
56                        "required_field".to_string(),
57                        serde_json::json!({
58                            "type": "string",
59                            "description": "This field is required"
60                        }),
61                    );
62                    props
63                }),
64                required: Some(vec!["required_field".to_string()]),
65            },
66            annotations: None,
67        }
68    }
69
70    async fn execute(&self, arguments: Option<serde_json::Value>) -> Result<Vec<Content>> {
71        let args = arguments
72            .ok_or_else(|| MCPError::invalid_params("strict_tool", "Missing arguments object"))?;
73
74        let required_field = args
75            .get("required_field")
76            .and_then(|v| v.as_str())
77            .ok_or_else(|| {
78                MCPError::invalid_params(
79                    "strict_tool",
80                    "Missing or invalid 'required_field' parameter",
81                )
82            })?;
83
84        Ok(vec![Content::Text(TextContent {
85            text: format!("Received: {required_field}"),
86            annotations: None,
87        })])
88    }
89}
90
91#[tokio::main]
92async fn main() -> Result<()> {
93    // Initialize logging
94    tracing_subscriber::fmt().with_target(false).init();
95
96    // Create server with error-prone tools
97    let mut server = MCPServer::new("error-example-server".to_string(), "0.1.0".to_string())
98        .with_capabilities(ServerCapabilities {
99            tools: Some(ToolsCapability {
100                list_changed: Some(true),
101            }),
102            ..Default::default()
103        });
104
105    // Register tools
106    server.register_tool(Box::new(FailingTool)).await;
107    server.register_tool(Box::new(StrictTool)).await;
108
109    info!("Starting MCP server with error handling examples...");
110    info!("Try these requests to see error handling:");
111    info!(
112        "1. Call a non-existent method: {{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"unknown_method\"}}"
113    );
114    info!(
115        "2. Call failing tool: {{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{{\"name\":\"always_fail\"}}}}"
116    );
117    info!(
118        "3. Call strict tool without params: {{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{{\"name\":\"strict_tool\"}}}}"
119    );
120
121    // Create and run transport
122    let transport: Box<dyn Transport> = Box::new(StdioTransport::new());
123    server.serve(transport).await
124}