1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! The `mcp` subcommand: run the Model Context Protocol server so an agent
//! (Claude, Codex, …) can drive one or more boatramp instances, or manage the
//! instances it targets.
//!
//! - `boatramp mcp` (or `mcp serve`) speaks MCP over **stdio** — what a desktop
//! agent spawns.
//! - `boatramp mcp setup add/list/remove` edits `~/.config/boatramp/mcp.toml`.
//!
//! The tool surface + transport live in the `boatramp-mcp` crate (kept separate
//! so rmcp's schemars 1.x doesn't collide with the operator's schemars 0.8).
use clap::{Args, Subcommand};
/// A failure in the `mcp` subcommand.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// An error from the MCP server / config layer.
#[error(transparent)]
Mcp(#[from] boatramp_mcp::Error),
}
/// `boatramp mcp [serve | setup …]`.
#[derive(Debug, Args)]
pub struct McpArgs {
#[command(subcommand)]
pub action: Option<McpAction>,
}
/// The `mcp` actions; absent means `serve`.
#[derive(Debug, Subcommand)]
pub enum McpAction {
/// Serve the MCP protocol over stdio (the default action).
Serve,
/// Manage the boatramp instances the MCP server can drive.
Setup {
#[command(subcommand)]
action: SetupAction,
},
}
/// `boatramp mcp setup …` — instance registry management.
#[derive(Debug, Subcommand)]
pub enum SetupAction {
/// Register a boatramp instance.
Add {
/// The name the agent uses to select this instance.
name: String,
/// The control-plane base URL (e.g. https://boatramp.example.com).
#[arg(long)]
server: String,
/// Admin token spec: `env:VAR`, `path:/file`, or a literal (empty = none).
#[arg(long, default_value = "")]
token: String,
/// Token holder (`cnf`) private-key spec for per-request DPoP/PoP proofs.
#[arg(long)]
holder_key: Option<String>,
/// Server raw-public-key SPKI hex to pin (RFC 7250 `--tls rpk`).
#[arg(long)]
server_pubkey: Option<String>,
/// Skip TLS verification (self-signed cert on a trusted network only).
#[arg(long)]
insecure: bool,
},
/// List the registered instances.
List,
/// Remove a registered instance by name.
Remove {
/// The instance name to remove.
name: String,
},
}
/// Run the `mcp` subcommand.
pub async fn run(args: McpArgs) -> Result<(), Error> {
match args.action.unwrap_or(McpAction::Serve) {
McpAction::Serve => boatramp_mcp::serve_stdio().await?,
McpAction::Setup { action } => match action {
SetupAction::Add {
name,
server,
token,
holder_key,
server_pubkey,
insecure,
} => {
let msg = boatramp_mcp::setup::add(boatramp_mcp::InstanceConfig {
name,
server,
token,
holder_key,
server_pubkey,
insecure,
})?;
println!("{msg}");
}
SetupAction::List => println!("{}", boatramp_mcp::setup::list()?),
SetupAction::Remove { name } => println!("{}", boatramp_mcp::setup::remove(&name)?),
},
}
Ok(())
}