symgraph 2026.5.30

Semantic code intelligence library and MCP server - build knowledge graphs of codebases
Documentation
//! symgraph: Semantic code intelligence MCP server
//!
//! Usage:
//!   symgraph serve              Start the MCP server (stdio transport)
//!   symgraph serve --port 8080  Start the MCP server (HTTP transport)
//!   symgraph index [path]       Index a codebase
//!   symgraph status [path]      Show index statistics
//!   symgraph search <query>     Search for symbols
//!   symgraph context <task>     Build context for a task

mod server;

use std::env;

use anyhow::Result;
use tracing::Level;
use tracing_subscriber::FmtSubscriber;

use symgraph::cli::{
    context_command, index_command, prune_command, search_command, status_command, where_command,
};

fn main() -> Result<()> {
    let mut args: Vec<String> = env::args().collect();

    if args.len() < 2 {
        print_usage();
        return Ok(());
    }

    // Global `--db <path>` override: applies to every command (CLI + serve) by
    // seeding SYMGRAPH_DB, which the path resolver consults first. Strip the
    // flag and its value so the remaining positional arguments are unaffected.
    if let Some(i) = args.iter().position(|a| a == "--db") {
        if i + 1 < args.len() {
            env::set_var("SYMGRAPH_DB", &args[i + 1]);
            args.drain(i..=i + 1);
        }
    }

    match args[1].as_str() {
        "serve" => {
            let port = args
                .iter()
                .position(|a| a == "--port")
                .and_then(|i| args.get(i + 1))
                .and_then(|p| p.parse::<u16>().ok());

            // Explicit bind override (e.g. `--bind 0.0.0.0:8080`). Takes
            // precedence over --port when both are given.
            let bind = args
                .iter()
                .position(|a| a == "--bind")
                .and_then(|i| args.get(i + 1))
                .cloned();

            let in_memory = args.iter().any(|a| a == "--in-memory");
            let auth_token = env::var("SYMGRAPH_AUTH_TOKEN")
                .ok()
                .filter(|s| !s.is_empty());

            match (bind, port) {
                (Some(bind), _) => server::start_http(server::HttpConfig {
                    bind,
                    in_memory,
                    auth_token,
                })?,
                (None, Some(port)) => server::start_http(server::HttpConfig {
                    bind: format!("127.0.0.1:{}", port),
                    in_memory,
                    auth_token,
                })?,
                (None, None) => server::start_stdio(in_memory)?,
            }
        }
        "index" => {
            setup_logging();
            let path = args.get(2).map(|s| s.as_str()).unwrap_or(".");
            index_command(path)?;
        }
        "status" => {
            let path = args.get(2).map(|s| s.as_str()).unwrap_or(".");
            status_command(path)?;
        }
        "where" => {
            let path = args.get(2).map(|s| s.as_str()).unwrap_or(".");
            where_command(path)?;
        }
        "prune" => {
            prune_command()?;
        }
        "search" => {
            if args.len() < 3 {
                eprintln!("Usage: symgraph search <query>");
                return Ok(());
            }
            let path = ".";
            let query = &args[2];
            search_command(path, query)?;
        }
        "context" => {
            if args.len() < 3 {
                eprintln!("Usage: symgraph context <task>");
                return Ok(());
            }
            let path = ".";
            let task = args[2..].join(" ");
            context_command(path, &task)?;
        }
        "help" | "--help" | "-h" => {
            print_usage();
        }
        "--version" | "-V" | "version" => {
            print_version();
        }
        cmd => {
            eprintln!("Unknown command: {}", cmd);
            print_usage();
        }
    }

    Ok(())
}

fn print_usage() {
    println!(
        r#"symgraph: Semantic code intelligence MCP server

USAGE:
    symgraph <COMMAND> [OPTIONS]

COMMANDS:
    serve                    Start the MCP server (stdio transport)
    serve --port <PORT>      Start the MCP server (HTTP on 127.0.0.1:<PORT>)
    serve --bind <ADDR:PORT> Start the MCP server on an explicit bind address
    serve --in-memory        Use in-memory database (no filesystem writes)
    index [path]             Index a codebase (default: current directory)
    status [path]            Show index statistics
    search <query>           Search for symbols by name
    context <task>           Build context for a task description
    where [path]             Show the resolved index location for a project
    prune                    Remove cached indexes for repos that no longer exist
    help                     Show this help message

OPTIONS:
    --db <path>             Use an explicit index database path (any command)

ENVIRONMENT:
    SYMGRAPH_ROOT           Project root directory (default: current directory)
    SYMGRAPH_DB             Explicit index database path (overrides storage)
    SYMGRAPH_STORAGE        Index location strategy: git | cache | local.
                            Default: reuse existing .symgraph/, else the git dir
                            (<git-common-dir>/symgraph), else an OS cache dir.
    SYMGRAPH_IN_MEMORY=1    Use in-memory database (alternative to --in-memory)
    SYMGRAPH_AUTH_TOKEN     Bearer token required on /mcp (required for non-
                            loopback binds; optional on 127.0.0.1)

EXAMPLES:
    symgraph index                    # Index current directory
    symgraph index ~/projects/myapp   # Index specific directory
    symgraph serve                    # Start MCP server (stdio)
    symgraph serve --port 8080        # Start MCP server (HTTP on port 8080)
    symgraph serve --in-memory        # Start MCP server with in-memory database
    symgraph search "authenticate"    # Find symbols matching "authenticate"
    symgraph context "add user login" # Build context for implementing login
"#
    );
}

fn print_version() {
    println!("symgraph {}", env!("CARGO_PKG_VERSION"));
}

fn setup_logging() {
    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::INFO)
        .with_target(false)
        .with_writer(std::io::stderr)
        .finish();
    tracing::subscriber::set_global_default(subscriber).ok();
}