Skip to main content

vestige_mcp/
lib.rs

1//! MCP adapter — six high-level tools over project memory (PRD §13.2).
2//!
3//! Thin adapter crate: each tool maps 1:1 to a `vestige-core` or
4//! `vestige-engine` function. No SQL, no destructive defaults, no
5//! cross-project access. Errors are always structured `{code, message,
6//! retryable}` so coding agents can branch on them.
7//!
8//! # Tool surface (PRD §13.2)
9//!
10//! | Tool | File | Purpose |
11//! |------|------|---------|
12//! | `vestige_bootstrap` | `tools/bootstrap.rs` | Standing context at session start |
13//! | `vestige_search` | `tools/search.rs` | Lexical / semantic / hybrid search |
14//! | `vestige_expand` | `tools/expand.rs` | Full content at chosen depth |
15//! | `vestige_get_project_context` | `tools/project_context.rs` | Budget-bounded context pack |
16//! | `vestige_record_observation` | `tools/record_observation.rs` | Write a new observation |
17//! | `vestige_record_decision` | `tools/record_decision.rs` | Write a new decision |
18//!
19//! # Entry point
20//!
21//! [`run`] resolves the project config, opens the store, and starts the
22//! `rmcp` stdio server. `vestige-cli` calls this from its `mcp` subcommand.
23
24mod server;
25mod tools;
26
27use std::path::PathBuf;
28
29use anyhow::{Context, Result};
30use rmcp::{transport::stdio, ServiceExt};
31use vestige_config::discover_config;
32use vestige_store::Store;
33
34pub use server::VestigeServer;
35pub use tools::expand::ExpandParams;
36pub use tools::get_candidate::GetCandidateParams;
37pub use tools::list_candidates::ListCandidatesParams;
38pub use tools::propose_candidate::{ProposeCandidateParams, ProposeSource};
39pub use tools::search::SearchParams;
40pub use tools::trace::TraceParams;
41
42/// Options forwarded from `vestige mcp` CLI flags.
43pub struct McpOptions {
44    /// When `true`, write tools (`record_observation`, `record_decision`) are
45    /// disabled and return `READ_ONLY` errors.
46    pub read_only: bool,
47}
48
49pub async fn run(opts: McpOptions) -> Result<()> {
50    let cwd = std::env::current_dir().context("reading current directory")?;
51    let (config_path, config) = discover_config(&cwd).context(
52        "no Vestige project found from this directory — run `vestige init` to create one",
53    )?;
54    let project_id = config.project_id()?;
55    let storage_path: PathBuf = config.resolved_storage_path()?;
56    let store = Store::open(&storage_path).context("opening project store")?;
57
58    tracing::info!(
59        project = %project_id,
60        config = %config_path.display(),
61        storage = %storage_path.display(),
62        read_only = opts.read_only,
63        "starting MCP server"
64    );
65
66    let server = VestigeServer::new(store, config, project_id, opts.read_only);
67    let service = server.serve(stdio()).await.context("MCP serve")?;
68    service.waiting().await.context("MCP wait")?;
69    Ok(())
70}