Skip to content

index_project

Registers projects and rebuilds the graph once, after explicit user approval. Pass every project in one call: a rebuild costs the whole corpus. It never writes inside the source projects.

ArgumentTypeDefaultMeaning
projectsarray of objectsnoneThe batch to register and index. Each entry requires name, path and languages, and accepts nothing else. This is the form to use.
namestringnoneSingle-project form: the repository identifier.
pathstringnoneSingle-project form: the repository directory. Absolute, ~-prefixed, or relative to the working directory of the server process.
languagesarray of stringsnoneSingle-project form: the languages to index.
confirmedboolean or nullnullApproval from a client that cannot use MCP elicitation. Only true proceeds.

Naming both forms in one request is rejected with INVALID_ARGUMENT, because two selectors can disagree and there is nothing to decide between them. Naming neither is rejected the same way.

The accepted languages values are the supported language vocabulary, and nothing else:

go, typescript, javascript, ts, js, rust, rs

Values are lowercased and trimmed. A duplicate in one entry, an empty string, or a word outside the list is rejected before anything is written.

Nothing, in the sense the other ten tools do. It registers the projects it is given in the repository registry, rebuilds the complete canonical graph once, and publishes the result as a new generation. On success it reports the generation and snapshot it published together with the per-language counters of the pass. It is the only mutating tool on the surface.

It refuses without explicit approval. A client that declares the MCP elicitation capability is asked directly, and the request proceeds only when the user accepts. A client that does not implement elicitation must obtain approval itself and then send confirmed: true; sending it without having asked is a lie the server cannot detect.

Called without either, it fails:

PERMISSION_REQUIRED: user approval is required; confirm the operation before setting confirmed=true

An elicitation the user declines fails with PERMISSION_DENIED. An elicitation the client cannot deliver fails with PERMISSION_REQUIRED.

The prompt names what approval covers: the project and its path for a single project, or the count and every name for a batch. Approving “11 projects” without seeing which ones is not approval.

{
"name": "index_project",
"arguments": {
"name": "kivgraph",
"path": "/Users/adria/Documents/programacion/projects/kivgraph",
"languages": ["go"]
}
}
PERMISSION_REQUIRED: user approval is required; confirm the operation before setting confirmed=true

Corpus: snapshot 30 of two repositories, kivgraph and mole. The captured call sent no confirmed and the client declared no elicitation capability, so the refusal is the whole response.

The batch form, which is the one to send:

{
"name": "index_project",
"arguments": {
"projects": [
{
"name": "kivgraph",
"path": "/Users/adria/Documents/programacion/projects/kivgraph",
"languages": ["go", "typescript"]
},
{
"name": "mole",
"path": "/Users/adria/Documents/programacion/projects/mole",
"languages": ["go", "typescript"]
}
],
"confirmed": true
}
}

This is not a style preference. A rebuild resolves cross-repository edges over the complete fact set, so there is no cheaper unit than the whole corpus: adding one project costs exactly what adding eleven costs. Calling the tool once per project pays that full cost once per project and throws away every result but the last, because only the newest generation is published.

Eleven projects registered one call at a time cost eleven rebuilds for one useful graph. Registered together they cost one.

The whole batch is registered before anything is built. If the index or the rebuild then fails, the prior registry is restored and the previous generation stays published.

The tool exists only on a configured serve route. Without a configured indexer it is not registered at all, so the read-only server cannot gain filesystem or storage access by accident.

It is also the only tool registered when no generation has been published yet. That is how a client with no graph builds its first one: the server completes the handshake, publishes index_project alone, and the other ten appear once a generation exists. See Troubleshooting.

Indexing a project that is already registered is not a conflict. The caller asked for that repository to be in the graph, and it is.

  • A project already registered with the same directory is reindexed without touching the registry. An identical request changes nothing on disk at all.
  • A change of languages for the same directory replaces the entry, and the exclusions on file survive it. The request cannot express exclusions, and dropping them would silently widen the index to directories that were excluded on purpose.
  • Only a name already held by a different directory is a real conflict, because then nothing can decide which of the two repositories the name means. The error names the registered path: project "mole" is already registered at "/repos/mole": choose another name or remove that entry.

Names are compared exactly, and a name is an identifier: ., .. and anything containing a path separator are rejected. The rust: namespace is reserved for the providers Kivgraph derives from the toolchain and cannot be taken.

A full rebuild takes minutes on a large registry, while an MCP client applies its own timeout to the call, thirty seconds in some, and cancels work that is progressing fine.

A request that carries a progressToken gets notifications/progress for every unit of work: the phase, the repository and a detail. A client that honours them waits for as long as the work reports. Without a token no progress callback is installed at all, so the index does not pay for a channel nobody reads.

Progress counts up and never repeats a value, as the protocol requires. A notification that cannot be delivered is dropped rather than failing the index.

The pass itself runs as a child process — index --full --json — and the server forwards the progress it reports. That is what keeps the peak of an index out of the process answering queries; see Indexing.

  • It never writes inside the source projects. It writes the repository registry and the Kivgraph state directory; the checkouts it reads are left untouched.
  • One index runs at a time inside a process. An index_project and a resynchronisation cannot overlap, and across processes a lock elects the single writer: the loser does not wait, because a rebuild lasting minutes would look like a hang.
  • A failure is reported as INDEXING_FAILED with the observed cause in the message: a module that needs a newer toolchain, a path that is not a repository, a dependency the module cache does not hold.
  • path must exist and be a directory. A relative path is resolved against the working directory of the server process, which is the client’s choice, not yours; prefer an absolute path.
  • Rust is indexed through an external rust-analyzer scip process and needs it configured. See Configuration.
  • It publishes a new generation, so every cursor issued from the previous one expires with CURSOR_SNAPSHOT_EXPIRED.