aether-agent-cli 0.9.6

CLI and ACP server for the Aether AI coding agent
Documentation
use rmcp::model::{
    GetPromptRequestParams, GetPromptResponse, Implementation, ListPromptsResult, PaginatedRequestParams,
    Prompt as McpPrompt, PromptMessage, Role, ServerCapabilities, ServerConfig,
};
use rmcp::service::{DynService, RequestContext};
use rmcp::{ErrorData as McpError, RoleServer, ServerHandler};
use std::sync::Arc;
use tokio::sync::{Notify, mpsc};

#[derive(Clone)]
pub(crate) struct FakePromptMcp {
    prompt_name: String,
    gate: Option<(Arc<Notify>, Arc<Notify>)>,
    elicitation_results: Option<mpsc::UnboundedSender<rmcp::model::ElicitResult>>,
}

impl FakePromptMcp {
    pub(crate) fn new(prompt_name: &str) -> Self {
        Self { prompt_name: prompt_name.to_string(), gate: None, elicitation_results: None }
    }

    pub(crate) fn with_gate(mut self, gate: Option<(Arc<Notify>, Arc<Notify>)>) -> Self {
        self.gate = gate;
        self
    }

    pub(crate) fn with_elicitation(
        mut self,
        results: Option<mpsc::UnboundedSender<rmcp::model::ElicitResult>>,
    ) -> Self {
        self.elicitation_results = results;
        self
    }

    pub(crate) fn into_dyn(self) -> Box<dyn DynService<RoleServer>> {
        Box::new(self)
    }
}

impl ServerHandler for FakePromptMcp {
    fn get_info(&self) -> ServerConfig {
        ServerConfig::new(ServerCapabilities::builder().enable_prompts().build())
            .with_server_info(Implementation::new("fake-prompt-mcp", "0.1.0"))
            .with_instructions("Fake MCP server exposing a single prompt for tests")
    }

    fn list_prompts(
        &self,
        _request: Option<PaginatedRequestParams>,
        _context: RequestContext<RoleServer>,
    ) -> impl std::future::Future<Output = Result<ListPromptsResult, McpError>> + Send + '_ {
        let prompt = McpPrompt::new(&self.prompt_name, Some(format!("{} command", self.prompt_name)), None);
        std::future::ready(Ok(ListPromptsResult::with_all_items(vec![prompt])))
    }

    fn get_prompt(
        &self,
        request: GetPromptRequestParams,
        context: RequestContext<RoleServer>,
    ) -> impl std::future::Future<Output = Result<GetPromptResponse, McpError>> + Send + '_ {
        let result = if request.name.as_str() == self.prompt_name {
            let messages = vec![PromptMessage::new_text(Role::User, format!("expanded {}", self.prompt_name))];
            Ok(rmcp::model::GetPromptResult::new(messages).into())
        } else {
            Err(McpError::invalid_params(format!("Prompt '{}' not found", request.name), None))
        };
        async move {
            if let Some((started, release)) = &self.gate {
                started.notify_one();
                release.notified().await;
            }
            if let Some(results) = &self.elicitation_results {
                let params = serde_json::from_value(serde_json::json!({
                    "mode": "form", "message": "Continue?", "requestedSchema": {"type": "object", "properties": {}}
                }))
                .expect("valid elicitation request");
                let response = context.peer.create_elicitation(params).await.expect("elicitation response");
                let _ = results.send(response);
            }
            result
        }
    }
}