Skip to main content

vibe_graph_mcp/
types.rs

1//! Types for MCP tool inputs and outputs.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::sync::Arc;
8use std::time::Instant;
9
10use dashmap::DashMap;
11use vibe_graph_core::SourceCodeGraph;
12use vibe_graph_ops::Store;
13
14// =============================================================================
15// Gateway Registry Types
16// =============================================================================
17
18/// A registered project in the MCP gateway.
19#[derive(Clone)]
20pub struct RegisteredProject {
21    /// Project name (derived from workspace directory name).
22    pub name: String,
23
24    /// Absolute path to the workspace root.
25    pub workspace_path: PathBuf,
26
27    /// The source code graph for this project.
28    pub graph: Arc<SourceCodeGraph>,
29
30    /// Store for accessing .self metadata.
31    pub store: Store,
32
33    /// When this project was registered.
34    pub registered_at: Instant,
35}
36
37/// Thread-safe registry of all projects served by the gateway.
38#[derive(Default)]
39pub struct ProjectRegistry {
40    /// Map from project name to project data.
41    pub projects: DashMap<String, RegisteredProject>,
42}
43
44impl ProjectRegistry {
45    /// Create a new empty registry.
46    pub fn new() -> Self {
47        Self {
48            projects: DashMap::new(),
49        }
50    }
51
52    /// Register a new project.
53    pub fn register(&self, project: RegisteredProject) {
54        self.projects.insert(project.name.clone(), project);
55    }
56
57    /// Unregister a project by name.
58    pub fn unregister(&self, name: &str) -> Option<RegisteredProject> {
59        self.projects.remove(name).map(|(_, v)| v)
60    }
61
62    /// Get a project by name.
63    pub fn get(
64        &self,
65        name: &str,
66    ) -> Option<dashmap::mapref::one::Ref<'_, String, RegisteredProject>> {
67        self.projects.get(name)
68    }
69
70    /// List all project names.
71    pub fn list_names(&self) -> Vec<String> {
72        self.projects.iter().map(|r| r.key().clone()).collect()
73    }
74
75    /// Get count of registered projects.
76    pub fn len(&self) -> usize {
77        self.projects.len()
78    }
79
80    /// Check if registry is empty.
81    pub fn is_empty(&self) -> bool {
82        self.projects.is_empty()
83    }
84
85    /// Get the single project if only one is registered.
86    pub fn get_single(&self) -> Option<dashmap::mapref::one::Ref<'_, String, RegisteredProject>> {
87        if self.projects.len() == 1 {
88            self.projects.iter().next().map(|r| {
89                // Get a proper Ref by looking up the key
90                let key = r.key().clone();
91                drop(r);
92                self.projects.get(&key).unwrap()
93            })
94        } else {
95            None
96        }
97    }
98}
99
100/// Request to register a project with the gateway.
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct RegisterProjectRequest {
103    /// Project name.
104    pub name: String,
105
106    /// Absolute path to the workspace.
107    pub workspace_path: PathBuf,
108}
109
110/// Response from project registration.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct RegisterProjectResponse {
113    /// Whether registration succeeded.
114    pub success: bool,
115
116    /// Message describing the result.
117    pub message: String,
118
119    /// Total number of projects now registered.
120    pub project_count: usize,
121}
122
123/// Health check response.
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct HealthResponse {
126    /// Server status.
127    pub status: String,
128
129    /// Server version.
130    pub version: String,
131
132    /// Number of registered projects.
133    pub project_count: usize,
134
135    /// List of registered project names.
136    pub projects: Vec<String>,
137}
138
139/// Project info for list_projects tool.
140#[derive(Debug, Clone, Serialize, JsonSchema)]
141pub struct ProjectInfo {
142    /// Project name.
143    pub name: String,
144
145    /// Workspace path.
146    pub workspace_path: String,
147
148    /// Number of nodes in the graph.
149    pub node_count: usize,
150
151    /// Number of edges in the graph.
152    pub edge_count: usize,
153}
154
155/// Output for the `list_projects` tool.
156#[derive(Debug, Clone, Serialize, JsonSchema)]
157pub struct ListProjectsOutput {
158    /// All registered projects.
159    pub projects: Vec<ProjectInfo>,
160
161    /// Total count.
162    pub count: usize,
163}
164
165// =============================================================================
166// Tool Input Types
167// =============================================================================
168
169/// Input for the `search_nodes` tool.
170#[derive(Debug, Clone, Deserialize, JsonSchema)]
171pub struct SearchNodesInput {
172    /// Project name to search in. Required if multiple projects are registered.
173    /// If only one project is registered, this is optional.
174    #[serde(default)]
175    pub project: Option<String>,
176
177    /// Search query (matches against node name and path).
178    pub query: String,
179
180    /// Filter by node kind: "file", "directory", "module", "test", "service".
181    #[serde(default)]
182    pub kind: Option<String>,
183
184    /// Filter by file extension (e.g., "rs", "py", "ts").
185    #[serde(default)]
186    pub extension: Option<String>,
187
188    /// Maximum number of results to return.
189    #[serde(default = "default_limit")]
190    pub limit: usize,
191}
192
193fn default_limit() -> usize {
194    20
195}
196
197/// Input for the `get_dependencies` tool.
198#[derive(Debug, Clone, Deserialize, JsonSchema)]
199pub struct GetDependenciesInput {
200    /// Project name to query. Required if multiple projects are registered.
201    #[serde(default)]
202    pub project: Option<String>,
203
204    /// Path or name of the node to query.
205    pub node_path: String,
206
207    /// Include incoming dependencies (nodes that depend on this one).
208    #[serde(default = "default_true")]
209    pub incoming: bool,
210
211    /// Include outgoing dependencies (nodes this one depends on).
212    #[serde(default = "default_true")]
213    pub outgoing: bool,
214}
215
216fn default_true() -> bool {
217    true
218}
219
220/// Input for the `impact_analysis` tool.
221#[derive(Debug, Clone, Deserialize, JsonSchema)]
222pub struct ImpactAnalysisInput {
223    /// Project name to analyze. Required if multiple projects are registered.
224    #[serde(default)]
225    pub project: Option<String>,
226
227    /// Paths to analyze for impact.
228    pub paths: Vec<String>,
229
230    /// Traversal depth for impact propagation.
231    #[serde(default = "default_depth")]
232    pub depth: usize,
233
234    /// Include test files in the impact analysis.
235    #[serde(default = "default_true")]
236    pub include_tests: bool,
237}
238
239fn default_depth() -> usize {
240    2
241}
242
243/// Input for the `get_node_context` tool.
244#[derive(Debug, Clone, Deserialize, JsonSchema)]
245pub struct GetNodeContextInput {
246    /// Project name to query. Required if multiple projects are registered.
247    #[serde(default)]
248    pub project: Option<String>,
249
250    /// Path or name of the node to get context for.
251    pub node_path: String,
252
253    /// Number of neighbor hops to include.
254    #[serde(default = "default_context_depth")]
255    pub depth: usize,
256
257    /// Include file content for source files.
258    #[serde(default)]
259    pub include_content: bool,
260}
261
262fn default_context_depth() -> usize {
263    1
264}
265
266/// Input for the `list_files` tool.
267#[derive(Debug, Clone, Deserialize, JsonSchema)]
268pub struct ListFilesInput {
269    /// Project name to list files from. Required if multiple projects are registered.
270    #[serde(default)]
271    pub project: Option<String>,
272
273    /// Directory path to list (empty for root).
274    #[serde(default)]
275    pub path: Option<String>,
276
277    /// Filter by file extension.
278    #[serde(default)]
279    pub extension: Option<String>,
280
281    /// Filter by node kind.
282    #[serde(default)]
283    pub kind: Option<String>,
284
285    /// Maximum number of results.
286    #[serde(default = "default_limit")]
287    pub limit: usize,
288}
289
290/// Input for the `get_git_changes` tool.
291#[derive(Debug, Clone, Deserialize, JsonSchema)]
292pub struct GetGitChangesInput {
293    /// Project name to get git changes for. Required if multiple projects are registered.
294    #[serde(default)]
295    pub project: Option<String>,
296}
297
298// =============================================================================
299// Tool Output Types
300// =============================================================================
301
302/// Information about a single node in the graph.
303#[derive(Debug, Clone, Serialize, JsonSchema)]
304pub struct NodeInfo {
305    /// Node ID.
306    pub id: u64,
307
308    /// Node name (typically filename).
309    pub name: String,
310
311    /// Full path to the node.
312    pub path: String,
313
314    /// Node kind: "file", "directory", "module", "test", "service", "other".
315    pub kind: String,
316
317    /// File extension (if applicable).
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub extension: Option<String>,
320
321    /// Programming language (if detected).
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub language: Option<String>,
324
325    /// Additional metadata.
326    #[serde(skip_serializing_if = "HashMap::is_empty")]
327    pub metadata: HashMap<String, String>,
328}
329
330/// Information about an edge/dependency.
331#[derive(Debug, Clone, Serialize, JsonSchema)]
332pub struct EdgeInfo {
333    /// Source node path.
334    pub from: String,
335
336    /// Target node path.
337    pub to: String,
338
339    /// Relationship type: "uses", "imports", "implements", "contains".
340    pub relationship: String,
341}
342
343/// Output for the `search_nodes` tool.
344#[derive(Debug, Clone, Serialize, JsonSchema)]
345pub struct SearchNodesOutput {
346    /// Matching nodes.
347    pub nodes: Vec<NodeInfo>,
348
349    /// Total number of matches (may be more than returned if limit applied).
350    pub total_matches: usize,
351
352    /// Query that was executed.
353    pub query: String,
354}
355
356/// Output for the `get_dependencies` tool.
357#[derive(Debug, Clone, Serialize, JsonSchema)]
358pub struct GetDependenciesOutput {
359    /// The queried node.
360    pub node: NodeInfo,
361
362    /// Nodes that depend on this one (incoming edges).
363    pub dependents: Vec<NodeInfo>,
364
365    /// Nodes this one depends on (outgoing edges).
366    pub dependencies: Vec<NodeInfo>,
367
368    /// Edge details.
369    pub edges: Vec<EdgeInfo>,
370}
371
372/// Output for the `impact_analysis` tool.
373#[derive(Debug, Clone, Serialize, JsonSchema)]
374pub struct ImpactAnalysisOutput {
375    /// Paths that were analyzed.
376    pub analyzed_paths: Vec<String>,
377
378    /// All impacted nodes.
379    pub impacted_nodes: Vec<NodeInfo>,
380
381    /// Test files that should be run.
382    pub impacted_tests: Vec<NodeInfo>,
383
384    /// Number of nodes impacted.
385    pub impact_count: usize,
386
387    /// Traversal depth used.
388    pub depth: usize,
389}
390
391/// Output for the `get_git_changes` tool.
392#[derive(Debug, Clone, Serialize, JsonSchema)]
393pub struct GitChangesOutput {
394    /// Changed files.
395    pub changes: Vec<GitFileChange>,
396
397    /// Number of files changed.
398    pub change_count: usize,
399
400    /// Summary by change kind.
401    pub summary: GitChangesSummary,
402}
403
404/// A single git file change.
405#[derive(Debug, Clone, Serialize, JsonSchema)]
406pub struct GitFileChange {
407    /// File path.
408    pub path: String,
409
410    /// Change kind: "modified", "added", "deleted", "untracked", "renamed".
411    pub kind: String,
412
413    /// Whether the change is staged.
414    pub staged: bool,
415}
416
417/// Summary of git changes by kind.
418#[derive(Debug, Clone, Serialize, JsonSchema)]
419pub struct GitChangesSummary {
420    pub modified: usize,
421    pub added: usize,
422    pub deleted: usize,
423    pub untracked: usize,
424}
425
426/// Output for the `get_node_context` tool.
427#[derive(Debug, Clone, Serialize, JsonSchema)]
428pub struct NodeContextOutput {
429    /// The central node.
430    pub node: NodeInfo,
431
432    /// Neighboring nodes within the specified depth.
433    pub neighbors: Vec<NodeInfo>,
434
435    /// Edges connecting the nodes.
436    pub edges: Vec<EdgeInfo>,
437
438    /// File content (if requested and available).
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub content: Option<String>,
441}
442
443/// Output for the `list_files` tool.
444#[derive(Debug, Clone, Serialize, JsonSchema)]
445pub struct ListFilesOutput {
446    /// Files matching the criteria.
447    pub files: Vec<NodeInfo>,
448
449    /// Total count.
450    pub total: usize,
451
452    /// Path that was listed.
453    pub path: Option<String>,
454}