Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ailoop-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ ailoop-core = { path = "../ailoop-core" }
ailoop-server = { path = "../ailoop-server" }

# CLI framework
cli-framework = { git = "https://github.com/aroff/cli-framework", features = ["doctor"] }
cli-framework = { git = "https://github.com/aroff/cli-framework", features = ["doctor", "mcp-server", "mcp-install"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }

# CLI-specific dependencies
Expand Down
1 change: 0 additions & 1 deletion ailoop-cli/src/cli/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::cli::message_converter::MessageConverter;
use crate::parser::{create_parser, InputFormat};
use ailoop_core::transport::factory::{create_transport, TransportConfig, TransportType};
use anyhow::{Context, Result};
use std::io::{self, BufRead, BufReader};
use std::path::PathBuf;
use tokio::io::AsyncBufReadExt;

Expand Down
20 changes: 20 additions & 0 deletions ailoop-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ fn ask_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let payload = named(&args, "payload");
Expand Down Expand Up @@ -196,6 +197,7 @@ fn authorize_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let action = named(&args, "action");
Expand Down Expand Up @@ -232,6 +234,7 @@ fn say_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let message = named(&args, "message");
Expand Down Expand Up @@ -264,6 +267,7 @@ fn serve_command() -> Command {
}),
validator: None,
expose_mcp: false,
expose_chat: false,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let host = named_or(&args, "host", "127.0.0.1");
Expand Down Expand Up @@ -295,6 +299,7 @@ fn config_command() -> Command {
}),
validator: None,
expose_mcp: false,
expose_chat: false,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let init = flag(&args, "init");
Expand Down Expand Up @@ -325,6 +330,7 @@ fn image_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let image_path = named(&args, "image_path");
Expand Down Expand Up @@ -352,6 +358,7 @@ fn navigate_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let url = named(&args, "url");
Expand Down Expand Up @@ -391,6 +398,7 @@ fn forward_command() -> Command {
}),
validator: None,
expose_mcp: false,
expose_chat: false,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let channel = named_or(&args, "channel", "public");
Expand Down Expand Up @@ -426,6 +434,7 @@ fn queue_command() -> Command {
}),
validator: None,
expose_mcp: false,
expose_chat: false,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let server = named(&args, "server");
Expand Down Expand Up @@ -457,6 +466,7 @@ fn task_create_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let title = named(&args, "title");
Expand Down Expand Up @@ -488,6 +498,7 @@ fn task_list_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let channel = named_or(&args, "channel", "public");
Expand Down Expand Up @@ -517,6 +528,7 @@ fn task_show_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let task_id = named(&args, "task_id");
Expand Down Expand Up @@ -547,6 +559,7 @@ fn task_update_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let task_id = named(&args, "task_id");
Expand All @@ -572,6 +585,7 @@ fn task_ready_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let channel = named_or(&args, "channel", "public");
Expand All @@ -595,6 +609,7 @@ fn task_blocked_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let channel = named_or(&args, "channel", "public");
Expand Down Expand Up @@ -628,6 +643,7 @@ fn task_dep_add_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let child_id = named(&args, "child_id");
Expand Down Expand Up @@ -665,6 +681,7 @@ fn task_dep_remove_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let child_id = named(&args, "child_id");
Expand Down Expand Up @@ -693,6 +710,7 @@ fn task_dep_graph_command() -> Command {
}),
validator: None,
expose_mcp: true,
expose_chat: true,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let task_id = named(&args, "task_id");
Expand Down Expand Up @@ -722,6 +740,7 @@ fn provider_list_command() -> Command {
}),
validator: None,
expose_mcp: false,
expose_chat: false,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let config = named_or(&args, "config", "~/.config/ailoop/config.toml");
Expand All @@ -747,6 +766,7 @@ fn provider_telegram_test_command() -> Command {
}),
validator: None,
expose_mcp: false,
expose_chat: false,
execute: Arc::new(|_ctx, args| {
Box::pin(async move {
let config = named_or(&args, "config", "~/.config/ailoop/config.toml");
Expand Down
36 changes: 36 additions & 0 deletions ailoop-cli/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,43 @@ mod authorize_timeout_tests {
stdout
);
}
}

#[cfg(test)]
mod mcp_cli_tests {
use super::*;

/// `mcp install --agent cursor --stdio --dry-run` must exit 0, print a JSON config
/// containing "ailoop" as a server key, and must NOT create `.cursor/mcp.json`.
#[test]
fn test_mcp_install_dry_run_cursor() {
let stdout = run_ailoop(&[
"mcp",
"install",
"--agent",
"cursor",
"--stdio",
"--dry-run",
])
.expect("mcp install --dry-run should exit 0");

assert!(
stdout.contains("ailoop"),
"dry-run output should contain 'ailoop' as a server key, got: {}",
stdout
);

// --dry-run must not write any files; .cursor/mcp.json must not exist in CWD
assert!(
!std::path::Path::new(".cursor/mcp.json").exists(),
".cursor/mcp.json must NOT be created during --dry-run"
);
}
}

#[cfg(test)]
mod authorize_timeout_tests_extra {
use super::*;
/// Tests the actual InputResult::Timeout path: --default no + timeout fires -> DENIED.
/// Stdin pipe is kept open so the process cannot receive EOF; only the countdown
/// timer fires, exercising the InputResult::Timeout branch end-to-end.
Expand Down
2 changes: 1 addition & 1 deletion ailoop-py/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading