navree
Documentation

Navigator

navree mcp: the MCP server for your coding agent, with your app's routes, module graph, providers, database schema, read-only SQL, logs, configuration and docs matched to your Navree version.

On this page

Introduction

A coding agent that reads your source files still has to guess at what the running app looks like: which routes exist after every module is wired, what the deals table really has in it, what failed in the last request. Navigator answers those questions from the app itself. It is an MCP server built into the Navree CLI, started with one command:

Shell

Connect your agent to it once, and it can ask your app:

ToolAnswers
app_infoNavree and Rust versions, the facade features, the modules, the database driver
list_routesEvery route with its method, path, access rule, handler and module
module_graphModules with their imports, exports, providers and commands
list_providersProviders with their scope, module, visibility and dependencies
list_commandsThe app's commands, as navree list shows them
describe_schemaTables, columns, indexes, foreign keys and migration status
query_databaseThe rows of a read-only SQL query
read_logsRecent log lines, filtered by level, target or request id
last_errorThe most recent error, with its request and the log lines around it
read_configThe merged configuration, secrets redacted
search_docs, read_docThese docs, for the Navree version your app uses

When the app includes Lighthouse (a file under src/ adds LighthouseModule, as navree new apps do), Navigator adds its five read tools (lighthouse_list_issues, lighthouse_get_issue, lighthouse_slow_routes, lighthouse_slow_queries and lighthouse_failed_jobs), reading Lighthouse's tables in your local database. See Lighthouse's tools.

Everything is read-only, and Navigator runs only on your machine, over stdio, for the project you start it in. It is for development; your production binary doesn't contain it. The code lives in the navree-navigator crate, which the CLI runs; the facade re-exports it as navree::navigator behind the opt-in navigator feature, for tools and tests.

Connecting your agent

Navigator is a stdio server: your agent starts navree mcp as a child process in your app's directory. Install the CLI first, then register Navigator with your agent.

From your app's directory:

Shell

To share the setup with your team, add it at project scope instead. Claude Code writes .mcp.json, which you commit:

Shell

Any other MCP client that launches stdio servers works the same way: the command is navree, the arguments are ["mcp"]. When a client doesn't start servers in your project's directory, name the project:

Shell

Ask your agent "Which Navigator tools do you have?" to check the connection. In Claude Code, /mcp lists the server and its tools.

How it works

Navigator runs next to your app, not inside it:

  1. It finds the app: the nearest Cargo package from the directory it starts in (or --path), and the navree version in its Cargo.lock.
  2. For the module graph, providers and commands, it builds your app with cargo build --message-format=json (the debug profile, sharing your target/ directory) and runs the binary with introspect --json. That built-in command validates the module graph without constructing anything, as navree check does, and prints it as JSON.
  3. For routes, it runs the binary with routes --json, which boots the app without serving traffic, as navree routes --json does. Navigator adds APP_DATABASE__MIGRATE_ON_BOOT=false and APP_LOG__LEVEL=error to that process's environment, so asking never migrates your database and adds nothing to your log but errors. Answers are cached until cargo build produces a new binary, that is until a source file changes.
  4. For the database, it reads your configuration exactly as the app does (.env, config/app.toml, APP_* variables, plus the settings of your dock.toml services, as navree dev passes them) and connects to database.url itself, read-only. For pending migrations it runs the binary with migrate:status --json.
  5. For logs, it reads the log file your app writes (log.file).
  6. For the docs, it searches the documentation of the Navree version in your lockfile. Navigator ships with the docs of its own version; for another version it downloads https://navree.com/v/<version>/docs.json once and caches it under ~/.navree/docs/<version>/ (NAVREE_HOME replaces ~/.navree, and NAVREE_DOCS_URL replaces https://navree.com, for a mirror).

A build that fails doesn't stop the other tools: list_routes and friends return the compiler's first error, which is often what the agent needed to see, while describe_schema, read_logs and search_docs keep working.

The tools

app_info

The first call an agent makes in a new project. It returns the package and its path, the Navree version from Cargo.lock, the Rust toolchain (rustc --version in the app's directory), the facade features your Cargo.toml turns on (with default when default features are on), the modules in boot order, the database driver with its URL (password masked) and the configuration files that exist (.env, .env.example, config/app.toml, dock.toml).

list_routes

Every route of the booted app, with the information of navree routes --json: method, path, name, access rule (public, auth, can:read Deal from id), the module, the handler (#[controller] records the controller method) and the route's throttle, timeout, body limit and audit action. Arguments filter by path prefix, method or module.

list_routes, path /api/v1/deals

An agent that checks the route table before adding a route doesn't create a second GET /deals/{id}, and sees the access rule the neighbors use.

module_graph and list_providers

The module graphAppModule, the root, imports the feature modules and the platform modules. DealsModule registers DealService as a provider and DealsController as a controller, exports DealService, and imports CompaniesModule to use the CompanyService it exports. The platform modules (ConfigModule, DatabaseModule, HttpModule, AuthModule, QueueModule) are global: any module can use their exports without importing them, as DealsModule does with DatabaseModule. The kernel walks this graph from the root at boot and checks every edge before it builds anything.Feature modulesPlatform · globalAppModulethe root · src/app.rsDealsModuleexports DealServiceCompaniesModuleexports CompanyServiceConfigModuleDatabaseModuleHttpModuleAuthModuleQueueModuleimportsimportsuses global exports
The module graph
  • imports or uses

module_graph returns the graph navree check --graph prints, as data: each module in dependency order (the implicit root App, which imports your root modules, comes last), whether it is global, what it imports and exports, its providers (controllers among them) and its commands. With a module argument it returns one module. list_providers lists providers across the app with their type, scope (singleton, request, transient), owning module, whether the module exports them, and their dependencies.

This is what an agent needs to wire a new service correctly: where DealService lives, that DealsModule exports it, and that a new module using it must import DealsModule. When the graph doesn't validate, both tools return the kernel's error, which names the module and the missing type.

list_commands

The app's commands with their one-line help, as navree list shows them: the built-ins (check, help, introspect), then what the agent can run, such as migrate, queue:work or your own crm:task-reminders, each with the module that registers it.

describe_schema

The database as it is, not as the code says it should be: tables, columns with their types, nullability, defaults and primary keys, indexes and foreign keys. It also lists the migrations that have run (the rows of navree_migrations) and those that haven't (from migrate:status --json), so the agent knows when a column it sees in an entity doesn't exist yet. When the app can't answer (it doesn't build), pending says why. A table argument returns one table.

query_database

Runs one read-only SQL statement against your development database and returns at most 100 rows:

query_database
JSON

truncated is true when the statement had more rows than the 100 returned. The limits below keep it read-only. Values in columns whose names contain password, secret, token, api_key or private_key come back as [redacted].

read_logs and last_error

read_logs returns recent lines of the log file your app writes, newest last, with the file it read. Arguments filter by level (the lowest level: warn returns warnings and errors), target (navree::job also matches navree::job::worker), request_id, since (an RFC 3339 time, or a duration back from now: 30s, 15m, 2h, 1d) and limit (default 100, at most 1000). Navigator reads JSON and pretty lines alike, but JSON keeps every field, and it reads rotated files (navree.2026-10-27.log) newest first.

last_error returns the most recent error line, its request id, the request's method, route and status (from its request completed line), and every other line of the same request: the error chain, the slow queries before it. "Why did that request fail?" is one call.

Both need the app to write a log file, which is off by default. Turn it on for development:

config/app.toml

Navigator reads log.file.path from your configuration, so a different path works too. Without a log file, both tools say how to turn it on.

read_config

The configuration tree your app sees, merged from .env, config/app.toml and APP_* variables (plus your dock.toml services' settings), with secrets redacted the way Config::redacted() does it: any key containing password, secret, token, key, authorization or cookie, and the password in any URL. A key argument returns one section or value (queue, database.url).

search_docs and read_doc

search_docs searches these docs for the Navree version in your Cargo.lock and returns the best sections with an excerpt each, their address on navree.com and the slug read_doc takes. read_doc returns one page as Markdown: give it a slug (basics/validation), a /docs/... path or a navree.com URL, and optionally a section (its heading or anchor) to read only that part. An agent working on an app pinned to an older Navree reads the docs of that version, not of the latest release, so the APIs it finds are the APIs it has. When those docs can't be had (offline and not cached), Navigator answers from the docs it ships with and says so in a note.

A version's docs are one JSON file, docs.json: {"version": "0.1.0", "pages": [{"slug": "ai/navigator", "title": "Navigator", "description": "...", "planned": false, "sections": [{"heading": "Options", "anchor": "options", "markdown": "..."}]}]}, each page split at its ## headings, the site's components written as plain Markdown.

Lighthouse's tools

When a file under your app's src/ adds LighthouseModule, Navigator lists five more tools. They read Lighthouse's tables (lighthouse_issues, lighthouse_occurrences, lighthouse_aggregates, lighthouse_alerts) in the database Lighthouse writes to: lighthouse.database_url when it is set, otherwise database.url, with the same rules as query_database (local databases only unless --allow-remote-database, read-only, 5 seconds). Your app doesn't have to be running, and nothing needs an access token: the data is your local database's.

ToolArgumentsAnswers
lighthouse_list_issuesstatus (open, the default, resolved, resolved_in_next_release, ignored or all), kind, level, search (text in the title or culprit, any case), sort (last_seen, the default, first_seen or count), limit (default 20, at most 100)Issues, most recently seen first: id, kind, level, status, title, culprit, count, first_seen, last_seen, last_release, environment
lighthouse_get_issueidThe issue with its message, source chain, location, error_type, releases and resolution; its five latest occurrences (time, request id, method, route, path, status, user, workspace, release, backtrace and context); the alerts sent for it
lighthouse_slow_routesperiod (1h, 24h, the default, or 7d), limit (default 10, at most 50)Routes, slowest p95 first: endpoint, requests, errors, error_rate, avg_ms, p50_ms, p95_ms, p99_ms, max_ms
lighthouse_slow_queriesperiod, limitNormalized statements by total time: statement, count, errors, total_ms, avg_ms, p95_ms, max_ms
lighthouse_failed_jobsperiod, limitJob types with failed attempts, most failures first (job_type, attempts, failed_attempts, failed_jobs, avg_ms, p95_ms), and the open failed_job issues

The figures are the dashboard's: Navigator merges the per-hour rows of every process (per-minute rows for 1h) and their latency histograms, so a p95 covers every recorded request, not a sample. When the tables don't exist yet, the tools say to run navree migrate. To change an issue (resolve, ignore, reopen), use the dashboard or Lighthouse's JSON API; Navigator only reads.

Working with Navigator

Agents use tools they know about. Two things help them reach for Navigator at the right moments:

  • Skills. The agent skills tell the agent what to check before a task: the route table before adding a route, the module graph before wiring a provider, the schema before writing a migration. The navree-navigator skill covers Navigator itself.
  • AGENTS.md. The AGENTS.md that navree new writes describes the project's conventions. Add a line about Navigator if your team relies on it, for example "Use the navree MCP tools to check routes, providers and the schema before changing them".

A typical exchange, in Claude Code:

Text

Options

OptionWhat it does
--path <dir>The app to serve, instead of the current directory.
--no-buildNever run cargo build; use the last built binary (target/debug/<app>), or answer without one.
--allow-remote-databaseLet query_database, describe_schema and Lighthouse's tools connect to a database that isn't on this machine.
--docs-offlineNever download docs; use the cache, or the docs Navigator ships with (and say so).

Security

Navigator gives a model a view into your app, so it is built to show and never to change.

  • Development only. It runs on your machine, as you, over stdio. It opens no port, and it isn't part of your app's binary, so nothing you deploy contains it.
  • Read-only. No tool writes anything. query_database parses the statement before sending it and refuses anything but one SELECT, WITH, EXPLAIN or SHOW: writes, DDL, PRAGMA, ATTACH, SET, transaction control, several statements, data-changing WITH clauses, EXPLAIN of a write, SELECT ... INTO, FOR UPDATE and FOR SHARE, and functions that act outside the transaction (pg_sleep, pg_terminate_backend, pg_read_file, advisory locks, dblink, lo_*, set_config, load_extension, ...). What is sent is the statement as parsed, never the raw text. It then runs read-only: on PostgreSQL in a session whose transactions default to read-only, inside BEGIN READ ONLY with a 5 second statement_timeout, through a cursor that fetches at most 101 rows, then rolled back; on SQLite on a connection with PRAGMA query_only on, interrupted after 5 seconds. At most 100 rows come back.
  • Local databases. Unless you pass --allow-remote-database, Navigator only connects to SQLite files and to databases on localhost, a loopback address (127.0.0.1, ::1) or a Unix socket. The same goes for lighthouse.database_url. A database.url pointing at a server elsewhere, such as a staging database in a .env you forgot to change, is refused with a message that says why. Navigator never creates a SQLite file that doesn't exist. It reads SQLite and PostgreSQL databases; other drivers are refused.
  • Asking the app. routes --json and migrate:status --json boot your app the way navree routes does, with migrate_on_boot turned off for that process; introspect --json constructs nothing.
  • Secrets redacted. Configuration is redacted like Config::redacted(), query results mask secret-looking columns, and logs carry what your app's log.redact already left out.
  • What leaves your machine. Tool results go to the model your agent uses, like any file the agent reads. Keep customer data out of your development database: seed it with factories instead of copying production. Navigator itself only reaches the network to download the docs of your Navree version once.