zeph-mcp 0.18.6

MCP client with multi-server lifecycle and Qdrant tool registry for Zeph
Documentation
// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::fmt::Write;

use crate::tool::McpTool;

#[must_use]
pub fn format_mcp_tools_prompt(tools: &[McpTool]) -> String {
    if tools.is_empty() {
        return String::new();
    }

    let mut out = String::from("<available_tools>\n");
    for tool in tools {
        let _ = writeln!(
            out,
            "  <tool server=\"{server}\" name=\"{name}\">\n\
             \x20   <description>{desc}</description>\n\
             \x20   <parameters>{schema}</parameters>\n\
             \x20   <invocation>\n\
             ```mcp\n\
             {{\"server\": \"{server}\", \"tool\": \"{name}\", \"args\": {{...}}}}\n\
             ```\n\
             \x20   </invocation>\n\
             \x20 </tool>",
            server = tool.server_id,
            name = tool.name,
            desc = tool.description,
            schema = tool.input_schema,
        );
    }
    out.push_str("</available_tools>");
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_tool(server: &str, name: &str, desc: &str) -> McpTool {
        McpTool {
            server_id: server.into(),
            name: name.into(),
            description: desc.into(),
            input_schema: serde_json::json!({"type": "object"}),
            security_meta: crate::tool::ToolSecurityMeta::default(),
        }
    }

    #[test]
    fn empty_tools_returns_empty() {
        assert!(format_mcp_tools_prompt(&[]).is_empty());
    }

    #[test]
    fn single_tool_prompt() {
        let tools = vec![make_tool("github", "create_issue", "Create issue")];
        let prompt = format_mcp_tools_prompt(&tools);
        assert!(prompt.starts_with("<available_tools>"));
        assert!(prompt.ends_with("</available_tools>"));
        assert!(prompt.contains("server=\"github\""));
        assert!(prompt.contains("name=\"create_issue\""));
        assert!(prompt.contains("<description>Create issue</description>"));
        assert!(prompt.contains("```mcp"));
        assert!(prompt.contains("\"server\": \"github\""));
    }

    #[test]
    fn multiple_tools_prompt() {
        let tools = vec![
            make_tool("github", "create_issue", "Create issue"),
            make_tool("fs", "read_file", "Read a file"),
        ];
        let prompt = format_mcp_tools_prompt(&tools);
        assert!(prompt.contains("server=\"github\""));
        assert!(prompt.contains("server=\"fs\""));
        assert!(prompt.contains("name=\"read_file\""));
    }

    #[test]
    fn prompt_contains_parameters() {
        let tools = vec![make_tool("s", "t", "d")];
        let prompt = format_mcp_tools_prompt(&tools);
        assert!(prompt.contains("<parameters>"));
        assert!(prompt.contains("\"type\":\"object\""));
    }
}