Skip to content

Commit 9e4dd6b

Browse files
committed
examples: add a simple chat client for example
1. optimize the readme in root 2. add example Signed-off-by: jokemanfire <[email protected]>
1 parent 31da5ff commit 9e4dd6b

File tree

13 files changed

+725
-12
lines changed

13 files changed

+725
-12
lines changed

README.md

+26-12
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "mai
2424

2525
Start a client in one line:
2626

27-
```rust
27+
```rust, ignore
2828
use rmcp::{ServiceExt, transport::TokioChildProcess};
2929
use tokio::process::Command;
3030
@@ -37,7 +37,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3737
}
3838
```
3939

40-
#### 1. Build a transport
40+
<details>
41+
<summary>1. Build a transport</summary>
4142

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

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

62-
#### 2. Build a service
64+
<details>
65+
<summary>2. Build a service</summary>
6366

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

6669
```rust, ignore
6770
let service = common::counter::Counter::new();
6871
```
72+
</details>
6973

70-
#### 3. Serve them together
74+
<details>
75+
<summary>3. Serve them together</summary>
7176

7277
```rust, ignore
7378
// this call will finish the initialization process
7479
let server = service.serve(transport).await?;
7580
```
81+
</details>
7682

77-
#### 4. Interact with the server
83+
<details>
84+
<summary>4. Interact with the server</summary>
7885

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

8188
```rust, ignore
82-
// request
89+
// request
8390
let roots = server.list_roots().await?;
8491
8592
// or send notification
8693
server.notify_cancelled(...).await?;
8794
```
95+
</details>
8896

89-
#### 5. Waiting for service shutdown
97+
<details>
98+
<summary>5. Waiting for service shutdown</summary>
9099

91100
```rust, ignore
92101
let quit_reason = server.waiting().await?;
93102
// or cancel it
94103
let quit_reason = server.cancel().await?;
95104
```
105+
</details>
96106

97107
### Use macros to declaring tool
98108

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

101-
Check this [file](examples/servers/src/common/calculator.rs).
111+
<details>
112+
<summary>Example: Calculator Tool</summary>
102113

114+
Check this [file](examples/servers/src/common/calculator.rs).
103115
```rust, ignore
104116
use rmcp::{ServerHandler, model::ServerInfo, schemars, tool};
105117
@@ -150,19 +162,19 @@ impl ServerHandler for Calculator {
150162
}
151163
}
152164
}
153-
154165
```
155166

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

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

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

162175
### Manage Multi Services
163176

164177
For many cases you need to manage several service in a collection, you can call `into_dyn` to convert services into the same type.
165-
166178
```rust, ignore
167179
let service = service.into_dyn();
168180
```
@@ -177,7 +189,7 @@ See [examples](examples/README.md)
177189
- `server`: use server side sdk
178190
- `macros`: macros default
179191

180-
#### Transports
192+
### Transports
181193

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

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

207+
## Development with Dev Container
194208
See [docs/DEVCONTAINER.md](docs/DEVCONTAINER.md) for instructions on using Dev Container for development.

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
# Integration
7676

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

7980
# WASI
8081

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "simple-chat-client"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
tokio = { version = "1", features = ["full"] }
8+
serde = { version = "1.0", features = ["derive"] }
9+
serde_json = "1.0"
10+
reqwest = { version = "0.11", features = ["json"] }
11+
anyhow = "1.0"
12+
thiserror = "1.0"
13+
async-trait = "0.1"
14+
futures = "0.3"
15+
toml = "0.8"
16+
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", features = [
17+
"client",
18+
"transport-child-process",
19+
"transport-sse",
20+
], no-default-features = true }

examples/simple-chat-client/README.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Simple Chat Client
2+
3+
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.
4+
5+
6+
## Config
7+
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.
8+
9+
## Usage
10+
11+
After configuring the config file, you can run the example:
12+
```bash
13+
cargo run --bin simple-chat
14+
```
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use anyhow::Result;
2+
use simple_chat_client::{
3+
chat::ChatSession,
4+
client::OpenAIClient,
5+
config::Config,
6+
tool::{get_mcp_tools, Tool, ToolSet},
7+
};
8+
use std::sync::Arc;
9+
10+
//default config path
11+
const DEFAULT_CONFIG_PATH: &str = "/etc/simple-chat-client/config.toml";
12+
13+
#[tokio::main]
14+
async fn main() -> Result<()> {
15+
// load config
16+
let config = Config::load(DEFAULT_CONFIG_PATH).await?;
17+
18+
// create openai client
19+
let api_key = config
20+
.openai_key
21+
.clone()
22+
.unwrap_or_else(|| std::env::var("OPENAI_API_KEY").expect("need set api key"));
23+
let url = config.chat_url.clone();
24+
println!("url is {:?}", url);
25+
let openai_client = Arc::new(OpenAIClient::new(api_key, url));
26+
27+
// create tool set
28+
let mut tool_set = ToolSet::default();
29+
30+
// load mcp
31+
if config.mcp.is_some() {
32+
let mcp_clients = config.create_mcp_clients().await?;
33+
34+
for (name, client) in mcp_clients {
35+
println!("loading mcp tools: {}", name);
36+
let server = client.peer().clone();
37+
let tools = get_mcp_tools(server).await?;
38+
39+
for tool in tools {
40+
println!("adding tool: {}", tool.name());
41+
tool_set.add_tool(tool);
42+
}
43+
}
44+
}
45+
46+
// create chat session
47+
let mut session = ChatSession::new(
48+
openai_client,
49+
tool_set,
50+
config
51+
.model_name
52+
.unwrap_or_else(|| "gpt-4o-mini".to_string()),
53+
);
54+
55+
// build system prompt with tool info
56+
let mut system_prompt =
57+
"you are a assistant, you can help user to complete various tasks. you have the following tools to use:\n".to_string();
58+
59+
// add tool info to system prompt
60+
for tool in session.get_tools() {
61+
system_prompt.push_str(&format!(
62+
"\ntool name: {}\ndescription: {}\nparameters: {}\n",
63+
tool.name(),
64+
tool.description(),
65+
serde_json::to_string_pretty(&tool.parameters()).unwrap_or_default()
66+
));
67+
}
68+
69+
// add tool call format guidance
70+
system_prompt.push_str(
71+
"\nif you need to call tool, please use the following format:\n\
72+
Tool: <tool name>\n\
73+
Inputs: <inputs>\n",
74+
);
75+
76+
// add system prompt
77+
session.add_system_prompt(system_prompt);
78+
79+
// start chat
80+
session.chat().await?;
81+
82+
Ok(())
83+
}

0 commit comments

Comments
 (0)