use anyhow::Result;
use jsonrpsee::{
core::RpcResult,
proc_macros::rpc,
server::{Server, ServerHandle as JsonRpcServerHandle},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
pub mod cache;
pub mod handlers;
pub mod rmcp_handlers;
pub mod rmcp_server;
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct HealthResponse {
pub status: String,
pub timestamp: u64,
pub version: String,
}
#[rpc(server)]
pub trait HealthRpc {
#[method(name = "health_check")]
async fn health_check(&self) -> RpcResult<HealthResponse>;
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct ProcessLocalRequest {
pub prompt: String,
pub path: std::path::PathBuf,
pub include_patterns: Vec<String>,
pub ignore_patterns: Vec<String>,
pub include_imports: bool,
pub max_tokens: Option<u32>,
pub llm_tool: Option<String>,
pub include_context: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct ProcessLocalResponse {
pub answer: String,
pub context: Option<String>,
pub file_count: usize,
pub token_count: usize,
pub processing_time_ms: u64,
pub llm_tool: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct ProcessRemoteRequest {
pub prompt: String,
pub repo_url: String,
pub include_patterns: Vec<String>,
pub ignore_patterns: Vec<String>,
pub include_imports: bool,
pub max_tokens: Option<u32>,
pub llm_tool: Option<String>,
pub include_context: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct ProcessRemoteResponse {
pub answer: String,
pub context: Option<String>,
pub file_count: usize,
pub token_count: usize,
pub processing_time_ms: u64,
pub repo_name: String,
pub llm_tool: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct GetFileMetadataRequest {
pub file_path: std::path::PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct GetFileMetadataResponse {
pub path: std::path::PathBuf,
pub size: u64,
pub modified: u64,
pub is_symlink: bool,
pub language: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct SearchCodebaseRequest {
pub path: std::path::PathBuf,
pub query: String,
pub max_results: Option<u32>,
pub file_pattern: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct SearchResult {
pub file_path: std::path::PathBuf,
pub line_number: usize,
pub line_content: String,
pub match_context: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct SearchCodebaseResponse {
pub results: Vec<SearchResult>,
pub total_matches: usize,
pub files_searched: usize,
pub search_time_ms: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct DiffFilesRequest {
pub file1_path: std::path::PathBuf,
pub file2_path: std::path::PathBuf,
pub context_lines: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct DiffHunk {
pub old_start: usize,
pub old_lines: usize,
pub new_start: usize,
pub new_lines: usize,
pub content: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct DiffFilesResponse {
pub file1_path: std::path::PathBuf,
pub file2_path: std::path::PathBuf,
pub hunks: Vec<DiffHunk>,
pub added_lines: usize,
pub removed_lines: usize,
pub is_binary: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct SemanticSearchRequest {
pub path: std::path::PathBuf,
pub query: String,
pub search_type: SemanticSearchType,
pub max_results: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SemanticSearchType {
Functions,
Types,
Imports,
References,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct SemanticSearchResult {
pub file_path: std::path::PathBuf,
pub symbol_name: String,
pub symbol_type: String,
pub line_number: usize,
pub context: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub struct SemanticSearchResponse {
pub results: Vec<SemanticSearchResult>,
pub total_matches: usize,
pub files_analyzed: usize,
pub search_time_ms: u64,
}
#[rpc(server)]
pub trait CodebaseRpc {
#[method(name = "process_local_codebase")]
async fn process_local_codebase(
&self,
request: ProcessLocalRequest,
) -> RpcResult<ProcessLocalResponse>;
#[method(name = "process_remote_repo")]
async fn process_remote_repo(
&self,
request: ProcessRemoteRequest,
) -> RpcResult<ProcessRemoteResponse>;
#[method(name = "get_file_metadata")]
async fn get_file_metadata(
&self,
request: GetFileMetadataRequest,
) -> RpcResult<GetFileMetadataResponse>;
#[method(name = "search_codebase")]
async fn search_codebase(
&self,
request: SearchCodebaseRequest,
) -> RpcResult<SearchCodebaseResponse>;
#[method(name = "diff_files")]
async fn diff_files(&self, request: DiffFilesRequest) -> RpcResult<DiffFilesResponse>;
#[method(name = "semantic_search")]
async fn semantic_search(
&self,
request: SemanticSearchRequest,
) -> RpcResult<SemanticSearchResponse>;
}
pub struct ServerHandle {
inner: JsonRpcServerHandle,
local_addr: SocketAddr,
}
impl ServerHandle {
pub fn local_addr(&self) -> Result<SocketAddr> {
Ok(self.local_addr)
}
pub fn stop(self) -> Result<()> {
self.inner.stop()?;
Ok(())
}
}
pub async fn start_server(addr: &str) -> Result<ServerHandle> {
let addr: SocketAddr = addr.parse()?;
let server = Server::builder().build(addr).await?;
let local_addr = server.local_addr()?;
let cache = std::sync::Arc::new(cache::McpCache::new());
let health_impl = handlers::HealthRpcImpl;
let codebase_impl = handlers::CodebaseRpcImpl::new(cache);
let mut rpc_module = health_impl.into_rpc();
rpc_module.merge(codebase_impl.into_rpc())?;
let handle = server.start(rpc_module);
Ok(ServerHandle {
inner: handle,
local_addr,
})
}