Index a pnpm / nx / turbo monorepo
One database, one CLI, one MCP server. Multiple workspaces. Here is the recipe that keeps every package indexable while staying fast on subsequent runs.
Layout
my-monorepo/ apps/ web/ cli/ packages/ shared/ ui/ pnpm-workspace.yaml package.json nci.config.json
nci.config.json
{ "project_root": ".", "workspaces": ["apps/*", "packages/*"], "package_scope": ["dependencies", "dev_dependencies"], "max_hops": 10 }
That config does three things:
- Scans the root
node_modulesplus every workspace’snode_modules. - Includes both
dependenciesanddev_dependenciesso test/build types are indexed too. - Lets you query across the whole tree from any package.
Index incrementally
--package accepts SQLite-style globs (repeatable or comma-separated, e.g. -p @my-org/pkg-a,@my-org/pkg-b). Use it to re-index only the workspace packages you control after a refactor — third-party dependencies stay cached.
Skip the root install
If your monorepo hoists everything into node_modules at the workspaces but keeps the root empty:
That tells the scanner to ignore <project_root>/node_modules and only walk the listed workspaces. Conflicts with --include-root-workspace (the override that forces the root in even when nci.config.json excluded it).
Stub noisy dependencies
When you index several workspace packages in one nci index, NCI automatically stubs consumer imports that target another package in the same run. Dependency ids use the batch form npm::<specifier>@<version>::<member> (for example npm::@my-org/shared@1.2.0::SharedConfig). The provider row keeps the full type surface. Details: Indexing · Batch crawl dedupe.
The rest of this section is for manual stubs (npm::<specifier>::<member>, no @version): external or heavy dependencies you want skipped even when they are not a sibling workspace package in that batch (for example zod from npm).
Some dependencies show up everywhere. zod lives in nearly every workspace package, and almost every file does import { z } from "zod". Its types are deep generic chains — z.object({...}).strict().refine(...) builds large inference trees that NCI has to walk on every consumer file. You almost never query zod’s internals; you just want NCI to know your packages reference it.
Tell NCI not to crawl into zod. Every consumer-side import collapses into a single stub edge:
The flag (short: -s) is repeatable or comma-separated (-s zod,@aws-sdk/client-s3) and accepts bare names or scoped specifiers. Each consumer-side import becomes an npm::<specifier>::<member> edge — e.g. import { z } from "zod" resolves to npm::zod::z. Fast to write, queryable as evidence, never parsed.
Make it sticky by setting dependency_stub_packages in nci.config.json:
{ "project_root": ".", "workspaces": ["apps/*", "packages/*"], "package_scope": ["dependencies", "dev_dependencies"], "dependency_stub_packages": [ "zod", "@aws-sdk/client-s3", "googleapis" ] }
The CLI value is unioned with the config — CI can add more stubs without rewriting the file. Drop a name from the config and the next index re-parses it in full.
Wire up the agent
Pair this walkthrough with any client setup page — Claude, Cursor, Codex, Antigravity, or OpenCode. The MCP server reads the same nci.config.json and resolves to the same nci.sqlite regardless of client.