rift

MCP

From repository discovery to precise edit targets.

The main interface for agents to work with rift is the Model Context Protocol, aka MCP. The local rift mcp process is launched via a CLI command by your agent (see Usage).

Rift follows the MCP specifications, and all inputs and structured outputs have a strictly defined JSON schema, available here.

An agent working on an unfamiliar project first needs to understand its structure, then find and read the code relevant to its task. Rift provides context before an edit and updates its local index as the agent changes project files, ready for the next read.

Typical workflow

Read the map, search, retrieve a symbol, inspect nodes, then edit with the agent's tools. Rift detects project file changes and updates the local index, closing the cycle back to the map and subsequent reads. Search and Symbols also draw dependency facts from the global index. Each card links to its documentation.

Let's assume we're working with the following example project:

FilesRust
src/config.rs (excerpt)
use std::path::Path;

use crate::error::ConfigError;

/// Workspace configuration read from `rift.toml`.
pub struct Config {
    pub root: std::path::PathBuf,
}

/// Loads the workspace configuration from `rift.toml`.
pub fn load_config(path: &Path) -> Result<Config, ConfigError> {
    let text = std::fs::read_to_string(path)?;
    parse_config(&text)
}

Map

Before searching, the agent is advised to read the map. The map describes the project that the local index serves: its directories, languages, documentation paths, and dependencies.

For any task, directories help the agent choose where to search. Documentation paths point to project guidance, while dependency entries identify the packages and versions used by the project.

The map's recommendations section lists installed language servers that the configuration has not selected. Each entry carries language, root, and a command to apply with rift config set from the map project's root. After the configuration is accepted, the next map publication leaves that entry out.

For the example project, a map can look like this:

rift://mapExample project3f9a1c2e

Directories

src/

3 files · 8 symbols

Languages

Markdown
1 file · 1 symbol
Rust
3 files · 8 symbols
TOML
2 files · 6 symbols

Documentation

README.md

Dependencies

tokiocargo

Version requirement: ^1.53.1

ruststdlib

Version: 1.98.1

Entry points

rift://symbol/rust/src/main.rs/main

In the text, the first line names the map's revision. Each section, such as languages: or packages:, lists its entries indented under it, and a package shows the version the project locks or, as for tokio here, the requirement its manifest declares.

Rift updates the map when it publishes a new index, so later reads follow changes to the project.

While Rift prepares the local index, the map can show discovered files before their symbols are available. A local_index_preparing warning reports that incomplete preparation.

The map reference describes additional facts, such as entry points and relationships, and when they are available. It also shows a map as compact text.

Once the agent knows where to look, search finds code by a name, a phrase, or a description of its behavior. A search for configuration loading can start with:

{
  "query": "load configuration",
  "include": [
    "source"
  ],
  "limit": 5
}

Each result identifies what matched and where it was found. A declaration result includes its symbol name and identity, so the agent can use that id with get_symbol to read it in more detail. Reading results explains the text response.

Full-text search (FTS)

Full-text search matches words in declaration names, signatures, documentation, source code, and included text files. Rift also recognizes identifier words, so loadConfig can find load_config. A word that only appears in a function's body still finds that function: Rift looks it up in the file's text and answers the declaration holding it.

Double quotes keep a phrase together. For example, the agent can look for this wording in indexed content:

{ "query": "\"workspace configuration\"", "include": ["source"] }

Results include matched_by, which identifies the fields that matched. The agent can narrow the result kind with target: symbol for declarations, documentation for collected documentation, or file for file results; the default, all, allows every kind.

Hybrid search

An agent can describe a behavior without knowing the words used in its implementation. Hybrid search combines identifier and full-text matches with vector ranking, which compares the query's meaning with indexed declarations.

For example, the agent can ask where the project reads its settings:

{ "query": "read project settings from disk", "target": "symbol", "limit": 5 }

This uses the same query argument as full-text search. When vector ranking is available, it contributes to the combined result order. The search configuration controls ranking weights, embedding options, and included text files.

Regex search

Sometimes the agent needs every place a piece of code occurs, not the best matches for a phrase: every TODO, every call written as spawn_blocking(, every path into a crate. It then sends a regular expression as pattern instead of query. The syntax is the one ripgrep reads, so a pattern the agent would write for Grep or rg works unchanged:

{
  "pattern": "load_config",
  "include": [
    "source"
  ],
  "limit": 10
}

In the example project, the pattern matches two lines of src/main.rs. Every match comes back as a file hit with its byte range and line; the text shows its path and line, with the source line below it. A match shows its own line only, so a statement that continues on later lines looks cut; nodes or get_symbol reads the rest. target: "symbol" answers each declaration holding a match instead. Matching runs line by line, and a (?i) prefix makes it case-insensitive. Rift reads only the files a trigram index says can match, then checks them with the pattern, so the answer holds every match the files carry.

Rift fills the trigram index in the background once the project's text is stored, so a search right after startup or an edit doesn't wait for it. Files the index doesn't hold yet are checked too, and when they're too many for one search, the answer covers the files the index holds and carries a pattern_index_preparing warning until the index catches up.

pattern doesn't combine with query, traversal, change, or rev. With scope set to global or all, it also searches the package files the global index holds. The pattern reference describes the bounds a search runs under and the warning a file with too many matches carries.

More search options

The agent can narrow the files it searches, look into dependencies, follow references, compare revisions, or search commit messages. These options use the same search tool:

Symbols

Search discovers a symbol and returns its canonical id. get_symbol reads that exact logical symbol; ownership and language come from the address.

The example project declares load_config in src/config.rs. Its identity names the Rust crate and declaration, rather than the physical file:

{
  "id": "rift://symbol/local/rust/example_project/config/load_config"
}

The result carries outcome: "found", one symbol, an immutable view with an expiry, and declarations in deterministic source order. Each declaration keeps its own origin, source address, byte range, and indices into the symbol's signatures, types, and documentation. Several declaration bindings still belong to one logical symbol. A source-less object can be found with no declarations. These runtime reads distinguish a declaration with no captured source from a synthetic builtin:

{
  "id": "rift://symbol/stdlib/node@26.11.1/javascript/fs/readFile"
}
{
  "id": "rift://symbol/stdlib/cpython@3.12.9/python/builtins/len"
}

Use include to select optional projections:

IncludeAdds
sourceDeclaration source and node identity when available
documentationBounded documentation referring to the exact declaration
historyThe logical symbol's version-control timeline

Omitted include defaults to ["source"]; an explicit empty list requests none of these projections. history lists the revisions that changed the symbol, newest first, with each commit's author. The local server fills its history store in the background; an incomplete timeline answers complete: false until the store holds the selected revision.

Pass a preceding view to retain its immutable selection. Expiry refuses the read instead of selecting latest implicitly. declaration_limit bounds returned declarations; a returned declaration_cursor continues the same symbol, projections, and view. It is not hit paging.

A named local identity selects an extra project accepted by the connection:

{
  "id": "rift://symbol/local@sdk/rust/docs_sdk/load_config"
}

A package or runtime identity selects that exact owner and served version. get_symbol takes neither scope nor packages; use those fields on search to discover the identity. The global search first names the actual served owner; optional documentation remains bound to that exact symbol:

{
  "query": "beacon",
  "scope": "global",
  "packages": [
    {
      "manager": "cargo",
      "registry": "crates.io",
      "name": "demo",
      "version": "1.0.0"
    }
  ],
  "target": "symbol",
  "include": [
    "source"
  ]
}
{
  "id": "rift://symbol/cargo/crates.io/demo@1.0.0/rust/demo/beacon",
  "include": [
    "source",
    "documentation"
  ]
}

A requested release can differ from the served release. Search reports the substitution and returns the served identity:

{
  "query": "beacon",
  "scope": "global",
  "packages": [
    {
      "manager": "cargo",
      "registry": "crates.io",
      "name": "demo",
      "version": "1.1.0"
    }
  ],
  "target": "symbol",
  "include": [
    "source"
  ]
}

Several declaration bindings share one identity. declaration_limit and its returned cursor continue those bindings in the same view:

{
  "id": "rift://symbol/npm/npmjs.org/prettier@3.8.5/typescript/prettier/format"
}
{
  "id": "rift://symbol/npm/npmjs.org/prettier@3.8.5/typescript/prettier/format",
  "declaration_limit": 1
}
{
  "id": "rift://symbol/npm/npmjs.org/prettier@3.8.5/typescript/prettier/format",
  "declaration_limit": 1,
  "declaration_cursor": "fixture-next",
  "view": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

Local Git rev is refused beside view and for package or runtime owners. Released source units remain physical addresses separate from logical symbol identities.

A valid absent identity returns MCP isError: true and outcome: "missing" with symbol_not_found and bounded structural alternatives. Its compact text starts with error: symbol_not_found and lists alternatives under closest matches:. The --output all policy retains the typed answer in structuredContent:

{
  "id": "rift://symbol/local/rust/example_project/config/load_configs"
}

A malformed identity is refused before lookup:

{
  "id": "rift://symbol/rust/src/lib.rs/load_config"
}

An incomplete exact selection returns outcome: "unavailable" with its typed reason, rather than a definite miss. See the symbol reference for bounds and refusals.

Source

get_source reads one physical source unit, independently of whether a declaration exists. For project documentation, omit range to read the whole unit:

{
  "unit": "rift://source/project/README.md"
}

Pass a half-open UTF-8 byte range to read one section from the same unit:

{
  "unit": "rift://source/project/README.md",
  "range": {
    "start": 360,
    "end": 398
  }
}

The view returned by the preceding named-project symbol read selects that project's documentation. The project unit keeps its repository-relative path:

{
  "unit": "rift://source/project/README.md",
  "view": "e1c7db5b8bb6eca9c67b20e69cfa0bba919e60b0c522429052ea2147ccbaeebd"
}
{
  "unit": "rift://source/project/README.md",
  "view": "e1c7db5b8bb6eca9c67b20e69cfa0bba919e60b0c522429052ea2147ccbaeebd",
  "range": {
    "start": 43,
    "end": 81
  }
}

The result names the actual unit and range in source.span, returns bytes in source.text, and states source_complete. A preceding view retains its exact selection. Released package and runtime units retain their defining owner:

{
  "unit": "rift://source/cargo/crates.io/demo@1.0.0/src/lib.rs"
}

For these units, rev selects local Git source alone.

Nodes

After reading a declaration, the agent may need to change only its name, body, or one expression. nodes identifies the syntax around a position in a local file, giving the agent source ranges for a precise edit target.

The position is a UTF-8 byte offset, rather than a line or character number.

In the sample src/config.rs, byte 338 lies inside the parse_config call:

{
  "path": "src/config.rs",
  "position": 338
}

The response lists enclosing nodes from outermost to innermost, with source excerpts and byte ranges:

Node in this exampleWhat the agent can inspect
FileThe surrounding source
load_config functionThe complete declaration
Function bodyThe implementation inside the braces
parse_config(&text) callThe expression to change
parse_config identifierThe name within that expression

In the text response:

  • Each node shows its syntax kind and line count, then its node identity, which carries the byte range: @334-353 is the parse_config(&text) call.
  • The function also shows its symbol identity and the ranges of its name and body.
  • The excerpt of the innermost node closes the answer; the structured output carries the excerpt of every node.

The agent chooses the range it needs and applies the change with its own editing tools. After the file changes, it should read again before using its ranges for another edit. See the nodes reference for the response shape and supported files.

Edits and index updates

As the agents edit the codebase, rift watches the project files and updates the local index when they change, so the next read can use the new code.

search also reaches collected package and runtime facts. get_symbol reads the exact owner named in its canonical identity; edits to project source update the local index. See Index for local updates and package scope for dependency reads.

Reading results

Search keeps responses small with limit and names another page with page_index. Its returned pagination states the served page and page count. An exact get_symbol read bounds declaration bindings with declaration_limit and continues with declaration_cursor in the same immutable view.

Warnings explain when an answer is incomplete or a capability could not contribute. For example, stale_index reports an index behind the captured project state or a rebuild failure; results_truncated means a narrower query or path selection can bring results within the bound. Large files and lockfiles carry their own: large_file_unparsed names a file too large to parse, whose text still answers, large_file_skipped counts files left out of text search, and lockfile_excluded names a lockfile the path selection reached but search leaves out. The read reference describes each warning.

The agent reads each answer as compact text. The text is a list of sections, and each section starts with a title that counts its entries, such as 2 results or 1 warning, with the entries indented under it. Warnings form their own section after the items:

{
  "query": "load configuration",
  "include": [
    "source"
  ],
  "limit": 5
}

A symbol hit shows its declaration, location and matched fields, canonical identity when established, and requested source. A warning names its code, payload facts, and optional detail. An exact read uses its outcome and declaration bindings instead of a hit count:

{
  "id": "rift://symbol/local/rust/example_project/config/load_config"
}

Compact text prints only what stands out, such as a requested score, a dependency origin, or a truncated history. rift mcp returns compact text by default. A program that needs every field as JSON starts rift mcp --output=all, which includes structured output and output schemas in the tool listing. The workspace resource then carries both compact text and JSON. The map always returns JSON.

The result content reference defines the form.

Resources

When a result needs more context, the agent can read the local configuration through MCP resources or diagnostics through rift server logs. The CLI reads recorded diagnostics directly from storage, with or without a running server:

The workspace resource comes as compact text, in the same form as a tool result:

rift://workspace, compact text of a workspace with one TypeScript language and one source file:

workspace 3f9a1c2e
languages:
	typescript:tsx · syntax · lsp typescript:tsx ready
		include src/**/*.tsx
		exclude src/generated/**
source:
	src/view.tsx · typescript:tsx · 8a4d20bc
  • The workspace text starts with the digest of the accepted rift.toml, then lists each language with its include and exclude patterns, and each source file with its language and content digest.

See resource contents for the compact text and the JSON body a read carries.

On this page