Skip to main content

rab/extensions/mcp/
types.rs

1//! MCP config types — matching pi-mcp-adapter's config schema.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Root MCP config matching pi's mcp.json format.
7#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8#[serde(rename_all = "camelCase")]
9pub struct McpConfig {
10    #[serde(default)]
11    pub mcp_servers: HashMap<String, ServerEntry>,
12
13    #[serde(default, skip_serializing_if = "Option::is_none")]
14    pub settings: Option<McpSettings>,
15}
16
17/// A single MCP server definition.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct ServerEntry {
21    /// Command to spawn (e.g. "npx", "node").
22    /// Optional for URL-based servers.
23    #[serde(default)]
24    pub command: Option<String>,
25
26    #[serde(default)]
27    pub args: Vec<String>,
28
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub env: Option<HashMap<String, String>>,
31
32    /// Working directory for the server process.
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub cwd: Option<String>,
35
36    /// Lifecycle mode: "keep-alive" | "lazy" | "eager".
37    /// Default: "lazy"
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub lifecycle: Option<String>,
40
41    /// Idle timeout in minutes (overrides global setting for this server).
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub idle_timeout: Option<u64>,
44
45    /// Enable direct tools for this server (register as individual LLM tools).
46    /// true = all tools, string[] = only named tools, false = proxy-only.
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub direct_tools: Option<serde_json::Value>,
49
50    /// Exclude specific MCP tools by original or prefixed name.
51    #[serde(default, skip_serializing_if = "Vec::is_empty")]
52    pub exclude_tools: Vec<String>,
53
54    /// For HTTP-based servers: the URL endpoint.
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub url: Option<String>,
57
58    /// HTTP headers for URL-based servers.
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub headers: Option<HashMap<String, String>>,
61}
62
63/// Global MCP settings.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct McpSettings {
67    /// Tool naming prefix mode: "server" (default), "none", or "short".
68    #[serde(default = "default_tool_prefix")]
69    pub tool_prefix: String,
70
71    /// Idle timeout in minutes (default: 10, 0 = disable).
72    #[serde(default = "default_idle_timeout")]
73    pub idle_timeout: u64,
74
75    /// Enable direct tools globally for all servers.
76    #[serde(default)]
77    pub direct_tools: bool,
78}
79
80fn default_tool_prefix() -> String {
81    "server".to_string()
82}
83
84fn default_idle_timeout() -> u64 {
85    10
86}
87
88impl Default for McpSettings {
89    fn default() -> Self {
90        Self {
91            tool_prefix: default_tool_prefix(),
92            idle_timeout: default_idle_timeout(),
93            direct_tools: false,
94        }
95    }
96}
97
98/// Format a tool name with server prefix.
99pub fn format_tool_name(tool_name: &str, server_name: &str, prefix_mode: &str) -> String {
100    match prefix_mode {
101        "none" => tool_name.to_string(),
102        "short" => {
103            let short = server_name
104                .trim_end_matches("mcp")
105                .trim_end_matches("-mcp")
106                .trim_end_matches('_');
107            let p = short.replace('-', "_");
108            if p.is_empty() {
109                tool_name.to_string()
110            } else {
111                format!("{}_{}", p, tool_name)
112            }
113        }
114        _ => {
115            // "server" mode
116            let p = server_name.replace('-', "_");
117            format!("{}_{}", p, tool_name)
118        }
119    }
120}
121
122/// Cached tool metadata (persisted to disk for fast startup).
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct CachedTool {
125    pub name: String,
126    pub description: Option<String>,
127    pub input_schema: serde_json::Value,
128}
129
130/// Per-server cache entry.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct ServerCacheEntry {
133    pub config_hash: u64,
134    pub tools: Vec<CachedTool>,
135    pub cached_at: u64,
136}
137
138/// Root metadata cache structure.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct MetadataCache {
141    pub version: u32,
142    pub servers: HashMap<String, ServerCacheEntry>,
143}