From bb88393f413ebe51ad892246f8234fe7e5230318 Mon Sep 17 00:00:00 2001 From: thedoublejay Date: Fri, 29 May 2026 14:37:55 +0700 Subject: [PATCH] feat: fix setup-mcp config targets, add codex client, prep 4.2.0 setup-mcp previously wrote the gather-step server block to `.claude/settings.json`, which Claude Code does not read for MCP server definitions, so the registered server never appeared in the client. - `--scope local` now writes the project-scoped `.mcp.json`; `--scope global` writes the user-scoped `~/.claude.json`. - New `--client codex` merges a `[mcp_servers.gather-step]` block into `~/.codex/config.toml` using toml_edit, preserving existing servers, other keys, and comments. Default client stays `claude`. - `status` MCP detection now reads `.mcp.json` / `~/.claude.json` to match the new write locations; integration and wizard tests updated accordingly. - Corrected the MCP clients guide (user scope is `~/.claude.json`, not `~/.claude/settings.json`; refreshed the Fast Path section). - Refreshed Cargo deps to latest SemVer-compatible versions (serde_json, tokio, rmcp, similar, quick_cache, memchr + transitive); exact pins left untouched. Bumped website astro 6.3.5 -> 6.4.2. - Bumped workspace, internal crate, and website versions and landing-page stamps to 4.2.0; marked 4.2.0 released in the changelog. Tests cover the JSON (.mcp.json / ~/.claude.json) and TOML (Codex) writers, status detection, and the init/wizard flow. --- Cargo.lock | 268 ++++++++++-------- Cargo.toml | 31 +- crates/gather-step-cli/Cargo.toml | 1 + crates/gather-step-cli/src/commands/init.rs | 16 +- .../gather-step-cli/src/commands/setup_mcp.rs | 94 +++++- crates/gather-step-cli/src/commands/status.rs | 8 +- crates/gather-step-cli/tests/cli_commands.rs | 6 +- crates/gather-step-cli/tests/cli_setup_mcp.rs | 87 +++++- .../gather-step-cli/tests/cli_wizard_full.rs | 4 +- crates/gather-step-mcp/Cargo.toml | 2 +- website/bun.lock | 12 +- website/package.json | 4 +- .../src/components/landing/Benchmark.astro | 4 +- website/src/components/landing/Hero.astro | 2 +- website/src/components/landing/Topology.astro | 2 +- website/src/content/docs/changelog.md | 27 ++ .../src/content/docs/guides/mcp-clients.md | 19 +- 17 files changed, 404 insertions(+), 183 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8753421..0f3c5c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "backtrace" @@ -299,9 +299,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytecheck" @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -879,9 +879,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "encode_unicode" @@ -1103,7 +1103,7 @@ dependencies = [ [[package]] name = "gather-step" -version = "4.1.1" +version = "4.2.0" dependencies = [ "anyhow", "blake3", @@ -1138,13 +1138,14 @@ dependencies = [ "tokio", "tokio-util", "toml", + "toml_edit", "tracing", "tracing-subscriber", ] [[package]] name = "gather-step-analysis" -version = "4.1.1" +version = "4.2.0" dependencies = [ "gather-step-core", "gather-step-parser", @@ -1159,7 +1160,7 @@ dependencies = [ [[package]] name = "gather-step-bench" -version = "4.1.1" +version = "4.2.0" dependencies = [ "anyhow", "chrono", @@ -1189,7 +1190,7 @@ dependencies = [ [[package]] name = "gather-step-core" -version = "4.1.1" +version = "4.2.0" dependencies = [ "bitcode", "blake3", @@ -1203,7 +1204,7 @@ dependencies = [ [[package]] name = "gather-step-deploy" -version = "4.1.1" +version = "4.2.0" dependencies = [ "gather-step-core", "pretty_assertions", @@ -1215,7 +1216,7 @@ dependencies = [ [[package]] name = "gather-step-git" -version = "4.1.1" +version = "4.2.0" dependencies = [ "blake3", "gather-step-core", @@ -1232,7 +1233,7 @@ dependencies = [ [[package]] name = "gather-step-mcp" -version = "4.1.1" +version = "4.2.0" dependencies = [ "blake3", "criterion", @@ -1263,7 +1264,7 @@ dependencies = [ [[package]] name = "gather-step-output" -version = "4.1.1" +version = "4.2.0" dependencies = [ "chrono", "gather-step-analysis", @@ -1275,7 +1276,7 @@ dependencies = [ [[package]] name = "gather-step-parser" -version = "4.1.1" +version = "4.2.0" dependencies = [ "aho-corasick", "blake3", @@ -1317,7 +1318,7 @@ dependencies = [ [[package]] name = "gather-step-storage" -version = "4.1.1" +version = "4.2.0" dependencies = [ "bitcode", "blake3", @@ -1466,9 +1467,9 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.41.0" +version = "0.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "272916673b83714734b15d4ef3c8b5f1ccddb15fea8ff548430b97c1ab7b7ed8" +checksum = "8bc998b8f746dda8565450d08a63b792ced9165d8c27a1ed3f02799ec6a7820f" dependencies = [ "bstr", "gix-date", @@ -1490,9 +1491,9 @@ dependencies = [ [[package]] name = "gix-attributes" -version = "0.33.0" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe17c5a1c0b6f2ef1476aa1d3222ea50cdff67608016613a58bfc3e078046000" +checksum = "8d43f12e246d3bf7ec624c8fc15ac4a4b62b7c4c6f586cb82be6c90bf84c9d02" dependencies = [ "bstr", "gix-glob", @@ -1507,9 +1508,9 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecbfc77ec6852294e341ecc305a490b59f2813e6ca42d79efda5099dcab1894" +checksum = "52ebef0c26ad305747649e727bbcd56a7b7910754eb7cea88f6dff6f93c51283" dependencies = [ "gix-error", ] @@ -1536,18 +1537,18 @@ dependencies = [ [[package]] name = "gix-chunk" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf288be9b60fe7231de03771faa292be1493d84786f68727e33ad1f91764320" +checksum = "9faee47943b638e58ddd5e275a4906ad3e4b6c8584f1d41bd18ab9032ec52afb" dependencies = [ "gix-error", ] [[package]] name = "gix-command" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86335306511abe43d75c866d4b1f3d90932fe202edcd43e1314036333e7384d8" +checksum = "00706d4fef135ef4b01680d5218c6ee40cda8baf697b864296cbc887d19118f6" dependencies = [ "bstr", "gix-path", @@ -1558,9 +1559,9 @@ dependencies = [ [[package]] name = "gix-commitgraph" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3b5aa0f24e19028c261d229aeeedafcaaa52ebd71021cc15184620fc9d32eb" +checksum = "7f675d0df484a7f6a47e64bd6f311af489d947c0323b0564f36d14f3d7762abb" dependencies = [ "bstr", "gix-chunk", @@ -1590,9 +1591,9 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b39ed39ee4c10a3b157f9fb94bac8098d9f8e56201f0cf7dee6c187416c4b2" +checksum = "ed42168329552f6c2e5df09665c104199d45d84bedb53683738a49b57fe1baab" dependencies = [ "bitflags", "bstr", @@ -1603,9 +1604,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.38.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ca11598b70811d7b16ff90945a6e57dfe521e85b744e51636965fe39cc8f60" +checksum = "f40cd22f0dd71988be12d6e78b1709de2370e1957c5f107ff31e56caeba3745d" dependencies = [ "bstr", "gix-command", @@ -1621,15 +1622,14 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94cdae4eb4b0f4136e3d9b3aa2d2cd03cfb5bb9b636b31263aea2df86d41543" +checksum = "a3ecab64a98bbac9f8e02990a9ea5e3c974a7d49b95f2bd70ad94ad22fa6b48c" dependencies = [ "bstr", "gix-error", "itoa", "jiff", - "smallvec", ] [[package]] @@ -1693,18 +1693,18 @@ dependencies = [ [[package]] name = "gix-error" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e207b971746ab724fccdfced2e4e19e854744611904a0195d3aa8fda8a110613" +checksum = "e57831e199be480af90dcd7e459abed8a174c09ec9a6e2cc8f7ca6c54598b06b" dependencies = [ "bstr", ] [[package]] name = "gix-features" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af375693ad5333d0a2c66b4c5b2cbe9ccc38e34f8e8bf24e4ae42c12307fdc4f" +checksum = "1849ae154d38bc403185be14fa871e38e3c93ee606875d94e207fdb9fba52dbc" dependencies = [ "bytes", "bytesize", @@ -1745,9 +1745,9 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1967daac9848757c47c2aef0c57bcadc1a897347f559778249bf286a536c86" +checksum = "6cdff46db8798e47e2f727d84b9379aac5add3dd3d9d0b07bb4d7d5d640771fe" dependencies = [ "bstr", "fastrand", @@ -1759,9 +1759,9 @@ dependencies = [ [[package]] name = "gix-glob" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bf29249a069bf2507f5964f80997f37b134d320ea348d66527726b9be2c38c" +checksum = "d1fcb8ef5b16bcf874abe9b68d8abb3c0493c876d367ab824151f30a0f3f3756" dependencies = [ "bitflags", "bstr", @@ -1771,9 +1771,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf70d1e252337eed16360f8b8ebb71865ece58eab7954b39ce38b420de703d2" +checksum = "cb0926d3819c837750b4e03c7754901e73f68b8c9b690753a6372a1bed4eedce" dependencies = [ "faster-hex", "gix-features", @@ -1783,20 +1783,20 @@ dependencies = [ [[package]] name = "gix-hashtable" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d33b455e07b3c16d3b2eeebc7b38d2dafcbf8a653de1138ef55d4c2a1fd0b08b" +checksum = "b0e30b93eea8718baf7d8153fcb938e2926175bbf18097c09f1c01b6f0be0563" dependencies = [ "gix-hash", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "parking_lot", ] [[package]] name = "gix-ignore" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb13fbbeeafee943e52b61fcc88dfddf6a452fcaf0c4d0cdc8f218fa25bbec5" +checksum = "d491bab9bf2c9f341dc754f425c31d5d3f63aca615312167b82e1deeaca97d8d" dependencies = [ "bstr", "gix-glob", @@ -1807,12 +1807,12 @@ dependencies = [ [[package]] name = "gix-imara-diff" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39eb0623e15e4cb83c02ce6a959e48fadd1ae3b715b36b5acc01816e01388c82" +checksum = "19753d40da53d0ec41604750eeb969097a90fb2d7f7992730d904541c04e2c19" dependencies = [ "bstr", - "hashbrown 0.16.1", + "hashbrown 0.17.0", ] [[package]] @@ -1845,9 +1845,9 @@ dependencies = [ [[package]] name = "gix-lock" -version = "23.0.0" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3bc074e5723027b482dcd9ab99d95804a53742f6de812d0172fbba4a186c1" +checksum = "65c9dedd9e90b0d47624d2ed241d394e09294118364e87b9b7e5f1fe755f3c2c" dependencies = [ "gix-tempfile", "gix-utils", @@ -1856,9 +1856,9 @@ dependencies = [ [[package]] name = "gix-mailmap" -version = "0.33.0" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023d3a6561cbebe45b89e0764d48928ad970667076f16fa5889e6f86d8432086" +checksum = "195fd20808055824531be2fd0d34136d900e5fbca3ffb0a3c07e8beeefb9c828" dependencies = [ "bstr", "gix-actor", @@ -1968,9 +1968,9 @@ dependencies = [ [[package]] name = "gix-packetline" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "362246df440ee691699f0664cbf7006a6ece477db6734222be95e4198e5656e6" +checksum = "bb18337ba2830bb43367d1af43819c8c78f31337f079fc76d0f1f1750a173126" dependencies = [ "bstr", "faster-hex", @@ -1980,9 +1980,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a6059e8a4c1b7f406e24716499cefa3926e060876fb1959ef225efeee346e" +checksum = "afa6ac14cd14939ea94a496ce7460daa6511c09f5b84757e9cfc6f9c8d0f93a6" dependencies = [ "bstr", "gix-trace", @@ -1992,9 +1992,9 @@ dependencies = [ [[package]] name = "gix-pathspec" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a84a4f083dd70fb49f4377e13afa6d90df2daaa1c705c49d6ff1331fc7e8855" +checksum = "3050783b41ee11511e1e8fb35623df81806194f4030395f14f48ea37c2798c9f" dependencies = [ "bitflags", "bstr", @@ -2007,9 +2007,9 @@ dependencies = [ [[package]] name = "gix-prompt" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e041a626c64cb69e4117fcdf80da8d0e454fba3b1f420412792d191f52251aee" +checksum = "3ee604d7746080ae7e1023bf47204bcc2c5f307bfbe2306a3c90b1bfd1a2c6d8" dependencies = [ "gix-command", "gix-config-value", @@ -2039,9 +2039,9 @@ dependencies = [ [[package]] name = "gix-quote" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e97b73791a64bc0fa7dd2c5b3e551136115f97750b876ed1c952c7a7dbaf8be" +checksum = "a6e541fc33cc2b783b7979040d445a0c86a2eca747c8faea4ca84230d06ae6ef" dependencies = [ "bstr", "gix-error", @@ -2121,9 +2121,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a3a2d3e504a238136751e646a6c028252286a0ea64ea9974bf0498633407c6" +checksum = "ab8519976e4c7e486270740a5400369f37940779b80bd1377d94cfa1125d01b3" dependencies = [ "bitflags", "gix-path", @@ -2133,9 +2133,9 @@ dependencies = [ [[package]] name = "gix-shallow" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29187305521bfacf4aefd284ab28dbfa9fb74abd39a5e63dd313b1baa5808c27" +checksum = "a292fc2fe548c5dfa575479d16b445b0ddf1dd2f56f1fec6aed386f82553cd97" dependencies = [ "bstr", "gix-hash", @@ -2184,9 +2184,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "23.0.0" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "691ea1e31435c7e7d4d04705ec9d1c0d9482c46b2acf512bc723939d8f0af7fb" +checksum = "27850097e1ff9515f46a0dad0f5f9c9d020e972727772dabab9450690c4adb22" dependencies = [ "dashmap", "gix-fs", @@ -2199,15 +2199,15 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f23569e55f2ffaf958617353b9734a7d52a7c19c439eeaa5e3efc217fd2270e" +checksum = "44dc45eae785c0eb14173e0f152e6e224dcf4d45b6a6999a3aed22af541ad678" [[package]] name = "gix-transport" -version = "0.57.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd6a5c676b92d4ead5f5a2b2935024415dec69edc997b6090ca9cac010a3018" +checksum = "7cd0e34995b1aab0fa8dff2af8db726a0bfad3e119c89302604463264046e7ff" dependencies = [ "bstr", "gix-command", @@ -2238,9 +2238,9 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.36.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35842d099e813f6f6bba529e88d4670572149c3df79b7a412952259887721ece" +checksum = "65bb01ec69d55e82ccb7a19e264501ead4e6aac38463a8cebfdd81e22bb67ab2" dependencies = [ "bstr", "gix-path", @@ -2250,9 +2250,9 @@ dependencies = [ [[package]] name = "gix-utils" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e477b4f07a6e8da4ba791c53c858102959703c60d70f199932010d5b94adb2c" +checksum = "66c50966184123caf580ffa64e28031a878597f1c7fceb8fe19566c38eb1b771" dependencies = [ "bstr", "fastrand", @@ -2261,9 +2261,9 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e26ac2602b43eadfdca0560b81d3341944162a3c9f64ccdeef8fc501ad80dad5" +checksum = "7bc6fc771c4063ba7cd2f47b91fb6076251c6a823b64b7fe7b8874b0fe4afae3" dependencies = [ "bstr", ] @@ -2324,9 +2324,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0" +checksum = "8fb167719045debebe9f532320accc7b5c993c5a3b813f5696a11d5ca7bdc57b" [[package]] name = "globset" @@ -2624,9 +2624,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -2634,14 +2634,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link", ] [[package]] name = "jiff-static" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -2675,9 +2675,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -2761,18 +2761,18 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libmimalloc-sys" -version = "0.1.47" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" dependencies = [ "cc", ] [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -2820,9 +2820,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru" @@ -2879,9 +2879,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memmap2" @@ -2925,9 +2925,9 @@ checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -3385,9 +3385,9 @@ dependencies = [ [[package]] name = "pastey" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" [[package]] name = "percent-encoding" @@ -4145,9 +4145,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "indexmap", "itoa", @@ -4318,9 +4318,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4328,9 +4328,9 @@ dependencies = [ [[package]] name = "sqlite-wasm-rs" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd578e94101503d97e2b286bbf8db2135035ca24b2ce4cbf3f9e2fb2bbf1eee" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" dependencies = [ "cc", "js-sys", @@ -4740,6 +4740,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" @@ -5122,9 +5135,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -5135,9 +5148,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5145,9 +5158,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -5158,9 +5171,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -5201,9 +5214,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -5396,6 +5409,9 @@ name = "winnow" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] [[package]] name = "wit-bindgen" @@ -5505,18 +5521,18 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e3d0244..735b8fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ ] [workspace.package] -version = "4.1.1" +version = "4.2.0" authors = ["JJ Adonis"] edition = "2024" rust-version = "1.94.1" @@ -24,15 +24,15 @@ homepage = "https://github.com/thedoublejay/gather-step" description = "High-performance multi-repo codebase intelligence engine" [workspace.dependencies] -gather-step = { path = "crates/gather-step-cli", version = "4.1.1" } -gather-step-analysis = { path = "crates/gather-step-analysis", version = "4.1.1" } -gather-step-core = { path = "crates/gather-step-core", version = "4.1.1" } -gather-step-deploy = { path = "crates/gather-step-deploy", version = "4.1.1" } -gather-step-git = { path = "crates/gather-step-git", version = "4.1.1" } -gather-step-mcp = { path = "crates/gather-step-mcp", version = "4.1.1" } -gather-step-output = { path = "crates/gather-step-output", version = "4.1.1" } -gather-step-parser = { path = "crates/gather-step-parser", version = "4.1.1" } -gather-step-storage = { path = "crates/gather-step-storage", version = "4.1.1" } +gather-step = { path = "crates/gather-step-cli", version = "4.2.0" } +gather-step-analysis = { path = "crates/gather-step-analysis", version = "4.2.0" } +gather-step-core = { path = "crates/gather-step-core", version = "4.2.0" } +gather-step-deploy = { path = "crates/gather-step-deploy", version = "4.2.0" } +gather-step-git = { path = "crates/gather-step-git", version = "4.2.0" } +gather-step-mcp = { path = "crates/gather-step-mcp", version = "4.2.0" } +gather-step-output = { path = "crates/gather-step-output", version = "4.2.0" } +gather-step-parser = { path = "crates/gather-step-parser", version = "4.2.0" } +gather-step-storage = { path = "crates/gather-step-storage", version = "4.2.0" } tree-sitter = "=0.26.8" tree-sitter-typescript = "0.23.2" @@ -56,9 +56,10 @@ console = "0.16.3" comfy-table = "7.2.2" chrono = { version = "0.4.44", features = ["serde"] } serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.149" +serde_json = "1.0.150" serde_norway = "0.9.42" toml = "1.1.2" +toml_edit = "0.25.12" bitcode = "0.6.9" blake3 = "1.8.5" dirs = "6" @@ -69,19 +70,19 @@ globset = "0.4.18" rustc-hash = "2.1.2" hashbrown = "=0.17.0" parking_lot = "=0.12.5" -tokio = { version = "1.52.2", features = ["macros", "rt-multi-thread", "sync", "time", "signal", "net", "io-util"] } +tokio = { version = "1.52.3", features = ["macros", "rt-multi-thread", "sync", "time", "signal", "net", "io-util"] } tokio-util = "0.7.18" notify = "=9.0.0-rc.4" gix = { version = "0.83", default-features = true } regex = "1.12.3" simdutf8 = "=0.1.5" -similar = "3.1.0" +similar = "3.1.1" lru = "=0.18.0" -quick_cache = "0.6.21" +quick_cache = "0.6.22" smallvec = { version = "=1.15.1", features = ["serde"] } camino = "1.2.2" aho-corasick = "1.1.4" -memchr = "2.8.0" +memchr = "2.8.1" libc = "0.2" dhat = "=0.3.3" regex-automata = { version = "0.4.14", default-features = false, features = ["std", "syntax", "meta", "nfa", "dfa", "hybrid", "perf", "unicode"] } diff --git a/crates/gather-step-cli/Cargo.toml b/crates/gather-step-cli/Cargo.toml index e86c8f6..a641bf5 100644 --- a/crates/gather-step-cli/Cargo.toml +++ b/crates/gather-step-cli/Cargo.toml @@ -46,6 +46,7 @@ serde.workspace = true thiserror.workspace = true serde_json.workspace = true serde_norway.workspace = true +toml_edit.workspace = true tokio.workspace = true tokio-util.workspace = true tracing.workspace = true diff --git a/crates/gather-step-cli/src/commands/init.rs b/crates/gather-step-cli/src/commands/init.rs index a5fdcac..00033e8 100644 --- a/crates/gather-step-cli/src/commands/init.rs +++ b/crates/gather-step-cli/src/commands/init.rs @@ -107,7 +107,13 @@ async fn run_non_interactive(app: &AppContext, args: InitArgs) -> Result<()> { generate::run_summary_pair(app)?; } if let Some(scope) = args.setup_mcp { - setup_mcp::run(app, setup_mcp::SetupMcpArgs { scope })?; + setup_mcp::run( + app, + setup_mcp::SetupMcpArgs { + client: setup_mcp::McpClient::Claude, + scope, + }, + )?; } if args.watch && !args.no_watch { emit_setup_complete(&output); @@ -193,7 +199,13 @@ async fn run_wizard(app: &AppContext, args: InitArgs) -> Result<()> { generate::run_summary_pair(app)?; } if let Some(scope) = scope { - setup_mcp::run(app, setup_mcp::SetupMcpArgs { scope })?; + setup_mcp::run( + app, + setup_mcp::SetupMcpArgs { + client: setup_mcp::McpClient::Claude, + scope, + }, + )?; } emit_setup_complete(&output); if do_watch { diff --git a/crates/gather-step-cli/src/commands/setup_mcp.rs b/crates/gather-step-cli/src/commands/setup_mcp.rs index 72b459d..be753d6 100644 --- a/crates/gather-step-cli/src/commands/setup_mcp.rs +++ b/crates/gather-step-cli/src/commands/setup_mcp.rs @@ -7,6 +7,7 @@ use anyhow::{Context, Result}; use clap::{Args, ValueEnum}; use serde::Serialize; use serde_json::{Map, Value, json}; +use toml_edit::{Array, DocumentMut, Item, Table, value}; use crate::app::AppContext; @@ -17,8 +18,19 @@ pub enum McpScope { Local, } +#[derive(Debug, Clone, Copy, ValueEnum, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum McpClient { + Claude, + Codex, +} + #[derive(Debug, Clone, Copy, Args)] pub struct SetupMcpArgs { + /// MCP client to configure. + #[arg(long, value_enum, default_value = "claude")] + pub client: McpClient, + /// Configuration scope. Ignored for Codex, whose config is always global. #[arg(long, value_enum, default_value = "local")] pub scope: McpScope, } @@ -26,6 +38,7 @@ pub struct SetupMcpArgs { #[derive(Debug, Serialize)] struct SetupMcpOutput { event: &'static str, + client: McpClient, scope: McpScope, settings_path: String, path_resolution: PathResolution, @@ -41,22 +54,22 @@ enum PathResolution { } pub fn run(app: &AppContext, args: SetupMcpArgs) -> Result<()> { - let settings_path = match args.scope { - McpScope::Local => app.workspace_path.join(".claude/settings.json"), - McpScope::Global => home_dir() - .context("cannot resolve HOME")? - .join(".claude/settings.json"), - }; + let settings_path = resolve_settings_path(args.client, args.scope, &app.workspace_path)?; let command_path = find_command_on_path("gather-step"); let path_resolution = if command_path.is_some() { PathResolution::Ok } else { PathResolution::NotFound }; - write_settings(&settings_path, &app.workspace_path)?; + + match args.client { + McpClient::Claude => write_settings(&settings_path, &app.workspace_path)?, + McpClient::Codex => write_codex_config(&settings_path, &app.workspace_path)?, + } let payload = SetupMcpOutput { event: "setup_mcp_completed", + client: args.client, scope: args.scope, settings_path: settings_path.display().to_string(), path_resolution, @@ -73,6 +86,28 @@ pub fn run(app: &AppContext, args: SetupMcpArgs) -> Result<()> { Ok(()) } +/// Resolve the config file the chosen client actually reads MCP server +/// definitions from. +/// +/// Claude Code does not read `mcpServers` out of `settings.json`: project scope +/// lives in `.mcp.json` at the workspace root and user scope in `~/.claude.json`. +/// Codex reads a single global `~/.codex/config.toml`, so scope does not apply. +fn resolve_settings_path(client: McpClient, scope: McpScope, workspace: &Path) -> Result { + match client { + McpClient::Claude => match scope { + McpScope::Local => Ok(workspace.join(".mcp.json")), + McpScope::Global => Ok(home_dir() + .context("cannot resolve HOME")? + .join(".claude.json")), + }, + McpClient::Codex => Ok(home_dir() + .context("cannot resolve HOME")? + .join(".codex/config.toml")), + } +} + +/// Merge a workspace-pinned `gather-step` entry into a JSON `mcpServers` map, +/// preserving every other key. Used for Claude's `.mcp.json` and `~/.claude.json`. pub fn write_settings(path: &Path, workspace: &Path) -> Result<()> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) @@ -112,6 +147,51 @@ pub fn write_settings(path: &Path, workspace: &Path) -> Result<()> { Ok(()) } +/// Merge a workspace-pinned `gather-step` entry into a Codex `config.toml`, +/// preserving existing servers, other tables, comments, and formatting. +pub fn write_codex_config(path: &Path, workspace: &Path) -> Result<()> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .with_context(|| format!("creating {}", parent.display()))?; + } + + let mut doc = if path.exists() { + let body = + std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?; + body.parse::() + .with_context(|| format!("parsing {}", path.display()))? + } else { + DocumentMut::new() + }; + + let workspace_str = workspace + .to_str() + .context("workspace path is not valid UTF-8")?; + let mut args = Array::new(); + args.push("--workspace"); + args.push(workspace_str); + args.push("serve"); + + let mut server = Table::new(); + server.insert("command", value("gather-step")); + server.insert("args", value(args)); + + // Keep `mcp_servers` an implicit table so the entry renders as the + // idiomatic `[mcp_servers.gather-step]` section rather than an inline table. + if doc.get("mcp_servers").is_none() { + let mut servers = Table::new(); + servers.set_implicit(true); + doc.insert("mcp_servers", Item::Table(servers)); + } + let servers = doc["mcp_servers"] + .as_table_mut() + .context("mcp_servers is not a table")?; + servers.insert("gather-step", Item::Table(server)); + + std::fs::write(path, doc.to_string()).with_context(|| format!("writing {}", path.display()))?; + Ok(()) +} + fn home_dir() -> Option { env::var_os("HOME").map(PathBuf::from) } diff --git a/crates/gather-step-cli/src/commands/status.rs b/crates/gather-step-cli/src/commands/status.rs index 92e4727..38d9ffe 100644 --- a/crates/gather-step-cli/src/commands/status.rs +++ b/crates/gather-step-cli/src/commands/status.rs @@ -110,13 +110,13 @@ fn mcp_state(app: &AppContext) -> &'static str { } fn mcp_state_with_home(app: &AppContext, home: Option<&std::ffi::OsStr>) -> &'static str { - let local = app.workspace_path.join(".claude/settings.json"); + let local = app.workspace_path.join(".mcp.json"); if json_has_gather_step(&local) { return "configured: local"; } if let Some(home) = home { - let global = std::path::PathBuf::from(home).join(".claude/settings.json"); + let global = std::path::PathBuf::from(home).join(".claude.json"); if json_has_gather_step(&global) { return "configured: global"; } @@ -418,7 +418,7 @@ mod tests { #[test] fn mcp_state_reports_local_configuration_first() { let temp = tempfile::tempdir().expect("temp dir"); - write_settings(&temp.path().join(".claude/settings.json")); + write_settings(&temp.path().join(".mcp.json")); assert_eq!( mcp_state_with_home(&app_for(temp.path()), None), @@ -430,7 +430,7 @@ mod tests { fn mcp_state_reports_global_configuration() { let workspace = tempfile::tempdir().expect("workspace"); let home = tempfile::tempdir().expect("home"); - write_settings(&home.path().join(".claude/settings.json")); + write_settings(&home.path().join(".claude.json")); assert_eq!( mcp_state_with_home(&app_for(workspace.path()), Some(home.path().as_os_str())), diff --git a/crates/gather-step-cli/tests/cli_commands.rs b/crates/gather-step-cli/tests/cli_commands.rs index 2e4bac1..f38a90a 100644 --- a/crates/gather-step-cli/tests/cli_commands.rs +++ b/crates/gather-step-cli/tests/cli_commands.rs @@ -119,7 +119,7 @@ fn setup_mcp_local_writes_workspace_settings() { let output = run_ok(temp.path(), &["setup-mcp", "--scope", "local"]); let stdout = String::from_utf8_lossy(&output.stdout); - let settings_path = temp.path().join(".claude/settings.json"); + let settings_path = temp.path().join(".mcp.json"); let settings = fs::read_to_string(&settings_path).expect("settings file should be written"); let value: Value = serde_json::from_str(&settings).expect("settings json"); @@ -174,7 +174,7 @@ fn setup_mcp_json_reports_missing_path_resolution() { } #[test] -fn setup_mcp_global_writes_home_claude_settings() { +fn setup_mcp_global_writes_home_claude_json() { let workspace = TempDir::new("setup-mcp-global-workspace"); let home = TempDir::new("setup-mcp-global-home"); @@ -194,7 +194,7 @@ fn setup_mcp_global_writes_home_claude_settings() { String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); - let settings_path = home.path().join(".claude/settings.json"); + let settings_path = home.path().join(".claude.json"); let settings = fs::read_to_string(&settings_path).expect("settings file should be written"); let value: Value = serde_json::from_str(&settings).expect("settings json"); assert_eq!(value["mcpServers"]["gather-step"]["command"], "gather-step"); diff --git a/crates/gather-step-cli/tests/cli_setup_mcp.rs b/crates/gather-step-cli/tests/cli_setup_mcp.rs index fc7cb4b..7600b42 100644 --- a/crates/gather-step-cli/tests/cli_setup_mcp.rs +++ b/crates/gather-step-cli/tests/cli_setup_mcp.rs @@ -1,10 +1,10 @@ -use gather_step::commands::setup_mcp::write_settings; +use gather_step::commands::setup_mcp::{write_codex_config, write_settings}; #[test] -fn writes_workspace_pinned_block_to_local_settings() { +fn writes_workspace_pinned_block_to_mcp_json() { let temp = tempfile::tempdir().expect("temp dir"); let workspace = temp.path().to_path_buf(); - let settings_path = workspace.join(".claude/settings.json"); + let settings_path = workspace.join(".mcp.json"); write_settings(&settings_path, &workspace).expect("settings should write"); @@ -21,7 +21,7 @@ fn writes_workspace_pinned_block_to_local_settings() { fn idempotent_merge_preserves_other_keys() { let temp = tempfile::tempdir().expect("temp dir"); let workspace = temp.path().to_path_buf(); - let settings_path = workspace.join(".claude/settings.json"); + let settings_path = workspace.join(".mcp.json"); std::fs::create_dir_all(settings_path.parent().expect("settings parent")) .expect("settings parent"); std::fs::write( @@ -44,7 +44,7 @@ fn idempotent_merge_preserves_other_keys() { fn malformed_existing_settings_json_returns_error_and_preserves_file() { let temp = tempfile::tempdir().expect("temp dir"); let workspace = temp.path().to_path_buf(); - let settings_path = workspace.join(".claude/settings.json"); + let settings_path = workspace.join(".mcp.json"); std::fs::create_dir_all(settings_path.parent().expect("settings parent")) .expect("settings parent"); let original = "{not valid json"; @@ -63,7 +63,7 @@ fn malformed_existing_settings_json_returns_error_and_preserves_file() { fn existing_gather_step_entry_is_replaced_not_merged() { let temp = tempfile::tempdir().expect("temp dir"); let workspace = temp.path().to_path_buf(); - let settings_path = workspace.join(".claude/settings.json"); + let settings_path = workspace.join(".mcp.json"); std::fs::create_dir_all(settings_path.parent().expect("settings parent")) .expect("settings parent"); std::fs::write( @@ -89,7 +89,7 @@ fn existing_gather_step_entry_is_replaced_not_merged() { fn non_object_mcp_servers_returns_error_and_preserves_file() { let temp = tempfile::tempdir().expect("temp dir"); let workspace = temp.path().to_path_buf(); - let settings_path = workspace.join(".claude/settings.json"); + let settings_path = workspace.join(".mcp.json"); std::fs::create_dir_all(settings_path.parent().expect("settings parent")) .expect("settings parent"); let original = r#"{"mcpServers":[]}"#; @@ -103,3 +103,76 @@ fn non_object_mcp_servers_returns_error_and_preserves_file() { original ); } + +#[test] +fn writes_codex_server_block_to_config_toml() { + let temp = tempfile::tempdir().expect("temp dir"); + let workspace = temp.path().to_path_buf(); + let config_path = workspace.join(".codex/config.toml"); + + write_codex_config(&config_path, &workspace).expect("codex config should write"); + + let body = std::fs::read_to_string(&config_path).expect("config body"); + let value: toml::Value = toml::from_str(&body).expect("config toml"); + assert_eq!( + value["mcp_servers"]["gather-step"]["command"] + .as_str() + .unwrap(), + "gather-step" + ); + let args = value["mcp_servers"]["gather-step"]["args"] + .as_array() + .expect("args array"); + let args: Vec<&str> = args.iter().map(|v| v.as_str().unwrap()).collect(); + assert_eq!(args, ["--workspace", workspace.to_str().unwrap(), "serve"]); +} + +#[test] +fn codex_merge_preserves_other_servers_and_comments() { + let temp = tempfile::tempdir().expect("temp dir"); + let workspace = temp.path().to_path_buf(); + let config_path = workspace.join(".codex/config.toml"); + std::fs::create_dir_all(config_path.parent().expect("config parent")).expect("config parent"); + std::fs::write( + &config_path, + "# top comment\nmodel = \"o3\"\n\n[mcp_servers.other]\ncommand = \"x\"\n", + ) + .expect("seed config"); + + write_codex_config(&config_path, &workspace).expect("first write"); + write_codex_config(&config_path, &workspace).expect("second write"); + + let body = std::fs::read_to_string(&config_path).expect("config body"); + assert!(body.contains("# top comment")); + let value: toml::Value = toml::from_str(&body).expect("config toml"); + assert_eq!(value["model"].as_str().unwrap(), "o3"); + assert_eq!( + value["mcp_servers"]["other"]["command"].as_str().unwrap(), + "x" + ); + assert_eq!( + value["mcp_servers"]["gather-step"]["command"] + .as_str() + .unwrap(), + "gather-step" + ); +} + +#[test] +fn codex_non_table_mcp_servers_returns_error_and_preserves_file() { + let temp = tempfile::tempdir().expect("temp dir"); + let workspace = temp.path().to_path_buf(); + let config_path = workspace.join(".codex/config.toml"); + std::fs::create_dir_all(config_path.parent().expect("config parent")).expect("config parent"); + let original = "mcp_servers = 1\n"; + std::fs::write(&config_path, original).expect("seed config"); + + let error = + write_codex_config(&config_path, &workspace).expect_err("non-table mcp_servers errors"); + + assert!(error.to_string().contains("mcp_servers is not a table")); + assert_eq!( + std::fs::read_to_string(&config_path).expect("config body"), + original + ); +} diff --git a/crates/gather-step-cli/tests/cli_wizard_full.rs b/crates/gather-step-cli/tests/cli_wizard_full.rs index 715654a..e20e877 100644 --- a/crates/gather-step-cli/tests/cli_wizard_full.rs +++ b/crates/gather-step-cli/tests/cli_wizard_full.rs @@ -72,7 +72,7 @@ fn init_flag_overrides_run_full_setup_without_prompting() { .join(".agents/skills/gather-step-context/SKILL.md") .exists() ); - assert!(tmp.path().join(".claude/settings.json").exists()); + assert!(tmp.path().join(".mcp.json").exists()); } #[test] @@ -140,7 +140,7 @@ fn init_flag_overrides_keep_setup_mcp_idempotent() { ); } - let settings = fs::read_to_string(tmp.path().join(".claude/settings.json")).expect("settings"); + let settings = fs::read_to_string(tmp.path().join(".mcp.json")).expect("settings"); let value: serde_json::Value = serde_json::from_str(&settings).expect("settings json"); assert_eq!( value["mcpServers"] diff --git a/crates/gather-step-mcp/Cargo.toml b/crates/gather-step-mcp/Cargo.toml index d2206ee..74b5948 100644 --- a/crates/gather-step-mcp/Cargo.toml +++ b/crates/gather-step-mcp/Cargo.toml @@ -22,7 +22,7 @@ gather-step-git.workspace = true gather-step-output.workspace = true gather-step-parser.workspace = true gather-step-storage.workspace = true -rmcp = { version = "1.5.0", features = ["client", "server", "macros", "transport-io"] } +rmcp = { version = "1.7.0", features = ["client", "server", "macros", "transport-io"] } serde.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/website/bun.lock b/website/bun.lock index 4f8073a..a20c36a 100644 --- a/website/bun.lock +++ b/website/bun.lock @@ -5,15 +5,15 @@ "": { "name": "gather-step-website", "dependencies": { - "@astrojs/starlight": "^0.39.1", - "astro": "^6.3.1", + "@astrojs/starlight": "^0.39.2", + "astro": "^6.3.5", }, }, }, "packages": { "@astrojs/compiler": ["@astrojs/compiler@4.0.0", "", {}, "sha512-eouss7G8ygdZqHuke033VMcVw5HTZUu+PXd/h06DGDUg/jt5btPYPqh66ENWw/mU78rBrf/oeC4oqoBwMtDMNA=="], - "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.9.1", "", { "dependencies": { "picomatch": "^4.0.4" } }, "sha512-1pWuARqYom/TzuU3+0ZugsTrKlUydWKuULmDqSMTuonY+9IRDUEGKX/8PXQ1nBxRq3w85uGtd9q9SXfqEldMIQ=="], + "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.0", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.1.1", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-Ry2R3VPeIN4uPCSA4xQc+e+vsJXkalKpEbDc07hV+a/o5Bs2N/s/uDcPJH/05L19DKh9tAy7e6JM3YZ6Cxfezw=="], "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.1.2", "", { "dependencies": { "@astrojs/internal-helpers": "0.9.1", "@astrojs/prism": "4.0.2", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "retext-smartypants": "^6.2.0", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-caXZ4Dc2St2dW8luEg22GlP0gupLdztCTQE4EzZOxW1pqWXz9mbeJEuHUkgDYcKWW8tjIHkydYDhWLVoxJ327Q=="], @@ -287,7 +287,7 @@ "astring": ["astring@1.9.0", "", { "bin": "bin/astring" }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], - "astro": ["astro@6.3.5", "", { "dependencies": { "@astrojs/compiler": "^4.0.0", "@astrojs/internal-helpers": "0.9.1", "@astrojs/markdown-remark": "7.1.2", "@astrojs/telemetry": "3.3.2", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "get-tsconfig": "5.0.0-beta.4", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.4", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.5", "vfile": "^6.0.3", "vite": "^7.3.2", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "./bin/astro.mjs" } }, "sha512-gU+4KedkbTuVgz7YoVAN+9Ftnq0GaYwejxK2NbqDzB0M9dWd0f3kXZBuaM9hzbchRFoRAJfJjFtdX9LK6Ir7ZA=="], + "astro": ["astro@6.4.2", "", { "dependencies": { "@astrojs/compiler": "^4.0.0", "@astrojs/internal-helpers": "0.10.0", "@astrojs/markdown-remark": "7.2.0", "@astrojs/telemetry": "3.3.2", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "get-tsconfig": "5.0.0-beta.4", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.4", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.5", "vfile": "^6.0.3", "vite": "^7.3.2", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "./bin/astro.mjs" } }, "sha512-8H89CH2dKL5SCU99OCqdU9BGjmPkSJqaPurywj5XMo7eMFGUFD3vsNhdEKnEh4mK4LgGje3/QDTTSIIGst0G0Q=="], "astro-expressive-code": ["astro-expressive-code@0.42.0", "", { "dependencies": { "rehype-expressive-code": "^0.42.0" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" } }, "sha512-aiTePi2Cn0mJPYWZSzP1GcxCinX9mNtJyCCshVVPSg1yRwM7ADvFJOx0FnS440M9t65hp8JH//dc2qr22Bm4ag=="], @@ -847,12 +847,16 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.9.1", "", { "dependencies": { "picomatch": "^4.0.4" } }, "sha512-1pWuARqYom/TzuU3+0ZugsTrKlUydWKuULmDqSMTuonY+9IRDUEGKX/8PXQ1nBxRq3w85uGtd9q9SXfqEldMIQ=="], + "@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.1.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.9.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "retext-smartypants": "^6.2.0", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-C6e9BnLGlbdv6bV8MYGeHpHxsUHrCrB4OuRLqi5LI7oiBVcBcqfUN06zpwFQdHgV48QCCrMmLpyqBr7VqC+swA=="], "@mdx-js/mdx/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "astro/@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.2.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.10.0", "@astrojs/prism": "4.0.2", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-+YxmVQu1Bd+MFfSzjq1rOJvD9+nIOJzz5YIIhdIH01RrxRkKbyKoEgyIqP3yv51MhzMDgd79QaPv+kCVPT8vHw=="], + "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], diff --git a/website/package.json b/website/package.json index 2f40aa4..2019834 100644 --- a/website/package.json +++ b/website/package.json @@ -1,7 +1,7 @@ { "name": "gather-step-website", "type": "module", - "version": "4.1.1", + "version": "4.2.0", "private": true, "packageManager": "bun@1.3.12", "scripts": { @@ -13,6 +13,6 @@ }, "dependencies": { "@astrojs/starlight": "^0.39.2", - "astro": "^6.3.5" + "astro": "^6.4.2" } } diff --git a/website/src/components/landing/Benchmark.astro b/website/src/components/landing/Benchmark.astro index efe3391..e79383b 100644 --- a/website/src/components/landing/Benchmark.astro +++ b/website/src/components/landing/Benchmark.astro @@ -69,7 +69,7 @@ const speedup = (totalManual / totalGatherStep).toFixed(1); on the same machine; medians of three runs.

- v4.1.1 · 2026-05-20 + v4.2.0 · 2026-05-29
@@ -141,7 +141,7 @@ const speedup = (totalManual / totalGatherStep).toFixed(1);
- Planning oracle (v4.1.1) + Planning oracle (v4.2.0)

Full 25-scenario suite, every metric at the HIGH ceiling, latency well under release-gate thresholds (p50 ≤ 50 ms, p95 ≤ 300 ms, p99 ≤ 1000 ms).

diff --git a/website/src/components/landing/Hero.astro b/website/src/components/landing/Hero.astro index bfc7e2f..5797629 100644 --- a/website/src/components/landing/Hero.astro +++ b/website/src/components/landing/Hero.astro @@ -6,7 +6,7 @@ const stats = [ }, { label: 'Spec · Release', - value: 'v4.1.1 · multi-PR review', + value: 'v4.2.0 · Claude & Codex MCP setup', }, { label: 'Spec · Deployment', diff --git a/website/src/components/landing/Topology.astro b/website/src/components/landing/Topology.astro index f3526bb..5979cf5 100644 --- a/website/src/components/landing/Topology.astro +++ b/website/src/components/landing/Topology.astro @@ -72,6 +72,6 @@ const consumers = [ CONSUMERS · N=4 - Approved · v4.1.1 + Approved · v4.2.0 diff --git a/website/src/content/docs/changelog.md b/website/src/content/docs/changelog.md index ddc2db4..1ccd2be 100644 --- a/website/src/content/docs/changelog.md +++ b/website/src/content/docs/changelog.md @@ -5,6 +5,33 @@ description: "User-visible changes to gather-step, listed by release. Updated ma This changelog lists significant user-visible changes. The latest release is shown in full at the top; earlier releases are collapsed under [Earlier releases](#earlier-releases) at the bottom of the page. +## v4.2.0 (2026-05-29) + +Release status: **released**. + +Minor release on top of v4.1.1. Fixes `setup-mcp` so it writes to the files MCP clients actually read, adds Codex support, and refreshes dependencies. Rolls up the unreleased v4.0.6 and v4.1.1 changes. + +### Fixed + +- `setup-mcp --scope local` now writes the project-scoped `.mcp.json`, and `--scope global` writes the user-scoped `~/.claude.json`. Previously it wrote to `.claude/settings.json`, which Claude Code does not read for server definitions, so the registered server never appeared in the client. + +### Added + +- `setup-mcp --client codex` merges a `[mcp_servers.gather-step]` block into `~/.codex/config.toml`, preserving existing servers, other keys, and comments. The default client remains `claude`. + +### Docs + +- Corrected the MCP clients guide: the Claude user-scoped config path is `~/.claude.json` (not `~/.claude/settings.json`), and the Fast Path section now reflects the `.mcp.json` / `~/.claude.json` / Codex targets. + +### Changed + +- Refreshed Cargo dependencies to the latest SemVer-compatible versions, including `serde_json 1.0.149 → 1.0.150`, `tokio 1.52.2 → 1.52.3`, `rmcp 1.5.0 → 1.7.0`, `similar 3.1.0 → 3.1.1`, `quick_cache 0.6.21 → 0.6.22`, and `memchr 2.8.0 → 2.8.1`, plus transitive lockfile updates. Intentionally exact-pinned dependencies were left untouched. +- Bumped the website `astro 6.3.5 → 6.4.2` floor and refreshed the website lockfile. + +### Release-wide + +- Bumped the app, Cargo workspace, internal crate dependency versions, and website package metadata to `4.2.0`. + ## v4.0.6 (2026-05-20) Release status: **prepared**. diff --git a/website/src/content/docs/guides/mcp-clients.md b/website/src/content/docs/guides/mcp-clients.md index 392c769..0aad0d2 100644 --- a/website/src/content/docs/guides/mcp-clients.md +++ b/website/src/content/docs/guides/mcp-clients.md @@ -20,17 +20,24 @@ Before connecting a client: 2. **The built binary** on your `PATH`. See [Installation](/guides/installation/) for build instructions. -## Fast Path for Claude +## Fast Path -For Claude Code, Gather Step can write the workspace-local MCP settings entry: +For Claude Code, Gather Step writes the project-scoped `.mcp.json` entry: ```bash gather-step --workspace /path/to/workspace setup-mcp --scope local ``` -Use `--scope global` only when you want the same workspace-pinned server entry -in `~/.claude/settings.json`. The command is idempotent and updates the -`mcpServers.gather-step` block without touching other server entries. +Use `--scope global` to write the user-scoped entry to `~/.claude.json` +instead. For Codex, pass `--client codex` to merge the server block into +`~/.codex/config.toml` (scope is ignored — Codex has a single global config): + +```bash +gather-step --workspace /path/to/workspace setup-mcp --client codex +``` + +The command is idempotent and updates only the `gather-step` entry without +touching other servers, keys, or comments. Restart the client afterward. ## Start the Server (Smoke Test) @@ -77,7 +84,7 @@ fits your workflow: } ``` -**User-scoped:** `~/.claude/settings.json` (applies to all +**User-scoped:** `~/.claude.json` (applies to all projects for this user): ```json