From c6ebafd64a0d8376d90c21eb3b7247459bb9532b Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 3 Sep 2025 07:47:30 -0700 Subject: [PATCH 1/8] Add MCP server support with initial `list_resources` tool --- dsc/Cargo.lock | 1021 +++++++++++++++++- dsc/Cargo.toml | 14 +- dsc/locales/en-us.toml | 8 + dsc/src/args.rs | 2 + dsc/src/main.rs | 12 +- dsc/src/mcp/list_resources.rs | 54 + dsc/src/mcp/mod.rs | 70 ++ dsc/src/subcommand.rs | 2 +- dsc/src/util.rs | 1 + dsc_lib/Cargo.lock | 31 +- dsc_lib/Cargo.toml | 7 +- dsc_lib/locales/en-us.toml | 1 + dsc_lib/src/discovery/command_discovery.rs | 5 +- dsc_lib/src/dscresources/command_resource.rs | 17 +- tools/test_group_resource/Cargo.lock | 31 +- 15 files changed, 1196 insertions(+), 80 deletions(-) create mode 100644 dsc/src/mcp/list_resources.rs create mode 100644 dsc/src/mcp/mod.rs diff --git a/dsc/Cargo.lock b/dsc/Cargo.lock index 50d44b9de..b9dc655cd 100644 --- a/dsc/Cargo.lock +++ b/dsc/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.3", "once_cell", "serde", "version_check", @@ -117,12 +117,72 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -210,6 +270,15 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "borrow-or-share" version = "0.2.2" @@ -275,6 +344,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] @@ -362,6 +432,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -423,6 +502,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "ctrlc" version = "3.4.7" @@ -439,8 +528,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b136475da5ef7b6ac596c0e956e37bad51b85b987ff3d5e230e964936736b2" +dependencies = [ + "darling_core 0.21.1", + "darling_macro 0.21.1", ] [[package]] @@ -457,13 +556,38 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b44ad32f92b75fb438b04b68547e521a548be8acc339a6dacc4a7121488f53e6" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5be8a7a562d315a5b92a630c30cec6bcf663e6673f00fbb69cca66a6f521b9" +dependencies = [ + "darling_core 0.21.1", "quote", "syn", ] @@ -492,7 +616,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", "syn", @@ -529,6 +653,16 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -559,9 +693,10 @@ dependencies = [ "ctrlc", "dsc_lib", "indicatif", - "jsonschema", + "jsonschema 0.33.0", "path-absolutize", "regex", + "rmcp", "rust-i18n", "schemars", "semver", @@ -571,6 +706,8 @@ dependencies = [ "syntect", "sysinfo", "thiserror 2.0.12", + "tokio", + "tokio-util", "tracing", "tracing-indicatif", "tracing-subscriber", @@ -587,7 +724,7 @@ dependencies = [ "clap", "derive_builder", "indicatif", - "jsonschema", + "jsonschema 0.32.1", "linked-hash-map", "murmurhash64", "num-traits", @@ -709,6 +846,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fraction" version = "0.15.3" @@ -725,6 +871,118 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -732,9 +990,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -785,6 +1045,115 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -973,6 +1342,22 @@ dependencies = [ "libc", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_elevated" version = "0.1.2" @@ -1031,7 +1416,33 @@ dependencies = [ "num-traits", "once_cell", "percent-encoding", - "referencing", + "referencing 0.32.1", + "regex", + "regex-syntax 0.8.5", + "serde", + "serde_json", + "uuid-simd", +] + +[[package]] +name = "jsonschema" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d46662859bc5f60a145b75f4632fbadc84e829e45df6c5de74cfc8e05acb96b5" +dependencies = [ + "ahash", + "base64", + "bytecount", + "email_address", + "fancy-regex 0.16.1", + "fraction", + "idna", + "itoa", + "num-cmp", + "num-traits", + "once_cell", + "percent-encoding", + "referencing 0.33.0", "regex", "regex-syntax 0.8.5", "serde", @@ -1091,6 +1502,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "matchers" version = "0.1.0" @@ -1100,12 +1517,24 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1261,6 +1690,26 @@ dependencies = [ "autocfg", ] +[[package]] +name = "oauth2" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" +dependencies = [ + "base64", + "chrono", + "getrandom 0.2.16", + "http", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + [[package]] name = "objc2-core-foundation" version = "0.3.1" @@ -1336,6 +1785,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "path-absolutize" version = "3.1.1" @@ -1366,6 +1821,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "plist" version = "1.7.4" @@ -1400,6 +1861,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -1418,6 +1888,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.40" @@ -1456,6 +1981,47 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1471,6 +2037,24 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -1523,6 +2107,20 @@ dependencies = [ "serde_json", ] +[[package]] +name = "referencing" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9c261f7ce75418b3beadfb3f0eb1299fe8eb9640deba45ffa2cb783098697d" +dependencies = [ + "ahash", + "fluent-uri", + "once_cell", + "parking_lot", + "percent-encoding", + "serde_json", +] + [[package]] name = "regex" version = "1.11.1" @@ -1567,6 +2165,108 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmcp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f521fbd040eba82684b17d787d423f43afb6e97974029b51f679157a589592a" +dependencies = [ + "axum", + "base64", + "bytes", + "chrono", + "futures", + "http", + "http-body", + "http-body-util", + "oauth2", + "paste", + "pin-project-lite", + "rand 0.9.2", + "reqwest", + "rmcp-macros", + "schemars", + "serde", + "serde_json", + "sse-stream", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tokio-util", + "tower-service", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "rmcp-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c162bf8a2846f70464ded6dda6430b60d1e2fb4b0e371f0906e39f63916641b9" +dependencies = [ + "darling 0.21.1", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + [[package]] name = "rt-format" version = "0.3.1" @@ -1637,6 +2337,12 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "1.0.7" @@ -1650,6 +2356,41 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.21" @@ -1677,6 +2418,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ + "chrono", "dyn-clone", "ref-cast", "schemars_derive", @@ -1760,6 +2502,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -1769,6 +2521,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -1782,6 +2546,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1847,12 +2622,25 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "sse-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb4dc4d33c68ec1f27d386b5610a351922656e1fdf5c05bbaad930cd1519479a" +dependencies = [ + "bytes", + "futures-util", + "http-body", + "http-body-util", + "pin-project-lite", ] [[package]] @@ -1873,6 +2661,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.104" @@ -1884,6 +2678,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -2021,24 +2824,38 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2052,6 +2869,41 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.8.23" @@ -2093,12 +2945,59 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2230,6 +3129,18 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -2266,6 +3177,24 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2284,7 +3213,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom", + "getrandom 0.3.3", "js-sys", "wasm-bindgen", ] @@ -2361,6 +3290,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2402,6 +3340,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -2434,6 +3385,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" @@ -2444,6 +3418,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "8.0.0" @@ -2856,6 +3839,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerotrie" version = "0.2.2" diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml index dacb92c47..df805a8b1 100644 --- a/dsc/Cargo.toml +++ b/dsc/Cargo.toml @@ -19,9 +19,19 @@ crossterm = { version = "0.29" } ctrlc = { version = "3.4" } dsc_lib = { path = "../dsc_lib" } indicatif = { version = "0.18" } -jsonschema = { version = "0.32", default-features = false } +jsonschema = { version = "0.33", default-features = false } path-absolutize = { version = "3.1" } regex = "1.11" +rmcp = { version = "0.6", features = [ + "server", + "macros", + "transport-sse-server", + "transport-io", + "transport-streamable-http-server", + "auth", + "elicitation", + "schemars", +] } rust-i18n = { version = "3.1" } schemars = { version = "1.0" } semver = "1.0" @@ -31,6 +41,8 @@ serde_yaml = { version = "0.9" } syntect = { version = "5.0", features = ["default-fancy"], default-features = false } sysinfo = { version = "0.37" } thiserror = "2.0" +tokio = "1.47" +tokio-util = "0.7" tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["ansi", "env-filter", "json"] } tracing-indicatif = { version = "0.3" } diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 060d552cd..0655779ee 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -35,6 +35,7 @@ resource = "The name of the resource to invoke" functionAbout = "Operations on DSC functions" listFunctionAbout = "List or find functions" version = "The version of the resource to invoke in semver format" +mcpAbout = "Use DSC as a MCP server" [main] ctrlCReceived = "Ctrl-C received" @@ -55,6 +56,13 @@ storeMessage = """DSC.exe is a command-line tool and cannot be run directly from Visit https://aka.ms/dscv3-docs for more information on how to use DSC.exe. Press any key to close this window""" +failedToStartMcpServer = "Failed to start MCP server: %{error}" + +[mcp.mod] +failedToInitialize = "Failed to initialize MCP server: %{error}" +instructions = "This server provides tools that work with DSC (DesiredStateConfiguration) which enables users to manage and configure their systems declaratively." +serverStopped = "MCP server stopped" +failedToWait = "Failed to wait for MCP server: %{error}" [resolve] processingInclude = "Processing Include input" diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 01b6dbcfb..a80111f3c 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -91,6 +91,8 @@ pub enum SubCommand { #[clap(subcommand)] subcommand: FunctionSubCommand, }, + #[clap(name = "mcp", about = t!("args.mcpAbout").to_string())] + Mcp, #[clap(name = "resource", about = t!("args.resourceAbout").to_string())] Resource { #[clap(subcommand)] diff --git a/dsc/src/main.rs b/dsc/src/main.rs index cbb7b80a9..f547d942e 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -4,6 +4,7 @@ use args::{Args, SubCommand}; use clap::{CommandFactory, Parser}; use clap_complete::generate; +use mcp::start_mcp_server; use rust_i18n::{i18n, t}; use std::{io, io::Read, process::exit}; use sysinfo::{Process, RefreshKind, System, get_current_pid, ProcessRefreshKind}; @@ -18,6 +19,7 @@ use crossterm::event; use std::env; pub mod args; +pub mod mcp; pub mod resolve; pub mod resource_command; pub mod subcommand; @@ -26,7 +28,8 @@ pub mod util; i18n!("locales", fallback = "en-us"); -fn main() { +#[tokio::main(flavor = "multi_thread")] +async fn main() { #[cfg(debug_assertions)] check_debug(); @@ -95,6 +98,13 @@ fn main() { SubCommand::Function { subcommand } => { subcommand::function(&subcommand); }, + SubCommand::Mcp => { + if let Err(err) = start_mcp_server().await { + error!("{}", t!("main.failedToStartMcpServer", error = err)); + exit(util::EXIT_MCP_FAILED); + } + exit(util::EXIT_SUCCESS); + } SubCommand::Resource { subcommand } => { subcommand::resource(&subcommand, progress_format); }, diff --git a/dsc/src/mcp/list_resources.rs b/dsc/src/mcp/list_resources.rs new file mode 100644 index 000000000..7e0cf7096 --- /dev/null +++ b/dsc/src/mcp/list_resources.rs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::mcp::McpServer; +use dsc_lib::{ + DscManager, + discovery::{ + command_discovery::ImportedManifest, + discovery_trait::DiscoveryKind, + }, + progress::ProgressFormat, +}; +use rmcp::{ErrorData as McpError, Json, tool, tool_router}; +use schemars::JsonSchema; +use serde::{Serialize, Deserialize}; +use tokio::task; + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct ResourceListResult { + pub resources: Vec, +} + +#[tool_router] +impl McpServer { + #[must_use] + pub fn new() -> Self { + Self { + tool_router: Self::tool_router() + } + } + + #[tool( + description = "List all DSC resources available on the local machine", + annotations( + title = "Enumerate all available DSC resources on the local machine", + read_only_hint = true, + destructive_hint = false, + idempotent_hint = true, + open_world_hint = true, + ) + )] + async fn list_resources(&self) -> Result, McpError> { + let result = task::spawn_blocking(move || { + let mut dsc = DscManager::new(); + let mut resources = Vec::new(); + for resource in dsc.list_available(&DiscoveryKind::Resource, "*", "*", ProgressFormat::None) { + resources.push(resource); + } + ResourceListResult { resources } + }).await.map_err(|e| McpError::internal_error(e.to_string(), None))?; + + Ok(Json(result)) + } +} diff --git a/dsc/src/mcp/mod.rs b/dsc/src/mcp/mod.rs new file mode 100644 index 000000000..f2c9f5d6f --- /dev/null +++ b/dsc/src/mcp/mod.rs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use rmcp::{ + ErrorData as McpError, + handler::server::tool::ToolRouter, + model::{InitializeResult, InitializeRequestParam, ServerCapabilities, ServerInfo}, + service::{RequestContext, RoleServer}, + ServerHandler, + ServiceExt, + tool_handler, + transport::stdio, +}; +use rust_i18n::t; + +pub mod list_resources; + +#[derive(Debug, Clone)] +pub struct McpServer { + tool_router: ToolRouter +} + +impl Default for McpServer { + fn default() -> Self { + Self::new() + } +} + +#[tool_handler] +impl ServerHandler for McpServer { + fn get_info(&self) -> ServerInfo { + ServerInfo { + capabilities: ServerCapabilities::builder() + .enable_tools() + .build(), + instructions: Some(t!("mcp.mod.instructions").to_string()), + ..Default::default() + } + } + + async fn initialize(&self, _request: InitializeRequestParam, _context: RequestContext) -> Result { + Ok(self.get_info()) + } +} + +/// This function initializes and starts the MCP server, handling any errors that may occur. +/// +/// # Errors +/// +/// This function will return an error if the MCP server fails to start. +pub async fn start_mcp_server() -> Result<(), McpError> { + let service = match McpServer::new().serve(stdio()).await { + Ok(service) => service, + Err(err) => { + tracing::error!(error = %err, "Failed to start MCP server"); + return Err(McpError::internal_error(t!("mcp.mod.failedToInitialize", error = err.to_string()), None)); + } + }; + + match service.waiting().await { + Ok(_) => { + tracing::info!("{}", t!("mcp.mod.serverStopped")); + } + Err(err) => { + tracing::error!("{}", t!("mcp.mod.failedToWait", error = err)); + } + } + + Ok(()) +} diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 59049749e..bf559cf65 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -744,7 +744,7 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> } } -fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec>, format: Option<&ListOutputFormat>, progress_format: ProgressFormat) { +pub fn list_resources(dsc: &mut DscManager, resource_name: Option<&String>, adapter_name: Option<&String>, description: Option<&String>, tags: Option<&Vec>, format: Option<&ListOutputFormat>, progress_format: ProgressFormat) { let mut write_table = false; let mut table = Table::new(&[ t!("subcommand.tableHeader_type").to_string().as_ref(), diff --git a/dsc/src/util.rs b/dsc/src/util.rs index c1a4d726c..f2504b8a0 100644 --- a/dsc/src/util.rs +++ b/dsc/src/util.rs @@ -69,6 +69,7 @@ pub const EXIT_VALIDATION_FAILED: i32 = 5; pub const EXIT_CTRL_C: i32 = 6; pub const EXIT_DSC_RESOURCE_NOT_FOUND: i32 = 7; pub const EXIT_DSC_ASSERTION_FAILED: i32 = 8; +pub const EXIT_MCP_FAILED: i32 = 9; pub const DSC_CONFIG_ROOT: &str = "DSC_CONFIG_ROOT"; pub const DSC_TRACE_LEVEL: &str = "DSC_TRACE_LEVEL"; diff --git a/dsc_lib/Cargo.lock b/dsc_lib/Cargo.lock index fa090b025..848b591d0 100644 --- a/dsc_lib/Cargo.lock +++ b/dsc_lib/Cargo.lock @@ -1532,9 +1532,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -1557,16 +1557,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1648,22 +1638,20 @@ dependencies = [ [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "slab", - "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2147,15 +2135,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" diff --git a/dsc_lib/Cargo.toml b/dsc_lib/Cargo.toml index c18272e03..36f3b224a 100644 --- a/dsc_lib/Cargo.toml +++ b/dsc_lib/Cargo.toml @@ -35,7 +35,12 @@ serde_yaml = { version = "0.9" } thiserror = "2.0" security_context_lib = { path = "../security_context_lib" } semver = "1.0" -tokio = { version = "1.45", features = ["full"] } +tokio = { version = "1.47", features = [ + "io-util", + "macros", + "process", + "rt-multi-thread", +] } tracing = "0.1" tracing-indicatif = { version = "0.3" } tree-sitter = "0.25" diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml index 3f57a4449..2d0f0521d 100644 --- a/dsc_lib/locales/en-us.toml +++ b/dsc_lib/locales/en-us.toml @@ -152,6 +152,7 @@ invalidArrayKey = "Unsupported array value for key '%{key}'. Only string and nu invalidKey = "Unsupported value for key '%{key}'. Only string, bool, number, and array is supported." inDesiredStateNotBool = "'_inDesiredState' is not a boolean" exportNotSupportedUsingGet = "Export is not supported by resource '%{resource}' using get operation" +runProcessError = "Failed to run process '%{executable}': %{error}" [dscresources.dscresource] invokeGet = "Invoking get for '%{resource}'" diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index c77c2389e..a576c4323 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -13,7 +13,8 @@ use crate::util::convert_wildcard_to_regex; use regex::RegexBuilder; use rust_i18n::t; use semver::{Version, VersionReq}; -use serde::Deserialize; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashSet, HashMap}; use std::env; use std::ffi::OsStr; @@ -29,7 +30,7 @@ use crate::util::get_exe_path; const DSC_RESOURCE_EXTENSIONS: [&str; 3] = [".dsc.resource.json", ".dsc.resource.yaml", ".dsc.resource.yml"]; const DSC_EXTENSION_EXTENSIONS: [&str; 3] = [".dsc.extension.json", ".dsc.extension.yaml", ".dsc.extension.yml"]; -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize, JsonSchema)] pub enum ImportedManifest { Resource(DscResource), Extension(DscExtension), diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 51f1c8522..cdbd2afb3 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -715,17 +715,22 @@ async fn run_process_async(executable: &str, args: Option>, input: O /// Will panic if tokio runtime can't be created. /// #[allow(clippy::implicit_hasher)] -pub fn invoke_command(executable: &str, args: Option>, input: Option<&str>, cwd: Option<&str>, env: Option>, exit_codes: Option<&HashMap>) -> Result<(i32, String, String), DscError> { +#[tokio::main] +pub async fn invoke_command(executable: &str, args: Option>, input: Option<&str>, cwd: Option<&str>, env: Option>, exit_codes: Option<&HashMap>) -> Result<(i32, String, String), DscError> { debug!("{}", t!("dscresources.commandResource.commandInvoke", executable = executable, args = args : {:?})); if let Some(cwd) = cwd { debug!("{}", t!("dscresources.commandResource.commandCwd", cwd = cwd)); } - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(run_process_async(executable, args, input, cwd, env, exit_codes)) + match run_process_async(executable, args, input, cwd, env, exit_codes).await { + Ok((code, stdout, stderr)) => { + Ok((code, stdout, stderr)) + }, + Err(err) => { + error!("{}", t!("dscresources.commandResource.runProcessError", executable = executable, error = err)); + Err(err) + } + } } /// Process the arguments for a command resource. diff --git a/tools/test_group_resource/Cargo.lock b/tools/test_group_resource/Cargo.lock index 8274c1093..691ef6b58 100644 --- a/tools/test_group_resource/Cargo.lock +++ b/tools/test_group_resource/Cargo.lock @@ -1525,9 +1525,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -1550,16 +1550,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1652,22 +1642,20 @@ dependencies = [ [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "slab", - "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2151,15 +2139,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" From ededda36720df5d2b2a03fa00d7e28921de968d8 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 3 Sep 2025 07:42:05 -0700 Subject: [PATCH 2/8] fix query and add tests --- dsc/src/mcp/list_resources.rs | 2 +- dsc/tests/dsc_mcp.tests.ps1 | 100 ++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 dsc/tests/dsc_mcp.tests.ps1 diff --git a/dsc/src/mcp/list_resources.rs b/dsc/src/mcp/list_resources.rs index 7e0cf7096..0e1343932 100644 --- a/dsc/src/mcp/list_resources.rs +++ b/dsc/src/mcp/list_resources.rs @@ -43,7 +43,7 @@ impl McpServer { let result = task::spawn_blocking(move || { let mut dsc = DscManager::new(); let mut resources = Vec::new(); - for resource in dsc.list_available(&DiscoveryKind::Resource, "*", "*", ProgressFormat::None) { + for resource in dsc.list_available(&DiscoveryKind::Resource, "*", "", ProgressFormat::None) { resources.push(resource); } ResourceListResult { resources } diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 new file mode 100644 index 000000000..b9ff1625d --- /dev/null +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -0,0 +1,100 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Tests for MCP server' { + BeforeAll { + $processStartInfo = [System.Diagnostics.ProcessStartInfo]::new() + $processStartInfo.FileName = "dsc" + $processStartInfo.Arguments = "--trace-format plaintext mcp" + $processStartInfo.RedirectStandardError = $true + $processStartInfo.RedirectStandardOutput = $true + $processStartInfo.RedirectStandardInput = $true + $mcp = [System.Diagnostics.Process]::Start($processStartInfo) + + function Send-McpRequest($request, [switch]$notify) { + $request = $request | ConvertTo-Json -Compress -Depth 10 + $mcp.StandardInput.WriteLine($request) + $mcp.StandardInput.Flush() + if (!$notify) { + while ($mcp.StandardOutput.Peek() -eq -1) { + Start-Sleep -Milliseconds 100 + } + $stdout = $mcp.StandardOutput.ReadLine() + return ($stdout | ConvertFrom-Json -Depth 30) + } + } + } + + AfterAll { + $mcp.StandardInput.Close() + $mcp.WaitForExit() + } + + It 'Initialization works' { + $mcpRequest = @{ + jsonrpc = "2.0" + id = 1 + method = "initialize" + params = @{ + protocolVersion = "2024-11-05" + capabilities = @{ + tools = @{} + } + clientInfo = @{ + name = "Test Client" + version = "1.0.0" + } + } + } + + $response = Send-McpRequest -request $mcpRequest + + $response.id | Should -Be 1 + $response.result.capabilities.tools | Should -Not -Be $null + $response.result.instructions | Should -Not -BeNullOrEmpty + + $notifyInitialized = @{ + jsonrpc = "2.0" + method = "notifications/initialized" + } + + Send-McpRequest -request $notifyInitialized -notify + } + + It 'Tools/List works' { + $mcpRequest = @{ + jsonrpc = "2.0" + id = 2 + method = "tools/list" + params = @{} + } + + $response = Send-McpRequest -request $mcpRequest + + $response.id | Should -Be 2 + $response.result.tools.Count | Should -Be 1 + $response.result.tools[0].name | Should -BeExactly 'list_resources' + } + + It 'Calling list_resources works' { + $mcpRequest = @{ + jsonrpc = "2.0" + id = 3 + method = "tools/call" + params = @{ + name = "list_resources" + arguments = @{} + } + } + + $response = Send-McpRequest -request $mcpRequest + $response.id | Should -Be 3 + $resources = dsc resource list | ConvertFrom-Json -Depth 20 + $response.result.structuredContent.resources.Count | Should -Be $resources.Count + for ($i = 0; $i -lt $resources.Count; $i++) { + $response.result.structuredContent.resources[$i].Resource.type | Should -BeExactly $resources[$i].type + $response.result.structuredContent.resources[$i].Resource.version | Should -BeExactly $resources[$i].version + $response.result.structuredContent.resources[$i].Resource.path | Should -BeExactly $resources[$i].path + } + } +} From 9c9638126433774b84ec552b9da9c8690be6e854 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 3 Sep 2025 21:10:02 -0700 Subject: [PATCH 3/8] move async off main --- dsc/src/main.rs | 5 ++--- dsc/src/mcp/mod.rs | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dsc/src/main.rs b/dsc/src/main.rs index f547d942e..7a1a7f876 100644 --- a/dsc/src/main.rs +++ b/dsc/src/main.rs @@ -28,8 +28,7 @@ pub mod util; i18n!("locales", fallback = "en-us"); -#[tokio::main(flavor = "multi_thread")] -async fn main() { +fn main() { #[cfg(debug_assertions)] check_debug(); @@ -99,7 +98,7 @@ async fn main() { subcommand::function(&subcommand); }, SubCommand::Mcp => { - if let Err(err) = start_mcp_server().await { + if let Err(err) = start_mcp_server() { error!("{}", t!("main.failedToStartMcpServer", error = err)); exit(util::EXIT_MCP_FAILED); } diff --git a/dsc/src/mcp/mod.rs b/dsc/src/mcp/mod.rs index f2c9f5d6f..2163702c7 100644 --- a/dsc/src/mcp/mod.rs +++ b/dsc/src/mcp/mod.rs @@ -48,6 +48,7 @@ impl ServerHandler for McpServer { /// # Errors /// /// This function will return an error if the MCP server fails to start. +#[tokio::main(flavor = "multi_thread")] pub async fn start_mcp_server() -> Result<(), McpError> { let service = match McpServer::new().serve(stdio()).await { Ok(service) => service, From 1a2dfefdad31a5af9b61df363f7b1c139c7f9679 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 3 Sep 2025 23:18:28 -0700 Subject: [PATCH 4/8] fix clippy, rename tool --- dsc/locales/en-us.toml | 2 + ...ist_resources.rs => list_dsc_resources.rs} | 2 +- dsc/src/mcp/mod.rs | 44 +++++++++++-------- dsc/tests/dsc_mcp.tests.ps1 | 6 +-- 4 files changed, 32 insertions(+), 22 deletions(-) rename dsc/src/mcp/{list_resources.rs => list_dsc_resources.rs} (94%) diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 0655779ee..341743255 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -60,9 +60,11 @@ failedToStartMcpServer = "Failed to start MCP server: %{error}" [mcp.mod] failedToInitialize = "Failed to initialize MCP server: %{error}" +failedToStart = "Failed to start MCP server: %{error}" instructions = "This server provides tools that work with DSC (DesiredStateConfiguration) which enables users to manage and configure their systems declaratively." serverStopped = "MCP server stopped" failedToWait = "Failed to wait for MCP server: %{error}" +failedToCreateRuntime = "Failed to create async runtime: %{error}" [resolve] processingInclude = "Processing Include input" diff --git a/dsc/src/mcp/list_resources.rs b/dsc/src/mcp/list_dsc_resources.rs similarity index 94% rename from dsc/src/mcp/list_resources.rs rename to dsc/src/mcp/list_dsc_resources.rs index 0e1343932..61acd8fbd 100644 --- a/dsc/src/mcp/list_resources.rs +++ b/dsc/src/mcp/list_dsc_resources.rs @@ -39,7 +39,7 @@ impl McpServer { open_world_hint = true, ) )] - async fn list_resources(&self) -> Result, McpError> { + async fn list_dsc_resources(&self) -> Result, McpError> { let result = task::spawn_blocking(move || { let mut dsc = DscManager::new(); let mut resources = Vec::new(); diff --git a/dsc/src/mcp/mod.rs b/dsc/src/mcp/mod.rs index 2163702c7..5a30e2291 100644 --- a/dsc/src/mcp/mod.rs +++ b/dsc/src/mcp/mod.rs @@ -13,7 +13,7 @@ use rmcp::{ }; use rust_i18n::t; -pub mod list_resources; +pub mod list_dsc_resources; #[derive(Debug, Clone)] pub struct McpServer { @@ -48,24 +48,32 @@ impl ServerHandler for McpServer { /// # Errors /// /// This function will return an error if the MCP server fails to start. -#[tokio::main(flavor = "multi_thread")] -pub async fn start_mcp_server() -> Result<(), McpError> { - let service = match McpServer::new().serve(stdio()).await { - Ok(service) => service, - Err(err) => { - tracing::error!(error = %err, "Failed to start MCP server"); - return Err(McpError::internal_error(t!("mcp.mod.failedToInitialize", error = err.to_string()), None)); - } - }; +pub async fn start_mcp_server_async() -> Result<(), McpError> { + // Initialize the MCP server + let server = McpServer::new(); - match service.waiting().await { - Ok(_) => { - tracing::info!("{}", t!("mcp.mod.serverStopped")); - } - Err(err) => { - tracing::error!("{}", t!("mcp.mod.failedToWait", error = err)); - } - } + // Try to create the service with proper error handling + let service = server.serve(stdio()).await + .map_err(|err| McpError::internal_error(t!("mcp.mod.failedToInitialize", error = err.to_string()), None))?; + + // Wait for the service to complete with proper error handling + service.waiting().await + .map_err(|err| McpError::internal_error(t!("mcp.mod.serverWaitFailed", error = err.to_string()), None))?; + + tracing::info!("{}", t!("mcp.mod.serverStopped")); + Ok(()) +} + +/// Synchronous wrapper to start the MCP server +/// +/// # Errors +/// +/// This function will return an error if the MCP server fails to start or if the tokio runtime cannot be created. +pub fn start_mcp_server() -> Result<(), Box> { + let rt = tokio::runtime::Runtime::new() + .map_err(|e| McpError::internal_error(t!("mcp.mod.failedToCreateRuntime", error = e.to_string()), None))?; + rt.block_on(start_mcp_server_async()) + .map_err(|e| McpError::internal_error(t!("mcp.mod.failedToStart", error = e.to_string()), None))?; Ok(()) } diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 index b9ff1625d..2348be489 100644 --- a/dsc/tests/dsc_mcp.tests.ps1 +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -73,16 +73,16 @@ Describe 'Tests for MCP server' { $response.id | Should -Be 2 $response.result.tools.Count | Should -Be 1 - $response.result.tools[0].name | Should -BeExactly 'list_resources' + $response.result.tools[0].name | Should -BeExactly 'list_dsc_resources' } - It 'Calling list_resources works' { + It 'Calling list_dsc_resources works' { $mcpRequest = @{ jsonrpc = "2.0" id = 3 method = "tools/call" params = @{ - name = "list_resources" + name = "list_dsc_resources" arguments = @{} } } From c3d950e3fd3a431b80209b5c6d110ff35d0f0c33 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 4 Sep 2025 06:48:26 -0700 Subject: [PATCH 5/8] add missing text --- dsc/locales/en-us.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 341743255..5a963e491 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -65,6 +65,7 @@ instructions = "This server provides tools that work with DSC (DesiredStateConfi serverStopped = "MCP server stopped" failedToWait = "Failed to wait for MCP server: %{error}" failedToCreateRuntime = "Failed to create async runtime: %{error}" +serverWaitFailed = "Failed to wait for MCP server: %{error}" [resolve] processingInclude = "Processing Include input" From 2fd36e63db15508460ace4e58805b8864fd92062 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 4 Sep 2025 07:36:08 -0700 Subject: [PATCH 6/8] remove unused text --- dsc/locales/en-us.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 5a963e491..56cdd82ee 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -63,7 +63,6 @@ failedToInitialize = "Failed to initialize MCP server: %{error}" failedToStart = "Failed to start MCP server: %{error}" instructions = "This server provides tools that work with DSC (DesiredStateConfiguration) which enables users to manage and configure their systems declaratively." serverStopped = "MCP server stopped" -failedToWait = "Failed to wait for MCP server: %{error}" failedToCreateRuntime = "Failed to create async runtime: %{error}" serverWaitFailed = "Failed to wait for MCP server: %{error}" From 88da18392f7f18d1ec97ae64352a3ec0fa3299a6 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 4 Sep 2025 09:28:38 -0700 Subject: [PATCH 7/8] change tool to only return summary information --- dsc/src/mcp/list_dsc_resources.rs | 39 ++++++++++++++++++++----------- dsc/tests/dsc_mcp.tests.ps1 | 9 +++---- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/dsc/src/mcp/list_dsc_resources.rs b/dsc/src/mcp/list_dsc_resources.rs index 61acd8fbd..d07974350 100644 --- a/dsc/src/mcp/list_dsc_resources.rs +++ b/dsc/src/mcp/list_dsc_resources.rs @@ -3,21 +3,27 @@ use crate::mcp::McpServer; use dsc_lib::{ - DscManager, - discovery::{ - command_discovery::ImportedManifest, + DscManager, discovery::{ + command_discovery::ImportedManifest::Resource, discovery_trait::DiscoveryKind, - }, - progress::ProgressFormat, + }, dscresources::resource_manifest::Kind, progress::ProgressFormat }; use rmcp::{ErrorData as McpError, Json, tool, tool_router}; use schemars::JsonSchema; -use serde::{Serialize, Deserialize}; +use serde::Serialize; +use std::collections::BTreeMap; use tokio::task; -#[derive(Serialize, Deserialize, JsonSchema)] +#[derive(Serialize, JsonSchema)] pub struct ResourceListResult { - pub resources: Vec, + pub resources: Vec, +} + +#[derive(Serialize, JsonSchema)] +pub struct ResourceSummary { + pub r#type: String, + pub kind: Kind, + pub description: Option, } #[tool_router] @@ -30,9 +36,9 @@ impl McpServer { } #[tool( - description = "List all DSC resources available on the local machine", + description = "List summary of all DSC resources available on the local machine", annotations( - title = "Enumerate all available DSC resources on the local machine", + title = "Enumerate all available DSC resources on the local machine returning name, kind, and description.", read_only_hint = true, destructive_hint = false, idempotent_hint = true, @@ -42,11 +48,18 @@ impl McpServer { async fn list_dsc_resources(&self) -> Result, McpError> { let result = task::spawn_blocking(move || { let mut dsc = DscManager::new(); - let mut resources = Vec::new(); + let mut resources = BTreeMap::::new(); for resource in dsc.list_available(&DiscoveryKind::Resource, "*", "", ProgressFormat::None) { - resources.push(resource); + if let Resource(resource) = resource { + let summary = ResourceSummary { + r#type: resource.type_name.clone(), + kind: resource.kind.clone(), + description: resource.description.clone(), + }; + resources.insert(resource.type_name.to_lowercase(), summary); + } } - ResourceListResult { resources } + ResourceListResult { resources: resources.into_values().collect() } }).await.map_err(|e| McpError::internal_error(e.to_string(), None))?; Ok(Json(result)) diff --git a/dsc/tests/dsc_mcp.tests.ps1 b/dsc/tests/dsc_mcp.tests.ps1 index 2348be489..44df9d3cb 100644 --- a/dsc/tests/dsc_mcp.tests.ps1 +++ b/dsc/tests/dsc_mcp.tests.ps1 @@ -89,12 +89,13 @@ Describe 'Tests for MCP server' { $response = Send-McpRequest -request $mcpRequest $response.id | Should -Be 3 - $resources = dsc resource list | ConvertFrom-Json -Depth 20 + $resources = dsc resource list | ConvertFrom-Json -Depth 20 | Select-Object type, kind, description -Unique $response.result.structuredContent.resources.Count | Should -Be $resources.Count for ($i = 0; $i -lt $resources.Count; $i++) { - $response.result.structuredContent.resources[$i].Resource.type | Should -BeExactly $resources[$i].type - $response.result.structuredContent.resources[$i].Resource.version | Should -BeExactly $resources[$i].version - $response.result.structuredContent.resources[$i].Resource.path | Should -BeExactly $resources[$i].path + ($response.result.structuredContent.resources[$i].psobject.properties | Measure-Object).Count | Should -Be 3 + $response.result.structuredContent.resources[$i].type | Should -BeExactly $resources[$i].type -Because ($response.result.structuredContent | ConvertTo-Json -Depth 20 | Out-String) + $response.result.structuredContent.resources[$i].kind | Should -BeExactly $resources[$i].kind -Because ($response.result.structuredContent | ConvertTo-Json -Depth 20 | Out-String) + $response.result.structuredContent.resources[$i].description | Should -BeExactly $resources[$i].description -Because ($response.result.structuredContent | ConvertTo-Json -Depth 20 | Out-String) } } } From 73ed7f9e68bc5ff7d8a76153661b76d4eff1d087 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 4 Sep 2025 18:04:08 -0700 Subject: [PATCH 8/8] remove unused features --- dsc/Cargo.lock | 109 ------------------------------------------------- dsc/Cargo.toml | 2 - 2 files changed, 111 deletions(-) diff --git a/dsc/Cargo.lock b/dsc/Cargo.lock index b9dc655cd..05cf86b2f 100644 --- a/dsc/Cargo.lock +++ b/dsc/Cargo.lock @@ -129,60 +129,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "axum" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" -dependencies = [ - "axum-core", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -1085,12 +1031,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.7.0" @@ -1104,7 +1044,6 @@ dependencies = [ "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -1517,24 +1456,12 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2226,32 +2153,22 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f521fbd040eba82684b17d787d423f43afb6e97974029b51f679157a589592a" dependencies = [ - "axum", "base64", - "bytes", "chrono", "futures", - "http", - "http-body", - "http-body-util", "oauth2", "paste", "pin-project-lite", - "rand 0.9.2", "reqwest", "rmcp-macros", "schemars", "serde", "serde_json", - "sse-stream", "thiserror 2.0.12", "tokio", - "tokio-stream", "tokio-util", - "tower-service", "tracing", "url", - "uuid", ] [[package]] @@ -2630,19 +2547,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "sse-stream" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb4dc4d33c68ec1f27d386b5610a351922656e1fdf5c05bbaad930cd1519479a" -dependencies = [ - "bytes", - "futures-util", - "http-body", - "http-body-util", - "pin-project-lite", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2879,17 +2783,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.10" @@ -2958,7 +2851,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -2997,7 +2889,6 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml index df805a8b1..abd9c8b88 100644 --- a/dsc/Cargo.toml +++ b/dsc/Cargo.toml @@ -25,9 +25,7 @@ regex = "1.11" rmcp = { version = "0.6", features = [ "server", "macros", - "transport-sse-server", "transport-io", - "transport-streamable-http-server", "auth", "elicitation", "schemars",