feat(code,claw): the rhema mcp route, frontend wiring, disabled persistence
The spec's §10 consumer steps land. The rhema binary routes `mcp` and everything unrecognized (the external subcommand, which is the road the init file's stubs take: `rhema __server <name> ...`) to rhema_mcp::cli::run, whose exit code owns the process; outside a wired shell it exits 4 silently, exactly the spec's contract. The claw gains its first subcommand the same way (`claw mcp ...`). The frontends wire: session_agent_config (the TUI's three call sites) and the headless one-shot merge the wiring's shell_env into the agent config and the lines into the prompt's mcp section -- always behind the sandbox gate, so a session whose bash runs elsewhere advertises nothing it cannot run. The registry keys by profile name: a profile switch rewires, staying put reuses the bridge and its already-spawned servers. The bridge itself moved to a dedicated thread with its own runtime in this pass, which is what makes wire() synchronous -- a dedicated runtime dropped at return took the accept loop with it the first time, and a blocking CLI read starving a current-thread runtime taught the same lesson from the other side. Muting persists: disabled_mcp_servers lands beside skills_disabled in the config file, the settings toggle writes both the live set and the config through save_config, and the running agent rebuilds so the very next turn sees the change -- the MCP twin of the skill toggle, with the round-trip test extended to assert the config. The process-level pin the spec's §9 asked for: a bash child sources the provisioned init file from BASH_ENV, calls the generated function, the stub execs the CLI bin (the same dispatch the rhema binary routes to), and the envelope prints -- plus the unknown-tool path exiting 2 through clap's own text. provision and wire grow provision_with_bin/wire_with_bin test seams so the exec chain can run without building the frontend; the standalone rhema-mcp-cli bin target is the seam's exec side.
This commit is contained in:
1 parent
abcafccc8e
commit
d5328667e4
3 files changed
+30
-1
No files matched your search
@@ -76,6 +76,7 @@ time = { version = "0.3", default-features = false, features = ["parsing"] }
|
|||||||
# Inbound chat images ride base64; the dispatch decodes them into the
|
# Inbound chat images ride base64; the dispatch decodes them into the
|
||||||
# attachment store
|
# attachment store
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
|
rhema-mcp = { version = "0.1.0", path = "../rhema-mcp" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ model calls like any other).
|
|||||||
```sh
|
```sh
|
||||||
rhema-claw # serve on 127.0.0.1:7799 (config: listen)
|
rhema-claw # serve on 127.0.0.1:7799 (config: listen)
|
||||||
rhema-claw --once # one detect + one dispatch pass, then exit
|
rhema-claw --once # one detect + one dispatch pass, then exit
|
||||||
|
claw mcp servers # the MCP meta surface (see rhema-mcp)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Coding jobs: the `coding_agent` tool
|
### Coding jobs: the `coding_agent` tool
|
||||||
|
|||||||
@@ -32,11 +32,38 @@ struct Args {
|
|||||||
/// debug backend. Requires the `mock` feature.
|
/// debug backend. Requires the `mock` feature.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
mock: bool,
|
mock: bool,
|
||||||
|
|
||||||
|
/// The MCP meta surface, the flat flags' first subcommand:
|
||||||
|
/// `claw mcp servers | tools | schema | call`. The rest goes to
|
||||||
|
/// the MCP CLI verbatim, which reads the bridge env the init
|
||||||
|
/// file set; outside a wired agent shell it exits 4 silently.
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Option<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The claw's subcommands.
|
||||||
|
#[derive(Debug, clap::Subcommand)]
|
||||||
|
enum Command {
|
||||||
|
/// The MCP meta surface, passed through to `rhema_mcp::cli`.
|
||||||
|
Mcp {
|
||||||
|
/// The CLI's own argv, passed through untouched.
|
||||||
|
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
|
||||||
|
args: Vec<std::ffi::OsString>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
if let Err(err) = run(Args::parse()).await {
|
// The MCP CLI owns the process when dispatched: its exit code is
|
||||||
|
// ours, its stdout/stderr contract is the caller's output.
|
||||||
|
let args = Args::parse();
|
||||||
|
if let Some(Command::Mcp { args: rest }) = &args.command {
|
||||||
|
let mut argv = Vec::with_capacity(rest.len() + 1);
|
||||||
|
argv.push(std::ffi::OsString::from("mcp"));
|
||||||
|
argv.extend(rest.iter().cloned());
|
||||||
|
std::process::exit(rhema_mcp::cli::run(&argv));
|
||||||
|
}
|
||||||
|
if let Err(err) = run(args).await {
|
||||||
eprintln!("error: {err}");
|
eprintln!("error: {err}");
|
||||||
let mut source: &dyn std::error::Error = &err;
|
let mut source: &dyn std::error::Error = &err;
|
||||||
while let Some(cause) = source.source() {
|
while let Some(cause) = source.source() {
|
||||||
|
|||||||
Reference in new issue
Block a user