Ninchi CLI and MCP server
Run Ninchi checks from a terminal, Git hook, CI job, or MCP-capable editor. The package is a thin HTTP client: it does not import the Ninchi backend or question engine.
A programmatic check records evidence of demonstrated understanding tied to the submitted artifact. It is a separate evidence class from pull-request verification, not a measurement of latent ability, mastery, or competency.
Install
Install the published package from PyPI:
pipx install ninchi-cli
Use ninchi-cli 0.2.1 or later. Version 0.2.0 predates an API change in how
a check reports that its question is still being generated. Against the
current API, 0.2.0 shows an empty question instead of waiting for it. Check
the installed version with pipx list and upgrade with
pipx upgrade ninchi-cli.
Development install from a checkout
From the repository root:
pipx install ./cli
For editable development with test dependencies:
cd cli
python3.12 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -q
The package requires Python 3.11 or newer.
PATH and GUI editors
After the verified public pipx install ... command:
pipx ensurepath
Close and reopen the shell, then verify:
command -v ninchi
ninchi --help
If an MCP client reports the literal error spawn ninchi ENOENT, it cannot
find the executable. GUI editors often do not inherit the PATH used by an
interactive shell. Run command -v ninchi (or which ninchi) in a terminal
and use the printed absolute path in the MCP configuration.
Prerequisites
GitHub is not required. A check is submitted directly to an organization. It needs no repository, pull request, or source-control connection.
- Have a Ninchi account. Any sign-in method works: email and password (with optional TOTP MFA), your organization's SAML single sign-on, or a GitHub, GitLab, or Bitbucket account.
- Join the target Ninchi organization. To start an organization without a
source-control connection, create a Direct organization at
/dashboard/create-organd invite members by email. GitHub, GitLab, and Bitbucket organizations work the same way for programmatic checks. - Find its ID in the dashboard URL — the segment after
/dashboard/orgs/. Both identifier forms work: a numeric id (/dashboard/orgs/42→--org-id 42) or the org's opaque UUID (/dashboard/orgs/1b671a64-…→--org-id 1b671a64-…). New dashboard URLs use the UUID form; numeric ids in existing configs keep working indefinitely. The CLI does not currently have an org-list command. - Ask an org admin to open org settings, choose the Challenges tab, find the Programmatic Checks (CLI & MCP) card, and turn on Enable CLI & MCP check submission. The same card sets Answers submitted by AI agents, which defaults to blocked (see Answering-actor semantics).
- Make sure the organization's plan includes programmatic checks. The Hobbyist / Free plan has a quota of zero, including for a new Direct organization.
Programmatic checks have a hard quota per developer per org in a trailing 30-day window. The limit comes from the org's plan, and checks you submit to one org do not count against another org's quota. A developer who belongs to two orgs has a separate allowance in each.
| Plan | Checks per developer, per org |
|---|---|
| Hobbyist / Free | 0 |
| Standard | 20 |
| Pro | 100 |
| Enterprise | 100+ (operator-configured, never below 100) |
When the quota is exhausted, the API returns HTTP 429. On a plan that allows some checks, the response also states the UTC time when the next check becomes available. At that time, enough checks have left the window to make room for another check. A plan downgrade can leave you above the cap. Several checks must then leave the window, and the stated time moves later.
A zero-allowance plan never frees a slot, so the response carries no reset time. Waiting does not help. The org needs a plan with a quota above zero.
Authentication
Device login
ninchi login
The CLI:
- prints a verification URL and a code such as
XXXX-XXXX; - waits while you sign in and approve the code at
/dashboard/device; and - saves the issued personal access token to
~/.config/ninchi/credentials.
The credentials file is mode 0600; its parent directory is mode 0700.
NINCHI_CONFIG_DIR overrides the directory. Otherwise the CLI honors
XDG_CONFIG_HOME before falling back to ~/.config/ninchi.
Login flags:
--timeout SECONDS approval timeout (default: 600)
--api-url URL API override (default: https://api.ninchi.ai)
--json machine-readable output
CI token
CI should use an environment variable rather than the credentials file:
export NINCHI_API_TOKEN="ninchi_pat_..."
export NINCHI_ORG_ID=42
Create and revoke PATs at /dashboard/profile. A token is shown once, is
stored by Ninchi as an HMAC-SHA256 digest, and stops working immediately after
revocation. The available scopes are:
checks:read— read challenges, results, and personal history.checks:write— submit checks and answers.
Device login requests both scopes by default. Manually created tokens also default to both scopes.
NINCHI_API_URL or --api-url can target another API environment.
CLI usage
ninchi check
With no source flag, check submits the working-tree diff from
git diff HEAD:
ninchi check --org-id 42
Choose exactly one alternate subject source:
git diff | ninchi check --stdin --org-id 42
ninchi check --staged --org-id 42
ninchi check --commit HEAD --org-id 42
ninchi check --file src/app.py --language python --org-id 42
Set the org once for subsequent commands:
export NINCHI_ORG_ID=42
ninchi check --staged
All check flags:
--stdin
--staged
--commit SHA
--file PATH
--language LANGUAGE
--org-id ID
--answer TEXT
--actor human_attested|agent_assisted|agent_autonomous
--poll-interval SECONDS
--timeout SECONDS
--api-url URL
--json
--language is a hint for --file. --poll-interval defaults to 2 seconds;
--timeout defaults to 600 seconds.
Automation can answer non-interactively and emit JSON:
ninchi check --staged \
--answer "$ANSWER" \
--actor agent_assisted \
--json
If --json is used without --answer, the CLI returns the generated
challenge and exits 2 with guidance to pass an answer.
Answering-actor semantics
- An answer typed interactively defaults to
human_attested. --answer, or a process with no terminal, defaults toagent_assisted.--actoroverrides the default. Use the value that honestly describes who authored the answer:human_attested: the human wrote the answer.agent_assisted: a human meaningfully directed the answer's content.agent_autonomous: an agent authored the answer.
The server enforces the organization's agent-answer policy. Never relabel an answer to bypass that policy.
ninchi result
ninchi result CHALLENGE_ID
ninchi result CHALLENGE_ID --json
ninchi history
ninchi history
ninchi history --limit 50
ninchi history --limit 50 --json
The default limit is 20. The server accepts limits from 1 to 100.
Exit codes
0: challenge passed.1: challenge failed.2: pending state, configuration error, authentication error, API error, or another operational error.
Pre-push hook
Example .git/hooks/pre-push:
#!/usr/bin/env bash
set -euo pipefail
base="${NINCHI_BASE_REF:-origin/main}"
git diff --patch "$base"...HEAD | ninchi check \
--stdin \
--org-id "${NINCHI_ORG_ID:?set NINCHI_ORG_ID}"
Make it executable:
chmod +x .git/hooks/pre-push
This form is human-attested: it omits --answer and prompts in the terminal.
CI
The CLI needs only two environment variables, NINCHI_API_TOKEN and
NINCHI_ORG_ID, so it runs in any CI system: GitLab CI, Bitbucket
Pipelines, Jenkins, and others. The GitHub Actions job below is one example.
The same pipeline works elsewhere: install with pipx, pipe the diff against
the target branch into ninchi check --stdin --json, and store the token as a
masked secret.
GitHub Actions
name: Ninchi check
on:
pull_request:
jobs:
ninchi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pipx install ninchi-cli
- name: Submit Ninchi check and surface the challenge
run: |
git diff --patch origin/${{ github.base_ref }}...HEAD |
ninchi check --stdin --json | tee ninchi-challenge.json || true
env:
NINCHI_API_TOKEN: ${{ secrets.NINCHI_API_TOKEN }}
NINCHI_ORG_ID: ${{ vars.NINCHI_ORG_ID }}
Store the PAT in GitHub Secrets. This job stops at submission by design: with
--json and no --answer, the CLI prints the challenge JSON — including the
question — and exits 2 (the || true keeps the step green). The answer must
come from whoever authored the change; do not wire a canned or scripted answer
into CI. The question is generated per submission, and the answer window (the
org-configured TTL) starts once the challenge is first viewed — so have authors
answer interactively where they work, such as the pre-push hook above or an
MCP-capable editor.
MCP server (ninchi mcp)
Authenticate once with ninchi login, then configure the stdio server in an
MCP-capable client.
Recommended: absolute path
Run:
command -v ninchi
Use that output as command:
{
"mcpServers": {
"ninchi": {
"command": "/absolute/path/from/command-v-ninchi",
"args": ["mcp"]
}
}
}
Cursor accepts this shape in .cursor/mcp.json or its global MCP settings.
Other MCP clients use the same command and argument, though their config-file
location differs.
Alternative when the editor inherits PATH
{
"mcpServers": {
"ninchi": {
"command": "ninchi",
"args": ["mcp"]
}
}
}
If this produces spawn ninchi ENOENT, use the absolute-path form.
Five MCP tools
| Tool | Purpose |
|---|---|
submit_check |
Submit exactly one diff, content block, or file region and start question generation. |
get_challenge |
Fetch the question, optionally waiting while generation finishes. |
submit_answer |
Submit an answer with a required, honest actor claim. |
get_result |
Fetch score and feedback, optionally waiting while evaluation finishes. |
list_history |
List the authenticated developer's recent challenges. |
submit_answer requires actor. When the org policy is blocked, the tool
returns guidance to hand the question to the human and relay only the human's
own words as human_attested. When the policy is recorded,
agent_assisted and agent_autonomous answers are accepted and stored with
that provenance.
MCP check submissions include:
{
"client_attribution": "mcp:<client>"
}
This names the submitting channel and feeds AI-authorship analytics as a
detection-floor signal. It does not, by itself, assert that AI assisted the
submitted work. When the client name is unavailable, the server uses mcp.
Evidence and score semantics
Programmatic checks use origin="api" and are stored as a distinct,
lower-weight evidence class. Their question, answer, score, artifact context,
answering-actor claim, client attribution, and timestamps remain inspectable.
By default, this evidence does not count toward Ninchi Scores. An org admin may opt in with Count programmatic checks toward Ninchi Scores in the Programmatic Checks card. Pull-request evidence is unaffected either way.
Troubleshooting
| Symptom | Resolution |
|---|---|
spawn ninchi ENOENT |
The editor cannot find the executable. Run pipx ensurepath, open a new shell, run command -v ninchi, and use the resulting absolute path in the MCP config. |
| 403: programmatic checks are not enabled | An org admin must enable Enable CLI & MCP check submission in Programmatic Checks (CLI & MCP) on the Challenges tab of org settings. |
| 429 | The trailing-30-day quota for this developer in this org is exhausted. When the plan allows some checks, the response gives the UTC time the window next frees a slot. On a zero-allowance plan (Hobbyist / Free) there is no reset time to report; the org needs a plan that includes programmatic checks. |
| 403 on an agent answer | The org's programmatic_agent_answers policy is blocked. Hand the question to the human; submit only their own words as human_attested. |
| Challenge expired | The answer window starts when a ready challenge is first viewed. The org configures the TTL; the shipped default is 5 minutes. |
Status generating or answered |
Generation and grading are asynchronous. generating means the question is not ready; answered means grading is still running. Keep polling get_challenge or get_result until a terminal result or client timeout. The MCP tools report a timed-out wait as generating or evaluating. |
| Empty question, then an error on answer | You are running ninchi-cli 0.2.0, which does not wait for generation against the current API. Run pipx upgrade ninchi-cli. |
CLI errors are printed to stderr, or as JSON with --json, and exit 2.
Metadata
Release files for ninchi-cli 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ninchi_cli-0.2.1.tar.gz | 31.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ninchi_cli-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 51.0 kB
Release files / ninchi_cli-0.2.1.tar.gz
| Download URL | ninchi_cli-0.2.1.tar.gz |
|---|---|
| Size | 31.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d9e276a358ec3819750d77ea7f182e0639718c3fe784b83bb083b7438fc8f767
|
|
BLAKE2b-256 checksum How to use checksums |
e3c84aedf9ccb7790f8ae43853c51f71ff4f5a7d8dbdbc4e60a5a3d013d98d86
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / ninchi_cli-0.2.1-py3-none-any.whl
| Download URL | ninchi_cli-0.2.1-py3-none-any.whl |
|---|---|
| Size | 20.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
36f4dfe45dcc31caeb617efe40d46b5b1c2b9325c0539b16d79a0d4e631b65f4
|
|
BLAKE2b-256 checksum How to use checksums |
321d7409071ccfda0ae330b1874f6359fba20dfe2a5c2390180eeece2519d2e5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log