use oxi_agent::mcp::ServerEntry;
#[derive(Debug, Clone)]
pub struct McpPreset {
pub name: &'static str,
pub summary: &'static str,
pub tag: &'static str,
pub entry: ServerEntry,
pub env_placeholder: Option<&'static str>,
pub requires_oauth: bool,
}
pub fn presets() -> &'static [McpPreset] {
use std::sync::OnceLock;
static CACHE: OnceLock<Vec<McpPreset>> = OnceLock::new();
CACHE.get_or_init(|| {
vec![
McpPreset {
name: "filesystem",
summary: "Read / write / search files in a directory (official MCP server).",
tag: "stdio",
entry: ServerEntry {
command: Some("npx".to_string()),
args: Some(vec![
"-y".to_string(),
"@modelcontextprotocol/server-filesystem".to_string(),
".".to_string(),
]),
..ServerEntry::default()
},
env_placeholder: None,
requires_oauth: false,
},
McpPreset {
name: "everything",
summary: "Reference server: prompts, resources, tools — good for testing.",
tag: "stdio",
entry: ServerEntry {
command: Some("npx".to_string()),
args: Some(vec![
"-y".to_string(),
"@modelcontextprotocol/server-everything".to_string(),
]),
..ServerEntry::default()
},
env_placeholder: None,
requires_oauth: false,
},
McpPreset {
name: "git",
summary: "Read / search / manipulate local Git repositories.",
tag: "stdio",
entry: ServerEntry {
command: Some("uvx".to_string()),
args: Some(vec!["mcp-server-git".to_string()]),
..ServerEntry::default()
},
env_placeholder: None,
requires_oauth: false,
},
McpPreset {
name: "context7",
summary: "Resolve library IDs and pull up-to-date docs into context.",
tag: "http",
entry: ServerEntry {
url: Some("https://mcp.context7.com/mcp".to_string()),
..ServerEntry::default()
},
env_placeholder: Some("CONTEXT7_API_KEY=…"),
requires_oauth: false,
},
McpPreset {
name: "gh_grep",
summary: "Grep by Vercel — search public code snippets on GitHub.",
tag: "http",
entry: ServerEntry {
url: Some("https://mcp.grep.app".to_string()),
..ServerEntry::default()
},
env_placeholder: None,
requires_oauth: false,
},
McpPreset {
name: "sentry",
summary: "Query Sentry issues, projects, and error events.",
tag: "http+oauth",
entry: ServerEntry {
url: Some("https://mcp.sentry.dev/mcp".to_string()),
..ServerEntry::default()
},
env_placeholder: None,
requires_oauth: true,
},
]
})
}
impl McpPreset {
#[cfg(test)]
pub fn sorted() -> Vec<&'static McpPreset> {
let mut v: Vec<&'static McpPreset> = presets().iter().collect();
v.sort_by(|a, b| a.name.cmp(b.name));
v
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn presets_have_unique_names() {
let mut seen = std::collections::HashSet::new();
for p in presets() {
assert!(seen.insert(p.name), "duplicate preset name: {}", p.name);
}
}
#[test]
fn presets_have_transport_field() {
for p in presets() {
let has_stdio = p.entry.command.is_some();
let has_http = p.entry.url.is_some();
assert!(
has_stdio ^ has_http,
"preset {} must set exactly one of command / url",
p.name
);
}
}
#[test]
fn presets_have_short_name() {
for p in presets() {
assert!(p.name.len() <= 24, "preset name too long: {}", p.name);
assert!(!p.name.is_empty(), "preset name empty");
}
}
#[test]
fn preset_tags_are_recognized() {
for p in presets() {
assert!(
matches!(p.tag, "stdio" | "http" | "http+oauth" | "oauth"),
"preset {} has unknown tag: {}",
p.name,
p.tag
);
}
}
#[test]
fn oauth_presets_have_url() {
for p in presets() {
if p.requires_oauth {
assert!(
p.entry.url.is_some(),
"oauth preset {} must use http transport",
p.name
);
}
}
}
#[test]
fn sorted_iter_is_ordered() {
let sorted = McpPreset::sorted();
for w in sorted.windows(2) {
assert!(w[0].name <= w[1].name);
}
}
}