Skip to content
Open
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
222 changes: 94 additions & 128 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,11 @@ struct MyServer;

impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
ServerInfo::new(
ServerCapabilities::builder()
.enable_resources()
.build(),
..Default::default()
}
)
}

async fn list_resources(
Expand All @@ -254,8 +253,8 @@ impl ServerHandler for MyServer {
) -> Result<ListResourcesResult, McpError> {
Ok(ListResourcesResult {
resources: vec![
RawResource::new("file:///config.json", "config").no_annotation(),
RawResource::new("memo://insights", "insights").no_annotation(),
Resource::new("file:///config.json", "config"),
Resource::new("memo://insights", "insights"),
],
next_cursor: None,
meta: None,
Expand All @@ -268,12 +267,12 @@ impl ServerHandler for MyServer {
_context: RequestContext<RoleServer>,
) -> Result<ReadResourceResult, McpError> {
match request.uri.as_str() {
"file:///config.json" => Ok(ReadResourceResult {
contents: vec![ResourceContents::text(r#"{"key": "value"}"#, &request.uri)],
}),
"memo://insights" => Ok(ReadResourceResult {
contents: vec![ResourceContents::text("Analysis results...", &request.uri)],
}),
"file:///config.json" => Ok(ReadResourceResult::new(vec![
ResourceContents::text(r#"{"key": "value"}"#, &request.uri),
])),
"memo://insights" => Ok(ReadResourceResult::new(vec![
ResourceContents::text("Analysis results...", &request.uri),
])),
_ => Err(McpError::resource_not_found(
"resource_not_found",
Some(json!({ "uri": request.uri })),
Expand Down Expand Up @@ -304,10 +303,9 @@ use rmcp::model::{ReadResourceRequestParams};
let resources = client.list_all_resources().await?;

// Read a specific resource by URI
let result = client.read_resource(ReadResourceRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
let result = client.read_resource(
ReadResourceRequestParams::new("file:///config.json"),
).await?;

// List resource templates
let templates = client.list_all_resource_templates().await?;
Expand All @@ -322,9 +320,9 @@ Servers can notify clients when the resource list changes or when a specific res
context.peer.notify_resource_list_changed().await?;

// Notify that a specific resource was updated
context.peer.notify_resource_updated(ResourceUpdatedNotificationParam {
uri: "file:///config.json".into(),
}).await?;
context.peer.notify_resource_updated(
ResourceUpdatedNotificationParam::new("file:///config.json"),
).await?;
```

Clients handle these via `ClientHandler`:
Expand Down Expand Up @@ -397,7 +395,7 @@ impl MyServer {
#[prompt(name = "greeting", description = "A simple greeting")]
async fn greeting(&self) -> Vec<PromptMessage> {
vec![PromptMessage::new_text(
PromptMessageRole::User,
Role::User,
"Hello! How can you help me today?",
)]
}
Expand All @@ -411,25 +409,20 @@ impl MyServer {
let focus = args.focus_areas
.unwrap_or_else(|| vec!["correctness".into()]);

Ok(GetPromptResult {
description: Some(format!("Code review for {}", args.language)),
messages: vec![
PromptMessage::new_text(
PromptMessageRole::User,
format!("Review my {} code. Focus on: {}", args.language, focus.join(", ")),
),
],
})
Ok(GetPromptResult::new(vec![
PromptMessage::new_text(
Role::User,
format!("Review my {} code. Focus on: {}", args.language, focus.join(", ")),
),
])
.with_description(format!("Code review for {}", args.language)))
}
}

#[prompt_handler]
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_prompts().build(),
..Default::default()
}
ServerInfo::new(ServerCapabilities::builder().enable_prompts().build())
}
}
```
Expand Down Expand Up @@ -485,25 +478,22 @@ Access the client's sampling capability through `context.peer.create_message()`:
use rmcp::model::*;

// Inside a ServerHandler method (e.g., call_tool):
let response = context.peer.create_message(CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("Explain this error: connection refused")],
model_preferences: Some(ModelPreferences {
hints: Some(vec![ModelHint { name: Some("claude".into()) }]),
cost_priority: Some(0.3),
speed_priority: Some(0.8),
intelligence_priority: Some(0.7),
}),
system_prompt: Some("You are a helpful assistant.".into()),
include_context: Some(ContextInclusion::None),
temperature: Some(0.7),
max_tokens: 150,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
}).await?;
let response = context.peer.create_message(
CreateMessageRequestParams::new(
vec![SamplingMessage::user_text("Explain this error: connection refused")],
150,
)
.with_model_preferences(
ModelPreferences::new()
.with_hints(vec![ModelHint::new("claude")])
.with_cost_priority(0.3)
.with_speed_priority(0.8)
.with_intelligence_priority(0.7),
)
.with_system_prompt("You are a helpful assistant.")
.with_include_context(ContextInclusion::None)
.with_temperature(0.7),
).await?;

// Extract the response text
let text = response.message.content
Expand Down Expand Up @@ -531,11 +521,11 @@ impl ClientHandler for MyClient {
// Forward to your LLM, or return a mock response:
let response_text = call_your_llm(&params.messages).await;

Ok(CreateMessageResult {
message: SamplingMessage::assistant_text(response_text),
model: "my-model".into(),
stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.into()),
})
Ok(CreateMessageResult::new(
SamplingMessage::assistant_text(response_text),
"my-model".into(),
)
.with_stop_reason(CreateMessageResult::STOP_REASON_END_TURN))
}
}
```
Expand Down Expand Up @@ -593,14 +583,9 @@ impl ClientHandler for MyClient {
&self,
_context: RequestContext<RoleClient>,
) -> Result<ListRootsResult, ErrorData> {
Ok(ListRootsResult {
roots: vec![
Root {
uri: "file:///home/user/project".into(),
name: Some("My Project".into()),
},
],
})
Ok(ListRootsResult::new(vec![
Root::new("file:///home/user/project").with_name("My Project"),
]))
}
}
```
Expand Down Expand Up @@ -631,12 +616,11 @@ use rmcp::{ServerHandler, model::*, service::RequestContext};

impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
ServerInfo::new(
ServerCapabilities::builder()
.enable_logging()
.build(),
..Default::default()
}
)
}

// Client sets the minimum log level
Expand All @@ -651,14 +635,16 @@ impl ServerHandler for MyServer {
}

// Send a log message from any handler with access to the peer:
context.peer.notify_logging_message(LoggingMessageNotificationParam {
level: LoggingLevel::Info,
logger: Some("my-server".into()),
data: serde_json::json!({
"message": "Processing completed",
"items_processed": 42
}),
}).await?;
context.peer.notify_logging_message(
LoggingMessageNotificationParam::new(
LoggingLevel::Info,
serde_json::json!({
"message": "Processing completed",
"items_processed": 42
}),
)
.with_logger("my-server"),
).await?;
```

Available log levels (from least to most severe): `Debug`, `Info`, `Notice`, `Warning`, `Error`, `Critical`, `Alert`, `Emergency`.
Expand All @@ -683,10 +669,7 @@ impl ClientHandler for MyClient {
Clients can also set the server's log level:

```rust
client.set_level(SetLevelRequestParams {
level: LoggingLevel::Warning,
meta: None,
}).await?;
client.set_level(SetLevelRequestParams::new(LoggingLevel::Warning)).await?;
```

---
Expand All @@ -706,13 +689,12 @@ use rmcp::{ErrorData as McpError, ServerHandler, model::*, service::RequestConte

impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
ServerInfo::new(
ServerCapabilities::builder()
.enable_completions()
.enable_prompts()
.build(),
..Default::default()
}
)
}

async fn complete(
Expand Down Expand Up @@ -750,13 +732,9 @@ impl ServerHandler for MyServer {
.filter(|v| v.to_lowercase().contains(&request.argument.value.to_lowercase()))
.collect();

Ok(CompleteResult {
completion: CompletionInfo {
values: filtered,
total: None,
has_more: Some(false),
},
})
let completion = CompletionInfo::with_pagination(filtered, None, false)
.map_err(|e| McpError::internal_error(e, None))?;
Ok(CompleteResult::new(completion))
}
}
```
Expand All @@ -766,17 +744,10 @@ impl ServerHandler for MyServer {
```rust
use rmcp::model::*;

let result = client.complete(CompleteRequestParams {
meta: None,
r#ref: Reference::Prompt(PromptReference {
name: "sql_query".into(),
}),
argument: ArgumentInfo {
name: "operation".into(),
value: "SEL".into(),
},
context: None,
}).await?;
let result = client.complete(CompleteRequestParams::new(
Reference::for_prompt("sql_query"),
ArgumentInfo::new("operation", "SEL"),
)).await?;

// result.completion.values contains suggestions like ["SELECT"]
```
Expand All @@ -802,12 +773,14 @@ use rmcp::model::*;
for i in 0..total_items {
process_item(i).await;

context.peer.notify_progress(ProgressNotificationParam {
progress_token: ProgressToken(NumberOrString::Number(i as i64)),
progress: i as f64,
total: Some(total_items as f64),
message: Some(format!("Processing item {}/{}", i + 1, total_items)),
}).await?;
context.peer.notify_progress(
ProgressNotificationParam::new(
ProgressToken(NumberOrString::Number(i as i64)),
i as f64,
)
.with_total(total_items as f64)
.with_message(format!("Processing item {}/{}", i + 1, total_items)),
).await?;
}
```

Expand All @@ -817,10 +790,10 @@ Either side can cancel an in-progress request:

```rust
// Send a cancellation
context.peer.notify_cancelled(CancelledNotificationParam {
request_id: the_request_id,
reason: Some("User requested cancellation".into()),
}).await?;
context.peer.notify_cancelled(CancelledNotificationParam::new(
Some(the_request_id),
Some("User requested cancellation".into()),
)).await?;
```

Handle cancellation in `ServerHandler` or `ClientHandler`:
Expand Down Expand Up @@ -891,13 +864,12 @@ struct MyServer {

impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
ServerInfo::new(
ServerCapabilities::builder()
.enable_resources()
.enable_resources_subscribe()
.build(),
..Default::default()
}
)
}

async fn subscribe(
Expand All @@ -924,9 +896,9 @@ When a subscribed resource changes, notify the client:

```rust
// Check if the resource has subscribers, then notify
context.peer.notify_resource_updated(ResourceUpdatedNotificationParam {
uri: "file:///config.json".into(),
}).await?;
context.peer.notify_resource_updated(
ResourceUpdatedNotificationParam::new("file:///config.json"),
).await?;
```

### Client-side
Expand All @@ -935,16 +907,10 @@ context.peer.notify_resource_updated(ResourceUpdatedNotificationParam {
use rmcp::model::*;

// Subscribe to updates for a resource
client.subscribe(SubscribeRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
client.subscribe(SubscribeRequestParams::new("file:///config.json")).await?;

// Unsubscribe when no longer needed
client.unsubscribe(UnsubscribeRequestParams {
meta: None,
uri: "file:///config.json".into(),
}).await?;
client.unsubscribe(UnsubscribeRequestParams::new("file:///config.json")).await?;
```

Handle update notifications in `ClientHandler`:
Expand Down
Loading