Skip to content

Commit d100eff

Browse files
committed
Add MCP tools example and update .gitignore
Introduce a new example crate 05-mcp-server: adds Cargo.toml, README and a complete src/main.rs demonstrating exposing RustAPI endpoints as MCP tools (in-process invocation, tagged routes, side-by-side HTTP + MCP endpoints). Also clean up .gitignore to use general patterns for build artifacts, editors, OS files and local env/logs. (Workspace metadata and lockfile updated to account for the new example.)
1 parent 43c8289 commit d100eff

8 files changed

Lines changed: 1102 additions & 57 deletions

File tree

.gitignore

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
1-
/serverless-lambda/target
2-
/serverless-lambda/target
3-
/serverless-lambda
4-
/microservices/target
5-
/cors-test/target
6-
/graphql-api/target
7-
/hello-world/target
8-
/mcp-server/target
9-
/middleware-chain/target
10-
/phase11-demo/target
11-
/rate-limit-demo/target
12-
/toon-api/target
13-
/cors-test
14-
/cors-test/target
15-
/rate-limit-demo/target
16-
/event-sourcing/target
17-
/microservices-advanced/target
18-
/auth-api/target
19-
/crud-api/target/debug
20-
/crud-api/target
21-
*.timestamp
22-
/proof-of-concept/target
23-
/sqlx-crud/target
24-
/templates/target
25-
/websocket/target
26-
/target
1+
# Build artifacts
2+
**/target/
3+
**/*.timestamp
4+
5+
# IDE / editor
6+
.idea/
7+
.vscode/
8+
*.swp
9+
10+
# OS
11+
.DS_Store
12+
Thumbs.db
13+
14+
# Local env / logs
15+
.env
16+
*.log
17+

05-mcp-server/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "mcp-server"
3+
version = "0.1.0"
4+
edition.workspace = true
5+
license.workspace = true
6+
7+
# Run with: cargo run -p mcp-server
8+
# HTTP API: http://127.0.0.1:8080
9+
# MCP tools: http://127.0.0.1:9090 (connect Claude / Cursor / agents here)
10+
11+
[dependencies]
12+
rustapi-rs = { version = "0.1.507", features = ["protocol-mcp", "swagger-ui"] }
13+
tokio = { version = "1", features = ["full"] }
14+
serde = { version = "1", features = ["derive"] }

05-mcp-server/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# 05-mcp-server — MCP Tools Example
2+
3+
This is a complete, standalone example of exposing your RustAPI endpoints as **MCP tools** for LLMs and AI agents (Claude, Cursor, Continue, custom agents, etc.).
4+
5+
## Features Demonstrated
6+
7+
- `RustApi::auto()` for zero-boilerplate routing + OpenAPI
8+
- Tagging routes with `#[rustapi_rs::tag("agent")]` to selectively expose them
9+
- `McpServer::from_rustapi(...)` with `InvocationMode::InProcess` (zero network overhead, full pipeline respect)
10+
- Side-by-side HTTP API (port 8080) + MCP server (port 9090)
11+
- Graceful shutdown via `run_rustapi_and_mcp_with_shutdown`
12+
- Security: untagged routes (e.g. `/admin/secret`) are **never** visible to agents
13+
14+
## Run
15+
16+
```bash
17+
# From the rustapi-rs-examples root
18+
cargo run -p mcp-server
19+
```
20+
21+
Output:
22+
```
23+
🚀 HTTP API: http://127.0.0.1:8080
24+
🧠 MCP tools: http://127.0.0.1:9090
25+
```
26+
27+
## Usage
28+
29+
### Normal HTTP (for humans / browsers)
30+
- `GET http://127.0.0.1:8080/weather/Istanbul`
31+
- `POST http://127.0.0.1:8080/calc/sum` with `{"a": 40, "b": 2}`
32+
33+
### MCP / Agent endpoint (port 9090)
34+
Point any MCP client at `http://127.0.0.1:9090` (HTTP + SSE transport).
35+
36+
Manual test with curl (JSON-RPC):
37+
38+
```bash
39+
# initialize
40+
curl -X POST http://127.0.0.1:9090 \
41+
-H 'content-type: application/json' \
42+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
43+
44+
# list tools (only the ones with "agent" tag)
45+
curl -X POST http://127.0.0.1:9090 \
46+
-H 'content-type: application/json' \
47+
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
48+
49+
# call a tool
50+
curl -X POST http://127.0.0.1:9090 \
51+
-H 'content-type: application/json' \
52+
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_weather","arguments":{"city":"Istanbul"}}}'
53+
```
54+
55+
## How It Works
56+
57+
Routes annotated with `#[rustapi_rs::tag("agent")]` are automatically turned into MCP tools using the OpenAPI schema RustAPI already generates.
58+
59+
`InvocationMode::InProcess` means:
60+
- Tool calls bypass the network entirely
61+
- Still go through validation, extractors, middleware, error handlers, etc.
62+
- Extremely fast (~microseconds)
63+
64+
See source in `src/main.rs`.
65+
66+
## See Also
67+
68+
- Main RustAPI repo examples: `crates/rustapi-rs/examples/mcp_tools.rs` (quick in-tree demo)
69+
- Cookbook: [MCP Integration](https://tuntii.github.io/RustAPI/recipes/mcp_integration.html)
70+
- Other MCP recipes in the cookbook (in-process, OpenAPI CLI, stdio)
71+
- The full [rustapi-rs-examples](https://github.com/Tuntii/rustapi-rs-examples) repository
72+
73+
## Dependencies (in this example)
74+
75+
```toml
76+
rustapi-rs = { version = "0.1.507", features = ["protocol-mcp", "swagger-ui"] }
77+
```
78+
79+
## License
80+
81+
Same as the main RustAPI project (MIT OR Apache-2.0).

05-mcp-server/src/main.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Run with: cargo run -p mcp-server
2+
// HTTP: http://127.0.0.1:8080
3+
// MCP: http://127.0.0.1:9090 (connect your AI agent here)
4+
//
5+
// This example demonstrates:
6+
// - Exposing routes as MCP tools using tags
7+
// - In-process MCP invocation for zero network overhead
8+
// - Full pipeline (validation, middleware) still applies
9+
//
10+
// For any OpenAPI (not just RustAPI): use `cargo rustapi mcp generate`
11+
12+
use rustapi_rs::prelude::*;
13+
use rustapi_rs::protocol::mcp::{InvocationMode, McpConfig, McpServer, ToolPolicy, run_rustapi_and_mcp_with_shutdown};
14+
use serde::{Deserialize, Serialize};
15+
16+
#[derive(Serialize, Schema)]
17+
struct Weather {
18+
city: String,
19+
temperature: i32,
20+
unit: &'static str,
21+
}
22+
23+
#[derive(Deserialize, Serialize, Schema)]
24+
struct SumRequest {
25+
a: i32,
26+
b: i32,
27+
}
28+
29+
#[derive(Serialize, Schema)]
30+
struct SumResponse {
31+
result: i32,
32+
}
33+
34+
/// Exposed as MCP tool (has "agent" tag) → automatically treated as read
35+
#[rustapi_rs::get("/weather/{city}")]
36+
#[rustapi_rs::tag("agent")]
37+
#[rustapi_rs::summary("Get current weather for a city")]
38+
async fn get_weather(Path(city): Path<String>) -> Json<Weather> {
39+
Json(Weather {
40+
city,
41+
temperature: 22,
42+
unit: "C",
43+
})
44+
}
45+
46+
/// Write operation — only exposed because we set ToolPolicy::All below.
47+
/// Marked to require confirmation from the agent.
48+
#[rustapi_rs::post("/calc/sum")]
49+
#[rustapi_rs::tag("agent")]
50+
#[rustapi_rs::mcp(write, require = "confirm")]
51+
#[rustapi_rs::summary("Add two numbers")]
52+
async fn sum_numbers(Json(req): Json<SumRequest>) -> Json<SumResponse> {
53+
Json(SumResponse {
54+
result: req.a + req.b,
55+
})
56+
}
57+
58+
/// Explicitly skipped via the mcp attribute (never becomes a tool)
59+
#[rustapi_rs::get("/admin/secret")]
60+
#[rustapi_rs::mcp(skip)]
61+
async fn admin_secret() -> &'static str {
62+
"This should never be visible to agents"
63+
}
64+
65+
#[tokio::main]
66+
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
67+
let app = RustApi::auto();
68+
69+
// Use in-process mode for best performance with agents.
70+
// Explicitly allow writes here (for the demo). In real agent setups prefer ReadOnly.
71+
let mcp = McpServer::from_rustapi(
72+
&app,
73+
McpConfig::new()
74+
.name("rustapi-mcp-demo")
75+
.version("0.1.0")
76+
.description("RustAPI MCP example - expose endpoints as AI tools")
77+
.allowed_tags(["agent"])
78+
.tool_policy(ToolPolicy::All)
79+
.invocation_mode(InvocationMode::InProcess),
80+
);
81+
82+
println!("🚀 HTTP API: http://127.0.0.1:8080");
83+
println!("🧠 MCP tools: http://127.0.0.1:9090");
84+
println!();
85+
println!("Connect Claude, Cursor or any MCP client to port 9090.");
86+
println!("Test manually:");
87+
println!(" curl -X POST http://127.0.0.1:9090 -d '{{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}}'");
88+
89+
run_rustapi_and_mcp_with_shutdown(
90+
app,
91+
"0.0.0.0:8080",
92+
mcp,
93+
"0.0.0.0:9090",
94+
async { let _ = tokio::signal::ctrl_c().await; },
95+
)
96+
.await?;
97+
98+
Ok(())
99+
}

0 commit comments

Comments
 (0)