Skip to main content

codetether_agent/tui/app/
mcp.rs

1#![allow(dead_code)]
2
3use std::sync::Arc;
4
5use anyhow::{Result, anyhow};
6use serde_json::{Value, json};
7use tokio::sync::RwLock;
8
9use crate::mcp::{McpClient, McpTool};
10
11#[allow(dead_code)]
12#[derive(Clone, Debug)]
13pub struct TuiMcpServerSummary {
14    pub name: String,
15    pub command: String,
16    pub tool_count: usize,
17}
18
19#[allow(dead_code)]
20#[derive(Clone)]
21struct TuiMcpConnection {
22    name: String,
23    command: String,
24    client: Arc<McpClient>,
25}
26
27#[allow(dead_code)]
28#[derive(Default)]
29pub struct TuiMcpRegistry {
30    connections: RwLock<Vec<TuiMcpConnection>>,
31}
32
33#[allow(dead_code)]
34impl TuiMcpRegistry {
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    pub async fn connect(&self, name: &str, command: &str) -> Result<usize> {
40        let parts: Vec<&str> = command.split_whitespace().collect();
41        if parts.is_empty() {
42            return Err(anyhow!("Empty MCP command"));
43        }
44
45        let client = McpClient::connect_subprocess(parts[0], &parts[1..]).await?;
46        let tool_count = client.tools().await.len();
47
48        let mut connections = self.connections.write().await;
49        if let Some(existing) = connections.iter_mut().find(|conn| conn.name == name) {
50            existing.command = command.to_string();
51            existing.client = client;
52            return Ok(tool_count);
53        }
54
55        connections.push(TuiMcpConnection {
56            name: name.to_string(),
57            command: command.to_string(),
58            client,
59        });
60        Ok(tool_count)
61    }
62
63    pub async fn list_servers(&self) -> Vec<TuiMcpServerSummary> {
64        let snapshot = self.connections.read().await.clone();
65        let mut result = Vec::new();
66        for conn in snapshot {
67            let tool_count = conn.client.tools().await.len();
68            result.push(TuiMcpServerSummary {
69                name: conn.name,
70                command: conn.command,
71                tool_count,
72            });
73        }
74        result
75    }
76
77    pub async fn list_tools(&self, server_name: Option<&str>) -> Result<Vec<(String, McpTool)>> {
78        let snapshot = self.connections.read().await.clone();
79        let mut result = Vec::new();
80        for conn in snapshot {
81            if let Some(target) = server_name
82                && conn.name != target
83            {
84                continue;
85            }
86            for tool in conn.client.tools().await {
87                result.push((conn.name.clone(), tool));
88            }
89        }
90
91        if let Some(target) = server_name
92            && result.is_empty()
93        {
94            return Err(anyhow!("No MCP server named '{target}'"));
95        }
96
97        Ok(result)
98    }
99
100    pub async fn call_tool(
101        &self,
102        server_name: &str,
103        tool_name: &str,
104        arguments: Value,
105    ) -> Result<String> {
106        let client = {
107            let snapshot = self.connections.read().await;
108            snapshot
109                .iter()
110                .find(|conn| conn.name == server_name)
111                .map(|conn| Arc::clone(&conn.client))
112        }
113        .ok_or_else(|| anyhow!("No MCP server named '{server_name}'"))?;
114
115        let result = client.call_tool(tool_name, arguments).await?;
116        Ok(result
117            .content
118            .iter()
119            .map(|item| match item {
120                crate::mcp::ToolContent::Text { text } => text.clone(),
121                crate::mcp::ToolContent::Image { data, mime_type } => {
122                    format!("[image: {mime_type} ({} bytes)]", data.len())
123                }
124                crate::mcp::ToolContent::Resource { resource } => {
125                    serde_json::to_string_pretty(resource).unwrap_or_default()
126                }
127            })
128            .collect::<Vec<_>>()
129            .join("\n"))
130    }
131
132    pub async fn summary_json(&self) -> Value {
133        let servers = self.list_servers().await;
134        json!(
135            servers
136                .into_iter()
137                .map(|server| json!({
138                    "name": server.name,
139                    "command": server.command,
140                    "tool_count": server.tool_count,
141                }))
142                .collect::<Vec<_>>()
143        )
144    }
145}