{
  "markdown": "# overseer-nvim-mcp\n\nAn [MCP](https://modelcontextprotocol.io) server that gives a coding agent the\nsame control over [overseer.nvim](https://github.com/stevearc/overseer.nvim)\ntasks that you have: list, tail, run, restart, stop, and dispose.\n\n![An agent starts a dev server as an overseer task; the user watches its output in the task list and stops it themselves](https://raw.githubusercontent.com/impossiblecode/overseer-nvim-mcp/main/.github/demo.gif)\n\n[![npm](https://img.shields.io/npm/v/overseer-nvim-mcp)](https://www.npmjs.com/package/overseer-nvim-mcp)\n[![CI](https://github.com/impossiblecode/overseer-nvim-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/impossiblecode/overseer-nvim-mcp/actions/workflows/ci.yml)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Glama score](https://glama.ai/mcp/servers/impossiblecode/overseer-nvim-mcp/badges/score.svg)](https://glama.ai/mcp/servers/impossiblecode/overseer-nvim-mcp)\n\n## Why\n\nYour agent's shell tool and your overseer task list are separate execution\nworlds. A dev server the agent starts in its own shell is invisible in the task\nlist, can't be stopped from it, and orphans its process tree when the session\nends. Tasks *you* start get proper teardown. This closes that gap: the agent's\nlong-running commands become real overseer tasks.\n\n## Requirements\n\n- Neovim with [overseer.nvim](https://github.com/stevearc/overseer.nvim)\n  (tested against `v2.1.0`)\n- Node >= 22\n- An MCP client running **inside** a Neovim terminal buffer\n\n## Install\n\nThe server speaks stdio and is started by your MCP client. The command is\nalways the same:\n\n```\nnpx -y overseer-nvim-mcp\n```\n\nThe client must run **inside a Neovim terminal buffer**; that is where the\n`$NVIM` socket it inherits comes from. Tools appear at the next session start,\nsince MCP servers connect at startup. If the tools never appear, the usual\ncause is that `$NVIM` did not reach the server's environment: the server\nregisters nothing without it, and some clients strip the environment they\npass to servers.\n\n<details>\n<summary>Claude Code</summary>\n\n```\nclaude mcp add overseer -- npx -y overseer-nvim-mcp\n```\n\n</details>\n\n<details>\n<summary>Codex CLI</summary>\n\n```\ncodex mcp add overseer -- npx -y overseer-nvim-mcp\n```\n\nThen add one line to the generated block in `~/.codex/config.toml`:\n\n```toml\n[mcp_servers.overseer]\ncommand = \"npx\"\nargs = [\"-y\", \"overseer-nvim-mcp\"]\nenv_vars = [\"NVIM\"]\n```\n\nThe `env_vars` line is required. Codex passes stdio servers a fixed whitelist\nof variables (`HOME`, `PATH`, `TERM` and the like), `$NVIM` is not on it, and\nwithout it the server registers no tools.\n\nCodex also won't reach for the server on its own; see\n[Getting your agent to actually use it](#getting-your-agent-to-actually-use-it).\n\n</details>\n\n<details>\n<summary>Antigravity CLI</summary>\n\nIn `~/.gemini/config/mcp_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"overseer\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"overseer-nvim-mcp\"]\n    }\n  }\n}\n```\n\nThat user-global file is the one to use: the CLI's non-interactive print mode\n(`agy -p`) loads MCP servers from it and from nowhere else; a workspace-level\n`.agents/mcp_config.json` is silently ignored there.\n\nAntigravity also won't reach for the server on its own; see\n[Getting your agent to actually use it](#getting-your-agent-to-actually-use-it).\n\n</details>\n\n<details>\n<summary>Gemini CLI</summary>\n\n```\ngemini mcp add overseer npx -y overseer-nvim-mcp\n```\n\nOr in `settings.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"overseer\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"overseer-nvim-mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n<summary>opencode</summary>\n\nIn `opencode.json`:\n\n```json\n{\n  \"mcp\": {\n    \"overseer\": {\n      \"type\": \"local\",\n      \"command\": [\"npx\", \"-y\", \"overseer-nvim-mcp\"]\n    }\n  }\n}\n```\n\n</details>\n\n<details>\n<summary>mcphub.nvim (codecompanion.nvim, avante.nvim)</summary>\n\nIn mcphub's servers config:\n\n```json\n{\n  \"mcpServers\": {\n    \"overseer\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"overseer-nvim-mcp\"],\n      \"env\": { \"NVIM\": \"${NVIM}\" }\n    }\n  }\n}\n```\n\nThe `env` block is required: mcp-hub does not pass its own environment to the\nservers it spawns, so without it the server sees no `$NVIM` and registers no\ntools. The server operates on the Neovim instance that started the hub. A hub\nstarted outside Neovim has no `$NVIM` to forward, and mcp-hub reports this\nserver as disconnected with `Variable 'NVIM' not found`.\n\n</details>\n\n### Using with LazyVim\n\nLazyVim ships an [overseer.nvim extra](https://www.lazyvim.org/extras/editor/overseer).\nEnable it with `:LazyExtras` (select `editor.overseer`), restart Neovim, then\nadd the server to your MCP client as above. Nothing else is needed; the server\ntalks to whatever overseer configuration you already have.\n\n## Tools\n\n| Tool | Purpose |\n|------|---------|\n| `overseer_list_tasks` | Tasks with id, name, status, `exit_code`, `cmd`, `cwd`, timings, `origin` |\n| `overseer_list_templates` | Templates in a directory (npm, go-task, make, just, VS Code) with `provider`, `desc`, `params` |\n| `overseer_tail` | A task's output, with status; can block until a pattern appears |\n| `overseer_run` | Start a long-running command: a raw `cmd` array, or a `template` with `params` |\n| `overseer_restart` | Restart a task by id or name substring |\n| `overseer_stop` | Stop a running task |\n| `overseer_dispose` | Stop and remove a task from the list |\n\nThe last three take `force`, and refuse a running task you started without it.\n\nTasks are addressed by numeric id or a case-insensitive name substring, so an\nagent can say `\"dev\"` instead of tracking ids.\n\n## How it works\n\nThe transport is `$NVIM`, the RPC socket Neovim exports to every process it\nspawns in a terminal buffer. Your MCP client inherits it, and this server, as a\nchild of that client, inherits it in turn.\n\nEverything else follows from that:\n\n- With **`$NVIM` set**, the server registers seven tools, each one\n  `nvim_exec_lua` against overseer over msgpack-RPC.\n- With **`$NVIM` unset**, it registers nothing and gets out of the way. Running\n  outside Neovim is a no-op rather than an error.\n\nThere is no socket discovery: no cwd hashing, no `lsof`, no\n`pgrep`. Those approaches are structurally broken (a cwd-hash scheme cannot tell\na crashed instance's stale socket from a live one and will unlink working\nsockets; `pgrep` on macOS excludes the caller's own ancestors, which is exactly\nthe Neovim instance that matters). `$NVIM` sidesteps both by construction.\n\nAll user input (task names, commands, working directories) is passed as msgpack\narguments and arrives in Lua as `...`. Nothing is ever interpolated into Lua\nsource, so a task name cannot become code execution.\n\n## It shares your task list, so it stays out of your tasks\n\nThe task list has two writers now, and only one of them can see it. Two things\nkeep that from biting:\n\n- **A substring matching more than one task is an error** that lists the\n  candidates and asks for a numeric id. `\"dev\"` matches a dozen names in a\n  monorepo, and silently taking the first is how the wrong thing gets stopped.\n- **Tasks are tagged with who started them.** `overseer_list_tasks` reports `origin`\n  as `agent` or `user`, and `overseer_stop`, `overseer_restart` and\n  `overseer_dispose` refuse a **running** task you started yourself unless\n  `force` is passed. Finished tasks are unguarded, since removing a dead row\n  costs nothing.\n\nThe asymmetry is on purpose. A wrong refusal costs one extra call. A wrong stop\nkills your dev server, loses whatever state it held, and you would have no\nreason to connect it to the agent.\n\n`overseer_run` is for commands that **do not exit on their own**: dev servers,\nfile watchers, `--watch` test runs. Short commands that terminate by themselves\nshould stay on the agent's normal shell tool, where output is available in-band.\nRound-tripping a two-second build through start-then-poll is worse.\n\n## An empty template list is normal\n\n`overseer_list_templates` returns whatever overseer's providers discover, verbatim.\nMost repos declare nothing runnable and return an empty list. That is a\nlegitimate answer, not an error. This is why `overseer_run` takes a raw `cmd` as\nits primary path: a template-only design would be unusable in the common case.\n\nNo provider-specific knowledge lives in this server. It does not filter or\nrewrite results, including help-only entries some task runners expose, because\ndoing so would mean encoding one provider's conventions into a server that must\nbehave identically in a repo that has never heard of it.\n\nWhat each entry carries:\n\n```json\n{\n  \"name\": \"just fixture-just-generate\",\n  \"provider\": \"just\",\n  \"desc\": \"Generate output for a language\",\n  \"params\": [{ \"name\": \"lang\", \"type\": \"string\", \"required\": true }],\n  \"running_task_id\": 128\n}\n```\n\n- **`desc` is an explicit `null`** when a provider supplies no descriptions.\n  npm and make never do; go-task and just usually do. An explicit null says\n  there is nothing to read, rather than leaving you guessing whether a field got\n  dropped somewhere.\n- **Entries with a description sort first.** That encodes nothing about any\n  provider, only about information content. A repo with a Taskfile *and* a\n  package.json would otherwise bury its documented half beneath dozens of bare\n  npm script names.\n- **`params` are the arguments a template takes**; `required` marks the ones\n  `overseer_run` will refuse the call without. Pass them as `params`. A missing\n  one is an error naming what it wanted rather than a prompt opened in your\n  editor.\n- **`running_task_id`** appears when a task of that name is already running.\n  It is a name match, so it can miss (a template invoked with params produces a\n  task named after the resolved command), but when it is there, it is the signal\n  not to start a second dev server on top of yours.\n- **`filter`** matches a substring against name and desc. Worth using: a\n  three-runner monorepo can return well over eighty entries.\n\n## Getting your agent to actually use it\n\nOn some clients this is automatic. The server returns MCP `instructions` in its\ninitialize result, and clients that surface those put them in the agent's system\nprompt, where it is actually looking rather than buried among fifty tool\ndescriptions. It states the lifetime boundary (long-running here, short commands\non the shell) and tells the agent to check templates before reconstructing a\ncommand, use `wait_for` instead of polling, and clean up only its own tasks.\n\nThe server withholds those instructions when `$NVIM` is unset, for the same\nreason it registers no tools: a session that gets none should not be told how to\nuse them.\n\nWhether the instructions reach the model is the whole game, and clients differ:\n\n- **Claude Code** injects them. **Copilot CLI** injects them for servers whose\n  instructions you allow (`--allow-all-mcp-server-instructions` in scripted\n  runs).\n- **Codex CLI** shows the model the tool definitions but not the instructions,\n  and leaving servers untouched until prompted is a known\n  [open issue](https://github.com/openai/codex/issues/29146). Asked to \"start\n  the dev server\", it runs `npm run dev` in its own shell with the server\n  connected and working.\n- **Antigravity CLI** injects nothing at all: it writes the instructions and\n  every tool schema to files under `~/.gemini/antigravity-cli/mcp/overseer/`\n  that the model only reads once something points it there. Same prompt, same\n  result: its own shell.\n\nOn those two, either name overseer in the prompt (\"start the dev server in\noverseer\" works on both) or say it once in the place the client actually\nreads: its context file. One line in `AGENTS.md` (Codex) or `GEMINI.md` (Antigravity)\nflips the same prompt to full overseer routing: templates checked first, task\nrun by name, output tailed after.\n\nThe markdown below is that one-time instruction, and it belongs in your\nproject context file on every client (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, whatever yours reads).\nClients re-read project instructions constantly, so they outweigh anything the\nserver can send:\n\n```markdown\n## Long-running commands\n\nStart dev servers, file watchers and `--watch` test runs with `overseer_run`,\nnot the shell. They then appear in my task list, I can stop them myself, and\ntheir process trees get torn down properly instead of being orphaned.\n\nShort commands that exit on their own stay on the shell: their output is\nin-band there, which is what you want.\n\nCheck `overseer_list_templates` first. If the repo declares one that matches, run\nit by name rather than reconstructing the command.\n```\n\nIf that still isn't enough, a `PreToolUse` hook makes it deterministic. This one\nblocks the shell for a few unambiguous cases and tells the agent what to do\ninstead. Start narrow and add patterns you actually hit, since a hook that fires\non the wrong thing is worse than none:\n\n```bash\n#!/usr/bin/env bash\n# ~/.claude/hooks/prefer-overseer.sh: exit 2 blocks the call and shows stderr\n# to the agent. Receives the tool call as JSON on stdin.\ncmd=$(jq -r '.tool_input.command // \"\"')\ncase \"$cmd\" in\n  *\"vitest run\"*) ;; # one-shot, stays on the shell\n  *\" --watch\"*|*\"vitest\"*|*\"npm run dev\"*|*\"pnpm dev\"*|*\"yarn dev\"*)\n    echo \"This looks long-running. Use overseer_run so it lands in the task list and can be stopped.\" >&2\n    exit 2\n    ;;\nesac\nexit 0\n```\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [{ \"type\": \"command\", \"command\": \"~/.claude/hooks/prefer-overseer.sh\" }]\n      }\n    ]\n  }\n}\n```\n\n### Waiting, without spinning\n\nAn agent driving a task by hand ends up guessing: start it, tail, get nothing,\ntail again, get nothing. It has no way to sleep, so every guess is a round-trip\nyou watch scroll past.\n\n`overseer_tail` takes a `wait_for` regex instead. It returns the moment a\nmatching line appears, or when the task exits, or at `timeout_ms`, and says\nwhich of the three happened, so a timeout can't be misread as success:\n\n```\noverseer_tail { task: 48, wait_for: \"ready - listening\" }\n\nstatus=RUNNING total=5 from=1 waited=matched\ncompiling... 0\ncompiling... 1\ncompiling... 2\nready - listening on http://localhost:3000\nGET /route-4 200\n```\n\n`total` is a cursor. Pass it back as `since` and you get only what is new,\nrather than re-reading the same screenful every poll:\n\n```\noverseer_tail { task: 48, since: 5 }\n\nstatus=RUNNING total=9 from=6\nGET /route-5 200\nGET /route-6 200\nGET /route-7 200\nGET /route-8 200\n```\n\n`from` is the index the block actually starts at. If it is greater than\n`since + 1`, output scrolled past between calls and you are looking at a gap\nrather than a continuation.\n\n`overseer_run` waits briefly too, up to `settle_ms` (default 1500, `0` to\ndisable), returning as soon as the task produces output or exits. A command that\ndies on startup reports its failure there instead of returning the same bare id\na healthy dev server would.\n\n**The waiting happens in Node.** This server runs inside\nyour editor, and `vim.wait` does not process input, so a fifteen-second wait in\nLua would freeze your session for fifteen seconds. Polling over the local socket\nkeeps Neovim responsive, and you never see the round-trips.\n\n## Slash commands and attachable resources\n\nTools are what the agent calls. The server also publishes two things *you* drive.\n\n**Prompts** appear as slash commands (`/mcp__overseer__...` in Claude Code). The\nserver generates them per invocation, so they inspect your live editor state\nrather than reciting a generic answer:\n\n- **`directory_local_task`** defines a task for a project that has no npm\n  script, Makefile or Taskfile, using overseer's own `register_template` in a\n  `.nvim.lua`. It checks whether `exrc` is actually on and whether a `.nvim.lua`\n  already exists, and includes the two things that make a correct setup look\n  broken: it will not appear until Neovim restarts, and Neovim will ask you to\n  `:trust` the file.\n- **`diagnose`** explains why overseer is not showing the tasks you expect.\n  Overseer records, per provider, why it contributed nothing, and that reason is\n  otherwise invisible:\n\n  ```\n  - `npm`: 11/11 available\n  - `make`: 0/0 available - No Makefile found\n  - `mise`: 0/0 available - Command \"mise\" not found\n  ```\n\n  Which distinguishes \"nothing to read here\" from \"that runner isn't installed\",\n  a distinction an empty list cannot make.\n\n**Resources** are attachable rather than called:\n\n- `overseer://tasks`: the whole task list as JSON\n- `overseer://task/{id}/output`: one task's output, with a status line\n\nThe server enumerates the per-task URIs with live ids and supports completion,\nso a client can offer the tasks that exist instead of making you look one up.\n\n## Notes\n\n- Output from a task started over RPC lives in the strategy's pending buffer, not\n  a terminal buffer, until you open the overseer panel. `overseer_tail` reads\n  both, strips ANSI colour codes and carriage returns, and trims the PTY's blank\n  padding so you get clean log lines rather than the bottom of an empty grid.\n- `exit_code` is absent while a task is running and present once it exits, which\n  is how an agent distinguishes a clean finish from a crash.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). Releases are automated with\nrelease-please, so commits must follow\n[Conventional Commits](https://www.conventionalcommits.org/).\n\n## License\n\nMIT © Miguel Angelo Sepulveda\n",
  "bytes": 17468,
  "sha": "e77ee22ddfbcdaedb8411a5b54340b8c230e0153c4db54ef829a56e58f75db2b",
  "repo_slug": "impossiblecode/overseer-nvim-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_impossiblecode_overseer_nvim_m_595b7926/readme"
}