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.
Map
Understand the project structure and dependencies.
Riftrift://mapSearch
Find code and documentation relevant to the task.
RiftsearchSymbols
Read the declaration, source, and documentation.
Riftget_symbolNodes
Inspect syntax and choose the range to edit.
RiftnodesEdit
Apply changes to project files. This action happens outside of rift with standard agent tooling.
AgentAgent toolsUpdate index
Rift detects file changes and updates project facts for the next read.
RiftLocal index
Let's assume we're working with the following example project:
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:
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
tokiocargoVersion requirement: ^1.53.1
ruststdlibVersion: 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.
Search
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:
| Include | Adds |
|---|---|
source | Declaration source and node identity when available |
documentation | Bounded documentation referring to the exact declaration |
history | The 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 example | What the agent can inspect |
|---|---|
| File | The surrounding source |
load_config function | The complete declaration |
| Function body | The implementation inside the braces |
parse_config(&text) call | The expression to change |
parse_config identifier | The 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-353is theparse_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:
Workspace
rift://workspace shows accepted configuration and the source catalog, including which files
and languages Rift serves.
MCP reference
Complete tool examples, resource fields, limits, warnings, and failure behavior.
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.