codetether_agent/tui/app/
mcp.rs1#![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}