<p align="center">
  <a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
</p>

<p align="center">
  <img src="https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos/ollama-intern-mcp/readme.png" alt="Ollama Intern MCP" width="500">
</p>

<p align="center">
  <a href="https://github.com/mcp-tool-shop-org/ollama-intern-mcp/actions"><img alt="CI" src="https://github.com/mcp-tool-shop-org/ollama-intern-mcp/actions/workflows/ci.yml/badge.svg"></a>
  <a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
  <a href="https://mcp-tool-shop-org.github.io/ollama-intern-mcp/"><img alt="Landing Page" src="https://img.shields.io/badge/landing-page-8b5cf6"></a>
  <a href="https://mcp-tool-shop-org.github.io/ollama-intern-mcp/handbook/"><img alt="Handbook" src="https://img.shields.io/badge/handbook-docs-10b981"></a>
</p>

> **The local intern for Claude Code.** <!-- TOOL_COUNT:start -->44<!-- TOOL_COUNT:end --> job-shaped tools, evidence-first briefs, durable artifacts.

An MCP server that gives Claude Code a **local intern** with rules, tiers, a desk, and a filing cabinet. Claude picks the _tool_; the tool picks the _tier_ (Instant / Workhorse / Deep / Embed); the tier writes a file you can open next week.

**Also drives [Hermes Agent](https://github.com/NousResearch/hermes-agent) on `hermes3:8b`** — validated end-to-end 2026-04-19. The default ladder is `hermes3:8b`; `qwen3:*` is the alternate rail. See [Use with Hermes](#use-with-hermes) below.

**Hardware requirements:** ~6 GB VRAM for `hermes3:8b`, or ~16 GB RAM for CPU inference. See [handbook/getting-started](https://mcp-tool-shop-org.github.io/ollama-intern-mcp/handbook/getting-started/#hardware-minimums) for the full breakdown.

**Not using Claude?** The [`examples/`](./examples/) directory has a minimal Node.js and Python MCP client you can spawn over stdio. See also [handbook/with-hermes](https://mcp-tool-shop-org.github.io/ollama-intern-mcp/handbook/with-hermes/).

**Local-first** — zero network egress until you opt in. No telemetry. No "autonomous" anything. Every call shows its work. Optional [Ollama Cloud](#ollama-cloud-optional) routing puts 600B-class models behind the same tools when local hardware is the bottleneck — with automatic fallback to local.

---

## New in v2.9.0

**The cloud feature pass — a cross-family verification lane, on-demand cloud escalation, and the economics to see it.** Local-first is unchanged: with no key set, behavior is byte-identical to v2.8.0 (zero egress, no startup cloud probe).

- **`ollama_verify_claims` — cross-family verification.** `ollama_code_review` *generates* findings; this *adjudicates* them. It runs a disjoint-family Ollama Cloud flagship panel (deepseek / kimi / glm by default) over your claims + evidence and returns per-claim CONFIRMED / REFUTED / NEEDS_REVIEW. Aggregation is lone-dissent-never-decides (≥2 to refute, ≥2 to confirm), every juror is served-model-verified (a local fallback or substituted model is excluded, never counted), and claim inputs are structurally reasoning-stripped. The honest ceiling is documented: a CONFIRMED is supporting evidence, not proof — reliable at flagging gross errors, weaker on a frontier model's subtle ones.
- **Per-call cloud escalation + standby mode.** Set `OLLAMA_API_KEY` *alone* (without `OLLAMA_CLOUD_PRIMARY`) and you're in **standby**: local-primary, zero egress, no startup probe — until a single call opts in with `backend:'cloud'`. Escalate one high-stakes review to a 600B model without flipping every call to cloud. The first escalation discloses egress loudly at the point it happens; a per-call `model` override now rides the cloud attempt verbatim.
- **`ollama_log_stats` — the measured economics the tagline promises.** A no-LLM rollup of your NDJSON receipts: cloud/local split, cloud→local fallback rate, tokens per tool, p50/p95 latency, bounded by a `since` window.
- **Doctor for CI + machine-readable tools.** `doctor --json --fail-unhealthy` gives pipelines a real gate (with a cloud-aware `healthy` flag), and every tool now carries MCP `readOnlyHint`/`destructiveHint`/`title` annotations so clients get correct permission UX. Plus `init --claude` scaffolds a paste-ready `.mcp.json`.

Full detail in [CHANGELOG.md](./CHANGELOG.md).

## New in v2.8.0

**Reliability, durability, and security hardening — 25 fixes, every one test-first and cross-family-verified.** Local-first behavior is unchanged and no tool contract was removed; existing callers keep working. The load-bearing wins:

- **No more silent corpus data-loss.** A transient read error during `ollama_corpus_refresh` (a Windows file lock, an antivirus hold, an editor's save window) used to classify the file "missing" and **permanently delete its indexed content**. Now only a genuinely-absent file is dropped; a transient error keeps the path, flags it for retry, and preserves its chunks.
- **Concurrency that honors its budgets.** A tier timeout can now cancel a call still queued for a permit (it used to hang far past budget while receipts claimed otherwise), and `ollama_chat` finally routes through the timeout/tier seam — so one wedged local generation can't stall every tool, and it actually reaches cloud in cloud-primary mode.
- **Cloud that degrades instead of dying.** A retired cloud-model id now falls back to local with a clear `cloud_model_missing` reason and a cloud-specific hint instead of a total outage; the circuit breaker can't wedge permanently; a persistently-missing model stops paying a cloud round-trip on every call.
- **Security surface that matches its docs.** `ollama_batch_proof_check` now really enforces cwd containment (with a new operator env cap `INTERN_BATCH_PROOF_ALLOWED_ROOTS` a caller can't widen), the prompt-injection sanitizers gained coverage + an honestly-disclosed ceiling, and the protected-path guard is case-insensitive on macOS too.
- **Honest artifacts & receipts.** Pack writes are atomic and never silently clobber; degraded batch envelopes report the tier actually used; the interrupted-write detector catches torn writes on any mutation; chunk IDs no longer collide across identical-content files. Dependency audit is fully clear (0 vulnerabilities).

Full detail in [CHANGELOG.md](./CHANGELOG.md).

## New in v2.7.0

**Optional Ollama Cloud routing — cloud-primary, local-fallback.** Opt in with a key + a flag and the generative tiers route to a 600B-class cloud model; embeddings stay local; a circuit breaker falls back to your local profile on any cloud failure. **Off by default — zero egress unless you set both `OLLAMA_API_KEY` and `OLLAMA_CLOUD_PRIMARY=1`.** Additive minor — pre-v2.7.0 callers (and anyone not opting in) see byte-identical behavior. See [Ollama Cloud (optional)](#ollama-cloud-optional).

- **Cloud-primary with a safety net.** A `RoutingOllamaClient` tries cloud first and falls back to the local profile on timeout / 5xx / 429 / network. Bad keys (401/403) surface loudly via a sticky breaker instead of degrading silently forever; a retired/typo'd cloud model id (404) surfaces too.
- **Never a silent downgrade.** Every envelope gains `backend` (`cloud`|`local`), `degraded`, and `degrade_reason` so you always know when you got the local model instead of the big one. A `backend_fallback` NDJSON event makes the cloud→local fallback rate visible in `ollama_log_tail`.
- **`ollama_doctor` reports cloud auth + reachability** as a distinct block; `ollama-intern-mcp doctor` shows a `Cloud (primary)` section.
- Default cloud model was `minimax-m3:cloud` at v2.7.0 release *(since repinned to `qwen3-coder-next:cloud` — a thinking default returned empty replies on capped-`num_predict` tools; see the [env table](#cloud-env-vars))*; override per-tier with `INTERN_CLOUD_MODEL` / `INTERN_CLOUD_DEEP_MODEL`.

## New in v2.6.0

Per-call tier-budget override on `ollama_extract`. Additive minor — pre-v2.6.0 callers unchanged. Detailed entry in [CHANGELOG.md](./CHANGELOG.md).

- **`tier_budget_ms_override?: number` schema field on `ollama_extract`** (optional, bounded `[1, 600000]` ms). When present, applies the override to every tier visited by the runner so the inner `runWithTimeoutAndFallback` machinery at `src/guardrails/timeouts.ts:61` honors the operator-supplied budget instead of the profile default. The cascade (workhorse → instant on timeout) still fires; the override governs each cascade hop uniformly.
- **Why this exists.** The research-os R-018 wrapper (v0.12.1) wrapped MCP `callTool` with `Promise.race` and found the wrapper's budget did not reach the inner tier — `DEV_RTX5080_TIMEOUTS.instant = 15_000` continued to fire `TIER_TIMEOUT` at 15000ms regardless of a 180000ms wrapper budget. v2.6.0 supplies the MCP-side authoritative budget so the operator's `--planner-timeout-ms` flag (research-os) finally controls inner-tier timeouts as designed.
- **Default behavior preserved.** Field omitted = profile defaults govern byte-identically. Pre-v2.6.0 callers see zero change.
- **R-010 fallback-cause regex preserved.** Server-side `TIER_TIMEOUT` error message still matches `/elapsed=(\d+)ms/` + `/budget=(\d+)ms/` so AI-advisor visibility downstream works on override and default paths alike.
- Consumed by research-os v0.13.0 (cumulative R-019 client wire-up + R-020 + R-021) in a coordinated multi-repo release.

### Historical — v2.4.0 deliverables

See [CHANGELOG.md](./CHANGELOG.md) and [docs/release-notes/v2.4.0.md](./docs/release-notes/v2.4.0.md) for the full v2.4.0 entry (per-tier `num_ctx` control on the profile system).

## New in v2.4.0

Per-tier `num_ctx` (context window) control on the profile system. Additive minor — v2.3.0 callers unchanged. Detailed entries in [CHANGELOG.md](./CHANGELOG.md) and [docs/release-notes/v2.4.0.md](./docs/release-notes/v2.4.0.md).

- **`TierConfig.num_ctx` map (new)** — optional `{ instant?, workhorse?, deep?, embed? }` on the profile. When set for a tier, the MCP server places `options.num_ctx = <value>` on every Ollama generate/chat request routed to that tier (initial + fallback). When unset, the request omits `num_ctx` entirely so Ollama uses its model-loaded default — v2.3.0 behavior preserved exactly.
- **New envelope field `num_ctx_used?: number`** — present only when the MCP server actually sent `num_ctx`. Absent when the request let Ollama choose. Do not infer a default — the MCP server does not query Ollama for the effective value.
- **Profile defaults**: `dev-rtx5080` / `dev-rtx5080-qwen3` ship with `instant: 4096`, `workhorse: 8192`, `deep`/`embed` UNSET. Sized to keep `hermes3:8b` resident in the RTX 5080's 16GB VRAM budget for fast tools. `m5-max` leaves every tier UNSET — 128GB unified memory has no spill problem.
- **Closes the v0.8.0 Phase 1 diagnostic** — `hermes3:8b` at the default 32K context on RTX 5080 spilled to CPU and started timing out workhorse `ollama_extract` calls. v2.4.0 prevents that at the profile layer.

### Per-tier `num_ctx` control (new in v2.4.0)

Profile (excerpt from `src/profiles.ts`):

```ts
"dev-rtx5080": {
  tiers: {
    instant: "hermes3:8b",
    workhorse: "hermes3:8b",
    deep: "hermes3:8b",
    embed: "nomic-embed-text",
    num_ctx: {
      instant: 4096,    // fast classify/summarize
      workhorse: 8192,  // schema-bound extract / batch
      // deep: UNSET — long-context briefs keep current behavior
      // embed: UNSET — no context-window pressure on embed
    },
  },
  // ... timeouts, prewarm
}
```

Envelope on a workhorse-tier call (e.g. `ollama_extract`):

```jsonc
{
  "result": { /* extracted data */ },
  "tier_used": "workhorse",
  "model": "hermes3:8b",
  "num_ctx_used": 8192,        // present because the profile set workhorse=8192
  // ... rest of envelope unchanged
}
```

On `m5-max` (or any profile that leaves a tier unset), `num_ctx_used` is absent from the envelope and the wire request to Ollama does not include the `num_ctx` field — Ollama uses its model-loaded default.

Operators tune by selecting / editing the profile; there is no per-call `num_ctx` input on tool schemas. If a future call surfaces the need, the pattern follows v2.3.0's `model` override.

### Historical — v2.3.0 deliverables

See [CHANGELOG.md](./CHANGELOG.md) and [docs/release-notes/v2.3.0.md](./docs/release-notes/v2.3.0.md) for the full v2.3.0 entry (per-call model override).

## New in v2.3.0

Per-call model override across LLM-backed atom tools. Additive minor — v2.2.0 callers unchanged. Detailed entries in [CHANGELOG.md](./CHANGELOG.md) and [docs/release-notes/v2.3.0.md](./docs/release-notes/v2.3.0.md).

- **Optional `model: string` input on 8 atom tools** — `ollama_extract`, `ollama_classify`, `ollama_summarize_fast`, `ollama_summarize_deep`, `ollama_research`, `ollama_corpus_answer`, `ollama_chat`, `ollama_code_citation`. The first attempt on the tool's tier runs against the caller-specified model; on timeout, the existing `TIER_FALLBACK` cascade resolves the cheaper tier's own model (NOT the caller's override). Composite/brief/pack tools deliberately do NOT accept `model` — atoms get per-call control, composites use tier defaults.
- **New envelope field `model_requested?: string`** — present only when the override was supplied. Calibration-aware callers compare `model_requested` vs `model` to detect fallback substitution: `if (env.model_requested && env.model !== env.model_requested) { /* substitution */ }`. Empty / whitespace-only inputs throw `ZodError` at schema parse, not silent fallthrough.
- **Bug fix — `src/version.ts` drift.** The runtime `VERSION` constant is now read from `package.json` at module load; v2.1.0 and v2.2.0 had shipped reporting the stale `"2.0.0"` identity string. New `tests/version.test.ts` locks `VERSION === pkg.version`.

### Per-call model override (new in v2.3.0)

```jsonc
{
  "tool": "ollama_classify",
  "arguments": {
    "text": "patch null pointer in auth",
    "labels": ["feat", "fix", "chore"],
    "frame": "what is the change kind?",
    "model": "hermes3:8b"
  }
}
```

Envelope:

```jsonc
{
  "result": { "label": "fix", "confidence": 0.9, "off_topic": false, ... },
  "tier_used": "instant",
  "model": "hermes3:8b",
  "model_requested": "hermes3:8b",       // present because override was supplied
  // ... rest of envelope unchanged
}
```

If the workhorse/deep tier had timed out and the call had cascaded to the instant tier, `env.model` would be the instant tier's resolved model and `env.fallback_from` would be `"workhorse"` — `env.model_requested` would still be `"hermes3:8b"`, and `env.model !== env.model_requested` is the substitution signal. The override is deliberately NOT carried into the cheaper tier; the chosen model may not fit that tier's role at all.

### Historical — v2.2.0 deliverables

See [CHANGELOG.md](./CHANGELOG.md) and [docs/release-notes/v2.2.0.md](./docs/release-notes/v2.2.0.md) for the full v2.2.0 entry (frame-bound topicality + structured abstention).

## New in v2.2.0

Local evidence-worker role contract: frame-bound topicality and structured abstention. Additive minor — v2.1.0 callers unchanged. Detailed entries in [CHANGELOG.md](./CHANGELOG.md) and [docs/release-notes/v2.2.0.md](./docs/release-notes/v2.2.0.md).

- **Frame-bound extraction** on `ollama_extract`, `ollama_classify`, `ollama_summarize_fast`, `ollama_summarize_deep` — optional `frame: string` input + structured `frame_alignment` / `on_topic` / `frame_addressed` outputs. Off-topic sources are flagged instead of paraphrased into the schema.
- **Structured abstention** on `ollama_research` — `weak` / `abstained` / `sources_address_question` fields. Empty `citations[]` with non-empty `answer` is no longer silent success.
- **Topicality threshold** on `ollama_corpus_answer` — optional `min_top_score`. Below the floor, the tool short-circuits with `abstained: true` and skips synthesis. Per-citation `score` now visible on each citation.
- **Retrieval score preservation** through brief evidence — `corpusHitsToEvidence` carries `score` (and `corpus_min_evidence_score` knob filters at assembly time on `incident_brief` / `repo_brief` / `change_brief`).
- **Citation line-range bounds** — `guardrails/citations.ts` rejects out-of-bounds ranges on `ollama_research`, matching the existing posture on `ollama_code_citation`.
- **Operator-contract docs corrected** — README `chunk_id`/`chunk_index` fix, "validated server-side" rewritten, Evidence Laws section qualified, marketing slogan annotated.

### Seed regression — the verification

The slice's contract is verified against the literal research-os fresh-pack failure: arxiv 2112.10422 (Cosmological Standard Timers) under the section-01 frame *"What does evidence custody mean in local-first vs cloud LLM deep-research workflows?"* — 9 / 9 mocked-LLM contract tests confirm the off-topic source is now contained (`frame_alignment.on_topic = false` on extract; `off_topic: true` on classify; `frame_addressed: false` on summarize_deep; `abstained: true` on corpus_answer with `min_top_score` set).

### Historical — v2.1.0 deliverables

See [CHANGELOG.md](./CHANGELOG.md) for the full v2.1.0 entry (feature pass: 13 new tools + 4 enhancements + freeze lift).

---

## Architecture at a glance

```mermaid
flowchart LR
  Claude["Claude Code<br/>(MCP client)"]
  MCP["ollama-intern-mcp<br/>server (stdio)"]
  Ollama["Ollama daemon<br/>(127.0.0.1:11434)"]
  Models[("Hermes 3 / Qwen 3<br/>nomic-embed-text")]
  Corpus[("~/.ollama-intern/<br/>corpora/")]
  Artifacts[("~/.ollama-intern/<br/>artifacts/")]
  NDJSON[("~/.ollama-intern/<br/>log.ndjson")]
  Guards{{"Guardrails<br/>citations · banned phrases<br/>protected paths · confidence"}}

  Claude -- "JSON-RPC over stdio" --> MCP
  MCP --> Guards
  MCP -- "/api/generate · /api/chat<br/>/api/embed · /api/ps · /api/tags" --> Ollama
  Ollama --> Models
  MCP --- Corpus
  MCP --- Artifacts
  MCP --> NDJSON
```

Every Claude tool call enters the MCP server over stdio JSON-RPC. The server validates the call against the tool's [zod](https://zod.dev) schema, runs the configured guardrails (citation validation, banned-phrase strip, protected-path enforcement, confidence thresholds), then routes to either a deterministic renderer (artifact tier) or an Ollama HTTP call (every other tier). The Ollama daemon never sees user-supplied paths — only the model tier and the prepared prompt. Every call appends one structured event to the NDJSON log at `~/.ollama-intern/log.ndjson`, where `ollama_log_tail` and your shell can read it.

---

## Lead example — one call, one artifact

```jsonc
// Claude → ollama-intern-mcp
{
  "tool": "ollama_incident_pack",
  "arguments": {
    "title": "sprite pipeline 5 AM paging regression",
    "logs": "[2026-04-16 05:07] worker-3 OOM killed\n[2026-04-16 05:07] ollama /api/ps reports evicted=true size=8.1GB\n...",
    "source_paths": ["F:/AI/sprite-foundry/src/worker.ts", "memory/sprite-foundry-visual-mastery.md"]
  }
}
```

Returns an envelope pointing at a file on disk:

```jsonc
{
  "result": {
    "pack": "incident",
    "slug": "2026-04-16-sprite-pipeline-5-am-paging-regression",
    "artifact_md":   "~/.ollama-intern/artifacts/incident/2026-04-16-sprite-pipeline-5-am-paging-regression.md",
    "artifact_json": "~/.ollama-intern/artifacts/incident/2026-04-16-sprite-pipeline-5-am-paging-regression.json",
    "weak": false,
    "evidence_count": 6,
    "next_checks": ["residency.evicted across last 24h", "OLLAMA_MAX_LOADED_MODELS vs loaded size"]
  },
  "tier_used": "deep",
  "model": "hermes3:8b",
  "hardware_profile": "dev-rtx5080",
  "tokens_in": 4180, "tokens_out": 612,
  "elapsed_ms": 8410,
  "residency": { "in_vram": true, "evicted": false }
}
```

→ `weak: false` means ≥2 evidence items were assembled; it does NOT mean the hypotheses are vetted. See [Evidence laws](#evidence-laws) below.

That markdown file is the intern's desk output — headings, evidence block with cited ids, investigative `next_checks`, `weak: true` banner if evidence is thin. It's deterministic: the renderer is code, not a prompt. (The renderer is deterministic; the *content* of hypotheses and surfaces is generative — read them as draft, not verified.) Open it tomorrow, diff it next week, export it into a handbook with `ollama_artifact_export_to_path`.

Every competitor in this category leads with "save tokens." We lead with _here is the file the intern wrote._

### Second example — build a corpus, then ask it

```jsonc
// 1. Build a persistent, searchable corpus over your project.
{ "tool": "ollama_corpus_index",
  "arguments": { "name": "sprite-foundry",
                 "paths": ["F:/AI/sprite-foundry/src"],
                 "embed_model": "nomic-embed-text" } }
// → { chunks_written: 1204, paths_indexed: 312, failed_paths: [] }

// 2. Ask an evidence-bound question against it.
{ "tool": "ollama_corpus_answer",
  "arguments": { "name": "sprite-foundry",
                 "query": "how does the worker handle OOM eviction?",
                 "top_k": 8 } }
// → { answer: "...", citations: [{chunk_index, path}...], weak: false }
```

The server validates citation identity and that each `chunk_index` is in range of the retrieved hits. It does NOT prove that every generated claim is semantically supported by the cited chunk content — that's the model's responsibility, and weak retrieval can still produce citation-shaped answers. Full walkthrough in [handbook/corpora](https://mcp-tool-shop-org.github.io/ollama-intern-mcp/handbook/corpora/).

---

## Frame-bound extraction (new in v2.2.0)

`ollama_extract`, `ollama_classify`, `ollama_summarize_fast`, and `ollama_summarize_deep` accept an optional `frame: string` input. The frame names the question the source is being asked to answer; the model is instructed to abstain rather than emit true-but-off-topic content when the source doesn't address the frame.

```jsonc
{
  "tool": "ollama_extract",
  "arguments": {
    "text": "<long source document>",
    "schema": { /* your fields */ },
    "frame": "section purpose here — e.g. 'OOM eviction behavior in the sprite worker'"
  }
}
// → result includes frame_alignment: { on_topic: boolean, reason: string, unaddressed_aspects: string[] }
```

If `frame` is omitted, behavior is unchanged from v2.1.0. When supplied, `frame_alignment.on_topic = false` signals that extracted fields may be true-of-the-source but not relevant to the frame — treat that as the same shape as a `weak: true` brief: useful, but spot-check before promoting into downstream evidence.

---

## Abstention contract (new in v2.2.0)

`ollama_research` returns structured abstention fields: `weak: boolean`, `abstained: boolean`, `sources_address_question: boolean | null`. An empty `citations[]` with a non-empty `answer` is no longer silent — `abstained: true` says the model declined to synthesize because the caller-supplied paths did not address the question. Treat abstention as a success, not a failure: it is the tool refusing to launder weak retrieval into authoritative output.

`ollama_corpus_answer` accepts an optional `min_top_score: number` topicality threshold (0.0–1.0). When the top retrieval score for a query falls below `min_top_score`, the tool short-circuits with `abstained: true` and skips synthesis — preventing the "5 off-topic chunks at score 0.21 still drive a full answer" failure mode that the v2.1.0 `weak: true` rule did not catch (`weak: true` only fired on `hits.length < 2`). Pair this with the per-citation `score` field newly surfaced on each citation to audit retrieval quality directly from the envelope.

---

## What's in here — four tiers, <!-- TOOL_COUNT:start -->44<!-- TOOL_COUNT:end --> tools

**Job-shaped** means each tool names a job you'd hand to an intern — classify this, extract that, triage these logs, draft this release note, pack this incident. The tool's input is the job spec; the output is the deliverable. No generic `run_model` / `chat_with_llm` primitive at the top.

| Tier | Count | What lives here |
|---|---|---|
| **Atoms** | 31 | Job-shaped primitives. **Original 15:** `classify`, `extract`, `triage_logs`, `summarize_fast` / `deep`, `draft`, `research`, `corpus_search` / `answer` / `index` / `refresh` / `list`, `embed_search`, `embed`, `chat`. **+13 added in v2.1.0:** `doctor`, `log_tail`, `batch_proof_check` (ops); `code_map`, `code_citation`, `multi_file_refactor_propose`, `refactor_plan` (refactor); `artifact_prune`, `hypothesis_drill` (artifact/brief); `corpus_health`, `corpus_amend`, `corpus_amend_history`, `corpus_rerank` (corpus). **+1 review atom:** `code_review` (structured PR-review findings, workhorse; review-only). **+2 in v2.9:** `verify_claims` (cross-family cloud flagship panel adjudicates claims; cloud-required) and `log_stats` (aggregate the NDJSON receipts into measured economics — cloud/local split, fallback rate, p50/p95 per tool; no model call). Batch-capable atoms (`classify`, `extract`, `triage_logs`) accept `items: [{id, text}]`. |
| **Briefs** | 3 | Evidence-backed structured operator briefs. `incident_brief`, `repo_brief`, `change_brief`. Every claim cites an evidence id; unknowns stripped server-side. Weak evidence surfaces `weak: true` rather than fake narrative. |
| **Packs** | 3 | Fixed-pipeline compound jobs that write durable markdown + JSON to `~/.ollama-intern/artifacts/`. `incident_pack`, `repo_pack`, `change_pack`. Deterministic renderers — no model calls on the artifact shape. |
| **Artifacts** | 7 | Continuity surface over pack outputs. `artifact_list` / `read` / `diff` / `export_to_path`, plus three deterministic snippets: `incident_note`, `onboarding_section`, `release_note`. |

Total: **31 atoms + 3 briefs + 3 packs + 7 artifact tools = <!-- TOOL_COUNT:start -->44<!-- TOOL_COUNT:end -->**.

Freeze lines:
- Atoms: freeze **lifted at v2.1.0** (31 today; +13 added in the v2.1.0 feature pass, +1 `code_review` later, +2 in v2.9: `verify_claims`, `log_stats`). New atoms still require an audit-justified gap, tests, handbook page, and CHANGELOG entry — no casual additions.
- Packs frozen at 3. No new pack types.
- Artifact tier frozen at 7.

The full tool reference lives in the [handbook](https://mcp-tool-shop-org.github.io/ollama-intern-mcp/handbook/tools/).

---

## Install

Requires [Ollama](https://ollama.com) running locally and the tier models pulled (see [Model pulls](#model-pulls) below).

### Claude Code (recommended)

Most users install this by adding it to their Claude Code MCP server config — no global install required. Claude Code runs the server on demand via `npx`:

```json
{
  "mcpServers": {
    "ollama-intern": {
      "command": "npx",
      "args": ["-y", "ollama-intern-mcp"],
      "env": {
        "OLLAMA_HOST": "http://127.0.0.1:11434",
        "INTERN_PROFILE": "dev-rtx5080"
      }
    }
  }
}
```

### Claude Desktop

Same block, written to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows).

### Global install (advanced)

Only needed if you want the binary on your `PATH` for ad-hoc use outside Claude Code:

```bash
npm install -g ollama-intern-mcp
```

### Use with Hermes

This MCP was validated end-to-end with [Hermes Agent](https://github.com/NousResearch/hermes-agent) against `hermes3:8b` on Ollama (2026-04-19). Hermes is an external agent that *calls into* this MCP's frozen primitive surface — it does the planning, we do the work.

Reference config ([hermes.config.example.yaml](hermes.config.example.yaml) in this repo):

```yaml
model:
  provider: custom
  base_url: http://localhost:11434/v1
  default: hermes3:8b
  context_length: 65536    # Hermes requires 64K floor under model.*

providers:
  local-ollama:
    name: local-ollama
    base_url: http://localhost:11434/v1
    api_mode: openai_chat
    api_key: ollama
    model: hermes3:8b

mcp_servers:
  ollama-intern:
    command: npx
    args: ["-y", "ollama-intern-mcp"]
    env:
      OLLAMA_HOST: http://localhost:11434
      INTERN_PROFILE: dev-rtx5080
      # hermes3:8b is the default ladder in v2.0.0, so tier overrides are
      # only needed if you're pinning a different local model.
```

**Prompt shape matters.** Imperative tool-invocation prompts ("Call X with args …") are the integration test — they give an 8B local model enough scaffolding to emit clean `tool_calls`. List-form multi-task prompts ("do A, then B, then C") are capability benchmarks for larger models; don't interpret a list-form failure on 8B as "the wiring is broken." See [handbook/with-hermes](https://mcp-tool-shop-org.github.io/ollama-intern-mcp/handbook/with-hermes/) for the full integration walkthrough + known transport caveats (Ollama `/v1` streaming + openai-SDK non-streaming shim).

### Model pulls

**Default dev profile (RTX 5080 16GB and similar):**

```bash
ollama pull hermes3:8b
ollama pull nomic-embed-text
export OLLAMA_MAX_LOADED_MODELS=2
export OLLAMA_KEEP_ALIVE=-1
```

**Qwen 3 alternate rail (same hardware, for Qwen tooling):**

```bash
ollama pull qwen3:8b
ollama pull qwen3:14b
ollama pull nomic-embed-text
export INTERN_PROFILE=dev-rtx5080-qwen3
```

**M5 Max profile (128GB unified):**

```bash
ollama pull qwen3:14b
ollama pull qwen3:32b
ollama pull nomic-embed-text
export INTERN_PROFILE=m5-max
```

Per-tier env vars (`INTERN_TIER_INSTANT`, `INTERN_TIER_WORKHORSE`, `INTERN_TIER_DEEP`, `INTERN_EMBED_MODEL`) still override profile picks for one-offs.

---

## Uniform envelope

Every tool returns the same shape:

```ts
{
  result: <tool-specific>,
  tier_used: "instant" | "workhorse" | "deep" | "embed",
  model: string,
  hardware_profile: string,     // "dev-rtx5080" | "dev-rtx5080-qwen3" | "m5-max"
  tokens_in: number,
  tokens_out: number,
  elapsed_ms: number,
  residency: {
    in_vram: boolean,
    size_bytes: number,
    size_vram_bytes: number,
    evicted: boolean
  } | null
}
```

`residency` comes from Ollama's `/api/ps`. When `evicted: true` or `size_vram < size`, the model paged to disk and inference dropped 5–10× — surface this to the user so they know to restart Ollama or trim loaded-model count.

In [Ollama Cloud](#ollama-cloud-optional) mode the envelope also carries `backend` (`"cloud"` | `"local"`) and, on a cloud→local fallback, `degraded: true` + `degrade_reason`. These fields are **absent** in the default local-only path, so existing consumers are unaffected. `residency` is `null` for cloud-served calls (the stateless cloud has no local-VRAM residency).

Every call is logged as one NDJSON line to `~/.ollama-intern/log.ndjson`. Filter by `hardware_profile` to keep dev numbers out of publishable benchmarks.

---

## Hardware profiles

| Profile | Instant | Workhorse | Deep | Embed |
|---|---|---|---|---|
| **`dev-rtx5080`** (default) | hermes3 8B | hermes3 8B | hermes3 8B | nomic-embed-text |
| `dev-rtx5080-qwen3` | qwen3 8B | qwen3 8B | qwen3 14B | nomic-embed-text |
| `m5-max` | qwen3 14B | qwen3 14B | qwen3 32B | nomic-embed-text |

**Default dev** collapses all three work tiers onto `hermes3:8b` — the validated Hermes Agent integration path. Same model top to bottom means there is one thing to pull, one residency cost, one set of behavior to understand. Users who prefer Qwen 3 (with its `THINK_BY_SHAPE` plumbing) opt into `dev-rtx5080-qwen3`. `m5-max` is the Qwen 3 ladder sized for unified memory.

---

## Ollama Cloud (optional)

Local 8B models are the hardware bottleneck most people hit. [Ollama Cloud](https://ollama.com/cloud) serves 600B-class models behind the **same** `/api/*` surface, so you can route the heavy tools to a far stronger model and free up local VRAM — while keeping local as an always-on fallback.

**This is opt-in and off by default.** With no key set, the package stays local-first with **zero egress** — anyone who doesn't opt in is unaffected. There are two ways to opt in:

- **Cloud-primary** (below): set *both* `OLLAMA_CLOUD_PRIMARY=1` and `OLLAMA_API_KEY` — the generative tiers route to cloud with local fallback.
- **Cloud standby** (v2.9): set **only** `OLLAMA_API_KEY` — everything stays local (still zero egress, not even a startup probe) until a single call explicitly asks to escalate with `backend: "cloud"`. See [Cloud standby & per-call escalation](#cloud-standby--per-call-escalation) below.

```json
{
  "mcpServers": {
    "ollama-intern": {
      "command": "npx",
      "args": ["-y", "ollama-intern-mcp"],
      "env": {
        "OLLAMA_CLOUD_PRIMARY": "1",
        "OLLAMA_API_KEY": "sk-...your-key...",
        "INTERN_PROFILE": "dev-rtx5080"
      }
    }
  }
}
```

> **The key is a runtime env var, not a CI secret.** A GitHub Actions secret is only visible inside CI runs — it never reaches the running server. Create a key at [ollama.com/settings/keys](https://ollama.com/settings/keys) and put it in your MCP client's `env` block (or your shell environment).

**How routing works.** When cloud is on, the generative tiers (instant / workhorse / deep) go to the cloud model; **embeddings always stay local** (Ollama Cloud serves no embedding models, so the corpus/embed tools are unaffected). A circuit breaker tries cloud first and falls back to your local profile on timeout / 5xx / 429 / network errors. A bad key (401/403) trips a *sticky* breaker that surfaces loudly rather than degrading silently. The local profile (`INTERN_PROFILE`) is the fallback ladder, so keep its models pulled.

**You're never silently downgraded.** Every envelope reports which backend served the call:

```ts
{ ...envelope, backend: "cloud" | "local", degraded?: true, degrade_reason?: "cloud_timeout" | "cloud_5xx" | "cloud_rate_limited" | "cloud_unreachable" | "cloud_auth_failed" | "circuit_open" }
```

A `backend_fallback` line lands in `~/.ollama-intern/log.ndjson` on every cloud→local fallback (`ollama_log_tail --filter_kind backend_fallback`), and `ollama-intern-mcp doctor` shows a **Cloud (primary | standby)** block with the mode, reachability, and auth status.

### Cloud standby & per-call escalation

Setting `OLLAMA_API_KEY` **without** `OLLAMA_CLOUD_PRIMARY` arms **standby**: routing stays local-primary and nothing leaves the machine — until a call carries `backend: "cloud"` (exposed on `ollama_chat`, used internally by `ollama_verify_claims`). That one call escalates to the cloud model, with the same breaker + local-fallback machinery and the same envelope provenance; every other call stays local. The **first** escalated call prints a loud stderr disclosure naming the host and writes a `cloud_egress` line to the NDJSON log — egress is disclosed at the point it happens, not just here in the docs.

The rules, mechanically enforced:

- No key → `backend: "cloud"` fails with `CLOUD_NOT_CONFIGURED`. It is **never** silently served by the local model while claiming it escalated.
- Standby + no directive → local, zero egress (startup does not probe the cloud host either).
- Under cloud-primary, `backend: "local"` pins one call local — the inverse escape hatch.
- A per-call `model` override now rides the cloud path verbatim (it used to be clobbered by the tier→cloud-model map), so receipt-backed orchestrators can name the exact cloud model per call.

The flagship consumer is **`ollama_verify_claims`**: adjudicate claims/findings with a 3-model cross-family cloud panel (default `deepseek-v4-pro:cloud` / `kimi-k2.7-code:cloud` / `glm-5.2:cloud`) — lone-dissent-never-decides aggregation, served-model checks on every juror, and an honest `weak` flag when the panel thins. A CONFIRMED from the panel on frontier-model-authored claims is *supporting evidence, not proof* — the panel reliably catches gross errors and is weaker on subtle ones. See the [handbook page](https://mcp-tool-shop-org.github.io/ollama-intern-mcp/handbook/tools/verify-claims/).

**Latency vs quality.** Big cloud models run far slower per token than a local 8B (seconds, not milliseconds) — a quality upgrade, not a speed one. Cloud tiers use a generous timeout ladder (instant 30s / workhorse 120s / deep 300s by default).

### Cloud env vars

| Var | Default | Purpose |
|---|---|---|
| `OLLAMA_CLOUD_PRIMARY` | _(unset)_ | **The cloud-primary switch.** `1`/`true`/`yes`/`on` routes the generative tiers to cloud. Unset with a key = **standby** (local-primary, per-call escalation only). Unset without a key = local-only, zero egress. |
| `OLLAMA_API_KEY` | _(unset)_ | Bearer key for Ollama Cloud. Setting it alone arms **standby**; **required** when `OLLAMA_CLOUD_PRIMARY` is enabled (fail-fast at startup if missing). |
| `OLLAMA_CLOUD_HOST` | `https://ollama.com` | Cloud base host. |
| `INTERN_CLOUD_MODEL` | `qwen3-coder-next:cloud` | Cloud model for instant + workhorse + deep. Keep the default **non-thinking** — a thinking model here burns short-output budgets on CoT (put big reasoners on the deep override below). |
| `INTERN_CLOUD_DEEP_MODEL` | _(= `INTERN_CLOUD_MODEL`)_ | Optional deep-tier-only override, e.g. `deepseek-v3.1:671b`. |
| `INTERN_CLOUD_TIMEOUT_{INSTANT,WORKHORSE,DEEP}_MS` | `30000`/`120000`/`300000` | Per-tier cloud-attempt timeouts. |
| `INTERN_CLOUD_NUM_CTX` | `32768` | Context-window cap for cloud calls (cloud bills by GPU-time; cap controls cost). |

> **Model availability changes.** Ollama rotates/retires cloud ids server-side. As of 2026-07, `qwen3-coder-next:cloud` (non-thinking default) and the thinking flagships `deepseek-v4-pro:cloud` / `kimi-k2.7-code:cloud` / `glm-5.2:cloud` are current; check [ollama.com/search?c=cloud](https://ollama.com/search?c=cloud) before pinning an id. A retired id degrades visibly (`cloud_model_missing`), never silently.

**Privacy note.** Routing to Ollama Cloud sends prompts to a third party. Ollama's [privacy policy](https://ollama.com/privacy) states cloud prompts are processed transiently, not retained beyond the request, and not used for training — but it is still egress, which is why it's opt-in and disclosed. Local-only mode (the default) sends nothing off the box.

---

## Evidence laws

These are enforced in the server, not the prompt:

- **Citations required.** Every brief claim cites an evidence id.
- **Unknowns stripped server-side.** Models that cite ids not in the evidence bundle have those ids dropped with a warning before the result returns.
- **ID-validated, not content-validated.** Server checks that every cited `evidence_ref` points to a real evidence id in the assembled set. It does NOT verify that the claim text is derivable from the cited evidence — that is the model's job, and weak briefs sometimes contain unsupported claims with valid refs. Use `weak: true` + coverage_notes + the included `excerpt` field to spot-check.
- **Weak is weak.** Thin evidence flags `weak: true` with coverage notes. Never smoothed into fake narrative.
- **Investigative, not prescriptive.** `next_checks` / `read_next` / `likely_breakpoints` only. Prompts forbid "apply this fix."
- **Deterministic renderers.** Artifact markdown shape is code, not a prompt. `draft` stays reserved for prose where model wording matters.
- **Same-pack diffs only.** Cross-pack `artifact_diff` is refused loudly; payloads stay distinct.

---

## Artifacts & continuity

Packs write to `~/.ollama-intern/artifacts/{incident,repo,change}/<slug>.(md|json)`. The artifact tier gives you a continuity surface without turning this into a file-management tool:

- `artifact_list` — metadata-only index, filterable by pack, date, slug glob
- `artifact_read` — typed read by `{pack, slug}` or `{json_path}`
- `artifact_diff` — structured same-pack comparison; weak-flip surfaced
- `artifact_export_to_path` — writes an existing artifact (with provenance header) to a caller-declared `allowed_roots`. Refuses existing files unless `overwrite: true`.
- `artifact_incident_note_snippet` — operator-note fragment
- `artifact_onboarding_section_snippet` — handbook fragment
- `artifact_release_note_snippet` — DRAFT release-note fragment

No model calls in this tier. All render from stored content.

---

## Threat model & telemetry

**Data touched:** file paths the caller explicitly hands in (`ollama_research`, corpus tools), inline text, and artifacts the caller asks to be written under `~/.ollama-intern/artifacts/` or a caller-declared `allowed_roots`.

**Data NOT touched:** anything outside `source_paths` / `allowed_roots`. `..` is rejected before normalize. `artifact_export_to_path` refuses existing files unless `overwrite: true`. Drafts targeting protected paths (`memory/`, `.claude/`, `docs/canon/`, etc.) require explicit `confirm_write: true`, enforced server-side.

**Network egress:** **off by default.** Out of the box the only outbound traffic is to the local Ollama HTTP endpoint — no cloud calls, no update pings, no crash reporting. **Opt-in exception:** if you enable [Ollama Cloud](#ollama-cloud-optional) (`OLLAMA_CLOUD_PRIMARY=1` + `OLLAMA_API_KEY`), prompts for the generative tiers are sent to `ollama.com` over HTTPS with a Bearer key. This is explicit, disclosed, and off unless you set both vars; embeddings still never leave the box. See [SECURITY.md](SECURITY.md) §11.

**Telemetry:** **none.** Every call is logged as one NDJSON line to `~/.ollama-intern/log.ndjson` on your machine. The server itself phones home to nothing.

**Errors:** structured shape `{ code, message, hint, retryable }`. Stack traces are never exposed through tool results.

Full policy: [SECURITY.md](SECURITY.md).

---

## Standards

Built to the [Shipcheck](https://github.com/mcp-tool-shop-org/shipcheck) bar. Hard gates A–D pass; see [SHIP_GATE.md](SHIP_GATE.md) and [SCORECARD.md](SCORECARD.md).

- **A. Security** — SECURITY.md, threat model, no telemetry, path-safety, `confirm_write` on protected paths
- **B. Errors** — structured shape across all tool results; no raw stacks
- **C. Docs** — README current, CHANGELOG, LICENSE; tool schemas self-document
- **D. Hygiene** — `npm run verify` (full vitest suite), CI with dep scanning, Dependabot, lockfile, `engines.node`

---

## Roadmap (hardening, not scope creep)

- **Phase 1 — Delegation Spine** ✓ shipped: atom surface, uniform envelope, tiered routing, guardrails
- **Phase 2 — Truth Spine** ✓ shipped: schema v2 chunking, BM25 + RRF, living corpora, evidence-backed briefs, retrieval eval pack
- **Phase 3 — Pack & Artifact Spine** ✓ shipped: fixed-pipeline packs with durable artifacts + continuity tier
- **Phase 4 — Adoption Spine** ✓ v2.0.1: three-stage health pass hardened corpus (TOCTOU, 50 MB file cap, symlink rejection, atomic writes, per-file failure capture), tool path traversal, observability (semaphore wait events, timeout error context, profile env-override logging, prewarm cold-start signal), test safety (module-load env snapshot across 10 files, `tools/call` E2E). Troubleshooting handbook + hardware minimums added for operators.
- **Phase 5 — M5 Max benchmarks** — publishable numbers once the hardware lands (~2026-04-24)

Phase by hardening layer. Pack and artifact tiers stay frozen at 3 and 7. The atom freeze was lifted at v2.1.0 — new atoms require an audit-justified gap, tests, handbook page, and CHANGELOG entry.

---

## License

MIT — see [LICENSE](LICENSE).

---

<p align="center">Built by <a href="https://mcp-tool-shop.github.io/">MCP Tool Shop</a></p>
