Skip to main content

xianyun_mcp/
lib.rs

1//! Library half of `xianyun-mcp`: the allowlist/annotation/middleware
2//! configuration ([`config`]) and the `clap::Command` tree-building logic
3//! shared between the real `xianyun-mcp` binary (`src/main.rs`) and this
4//! crate's tests (golden-file snapshot, guardrail, in-process protocol).
5//!
6//! See `src/main.rs`'s module doc comment for the full story on why the
7//! binary routes argv between the `mcp` subtree and real `xianyun`
8//! command dispatch, and why [`mounted_root`] un-globals `Cli::command()`'s
9//! global arguments before mounting `brontes::command` onto it.
10
11pub mod config;
12
13use clap::{Command, CommandFactory as _};
14use xianyun_cli::Cli;
15
16/// Group name `brontes::command` mounts its subtree under; matches
17/// `brontes`'s own default (see `brontes::Config::command_name`, which
18/// this crate does not override).
19pub const MCP_GROUP: &str = "mcp";
20
21/// Builds the full `xianyun` command tree with `brontes::command` mounted
22/// onto it, after un-globaling `Cli::command()`'s own global arguments so
23/// they don't collide with `brontes`'s own `--log-level` flag on its
24/// mounted subcommands. See the crate-level doc comment for the full
25/// story.
26#[must_use]
27pub fn mounted_root(cfg: &brontes::Config) -> Command {
28    let mut root = Cli::command();
29    let global_ids: Vec<clap::Id> = root
30        .get_arguments()
31        .filter(|a| a.is_global_set())
32        .map(|a| a.get_id().clone())
33        .collect();
34    for id in global_ids {
35        root = root.mut_arg(id, |a| a.global(false));
36    }
37    root.subcommand(brontes::command(Some(cfg)))
38}
39
40/// `true` when `argv[1]` is exactly `"mcp"`, or `argv` carries no arguments
41/// beyond the binary name (the no-arguments-given default).
42#[must_use]
43pub fn is_mcp_invocation<S: AsRef<std::ffi::OsStr>>(argv: &[S]) -> bool {
44    argv.len() == 1 || argv.get(1).is_some_and(|a| a.as_ref() == MCP_GROUP)
45}