Skip to main content

grr_cli/commands/
mcp.rs

1//! `grr mcp` — the Model Context Protocol server. // wired in cli.rs
2//!
3//! The protocol implementation lives in [`crate::mcp`]; this module is the
4//! clap surface (the `mcp` subcommand is registered in `src/cli.rs`) and
5//! the startup path: one `GoogleAuth` built the same way `run()` does,
6//! reused for every call, plus the `--readonly` safety profile the server
7//! honors exactly as the CLI dispatch does.
8//!
9//! ```text
10//! grr mcp                # serve every Discovery method over stdio
11//! grr mcp --readonly     # writes refused with MCP error results
12//! ```
13
14use crate::commands::build_auth;
15use crate::commands::safety::SafetyProfile;
16use crate::core::ConfigLoader;
17use anyhow::Result;
18use clap::Args;
19
20#[derive(Args, Debug, Clone)]
21pub struct McpArgs {
22    /// Refuse every write method (POST/PATCH/PUT/DELETE); only reads run
23    #[arg(long)]
24    pub readonly: bool,
25}
26
27pub async fn run_mcp(args: McpArgs) -> Result<()> {
28    let config = ConfigLoader::load().await?;
29    let auth = build_auth(&config).await?;
30    let readonly = args.readonly;
31    let profile = SafetyProfile {
32        readonly,
33        ..SafetyProfile::default()
34    };
35    let server = crate::mcp::McpServer::new(auth, profile);
36
37    // stderr ONLY: stdout is the MCP JSON-RPC stream.
38    eprintln!(
39        "grr mcp: {} tools over stdio (JSON-RPC 2.0, newline-delimited){}",
40        server.tool_count(),
41        if readonly {
42            "; --readonly: writes refused"
43        } else {
44            ""
45        }
46    );
47
48    let reader = tokio::io::BufReader::new(tokio::io::stdin());
49    let mut writer = tokio::io::stdout();
50    crate::mcp::serve(&server, reader, &mut writer).await
51}