1use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10use rmcp::handler::server::router::tool::ToolRouter;
11use rmcp::handler::server::wrapper::Parameters;
12use rmcp::model::ServerCapabilities;
13use rmcp::model::ServerInfo;
14use rmcp::{tool, tool_handler, tool_router, ServerHandler, ServiceExt};
15
16use graphyn_core::graph::GraphynGraph;
17use graphyn_store::RocksGraphStore;
18
19use crate::tools::{blast_radius, dependencies, symbol_usages};
20
21#[derive(Clone)]
23pub struct GraphynMcpServer {
24 graph: Arc<GraphynGraph>,
25 #[allow(dead_code)]
26 repo_root: PathBuf,
27 #[allow(dead_code)]
28 tool_router: ToolRouter<Self>,
29}
30
31#[tool_router]
32impl GraphynMcpServer {
33 pub fn new(repo_root: PathBuf) -> Result<Self, String> {
35 let graph = load_graph(&repo_root)?;
36 Ok(Self {
37 graph: Arc::new(graph),
38 repo_root,
39 tool_router: Self::tool_router(),
40 })
41 }
42
43 #[tool(
46 name = "get_blast_radius",
47 description = "Given a symbol name, returns all symbols that depend on it and would be affected by changes. Resolves aliases. Tracks property-level access."
48 )]
49 async fn get_blast_radius(
50 &self,
51 params: Parameters<blast_radius::BlastRadiusParams>,
52 ) -> Result<String, String> {
53 blast_radius::execute(&self.graph, params.0)
54 }
55
56 #[tool(
58 name = "get_dependencies",
59 description = "Returns everything a given symbol depends on — its full dependency tree."
60 )]
61 async fn get_dependencies(
62 &self,
63 params: Parameters<dependencies::DependenciesParams>,
64 ) -> Result<String, String> {
65 dependencies::execute(&self.graph, params.0)
66 }
67
68 #[tool(
71 name = "get_symbol_usages",
72 description = "Finds all usages of a symbol across the codebase, including under aliases and re-exports. Use this when you need to find all references before renaming or deleting a symbol."
73 )]
74 async fn get_symbol_usages(
75 &self,
76 params: Parameters<symbol_usages::SymbolUsagesParams>,
77 ) -> Result<String, String> {
78 symbol_usages::execute(&self.graph, params.0)
79 }
80}
81
82#[tool_handler]
83impl ServerHandler for GraphynMcpServer {
84 fn get_info(&self) -> ServerInfo {
85 ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
86 .with_server_info(rmcp::model::Implementation::new(
87 "graphyn",
88 env!("CARGO_PKG_VERSION"),
89 ))
90 .with_instructions(
91 "Graphyn is a code intelligence engine. Use get_blast_radius to find \
92 what will break if you change a symbol, get_dependencies to see what \
93 a symbol depends on, and get_symbol_usages to find every usage \
94 including aliased imports.",
95 )
96 }
97}
98
99pub async fn serve_stdio(repo_root: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
101 let server = GraphynMcpServer::new(repo_root)?;
102
103 let transport = rmcp::transport::io::stdio();
104 let running_service = server.serve(transport).await?;
105
106 running_service.waiting().await?;
108
109 Ok(())
110}
111
112fn load_graph(repo_root: &Path) -> Result<GraphynGraph, String> {
115 let db = repo_root.join(".graphyn").join("db");
116 if !db.exists() {
117 return Err(format!(
118 "No graph found at {}. Run `graphyn analyze <path>` first.",
119 db.display(),
120 ));
121 }
122 let store = RocksGraphStore::open(&db).map_err(|e| format!("failed to open store: {e}"))?;
123 store
124 .load_graph()
125 .map_err(|e| format!("failed to load graph: {e}"))
126}