Skip to main content
NDI speaks Model Context Protocol, so an agent can call parse, extract, search, and the rest as tools. Hosted and local servers expose the same catalog. Prefer uvx ndi-mcp (or pip install ndi-mcp). The older ndi-sdk[mcp] extra still resolves to that package.

Hosted

Paste this into the agent’s MCP config. Mint the key on the API keys page; do not commit it.
For another NDI deployment, use its API host: <NDI_BASE_URL>/v1/mcp, with no trailing slash. The server also accepts the key as Authorization: Bearer ndi_sk_.... The hosted server runs in our cloud, so it cannot see your filesystem. upload_document and upload_file refuse a local path there. Pass a public URL, or run the local server.

Local

ndi-mcp login opens the NDI console in your browser. Approve there; the CLI stores the minted key in ~/.ndi/config.toml. Pass --api-key (or set $NDI_API_KEY) to skip the browser. Cursor — add to .cursor/mcp.json:
opencode — add to opencode.json:
$NDI_API_KEY is read first; otherwise the key saved by ndi-mcp login. Local files go through upload_document, then parse_document / extract_data with the returned upload_id.

Tools

Same catalog on hosted and local. Names map to the Python SDK: Ask get_documentation with a topic (parse, extract, auth, workspaces, …) before writing integration code. To decide between automatic_search, qa, je_testing, qa_file, and query_tables for a question, follow Choose the right tool. deep_search and fact_search are deprecated: use qa or automatic_search instead of deep_search, and automatic_search with mode="fact" instead of fact_search. Both keep working until they are retired. start_ledger_understanding measures the ingested journal package; get_ledger_understanding reads the latest report. list_ledger_sources, select_ledger_sources, and get_ledger_selection confirm a source selection; pass the returned selection_id to start and get. Without force, an unchanged package republishes the stored report. A run already in flight returns 409 ledger_understanding_in_progress. A failed tool call returns an error result (isError: true) whose text is <code>: <message>, such as unauthorized: …. A refused request (invalid_request) adds one - <field>: <message> line per refused field, up to five, without repeating the values you sent. Some arguments are checked before any call; for example, evaluate_systemone refuses an unknown question type with the validation library’s message. After automatic_search, qa, or je_testing succeeds, poll every grounding job id with wait_for_job. For automatic_search, those ids are on result.output when fact search answered (or a stored deep route), and an agent answer’s are on result.output.answer when present; for qa, on result.answer when present; for je_testing and the deprecated search tools, on result.grounding_job_ids. Do not call ground_items for those evidences.

Next steps