diff --git a/Cargo.toml b/Cargo.toml index 317e479b..f506e505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [workspace] -members = ["crates/rmcp", "crates/rmcp-macros", "examples/*"] +members = ["crates/rmcp","crates/rmcp-core", "crates/rmcp-macros", "examples/*"] resolver = "2" [workspace.dependencies] +rmcp-core = { version = "0.1.5", path = "./crates/rmcp-core" } rmcp = { version = "0.1.5", path = "./crates/rmcp" } rmcp-macros = { version = "0.1.5", path = "./crates/rmcp-macros" } diff --git a/crates/rmcp-core/Cargo.toml b/crates/rmcp-core/Cargo.toml new file mode 100644 index 00000000..d2fbb341 --- /dev/null +++ b/crates/rmcp-core/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "rmcp-core" +license = { workspace = true } +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +readme = { workspace = true } +description = "Rust SDK for Model Context Protocol" +documentation = "https://docs.rs/rmcp" + +[package.metadata.docs.rs] +all-features = true + +[dependencies] +serde = { version = "1.0", features = ["derive", "rc"] } +serde_json = "1.0" +thiserror = "2" +chrono = { version = "0.4.38", features = ["serde"] } +tokio = { version = "1", features = ["sync", "macros", "rt", "time"] } +futures = "0.3" +tracing = { version = "0.1" } +tokio-util = { version = "0.7" } +pin-project-lite = "0.2" +paste = { version = "1", optional = true } +# for auto generate schema +schemars = { version = "0.8", optional = true } + +# for image encoding +base64 = { version = "0.22", optional = true } + +# macro +rmcp-macros = { version = "0.1", workspace = true, optional = true } + +[features] +default = ["base64", "macros"] +macros = ["dep:rmcp-macros", "dep:paste"] +schemars = ["dep:schemars"] + +[dev-dependencies] +tokio = { version = "1", features = ["full"] } + +anyhow = "1.0" +tracing-subscriber = { version = "0.3", features = [ + "env-filter", + "std", + "fmt", +] } +async-trait = "0.1" diff --git a/crates/rmcp/src/error.rs b/crates/rmcp-core/src/error.rs similarity index 100% rename from crates/rmcp/src/error.rs rename to crates/rmcp-core/src/error.rs diff --git a/crates/rmcp-core/src/lib.rs b/crates/rmcp-core/src/lib.rs new file mode 100644 index 00000000..e93171ba --- /dev/null +++ b/crates/rmcp-core/src/lib.rs @@ -0,0 +1,9 @@ +mod error; +pub use error::Error; + +pub mod model; +pub use model::*; +#[cfg(feature = "macros")] +pub use paste; +#[cfg(feature = "macros")] +pub use rmcp_macros::tool; diff --git a/crates/rmcp/src/model.rs b/crates/rmcp-core/src/model.rs similarity index 97% rename from crates/rmcp/src/model.rs rename to crates/rmcp-core/src/model.rs index 7ff035ee..ef5da27c 100644 --- a/crates/rmcp/src/model.rs +++ b/crates/rmcp-core/src/model.rs @@ -45,7 +45,7 @@ macro_rules! object { }; } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy, Eq)] -#[cfg_attr(feature = "server", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct EmptyObject {} pub trait ConstString: Default { @@ -1040,6 +1040,36 @@ impl From for ClientNotification { } } +pub trait IntoCallToolResult { + fn into_call_tool_result(self) -> Result; +} +impl IntoCallToolResult for () { + fn into_call_tool_result(self) -> Result { + Ok(CallToolResult::success(vec![])) + } +} + +impl IntoCallToolResult for T { + fn into_call_tool_result(self) -> Result { + Ok(CallToolResult::success(self.into_contents())) + } +} + +impl IntoCallToolResult for Result { + fn into_call_tool_result(self) -> Result { + self + } +} + +impl IntoCallToolResult for Result { + fn into_call_tool_result(self) -> Result { + match self { + Ok(value) => Ok(CallToolResult::success(value.into_contents())), + Err(error) => Ok(CallToolResult::error(error.into_contents())), + } + } +} + #[cfg(test)] mod tests { use serde_json::json; diff --git a/crates/rmcp/src/model/annotated.rs b/crates/rmcp-core/src/model/annotated.rs similarity index 100% rename from crates/rmcp/src/model/annotated.rs rename to crates/rmcp-core/src/model/annotated.rs diff --git a/crates/rmcp/src/model/capabilities.rs b/crates/rmcp-core/src/model/capabilities.rs similarity index 99% rename from crates/rmcp/src/model/capabilities.rs rename to crates/rmcp-core/src/model/capabilities.rs index 7e853402..2157e8ac 100644 --- a/crates/rmcp/src/model/capabilities.rs +++ b/crates/rmcp-core/src/model/capabilities.rs @@ -38,7 +38,7 @@ pub struct RootsCapabilities { /// /// # Builder /// ```rust -/// # use rmcp::model::ClientCapabilities; +/// # use rmcp_core::model::ClientCapabilities; /// let cap = ClientCapabilities::builder() /// .enable_experimental() /// .enable_roots() @@ -58,7 +58,7 @@ pub struct ClientCapabilities { /// /// ## Builder /// ```rust -/// # use rmcp::model::ServerCapabilities; +/// # use rmcp_core::model::ServerCapabilities; /// let cap = ServerCapabilities::builder() /// .enable_logging() /// .enable_experimental() diff --git a/crates/rmcp/src/model/content.rs b/crates/rmcp-core/src/model/content.rs similarity index 100% rename from crates/rmcp/src/model/content.rs rename to crates/rmcp-core/src/model/content.rs diff --git a/crates/rmcp/src/model/extension.rs b/crates/rmcp-core/src/model/extension.rs similarity index 95% rename from crates/rmcp/src/model/extension.rs rename to crates/rmcp-core/src/model/extension.rs index a9d78d4c..b650163a 100644 --- a/crates/rmcp/src/model/extension.rs +++ b/crates/rmcp-core/src/model/extension.rs @@ -61,7 +61,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// assert!(ext.insert(5i32).is_none()); /// assert!(ext.insert(4u8).is_none()); @@ -79,7 +79,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// assert!(ext.get::().is_none()); /// ext.insert(5i32); @@ -98,7 +98,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// ext.insert(String::from("Hello")); /// ext.get_mut::().unwrap().push_str(" World"); @@ -118,7 +118,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// *ext.get_or_insert(1i32) += 2; /// @@ -134,7 +134,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// *ext.get_or_insert_with(|| 1i32) += 2; /// @@ -158,7 +158,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// *ext.get_or_insert_default::() += 2; /// @@ -175,7 +175,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// ext.insert(5i32); /// assert_eq!(ext.remove::(), Some(5i32)); @@ -193,7 +193,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// ext.insert(5i32); /// ext.clear(); @@ -212,7 +212,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// assert!(ext.is_empty()); /// ext.insert(5i32); @@ -228,7 +228,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext = Extensions::new(); /// assert_eq!(ext.len(), 0); /// ext.insert(5i32); @@ -247,7 +247,7 @@ impl Extensions { /// # Example /// /// ``` - /// # use rmcp::model::Extensions; + /// # use rmcp_core::model::Extensions; /// let mut ext_a = Extensions::new(); /// ext_a.insert(8u8); /// ext_a.insert(16u16); diff --git a/crates/rmcp/src/model/meta.rs b/crates/rmcp-core/src/model/meta.rs similarity index 100% rename from crates/rmcp/src/model/meta.rs rename to crates/rmcp-core/src/model/meta.rs diff --git a/crates/rmcp/src/model/prompt.rs b/crates/rmcp-core/src/model/prompt.rs similarity index 100% rename from crates/rmcp/src/model/prompt.rs rename to crates/rmcp-core/src/model/prompt.rs diff --git a/crates/rmcp/src/model/resource.rs b/crates/rmcp-core/src/model/resource.rs similarity index 100% rename from crates/rmcp/src/model/resource.rs rename to crates/rmcp-core/src/model/resource.rs diff --git a/crates/rmcp/src/model/serde_impl.rs b/crates/rmcp-core/src/model/serde_impl.rs similarity index 100% rename from crates/rmcp/src/model/serde_impl.rs rename to crates/rmcp-core/src/model/serde_impl.rs diff --git a/crates/rmcp/src/model/tool.rs b/crates/rmcp-core/src/model/tool.rs similarity index 100% rename from crates/rmcp/src/model/tool.rs rename to crates/rmcp-core/src/model/tool.rs diff --git a/crates/rmcp/Cargo.toml b/crates/rmcp/Cargo.toml index 738f2446..627c292c 100644 --- a/crates/rmcp/Cargo.toml +++ b/crates/rmcp/Cargo.toml @@ -27,7 +27,7 @@ paste = { version = "1", optional = true } schemars = { version = "0.8", optional = true } # for image encoding -base64 = { version = "0.21", optional = true } +base64 = { version = "0.22", optional = true } # for SSE client reqwest = { version = "0.12", default-features = false, features = [ @@ -52,11 +52,12 @@ tokio-stream = { version = "0.1", optional = true } # macro rmcp-macros = { version = "0.1", workspace = true, optional = true } +rmcp-core = { version = "0.1", workspace = true, optional = true, features = ["schemars"] } [features] -default = ["base64", "macros", "server"] -client = [] -server = ["transport-async-rw", "dep:schemars"] +default = ["base64", "macros", "server", "dep:rmcp-core"] +client = ["dep:rmcp-core"] +server = ["transport-async-rw", "dep:schemars", "dep:rmcp-core"] macros = ["dep:rmcp-macros", "dep:paste"] transport-sse = ["dep:reqwest", "dep:sse-stream", "dep:url"] transport-async-rw = ["tokio/io-util", "tokio-util/codec"] diff --git a/crates/rmcp/src/handler/client.rs b/crates/rmcp/src/handler/client.rs index 13bd9677..174e29fa 100644 --- a/crates/rmcp/src/handler/client.rs +++ b/crates/rmcp/src/handler/client.rs @@ -1,5 +1,5 @@ use crate::{ - error::Error as McpError, + Error as McpError, model::*, service::{Peer, RequestContext, RoleClient, Service, ServiceRole}, }; diff --git a/crates/rmcp/src/handler/server.rs b/crates/rmcp/src/handler/server.rs index 808c9604..b293a20d 100644 --- a/crates/rmcp/src/handler/server.rs +++ b/crates/rmcp/src/handler/server.rs @@ -1,5 +1,5 @@ use crate::{ - error::Error as McpError, + Error as McpError, model::*, service::{Peer, RequestContext, RoleServer, Service, ServiceRole}, }; diff --git a/crates/rmcp/src/handler/server/tool.rs b/crates/rmcp/src/handler/server/tool.rs index c4cde64f..e1e3b5f3 100644 --- a/crates/rmcp/src/handler/server/tool.rs +++ b/crates/rmcp/src/handler/server/tool.rs @@ -9,7 +9,7 @@ use tokio_util::sync::CancellationToken; use crate::{ RoleServer, - model::{CallToolRequestParam, CallToolResult, ConstString, IntoContents, JsonObject}, + model::{CallToolRequestParam, CallToolResult, ConstString, IntoCallToolResult, JsonObject}, service::RequestContext, }; /// A shortcut for generating a JSON schema for a type. @@ -86,30 +86,6 @@ pub trait FromToolCallContextPart<'a, S>: Sized { ) -> Result<(Self, ToolCallContext<'a, S>), crate::Error>; } -pub trait IntoCallToolResult { - fn into_call_tool_result(self) -> Result; -} -impl IntoCallToolResult for () { - fn into_call_tool_result(self) -> Result { - Ok(CallToolResult::success(vec![])) - } -} - -impl IntoCallToolResult for T { - fn into_call_tool_result(self) -> Result { - Ok(CallToolResult::success(self.into_contents())) - } -} - -impl IntoCallToolResult for Result { - fn into_call_tool_result(self) -> Result { - match self { - Ok(value) => Ok(CallToolResult::success(value.into_contents())), - Err(error) => Ok(CallToolResult::error(error.into_contents())), - } - } -} - pin_project_lite::pin_project! { #[project = IntoCallToolResultFutProj] pub enum IntoCallToolResultFut { @@ -145,12 +121,6 @@ where } } -impl IntoCallToolResult for Result { - fn into_call_tool_result(self) -> Result { - self - } -} - pub trait CallToolHandler<'a, S, A> { type Fut: Future> + Send + 'a; fn call(self, context: ToolCallContext<'a, S>) -> Self::Fut; diff --git a/crates/rmcp/src/lib.rs b/crates/rmcp/src/lib.rs index 08a1908b..25ef3dc0 100644 --- a/crates/rmcp/src/lib.rs +++ b/crates/rmcp/src/lib.rs @@ -1,8 +1,6 @@ -mod error; -pub use error::Error; +pub use rmcp_core::{Error, model}; /// Basic data types in MCP specification -pub mod model; #[cfg(any(feature = "client", feature = "server"))] pub mod service; #[cfg(feature = "client")] diff --git a/crates/rmcp/src/service.rs b/crates/rmcp/src/service.rs index 696947b6..24c5481e 100644 --- a/crates/rmcp/src/service.rs +++ b/crates/rmcp/src/service.rs @@ -2,7 +2,7 @@ use futures::future::BoxFuture; use thiserror::Error; use crate::{ - error::Error as McpError, + Error as McpError, model::{ CancelledNotification, CancelledNotificationParam, Extensions, GetExtensions, GetMeta, JsonRpcBatchRequestItem, JsonRpcBatchResponseItem, JsonRpcError, JsonRpcMessage, diff --git a/crates/rmcp/tests/common/calculator.rs b/crates/rmcp/tests/common/calculator.rs index e179f258..80c2d67c 100644 --- a/crates/rmcp/tests/common/calculator.rs +++ b/crates/rmcp/tests/common/calculator.rs @@ -1,6 +1,6 @@ use rmcp::{ ServerHandler, - model::{ServerCapabilities, ServerInfo}, + model::{IntoCallToolResult, ServerCapabilities, ServerInfo}, schemars, tool, }; #[derive(Debug, serde::Deserialize, schemars::JsonSchema)] diff --git a/crates/rmcp/tests/test_tool_macros.rs b/crates/rmcp/tests/test_tool_macros.rs index daa5ee3d..fd52e81b 100644 --- a/crates/rmcp/tests/test_tool_macros.rs +++ b/crates/rmcp/tests/test_tool_macros.rs @@ -1,6 +1,11 @@ use std::sync::Arc; -use rmcp::{ServerHandler, handler::server::tool::ToolCallContext, tool}; +use rmcp::{ + ServerHandler, + handler::server::tool::ToolCallContext, + model::IntoCallToolResult, + tool, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/examples/clients/Cargo.toml b/examples/clients/Cargo.toml index 52226ed4..21de0af7 100644 --- a/examples/clients/Cargo.toml +++ b/examples/clients/Cargo.toml @@ -1,5 +1,3 @@ - - [package] name = "mcp-client-examples" version = "0.1.5" @@ -13,12 +11,13 @@ rmcp = { path = "../../crates/rmcp", features = [ "transport-child-process", "tower" ] } +rmcp-core = { path = "../../crates/rmcp-core", features = ["macros"] } tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -rand = "0.8" +rand = "0.9" futures = "0.3" anyhow = "1.0" diff --git a/examples/clients/src/everything_stdio.rs b/examples/clients/src/everything_stdio.rs index 091e9053..f373c97e 100644 --- a/examples/clients/src/everything_stdio.rs +++ b/examples/clients/src/everything_stdio.rs @@ -2,9 +2,9 @@ use anyhow::Result; use rmcp::{ ServiceExt, model::{CallToolRequestParam, GetPromptRequestParam, ReadResourceRequestParam}, - object, transport::TokioChildProcess, }; +use rmcp_core::object; use tokio::process::Command; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; diff --git a/examples/servers/Cargo.toml b/examples/servers/Cargo.toml index 63d9d6d2..32c27424 100644 --- a/examples/servers/Cargo.toml +++ b/examples/servers/Cargo.toml @@ -1,5 +1,3 @@ - - [package] name = "mcp-server-examples" version = "0.1.5" @@ -7,7 +5,8 @@ edition = "2024" publish = false [dependencies] -rmcp= { path = "../../crates/rmcp", features = ["server", "transport-sse-server", "transport-io"] } +rmcp= { path = "../../crates/rmcp", features = ["server", "transport-sse-server", "transport-io", "macros"] } +rmcp-core = { path = "../../crates/rmcp-core" } tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "io-std", "signal"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/examples/servers/src/common/calculator.rs b/examples/servers/src/common/calculator.rs index f2de8f67..e0a7afe5 100644 --- a/examples/servers/src/common/calculator.rs +++ b/examples/servers/src/common/calculator.rs @@ -1,7 +1,7 @@ use rmcp::{ ServerHandler, handler::server::wrapper::Json, - model::{ServerCapabilities, ServerInfo}, + model::{IntoCallToolResult, ServerCapabilities, ServerInfo}, schemars, tool, }; diff --git a/examples/servers/src/common/counter.rs b/examples/servers/src/common/counter.rs index 7bed523a..d9f19c6a 100644 --- a/examples/servers/src/common/counter.rs +++ b/examples/servers/src/common/counter.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use rmcp::{ - Error as McpError, RoleServer, ServerHandler, const_string, model::*, schemars, - service::RequestContext, tool, + Error as McpError, RoleServer, ServerHandler, model::*, schemars, service::RequestContext, tool, }; +use rmcp_core::const_string; use serde_json::json; use tokio::sync::Mutex; diff --git a/examples/servers/src/common/generic_service.rs b/examples/servers/src/common/generic_service.rs index 433a4308..ac957966 100644 --- a/examples/servers/src/common/generic_service.rs +++ b/examples/servers/src/common/generic_service.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use rmcp::{ ServerHandler, - model::{ServerCapabilities, ServerInfo}, + model::{IntoCallToolResult, ServerCapabilities, ServerInfo}, schemars, tool, }; diff --git a/examples/transport/Cargo.toml b/examples/transport/Cargo.toml index 06708bb9..a9827f87 100644 --- a/examples/transport/Cargo.toml +++ b/examples/transport/Cargo.toml @@ -15,7 +15,8 @@ readme = { workspace = true } all-features = true [dependencies] -rmcp = { path = "../../crates/rmcp", features = ["server", "client"] } +rmcp = { path = "../../crates/rmcp", features = ["server", "client", "macros"] } +rmcp-core = { path = "../../crates/rmcp-core", features = ["macros"] } tokio = { version = "1", features = [ "macros", "rt", diff --git a/examples/transport/src/common/calculator.rs b/examples/transport/src/common/calculator.rs index 99b7314a..fe5e56f7 100644 --- a/examples/transport/src/common/calculator.rs +++ b/examples/transport/src/common/calculator.rs @@ -1,4 +1,8 @@ -use rmcp::{ServerHandler, model::ServerInfo, schemars, tool, tool_box}; +use rmcp::{ + ServerHandler, + model::{IntoCallToolResult, ServerInfo}, + schemars, tool, tool_box, +}; #[derive(Debug, serde::Deserialize, schemars::JsonSchema)] pub struct SumRequest { diff --git a/examples/transport/src/unix_socket.rs b/examples/transport/src/unix_socket.rs index 875ed9bb..a0329e27 100644 --- a/examples/transport/src/unix_socket.rs +++ b/examples/transport/src/unix_socket.rs @@ -1,7 +1,8 @@ use std::fs; use common::calculator::Calculator; -use rmcp::{serve_client, serve_server}; +use rmcp::{model::CallToolRequestParam, serve_client, serve_server}; +use rmcp_core::object; use tokio::net::{UnixListener, UnixStream}; mod common; @@ -66,9 +67,9 @@ async fn client() -> anyhow::Result<()> { println!("Calling sum tool: {}", sum_tool.name); let result = client .peer() - .call_tool(rmcp::model::CallToolRequestParam { + .call_tool(CallToolRequestParam { name: sum_tool.name.clone(), - arguments: Some(rmcp::object!({ + arguments: Some(object!({ "a": 10, "b": 20 })), diff --git a/examples/wasi/src/calculator.rs b/examples/wasi/src/calculator.rs index f1c35eea..fbe4792d 100644 --- a/examples/wasi/src/calculator.rs +++ b/examples/wasi/src/calculator.rs @@ -1,6 +1,6 @@ use rmcp::{ ServerHandler, - model::{ServerCapabilities, ServerInfo}, + model::{IntoCallToolResult, ServerCapabilities, ServerInfo}, schemars, tool, tool_box, };