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
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "mai

Start a client in one line:

```rust
```rust, ignore
use rmcp::{ServiceExt, transport::TokioChildProcess};
use tokio::process::Command;

Expand All @@ -37,7 +37,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
```

#### 1. Build a transport
<details>
<summary>1. Build a transport</summary>

```rust, ignore
use tokio::io::{stdin, stdout};
Expand All @@ -58,48 +59,59 @@ For server, the sink item is [`ServerJsonRpcMessage`](crate::model::ServerJsonRp
4. A tuple of [`tokio::io::AsyncRead`] `R `and [`tokio::io::AsyncWrite`] `W`: `(R, W)`.

For example, you can see how we build a transport through TCP stream or http upgrade so easily. [examples](examples/README.md)
</details>

#### 2. Build a service
<details>
<summary>2. Build a service</summary>

You can easily build a service by using [`ServerHandler`](crates/rmcp/src/handler/server.rs) or [`ClientHandler`](crates/rmcp/src/handler/client.rs).

```rust, ignore
let service = common::counter::Counter::new();
```
</details>

#### 3. Serve them together
<details>
<summary>3. Serve them together</summary>

```rust, ignore
// this call will finish the initialization process
let server = service.serve(transport).await?;
```
</details>

#### 4. Interact with the server
<details>
<summary>4. Interact with the server</summary>

Once the server is initialized, you can send requests or notifications:

```rust, ignore
// request
// request
let roots = server.list_roots().await?;

// or send notification
server.notify_cancelled(...).await?;
```
</details>

#### 5. Waiting for service shutdown
<details>
<summary>5. Waiting for service shutdown</summary>

```rust, ignore
let quit_reason = server.waiting().await?;
// or cancel it
let quit_reason = server.cancel().await?;
```
</details>

### Use macros to declaring tool

Use `toolbox` and `tool` macros to create tool quickly.

Check this [file](examples/servers/src/common/calculator.rs).
<details>
<summary>Example: Calculator Tool</summary>

Check this [file](examples/servers/src/common/calculator.rs).
```rust, ignore
use rmcp::{ServerHandler, model::ServerInfo, schemars, tool};

Expand Down Expand Up @@ -150,19 +162,19 @@ impl ServerHandler for Calculator {
}
}
}

```


The only thing you should do is to make the function's return type implement `IntoCallToolResult`.

And you can just implement `IntoContents`, and the return value will be marked as success automatically.

If you return a type of `Result<T, E>` where `T` and `E` both implemented `IntoContents`, it's also OK.
</details>

### Manage Multi Services

For many cases you need to manage several service in a collection, you can call `into_dyn` to convert services into the same type.

```rust, ignore
let service = service.into_dyn();
```
Expand All @@ -177,7 +189,7 @@ See [examples](examples/README.md)
- `server`: use server side sdk
- `macros`: macros default

#### Transports
### Transports

- `transport-io`: Server stdio transport
- `transport-sse-server`: Server SSE transport
Expand All @@ -189,6 +201,8 @@ See [examples](examples/README.md)
- [MCP Specification](https://spec.modelcontextprotocol.io/specification/2024-11-05/)
- [Schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/2024-11-05/schema.ts)

## Development with Dev Container
## Related Projects
- [containerd-mcp-server](https://github.com/modelcontextprotocol/containerd-mcp-server) - A containerd-based MCP server implementation

## Development with Dev Container
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
# Integration

- [Rig](examples/rig-integration) A stream chatbot with rig
- [Simple Chat Client](examples/simple-chat-client) A simple chat client implementation using the Model Context Protocol (MCP) SDK.

# WASI

Expand Down
20 changes: 20 additions & 0 deletions examples/simple-chat-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "simple-chat-client"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.11", features = ["json"] }
anyhow = "1.0"
thiserror = "1.0"
async-trait = "0.1"
futures = "0.3"
toml = "0.8"
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", features = [
"client",
"transport-child-process",
"transport-sse",
], no-default-features = true }
15 changes: 15 additions & 0 deletions examples/simple-chat-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Simple Chat Client

A simple chat client implementation using the Model Context Protocol (MCP) SDK. It just a example for developers to understand how to use the MCP SDK. This example use the easiest way to start a MCP server, and call the tool directly. No need embedding or complex third library or function call(because some models can't support function call).Just add tool in system prompt, and the client will call the tool automatically.


## Config
the config file is in `src/config.toml`. you can change the config to your own.Move the config file to `/etc/simple-chat-client/config.toml` for system-wide configuration.

## Usage

After configuring the config file, you can run the example:
```bash
cargo run --bin simple-chat
```

84 changes: 84 additions & 0 deletions examples/simple-chat-client/src/bin/simple_chat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::sync::Arc;

use anyhow::Result;
use simple_chat_client::{
chat::ChatSession,
client::OpenAIClient,
config::Config,
tool::{Tool, ToolSet, get_mcp_tools},
};

//default config path
const DEFAULT_CONFIG_PATH: &str = "/etc/simple-chat-client/config.toml";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for an example app, should use ./config.toml as to not need escalated privileges to read from /etc

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thks, I 'd like to add a arg, to point the config path.


#[tokio::main]
async fn main() -> Result<()> {
// load config
let config = Config::load(DEFAULT_CONFIG_PATH).await?;

// create openai client
let api_key = config
.openai_key
.clone()
.unwrap_or_else(|| std::env::var("OPENAI_API_KEY").expect("need set api key"));
let url = config.chat_url.clone();
println!("url is {:?}", url);
let openai_client = Arc::new(OpenAIClient::new(api_key, url));

// create tool set
let mut tool_set = ToolSet::default();

// load mcp
if config.mcp.is_some() {
let mcp_clients = config.create_mcp_clients().await?;

for (name, client) in mcp_clients {
println!("loading mcp tools: {}", name);
let server = client.peer().clone();
let tools = get_mcp_tools(server).await?;

for tool in tools {
println!("adding tool: {}", tool.name());
tool_set.add_tool(tool);
}
}
}

// create chat session
let mut session = ChatSession::new(
openai_client,
tool_set,
config
.model_name
.unwrap_or_else(|| "gpt-4o-mini".to_string()),
);

// build system prompt with tool info
let mut system_prompt =
"you are a assistant, you can help user to complete various tasks. you have the following tools to use:\n".to_string();

// add tool info to system prompt
for tool in session.get_tools() {
system_prompt.push_str(&format!(
"\ntool name: {}\ndescription: {}\nparameters: {}\n",
tool.name(),
tool.description(),
serde_json::to_string_pretty(&tool.parameters()).unwrap_or_default()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe have an example of error handling instead of hiding serialization issues?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right

));
}

// add tool call format guidance
system_prompt.push_str(
"\nif you need to call tool, please use the following format:\n\
Tool: <tool name>\n\
Inputs: <inputs>\n",
);

// add system prompt
session.add_system_prompt(system_prompt);

// start chat
session.chat().await?;

Ok(())
}
Loading
Loading