1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//! kuri is a framework to build [Model Context Protocol][mcp-spec] (MCP) servers, focused on
//! developer ergonomics and clarity.
//!
//! # Example
//!
//! The "Hello World" of kuri is:
//!
//! ```rust
//! use kuri::{MCPServiceBuilder, serve, tool, ServiceExt};
//! use kuri::transport::{StdioTransport, TransportError};
//!
//! #[tool]
//! async fn hello_world_tool() -> String {
//! "Hello World".to_string()
//! }
//!
//! #[tokio::main]
//! async fn main() -> Result<(), TransportError> {
//! let service = MCPServiceBuilder::new("Hello World".to_string())
//! .with_tool(HelloWorldTool)
//! .build();
//!
//! serve(service.into_request_service(), StdioTransport::new()).await
//! }
//! ```
//!
//! There are more [examples] in the repository.
//!
//! # Getting started
//!
//! You'll need to add these dependencies to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! kuri = "0.1"
//! tokio = { version = "1", features = ["full"] }
//! serde = { version = "1.0", features = ["derive"] }
//! serde_json = "1.0"
//! schemars = "0.8"
//! async-trait = "0.1"
//! ```
//!
//! The `full` feature of `tokio` isn't necessary, but is the easiest way to get started.
//!
//! # Defining tools and prompts
//!
//! Handlers are the functions invoked when a tool or prompt is invoked. They're just normal Rust
//! functions, and can return any type that implements [`IntoCallToolResult`]. Since handlers are
//! just Rust functions, you can use them as normal. Testing is also straightforward; just call the
//! function directly.
//!
//! # Handling notifications
//!
//! If you wish to handle notifications, you'll need to define your own function to handle the raw
//! [`Notification`] and provide this function to the [`MCPServiceBuilder`] when building your service.
//!
//! ```rust
//! use kuri::{MCPServiceBuilder};
//! use kuri_mcp_protocol::jsonrpc::Notification;
//!
//! async fn my_notification_handler(notification: Notification) {
//! println!("Notification received: {:?}", notification.method);
//! }
//!
//! let mut service = MCPServiceBuilder::new("Notification server".to_string())
//! .with_notification_handler(move |_, notification| {
//! Box::pin(my_notification_handler(notification))
//! })
//! .build();
//! ```
//!
//! ## Error handling
//!
//! The MCP protocol supports two types of errors: RPC errors, and logical errors. kuri tool handlers
//! can return a [`ToolError`], which combines both types of errors (`ExecutionError` is mapped to
//! logical errors). You can return your own error type if you prefer; just implement
//! [`IntoCallToolResult`] for your type.
//!
//! # Middleware and layers
//!
//! Like axum, kuri does not have its own bespoke middleware system, and instead utilises the tower
//! ecosystem of middleware. This means you can use anything from [`tower`], [`axum`], or [`tonic`]
//! (gRPC). Middleware can be used to implement functionality like authorisation and logging. More
//! generally, anything that needs to happen before, after, or intercepts a request to a tool,
//! prompt, or resource, can be implemented using tower layers with kuri.
//!
//! We provide [an example][middleware example] of integrating tracing using a layer. Tower also
//! provides [a guide][tower guide to writing middleware] to get started writing middleware.
//!
//! ## Global middleware
//!
//! If your middleware needs to run on all invocations, you can apply the `.layer` using tower's
//! [`ServiceBuilder`]:
//! ```rust
//! use kuri::{MCPServiceBuilder, middleware::tracing::TracingLayer};
//! use tower::ServiceBuilder;
//! # use kuri::tool;
//! # #[tool]
//! # async fn hello_world_tool() -> String {
//! # "Hello World".to_string()
//! # }
//!
//! let service = MCPServiceBuilder::new("Hello World".to_string())
//! .with_tool(HelloWorldTool)
//! .build();
//!
//! let final_service = ServiceBuilder::new()
//! // Add tracing middleware
//! .layer(TracingLayer::new())
//! // Route to the MCP service
//! .service(service);
//! ```
//!
//! The layers are applied in the order they're declared, before finally routing the request to the
//! MCP service. On return, the handlers are called in reverse order. So the first declared layer
//! will be the first to process an incoming request, and the last to process an outgoing response.
//!
//! ## Per-[tool/prompt/resource] middleware
//!
//! For now, you will need to add the code to your handler to invoke your middleware. We're still
//! working on making this more ergonomic within kuri.
//!
//! ## `.into_request_service()`
//!
//! [`MCPService`] is a service that processes a single JSON-RPC message (represented by [`SendableMessage`]).
//! However, a JSON-RPC request (represented by [`Request`]) may contain a batch of messages as well.
//! [`MCPRequestService`] is a tower service that processes these JSON-RPC requests. On the transport,
//! you'll want to serve a service that handles the JSON-RPC requests. To turn an [`MCPService`] into a
//! [`MCPRequestService`], you can use the `.into_request_service()` method.
//!
//! This has a few implications for middleware. For tracing for instance, you may want this to apply
//! at the request level. In that case, you can use `.into_request_service()` on the service before
//! applying your tracing middleware. Other middleware may prefer to be applied at the message level,
//! and can be applied on [`MCPService`] instead.
//!
//! # Sharing state with handlers
//!
//! Handlers can share state with each other, and persist state across invocations, through types
//! saved within the MCPService's [`Context`]. As in the [counter example], when creating your
//! service, provide state to the builder. You can then access the state within your handlers using
//! by wrapping your type in [`Inject`]:
//!
//! ```rust
//! use kuri::{MCPServiceBuilder, context::Inject};
//! use serde::Deserialize;
//! use std::sync::atomic::{AtomicI32, Ordering};
//!
//! #[derive(Default, Deserialize)]
//! struct Counter(AtomicI32);
//!
//! let my_state = Counter::default();
//! let service = MCPServiceBuilder::new("Hello World".to_string())
//! .with_state(Inject::new(my_state))
//! .build();
//!
//! async fn increment(counter: Inject<Counter>, quantity: u32) {
//! counter.0.fetch_add(quantity as i32, Ordering::SeqCst);
//! }
//! ```
//!
//! You don't need to use `Inject`, but it's the easiest way to get started. If you have more
//! specific needs, see the [`FromContext`] trait, which you may implement for your own types.
//!
//! # Transports
//!
//! Once you instantiate a [`MCPService`], you can use the [`serve`] function to start the server
//! over some transport, as in the Hello World example above.
//!
//! # Logging
//!
//! kuri uses tokio's tracing throughout for log messages. Typically, applications might consume
//! these messages to stdout, however when using the stdin transport to communicate with the client,
//! we are unable to log messages to stdout, as discussed in [the MCP docs](https://modelcontextprotocol.io/docs/tools/debugging#server-side-logging)
//!
//! You can change the tokio_subscriber writer to any other output stream, for example file logging:
//! ```rust
//! use tracing_subscriber::EnvFilter;
//!
//! let file_appender = tracing_appender::rolling::daily(tempfile::tempdir().unwrap(), "server.log");
//! tracing_subscriber::fmt()
//! .with_env_filter(EnvFilter::from_default_env())
//! .with_writer(file_appender)
//! .with_target(false)
//! .with_thread_ids(true)
//! .with_file(true)
//! .with_line_number(true)
//! .init();
//! ```
//!
//! [mcp-spec]: https://modelcontextprotocol.io/specification/2025-03-26/
//! [examples]: https://github.com/itsaphel/kuri/tree/main/examples
//! [`tower`]: https://github.com/tower-rs/tower
//! [`axum`]: https://github.com/tokio-rs/axum
//! [`tonic`]: https://github.com/hyperium/tonic
//! [counter example]: https://github.com/itsaphel/kuri/tree/main/examples/02_stateful_counter_tool_server.rs
//! [middleware example]: https://github.com/itsaphel/kuri/tree/main/examples/04_hyper_middleware.rs
//! [`ServiceBuilder`]: tower::ServiceBuilder
//! [tower guide to writing middleware]: https://github.com/tower-rs/tower/blob/master/guides/building-a-middleware-from-scratch.md
//! [`IntoCallToolResult`]: crate::response::IntoCallToolResult
//! [`Notification`]: kuri_mcp_protocol::jsonrpc::Notification
//! [`SendableMessage`]: kuri_mcp_protocol::jsonrpc::SendableMessage
//! [`Request`]: kuri_mcp_protocol::jsonrpc::Request
//! [`MCPService`]: crate::MCPService
//! [`MCPRequestService`]: crate::MCPRequestService
//! [`MCPServer`]: crate::MCPService
//! [`Context`]: crate::context::Context
//! [`Inject`]: crate::context::Inject
//! [`FromContext`]: crate::context::FromContext
// aliases
pub use ;
pub use serve;
pub use ;
pub use ServiceExt;
// re-export certain MCP protocol types
pub use ;
// re-export macros
pub use prompt;
pub use tool;