diff --git a/.gitignore b/.gitignore
index fba33d24f..236a4ac63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -73,3 +73,5 @@ clients/generated/smithy/typescript/tsconfig.*
*.dylib
*.so
*.dll
+smithy/mcp-codegen/build/
+smithy/mcp-codegen/.gradle/
diff --git a/Cargo.lock b/Cargo.lock
index 2e68e84ba..04cbe3868 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -72,7 +72,7 @@ dependencies = [
"mime",
"percent-encoding",
"pin-project-lite",
- "rand",
+ "rand 0.8.5",
"sha1",
"smallvec",
"tokio",
@@ -224,15 +224,6 @@ dependencies = [
"syn 1.0.109",
]
-[[package]]
-name = "addr2line"
-version = "0.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
-dependencies = [
- "gimli",
-]
-
[[package]]
name = "adler"
version = "1.0.2"
@@ -257,7 +248,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
- "cpufeatures",
+ "cpufeatures 0.2.7",
]
[[package]]
@@ -327,12 +318,6 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
-[[package]]
-name = "android-tzdata"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
-
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -442,7 +427,7 @@ dependencies = [
"rustc-hash 2.1.1",
"serde",
"serde_derive",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -475,18 +460,40 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
]
[[package]]
name = "async-trait"
-version = "0.1.83"
+version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -506,7 +513,7 @@ dependencies = [
"manyhow",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -522,7 +529,7 @@ dependencies = [
"proc-macro2",
"quote",
"quote-use",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -568,7 +575,7 @@ dependencies = [
"mime",
"percent-encoding",
"pin-project-lite",
- "rand",
+ "rand 0.8.5",
"serde",
"serde_json",
"serde_urlencoded",
@@ -1009,21 +1016,6 @@ dependencies = [
"tracing",
]
-[[package]]
-name = "backtrace"
-version = "0.3.67"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
-dependencies = [
- "addr2line",
- "cc",
- "cfg-if",
- "libc",
- "miniz_oxide 0.6.2",
- "object",
- "rustc-demangle",
-]
-
[[package]]
name = "base16ct"
version = "0.2.0"
@@ -1104,7 +1096,7 @@ dependencies = [
"regex",
"rustc-hash 1.1.0",
"shlex",
- "syn 2.0.103",
+ "syn 2.0.117",
"which",
]
@@ -1157,6 +1149,31 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "bon"
+version = "3.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f47dbe92550676ee653353c310dfb9cf6ba17ee70396e1f7cf0a2020ad49b2fe"
+dependencies = [
+ "bon-macros",
+ "rustversion",
+]
+
+[[package]]
+name = "bon-macros"
+version = "3.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c"
+dependencies = [
+ "darling 0.23.0",
+ "ident_case",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.117",
+]
+
[[package]]
name = "brotli"
version = "3.3.4"
@@ -1442,19 +1459,29 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "chacha20"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.0",
+]
+
[[package]]
name = "chrono"
-version = "0.4.34"
+version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
- "android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
- "windows-targets 0.52.0",
+ "windows-link",
]
[[package]]
@@ -1554,7 +1581,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -1783,6 +1810,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "crc16"
version = "0.4.0"
@@ -1835,7 +1871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
- "rand_core",
+ "rand_core 0.6.4",
"subtle",
"zeroize",
]
@@ -1847,7 +1883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
- "rand_core",
+ "rand_core 0.6.4",
"typenum",
]
@@ -1867,7 +1903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.7",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
@@ -1884,7 +1920,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -1911,7 +1947,7 @@ dependencies = [
"proc-macro2",
"quote",
"scratch",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -1928,7 +1964,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -1951,6 +1987,16 @@ dependencies = [
"darling_macro 0.20.10",
]
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core 0.23.0",
+ "darling_macro 0.23.0",
+]
+
[[package]]
name = "darling_core"
version = "0.14.4"
@@ -1976,7 +2022,20 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.11.1",
- "syn 2.0.103",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.11.1",
+ "syn 2.0.117",
]
[[package]]
@@ -1998,7 +2057,18 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core 0.20.10",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core 0.23.0",
+ "quote",
+ "syn 2.0.117",
]
[[package]]
@@ -2049,7 +2119,7 @@ checksum = "146398d62142a0f35248a608f17edf0dde57338354966d6e41d0eb2d16980ccb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -2084,7 +2154,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
- "syn 2.0.103",
+ "syn 2.0.117",
"unicode-xid",
]
@@ -2139,7 +2209,7 @@ dependencies = [
"dsl_auto_type",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -2148,7 +2218,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25"
dependencies = [
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -2204,7 +2274,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -2278,7 +2348,7 @@ dependencies = [
"hkdf",
"pem-rfc7468",
"pkcs8",
- "rand_core",
+ "rand_core 0.6.4",
"sec1",
"subtle",
"zeroize",
@@ -2417,7 +2487,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
- "rand_core",
+ "rand_core 0.6.4",
"subtle",
]
@@ -2446,7 +2516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
- "miniz_oxide 0.7.1",
+ "miniz_oxide",
]
[[package]]
@@ -2464,6 +2534,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -2520,7 +2596,7 @@ dependencies = [
"futures",
"log",
"parking_lot",
- "rand",
+ "rand 0.8.5",
"redis-protocol",
"semver",
"socket2 0.5.10",
@@ -2539,7 +2615,7 @@ checksum = "1458c6e22d36d61507034d5afecc64f105c1d39712b7ac6ec3b352c423f715cc"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -2643,7 +2719,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -2709,11 +2785,25 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
- "r-efi",
+ "r-efi 5.3.0",
"wasi 0.14.2+wasi-0.2.4",
"wasm-bindgen",
]
+[[package]]
+name = "getrandom"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+ "rand_core 0.10.0",
+ "wasip2",
+ "wasip3",
+]
+
[[package]]
name = "ghash"
version = "0.5.1"
@@ -2724,12 +2814,6 @@ dependencies = [
"polyval",
]
-[[package]]
-name = "gimli"
-version = "0.27.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
-
[[package]]
name = "glob"
version = "0.3.1"
@@ -2801,7 +2885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
- "rand_core",
+ "rand_core 0.6.4",
"subtle",
]
@@ -2881,6 +2965,15 @@ dependencies = [
"allocator-api2",
]
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash",
+]
+
[[package]]
name = "hashbrown"
version = "0.16.1"
@@ -3183,6 +3276,12 @@ dependencies = [
"cxx-build",
]
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -3451,6 +3550,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
[[package]]
name = "leptos"
version = "0.6.11"
@@ -3550,7 +3655,7 @@ dependencies = [
"quote",
"rstml",
"serde",
- "syn 2.0.103",
+ "syn 2.0.117",
"walkdir",
]
@@ -3586,7 +3691,7 @@ dependencies = [
"quote",
"rstml",
"server_fn_macro",
- "syn 2.0.103",
+ "syn 2.0.117",
"tracing",
"uuid",
]
@@ -3800,7 +3905,7 @@ dependencies = [
"manyhow-macros",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -3876,15 +3981,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-[[package]]
-name = "miniz_oxide"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
-dependencies = [
- "adler",
-]
-
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@@ -3941,7 +4037,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -4034,7 +4130,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
- "rand",
+ "rand 0.8.5",
"smallvec",
"zeroize",
]
@@ -4123,7 +4219,7 @@ dependencies = [
"chrono",
"getrandom 0.2.15",
"http 0.2.9",
- "rand",
+ "rand 0.8.5",
"reqwest",
"serde",
"serde_json",
@@ -4133,15 +4229,6 @@ dependencies = [
"url",
]
-[[package]]
-name = "object"
-version = "0.30.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385"
-dependencies = [
- "memchr",
-]
-
[[package]]
name = "once_cell"
version = "1.18.0"
@@ -4186,7 +4273,7 @@ dependencies = [
"oauth2",
"p256",
"p384",
- "rand",
+ "rand 0.8.5",
"rsa",
"serde",
"serde-value",
@@ -4224,7 +4311,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -4325,6 +4412,12 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
+[[package]]
+name = "pastey"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec"
+
[[package]]
name = "pathdiff"
version = "0.2.1"
@@ -4377,7 +4470,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -4418,7 +4511,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -4473,7 +4566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.7",
"opaque-debug",
"universal-hash",
]
@@ -4537,12 +4630,12 @@ dependencies = [
[[package]]
name = "prettyplease"
-version = "0.2.15"
+version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -4614,7 +4707,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
"version_check",
"yansi",
]
@@ -4659,7 +4752,7 @@ dependencies = [
"proc-macro-utils",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -4668,6 +4761,12 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
[[package]]
name = "r2d2"
version = "0.8.10"
@@ -4687,7 +4786,18 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
- "rand_core",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.2",
+ "rand_core 0.10.0",
]
[[package]]
@@ -4697,7 +4807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
- "rand_core",
+ "rand_core 0.6.4",
]
[[package]]
@@ -4709,6 +4819,12 @@ dependencies = [
"getrandom 0.2.15",
]
+[[package]]
+name = "rand_core"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
+
[[package]]
name = "redis-protocol"
version = "5.0.1"
@@ -4741,6 +4857,26 @@ dependencies = [
"bitflags 1.3.2",
]
+[[package]]
+name = "ref-cast"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "regex"
version = "1.9.4"
@@ -4873,7 +5009,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -4906,6 +5042,68 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "rmcp"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba6b9d2f0efe2258b23767f1f9e0054cfbcac9c2d6f81a031214143096d7864f"
+dependencies = [
+ "async-trait",
+ "base64 0.22.1",
+ "bytes",
+ "chrono",
+ "futures",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "pastey",
+ "pin-project-lite",
+ "rand 0.10.0",
+ "rmcp-macros",
+ "schemars",
+ "serde",
+ "serde_json",
+ "sse-stream",
+ "thiserror 2.0.12",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "rmcp-actix-web"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85f125d3ab16dad1f7fb9542e3ebe93f25eefee847a03692b7eb37134104cf3d"
+dependencies = [
+ "actix-web",
+ "async-stream",
+ "bon",
+ "futures",
+ "rmcp",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+]
+
+[[package]]
+name = "rmcp-macros"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab9d95d7ed26ad8306352b0d5f05b593222b272790564589790d210aa15caa9e"
+dependencies = [
+ "darling 0.23.0",
+ "proc-macro2",
+ "quote",
+ "serde_json",
+ "syn 2.0.117",
+]
+
[[package]]
name = "roxmltree"
version = "0.14.1"
@@ -4934,7 +5132,7 @@ dependencies = [
"num-traits",
"pkcs1",
"pkcs8",
- "rand_core",
+ "rand_core 0.6.4",
"signature",
"spki",
"subtle",
@@ -4950,17 +5148,11 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
"syn_derive",
"thiserror 1.0.58",
]
-[[package]]
-name = "rustc-demangle"
-version = "0.1.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
-
[[package]]
name = "rustc-hash"
version = "1.1.0"
@@ -5101,9 +5293,9 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.12"
+version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
@@ -5138,6 +5330,32 @@ dependencies = [
"parking_lot",
]
+[[package]]
+name = "schemars"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
+dependencies = [
+ "chrono",
+ "dyn-clone",
+ "ref-cast",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn 2.0.117",
+]
+
[[package]]
name = "scopeguard"
version = "1.1.0"
@@ -5167,7 +5385,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -5317,7 +5535,18 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
]
[[package]]
@@ -5429,7 +5658,7 @@ dependencies = [
"darling 0.20.10",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -5473,7 +5702,7 @@ dependencies = [
"convert_case 0.6.0",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
"xxhash-rust",
]
@@ -5484,7 +5713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4ad11700cbccdbd313703916eb8c97301ee423c4a06e5421b77956fdcb36a9f"
dependencies = [
"server_fn_macro",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -5508,7 +5737,7 @@ dependencies = [
"log",
"once_cell",
"openidconnect",
- "rand",
+ "rand 0.8.5",
"regex",
"reqwest",
"rs-snowflake",
@@ -5533,7 +5762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.7",
"digest",
]
@@ -5544,7 +5773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.7",
"digest",
]
@@ -5579,7 +5808,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
- "rand_core",
+ "rand_core 0.6.4",
]
[[package]]
@@ -5669,6 +5898,16 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "socket2"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "spin"
version = "0.5.2"
@@ -5691,6 +5930,19 @@ dependencies = [
"der",
]
+[[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 1.0.1",
+ "http-body-util",
+ "pin-project-lite",
+]
+
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -5725,7 +5977,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -5764,6 +6016,7 @@ dependencies = [
"service_utils",
"superposition_derives",
"superposition_macros",
+ "superposition_mcp",
"superposition_types",
"tracing",
"tracing-actix-web",
@@ -5814,13 +6067,32 @@ version = "0.100.2"
dependencies = [
"proc-macro-crate",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
name = "superposition_macros"
version = "0.100.2"
+[[package]]
+name = "superposition_mcp"
+version = "0.100.2"
+dependencies = [
+ "actix-web",
+ "anyhow",
+ "aws-smithy-runtime-api",
+ "aws-smithy-types",
+ "rmcp",
+ "rmcp-actix-web",
+ "schemars",
+ "serde",
+ "serde_json",
+ "superposition_sdk",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+]
+
[[package]]
name = "superposition_provider"
version = "0.100.2"
@@ -5965,9 +6237,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.103"
+version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -5983,7 +6255,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -6092,7 +6364,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -6103,7 +6375,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -6172,31 +6444,30 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.40.0"
+version = "1.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
+checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
dependencies = [
- "backtrace",
"bytes",
"libc",
"mio 1.0.2",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
- "socket2 0.5.10",
+ "socket2 0.6.3",
"tokio-macros",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
-version = "2.4.0"
+version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -6242,16 +6513,15 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.8"
+version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
- "tracing",
]
[[package]]
@@ -6383,7 +6653,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -6476,7 +6746,7 @@ checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -6487,7 +6757,7 @@ checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -6614,7 +6884,7 @@ dependencies = [
"indexmap 2.12.1",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
]
[[package]]
@@ -6629,7 +6899,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
- "syn 2.0.103",
+ "syn 2.0.117",
"toml 0.5.11",
"uniffi_meta",
]
@@ -6836,6 +7106,24 @@ dependencies = [
"wit-bindgen-rt",
]
+[[package]]
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasip3"
+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
+dependencies = [
+ "wit-bindgen",
+]
+
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
@@ -6858,7 +7146,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
"wasm-bindgen-shared",
]
@@ -6893,7 +7181,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -6928,7 +7216,29 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.103",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "wasm-encoder"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
+dependencies = [
+ "leb128fmt",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
+dependencies = [
+ "anyhow",
+ "indexmap 2.12.1",
+ "wasm-encoder",
+ "wasmparser",
]
[[package]]
@@ -6944,6 +7254,18 @@ dependencies = [
"web-sys",
]
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags 2.9.1",
+ "hashbrown 0.15.5",
+ "indexmap 2.12.1",
+ "semver",
+]
+
[[package]]
name = "web-sys"
version = "0.3.77"
@@ -7021,6 +7343,12 @@ dependencies = [
"windows-targets 0.48.0",
]
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
[[package]]
name = "windows-sys"
version = "0.42.0"
@@ -7063,6 +7391,15 @@ dependencies = [
"windows-targets 0.52.0",
]
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
[[package]]
name = "windows-targets"
version = "0.42.2"
@@ -7262,6 +7599,26 @@ dependencies = [
"windows-sys 0.48.0",
]
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+dependencies = [
+ "wit-bindgen-rust-macro",
+]
+
+[[package]]
+name = "wit-bindgen-core"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
+dependencies = [
+ "anyhow",
+ "heck 0.5.0",
+ "wit-parser",
+]
+
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
@@ -7271,6 +7628,74 @@ dependencies = [
"bitflags 2.9.1",
]
+[[package]]
+name = "wit-bindgen-rust"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
+dependencies = [
+ "anyhow",
+ "heck 0.5.0",
+ "indexmap 2.12.1",
+ "prettyplease",
+ "syn 2.0.117",
+ "wasm-metadata",
+ "wit-bindgen-core",
+ "wit-component",
+]
+
+[[package]]
+name = "wit-bindgen-rust-macro"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
+dependencies = [
+ "anyhow",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "wit-bindgen-core",
+ "wit-bindgen-rust",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
+dependencies = [
+ "anyhow",
+ "bitflags 2.9.1",
+ "indexmap 2.12.1",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap 2.12.1",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser",
+]
+
[[package]]
name = "xmlparser"
version = "0.13.6"
diff --git a/Cargo.toml b/Cargo.toml
index ee3901618..0aacadaa4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,6 +24,7 @@ members = [
"crates/superposition_core",
"crates/superposition_provider",
"crates/superposition_sdk",
+ "crates/superposition_mcp",
"examples/experimentation_client_integration_example",
"examples/cac_client_integration_example",
"examples/superposition-demo-app",
diff --git a/crates/frontend/src/components/default_config_form.rs b/crates/frontend/src/components/default_config_form.rs
index 531545470..2f44c9602 100644
--- a/crates/frontend/src/components/default_config_form.rs
+++ b/crates/frontend/src/components/default_config_form.rs
@@ -557,7 +557,7 @@ pub fn DefaultConfigForm(
view! {
@@ -571,7 +571,7 @@ pub fn DefaultConfigForm(
#[derive(Clone)]
pub enum ChangeType {
Delete,
- Update(DefaultConfigUpdateRequest),
+ Update(Box),
}
#[component]
diff --git a/crates/superposition/Cargo.toml b/crates/superposition/Cargo.toml
index 26fa4bada..8a4c10c15 100644
--- a/crates/superposition/Cargo.toml
+++ b/crates/superposition/Cargo.toml
@@ -7,7 +7,12 @@ rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[features]
+default = []
+mcp = ["superposition_mcp"]
+
[dependencies]
+superposition_mcp = { path = "../superposition_mcp", features = ["actix"], optional = true }
actix-files = { version = "0.6" }
actix-web = { workspace = true }
anyhow = { workspace = true }
diff --git a/crates/superposition/src/main.rs b/crates/superposition/src/main.rs
index cac15e002..78a016333 100644
--- a/crates/superposition/src/main.rs
+++ b/crates/superposition/src/main.rs
@@ -6,6 +6,9 @@ mod resolve;
mod webhooks;
mod workspace;
+#[cfg(feature = "mcp")]
+use superposition_mcp as _;
+
use std::{io::Result, time::Duration};
use actix_files::Files;
@@ -146,36 +149,54 @@ async fn main() -> Result<()> {
let auth_z = AuthZHandler::init(&kms_client, &app_env).await;
let auth_z_manager = AuthZManager::init(&kms_client, &app_env).await;
+ // MCP server (optional, enabled via `mcp` feature + SUPERPOSITION_MCP=true env var)
+ #[cfg(feature = "mcp")]
+ let mcp_service = {
+ if get_from_env_or_default("SUPERPOSITION_MCP", false) {
+ match superposition_mcp::McpServerConfig::from_env() {
+ Ok(mcp_config) => {
+ tracing::info!("MCP server enabled at {}/mcp", base);
+ Some(superposition_mcp::actix::mcp_scope(mcp_config))
+ }
+ Err(e) => {
+ tracing::warn!("MCP server disabled: {e}");
+ None
+ }
+ }
+ } else {
+ None
+ }
+ };
+
HttpServer::new(move || {
let leptos_options = &conf.leptos_options;
let site_root = &leptos_options.site_root;
let leptos_envs = ui_envs.clone();
- App::new()
- .app_data(app_state.clone())
- .app_data(PathConfig::default().error_handler(|err, _| bad_argument!(err).into()))
- .app_data(QueryConfig::default().error_handler(|err, _| bad_argument!(err).into()))
- .leptos_routes(
- leptos_options.to_owned(),
- routes.to_owned(),
- move || {
- provide_context(use_request_headers());
- view! { }
- },
+
+ #[allow(unused_mut)]
+ let mut base_scope = scope(&base)
+ .route(
+ "/health",
+ get().to(|| async { HttpResponse::Ok().body("Health is good :D") }),
)
- .service(
- scope(&base)
- .route(
- "/health",
- get().to(|| async { HttpResponse::Ok().body("Health is good :D") }),
- )
- .service(auth_n.routes())
- .service(auth_n.org_routes())
- .service(web::redirect("", ui_redirect_path.to_string()))
- .service(web::redirect("/", ui_redirect_path.to_string()))
- .service(web::redirect("/admin", ui_redirect_path.to_string()))
- .service(web::redirect("/admin/", ui_redirect_path.to_string()))
- .service(web::redirect("/admin/{org_id}/", "workspaces"))
- .service(web::redirect("/admin/{org_id}/{tenant}/", "default-config"))
+ .service(auth_n.routes())
+ .service(auth_n.org_routes())
+ .service(web::redirect("", ui_redirect_path.to_string()))
+ .service(web::redirect("/", ui_redirect_path.to_string()))
+ .service(web::redirect("/admin", ui_redirect_path.to_string()))
+ .service(web::redirect("/admin/", ui_redirect_path.to_string()))
+ .service(web::redirect("/admin/{org_id}/", "workspaces"))
+ .service(web::redirect("/admin/{org_id}/{tenant}/", "default-config"));
+
+ // Mount MCP endpoint if enabled
+ #[cfg(feature = "mcp")]
+ if let Some(ref mcp) = mcp_service {
+ base_scope = base_scope.service(
+ web::scope("/mcp").service(mcp.clone().scope()),
+ );
+ }
+
+ let base_scope = base_scope
/***************************** V1 Routes *****************************/
.service(
scope("/context")
@@ -279,7 +300,21 @@ async fn main() -> Result<()> {
// serve other assets from the `assets` directory
.service(Files::new("/assets", site_root.to_string()))
// serve the favicon from /favicon.ico
+ ;
+
+ App::new()
+ .app_data(app_state.clone())
+ .app_data(PathConfig::default().error_handler(|err, _| bad_argument!(err).into()))
+ .app_data(QueryConfig::default().error_handler(|err, _| bad_argument!(err).into()))
+ .leptos_routes(
+ leptos_options.to_owned(),
+ routes.to_owned(),
+ move || {
+ provide_context(use_request_headers());
+ view! { }
+ },
)
+ .service(base_scope)
.route(
"/health",
get().to(|| async { HttpResponse::Ok().body("Health is good :D") }),
diff --git a/crates/superposition_mcp/CODEGEN_PLAN.md b/crates/superposition_mcp/CODEGEN_PLAN.md
new file mode 100644
index 000000000..731e21e2e
--- /dev/null
+++ b/crates/superposition_mcp/CODEGEN_PLAN.md
@@ -0,0 +1,329 @@
+# Smithy-to-MCP Deterministic Code Generator Plan
+
+## Problem
+
+Each MCP tool file manually declares parameter structs, SDK builder calls, and
+response formatting that can be derived from the Smithy model. When a new
+operation or service is added to Smithy, an engineer must manually write the
+corresponding MCP tool code — ~200 lines of boilerplate per resource.
+
+## Approach: Smithy Codegen Plugin
+
+Write a **custom Smithy codegen plugin** (Java/Kotlin) that reads the same
+`.smithy` models and emits Rust source files for the MCP crate. This runs
+alongside the existing `rust-client-codegen` plugin in `smithy-build.json`.
+
+### Why a Smithy Plugin (vs. Parsing Rust SDK Output)
+
+- Smithy's Java model API gives structured access to operations, shapes,
+ traits (`@http`, `@httpQuery`, `@required`, `@documentation`, etc.)
+- No fragile regex/AST parsing of generated Rust code
+- Runs as part of the existing `smithy build` pipeline
+- Same approach used by the 6 existing codegen plugins
+
+## Generated Artifacts
+
+For each Smithy resource (e.g., `Context`, `Dimension`), generate:
+
+### 1. `tools/{resource}.rs` — Parameter Structs + Impl
+
+```
+// AUTO-GENERATED — DO NOT EDIT
+// Source: smithy/models/context.smithy
+
+use serde::Deserialize;
+use schemars::JsonSchema;
+
+/// Params for CreateContext
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateContextParams {
+ /// The context conditions (required)
+ pub context: serde_json::Value,
+ /// Override values (required)
+ pub r#override: serde_json::Value,
+ /// Reason for the change (required)
+ pub change_reason: String,
+ /// Optional description
+ pub description: Option,
+ /// Config tags header
+ pub config_tags: Option,
+}
+
+// ... more param structs for Get, List, Update, Delete ...
+
+impl SuperpositionMcpServer {
+ pub async fn create_context_impl(
+ &self,
+ args: CreateContextParams,
+ ) -> Result {
+ let ctx_map = json_to_doc_map(args.context).map_err(mcp_err)?;
+ let ovr_map = json_to_doc_map(args.r#override).map_err(mcp_err)?;
+ let mut put_builder = superposition_sdk::types::ContextPut::builder()
+ .set_context(Some(ctx_map))
+ .set_override(Some(ovr_map))
+ .change_reason(args.change_reason);
+ if let Some(d) = args.description {
+ put_builder = put_builder.description(d);
+ }
+ let put = put_builder.build().map_err(mcp_err)?;
+ let mut req = self.client
+ .create_context()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id)
+ .request(put);
+ if let Some(tags) = args.config_tags {
+ req = req.config_tags(tags);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(
+ &context_to_json!(resp)
+ ).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+}
+```
+
+### 2. `server_tools.rs` — Tool Registration
+
+```
+// AUTO-GENERATED — DO NOT EDIT
+
+#[tool_router]
+impl SuperpositionMcpServer {
+ #[tool(
+ name = "context.create",
+ description = "Creates a new context with specified conditions and overrides."
+ )]
+ async fn context_create(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.create_context_impl(args).await
+ }
+ // ... all other tools ...
+}
+```
+
+### 3. `helpers_gen.rs` — Response Macros
+
+```
+// AUTO-GENERATED — DO NOT EDIT
+
+macro_rules! context_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "id": $r.id,
+ "value": $crate::helpers::doc_map_to_json(&$r.value),
+ // ... derived from output shape fields ...
+ })
+ }}
+}
+```
+
+## Smithy Model → Rust Mapping Rules
+
+These are deterministic rules the codegen plugin applies:
+
+### Tool Naming
+
+```
+Resource "Context" + Operation "Create" → "context.create"
+Resource "DefaultConfig" + Operation "List" → "default_config.list"
+Resource "ExperimentGroup" + Operation "Delete" → "experiment_group.delete"
+```
+
+Rule: `snake_case(resource) + "." + snake_case(verb)`
+
+### Parameter Struct Generation
+
+For each operation input shape, emit a Rust struct field per member:
+
+| Smithy Trait | Rust Type | Builder Call Pattern |
+|----------------------------|------------------------------|---------------------------------------------|
+| `@required` String | `pub field: String` | `.field(args.field)` |
+| Optional String | `pub field: Option` | `if let Some(x) = args.field { req = req.field(x); }` |
+| `@required` Document/Map | `pub field: serde_json::Value` | `.set_field(Some(json_to_doc_map(args.field)?))` |
+| Optional Document/Map | `pub field: Option` | conversion + conditional set |
+| `@required` Integer | `pub field: i32` | `.field(args.field)` |
+| Optional Integer | `pub field: Option` | conditional |
+| `@required` List | `pub field: Vec` | `for x in args.field { req = req.field(x); }` |
+| Optional List | `pub field: Option>` | conditional iteration |
+| `@httpHeader("x-config-tags")` | `pub config_tags: Option` | `req = req.config_tags(tags);` |
+| `@httpQuery("prefix")` | `pub prefix: Option>` | iteration pattern |
+| `@httpLabel` | `pub id: String` | `.id(args.id)` |
+| `WorkspaceMixin` | (omitted from params) | `.workspace_id(...).org_id(...)` |
+| `PaginationParams` | count/page Option | conditional set |
+
+### Response Macro Generation
+
+For each output shape, emit a `resource_to_json!` macro. Field mapping:
+
+| Smithy Output Type | JSON Macro Expression |
+|-----------------------------|------------------------------------------------|
+| String field | `"field": $r.field` |
+| DateTime field | `"field": format_datetime(&$r.field)` |
+| HashMap | `"field": doc_map_to_json(&$r.field)` |
+| Document field | `"field": doc_to_json(&$r.field)` |
+| Integer/Boolean | `"field": $r.field` |
+| Optional | `"field": $r.field` (serde handles None→null) |
+| Nested structure | `"field": nested_to_json!($r.field)` |
+
+### Operation Classification
+
+| Smithy Trait | Behavior |
+|-----------------|---------------------------------------------------|
+| `@readonly` | GET — no change_reason, no config_tags |
+| `@idempotent` | PUT — may have @httpPayload wrapping |
+| (default POST) | POST — standard create/update |
+| DELETE | DELETE — minimal params (usually just id) |
+
+### Special Cases (Handled Deterministically)
+
+1. **`@httpPayload` wrapper**: When input has a `request` field marked
+ `@httpPayload`, the params struct flattens the payload fields and the impl
+ constructs the wrapper type via its builder.
+
+2. **`ContextIdentifier` union**: The `UpdateOverride` operation uses a union
+ type. The codegen can detect `@union` shapes and emit the appropriate
+ if/else dispatch.
+
+3. **Enum fields** (e.g., `ExperimentType`, `SortBy`): Parse string → enum
+ using `from_str` or match, with validation error on unknown values.
+
+4. **Bulk operations**: Operations with `List` input get iteration in the
+ builder call.
+
+## Implementation Steps
+
+### Phase 1: Smithy Plugin Skeleton
+
+1. Create `smithy/mcp-codegen/` as a Gradle/Maven Java project
+2. Implement `SmithyIntegration` and `DirectedCodegen` interfaces
+3. Register plugin in `smithy-build.json` as `"mcp-rust-codegen"`
+4. Walk all operations in the service, group by resource
+
+### Phase 2: Parameter Struct Generator
+
+1. For each operation, resolve the effective input shape (expanding mixins)
+2. Classify each member by its traits (@httpQuery, @httpHeader, @httpLabel,
+ @httpPayload, @required, etc.)
+3. Filter out WorkspaceMixin/OrganisationMixin members (handled implicitly)
+4. Emit `#[derive(Debug, Deserialize, JsonSchema)]` struct with doc comments
+ from `@documentation`
+
+### Phase 3: Implementation Generator
+
+1. Emit the `impl SuperpositionMcpServer` block per resource
+2. For each operation, emit the handler method:
+ - Initialize SDK builder: `self.client.{snake_case(operation_name)}()`
+ - Always chain `.workspace_id(...)` and `.org_id(...)` (unless org-level op)
+ - For `@httpPayload` ops: construct the payload type via its builder first
+ - Chain required fields directly
+ - Wrap optional fields in `if let Some`
+ - Apply type conversions (json_to_doc_map for Document types)
+ - `.send().await.map_err(mcp_err)?`
+ - Format response using the generated macro
+
+### Phase 4: Tool Registration Generator
+
+1. Emit `#[tool_router] impl` block with all `#[tool(...)]` methods
+2. Tool name = `snake_case(resource) + "." + snake_case(verb)`
+3. Description = `@documentation` from the Smithy operation
+
+### Phase 5: Response Macro Generator
+
+1. For each output shape, walk its members
+2. Emit `macro_rules!` with field-by-field JSON construction
+3. Apply type-specific formatting (DateTime, Document, etc.)
+
+### Phase 6: Integration
+
+1. Add to `smithy-build.json` plugins list
+2. Wire generated files into `crates/superposition_mcp/src/` via
+ `include!()` or as a separate generated module
+3. Keep `helpers.rs` (json_to_doc, doc_to_json, mcp_err) as hand-written
+ shared utilities
+4. Add CI step: `smithy build` then `cargo check` on MCP crate
+
+## Alternative: Standalone Rust Generator (Simpler)
+
+If adding a Smithy Java plugin feels heavyweight, an alternative is a
+standalone Rust binary that:
+
+1. Parses the Smithy JSON AST (run `smithy ast` to get JSON)
+2. Walks operations and shapes from the JSON
+3. Applies the same deterministic rules above
+4. Emits `.rs` files via string templates (e.g., `askama` or `tera`)
+
+**Pros**: No Java toolchain, can run as `cargo run -p mcp-codegen`
+**Cons**: Must parse Smithy's JSON AST format instead of using the Java API
+
+### Smithy JSON AST Approach
+
+```bash
+# Generate JSON AST from Smithy models
+cd smithy && smithy ast models/ > ast.json
+```
+
+The JSON AST contains all shapes, traits, and relationships needed:
+
+```json
+{
+ "smithy": "2.0",
+ "shapes": {
+ "io.superposition#CreateContext": {
+ "type": "operation",
+ "input": { "target": "io.superposition#CreateContextInput" },
+ "output": { "target": "io.superposition#ContextResponse" },
+ "traits": {
+ "smithy.api#http": { "method": "PUT", "uri": "/context" },
+ "smithy.api#documentation": "Creates a new context...",
+ "smithy.api#tags": ["Context Management"]
+ }
+ }
+ }
+}
+```
+
+## File Structure
+
+```
+superposition/
+├── smithy/
+│ ├── models/ # Existing .smithy files (source of truth)
+│ ├── smithy-build.json # Add mcp-codegen plugin
+│ └── mcp-codegen/ # Option A: Smithy Java plugin
+│ └── src/main/java/
+│
+├── crates/
+│ ├── mcp-codegen/ # Option B: Standalone Rust generator
+│ │ ├── Cargo.toml
+│ │ ├── src/main.rs # Reads JSON AST, emits .rs files
+│ │ └── templates/ # Tera/Askama templates
+│ │
+│ └── superposition_mcp/
+│ └── src/
+│ ├── server.rs # Hand-written: McpServer struct, config
+│ ├── helpers.rs # Hand-written: json_to_doc, mcp_err, etc.
+│ ├── generated/ # AUTO-GENERATED
+│ │ ├── mod.rs
+│ │ ├── tools/
+│ │ │ ├── context.rs
+│ │ │ ├── dimension.rs
+│ │ │ └── ...
+│ │ ├── server_tools.rs # #[tool_router] impl
+│ │ └── response_macros.rs
+│ └── tools/ # Optional: hand-written overrides
+│ └── mod.rs # Re-exports generated + any custom tools
+```
+
+## Estimated Complexity
+
+- **Smithy Plugin (Java)**: ~500-800 lines of Java/Kotlin
+- **Standalone Rust Generator**: ~600-1000 lines of Rust
+- **Templates**: ~200 lines of template code
+- **Integration/CI**: ~50 lines of build config
+
+The generator would eliminate ~3000+ lines of hand-written boilerplate across
+14 tool files and make adding new Smithy operations a zero-code-change process
+for the MCP layer.
diff --git a/crates/superposition_mcp/Cargo.toml b/crates/superposition_mcp/Cargo.toml
new file mode 100644
index 000000000..76c7bdd4c
--- /dev/null
+++ b/crates/superposition_mcp/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "superposition_mcp"
+description = "MCP (Model Context Protocol) server for Superposition, exposing all API operations as MCP tools"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+
+[features]
+default = []
+actix = ["rmcp-actix-web", "actix-web"]
+
+[dependencies]
+rmcp = { version = "1.2.0", features = ["server", "transport-io"] }
+rmcp-actix-web = { version = "0.12", optional = true }
+superposition_sdk = { workspace = true }
+aws-smithy-types = "1.3.0"
+aws-smithy-runtime-api = "1.8.3"
+serde = { workspace = true }
+serde_json = { workspace = true }
+tokio = { workspace = true }
+actix-web = { workspace = true, optional = true }
+schemars = "1"
+tracing = { workspace = true }
+tracing-subscriber = { workspace = true }
+anyhow = { workspace = true }
+
+[dev-dependencies]
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
+
+[lints]
+workspace = true
diff --git a/crates/superposition_mcp/src/actix.rs b/crates/superposition_mcp/src/actix.rs
new file mode 100644
index 000000000..423ec3bdc
--- /dev/null
+++ b/crates/superposition_mcp/src/actix.rs
@@ -0,0 +1,48 @@
+//! Actix-web integration for mounting the MCP server as an HTTP endpoint.
+
+use std::sync::Arc;
+use std::time::Duration;
+
+use actix_web::web;
+use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
+use rmcp_actix_web::transport::StreamableHttpService;
+
+use crate::{McpServerConfig, SuperpositionMcpServer};
+
+/// Creates an actix-web `Scope` that serves the MCP server at the mounted path.
+///
+/// The returned scope handles POST (JSON-RPC requests), GET (SSE stream resumption),
+/// and DELETE (session termination) for the Streamable HTTP MCP transport.
+///
+/// # Example
+///
+/// ```ignore
+/// use actix_web::{App, HttpServer, web};
+/// use superposition_mcp::actix::mcp_scope;
+///
+/// let mcp_service = mcp_scope(mcp_config);
+/// HttpServer::new(move || {
+/// App::new()
+/// .service(web::scope("/mcp").service(mcp_service.clone()))
+/// })
+/// ```
+pub fn mcp_scope(
+ config: McpServerConfig,
+) -> StreamableHttpService {
+ StreamableHttpService::builder()
+ .service_factory(Arc::new(move || {
+ Ok(SuperpositionMcpServer::new(config.clone()))
+ }))
+ .session_manager(Arc::new(LocalSessionManager::default()))
+ .stateful_mode(true)
+ .sse_keep_alive(Duration::from_secs(30))
+ .build()
+}
+
+/// Creates an actix-web `Scope` mounted at `/mcp`.
+///
+/// This is a convenience wrapper that creates the scope with the standard path.
+pub fn mcp_service(config: McpServerConfig) -> actix_web::Scope {
+ let service = mcp_scope(config);
+ web::scope("/mcp").service(service.scope())
+}
diff --git a/crates/superposition_mcp/src/config.rs b/crates/superposition_mcp/src/config.rs
new file mode 100644
index 000000000..c0a433f01
--- /dev/null
+++ b/crates/superposition_mcp/src/config.rs
@@ -0,0 +1,54 @@
+use std::env;
+
+/// Configuration for the Superposition MCP server.
+#[derive(Clone, Debug)]
+pub struct McpServerConfig {
+ /// Base URL of the Superposition API (e.g., "http://localhost:8080")
+ pub endpoint_url: String,
+ /// Default workspace ID injected into all SDK calls
+ pub workspace_id: String,
+ /// Default organisation ID injected into all SDK calls
+ pub org_id: String,
+ /// Optional bearer token for authentication
+ pub auth_token: Option,
+}
+
+impl McpServerConfig {
+ /// Reads configuration from environment variables.
+ ///
+ /// Required:
+ /// - `SUPERPOSITION_URL` — Base URL of the Superposition API
+ /// - `SUPERPOSITION_WORKSPACE` — Default workspace ID
+ /// - `SUPERPOSITION_ORG_ID` — Default organisation ID
+ ///
+ /// Optional:
+ /// - `SUPERPOSITION_AUTH_TOKEN` — Bearer token for authentication
+ pub fn from_env() -> anyhow::Result {
+ Ok(Self {
+ endpoint_url: env::var("SUPERPOSITION_URL")
+ .map_err(|_| anyhow::anyhow!("SUPERPOSITION_URL env var is required"))?,
+ workspace_id: env::var("SUPERPOSITION_WORKSPACE").map_err(|_| {
+ anyhow::anyhow!("SUPERPOSITION_WORKSPACE env var is required")
+ })?,
+ org_id: env::var("SUPERPOSITION_ORG_ID").map_err(|_| {
+ anyhow::anyhow!("SUPERPOSITION_ORG_ID env var is required")
+ })?,
+ auth_token: env::var("SUPERPOSITION_AUTH_TOKEN").ok(),
+ })
+ }
+
+ /// Build a `superposition_sdk::Client` from this configuration.
+ pub fn build_sdk_client(&self) -> superposition_sdk::Client {
+ let mut config_builder = superposition_sdk::Config::builder()
+ .endpoint_url(&self.endpoint_url)
+ .behavior_version(superposition_sdk::config::BehaviorVersion::latest());
+
+ if let Some(ref token) = self.auth_token {
+ use aws_smithy_runtime_api::client::identity::http::Token;
+ config_builder = config_builder.bearer_token(Token::new(token, None));
+ }
+
+ let config = config_builder.build();
+ superposition_sdk::Client::from_conf(config)
+ }
+}
diff --git a/crates/superposition_mcp/src/generated/audit_log.rs b/crates/superposition_mcp/src/generated/audit_log.rs
new file mode 100644
index 000000000..c48e344f6
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/audit_log.rs
@@ -0,0 +1,73 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Retrieves a paginated list of audit logs with support for filtering by date range, table names, actions, and usernames for compliance and monitoring purposes.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListAuditLogParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+ pub from_date: Option,
+ pub to_date: Option,
+ pub tables: Option>,
+ pub action: Option>,
+ pub username: Option,
+ pub sort_by: Option,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn list_audit_log_impl(&self, args: ListAuditLogParams) -> Result {
+ let mut req = self.client.list_audit_logs()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ if let Some(v) = args.from_date {
+ req = req.from_date(v);
+ }
+ if let Some(v) = args.to_date {
+ req = req.to_date(v);
+ }
+ if let Some(v) = args.tables {
+ for item in v {
+ req = req.tables(item);
+ }
+ }
+ if let Some(v) = args.action {
+ for item in v {
+ req = req.action(item);
+ }
+ }
+ if let Some(v) = args.username {
+ req = req.username(v);
+ }
+ if let Some(v) = args.sort_by {
+ req = req.sort_by(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| audit_log_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/config.rs b/crates/superposition_mcp/src/generated/config.rs
new file mode 100644
index 000000000..b7300b5dd
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/config.rs
@@ -0,0 +1,159 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Retrieves config data with context evaluation, including applicable contexts, overrides, and default values based on provided conditions.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetConfigParams {
+ pub prefix: Option>,
+ pub version: Option,
+ pub context: Option,
+}
+
+/// Resolves and merges config values based on context conditions, applying overrides and merge strategies to produce the final configuration.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetResolvedConfigConfigParams {
+ pub prefix: Option>,
+ pub version: Option,
+ pub show_reasoning: Option,
+ pub merge_strategy: Option,
+ pub context_id: Option,
+ /// Intended for control resolution. If true, evaluates and includes remote cohort-based contexts during config resolution.
+ pub resolve_remote: Option,
+ pub context: Option,
+}
+
+/// Resolves and merges config values based on context conditions and identifier, applying overrides and merge strategies to produce the final configuration.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetResolvedConfigWithIdentifierConfigParams {
+ pub prefix: Option>,
+ pub version: Option,
+ pub show_reasoning: Option,
+ pub merge_strategy: Option,
+ pub context_id: Option,
+ /// Intended for control resolution. If true, evaluates and includes remote cohort-based contexts during config resolution.
+ pub resolve_remote: Option,
+ pub context: Option,
+ pub identifier: Option,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn get_config_fast_config_impl(&self) -> Result {
+ let mut req = self.client.get_config_fast()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_config_impl(&self, args: GetConfigParams) -> Result {
+ let mut req = self.client.get_config()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.prefix {
+ for item in v {
+ req = req.prefix(item);
+ }
+ }
+ if let Some(v) = args.version {
+ req = req.version(v);
+ }
+ if let Some(v) = args.context {
+ req = req.set_context(Some(json_to_doc_map(v).map_err(mcp_err)?));
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_resolved_config_config_impl(&self, args: GetResolvedConfigConfigParams) -> Result {
+ let mut req = self.client.get_resolved_config()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.prefix {
+ for item in v {
+ req = req.prefix(item);
+ }
+ }
+ if let Some(v) = args.version {
+ req = req.version(v);
+ }
+ if let Some(v) = args.show_reasoning {
+ req = req.show_reasoning(v);
+ }
+ if let Some(v) = args.context_id {
+ req = req.context_id(v);
+ }
+ if let Some(v) = args.resolve_remote {
+ req = req.resolve_remote(v);
+ }
+ if let Some(v) = args.context {
+ req = req.set_context(Some(json_to_doc_map(v).map_err(mcp_err)?));
+ }
+ if let Some(v) = args.merge_strategy {
+ req = req.merge_strategy(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_resolved_config_with_identifier_config_impl(&self, args: GetResolvedConfigWithIdentifierConfigParams) -> Result {
+ let mut req = self.client.get_resolved_config_with_identifier()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.prefix {
+ for item in v {
+ req = req.prefix(item);
+ }
+ }
+ if let Some(v) = args.version {
+ req = req.version(v);
+ }
+ if let Some(v) = args.show_reasoning {
+ req = req.show_reasoning(v);
+ }
+ if let Some(v) = args.context_id {
+ req = req.context_id(v);
+ }
+ if let Some(v) = args.resolve_remote {
+ req = req.resolve_remote(v);
+ }
+ if let Some(v) = args.context {
+ req = req.set_context(Some(json_to_doc_map(v).map_err(mcp_err)?));
+ }
+ if let Some(v) = args.identifier {
+ req = req.identifier(v);
+ }
+ if let Some(v) = args.merge_strategy {
+ req = req.merge_strategy(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_config_toml_config_impl(&self) -> Result {
+ let mut req = self.client.get_config_toml()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_config_json_config_impl(&self) -> Result {
+ let mut req = self.client.get_config_json()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/config_version.rs b/crates/superposition_mcp/src/generated/config_version.rs
new file mode 100644
index 000000000..b800e917c
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/config_version.rs
@@ -0,0 +1,56 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Retrieves a specific config version along with its metadata for audit and rollback purposes.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetConfigVersionParams {
+ pub id: String,
+}
+
+/// Retrieves a paginated list of config versions with their metadata, hash values, and creation timestamps for audit and rollback purposes.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListConfigVersionParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn get_config_version_impl(&self, args: GetConfigVersionParams) -> Result {
+ let mut req = self.client.get_version()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&config_version_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_config_version_impl(&self, args: ListConfigVersionParams) -> Result {
+ let mut req = self.client.list_versions()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| config_version_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/context.rs b/crates/superposition_mcp/src/generated/context.rs
new file mode 100644
index 000000000..feaca89e3
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/context.rs
@@ -0,0 +1,258 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Creates a new context with specified conditions and overrides. Contexts define conditional rules for config management.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateContextParams {
+ pub config_tags: Option,
+ pub context: serde_json::Value,
+ pub r#override: serde_json::Value,
+ pub description: Option,
+ pub change_reason: String,
+}
+
+/// Retrieves detailed information about a specific context by its unique identifier, including conditions, overrides, and metadata.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetContextParams {
+ pub id: String,
+}
+
+/// Permanently removes a context from the workspace. This operation cannot be undone and will affect config resolution.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct DeleteContextParams {
+ pub id: String,
+ pub config_tags: Option,
+}
+
+/// Retrieves a paginated list of contexts with support for filtering by creation date, modification date, weight, and other criteria.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListContextParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+ pub prefix: Option>,
+ pub sort_on: Option,
+ pub sort_by: Option,
+ pub created_by: Option>,
+ pub last_modified_by: Option>,
+ pub plaintext: Option,
+ pub dimension_match_strategy: Option,
+}
+
+/// Updates the condition of the mentioned context, if a context with the new condition already exists, it merges the override and effectively deleting the old context
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct MoveContextParams {
+ pub id: String,
+ pub context: serde_json::Value,
+ pub description: Option,
+ pub change_reason: String,
+}
+
+/// Updates the overrides for an existing context. Allows modification of override values while maintaining the context's conditions.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateOverrideContextParams {
+ pub config_tags: Option,
+ pub context: serde_json::Value,
+ pub r#override: serde_json::Value,
+ pub description: Option,
+ pub change_reason: String,
+}
+
+/// Recalculates and updates the priority weights for all contexts in the workspace based on their dimensions.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct WeightRecomputeContextParams {
+ pub config_tags: Option,
+}
+
+/// Executes multiple context operations (PUT, REPLACE, DELETE, MOVE) in a single atomic transaction for efficient batch processing.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct BulkOperationContextParams {
+ pub config_tags: Option,
+ pub operations: Vec,
+}
+
+/// Validates if a given context condition is well-formed
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ValidateContextParams {
+ pub context: serde_json::Value,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn create_context_impl(&self, args: CreateContextParams) -> Result {
+ let mut req = self.client.create_context()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.set_context(Some(json_to_doc_map(args.context).map_err(mcp_err)?));
+ req = req.set_override(Some(json_to_doc_map(args.r#override).map_err(mcp_err)?));
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ if let Some(v) = args.config_tags {
+ req = req.config_tags(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&context_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_context_impl(&self, args: GetContextParams) -> Result {
+ let mut req = self.client.get_context()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&context_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn delete_context_impl(&self, args: DeleteContextParams) -> Result {
+ let mut req = self.client.delete_context()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ if let Some(v) = args.config_tags {
+ req = req.config_tags(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = "Deleted successfully".to_string();
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_context_impl(&self, args: ListContextParams) -> Result {
+ let mut req = self.client.list_contexts()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ if let Some(v) = args.prefix {
+ for item in v {
+ req = req.prefix(item);
+ }
+ }
+ if let Some(v) = args.sort_on {
+ req = req.sort_on(v);
+ }
+ if let Some(v) = args.sort_by {
+ req = req.sort_by(v);
+ }
+ if let Some(v) = args.created_by {
+ for item in v {
+ req = req.created_by(item);
+ }
+ }
+ if let Some(v) = args.last_modified_by {
+ for item in v {
+ req = req.last_modified_by(item);
+ }
+ }
+ if let Some(v) = args.plaintext {
+ req = req.plaintext(v);
+ }
+ if let Some(v) = args.dimension_match_strategy {
+ req = req.dimension_match_strategy(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| context_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn move_context_impl(&self, args: MoveContextParams) -> Result {
+ let mut req = self.client.move_context()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.set_context(Some(json_to_doc_map(args.context).map_err(mcp_err)?));
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&context_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn update_override_context_impl(&self, args: UpdateOverrideContextParams) -> Result {
+ let mut req = self.client.update_override()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.context(args.context);
+ req = req.set_override(Some(json_to_doc_map(args.r#override).map_err(mcp_err)?));
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ if let Some(v) = args.config_tags {
+ req = req.config_tags(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&context_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_context_from_condition_context_impl(&self) -> Result {
+ let mut req = self.client.get_context_from_condition()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&context_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn weight_recompute_context_impl(&self, args: WeightRecomputeContextParams) -> Result {
+ let mut req = self.client.weight_recompute()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.config_tags {
+ req = req.config_tags(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&context_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn bulk_operation_context_impl(&self, args: BulkOperationContextParams) -> Result {
+ let mut req = self.client.bulk_operation()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.operations(args.operations);
+ if let Some(v) = args.config_tags {
+ req = req.config_tags(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&context_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn validate_context_impl(&self, args: ValidateContextParams) -> Result {
+ let mut req = self.client.validate_context()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.set_context(Some(json_to_doc_map(args.context).map_err(mcp_err)?));
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = "Success".to_string();
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/default_config.rs b/crates/superposition_mcp/src/generated/default_config.rs
new file mode 100644
index 000000000..c40dc4e1d
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/default_config.rs
@@ -0,0 +1,153 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Retrieves a specific default config entry by its key, including its value, schema, function mappings, and metadata.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetDefaultConfigParams {
+ pub key: String,
+}
+
+/// Updates an existing default config entry. Allows modification of value, schema, function mappings, and description while preserving the key identifier.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateDefaultConfigParams {
+ pub key: String,
+ pub change_reason: String,
+ pub value: Option,
+ pub schema: Option,
+ /// To unset the function name, pass "null" string.
+ pub value_validation_function_name: Option,
+ pub description: Option,
+ /// To unset the function name, pass "null" string.
+ pub value_compute_function_name: Option,
+}
+
+/// Permanently removes a default config entry from the workspace. This operation cannot be performed if it affects config resolution for contexts that rely on this fallback value.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct DeleteDefaultConfigParams {
+ pub key: String,
+}
+
+/// Retrieves a paginated list of all default config entries in the workspace, including their values, schemas, and metadata.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListDefaultConfigParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+ pub name: Option,
+}
+
+/// Creates a new default config entry with specified key, value, schema, and metadata. Default configs serve as fallback values when no specific context matches.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateDefaultConfigParams {
+ pub key: String,
+ pub value: serde_json::Value,
+ pub schema: serde_json::Value,
+ pub description: String,
+ pub change_reason: String,
+ pub value_validation_function_name: Option,
+ pub value_compute_function_name: Option,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn get_default_config_impl(&self, args: GetDefaultConfigParams) -> Result {
+ let mut req = self.client.get_default_config()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.key(args.key);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&default_config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn update_default_config_impl(&self, args: UpdateDefaultConfigParams) -> Result {
+ let mut req = self.client.update_default_config()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.key(args.key);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.value {
+ req = req.value(json_to_doc(v));
+ }
+ if let Some(v) = args.schema {
+ req = req.set_schema(Some(json_to_doc_map(v).map_err(mcp_err)?));
+ }
+ if let Some(v) = args.value_validation_function_name {
+ req = req.value_validation_function_name(v);
+ }
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ if let Some(v) = args.value_compute_function_name {
+ req = req.value_compute_function_name(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&default_config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn delete_default_config_impl(&self, args: DeleteDefaultConfigParams) -> Result {
+ let mut req = self.client.delete_default_config()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.key(args.key);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = "Deleted successfully".to_string();
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_default_config_impl(&self, args: ListDefaultConfigParams) -> Result {
+ let mut req = self.client.list_default_configs()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ if let Some(v) = args.name {
+ req = req.name(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| default_config_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn create_default_config_impl(&self, args: CreateDefaultConfigParams) -> Result {
+ let mut req = self.client.create_default_config()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.key(args.key);
+ req = req.value(json_to_doc(args.value));
+ req = req.set_schema(Some(json_to_doc_map(args.schema).map_err(mcp_err)?));
+ req = req.description(args.description);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.value_validation_function_name {
+ req = req.value_validation_function_name(v);
+ }
+ if let Some(v) = args.value_compute_function_name {
+ req = req.value_compute_function_name(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&default_config_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/dimension.rs b/crates/superposition_mcp/src/generated/dimension.rs
new file mode 100644
index 000000000..db5bbe0d8
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/dimension.rs
@@ -0,0 +1,153 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Retrieves detailed information about a specific dimension, including its schema, cohort dependency graph, and configuration metadata.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetDimensionParams {
+ pub dimension: String,
+}
+
+/// Updates an existing dimension's configuration. Allows modification of schema, position, function mappings, and other properties while maintaining dependency relationships.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateDimensionParams {
+ pub dimension: String,
+ pub schema: Option,
+ pub position: Option,
+ /// To unset the function name, pass "null" string.
+ pub value_validation_function_name: Option,
+ pub description: Option,
+ pub change_reason: String,
+ /// To unset the function name, pass "null" string.
+ pub value_compute_function_name: Option,
+}
+
+/// Permanently removes a dimension from the workspace. This operation will fail if the dimension has active dependencies or is referenced by existing configurations.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct DeleteDimensionParams {
+ pub dimension: String,
+}
+
+/// Retrieves a paginated list of all dimensions in the workspace. Dimensions are returned with their details and metadata.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListDimensionParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+}
+
+/// Creates a new dimension with the specified json schema. Dimensions define categorical attributes used for context-based config management.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateDimensionParams {
+ pub dimension: String,
+ pub position: i32,
+ pub schema: serde_json::Value,
+ pub value_validation_function_name: Option,
+ pub description: String,
+ pub change_reason: String,
+ pub dimension_type: Option,
+ pub value_compute_function_name: Option,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn get_dimension_impl(&self, args: GetDimensionParams) -> Result {
+ let mut req = self.client.get_dimension()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.dimension(args.dimension);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&dimension_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn update_dimension_impl(&self, args: UpdateDimensionParams) -> Result {
+ let mut req = self.client.update_dimension()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.dimension(args.dimension);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.schema {
+ req = req.set_schema(Some(json_to_doc_map(v).map_err(mcp_err)?));
+ }
+ if let Some(v) = args.position {
+ req = req.position(v);
+ }
+ if let Some(v) = args.value_validation_function_name {
+ req = req.value_validation_function_name(v);
+ }
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ if let Some(v) = args.value_compute_function_name {
+ req = req.value_compute_function_name(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&dimension_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn delete_dimension_impl(&self, args: DeleteDimensionParams) -> Result {
+ let mut req = self.client.delete_dimension()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.dimension(args.dimension);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = "Deleted successfully".to_string();
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_dimension_impl(&self, args: ListDimensionParams) -> Result {
+ let mut req = self.client.list_dimensions()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| dimension_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn create_dimension_impl(&self, args: CreateDimensionParams) -> Result {
+ let mut req = self.client.create_dimension()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.dimension(args.dimension);
+ req = req.position(args.position);
+ req = req.set_schema(Some(json_to_doc_map(args.schema).map_err(mcp_err)?));
+ req = req.description(args.description);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.value_validation_function_name {
+ req = req.value_validation_function_name(v);
+ }
+ if let Some(v) = args.dimension_type {
+ req = req.dimension_type(v);
+ }
+ if let Some(v) = args.value_compute_function_name {
+ req = req.value_compute_function_name(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&dimension_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/experiment_group.rs b/crates/superposition_mcp/src/generated/experiment_group.rs
new file mode 100644
index 000000000..c43edbbdc
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/experiment_group.rs
@@ -0,0 +1,218 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Creates a new experiment group.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateExperimentGroupParams {
+ pub name: String,
+ pub description: String,
+ /// Reason for creating this experiment group.
+ pub change_reason: String,
+ pub context: serde_json::Value,
+ pub traffic_percentage: i32,
+ /// List of experiment IDs that are members of this group.
+ pub member_experiment_ids: Option>,
+}
+
+/// Retrieves an existing experiment group by its ID.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetExperimentGroupParams {
+ pub id: String,
+}
+
+/// Updates an existing experiment group. Allows partial updates to specified fields.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateExperimentGroupParams {
+ pub id: String,
+ /// Reason for this update.
+ pub change_reason: String,
+ /// Optional new description for the group.
+ pub description: Option,
+ /// Optional new traffic percentage for the group.
+ pub traffic_percentage: Option,
+}
+
+/// Deletes an experiment group.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct DeleteExperimentGroupParams {
+ pub id: String,
+}
+
+/// Lists experiment groups, with support for filtering and pagination.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListExperimentGroupParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+ /// Filter by experiment group name (exact match or substring, depending on backend implementation).
+ pub name: Option,
+ /// Filter by the user who created the experiment group.
+ pub created_by: Option,
+ /// Filter by the user who last modified the experiment group.
+ pub last_modified_by: Option,
+ /// Field to sort the results by.
+ pub sort_on: Option,
+ /// Sort order (ascending or descending).
+ pub sort_by: Option,
+ /// Filter by the type of group (USER_CREATED or SYSTEM_GENERATED).
+ pub group_type: Option>,
+}
+
+/// Adds members to an existing experiment group.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct AddMembersToGroupExperimentGroupParams {
+ pub id: String,
+ /// Reason for adding these members.
+ pub change_reason: String,
+ /// List of experiment IDs to add/remove to this group.
+ pub member_experiment_ids: Vec,
+}
+
+/// Removes members from an existing experiment group.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct RemoveMembersFromGroupExperimentGroupParams {
+ pub id: String,
+ /// Reason for adding these members.
+ pub change_reason: String,
+ /// List of experiment IDs to add/remove to this group.
+ pub member_experiment_ids: Vec,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn create_experiment_group_impl(&self, args: CreateExperimentGroupParams) -> Result {
+ let mut req = self.client.create_experiment_group()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.name(args.name);
+ req = req.description(args.description);
+ req = req.change_reason(args.change_reason);
+ req = req.set_context(Some(json_to_doc_map(args.context).map_err(mcp_err)?));
+ req = req.traffic_percentage(args.traffic_percentage);
+ if let Some(v) = args.member_experiment_ids {
+ for item in v {
+ req = req.member_experiment_ids(item);
+ }
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiment_group_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_experiment_group_impl(&self, args: GetExperimentGroupParams) -> Result {
+ let mut req = self.client.get_experiment_group()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiment_group_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn update_experiment_group_impl(&self, args: UpdateExperimentGroupParams) -> Result {
+ let mut req = self.client.update_experiment_group()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ if let Some(v) = args.traffic_percentage {
+ req = req.traffic_percentage(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiment_group_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn delete_experiment_group_impl(&self, args: DeleteExperimentGroupParams) -> Result {
+ let mut req = self.client.delete_experiment_group()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiment_group_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_experiment_group_impl(&self, args: ListExperimentGroupParams) -> Result {
+ let mut req = self.client.list_experiment_groups()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ if let Some(v) = args.name {
+ req = req.name(v);
+ }
+ if let Some(v) = args.created_by {
+ req = req.created_by(v);
+ }
+ if let Some(v) = args.last_modified_by {
+ req = req.last_modified_by(v);
+ }
+ if let Some(v) = args.sort_on {
+ req = req.sort_on(v);
+ }
+ if let Some(v) = args.sort_by {
+ req = req.sort_by(v);
+ }
+ if let Some(v) = args.group_type {
+ for item in v {
+ req = req.group_type(item);
+ }
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| experiment_group_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn add_members_to_group_experiment_group_impl(&self, args: AddMembersToGroupExperimentGroupParams) -> Result {
+ let mut req = self.client.add_members_to_group()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.change_reason(args.change_reason);
+ for item in args.member_experiment_ids {
+ req = req.member_experiment_ids(item);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiment_group_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn remove_members_from_group_experiment_group_impl(&self, args: RemoveMembersFromGroupExperimentGroupParams) -> Result {
+ let mut req = self.client.remove_members_from_group()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.change_reason(args.change_reason);
+ for item in args.member_experiment_ids {
+ req = req.member_experiment_ids(item);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiment_group_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/experiments.rs b/crates/superposition_mcp/src/generated/experiments.rs
new file mode 100644
index 000000000..03787f08c
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/experiments.rs
@@ -0,0 +1,313 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Creates a new experiment with variants, context and conditions. You can optionally specify metrics and experiment group for tracking and analysis.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateExperimentsParams {
+ pub name: String,
+ pub experiment_type: Option,
+ pub context: serde_json::Value,
+ pub variants: Vec,
+ pub description: String,
+ pub change_reason: String,
+ pub metrics: Option,
+ pub experiment_group_id: Option,
+}
+
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateExperimentsVariantsItem {
+ pub id: String,
+ pub variant_type: String,
+ pub context_id: Option,
+ pub override_id: Option,
+ pub overrides: serde_json::Value,
+}
+
+/// Retrieves detailed information about a specific experiment, including its config, variants, status, and metrics.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetExperimentsParams {
+ pub id: String,
+}
+
+/// Retrieves a paginated list of experiments with support for filtering by status, date range, name, creator, and experiment group.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListExperimentsParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+ pub status: Option>,
+ pub from_date: Option,
+ pub to_date: Option,
+ pub experiment_name: Option,
+ pub experiment_ids: Option>,
+ pub experiment_group_ids: Option>,
+ pub created_by: Option>,
+ pub sort_on: Option,
+ pub sort_by: Option,
+ pub global_experiments_only: Option,
+ pub dimension_match_strategy: Option,
+}
+
+/// Updates the overrides for specific variants within an experiment, allowing modification of experiment behavior Updates the overrides for specific variants within an experiment, allowing modification of experiment behavior while it is in the created state.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateOverridesExperimentExperimentsParams {
+ pub id: String,
+ pub variant_list: Vec,
+ pub description: Option,
+ pub change_reason: String,
+ pub metrics: Option,
+ /// To unset experiment group, pass "null" string.
+ pub experiment_group_id: Option,
+}
+
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateOverridesExperimentExperimentsVariantListItem {
+ pub id: String,
+ pub overrides: serde_json::Value,
+}
+
+/// Concludes an inprogress experiment by selecting a winning variant and transitioning the experiment to a concluded state.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ConcludeExperimentExperimentsParams {
+ pub id: String,
+ pub chosen_variant: String,
+ pub description: Option,
+ pub change_reason: String,
+}
+
+/// Discards an experiment without selecting a winner, effectively canceling the experiment and removing its effects.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct DiscardExperimentExperimentsParams {
+ pub id: String,
+ pub change_reason: String,
+}
+
+/// Adjusts the traffic percentage allocation for an in-progress experiment, allowing gradual rollout or rollback of experimental features.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct RampExperimentExperimentsParams {
+ pub id: String,
+ pub change_reason: String,
+ pub traffic_percentage: i32,
+}
+
+/// Temporarily pauses an inprogress experiment, suspending its effects while preserving the experiment config for later resumption.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct PauseExperimentExperimentsParams {
+ pub id: String,
+ pub change_reason: String,
+}
+
+/// Resumes a previously paused experiment, restoring its in-progress state and re-enabling variant evaluation.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ResumeExperimentExperimentsParams {
+ pub id: String,
+ pub change_reason: String,
+}
+
+/// Determines which experiment variants are applicable to a given context, used for experiment evaluation and variant selection.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ApplicableVariantsExperimentsParams {
+ pub context: serde_json::Value,
+ pub identifier: String,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn create_experiments_impl(&self, args: CreateExperimentsParams) -> Result {
+ let mut req = self.client.create_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.name(args.name);
+ req = req.set_context(Some(json_to_doc_map(args.context).map_err(mcp_err)?));
+ req = req.variants(args.variants);
+ req = req.description(args.description);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.experiment_type {
+ req = req.experiment_type(v);
+ }
+ if let Some(v) = args.metrics {
+ req = req.metrics(json_to_doc(v));
+ }
+ if let Some(v) = args.experiment_group_id {
+ req = req.experiment_group_id(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_experiments_impl(&self, args: GetExperimentsParams) -> Result {
+ let mut req = self.client.get_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_experiments_impl(&self, args: ListExperimentsParams) -> Result {
+ let mut req = self.client.list_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ if let Some(v) = args.status {
+ for item in v {
+ req = req.status(item);
+ }
+ }
+ if let Some(v) = args.from_date {
+ req = req.from_date(v);
+ }
+ if let Some(v) = args.to_date {
+ req = req.to_date(v);
+ }
+ if let Some(v) = args.experiment_name {
+ req = req.experiment_name(v);
+ }
+ if let Some(v) = args.experiment_ids {
+ for item in v {
+ req = req.experiment_ids(item);
+ }
+ }
+ if let Some(v) = args.experiment_group_ids {
+ for item in v {
+ req = req.experiment_group_ids(item);
+ }
+ }
+ if let Some(v) = args.created_by {
+ for item in v {
+ req = req.created_by(item);
+ }
+ }
+ if let Some(v) = args.sort_on {
+ req = req.sort_on(v);
+ }
+ if let Some(v) = args.sort_by {
+ req = req.sort_by(v);
+ }
+ if let Some(v) = args.global_experiments_only {
+ req = req.global_experiments_only(v);
+ }
+ if let Some(v) = args.dimension_match_strategy {
+ req = req.dimension_match_strategy(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| experiments_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn update_overrides_experiment_experiments_impl(&self, args: UpdateOverridesExperimentExperimentsParams) -> Result {
+ let mut req = self.client.update_overrides_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.variant_list(args.variant_list);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ if let Some(v) = args.metrics {
+ req = req.metrics(json_to_doc(v));
+ }
+ if let Some(v) = args.experiment_group_id {
+ req = req.experiment_group_id(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn conclude_experiment_experiments_impl(&self, args: ConcludeExperimentExperimentsParams) -> Result {
+ let mut req = self.client.conclude_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.chosen_variant(args.chosen_variant);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn discard_experiment_experiments_impl(&self, args: DiscardExperimentExperimentsParams) -> Result {
+ let mut req = self.client.discard_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.change_reason(args.change_reason);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn ramp_experiment_experiments_impl(&self, args: RampExperimentExperimentsParams) -> Result {
+ let mut req = self.client.ramp_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.change_reason(args.change_reason);
+ req = req.traffic_percentage(args.traffic_percentage);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn pause_experiment_experiments_impl(&self, args: PauseExperimentExperimentsParams) -> Result {
+ let mut req = self.client.pause_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.change_reason(args.change_reason);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn resume_experiment_experiments_impl(&self, args: ResumeExperimentExperimentsParams) -> Result {
+ let mut req = self.client.resume_experiment()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ req = req.change_reason(args.change_reason);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn applicable_variants_experiments_impl(&self, args: ApplicableVariantsExperimentsParams) -> Result {
+ let mut req = self.client.applicable_variants()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.set_context(Some(json_to_doc_map(args.context).map_err(mcp_err)?));
+ req = req.identifier(args.identifier);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&experiments_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/function.rs b/crates/superposition_mcp/src/generated/function.rs
new file mode 100644
index 000000000..d73ef168f
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/function.rs
@@ -0,0 +1,175 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Retrieves detailed information about a specific function including its published and draft versions, code, and metadata.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetFunctionParams {
+ pub function_name: String,
+}
+
+/// Updates the draft version of an existing function with new code, runtime version, or description while preserving the published version.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateFunctionParams {
+ pub function_name: String,
+ pub description: Option,
+ pub change_reason: String,
+ pub function: Option,
+ pub runtime_version: Option,
+}
+
+/// Permanently removes a function from the workspace, deleting both draft and published versions along with all associated code. It fails if already in use
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct DeleteFunctionParams {
+ pub function_name: String,
+}
+
+/// Retrieves a paginated list of all functions in the workspace with their basic information and current status.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListFunctionParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+ pub function_type: Option>,
+}
+
+/// Creates a new custom function for value_validation, value_compute, context_validation or change_reason_validation with specified code, runtime version, and function type.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateFunctionParams {
+ pub function_name: String,
+ pub description: String,
+ pub change_reason: String,
+ pub function: String,
+ pub runtime_version: String,
+ pub function_type: String,
+}
+
+/// Executes a function in test mode with provided input parameters to validate its behavior before publishing or deployment.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct TestFunctionParams {
+ pub function_name: String,
+ pub stage: String,
+}
+
+/// Publishes the draft version of a function, making it the active version used for value_validation, value_compute, context_validation or change_reason_validation in the system.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct PublishFunctionParams {
+ pub function_name: String,
+ pub change_reason: String,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn get_function_impl(&self, args: GetFunctionParams) -> Result {
+ let mut req = self.client.get_function()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.function_name(args.function_name);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&function_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn update_function_impl(&self, args: UpdateFunctionParams) -> Result {
+ let mut req = self.client.update_function()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.function_name(args.function_name);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ if let Some(v) = args.function {
+ req = req.function(v);
+ }
+ if let Some(v) = args.runtime_version {
+ req = req.runtime_version(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&function_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn delete_function_impl(&self, args: DeleteFunctionParams) -> Result {
+ let mut req = self.client.delete_function()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.function_name(args.function_name);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = "Deleted successfully".to_string();
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_function_impl(&self, args: ListFunctionParams) -> Result {
+ let mut req = self.client.list_function()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ if let Some(v) = args.function_type {
+ for item in v {
+ req = req.function_type(item);
+ }
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| function_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn create_function_impl(&self, args: CreateFunctionParams) -> Result {
+ let mut req = self.client.create_function()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.function_name(args.function_name);
+ req = req.description(args.description);
+ req = req.change_reason(args.change_reason);
+ req = req.function(args.function);
+ req = req.runtime_version(args.runtime_version);
+ req = req.function_type(args.function_type);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&function_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn test_function_impl(&self, args: TestFunctionParams) -> Result {
+ let mut req = self.client.test()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.function_name(args.function_name);
+ req = req.stage(args.stage);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&function_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn publish_function_impl(&self, args: PublishFunctionParams) -> Result {
+ let mut req = self.client.publish()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.function_name(args.function_name);
+ req = req.change_reason(args.change_reason);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&function_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/master_key.rs b/crates/superposition_mcp/src/generated/master_key.rs
new file mode 100644
index 000000000..5696b55df
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/master_key.rs
@@ -0,0 +1,19 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+impl SuperpositionMcpServer {
+ pub async fn rotate_master_encryption_key_master_key_impl(&self) -> Result {
+ let mut req = self.client.rotate_master_encryption_key()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&master_key_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/mod.rs b/crates/superposition_mcp/src/generated/mod.rs
new file mode 100644
index 000000000..813eeb7b1
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/mod.rs
@@ -0,0 +1,42 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+
+pub mod tools {
+ mod default_config;
+ pub use default_config::*;
+ mod dimension;
+ pub use dimension::*;
+ mod context;
+ pub use context::*;
+ mod config;
+ pub use config::*;
+ mod config_version;
+ pub use config_version::*;
+ mod audit_log;
+ pub use audit_log::*;
+ mod function;
+ pub use function::*;
+ mod organisation;
+ pub use organisation::*;
+ mod experiments;
+ pub use experiments::*;
+ mod type_templates;
+ pub use type_templates::*;
+ mod workspace;
+ pub use workspace::*;
+ mod webhook;
+ pub use webhook::*;
+ mod experiment_group;
+ pub use experiment_group::*;
+ mod variable;
+ pub use variable::*;
+ mod secret;
+ pub use secret::*;
+ mod master_key;
+ pub use master_key::*;
+}
+
+#[macro_use]
+mod response_macros;
+
+mod server_tools;
+pub use server_tools::*;
diff --git a/crates/superposition_mcp/src/generated/organisation.rs b/crates/superposition_mcp/src/generated/organisation.rs
new file mode 100644
index 000000000..f8902f40c
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/organisation.rs
@@ -0,0 +1,131 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Creates a new organisation with specified name and administrator email. This is the top-level entity that contains workspaces and manages organizational-level settings.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateOrganisationParams {
+ pub country_code: Option,
+ pub contact_email: Option,
+ pub contact_phone: Option,
+ pub admin_email: String,
+ pub sector: Option,
+ pub name: String,
+}
+
+/// Retrieves detailed information about a specific organisation including its status, contact details, and administrative metadata.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetOrganisationParams {
+ pub id: String,
+}
+
+/// Updates an existing organisation's information including contact details, status, and administrative properties.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateOrganisationParams {
+ pub country_code: Option,
+ pub contact_email: Option,
+ pub contact_phone: Option,
+ pub admin_email: Option,
+ pub sector: Option,
+ pub id: String,
+ pub status: Option,
+}
+
+/// Retrieves a paginated list of all organisations with their basic information, creation details, and current status.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListOrganisationParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn create_organisation_impl(&self, args: CreateOrganisationParams) -> Result {
+ let mut req = self.client.create_organisation()
+ .org_id(&self.config.org_id);
+ req = req.admin_email(args.admin_email);
+ req = req.name(args.name);
+ if let Some(v) = args.country_code {
+ req = req.country_code(v);
+ }
+ if let Some(v) = args.contact_email {
+ req = req.contact_email(v);
+ }
+ if let Some(v) = args.contact_phone {
+ req = req.contact_phone(v);
+ }
+ if let Some(v) = args.sector {
+ req = req.sector(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&organisation_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn get_organisation_impl(&self, args: GetOrganisationParams) -> Result {
+ let mut req = self.client.get_organisation()
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&organisation_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn update_organisation_impl(&self, args: UpdateOrganisationParams) -> Result {
+ let mut req = self.client.update_organisation()
+ .org_id(&self.config.org_id);
+ req = req.id(args.id);
+ if let Some(v) = args.country_code {
+ req = req.country_code(v);
+ }
+ if let Some(v) = args.contact_email {
+ req = req.contact_email(v);
+ }
+ if let Some(v) = args.contact_phone {
+ req = req.contact_phone(v);
+ }
+ if let Some(v) = args.admin_email {
+ req = req.admin_email(v);
+ }
+ if let Some(v) = args.sector {
+ req = req.sector(v);
+ }
+ if let Some(v) = args.status {
+ req = req.status(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&organisation_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_organisation_impl(&self, args: ListOrganisationParams) -> Result {
+ let mut req = self.client.list_organisation()
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| organisation_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/response_macros.rs b/crates/superposition_mcp/src/generated/response_macros.rs
new file mode 100644
index 000000000..a6545cd8c
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/response_macros.rs
@@ -0,0 +1,315 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+
+macro_rules! default_config_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "key": $r.key,
+ "value": $crate::helpers::doc_to_json(&$r.value),
+ "schema": $crate::helpers::doc_map_to_json(&$r.schema),
+ "description": $r.description,
+ "change_reason": $r.change_reason,
+ "value_validation_function_name": $r.value_validation_function_name,
+ "value_compute_function_name": $r.value_compute_function_name,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "created_by": $r.created_by,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ "last_modified_by": $r.last_modified_by,
+ })
+ }}
+}
+
+pub(crate) use default_config_to_json;
+
+macro_rules! dimension_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "dimension": $r.dimension,
+ "position": $r.position,
+ "schema": $crate::helpers::doc_map_to_json(&$r.schema),
+ "value_validation_function_name": $r.value_validation_function_name,
+ "description": $r.description,
+ "change_reason": $r.change_reason,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ "last_modified_by": $r.last_modified_by,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "created_by": $r.created_by,
+ "dependency_graph": $r.dependency_graph,
+ "dimension_type": format!("{:?}", $r.dimension_type),
+ "value_compute_function_name": $r.value_compute_function_name,
+ "mandatory": $r.mandatory,
+ })
+ }}
+}
+
+pub(crate) use dimension_to_json;
+
+macro_rules! context_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "id": $r.id,
+ "value": $crate::helpers::doc_map_to_json(&$r.value),
+ "override": $crate::helpers::doc_map_to_json(&$r.r#override),
+ "override_id": $r.override_id,
+ "weight": $r.weight,
+ "description": $r.description,
+ "change_reason": $r.change_reason,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "created_by": $r.created_by,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ "last_modified_by": $r.last_modified_by,
+ })
+ }}
+}
+
+pub(crate) use context_to_json;
+
+macro_rules! config_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "contexts": $r.contexts,
+ "overrides": $r.overrides,
+ "default_configs": $crate::helpers::doc_map_to_json(&$r.default_configs),
+ "dimensions": $r.dimensions,
+ "version": $r.version,
+ "last_modified": $crate::helpers::format_datetime(&$r.last_modified),
+ "audit_id": $r.audit_id,
+ })
+ }}
+}
+
+pub(crate) use config_to_json;
+
+macro_rules! config_version_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "id": $r.id,
+ "config": $crate::helpers::doc_to_json(&$r.config),
+ "config_hash": $r.config_hash,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "description": $r.description,
+ "tags": $r.tags,
+ })
+ }}
+}
+
+pub(crate) use config_version_to_json;
+
+macro_rules! audit_log_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "total_pages": $r.total_pages,
+ "total_items": $r.total_items,
+ "data": $r.data,
+ })
+ }}
+}
+
+pub(crate) use audit_log_to_json;
+
+macro_rules! function_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "function_name": $r.function_name,
+ "published_code": $r.published_code,
+ "draft_code": $r.draft_code,
+ "published_runtime_version": format!("{:?}", $r.published_runtime_version),
+ "draft_runtime_version": format!("{:?}", $r.draft_runtime_version),
+ "published_at": $r.published_at.as_ref().map($crate::helpers::format_datetime),
+ "draft_edited_at": $crate::helpers::format_datetime(&$r.draft_edited_at),
+ "published_by": $r.published_by,
+ "draft_edited_by": $r.draft_edited_by,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ "last_modified_by": $r.last_modified_by,
+ "change_reason": $r.change_reason,
+ "description": $r.description,
+ "function_type": format!("{:?}", $r.function_type),
+ })
+ }}
+}
+
+pub(crate) use function_to_json;
+
+macro_rules! organisation_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "id": $r.id,
+ "name": $r.name,
+ "country_code": $r.country_code,
+ "contact_email": $r.contact_email,
+ "contact_phone": $r.contact_phone,
+ "created_by": $r.created_by,
+ "admin_email": $r.admin_email,
+ "status": format!("{:?}", $r.status),
+ "sector": $r.sector,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "updated_at": $crate::helpers::format_datetime(&$r.updated_at),
+ "updated_by": $r.updated_by,
+ })
+ }}
+}
+
+pub(crate) use organisation_to_json;
+
+macro_rules! experiments_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "id": $r.id,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "created_by": $r.created_by,
+ "last_modified": $crate::helpers::format_datetime(&$r.last_modified),
+ "name": $r.name,
+ "experiment_type": format!("{:?}", $r.experiment_type),
+ "override_keys": $r.override_keys,
+ "status": format!("{:?}", $r.status),
+ "traffic_percentage": $r.traffic_percentage,
+ "context": $crate::helpers::doc_map_to_json(&$r.context),
+ "variants": $r.variants,
+ "last_modified_by": $r.last_modified_by,
+ "chosen_variant": $r.chosen_variant,
+ "description": $r.description,
+ "change_reason": $r.change_reason,
+ "started_at": $r.started_at.as_ref().map($crate::helpers::format_datetime),
+ "started_by": $r.started_by,
+ "metrics_url": $r.metrics_url,
+ "metrics": $r.metrics.as_ref().map($crate::helpers::doc_to_json),
+ "experiment_group_id": $r.experiment_group_id,
+ })
+ }}
+}
+
+pub(crate) use experiments_to_json;
+
+macro_rules! type_templates_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "type_name": $r.type_name,
+ "type_schema": $crate::helpers::doc_map_to_json(&$r.type_schema),
+ "description": $r.description,
+ "change_reason": $r.change_reason,
+ "created_by": $r.created_by,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ "last_modified_by": $r.last_modified_by,
+ })
+ }}
+}
+
+pub(crate) use type_templates_to_json;
+
+macro_rules! workspace_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "workspace_name": $r.workspace_name,
+ "organisation_id": $r.organisation_id,
+ "organisation_name": $r.organisation_name,
+ "workspace_schema_name": $r.workspace_schema_name,
+ "workspace_status": format!("{:?}", $r.workspace_status),
+ "workspace_admin_email": $r.workspace_admin_email,
+ "config_version": $r.config_version,
+ "created_by": $r.created_by,
+ "last_modified_by": $r.last_modified_by,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "mandatory_dimensions": $r.mandatory_dimensions,
+ "metrics": $crate::helpers::doc_to_json(&$r.metrics),
+ "allow_experiment_self_approval": $r.allow_experiment_self_approval,
+ "auto_populate_control": $r.auto_populate_control,
+ "enable_context_validation": $r.enable_context_validation,
+ "enable_change_reason_validation": $r.enable_change_reason_validation,
+ })
+ }}
+}
+
+pub(crate) use workspace_to_json;
+
+macro_rules! webhook_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "name": $r.name,
+ "description": $r.description,
+ "enabled": $r.enabled,
+ "url": $r.url,
+ "method": format!("{:?}", $r.method),
+ "version": format!("{:?}", $r.version),
+ "custom_headers": $r.custom_headers.as_ref().map($crate::helpers::doc_map_to_json),
+ "events": $r.events,
+ "max_retries": $r.max_retries,
+ "last_triggered_at": $r.last_triggered_at.as_ref().map($crate::helpers::format_datetime),
+ "change_reason": $r.change_reason,
+ "created_by": $r.created_by,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "last_modified_by": $r.last_modified_by,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ })
+ }}
+}
+
+pub(crate) use webhook_to_json;
+
+macro_rules! experiment_group_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "id": $r.id,
+ "context_hash": $r.context_hash,
+ "name": $r.name,
+ "description": $r.description,
+ "change_reason": $r.change_reason,
+ "context": $crate::helpers::doc_map_to_json(&$r.context),
+ "traffic_percentage": $r.traffic_percentage,
+ "member_experiment_ids": $r.member_experiment_ids,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "created_by": $r.created_by,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ "last_modified_by": $r.last_modified_by,
+ "buckets": $r.buckets,
+ "group_type": format!("{:?}", $r.group_type),
+ })
+ }}
+}
+
+pub(crate) use experiment_group_to_json;
+
+macro_rules! variable_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "name": $r.name,
+ "value": $r.value,
+ "description": $r.description,
+ "change_reason": $r.change_reason,
+ "created_by": $r.created_by,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "last_modified_by": $r.last_modified_by,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ })
+ }}
+}
+
+pub(crate) use variable_to_json;
+
+macro_rules! secret_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "name": $r.name,
+ "description": $r.description,
+ "change_reason": $r.change_reason,
+ "created_by": $r.created_by,
+ "created_at": $crate::helpers::format_datetime(&$r.created_at),
+ "last_modified_by": $r.last_modified_by,
+ "last_modified_at": $crate::helpers::format_datetime(&$r.last_modified_at),
+ })
+ }}
+}
+
+pub(crate) use secret_to_json;
+
+macro_rules! master_key_to_json {
+ ($r:expr) => {{
+ serde_json::json!({
+ "workspaces_rotated": $r.workspaces_rotated,
+ "total_secrets_re_encrypted": $r.total_secrets_re_encrypted,
+ })
+ }}
+}
+
+pub(crate) use master_key_to_json;
+
diff --git a/crates/superposition_mcp/src/generated/secret.rs b/crates/superposition_mcp/src/generated/secret.rs
new file mode 100644
index 000000000..859899214
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/secret.rs
@@ -0,0 +1,158 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::model::*;
+use schemars::JsonSchema;
+use serde::Deserialize;
+
+use crate::SuperpositionMcpServer;
+use crate::helpers::*;
+
+/// Retrieves detailed information about a specific secret by its name. The value is masked for security.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct GetSecretParams {
+ pub name: String,
+}
+
+/// Updates an existing secret's value or description. The value is re-encrypted with the current workspace encryption key. Returns masked value.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct UpdateSecretParams {
+ pub name: String,
+ /// New plaintext value to encrypt and store. If provided, will be encrypted with current key.
+ pub value: Option,
+ pub description: Option,
+ pub change_reason: String,
+}
+
+/// Permanently deletes a secret from the workspace. The encrypted value is removed and cannot be recovered.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct DeleteSecretParams {
+ pub name: String,
+}
+
+/// Retrieves a paginated list of all secrets in the workspace with optional filtering and sorting. All secret values are masked.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct ListSecretParams {
+ /// Number of items to be returned in each page.
+ pub count: Option,
+ /// Page number to retrieve, starting from 1.
+ pub page: Option,
+ /// If true, returns all requested items, ignoring pagination parameters page and count.
+ pub all: Option,
+ /// Filter by secret name.
+ pub name: Option>,
+ /// Filter by the user who created the secret.
+ pub created_by: Option>,
+ /// Filter by the user who last modified the secret.
+ pub last_modified_by: Option>,
+ /// Field to sort the results by.
+ pub sort_on: Option,
+ /// Sort order (ascending or descending).
+ pub sort_by: Option,
+}
+
+/// Creates a new encrypted secret with the specified name and value. The secret is encrypted with the workspace's current encryption key. Secret values are never returned in responses for security.
+#[derive(Debug, Deserialize, JsonSchema)]
+pub struct CreateSecretParams {
+ pub name: String,
+ /// Plaintext value to be encrypted and stored.
+ pub value: String,
+ pub description: String,
+ pub change_reason: String,
+}
+
+impl SuperpositionMcpServer {
+ pub async fn get_secret_impl(&self, args: GetSecretParams) -> Result {
+ let mut req = self.client.get_secret()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.name(args.name);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&secret_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn update_secret_impl(&self, args: UpdateSecretParams) -> Result {
+ let mut req = self.client.update_secret()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.name(args.name);
+ req = req.change_reason(args.change_reason);
+ if let Some(v) = args.value {
+ req = req.value(v);
+ }
+ if let Some(v) = args.description {
+ req = req.description(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&secret_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn delete_secret_impl(&self, args: DeleteSecretParams) -> Result {
+ let mut req = self.client.delete_secret()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.name(args.name);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&secret_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn list_secret_impl(&self, args: ListSecretParams) -> Result {
+ let mut req = self.client.list_secrets()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ if let Some(v) = args.count {
+ req = req.count(v);
+ }
+ if let Some(v) = args.page {
+ req = req.page(v);
+ }
+ if let Some(v) = args.all {
+ req = req.all(v);
+ }
+ if let Some(v) = args.name {
+ for item in v {
+ req = req.name(item);
+ }
+ }
+ if let Some(v) = args.created_by {
+ for item in v {
+ req = req.created_by(item);
+ }
+ }
+ if let Some(v) = args.last_modified_by {
+ for item in v {
+ req = req.last_modified_by(item);
+ }
+ }
+ if let Some(v) = args.sort_on {
+ req = req.sort_on(v);
+ }
+ if let Some(v) = args.sort_by {
+ req = req.sort_by(v);
+ }
+ let resp = req.send().await.map_err(mcp_err)?;
+ let items: Vec = resp.data.iter().map(|r| secret_to_json!(r)).collect();
+ let result = serde_json::json!({
+ "total_pages": resp.total_pages,
+ "total_items": resp.total_items,
+ "data": items,
+ });
+ let json = serde_json::to_string_pretty(&result).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+ pub async fn create_secret_impl(&self, args: CreateSecretParams) -> Result {
+ let mut req = self.client.create_secret()
+ .workspace_id(&self.config.workspace_id)
+ .org_id(&self.config.org_id);
+ req = req.name(args.name);
+ req = req.value(args.value);
+ req = req.description(args.description);
+ req = req.change_reason(args.change_reason);
+ let resp = req.send().await.map_err(mcp_err)?;
+ let json = serde_json::to_string_pretty(&secret_to_json!(resp)).map_err(mcp_err)?;
+ Ok(CallToolResult::success(vec![Content::text(json)]))
+ }
+
+}
diff --git a/crates/superposition_mcp/src/generated/server_tools.rs b/crates/superposition_mcp/src/generated/server_tools.rs
new file mode 100644
index 000000000..c51c6e791
--- /dev/null
+++ b/crates/superposition_mcp/src/generated/server_tools.rs
@@ -0,0 +1,947 @@
+// AUTO-GENERATED by smithy mcp-codegen — DO NOT EDIT
+use rmcp::handler::server::wrapper::Parameters;
+use rmcp::model::*;
+use rmcp::{tool, tool_router};
+
+use crate::SuperpositionMcpServer;
+use crate::generated::tools::*;
+
+#[tool_router]
+impl SuperpositionMcpServer {
+ // ===== DefaultConfig =====
+ #[tool(
+ name = "default_config.get",
+ description = "Retrieves a specific default config entry by its key, including its value, schema, function mappings, and metadata."
+ )]
+ async fn default_config_get(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.get_default_config_impl(args).await
+ }
+
+ #[tool(
+ name = "default_config.update",
+ description = "Updates an existing default config entry. Allows modification of value, schema, function mappings, and description while preserving the key identifier."
+ )]
+ async fn default_config_update(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.update_default_config_impl(args).await
+ }
+
+ #[tool(
+ name = "default_config.delete",
+ description = "Permanently removes a default config entry from the workspace. This operation cannot be performed if it affects config resolution for contexts that rely on this fallback value."
+ )]
+ async fn default_config_delete(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.delete_default_config_impl(args).await
+ }
+
+ #[tool(
+ name = "default_config.list",
+ description = "Retrieves a paginated list of all default config entries in the workspace, including their values, schemas, and metadata."
+ )]
+ async fn default_config_list(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.list_default_config_impl(args).await
+ }
+
+ #[tool(
+ name = "default_config.create",
+ description = "Creates a new default config entry with specified key, value, schema, and metadata. Default configs serve as fallback values when no specific context matches."
+ )]
+ async fn default_config_create(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.create_default_config_impl(args).await
+ }
+
+ // ===== Dimension =====
+ #[tool(
+ name = "dimension.get",
+ description = "Retrieves detailed information about a specific dimension, including its schema, cohort dependency graph, and configuration metadata."
+ )]
+ async fn dimension_get(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.get_dimension_impl(args).await
+ }
+
+ #[tool(
+ name = "dimension.update",
+ description = "Updates an existing dimension's configuration. Allows modification of schema, position, function mappings, and other properties while maintaining dependency relationships."
+ )]
+ async fn dimension_update(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.update_dimension_impl(args).await
+ }
+
+ #[tool(
+ name = "dimension.delete",
+ description = "Permanently removes a dimension from the workspace. This operation will fail if the dimension has active dependencies or is referenced by existing configurations."
+ )]
+ async fn dimension_delete(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.delete_dimension_impl(args).await
+ }
+
+ #[tool(
+ name = "dimension.list",
+ description = "Retrieves a paginated list of all dimensions in the workspace. Dimensions are returned with their details and metadata."
+ )]
+ async fn dimension_list(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.list_dimension_impl(args).await
+ }
+
+ #[tool(
+ name = "dimension.create",
+ description = "Creates a new dimension with the specified json schema. Dimensions define categorical attributes used for context-based config management."
+ )]
+ async fn dimension_create(
+ &self,
+ Parameters(args): Parameters,
+ ) -> Result {
+ self.create_dimension_impl(args).await
+ }
+
+ // ===== Context =====
+ #[tool(
+ name = "context.create",
+ description = "Creates a new context with specified conditions and overrides. Contexts define conditional rules for config management."
+ )]
+ async fn context_create(
+ &self,
+ Parameters(args): Parameters