# Doora

> Doora parses source files into syntax trees and runs S-expression queries to find functions, types, and call sites that grep misses. Supports Rust, Python, JS, TS, Go, C, and C++. Exposes an MCP server so AI agents understand your codebase semantically, cutting hallucinations and wasted context tokens.

- **Type:** MCP server
- **Install:** `agentstack add mcp-backpack-lab-doora`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [backpack-lab](https://agentstack.voostack.com/s/backpack-lab)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [backpack-lab](https://github.com/backpack-lab)
- **Source:** https://github.com/backpack-lab/doora
- **Website:** https://backpack-lab.github.io/maps/

## Install

```sh
agentstack add mcp-backpack-lab-doora
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# doora

**Search code the way compilers see it — not the way text editors do.**

[](https://crates.io/crates/doora)
[](./LICENSE)
[](https://github.com/backpack-lab/doora/actions)
[](#supported-languages)
[](#installation)

`doora` is a high-performance structural code search engine built on Tree-sitter. It parses source files into `Abstract Syntax Trees` and executes pattern queries against them — finding functions, types, call sites, and structural relationships that text search tools are fundamentally incapable of locating. Unlike grep, which cannot tell the difference between a function named authenticate and a comment that mentions authenticate, `doora` understands your code's grammar. 

Additionally, `doora` serves as a persistent "Codebase Memory" for AI coding agents. By exposing its structural index via the Model Context Protocol (`MCP`), `LLM`s can execute precise, graph-native queries directly against your codebase, retrieving exact function signatures and dependency relationships without overwhelming their context windows with raw source text.

---

## Table of Contents

- [Why Not grep?](#why-not-grep)
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [CLI Reference](#cli-reference)
  - [search](#search-command-default)
  - [index](#index-command)
  - [lookup](#lookup-command)
  - [serve --mcp](#serve---mcp-command)
- [Query Syntax Guide](#query-syntax-guide)
  - [Basics](#basics)
  - [Captures](#captures)
  - [Predicates](#predicates)
  - [Multiple queries](#multiple-queries)
- [Usage Examples by Language](#usage-examples-by-language)
  - [Rust](#rust)
  - [Python](#python)
  - [JavaScript](#javascript)
  - [TypeScript](#typescript)
  - [Go](#go)
  - [C](#c)
  - [C++](#c-1)
  - [Auto-detection](#auto-detection)
- [The Bloom Filter Index](#the-bloom-filter-index)
- [The Persistent Structural Index](#the-persistent-structural-index)
- [Semantic Rewriting](#semantic-rewriting)
- [Interactive TUI](#interactive-tui)
- [MCP Server for AI Agents](#mcp-server-for-ai-agents)
- [Performance](#performance)
- [Architecture](#architecture)
- [Building from Source](#building-from-source)
- [Contributing](#contributing)
- [License](#license)

---

## Why Not grep?

Every text-based search tool — `grep`, `ripgrep`, `ack`, `ag` — suffers from the same fundamental blindness: they treat source code as a string. They have no concept of grammar, scope, or structure.

When you run:

```bash
rg "authenticate"
```

You get every occurrence of those 12 characters — inside function names, variable names, string literals, comments, dead code, documentation, and test fixtures alike. You get everything, and you cannot filter it without writing increasingly fragile regular expressions.

`doora` answers questions that text search cannot:

| Question | grep / ripgrep | doora |
|---|---|---|
| Find function *definitions* named `authenticate` | Returns all occurrences everywhere | Returns only `function_item` definition nodes |
| Find functions taking exactly 2 arguments | Cannot be expressed reliably | Trivial — query the parameter list child count |
| Find all `unwrap()` calls outside test modules | Cannot express scope constraints | Single query with scope predicate |
| Find structs that implement a specific trait | Multi-step, fragile, many false positives | One S-expression query |
| Rename a function at every *definition* site | Risks corrupting string literals and comments | Semantic rewriting via AST — surgical precision |
| Find all type aliases named `Result` | Returns `Result` everywhere | Returns only `type_alias_declaration` nodes |

The key insight: `doora` is to `grep` what a SQL database is to a flat text file. Both contain the same data; one understands its structure.

---

## Features

- **Structural pattern matching** via Tree-sitter S-expression queries
- **7 languages**: Rust, Python, JavaScript, TypeScript, Go, C, C++
- **Language auto-detection** per file from extension — walk mixed-language repos in one command
- **Multiple queries in one pass** — the AST is traversed exactly once regardless of how many `-q` flags you pass
- **Bloom filter pre-rejection index** — skip files that mathematically cannot contain your search term before invoking the parser
- **Persistent SQLite structural index** — extract and query all symbols (functions, structs, types, imports) across an entire codebase
- **Semantic rewriting** — surgically replace structural patterns without corrupting surrounding syntax
- **Interactive TUI** — split-pane AST visualizer with live streaming results
- **MCP server** — expose your codebase's structural graph to LLM coding agents
- **Respects `.gitignore`** — never parses `node_modules`, `target/`, or build artifacts
- **Parallel file processing** via Rayon work-stealing thread pool
- **Flat RAM profile** — memory usage is bounded by thread count, not repository size
- **Shell completions** for bash, zsh, and fish

---

## Installation

### From crates.io

```bash
cargo install doora
```

Requires Rust 1.78 or later.

### Pre-built binaries

Download from the [Releases page](https://github.com/backpack-lab/doora/releases):

| Platform | Architecture | Binary |
|---|---|---|
| Linux | x86_64 | `doora-x86_64-unknown-linux-gnu` |
| Linux | aarch64 | `doora-aarch64-unknown-linux-gnu` |
| macOS | x86_64 | `doora-x86_64-apple-darwin` |
| macOS | Apple Silicon | `doora-aarch64-apple-darwin` |
| Windows | x86_64 | `doora-x86_64-pc-windows-msvc.exe` |

### From source

```bash
git clone https://github.com/backpack-lab/doora
cd doora
cargo build --release
# Binary at ./target/release/doora
```

### Shell completions

```bash
# Bash
doora --generate-completions bash >> ~/.bashrc

# Zsh
doora --generate-completions zsh >> ~/.zshrc

# Fish
doora --generate-completions fish > ~/.config/fish/completions/doora.fish
```

---

## Quick Start

```bash
# Find all Rust function definitions in ./src
doora -q '(function_item name: (identifier) @fn_name)' -p ./src

# Find a specific function by name
doora -q '(function_item name: (identifier) @fn (#eq? @fn "connect"))' -p ./src

# Search Python files
doora -q '(function_definition name: (identifier) @fn)' -p . --lang python

# Auto-detect language per file — search everything at once
doora -q '(function_declaration name: (identifier) @fn_name)' -p .

# Multiple queries in a single tree-traversal pass
doora \
  -q '(function_item name: (identifier) @fn_name)' \
  -q '(struct_item name: (type_identifier) @struct_name)' \
  -p ./src --no-color

# Build a Bloom filter index for faster searches
doora index ./src

# Build both Bloom filter and persistent SQLite symbol index
doora index ./src --persist

# Look up a symbol by name in the persistent index
doora lookup --symbol parse_file --path ./src

# Launch the interactive TUI
doora -q '(function_item name: (identifier) @fn)' -p . --tui
```

**Example output:**

```
src/auth/handler.rs:42:0  [@fn_name]  "parse_token"
src/auth/handler.rs:89:0  [@fn_name]  "validate_session"
src/db/pool.rs:14:0       [@fn_name]  "connect"

Found 47 matches across 23 files in 38ms
```

---

## CLI Reference

### search command (default)

When no subcommand is given, `doora` runs a structural search.

```
doora [search] [OPTIONS] --query 
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `-q, --query ` | String (repeatable) | required | S-expression query. Pass multiple `-q` flags for single-pass multi-query search. |
| `-p, --path ` | PathBuf | `.` | Root directory to search. Must exist and be a directory. |
| `-l, --lang ` | String | `auto` | Language: `rust`, `python`, `js`, `ts`, `go`, `c`, `cpp`, `auto`. |
| `--no-color` | bool | false | Disable ANSI color. Also respected via `NO_COLOR` env var. |
| `-Q, --quiet` | bool | false | Suppress per-match lines. Show only the summary. |
| `--stats` | bool | false | Print detailed performance diagnostics to stderr. |
| `--tui` | bool | false | Launch the interactive terminal UI. |
| `--rewrite ` | String | — | Rewrite matched captures using `@capture_name` substitution. Dry-run by default. |
| `--in-place` | bool | false | Apply rewrites to files. Requires `--rewrite`. Shows diff and prompts for confirmation. |
| `--yes` | bool | false | Skip confirmation prompt with `--in-place`. |
| `--no-update-index` | bool | false | Disable automatic incremental index updates during search. |

**Output format (stdout):**

```
src/auth/handler.rs:42:0  [@fn_name]  "parse_token"
^─────────────────────^   ^─────────^ ^────────────^
      filepath:line:col   capture     matched text
```

- Filepath: cyan
- Capture name: yellow
- Matched text: green, always in literal quotes
- Line numbers: 1-indexed
- Columns: 0-indexed byte offsets

**Summary (stderr):**

```
Found 47 matches across 23 files in 38ms
```

**Stats output (with `--stats`):**

```
--- search statistics ---
files walked:     47
files parsed:     46
files skipped:    1
matches found:    12
sieve rejected:   18
match rate:       26.09% (files with matches / files parsed)
wall time:        38ms
throughput:       1236.84 files/sec
index updated:    3 entries
```

---

### index command

Builds or updates the Bloom filter index and optionally the persistent SQLite structural index.

```
doora index  [OPTIONS]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `` | PathBuf | required | Root directory to index. |
| `--lang ` | String | `auto` | Language filter for indexing. |
| `--persist` | bool | false | Also extract symbols and insert into the SQLite structural index. |
| `--verbose` | bool | false | Print one line per file: `indexed:`, `fresh:`, or `removed:`. |

The Bloom filter index is stored at `/.doora-index` (bincode format).
The SQLite structural index is stored at `/.doora-memory.db`.

Both indexes are updated incrementally — files whose mtime and size match the stored entry are skipped.

```bash
# Build Bloom filter index only
doora index ./src

# Build both indexes with verbose output
doora index ./src --persist --verbose
```

```
indexed: src/auth/handler.rs
indexed: src/db/pool.rs
fresh:   src/main.rs
removed: src/old/legacy.rs

indexed 44 files, skipped 2 fresh, removed 1 stale entries, extracted 312 symbols
index written to .doora-index
```

---

### lookup command

Queries the persistent SQLite structural index for symbols by name, prefix, kind, or language.

```
doora lookup [OPTIONS]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `--symbol ` | String | — | Exact symbol name. Mutually exclusive with `--prefix`. |
| `--prefix ` | String | — | Find all symbols whose name starts with PREFIX. |
| `--kind ` | String | — | Filter by kind: `function`, `struct`, `class`, `enum`, `trait`, `interface`, `type_alias`, `constant`, `variable`, `module`, `import`. |
| `--lang ` | String | — | Filter results to files of this language. |
| `-p, --path ` | PathBuf | `.` | Root directory where the index was built. |
| `--no-color` | bool | false | Disable ANSI color output. |

At least one of `--symbol` or `--prefix` is required. Both cannot be used together.

**Output format** matches structural search output exactly:

```
src/parser.rs:45:0  [@function]  "parse_file"
  signature: pub fn parse_file(path: &Path, language: &tree_sitter::Language) -> Result

Found 1 symbol in 1 file in 2ms
```

**Examples:**

```bash
# Look up an exact function name
doora lookup --symbol authenticate --path ./src

# Find all symbols starting with "handle_"
doora lookup --prefix handle_ --path ./src

# Find all structs
doora lookup --prefix "" --kind struct --path ./src

# Find Rust functions only (in a mixed-language repo)
doora lookup --prefix connect --kind function --lang rust --path .
```

---

### serve --mcp command

Starts an MCP (Model Context Protocol) server that exposes the structural index to LLM coding agents.

```
doora serve --mcp [OPTIONS]
```

| Flag | Type | Default | Description |
|---|---|---|---|
| `--mcp` | bool | required | Enable MCP server mode over JSON-RPC stdio transport. |

See [MCP Server for AI Agents](#mcp-server-for-ai-agents) for full setup instructions.

---

## Query Syntax Guide

`doora` uses Tree-sitter's S-expression pattern syntax. An S-expression is a Lisp-like notation that mirrors the shape of the syntax tree.

**Looking for how to write queries?** Check out our detailed [Query Guide](QUERY_GUIDE.md).

### Basics

**Match any node of a given type:**

```scheme
(function_item)
```

Matches every `function_item` node anywhere in the tree.

**Match a specific child:**

```scheme
(function_item name: (identifier))
```

Matches only `function_item` nodes that have a `name` field containing an `identifier` node.

**Nested patterns:**

```scheme
(impl_item
  type: (type_identifier)
  body: (declaration_list
    (function_item name: (identifier))))
```

Patterns can nest to arbitrary depth, mirroring the tree structure.

**Wildcard:**

```scheme
(function_item name: (_))
```

`(_)` matches any single node regardless of type.

### Captures

A `@capture_name` tag extracts the matched node's text and includes it in the output.

```scheme
(function_item name: (identifier) @fn_name)
```

Multiple captures per query are supported:

```scheme
(function_item
  name: (identifier) @fn_name
  parameters: (parameters) @params)
```

Each capture produces a separate result line in the output.

### Predicates

Predicates filter captures based on their text content. They appear inside the S-expression after the structural pattern.

**`#eq?` — exact equality:**

```scheme
(function_item
  name: (identifier) @fn
  (#eq? @fn "connect"))
```

**`#match?` — regular expression:**

```scheme
(function_item
  name: (identifier) @fn
  (#match? @fn "^(get|set|update)_"))
```

Matches function names starting with `get_`, `set_`, or `update_`. The regex is compiled once at query compile time and never recompiled per file.

**`#not-eq?` — negative equality:**

```scheme
(function_item
  name: (identifier) @fn
  (#not-eq? @fn "main"))
```

**`#any-of?` — match any value in a list:**

```scheme
(function_item
  name: (identifier) @fn
  (#any-of? @fn "get" "set" "delete"))
```

### Multiple queries

Pass multiple `-q` flags to run several queries in a single tree traversal. The AST is walked exactly once per file regardless of query count:

```bash
doora \
  -q '(function_item name: (identifier) @fn_name)' \
  -q '(struct_item name: (type_identifier) @struct_name)' \
  -q '(enum_item name: (type_identifier) @enum_name)' \
  -p ./src
```

Results from all queries are merged, sorted by file and position, and deduplicated.

---

## Usage Examples by Language

### Rust

```bash
# All function definitions
doora -q '(function_item name: (identifier) @fn_name)' -p ./src

# A specific function
doora -q '(function_item name: (identifier) @fn (#eq? @fn "authenticate"))' -p .

# All functions matching a naming pattern
doora -q '(function_item name: (identifier) @fn (#match? @fn "^handle_"))' -p ./src

# All struct definitions
doora -q '(struct_item name: (type_identifier) @struct_name)' -p ./src

# All enum definitions
doora -q '(enum_item name: (type_identifier) @enum_name)' -p ./src

# All trait definitions
doora -q '(trait_item name: (type_identifier) @trait_name)' -p ./src

# All impl blocks for a specific type
doora \
  -q '(impl_item type: (type_identifier) @t (#eq? @t "Config"))' \
  -p ./src

# All trait implementations (impl Trait for Type)
doora \
  -q '(impl_item trait: (type_identifier) @trait type: (type_identifier) @type)' \
  -p ./src

# All .unwrap() call sites
doora \
  -q '(call_expression function: (field_expression field: (field_identifier) @m (#eq? @m "unwrap")))' \
  -p ./src

# All use declarations (imports)
doora -q '(use_declaration) @import' -p ./src

# Functions returning a specific type
doora \
  -q '(function_item return_type: (generic_type type: (type_identifier) @t (#eq? @t "Result")) @fn)' \
  -p ./src

# All type aliases
doora -q '(type_item name:

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [backpack-lab](https://github.com/backpack-lab)
- **Source:** [backpack-lab/doora](https://github.com/backpack-lab/doora)
- **License:** Apache-2.0
- **Homepage:** https://backpack-lab.github.io/maps/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-backpack-lab-doora
- Seller: https://agentstack.voostack.com/s/backpack-lab
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
