oxify_mcp/
lib.rs

1//! Model Context Protocol implementation for OxiFY
2
3pub mod auth;
4pub mod registry;
5pub mod servers;
6mod transport;
7
8pub use auth::{
9    ApiKeyAuth, AuthConfig, AuthMethod, AuthenticatedHttpTransport, BasicAuth, BearerAuth,
10    CredentialStore, CustomHeaderAuth,
11};
12pub use registry::{
13    LoadBalanceConfig, LoadBalanceStrategy, McpRegistry, RegistryStats, ServerEntry, ServerHealth,
14    ServerMetrics,
15};
16pub use servers::{
17    DatabaseConfig, DatabaseServer, DatabaseType, ExecuteResult, FilesystemServer, GitServer,
18    QueryResult, ShellServer, StatementResult, TransactionResult, WebServer, WorkflowExecutor,
19    WorkflowServer, WorkflowServerConfig,
20};
21pub use transport::{HttpTransport, McpTransport, StdioTransport};
22
23use async_trait::async_trait;
24use serde::{Deserialize, Serialize};
25use serde_json::json;
26use thiserror::Error;
27
28pub type Result<T> = std::result::Result<T, McpError>;
29
30#[derive(Error, Debug)]
31pub enum McpError {
32    #[error("Protocol error: {0}")]
33    ProtocolError(String),
34
35    #[error("Server error: {0}")]
36    ServerError(String),
37
38    #[error("Tool not found: {0}")]
39    ToolNotFound(String),
40
41    #[error("Invalid request: {0}")]
42    InvalidRequest(String),
43
44    #[error("Invalid argument: {0}")]
45    InvalidArgument(String),
46
47    #[error("Tool execution error: {0}")]
48    ToolExecutionError(String),
49
50    #[error("IO error: {0}")]
51    IoError(#[from] std::io::Error),
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct McpRequest {
56    pub server_id: String,
57    pub tool_name: String,
58    pub parameters: serde_json::Value,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct McpResponse {
63    pub result: serde_json::Value,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ToolSchema {
68    pub name: String,
69    pub description: Option<String>,
70    #[serde(rename = "inputSchema")]
71    pub input_schema: serde_json::Value,
72}
73
74/// Trait for MCP server communication
75#[async_trait]
76pub trait McpClient: Send + Sync {
77    async fn invoke_tool(&mut self, request: McpRequest) -> Result<McpResponse>;
78    async fn list_tools(&mut self, server_id: &str) -> Result<Vec<ToolSchema>>;
79}
80
81/// Trait for implementing MCP servers
82#[async_trait]
83pub trait McpServer: Send + Sync {
84    /// Call a tool provided by this server
85    async fn call_tool(
86        &self,
87        name: &str,
88        arguments: serde_json::Value,
89    ) -> Result<serde_json::Value>;
90
91    /// List all tools provided by this server
92    async fn list_tools(&self) -> Result<Vec<serde_json::Value>>;
93}
94
95/// MCP client implementation using transport layer
96pub struct DefaultMcpClient<T: McpTransport> {
97    transport: T,
98    #[allow(dead_code)]
99    server_id: String,
100}
101
102impl<T: McpTransport> DefaultMcpClient<T> {
103    pub fn new(transport: T, server_id: String) -> Self {
104        Self {
105            transport,
106            server_id,
107        }
108    }
109
110    pub async fn initialize(&mut self) -> Result<()> {
111        let request = json!({
112            "method": "initialize",
113            "params": {
114                "protocolVersion": "1.0",
115                "clientInfo": {
116                    "name": "oxify",
117                    "version": env!("CARGO_PKG_VERSION")
118                }
119            }
120        });
121
122        let _response = self.transport.send_request(request).await?;
123        Ok(())
124    }
125
126    pub async fn close(&mut self) -> Result<()> {
127        self.transport.close().await
128    }
129}
130
131#[async_trait]
132impl<T: McpTransport> McpClient for DefaultMcpClient<T> {
133    async fn invoke_tool(&mut self, request: McpRequest) -> Result<McpResponse> {
134        let rpc_request = json!({
135            "method": "tools/call",
136            "params": {
137                "name": request.tool_name,
138                "arguments": request.parameters
139            }
140        });
141
142        let response = self.transport.send_request(rpc_request).await?;
143
144        if let Some(error) = response.get("error") {
145            return Err(McpError::ServerError(format!(
146                "Tool invocation failed: {}",
147                error
148            )));
149        }
150
151        let result = response
152            .get("result")
153            .ok_or_else(|| McpError::ProtocolError("Missing result field".to_string()))?
154            .clone();
155
156        Ok(McpResponse { result })
157    }
158
159    async fn list_tools(&mut self, _server_id: &str) -> Result<Vec<ToolSchema>> {
160        let request = json!({
161            "method": "tools/list",
162            "params": {}
163        });
164
165        let response = self.transport.send_request(request).await?;
166
167        if let Some(error) = response.get("error") {
168            return Err(McpError::ServerError(format!(
169                "Failed to list tools: {}",
170                error
171            )));
172        }
173
174        let result = response
175            .get("result")
176            .and_then(|r| r.get("tools"))
177            .ok_or_else(|| McpError::ProtocolError("Missing tools in response".to_string()))?;
178
179        let tools: Vec<ToolSchema> = serde_json::from_value(result.clone())
180            .map_err(|e| McpError::ProtocolError(format!("Failed to parse tools: {}", e)))?;
181
182        Ok(tools)
183    }
184}