MCP server giving coding agents visibility and control over overseer.nvim tasks: list, tail, run, restart, stop, dispose
This MCP server demonstrates strong tool design with excellent descriptions and clear naming conventions. All 7 tools are explicitly registered with proper schemas and comprehensive parameter documentation. The server uses verb-first naming (overseer_list_*, overseer_tail, overseer_run, overseer_restart, overseer_stop, overseer_dispose) which aligns with production patterns. Descriptions are detailed (100-500+ chars) and context-rich, explaining not just what tools do but when to use them and critical implementation details (e.g., overseer_tail's wait_for regex pattern, overseer_run's settle_ms behavior). Parameter schemas use Zod validation with union types and optional fields. However, some parameter descriptions could be more explicit about constraints and error recovery paths. Tool annotations (readOnlyHint/destructiveHint) are present for READ_ONLY and DESTRUCTIVE tools, which is excellent. Output schemas are documented via formatTail utility and JSON responses. Error handling is implemented via guard() wrapper that returns isError flag, though recovery guidance in error messages could be more specific per tool.
Stop (if running) and remove a task from the list by id or name substring. Disposing a finished task is unguarded; a running one the user started is refused unless `force` is set. A name matching more than one task is an error.
List running and completed overseer.nvim tasks (id, name, status), newest first. `origin` is 'agent' for tasks this server started and 'user' for the ones they started themselves; only clean up your own.
List task templates overseer discovers in a directory (npm scripts, go-task, make, just, VS Code tasks, etc). An empty list is normal (many repos declare nothing runnable); use overseer_run with a raw cmd instead. Each entry has `provider` (which task runner it came from) and `desc`, which is null when that provider supplies no descriptions (npm and make never do, go-task and just usually do), so a null desc means there is nothing to read, not that something was withheld. Entries that have a description are listed first. `params` lists arguments a template takes, with `required` marking those that overseer_run will reject the call without. `running_task_id` is present when a task of that name is already running, which is your signal not to start a second one. Pass `filter` to match a substring against name and desc; worth doing in a large monorepo, where this can return well over eighty entries.
Restart a task by id or name substring (stops it first if running). A name matching more than one task is an error listing the candidates; pass a numeric id instead. Running tasks the user started are refused unless `force` is set.
overseer_restart, overseer_stop, overseer_dispose lack explicit recovery guidance in error messages. When a name substring matches multiple tasks, the error lists candidates but does not guide LLM on next step (retry with ID vs. refine substring).
overseer_run parameter descriptions do not explicitly state that passing both 'template' and 'cmd' is an error, or what happens if 'params' are provided but the template does not declare params. Parameter interdependencies should be documented.
overseer_tail 'task' parameter description says 'omit for most recent task' but does not state the default behavior explicitly (returns last task by ID). When 'timeout_ms' exceeds 120000 (max), error message should guide user on valid range.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 67 | 2026-07-28+ | v2 |
Start a long-running command as an overseer task, so it appears in the user's task list, can be stopped from it, and inherits proper process-group teardown. Use for commands that DO NOT exit on their own: dev servers, file watchers, --watch test runs, log tails. For short commands that terminate by themselves, use Bash instead: you need their output in-band, and round-tripping a fast build through start-then-poll is worse. Pass exactly one of `template` or `cmd`. Prefer `template` (a name from overseer_list_templates) when one matches what you want; it runs the repo's own definition under the name the user already sees in their task list. Most repos declare no templates, in which case pass `cmd` as an argv array, which works anywhere. Note a `cmd` is a one-off, though: gone when the session ends, and the user cannot run it again without you. If they want it to persist, it belongs in a directory-local template instead of being re-passed as `cmd` every time; the server instructions describe how. `cmd` does not go through a shell, so for pipes, globs, `&&` or env prefixes pass an explicit wrapper: ["sh", "-c", "..."]. Returns once the task has produced output or exited, up to `settle_ms` (default 1500, 0 to return immediately), so a command that dies on startup reports its failure here rather than looking identical to a healthy one until some later tail. Templates that declare `params` need them supplied here; pass `params` as an object of name to value. A missing required param comes back as an error naming what it wanted rather than a prompt opening in the editor.
Stop a running task by id or name substring. Overseer stops the underlying nvim job, so the task's process tree is torn down rather than left orphaned. A name matching more than one task is an error listing the candidates. Running tasks the user started are refused unless `force` is set, since a substring like 'dev' can match theirs.
Return a task's live output, preceded by a status line. `task` is a numeric id or a case-insensitive name substring; omit it for the most recent task. The status line reports `status`, `exit_code` once the task has exited, and `total` (lines available so far), so you never need a separate overseer_list_tasks to find out whether what you are tailing is still alive. Pass the previous `total` back as `since` to get only what is new instead of re-reading the same lines; `from` tells you where the returned block actually starts, and a `from` greater than `since` + 1 means output scrolled past between calls. Set `wait_for` to a regular expression to block until a matching line appears; use it instead of polling repeatedly. It returns as soon as it matches, or when the task exits, or at `timeout_ms` (default 15000, max 120000), and reports which of the three happened as `waited`, so a timeout is never mistaken for success.
Error responses use guard() wrapper that catches exceptions and returns isError=true, but error text is a bare exception message. For API/RPC failures, should include specific guidance: e.g., if Neovim RPC fails, suggest checking Neovim server status or reconnecting.