From e433d63eadbcae594e3d8c85deb5d31747dc8602 Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Tue, 1 Jul 2025 21:49:30 -0300 Subject: [PATCH 1/8] chore: implement support for mcp 2025-06-18 --- Cargo.lock | 181 +++++++------- Cargo.toml | 3 +- crates/rust-mcp-macros/Cargo.toml | 16 +- crates/rust-mcp-macros/src/lib.rs | 231 ++++++++++++------ crates/rust-mcp-sdk/Cargo.toml | 16 +- .../src/mcp_handlers/mcp_client_handler.rs | 16 ++ .../client_runtime/mcp_client_runtime.rs | 6 + .../server_runtime/mcp_server_runtime.rs | 5 +- crates/rust-mcp-sdk/src/schema.rs | 13 +- crates/rust-mcp-sdk/tests/common/common.rs | 25 +- .../rust-mcp-sdk/tests/common/test_server.rs | 2 + crates/rust-mcp-transport/Cargo.toml | 6 +- crates/rust-mcp-transport/src/schema.rs | 13 +- .../hello-world-mcp-server-core/Cargo.toml | 2 +- .../hello-world-mcp-server-core/src/main.rs | 1 + .../hello-world-mcp-server-core/src/tools.rs | 12 +- examples/hello-world-mcp-server/Cargo.toml | 2 +- examples/hello-world-mcp-server/src/main.rs | 1 + examples/hello-world-mcp-server/src/tools.rs | 16 +- .../hello-world-server-core-sse/Cargo.toml | 2 +- .../hello-world-server-core-sse/src/main.rs | 1 + .../hello-world-server-core-sse/src/tools.rs | 12 +- examples/hello-world-server-sse/Cargo.toml | 2 +- examples/hello-world-server-sse/src/main.rs | 1 + examples/hello-world-server-sse/src/tools.rs | 12 +- .../simple-mcp-client-core-sse/Cargo.toml | 2 +- .../simple-mcp-client-core-sse/src/handler.rs | 2 + .../simple-mcp-client-core-sse/src/main.rs | 5 +- examples/simple-mcp-client-core/Cargo.toml | 2 +- .../simple-mcp-client-core/src/handler.rs | 2 + examples/simple-mcp-client-core/src/main.rs | 3 +- examples/simple-mcp-client-sse/Cargo.toml | 2 +- examples/simple-mcp-client-sse/src/main.rs | 5 +- examples/simple-mcp-client/Cargo.toml | 2 +- examples/simple-mcp-client/src/main.rs | 5 +- 35 files changed, 410 insertions(+), 217 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64fa75a..409e854 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -78,9 +78,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" @@ -245,9 +245,9 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -257,9 +257,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.24" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -277,9 +277,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -428,12 +428,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -468,9 +468,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f89bda4c2a21204059a977ed3bfe746677dfd137b83c339e702b0ac91d482aa" +checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683" dependencies = [ "autocfg", "tokio", @@ -612,7 +612,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -682,9 +682,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "hello-world-mcp-server" @@ -742,9 +742,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "home" @@ -877,7 +877,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.10", + "h2 0.4.11", "http 1.3.1", "http-body 1.0.1", "httparse", @@ -891,9 +891,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", "hyper 1.6.0", @@ -908,9 +908,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64 0.22.1", "bytes", @@ -1039,9 +1039,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", @@ -1127,9 +1127,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -1138,7 +1138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -1161,9 +1161,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1198,9 +1198,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" @@ -1226,9 +1226,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -1240,7 +1240,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -1272,9 +1272,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", @@ -1309,9 +1309,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1319,9 +1319,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -1374,9 +1374,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", "syn", @@ -1450,16 +1450,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1473,9 +1473,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -1549,9 +1549,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] @@ -1602,9 +1602,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.20" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64 0.22.1", "bytes", @@ -1678,9 +1678,9 @@ dependencies = [ [[package]] name = "rust-mcp-schema" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a794de25669a2d21c5074ec5082f74f5e88863a112339fe90264d9e480b0ee8b" +checksum = "fec1ff3507a619b9945f60a94dac448541aef8c9803aa6192c30f4a932cb1499" dependencies = [ "serde", "serde_json", @@ -1729,9 +1729,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -1760,9 +1760,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "aws-lc-rs", "once_cell", @@ -1973,18 +1973,15 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" @@ -2010,9 +2007,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -2081,12 +2078,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -2268,9 +2264,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -2279,9 +2275,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -2404,9 +2400,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -2523,9 +2519,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] @@ -2582,6 +2578,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2600,9 +2605,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -2773,18 +2778,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 97b6a88..7827c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ rust-mcp-sdk = { path = "crates/rust-mcp-sdk", default-features = false } rust-mcp-macros = { version = "0.4.2", path = "crates/rust-mcp-macros", default-features = false } # External crates -rust-mcp-schema = { version = "0.6", default-features = false } +rust-mcp-schema = { version = "0.7", default-features = false } + futures = { version = "0.3" } tokio = { version = "1.4", features = ["full"] } diff --git a/crates/rust-mcp-macros/Cargo.toml b/crates/rust-mcp-macros/Cargo.toml index 65a5d19..dc1ab71 100644 --- a/crates/rust-mcp-macros/Cargo.toml +++ b/crates/rust-mcp-macros/Cargo.toml @@ -33,13 +33,15 @@ proc-macro = true [features] # defalt features -default = ["2025_03_26"] # Default features +default = ["2025_06_18"] # Default features # activates the latest MCP schema version, this will be updated once a new version of schema is published -latest = ["2025_03_26"] - -# enabled mcp schema version 2025_03_26 -2025_03_26 = ["rust-mcp-schema/2025_03_26","rust-mcp-schema/schema_utils"] -# enabled mcp schema version 2024_11_05 -2024_11_05 = ["rust-mcp-schema/2024_11_05","rust-mcp-schema/schema_utils"] +latest = ["2025_06_18"] + +# enables mcp schema version 2025_06_18 +2025_06_18 = ["rust-mcp-schema/2025_06_18", "rust-mcp-schema/schema_utils"] +# enables mcp schema version 2025_03_26 +2025_03_26 = ["rust-mcp-schema/2025_03_26", "rust-mcp-schema/schema_utils"] +# enables mcp schema version 2024_11_05 +2024_11_05 = ["rust-mcp-schema/2024_11_05", "rust-mcp-schema/schema_utils"] sdk = [] diff --git a/crates/rust-mcp-macros/src/lib.rs b/crates/rust-mcp-macros/src/lib.rs index f7611e0..8981223 100644 --- a/crates/rust-mcp-macros/src/lib.rs +++ b/crates/rust-mcp-macros/src/lib.rs @@ -12,33 +12,35 @@ use utils::{is_option, renamed_field, type_to_json_schema}; /// Represents the attributes for the `mcp_tool` procedural macro. /// -/// This struct parses and validates the `name` and `description` attributes provided -/// to the `mcp_tool` macro. Both attributes are required and must not be empty strings. +/// This struct parses and validates the attributes provided to the `mcp_tool` macro. +/// The `name` and `description` attributes are required and must not be empty strings. /// /// # Fields -/// * `name` - An optional string representing the tool's name. -/// * `description` - An optional string describing the tool. -/// -/// The following fields are available only with the `2025_03_26` feature: -/// * `destructive_hint` - Optional boolean for `ToolAnnotations::destructive_hint`. -/// * `idempotent_hint` - Optional boolean for `ToolAnnotations::idempotent_hint`. -/// * `open_world_hint` - Optional boolean for `ToolAnnotations::open_world_hint`. -/// * `read_only_hint` - Optional boolean for `ToolAnnotations::read_only_hint`. -/// * `title` - Optional string for `ToolAnnotations::title`. +/// * `name` - A string representing the tool's name (required). +/// * `description` - A string describing the tool (required). +/// * `meta` - An optional JSON string for metadata. +/// * `title` - An optional string for the tool's title. +/// * The following fields are available only with the `2025_03_26` feature and later: +/// * `destructive_hint` - Optional boolean for `ToolAnnotations::destructive_hint`. +/// * `idempotent_hint` - Optional boolean for `ToolAnnotations::idempotent_hint`. +/// * `open_world_hint` - Optional boolean for `ToolAnnotations::open_world_hint`. +/// * `read_only_hint` - Optional boolean for `ToolAnnotations::read_only_hint`. /// struct McpToolMacroAttributes { name: Option, description: Option, - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + meta: Option, // Store raw JSON string instead of parsed Map + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + title: Option, + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] destructive_hint: Option, - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] idempotent_hint: Option, - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] open_world_hint: Option, - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] read_only_hint: Option, - #[cfg(feature = "2025_03_26")] - title: Option, } use syn::parse::ParseStream; @@ -58,27 +60,32 @@ impl Parse for ExprList { impl Parse for McpToolMacroAttributes { /// Parses the macro attributes from a `ParseStream`. /// - /// This implementation extracts `name` and `description` from the attribute input, - /// ensuring they are provided as string literals and are non-empty. + /// This implementation extracts `name`, `description`, `meta`, and `title` from the attribute input. + /// The `name` and `description` must be provided as string literals and be non-empty. + /// The `meta` attribute must be a valid JSON object provided as a string literal, and `title` must be a string literal. /// /// # Errors /// Returns a `syn::Error` if: /// - The `name` attribute is missing or empty. /// - The `description` attribute is missing or empty. + /// - The `meta` attribute is provided but is not a valid JSON object. + /// - The `title` attribute is provided but is not a string literal. fn parse(attributes: syn::parse::ParseStream) -> syn::Result { let mut instance = Self { name: None, description: None, - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + meta: None, + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + title: None, + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] destructive_hint: None, - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] idempotent_hint: None, - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] open_world_hint: None, - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] read_only_hint: None, - #[cfg(feature = "2025_03_26")] - title: None, }; let meta_list: Punctuated = Punctuated::parse_terminated(attributes)?; @@ -134,9 +141,55 @@ impl Parse for McpToolMacroAttributes { _ => {} } } + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + "meta" => { + let value = match &meta_name_value.value { + Expr::Lit(ExprLit { + lit: Lit::Str(lit_str), + .. + }) => lit_str.value(), + _ => { + return Err(Error::new_spanned( + &meta_name_value.value, + "Expected a JSON object as a string literal", + )); + } + }; + // Validate that the string is a valid JSON object + let parsed: serde_json::Value = + serde_json::from_str(&value).map_err(|e| { + Error::new_spanned( + &meta_name_value.value, + format!("Expected a valid JSON object: {e}"), + ) + })?; + if !parsed.is_object() { + return Err(Error::new_spanned( + &meta_name_value.value, + "Expected a JSON object", + )); + } + instance.meta = Some(value); + } + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + "title" => { + let value = match &meta_name_value.value { + Expr::Lit(ExprLit { + lit: Lit::Str(lit_str), + .. + }) => lit_str.value(), + _ => { + return Err(Error::new_spanned( + &meta_name_value.value, + "Expected a string literal", + )); + } + }; + instance.title = Some(value); + } "destructive_hint" | "idempotent_hint" | "open_world_hint" | "read_only_hint" => { - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] { let value = match &meta_name_value.value { Expr::Lit(ExprLit { @@ -160,22 +213,6 @@ impl Parse for McpToolMacroAttributes { } } } - #[cfg(feature = "2025_03_26")] - "title" => { - let value = match &meta_name_value.value { - Expr::Lit(ExprLit { - lit: Lit::Str(lit_str), - .. - }) => lit_str.value(), - _ => { - return Err(Error::new_spanned( - &meta_name_value.value, - "Expected a string literal", - )); - } - }; - instance.title = Some(value); - } _ => {} } } @@ -214,39 +251,46 @@ impl Parse for McpToolMacroAttributes { /// The `mcp_tool` macro generates an implementation for the annotated struct that includes: /// - A `tool_name()` method returning the tool's name as a string. /// - A `tool()` method returning a `rust_mcp_schema::Tool` instance with the tool's name, -/// description, and input schema derived from the struct's fields. +/// description, input schema, meta, and title derived from the struct's fields and attributes. /// /// # Attributes /// * `name` - The name of the tool (required, non-empty string). /// * `description` - A description of the tool (required, non-empty string). +/// * `meta` - Optional JSON object as a string literal for metadata. +/// * `title` - Optional string for the tool's title. /// /// # Panics /// Panics if the macro is applied to anything other than a struct. /// /// # Example /// ```rust -/// #[rust_mcp_macros::mcp_tool(name = "example_tool", description = "An example tool", idempotent_hint=true )] +/// #[rust_mcp_macros::mcp_tool( +/// name = "example_tool", +/// description = "An example tool", +/// meta = "{\"version\": \"1.0\"}", +/// title = "Example Tool" +/// )] /// #[derive(rust_mcp_macros::JsonSchema)] /// struct ExampleTool { /// field1: String, /// field2: i32, /// } /// -/// assert_eq!(ExampleTool::tool_name() , "example_tool"); -/// let tool : rust_mcp_schema::Tool = ExampleTool::tool(); -/// assert_eq!(tool.name , "example_tool"); -/// assert_eq!(tool.description.unwrap() , "An example tool"); -/// assert_eq!(tool.annotations.unwrap().idempotent_hint.unwrap() , true); +/// assert_eq!(ExampleTool::tool_name(), "example_tool"); +/// let tool: rust_mcp_schema::Tool = ExampleTool::tool(); +/// assert_eq!(tool.name, "example_tool"); +/// assert_eq!(tool.description.unwrap(), "An example tool"); +/// assert_eq!(tool.meta.as_ref().unwrap().get("version").unwrap(), "1.0"); +/// assert_eq!(tool.title.unwrap(), "Example Tool"); /// /// let schema_properties = tool.input_schema.properties.unwrap(); -/// assert_eq!(schema_properties.len() , 2); +/// assert_eq!(schema_properties.len(), 2); /// assert!(schema_properties.contains_key("field1")); /// assert!(schema_properties.contains_key("field2")); -/// /// ``` #[proc_macro_attribute] pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); // Parse the input as a function + let input = parse_macro_input!(input as DeriveInput); let input_ident = &input.ident; // Conditionally select the path for Tool @@ -261,14 +305,28 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { let tool_name = macro_attributes.name.unwrap_or_default(); let tool_description = macro_attributes.description.unwrap_or_default(); - #[cfg(feature = "2025_03_26")] + #[cfg(not(any(feature = "2025_03_26", feature = "2025_06_18")))] + let meta = quote! {}; + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + let meta = macro_attributes.meta.map_or(quote! { meta: None }, |m| { + quote! { meta: Some(serde_json::from_str(#m).expect("Failed to parse meta JSON")) } + }); + + #[cfg(not(any(feature = "2025_03_26", feature = "2025_06_18")))] + let title = quote! {}; + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + let title = macro_attributes.title.map_or( + quote! { title: None }, + |t| quote! { title: Some(#t.to_string()) }, + ); + + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] let some_annotations = macro_attributes.destructive_hint.is_some() || macro_attributes.idempotent_hint.is_some() || macro_attributes.open_world_hint.is_some() - || macro_attributes.read_only_hint.is_some() - || macro_attributes.title.is_some(); + || macro_attributes.read_only_hint.is_some(); - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] let annotations = if some_annotations { let destructive_hint = macro_attributes .destructive_hint @@ -283,28 +341,25 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { let read_only_hint = macro_attributes .read_only_hint .map_or(quote! {None}, |v| quote! {Some(#v)}); - let title = macro_attributes - .title - .map_or(quote! {None}, |v| quote! {Some(#v)}); quote! { Some(#base_crate::ToolAnnotations { - destructive_hint: #destructive_hint, - idempotent_hint: #idempotent_hint, - open_world_hint: #open_world_hint, - read_only_hint: #read_only_hint, - title: #title, - }), + destructive_hint: #destructive_hint, + idempotent_hint: #idempotent_hint, + open_world_hint: #open_world_hint, + read_only_hint: #read_only_hint, + title: None, + }) } } else { - quote! {None} + quote! { None } }; let annotations_token = { - #[cfg(feature = "2025_03_26")] + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] { quote! { annotations: #annotations } } - #[cfg(not(feature = "2025_03_26"))] + #[cfg(not(any(feature = "2025_03_26", feature = "2025_06_18")))] { quote! {} } @@ -315,6 +370,9 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { name: #tool_name.to_string(), description: Some(#tool_description.to_string()), input_schema: #base_crate::ToolInputSchema::new(required, properties), + output_schema: None, + #title, + #meta, #annotations_token } }; @@ -322,16 +380,15 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { let output = quote! { impl #input_ident { /// Returns the name of the tool as a string. - pub fn tool_name()->String{ + pub fn tool_name() -> String { #tool_name.to_string() } /// Constructs and returns a `rust_mcp_schema::Tool` instance. /// - /// The tool includes the name, description, and input schema derived from + /// The tool includes the name, description, input schema, meta, and title derived from /// the struct's attributes. - pub fn tool()-> #base_crate::Tool - { + pub fn tool() -> #base_crate::Tool { let json_schema = &#input_ident::json_schema(); let required: Vec<_> = match json_schema.get("required").and_then(|r| r.as_array()) { @@ -482,11 +539,13 @@ mod tests { use syn::parse_str; #[test] fn test_valid_macro_attributes() { - let input = r#"name = "test_tool", description = "A test tool.""#; + let input = r#"name = "test_tool", description = "A test tool.", meta = "{\"version\": \"1.0\"}", title = "Test Tool""#; let parsed: McpToolMacroAttributes = parse_str(input).unwrap(); assert_eq!(parsed.name.unwrap(), "test_tool"); assert_eq!(parsed.description.unwrap(), "A test tool."); + assert_eq!(parsed.meta.unwrap(), "{\"version\": \"1.0\"}"); + assert_eq!(parsed.title.unwrap(), "Test Tool"); } #[test] @@ -497,7 +556,7 @@ mod tests { assert_eq!( result.err().unwrap().to_string(), "The 'name' attribute is required and must not be empty." - ) + ); } #[test] @@ -508,7 +567,7 @@ mod tests { assert_eq!( result.err().unwrap().to_string(), "The 'description' attribute is required and must not be empty." - ) + ); } #[test] @@ -521,6 +580,7 @@ mod tests { "The 'name' attribute is required and must not be empty." ); } + #[test] fn test_empty_description_field() { let input = r#"name = "my-tool", description = """#; @@ -531,4 +591,25 @@ mod tests { "The 'description' attribute is required and must not be empty." ); } + + #[test] + fn test_invalid_meta() { + let input = + r#"name = "test_tool", description = "A test tool.", meta = "not_a_json_object""#; + let result: Result = parse_str(input); + assert!(result.is_err()); + assert!(result + .err() + .unwrap() + .to_string() + .contains("Expected a valid JSON object")); + } + + #[test] + fn test_non_object_meta() { + let input = r#"name = "test_tool", description = "A test tool.", meta = "[1, 2, 3]""#; + let result: Result = parse_str(input); + assert!(result.is_err()); + assert_eq!(result.err().unwrap().to_string(), "Expected a JSON object"); + } } diff --git a/crates/rust-mcp-sdk/Cargo.toml b/crates/rust-mcp-sdk/Cargo.toml index 01f3f50..99ef49b 100644 --- a/crates/rust-mcp-sdk/Cargo.toml +++ b/crates/rust-mcp-sdk/Cargo.toml @@ -13,7 +13,7 @@ exclude = ["assets/", "tests/"] [dependencies] rust-mcp-schema = { workspace = true, default-features = false } -rust-mcp-transport = { workspace = true, default-features = false, optional = true } +rust-mcp-transport = { workspace = true, default-features = false } rust-mcp-macros = { workspace = true, optional = true, default-features = false } tokio.workspace = true @@ -53,7 +53,7 @@ default = [ "macros", "hyper-server", "ssl", - "2025_03_26", + "2025_06_18", ] # All features enabled by default server = ["rust-mcp-transport/stdio"] # Server feature client = ["rust-mcp-transport/stdio", "rust-mcp-transport/sse"] # Client feature @@ -69,7 +69,15 @@ hyper-server = [ ssl = ["axum-server/tls-rustls"] macros = ["rust-mcp-macros/sdk"] -# enabled mcp protocol version 2025_03_26 +# enables mcp protocol version 2025_06_18 +2025_06_18 = [ + "rust-mcp-schema/2025_06_18", + "rust-mcp-macros/2025_06_18", + "rust-mcp-transport/2025_06_18", + "rust-mcp-schema/schema_utils", +] + +# enables mcp protocol version 2025_03_26 2025_03_26 = [ "rust-mcp-schema/2025_03_26", "rust-mcp-macros/2025_03_26", @@ -77,7 +85,7 @@ macros = ["rust-mcp-macros/sdk"] "rust-mcp-schema/schema_utils", ] -# enabled mcp protocol version 2024_11_05 +# enables mcp protocol version 2024_11_05 2024_11_05 = [ "rust-mcp-schema/2024_11_05", "rust-mcp-macros/2024_11_05", diff --git a/crates/rust-mcp-sdk/src/mcp_handlers/mcp_client_handler.rs b/crates/rust-mcp-sdk/src/mcp_handlers/mcp_client_handler.rs index 5f218bd..f8ee1a0 100644 --- a/crates/rust-mcp-sdk/src/mcp_handlers/mcp_client_handler.rs +++ b/crates/rust-mcp-sdk/src/mcp_handlers/mcp_client_handler.rs @@ -4,6 +4,9 @@ use crate::schema::{ PromptListChangedNotification, ResourceListChangedNotification, ResourceUpdatedNotification, Result, RpcError, ToolListChangedNotification, }; +#[cfg(feature = "2025_06_18")] +use crate::schema::{ElicitRequest, ElicitResult}; + use async_trait::async_trait; use serde_json::Value; @@ -50,6 +53,19 @@ pub trait ClientHandler: Send + Sync + 'static { ))) } + #[cfg(feature = "2025_06_18")] + async fn handle_elicit_request( + &self, + request: ElicitRequest, + runtime: &dyn McpClient, + ) -> std::result::Result { + runtime.assert_client_request_capabilities(request.method())?; + Err(RpcError::method_not_found().with_message(format!( + "No handler is implemented for '{}'.", + request.method(), + ))) + } + async fn handle_custom_request( &self, request: Value, diff --git a/crates/rust-mcp-sdk/src/mcp_runtimes/client_runtime/mcp_client_runtime.rs b/crates/rust-mcp-sdk/src/mcp_runtimes/client_runtime/mcp_client_runtime.rs index 17ae6c9..f3a1c79 100644 --- a/crates/rust-mcp-sdk/src/mcp_runtimes/client_runtime/mcp_client_runtime.rs +++ b/crates/rust-mcp-sdk/src/mcp_runtimes/client_runtime/mcp_client_runtime.rs @@ -88,6 +88,12 @@ impl McpClientHandler for ClientInternalHandler> { .handle_list_roots_request(list_roots_request, runtime) .await .map(|value| value.into()), + #[cfg(feature = "2025_06_18")] + ServerRequest::ElicitRequest(elicit_request) => self + .handler + .handle_elicit_request(elicit_request, runtime) + .await + .map(|value| value.into()), }, // Handles custom notifications received from the server by passing the request to self.handler RequestFromServer::CustomRequest(custom_request) => self diff --git a/crates/rust-mcp-sdk/src/mcp_runtimes/server_runtime/mcp_server_runtime.rs b/crates/rust-mcp-sdk/src/mcp_runtimes/server_runtime/mcp_server_runtime.rs index b3cf6c6..35d615a 100644 --- a/crates/rust-mcp-sdk/src/mcp_runtimes/server_runtime/mcp_server_runtime.rs +++ b/crates/rust-mcp-sdk/src/mcp_runtimes/server_runtime/mcp_server_runtime.rs @@ -142,7 +142,10 @@ impl McpServerHandler for ServerRuntimeInternalHandler> { .await; Ok(result.map_or_else( - |err| CallToolResult::with_error(CallToolError::new(err)).into(), + |err| { + let r: CallToolResult = CallToolError::new(err).into(); + r.into() + }, |value| value.into(), )) } diff --git a/crates/rust-mcp-sdk/src/schema.rs b/crates/rust-mcp-sdk/src/schema.rs index 35c676d..2c7e7b4 100644 --- a/crates/rust-mcp-sdk/src/schema.rs +++ b/crates/rust-mcp-sdk/src/schema.rs @@ -1,5 +1,14 @@ -#[cfg(feature = "2025_03_26")] +#[cfg(feature = "2025_06_18")] pub use rust_mcp_schema::*; -#[cfg(all(feature = "2024_11_05", not(any(feature = "2025_03_26"))))] +#[cfg(all( + feature = "2025_03_26", + not(any(feature = "2024_11_05", feature = "2025_06_18")) +))] +pub use rust_mcp_schema::mcp_2025_03_26::*; + +#[cfg(all( + feature = "2024_11_05", + not(any(feature = "2025_03_26", feature = "2025_06_18")) +))] pub use rust_mcp_schema::mcp_2024_11_05::*; diff --git a/crates/rust-mcp-sdk/tests/common/common.rs b/crates/rust-mcp-sdk/tests/common/common.rs index b72a08f..c9d88c9 100644 --- a/crates/rust-mcp-sdk/tests/common/common.rs +++ b/crates/rust-mcp-sdk/tests/common/common.rs @@ -1,9 +1,11 @@ mod test_server; use async_trait::async_trait; +use rust_mcp_schema::ProtocolVersion; use rust_mcp_sdk::mcp_client::ClientHandler; use rust_mcp_sdk::schema::{ ClientCapabilities, Implementation, InitializeRequestParams, LATEST_PROTOCOL_VERSION, }; + pub use test_server::*; pub const NPX_SERVER_EVERYTHING: &str = "@modelcontextprotocol/server-everything"; @@ -17,8 +19,10 @@ pub fn test_client_info() -> InitializeRequestParams { client_info: Implementation { name: "test-rust-mcp-client".into(), version: "0.1.0".into(), + #[cfg(feature = "2025_06_18")] + title: None, }, - protocol_version: LATEST_PROTOCOL_VERSION.into(), + protocol_version: ProtocolVersion::V2025_03_26.to_string(), } } @@ -36,6 +40,7 @@ pub fn sse_data(sse_raw: &str) -> String { } pub mod sample_tools { + #[cfg(feature = "2025_06_18")] use rust_mcp_sdk::macros::{mcp_tool, JsonSchema}; use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; @@ -59,7 +64,13 @@ pub mod sample_tools { impl SayHelloTool { pub fn call_tool(&self) -> Result { let hello_message = format!("Hello, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + + #[cfg(feature = "2025_06_18")] + return Ok(CallToolResult::text_content(vec![ + rust_mcp_sdk::schema::TextContent::from(hello_message), + ])); + #[cfg(not(feature = "2025_06_18"))] + return Ok(CallToolResult::text_content(hello_message, None)); } } @@ -81,8 +92,14 @@ pub mod sample_tools { } impl SayGoodbyeTool { pub fn call_tool(&self) -> Result { - let hello_message = format!("Goodbye, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + let goodbye_message = format!("Goodbye, {}!", self.name); + + #[cfg(feature = "2025_06_18")] + return Ok(CallToolResult::text_content(vec![ + rust_mcp_sdk::schema::TextContent::from(goodbye_message), + ])); + #[cfg(not(feature = "2025_06_18"))] + return Ok(CallToolResult::text_content(goodbye_message, None)); } } } diff --git a/crates/rust-mcp-sdk/tests/common/test_server.rs b/crates/rust-mcp-sdk/tests/common/test_server.rs index ea2afb5..0ea8d0a 100644 --- a/crates/rust-mcp-sdk/tests/common/test_server.rs +++ b/crates/rust-mcp-sdk/tests/common/test_server.rs @@ -23,6 +23,8 @@ pub mod test_server_common { server_info: Implementation { name: "Test MCP Server".to_string(), version: "0.1.0".to_string(), + #[cfg(feature = "2025_06_18")] + title: None, }, capabilities: ServerCapabilities { // indicates that server support mcp tools diff --git a/crates/rust-mcp-transport/Cargo.toml b/crates/rust-mcp-transport/Cargo.toml index efdbbf4..6644a1a 100644 --- a/crates/rust-mcp-transport/Cargo.toml +++ b/crates/rust-mcp-transport/Cargo.toml @@ -42,10 +42,14 @@ workspace = true ### FEATURES ################################################################# [features] -default = ["stdio", "sse", "2025_03_26"] # Default features +default = ["stdio", "sse", "2025_06_18"] # Default features stdio = [] sse = ["reqwest"] + +# enabled mcp protocol version 2025_06_18 +2025_06_18 = ["rust-mcp-schema/2025_06_18", "rust-mcp-schema/schema_utils"] + # enabled mcp protocol version 2025_03_26 2025_03_26 = ["rust-mcp-schema/2025_03_26", "rust-mcp-schema/schema_utils"] diff --git a/crates/rust-mcp-transport/src/schema.rs b/crates/rust-mcp-transport/src/schema.rs index 35c676d..2c7e7b4 100644 --- a/crates/rust-mcp-transport/src/schema.rs +++ b/crates/rust-mcp-transport/src/schema.rs @@ -1,5 +1,14 @@ -#[cfg(feature = "2025_03_26")] +#[cfg(feature = "2025_06_18")] pub use rust_mcp_schema::*; -#[cfg(all(feature = "2024_11_05", not(any(feature = "2025_03_26"))))] +#[cfg(all( + feature = "2025_03_26", + not(any(feature = "2024_11_05", feature = "2025_06_18")) +))] +pub use rust_mcp_schema::mcp_2025_03_26::*; + +#[cfg(all( + feature = "2024_11_05", + not(any(feature = "2025_03_26", feature = "2025_06_18")) +))] pub use rust_mcp_schema::mcp_2024_11_05::*; diff --git a/examples/hello-world-mcp-server-core/Cargo.toml b/examples/hello-world-mcp-server-core/Cargo.toml index 755ca4c..7e4e0e7 100644 --- a/examples/hello-world-mcp-server-core/Cargo.toml +++ b/examples/hello-world-mcp-server-core/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" rust-mcp-sdk = { workspace = true, default-features = false, features = [ "server", "macros", - "2025_03_26" + "2025_06_18", ] } tokio = { workspace = true } diff --git a/examples/hello-world-mcp-server-core/src/main.rs b/examples/hello-world-mcp-server-core/src/main.rs index 1bb9f72..d410526 100644 --- a/examples/hello-world-mcp-server-core/src/main.rs +++ b/examples/hello-world-mcp-server-core/src/main.rs @@ -18,6 +18,7 @@ async fn main() -> SdkResult<()> { server_info: Implementation { name: "Hello World MCP Server".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools diff --git a/examples/hello-world-mcp-server-core/src/tools.rs b/examples/hello-world-mcp-server-core/src/tools.rs index 2212192..70533f2 100644 --- a/examples/hello-world-mcp-server-core/src/tools.rs +++ b/examples/hello-world-mcp-server-core/src/tools.rs @@ -1,4 +1,4 @@ -use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; +use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult, TextContent}; use rust_mcp_sdk::{ macros::{mcp_tool, JsonSchema}, tool_box, @@ -20,7 +20,9 @@ pub struct SayHelloTool { impl SayHelloTool { pub fn call_tool(&self) -> Result { let hello_message = format!("Hello, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + hello_message, + )])) } } @@ -38,8 +40,10 @@ pub struct SayGoodbyeTool { } impl SayGoodbyeTool { pub fn call_tool(&self) -> Result { - let hello_message = format!("Goodbye, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + let goodbye_message = format!("Goodbye, {}!", self.name); + Ok(CallToolResult::text_content(vec![TextContent::from( + goodbye_message, + )])) } } diff --git a/examples/hello-world-mcp-server/Cargo.toml b/examples/hello-world-mcp-server/Cargo.toml index 474e235..aae9b9f 100644 --- a/examples/hello-world-mcp-server/Cargo.toml +++ b/examples/hello-world-mcp-server/Cargo.toml @@ -12,7 +12,7 @@ rust-mcp-sdk = { workspace = true, default-features = false, features = [ "macros", "hyper-server", "ssl", - "2025_03_26", + "2025_06_18", ] } tokio = { workspace = true } diff --git a/examples/hello-world-mcp-server/src/main.rs b/examples/hello-world-mcp-server/src/main.rs index d946fd2..00ca6a7 100644 --- a/examples/hello-world-mcp-server/src/main.rs +++ b/examples/hello-world-mcp-server/src/main.rs @@ -21,6 +21,7 @@ async fn main() -> SdkResult<()> { server_info: Implementation { name: "Hello World MCP Server".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools diff --git a/examples/hello-world-mcp-server/src/tools.rs b/examples/hello-world-mcp-server/src/tools.rs index 5d87715..15d6a8b 100644 --- a/examples/hello-world-mcp-server/src/tools.rs +++ b/examples/hello-world-mcp-server/src/tools.rs @@ -1,4 +1,4 @@ -use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; +use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult, TextContent}; use rust_mcp_sdk::{ macros::{mcp_tool, JsonSchema}, tool_box, @@ -10,10 +10,12 @@ use rust_mcp_sdk::{ #[mcp_tool( name = "say_hello", description = "Accepts a person's name and says a personalized \"Hello\" to that person", + title = "A tool that says hello!", idempotent_hint = false, destructive_hint = false, open_world_hint = false, - read_only_hint = false + read_only_hint = false, + meta = r#"{"version": "1.0"}"# )] #[derive(Debug, ::serde::Deserialize, ::serde::Serialize, JsonSchema)] pub struct SayHelloTool { @@ -24,7 +26,9 @@ pub struct SayHelloTool { impl SayHelloTool { pub fn call_tool(&self) -> Result { let hello_message = format!("Hello, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + hello_message, + )])) } } @@ -46,8 +50,10 @@ pub struct SayGoodbyeTool { } impl SayGoodbyeTool { pub fn call_tool(&self) -> Result { - let hello_message = format!("Goodbye, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + let goodbye_message = format!("Goodbye, {}!", self.name); + Ok(CallToolResult::text_content(vec![TextContent::from( + goodbye_message, + )])) } } diff --git a/examples/hello-world-server-core-sse/Cargo.toml b/examples/hello-world-server-core-sse/Cargo.toml index 920d9cb..1158eeb 100644 --- a/examples/hello-world-server-core-sse/Cargo.toml +++ b/examples/hello-world-server-core-sse/Cargo.toml @@ -11,7 +11,7 @@ rust-mcp-sdk = { workspace = true, default-features = false, features = [ "server", "macros", "hyper-server", - "2025_03_26" + "2025_06_18", ] } tokio = { workspace = true } diff --git a/examples/hello-world-server-core-sse/src/main.rs b/examples/hello-world-server-core-sse/src/main.rs index f58a3c7..31a5620 100644 --- a/examples/hello-world-server-core-sse/src/main.rs +++ b/examples/hello-world-server-core-sse/src/main.rs @@ -28,6 +28,7 @@ async fn main() -> SdkResult<()> { server_info: Implementation { name: "Hello World MCP Server SSE".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server SSE".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools diff --git a/examples/hello-world-server-core-sse/src/tools.rs b/examples/hello-world-server-core-sse/src/tools.rs index 2212192..70533f2 100644 --- a/examples/hello-world-server-core-sse/src/tools.rs +++ b/examples/hello-world-server-core-sse/src/tools.rs @@ -1,4 +1,4 @@ -use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; +use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult, TextContent}; use rust_mcp_sdk::{ macros::{mcp_tool, JsonSchema}, tool_box, @@ -20,7 +20,9 @@ pub struct SayHelloTool { impl SayHelloTool { pub fn call_tool(&self) -> Result { let hello_message = format!("Hello, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + hello_message, + )])) } } @@ -38,8 +40,10 @@ pub struct SayGoodbyeTool { } impl SayGoodbyeTool { pub fn call_tool(&self) -> Result { - let hello_message = format!("Goodbye, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + let goodbye_message = format!("Goodbye, {}!", self.name); + Ok(CallToolResult::text_content(vec![TextContent::from( + goodbye_message, + )])) } } diff --git a/examples/hello-world-server-sse/Cargo.toml b/examples/hello-world-server-sse/Cargo.toml index 73c5efb..263243e 100644 --- a/examples/hello-world-server-sse/Cargo.toml +++ b/examples/hello-world-server-sse/Cargo.toml @@ -11,7 +11,7 @@ rust-mcp-sdk = { workspace = true, default-features = false, features = [ "server", "macros", "hyper-server", - "2025_03_26" + "2025_06_18", ] } tokio = { workspace = true } diff --git a/examples/hello-world-server-sse/src/main.rs b/examples/hello-world-server-sse/src/main.rs index 2a4d49e..b5064f3 100644 --- a/examples/hello-world-server-sse/src/main.rs +++ b/examples/hello-world-server-sse/src/main.rs @@ -36,6 +36,7 @@ async fn main() -> SdkResult<()> { server_info: Implementation { name: "Hello World MCP Server SSE".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server SSE".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools diff --git a/examples/hello-world-server-sse/src/tools.rs b/examples/hello-world-server-sse/src/tools.rs index 2212192..70533f2 100644 --- a/examples/hello-world-server-sse/src/tools.rs +++ b/examples/hello-world-server-sse/src/tools.rs @@ -1,4 +1,4 @@ -use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult}; +use rust_mcp_sdk::schema::{schema_utils::CallToolError, CallToolResult, TextContent}; use rust_mcp_sdk::{ macros::{mcp_tool, JsonSchema}, tool_box, @@ -20,7 +20,9 @@ pub struct SayHelloTool { impl SayHelloTool { pub fn call_tool(&self) -> Result { let hello_message = format!("Hello, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + Ok(CallToolResult::text_content(vec![TextContent::from( + hello_message, + )])) } } @@ -38,8 +40,10 @@ pub struct SayGoodbyeTool { } impl SayGoodbyeTool { pub fn call_tool(&self) -> Result { - let hello_message = format!("Goodbye, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + let goodbye_message = format!("Goodbye, {}!", self.name); + Ok(CallToolResult::text_content(vec![TextContent::from( + goodbye_message, + )])) } } diff --git a/examples/simple-mcp-client-core-sse/Cargo.toml b/examples/simple-mcp-client-core-sse/Cargo.toml index 279914f..3516bdf 100644 --- a/examples/simple-mcp-client-core-sse/Cargo.toml +++ b/examples/simple-mcp-client-core-sse/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" rust-mcp-sdk = { workspace = true, default-features = false, features = [ "client", "macros", - "2024_11_05", + "2025_06_18", ] } tokio = { workspace = true } diff --git a/examples/simple-mcp-client-core-sse/src/handler.rs b/examples/simple-mcp-client-core-sse/src/handler.rs index b8bcbc0..a1a95e4 100644 --- a/examples/simple-mcp-client-core-sse/src/handler.rs +++ b/examples/simple-mcp-client-core-sse/src/handler.rs @@ -31,6 +31,8 @@ impl ClientHandlerCore for MyClientHandler { Err(RpcError::internal_error() .with_message("ListRootsRequest handler is not implemented".to_string())) } + ServerRequest::ElicitRequest(_elicit_request) => Err(RpcError::internal_error() + .with_message("ElicitRequest handler is not implemented".to_string())), }, RequestFromServer::CustomRequest(_value) => Err(RpcError::internal_error() .with_message("CustomRequest handler is not implemented".to_string())), diff --git a/examples/simple-mcp-client-core-sse/src/main.rs b/examples/simple-mcp-client-core-sse/src/main.rs index 787c8c9..48d2e0d 100644 --- a/examples/simple-mcp-client-core-sse/src/main.rs +++ b/examples/simple-mcp-client-core-sse/src/main.rs @@ -31,8 +31,9 @@ async fn main() -> SdkResult<()> { let client_details: InitializeRequestParams = InitializeRequestParams { capabilities: ClientCapabilities::default(), client_info: Implementation { - name: "simple-rust-mcp-client-core-sse".into(), - version: "0.1.0".into(), + name: "simple-rust-mcp-client-core-sse".to_string(), + version: "0.1.0".to_string(), + title: Some("Simple Rust MCP Client (Core,SSE)".to_string()), }, protocol_version: LATEST_PROTOCOL_VERSION.into(), }; diff --git a/examples/simple-mcp-client-core/Cargo.toml b/examples/simple-mcp-client-core/Cargo.toml index e7871c1..76505a6 100644 --- a/examples/simple-mcp-client-core/Cargo.toml +++ b/examples/simple-mcp-client-core/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" rust-mcp-sdk = { workspace = true, default-features = false, features = [ "client", "macros", - "2024_11_05", + "2025_06_18", ] } tokio = { workspace = true } diff --git a/examples/simple-mcp-client-core/src/handler.rs b/examples/simple-mcp-client-core/src/handler.rs index b8bcbc0..a1a95e4 100644 --- a/examples/simple-mcp-client-core/src/handler.rs +++ b/examples/simple-mcp-client-core/src/handler.rs @@ -31,6 +31,8 @@ impl ClientHandlerCore for MyClientHandler { Err(RpcError::internal_error() .with_message("ListRootsRequest handler is not implemented".to_string())) } + ServerRequest::ElicitRequest(_elicit_request) => Err(RpcError::internal_error() + .with_message("ElicitRequest handler is not implemented".to_string())), }, RequestFromServer::CustomRequest(_value) => Err(RpcError::internal_error() .with_message("CustomRequest handler is not implemented".to_string())), diff --git a/examples/simple-mcp-client-core/src/main.rs b/examples/simple-mcp-client-core/src/main.rs index 86e9489..c129239 100644 --- a/examples/simple-mcp-client-core/src/main.rs +++ b/examples/simple-mcp-client-core/src/main.rs @@ -19,8 +19,9 @@ async fn main() -> SdkResult<()> { let client_details: InitializeRequestParams = InitializeRequestParams { capabilities: ClientCapabilities::default(), client_info: Implementation { - name: "simple-rust-mcp-client-core".into(), + name: "simple-rust-mcp-client-core".to_string(), version: "0.1.0".into(), + title: Some("Simple Rust MCP Client Core".to_string()), }, protocol_version: LATEST_PROTOCOL_VERSION.into(), }; diff --git a/examples/simple-mcp-client-sse/Cargo.toml b/examples/simple-mcp-client-sse/Cargo.toml index 8722319..83909b7 100644 --- a/examples/simple-mcp-client-sse/Cargo.toml +++ b/examples/simple-mcp-client-sse/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" rust-mcp-sdk = { workspace = true, default-features = false, features = [ "client", "macros", - "2024_11_05", + "2025_06_18", ] } tokio = { workspace = true } diff --git a/examples/simple-mcp-client-sse/src/main.rs b/examples/simple-mcp-client-sse/src/main.rs index 7bb9e92..64886b2 100644 --- a/examples/simple-mcp-client-sse/src/main.rs +++ b/examples/simple-mcp-client-sse/src/main.rs @@ -31,8 +31,9 @@ async fn main() -> SdkResult<()> { let client_details: InitializeRequestParams = InitializeRequestParams { capabilities: ClientCapabilities::default(), client_info: Implementation { - name: "simple-rust-mcp-client-sse".into(), - version: "0.1.0".into(), + name: "simple-rust-mcp-client-sse".to_string(), + version: "0.1.0".to_string(), + title: Some("Simple Rust MCP Client (SSE)".to_string()), }, protocol_version: LATEST_PROTOCOL_VERSION.into(), }; diff --git a/examples/simple-mcp-client/Cargo.toml b/examples/simple-mcp-client/Cargo.toml index 7a76176..832adef 100644 --- a/examples/simple-mcp-client/Cargo.toml +++ b/examples/simple-mcp-client/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" rust-mcp-sdk = { workspace = true, default-features = false, features = [ "client", "macros", - "2024_11_05", + "2025_06_18", ] } tokio = { workspace = true } diff --git a/examples/simple-mcp-client/src/main.rs b/examples/simple-mcp-client/src/main.rs index 1fa3fbe..e2bb3ab 100644 --- a/examples/simple-mcp-client/src/main.rs +++ b/examples/simple-mcp-client/src/main.rs @@ -20,8 +20,9 @@ async fn main() -> SdkResult<()> { let client_details: InitializeRequestParams = InitializeRequestParams { capabilities: ClientCapabilities::default(), client_info: Implementation { - name: "simple-rust-mcp-client".into(), - version: "0.1.0".into(), + name: "simple-rust-mcp-client".to_string(), + version: "0.1.0".to_string(), + title: Some("Simple Rust MCP Client".to_string()), }, protocol_version: LATEST_PROTOCOL_VERSION.into(), }; From 89a4bcc0c202fef5a6794a0840c70a660493a7c3 Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Wed, 2 Jul 2025 18:59:10 -0300 Subject: [PATCH 2/8] fix: update macro version compability --- crates/rust-mcp-macros/src/lib.rs | 35 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/crates/rust-mcp-macros/src/lib.rs b/crates/rust-mcp-macros/src/lib.rs index 8981223..f509256 100644 --- a/crates/rust-mcp-macros/src/lib.rs +++ b/crates/rust-mcp-macros/src/lib.rs @@ -29,9 +29,9 @@ use utils::{is_option, renamed_field, type_to_json_schema}; struct McpToolMacroAttributes { name: Option, description: Option, - #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + #[cfg(feature = "2025_06_18")] meta: Option, // Store raw JSON string instead of parsed Map - #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + #[cfg(feature = "2025_06_18")] title: Option, #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] destructive_hint: Option, @@ -74,9 +74,9 @@ impl Parse for McpToolMacroAttributes { let mut instance = Self { name: None, description: None, - #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + #[cfg(feature = "2025_06_18")] meta: None, - #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + #[cfg(feature = "2025_06_18")] title: None, #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] destructive_hint: None, @@ -141,7 +141,7 @@ impl Parse for McpToolMacroAttributes { _ => {} } } - #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + #[cfg(feature = "2025_06_18")] "meta" => { let value = match &meta_name_value.value { Expr::Lit(ExprLit { @@ -171,7 +171,7 @@ impl Parse for McpToolMacroAttributes { } instance.meta = Some(value); } - #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + #[cfg(feature = "2025_06_18")] "title" => { let value = match &meta_name_value.value { Expr::Lit(ExprLit { @@ -305,21 +305,26 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { let tool_name = macro_attributes.name.unwrap_or_default(); let tool_description = macro_attributes.description.unwrap_or_default(); - #[cfg(not(any(feature = "2025_03_26", feature = "2025_06_18")))] + #[cfg(not(feature = "2025_06_18"))] let meta = quote! {}; - #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + #[cfg(feature = "2025_06_18")] let meta = macro_attributes.meta.map_or(quote! { meta: None }, |m| { - quote! { meta: Some(serde_json::from_str(#m).expect("Failed to parse meta JSON")) } + quote! { meta: Some(serde_json::from_str(#m).expect("Failed to parse meta JSON")), } }); - #[cfg(not(any(feature = "2025_03_26", feature = "2025_06_18")))] + #[cfg(not(feature = "2025_06_18"))] let title = quote! {}; - #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] + #[cfg(feature = "2025_06_18")] let title = macro_attributes.title.map_or( quote! { title: None }, - |t| quote! { title: Some(#t.to_string()) }, + |t| quote! { title: Some(#t.to_string()), }, ); + #[cfg(not(feature = "2025_06_18"))] + let output_schema = quote! {}; + #[cfg(feature = "2025_06_18")] + let output_schema = quote! { output_schema: None,}; + #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] let some_annotations = macro_attributes.destructive_hint.is_some() || macro_attributes.idempotent_hint.is_some() @@ -370,9 +375,9 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { name: #tool_name.to_string(), description: Some(#tool_description.to_string()), input_schema: #base_crate::ToolInputSchema::new(required, properties), - output_schema: None, - #title, - #meta, + #output_schema + #title + #meta #annotations_token } }; From de292604116c7eb04854987f178f55e7c5ab477e Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Wed, 2 Jul 2025 19:30:04 -0300 Subject: [PATCH 3/8] fix: macro syntax --- crates/rust-mcp-macros/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/rust-mcp-macros/src/lib.rs b/crates/rust-mcp-macros/src/lib.rs index f509256..c86a3d0 100644 --- a/crates/rust-mcp-macros/src/lib.rs +++ b/crates/rust-mcp-macros/src/lib.rs @@ -308,7 +308,7 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { #[cfg(not(feature = "2025_06_18"))] let meta = quote! {}; #[cfg(feature = "2025_06_18")] - let meta = macro_attributes.meta.map_or(quote! { meta: None }, |m| { + let meta = macro_attributes.meta.map_or(quote! { meta: None, }, |m| { quote! { meta: Some(serde_json::from_str(#m).expect("Failed to parse meta JSON")), } }); @@ -316,7 +316,7 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { let title = quote! {}; #[cfg(feature = "2025_06_18")] let title = macro_attributes.title.map_or( - quote! { title: None }, + quote! { title: None, }, |t| quote! { title: Some(#t.to_string()), }, ); @@ -362,7 +362,7 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { let annotations_token = { #[cfg(any(feature = "2025_03_26", feature = "2025_06_18"))] { - quote! { annotations: #annotations } + quote! { annotations: #annotations, } } #[cfg(not(any(feature = "2025_03_26", feature = "2025_06_18")))] { @@ -374,11 +374,11 @@ pub fn mcp_tool(attributes: TokenStream, input: TokenStream) -> TokenStream { #base_crate::Tool { name: #tool_name.to_string(), description: Some(#tool_description.to_string()), - input_schema: #base_crate::ToolInputSchema::new(required, properties), #output_schema #title #meta #annotations_token + input_schema: #base_crate::ToolInputSchema::new(required, properties) } }; From 5e83de7d50ef5bb815fab646751a040f800b2f51 Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Wed, 2 Jul 2025 19:30:21 -0300 Subject: [PATCH 4/8] chore: remove unused import --- crates/rust-mcp-sdk/tests/common/common.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/rust-mcp-sdk/tests/common/common.rs b/crates/rust-mcp-sdk/tests/common/common.rs index c9d88c9..046e796 100644 --- a/crates/rust-mcp-sdk/tests/common/common.rs +++ b/crates/rust-mcp-sdk/tests/common/common.rs @@ -2,9 +2,7 @@ mod test_server; use async_trait::async_trait; use rust_mcp_schema::ProtocolVersion; use rust_mcp_sdk::mcp_client::ClientHandler; -use rust_mcp_sdk::schema::{ - ClientCapabilities, Implementation, InitializeRequestParams, LATEST_PROTOCOL_VERSION, -}; +use rust_mcp_sdk::schema::{ClientCapabilities, Implementation, InitializeRequestParams}; pub use test_server::*; From 3147577ab9c060f9342f47a553c5479027df9dc8 Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Thu, 3 Jul 2025 07:23:45 -0300 Subject: [PATCH 5/8] chore: update macro tests --- crates/rust-mcp-macros/tests/macro_test.rs | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/rust-mcp-macros/tests/macro_test.rs b/crates/rust-mcp-macros/tests/macro_test.rs index 4f24c9e..5ddb680 100644 --- a/crates/rust-mcp-macros/tests/macro_test.rs +++ b/crates/rust-mcp-macros/tests/macro_test.rs @@ -32,6 +32,58 @@ fn test_rename() { assert_eq!(properties.len(), 2); } +#[test] +#[cfg(feature = "2025_06_18")] +fn test_mcp_tool() { + use serde_json::{Map, Value}; + + #[rust_mcp_macros::mcp_tool( + name = "example_tool", + title = "Example Tool", + description = "An example tool", + idempotent_hint = true, + destructive_hint = true, + open_world_hint = true, + read_only_hint = true, + meta = r#"{ + "string_meta" : "meta value", + "numeric_meta" : 15 + }"# + )] + #[derive(rust_mcp_macros::JsonSchema)] + #[allow(unused)] + struct ExampleTool { + field1: String, + field2: i32, + } + + assert_eq!(ExampleTool::tool_name(), "example_tool"); + let tool: rust_mcp_schema::Tool = ExampleTool::tool(); + assert_eq!(tool.name, "example_tool"); + assert_eq!(tool.description.unwrap(), "An example tool"); + assert!(tool.annotations.as_ref().unwrap().idempotent_hint.unwrap(),); + assert!(tool.annotations.as_ref().unwrap().destructive_hint.unwrap(),); + assert!(tool.annotations.as_ref().unwrap().open_world_hint.unwrap(),); + assert!(tool.annotations.as_ref().unwrap().read_only_hint.unwrap(),); + + assert_eq!(tool.title.as_ref().unwrap(), "Example Tool"); + + let meta: &Map = tool.meta.as_ref().unwrap(); + // Assert that "string_meta" equals "meta value" + assert_eq!( + meta.get("string_meta").unwrap(), + &Value::String("meta value".to_string()) + ); + + // Assert that "numeric_meta" equals 15 + assert_eq!(meta.get("numeric_meta").unwrap(), &Value::Number(15.into())); + + let schema_properties = tool.input_schema.properties.unwrap(); + assert_eq!(schema_properties.len(), 2); + assert!(schema_properties.contains_key("field1")); + assert!(schema_properties.contains_key("field2")); +} + #[test] #[cfg(feature = "2025_03_26")] fn test_mcp_tool() { From 6be3dd9c955205b534dd2af152944e9c08976120 Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Thu, 3 Jul 2025 07:23:57 -0300 Subject: [PATCH 6/8] chore: update docs --- README.md | 7 +++---- crates/rust-mcp-macros/README.md | 21 +++++++++++++++++++++ crates/rust-mcp-sdk/README.md | 7 +++---- doc/getting-started-mcp-server.md | 5 +++-- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index bedaf27..76c4515 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ async fn main() -> SdkResult<()> { server_info: Implementation { name: "Hello World MCP Server".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools @@ -106,6 +107,7 @@ let server_details = InitializeResult { server_info: Implementation { name: "Hello World MCP Server".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools @@ -167,10 +169,7 @@ impl ServerHandler for MyServerHandler { async fn handle_call_tool_request( &self, request: CallToolRequest, runtime: &dyn McpServer, ) -> Result { if request.tool_name() == SayHelloTool::tool_name() { - Ok(CallToolResult::text_content( - "Hello World!".to_string(), - None, - )) + Ok( CallToolResult::text_content( vec![TextContent::from("Hello World!".to_string())] )) } else { Err(CallToolError::unknown_tool(request.tool_name().to_string())) } diff --git a/crates/rust-mcp-macros/README.md b/crates/rust-mcp-macros/README.md index 6f6c956..92da2c3 100644 --- a/crates/rust-mcp-macros/README.md +++ b/crates/rust-mcp-macros/README.md @@ -12,17 +12,31 @@ The `mcp_tool` macro generates an implementation for the annotated struct that i - `name` - The name of the tool (required, non-empty string). - `description` - A description of the tool (required, non-empty string). +- `title` - An optional human-readable and easily understood title. +- `meta` - An optional JSON string that provides additional metadata for the tool. +- `destructive_hint` – Optional boolean, indicates whether the tool may make destructive changes to its environment. +- `idempotent_hint` – Optional boolean, indicates whether repeated calls with the same input have the same effect. +- `open_world_hint` – Optional boolean, indicates whether the tool can interact with external or unknown entities. +- `read_only_hint` – Optional boolean, indicates whether the tool makes no modifications to its environment. + + ## Usage Example ```rust #[mcp_tool( name = "write_file", + title = "Write File Tool" description = "Create a new file or completely overwrite an existing file with new content." destructive_hint = false idempotent_hint = false open_world_hint = false read_only_hint = false + meta = r#"{ + "key" : "value", + "string_meta" : "meta value", + "numeric_meta" : 15 + }"# )] #[derive(rust_mcp_macros::JsonSchema)] pub struct WriteFileTool { @@ -38,8 +52,15 @@ fn main() { let tool: rust_mcp_schema::Tool = WriteFileTool::tool(); assert_eq!(tool.name, "write_file"); + assert_eq!(tool.title.as_ref().unwrap(), "Write File Tool"); assert_eq!( tool.description.unwrap(),"Create a new file or completely overwrite an existing file with new content."); + let meta: &Map = tool.meta.as_ref().unwrap(); + assert_eq!( + meta.get("key").unwrap(), + &Value::String("value".to_string()) + ); + let schema_properties = tool.input_schema.properties.unwrap(); assert_eq!(schema_properties.len(), 2); assert!(schema_properties.contains_key("path")); diff --git a/crates/rust-mcp-sdk/README.md b/crates/rust-mcp-sdk/README.md index bedaf27..96ab2bc 100644 --- a/crates/rust-mcp-sdk/README.md +++ b/crates/rust-mcp-sdk/README.md @@ -63,6 +63,7 @@ async fn main() -> SdkResult<()> { server_info: Implementation { name: "Hello World MCP Server".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools @@ -106,6 +107,7 @@ let server_details = InitializeResult { server_info: Implementation { name: "Hello World MCP Server".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools @@ -167,10 +169,7 @@ impl ServerHandler for MyServerHandler { async fn handle_call_tool_request( &self, request: CallToolRequest, runtime: &dyn McpServer, ) -> Result { if request.tool_name() == SayHelloTool::tool_name() { - Ok(CallToolResult::text_content( - "Hello World!".to_string(), - None, - )) + Ok(CallToolResult::text_content( vec![TextContent::from("Hello World!".to_string())] ) } else { Err(CallToolError::unknown_tool(request.tool_name().to_string())) } diff --git a/doc/getting-started-mcp-server.md b/doc/getting-started-mcp-server.md index 9b7ef98..3636cf3 100644 --- a/doc/getting-started-mcp-server.md +++ b/doc/getting-started-mcp-server.md @@ -93,7 +93,7 @@ pub struct SayHelloTool { impl SayHelloTool { pub fn call_tool(&self) -> Result { let hello_message = format!("Hello, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + Ok(CallToolResult::text_content( vec![TextContent::from(hello_message)] )) } } @@ -112,7 +112,7 @@ pub struct SayGoodbyeTool { impl SayGoodbyeTool { pub fn call_tool(&self) -> Result { let hello_message = format!("Goodbye, {}!", self.name); - Ok(CallToolResult::text_content(hello_message, None)) + Ok(CallToolResult::text_content( vec![TextContent::from(hello_message)] )) } } @@ -225,6 +225,7 @@ async fn main() -> SdkResult<()> { server_info: Implementation { name: "Hello World MCP Server".to_string(), version: "0.1.0".to_string(), + title: Some("Hello World MCP Server".to_string()), }, capabilities: ServerCapabilities { // indicates that server support mcp tools From 24f8a22b4ed137f25b4aae58b22a1d0e01dc8965 Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Thu, 3 Jul 2025 07:47:58 -0300 Subject: [PATCH 7/8] chore: update readme --- README.md | 9 ++++++++- crates/rust-mcp-sdk/README.md | 11 +++++++++-- .../mcp_runtimes/server_runtime/mcp_server_runtime.rs | 6 +++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 76c4515..1c73330 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ Focus on your app's logic while **rust-mcp-sdk** takes care of the rest! **rust-mcp-sdk** provides the necessary components for developing both servers and clients in the MCP ecosystem. Leveraging the [rust-mcp-schema](https://github.com/rust-mcp-stack/rust-mcp-schema) crate simplifies the process of building robust and reliable MCP servers and clients, ensuring consistency and minimizing errors in data handling and message processing. + +**rust-mcp-sdk** supports all three official versions of the MCP protocol. +By default, it uses the **2025-06-18** version, but earlier versions can be enabled via Cargo features. + + + This project currently supports following transports: - **stdio** (Standard Input/Output) - **sse** (Server-Sent Events). @@ -300,7 +306,8 @@ The `rust-mcp-sdk` crate provides several features that can be enabled or disabl #### MCP Protocol Versions with Corresponding Features -- `2025_03_26` : Activates MCP Protocol version 2025-03-26 (enabled by default) +- `2025_06_18` : Activates MCP Protocol version 2025-06-18 (enabled by default) +- `2025_03_26` : Activates MCP Protocol version 2025-03-26 - `2024_11_05` : Activates MCP Protocol version 2024-11-05 > Note: MCP protocol versions are mutually exclusive—only one can be active at any given time. diff --git a/crates/rust-mcp-sdk/README.md b/crates/rust-mcp-sdk/README.md index 96ab2bc..1c73330 100644 --- a/crates/rust-mcp-sdk/README.md +++ b/crates/rust-mcp-sdk/README.md @@ -17,6 +17,12 @@ Focus on your app's logic while **rust-mcp-sdk** takes care of the rest! **rust-mcp-sdk** provides the necessary components for developing both servers and clients in the MCP ecosystem. Leveraging the [rust-mcp-schema](https://github.com/rust-mcp-stack/rust-mcp-schema) crate simplifies the process of building robust and reliable MCP servers and clients, ensuring consistency and minimizing errors in data handling and message processing. + +**rust-mcp-sdk** supports all three official versions of the MCP protocol. +By default, it uses the **2025-06-18** version, but earlier versions can be enabled via Cargo features. + + + This project currently supports following transports: - **stdio** (Standard Input/Output) - **sse** (Server-Sent Events). @@ -169,7 +175,7 @@ impl ServerHandler for MyServerHandler { async fn handle_call_tool_request( &self, request: CallToolRequest, runtime: &dyn McpServer, ) -> Result { if request.tool_name() == SayHelloTool::tool_name() { - Ok(CallToolResult::text_content( vec![TextContent::from("Hello World!".to_string())] ) + Ok( CallToolResult::text_content( vec![TextContent::from("Hello World!".to_string())] )) } else { Err(CallToolError::unknown_tool(request.tool_name().to_string())) } @@ -300,7 +306,8 @@ The `rust-mcp-sdk` crate provides several features that can be enabled or disabl #### MCP Protocol Versions with Corresponding Features -- `2025_03_26` : Activates MCP Protocol version 2025-03-26 (enabled by default) +- `2025_06_18` : Activates MCP Protocol version 2025-06-18 (enabled by default) +- `2025_03_26` : Activates MCP Protocol version 2025-03-26 - `2024_11_05` : Activates MCP Protocol version 2024-11-05 > Note: MCP protocol versions are mutually exclusive—only one can be active at any given time. diff --git a/crates/rust-mcp-sdk/src/mcp_runtimes/server_runtime/mcp_server_runtime.rs b/crates/rust-mcp-sdk/src/mcp_runtimes/server_runtime/mcp_server_runtime.rs index 35d615a..5d9c433 100644 --- a/crates/rust-mcp-sdk/src/mcp_runtimes/server_runtime/mcp_server_runtime.rs +++ b/crates/rust-mcp-sdk/src/mcp_runtimes/server_runtime/mcp_server_runtime.rs @@ -143,10 +143,10 @@ impl McpServerHandler for ServerRuntimeInternalHandler> { Ok(result.map_or_else( |err| { - let r: CallToolResult = CallToolError::new(err).into(); - r.into() + let result: CallToolResult = CallToolError::new(err).into(); + result.into() }, - |value| value.into(), + Into::into, )) } ClientRequest::SetLevelRequest(set_level_request) => self From 28c76af2b8ad102e9877bb5d78202df90a6e53ec Mon Sep 17 00:00:00 2001 From: Ali Hashemi Date: Thu, 3 Jul 2025 18:00:36 -0300 Subject: [PATCH 8/8] chore: update macros dev dependencies --- Cargo.lock | 1 + crates/rust-mcp-macros/Cargo.toml | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 409e854..d2f661f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1671,6 +1671,7 @@ dependencies = [ "proc-macro2", "quote", "rust-mcp-schema", + "rust-mcp-sdk", "serde", "serde_json", "syn", diff --git a/crates/rust-mcp-macros/Cargo.toml b/crates/rust-mcp-macros/Cargo.toml index dc1ab71..41f7ad1 100644 --- a/crates/rust-mcp-macros/Cargo.toml +++ b/crates/rust-mcp-macros/Cargo.toml @@ -23,6 +23,10 @@ proc-macro2 = "1.0" [dev-dependencies] rust-mcp-schema = { workspace = true, default-features = false } +rust-mcp-sdk = { workspace = true, default-features = false, features = [ + "server", + "client", +] } [lints] workspace = true @@ -39,9 +43,21 @@ default = ["2025_06_18"] # Default features latest = ["2025_06_18"] # enables mcp schema version 2025_06_18 -2025_06_18 = ["rust-mcp-schema/2025_06_18", "rust-mcp-schema/schema_utils"] +2025_06_18 = [ + "rust-mcp-schema/2025_06_18", + "rust-mcp-schema/schema_utils", + "rust-mcp-sdk/2025_06_18", +] # enables mcp schema version 2025_03_26 -2025_03_26 = ["rust-mcp-schema/2025_03_26", "rust-mcp-schema/schema_utils"] +2025_03_26 = [ + "rust-mcp-schema/2025_03_26", + "rust-mcp-schema/schema_utils", + "rust-mcp-sdk/2025_03_26", +] # enables mcp schema version 2024_11_05 -2024_11_05 = ["rust-mcp-schema/2024_11_05", "rust-mcp-schema/schema_utils"] +2024_11_05 = [ + "rust-mcp-schema/2024_11_05", + "rust-mcp-schema/schema_utils", + "rust-mcp-sdk/2024_11_05", +] sdk = []