Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/mcp-core/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ pub enum ToolError {
NotFound(String),
}

#[derive(Error, Debug)]
pub enum ResourceError {
#[error("Execution failed: {0}")]
ExecutionError(String),
#[error("Resource not found: {0}")]
NotFound(String),
}

pub type Result<T> = std::result::Result<T, ToolError>;

/// Trait for implementing MCP tools
Expand Down
7 changes: 7 additions & 0 deletions crates/mcp-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Test with MCP Inspector

```bash
npx @modelcontextprotocol/inspector cargo run -p mcp-server
```

Then visit the Inspector in the browser window and test the different endpoints.
16 changes: 16 additions & 0 deletions crates/mcp-server/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub enum RouterError {
#[error("Tool not found: {0}")]
ToolNotFound(String),

#[error("Resource not found: {0}")]
ResourceNotFound(String),
}

impl From<RouterError> for mcp_core::protocol::ErrorData {
Expand All @@ -75,6 +77,20 @@ impl From<RouterError> for mcp_core::protocol::ErrorData {
message: msg,
data: None,
},
RouterError::ResourceNotFound(msg) => ErrorData {
code: INVALID_REQUEST,
message: msg,
data: None,
},
}
}
}

impl From<mcp_core::handler::ResourceError> for RouterError {
fn from(err: mcp_core::handler::ResourceError) -> Self {
match err {
mcp_core::handler::ResourceError::NotFound(msg) => RouterError::ResourceNotFound(msg),
_ => RouterError::Internal("Unknown resource error".to_string()),
}
}
}
32 changes: 32 additions & 0 deletions crates/mcp-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use anyhow::Result;
use mcp_core::handler::ResourceError;
use mcp_core::{
handler::ToolError,
tool::Tool,
protocol::ServerCapabilities,
resource::Resource,
};
use mcp_server::{Router, Server, ByteTransport};
use mcp_server::router::{RouterService, CapabilitiesBuilder};
Expand Down Expand Up @@ -104,6 +106,36 @@ impl Router for CounterRouter {
}
})
}

fn list_resources(&self) -> Vec<Resource> {
vec![
Resource::new(
"memo://insights".to_string(),
Some("text/plain".to_string()),
Some("memo-resource".to_string())
).unwrap()
]
}

fn read_resource(
&self,
uri: &str,
) -> Pin<Box<dyn Future<Output = Result<String, ResourceError>> + Send + 'static>> {
let uri = uri.to_string();
Box::pin(async move {
match uri.as_str() {
"memo://insights" => {
let memo =
"Business Intelligence Memo\n\nAnalysis has revealed 5 key insights ...";
Ok(memo.to_string())
}
_ => Err(ResourceError::NotFound(format!(
"Resource {} not found",
uri
))),
}
})
}
}

#[tokio::main]
Expand Down
60 changes: 54 additions & 6 deletions crates/mcp-server/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ use std::{
use tokio::time::sleep;

use mcp_core::{
handler::ToolError,
protocol::{
JsonRpcRequest, JsonRpcResponse, ServerCapabilities, InitializeResult, Implementation,
PromptsCapability, ResourcesCapability, ToolsCapability, ListToolsResult, CallToolResult,
},
content::Content,
content::Content, handler::{ResourceError, ToolError}, protocol::{
CallToolResult, Implementation, InitializeResult, JsonRpcRequest, JsonRpcResponse, ListResourcesResult, ListToolsResult, PromptsCapability, ReadResourceResult, ResourcesCapability, ServerCapabilities, ToolsCapability
}, ResourceContents
};
use tower_service::Service;
use serde_json::Value;
Expand Down Expand Up @@ -76,6 +73,8 @@ pub trait Router: Send + Sync + 'static {
fn capabilities(&self) -> ServerCapabilities;
fn list_tools(&self) -> Vec<mcp_core::tool::Tool>;
fn call_tool(&self, tool_name: &str, arguments: Value) -> Pin<Box<dyn Future<Output = Result<Value, ToolError>> + Send + 'static>>;
fn list_resources(&self) -> Vec<mcp_core::resource::Resource>;
fn read_resource(&self, uri: &str) -> Pin<Box<dyn Future<Output = Result<String, ResourceError>> + Send + 'static>>;

// Helper method to create base response
fn create_response(&self, id: Option<u64>) -> JsonRpcResponse {
Expand Down Expand Up @@ -149,6 +148,53 @@ pub trait Router: Send + Sync + 'static {
Ok(response)
}
}

fn handle_resources_list(&self, req: JsonRpcRequest) -> impl Future<Output = Result<JsonRpcResponse, RouterError>> + Send {
async move {
let resources = self.list_resources();

let result = ListResourcesResult { resources };
let mut response = self.create_response(req.id);
response.result = Some(serde_json::to_value(result)
.map_err(|e| RouterError::Internal(format!("JSON serialization error: {}", e)))?);

Ok(response)
}
}

fn handle_resources_read(
&self,
req: JsonRpcRequest,
) -> impl Future<Output = Result<JsonRpcResponse, RouterError>> + Send {
async move {
let params = req
.params
.ok_or_else(|| RouterError::InvalidParams("Missing parameters".into()))?;

let uri = params
.get("uri")
.and_then(Value::as_str)
.ok_or_else(|| RouterError::InvalidParams("Missing resource URI".into()))?;

let contents = self.read_resource(uri).await.map_err(RouterError::from)?;

let result = ReadResourceResult {
contents: vec![ResourceContents::TextResourceContents {
uri: uri.to_string(),
mime_type: Some("text/plain".to_string()),
text: contents,
}],
};

let mut response = self.create_response(req.id);
response.result =
Some(serde_json::to_value(result).map_err(|e| {
RouterError::Internal(format!("JSON serialization error: {}", e))
})?);

Ok(response)
}
}
}

// A wrapper type to implement the Service trait locally
Expand Down Expand Up @@ -180,6 +226,8 @@ where
"initialize" => this.handle_initialize(req).await,
"tools/list" => this.handle_tools_list(req).await,
"tools/call" => this.handle_tools_call(req).await,
"resources/list" => this.handle_resources_list(req).await,
"resources/read" => this.handle_resources_read(req).await,
_ => {
let mut response = this.create_response(req.id);
response.error = Some(RouterError::MethodNotFound(req.method).into());
Expand Down