Skip to main content
Glama

workspace-status-mcp

Українською

An MCP server with six tools:

  • sweep_status — a one-call snapshot of every git repository under a given folder: branch, uncommitted changes, unpushed commits, and (optionally) the latest GitHub Actions CI conclusion. Replaces manually looping git status + gh run list over dozens of repos one at a time.

  • check_docs — flags which repos' docs (<repo>/.arch/, see Doc layout) are missing or stale. Doesn't write or regenerate anything itself (understanding a codebase well enough to document it is an LLM/human job, not a script's) — it just says where to look, so docs get updated deliberately instead of silently rotting.

  • write_doc — writes one doc file (<repo>/.arch/<file>) and stamps it with the repo's current commit hash, so check_docs can later measure staleness precisely (commits since write) instead of guessing from file mtime.

  • check_release_drift — for explicit (source repo, release repo) pairs, counts how many commits landed in the source since the release repo's last git tag, and how old the oldest one is. Cutting a release is usually a manual "whenever I remember" step (tag a version, push it, CI builds and publishes) — this answers "has anyone actually done that lately" without checking by hand.

  • read_doc — reads a repo's docs in PARTS instead of whole files: no arguments → the list of files in .arch/ plus a numbered table of contents of README.txt; file → another file; section (number or title fragment) → one section; query → only the matching lines with context (across ALL files unless file is given). Recognises underlined, ##, ALL-CAPS, dated-entry and --- entry --- / === entry === / [YYYY-MM-DD] headings.

  • check_pr_status — a one-call snapshot of several GitHub PRs at once: state, mergeable, review decision, CI status, and — separately — how many top-level and inline review comments each has, with the latest author/timestamp of each kind. Top-level (issue) comments and inline (review) comments are two genuinely different GitHub API resources; a PR reviewed with inline comments only can look untouched if you check just the review body. Replaces looping gh pr view + two separate gh api .../comments calls per PR.

Why

Working across ~50 repositories in the same workspace, "what actually needs attention right now" was a real recurring question — checked by hand, repo by repo, over and over in the same session. sweep_status answers it in one call and, by default, only returns repos that actually need a look (dirty working tree, unpushed commits, or a CI run that isn't a plain success) — clean repos are silently skipped so the answer stays short.

check_docs exists for the same reason, one level up: a per-project architecture doc is only useful if it's trusted, and it's only trusted if someone actually checks it's current. Comparing "last commit" to "doc's mtime" turns that from a thing you have to remember into a thing you can just ask.

Related MCP server: devagent

Doc layout: .arch/ and backup

Since v0.7.0 each repo keeps its docs inside itself, in a git-ignored .arch/ folder, instead of one big <repo>.txt in a central folder (the big ones had grown to 1500 lines / 150 KB, unreadable as one file):

<projectsRoot>/<repo>/.arch/
    README.txt      overview: what it is, stack, layout, list of the other files
    <topic>.txt     topic files for big projects (e.g. usb.txt, disk-installer.txt)
    changelog.txt   dated entries ("what changed") — new ones are appended here
    .meta.json      {commitHash, writtenAt} written by write_doc
  • Add .arch/ to your global gitignore (~/.config/git/ignore) so it never lands in a (public) repo.

  • Backup. Because .arch/ is ignored, nothing else keeps a copy of it. bin/arch-backup.mjs <projectsRoot> <backupDir> copies every <repo>/.arch/ into a separate (private) git repo as <backupDir>/<repo>/…, commits and pushes. It only ever adds/updates — never deletes: a lost .arch/ (or a whole deleted project) stays in the backup. Symlinks are not followed. A lock file stops two runs from colliding; pushes are throttled to once per 30 min unless --push-now.

  • It runs automatically from two places: the check-docs-reminder hook on every Claude Code Stop (detached, when <projectsRoot>/Architecture-backup/.git exists), and a global git pre-push hook (hooks/git/dispatch.sh, installed by hooks/git/install.sh as core.hooksPath) — so every git push of any repo also pushes the docs backup. The dispatcher first runs the repo's own .git/hooks/<name> if there is one (setting core.hooksPath would otherwise disable it) and never blocks the push.

  • The old central layout still works: pass docsRoot explicitly (tool argument or watch point) and <docsRoot>/<repo>.txt + <docsRoot>/.meta/<repo>.json are used exactly as before.

Claude Code hook: check-docs-reminder

The tools above only help if something actually calls them. hooks/check-docs-reminder.mjs closes that gap: registered as a SessionStart + Stop hook in ~/.claude/settings.json, it runs checkDocs() itself against whatever repo Claude's current working directory is under (walking up to the nearest git root that's a direct child of the projects folder), and — only when that repo's doc is missing or stale — injects a one-line reminder into Claude's context via hookSpecificOutput.additionalContext. Silent otherwise (clean repos, or a cwd outside the projects folder, produce no output; likewise if no repo under the projects folder has a .arch/ yet (or, for a point with an explicit docsRoot, that folder doesn't exist) — someone who's never opted into this convention doesn't get nagged about every repo being "missing"). SessionStart covers forgetting between sessions; Stop (which fires each time Claude's turn ends) re-checks every turn within the same session too, and self-quiets the moment write_doc actually gets called. Deliberately non-blocking — a stale doc is worth a nudge, not a halted turn.

Not hardcoded to any one person's folder layout, and works the same on Windows as Linux/macOS. Register it with the args array ("exec form" — spawned directly, no shell involved, so there's no bash-vs-PowerShell-vs-cmd syntax difference to worry about):

{
  "hooks": {
    "SessionStart": [{ "hooks": [{ "type": "command", "command": "node",
      "args": ["/path/to/workspace-status-mcp/hooks/check-docs-reminder.mjs", "SessionStart"] }] }],
    "Stop": [{ "hooks": [{ "type": "command", "command": "node",
      "args": ["/path/to/workspace-status-mcp/hooks/check-docs-reminder.mjs", "Stop"] }] }]
  }
}

Watch points

Not every repo necessarily lives under one root — one might get moved out of ~/Projects onto the Desktop, say, with its doc sitting right next to it there. The hook resolves "watch points" (projectsRoot + optional docsRoot pairs) in this order, using the first point whose projectsRoot contains the repo you're currently in:

  1. CLI args (args[2]/args[3] after the event name) — a one-off single-point override.

  2. The watch-points config file — ~/.claude/workspace-status-points.json by default (override the path with WATCH_POINTS_FILE). This is the normal way to manage this day to day:

    {
      "points": [
        { "projectsRoot": "/home/sviat/Projects" },
        { "projectsRoot": "/home/sviat/Desktop", "docsRoot": "/home/sviat/Desktop" }
      ]
    }

    Add a point, remove one (or all of them), or redirect an existing one just by editing this file — no code change, no re-registering the hook. A missing file, or an empty/absent points array, falls through to the next source below.

  3. PROJECTS_ROOT/DOCS_ROOT environment variables — single-point fallback for anyone registering the hook through a shell command instead of the args exec form.

  4. Default: a single point at <home>/Projects.

check_docs/sweep_status accept the same idea directly as points/roots arguments (see below) if you want to query multiple locations from a conversation without touching the config file.

Install

npm install

Requires the GitHub CLI (gh), authenticated, if you want CI status (check_ci: true, the default). Without it CI results just come back as null per repo.

Cross-platform — all six tools and the hook are plain Node.js (path.join, os.homedir(), no hardcoded /) shelling out to git/gh, both of which run natively on Windows too. No platform-specific code path.

Updating

There's no separate build or publish step — claude mcp add points straight at this checkout's src/server.js, so updating is just:

git pull && npm install

Take effect on the next new Claude Code session (each session spawns its own MCP server process, so an already-running session keeps using the code it started with).

Tool: sweep_status

Argument

Type

Default

Meaning

root

string

— (required unless roots)

Folder to scan, one level deep (e.g. /home/user/Projects)

roots

string[]

—

Several roots in one call instead of one root — results are merged (e.g. repos split between ~/Projects and elsewhere)

repos

string[]

all subfolders

Limit to specific repo names instead of scanning everything (matched across all roots)

check_ci

boolean

true

Also query GitHub Actions for each repo's latest run

only_attention

boolean

true

Only return repos that need a look; false returns everything

Each repo entry: name, path, branch, uncommittedFiles (count), ahead/behind (vs upstream, null if no upstream configured), hasUpstream, and ci ({status, conclusion, workflow, url} or null).

Tool: check_docs

Argument

Type

Default

Meaning

projectsRoot

string

— (required unless points)

Folder with the repos

docsRoot

string

— (in-repo .arch/)

Old central layout only: folder with <repo>.txt docs

points

{projectsRoot, docsRoot?}[]

—

Several independent projectsRoot+docsRoot pairs in one call instead of one projectsRoot/docsRoot (e.g. a repo moved out of ~/Projects, doc sitting right next to it wherever it went)

repos

string[]

all subfolders

Limit to specific repo names (applied within each point independently)

only_attention

boolean

true

Only return missing/stale; false returns everything including current

Each repo entry: name, projectsRoot (which point it came from), docPath, status (missing / stale / current / no-commits), trackingMethod (commit if the doc was written via write_doc, mtime otherwise — see below), lastCommitAt, and either commitsSinceWrite/writtenAtCommit/writtenAt (commit tracking) or docUpdatedAt/staleBySeconds (mtime tracking, only on stale).

Tool: write_doc

Argument

Type

Default

Meaning

projectsRoot

string

— (required)

Folder with the repos

repo

string

— (required)

Repo folder name (e.g. "anylint")

content

string

— (required)

With mode:"replace" - full text of the file. With mode:"append" - just the new chunk

file

string

"README.txt"

File inside <repo>/.arch/; a bare *.txt name (no path). New dated entry → "changelog.txt" + mode:"append"

docsRoot

string

— (in-repo .arch/)

Old central layout only: folder with <repo>.txt docs (file is ignored)

mode

"replace" | "append"

"replace"

"append" appends content to the end of the existing file (one blank-line separator)

Writes <repo>/.arch/<file> and <repo>/.arch/.meta.json with the repo's HEAD commit hash at write time. check_docs then reports the exact number of commits since the doc was written (git rev-list --count) instead of the coarser mtime-vs-last-commit-time comparison — the same pattern check_release_drift already uses for source→release drift. Docs written directly (e.g. via a plain file write, not this tool) keep using mtime tracking — there's no meta file to compare against.

mode:"append" exists for one specific case: adding a single new dated entry to an already-large (hundreds/thousands of lines) doc file without first reading the whole file into context and sending it back unchanged just to append one paragraph — that pattern used to waste a lot of tokens for no reason.

Tool: check_release_drift

Argument

Type

Default

Meaning

projectsRoot

string

— (required)

Folder with the repos

pairs

{source, release}[]

— (required)

Explicit list of source→release folder-name pairs

only_attention

boolean

true

Only return drifted; false returns everything including current/no-tags

Each pair entry: source, release, status (drifted / current / no-tags), and on drifted: latestTag, tagCreatedAt, commitsSinceTag, oldestUnreleasedCommitAt, oldestUnreleasedAgeDays.

Tool: check_pr_status

Argument

Type

Default

Meaning

prs

{repo, number}[]

— (required)

PRs to check, repo as "owner/name"

only_attention

boolean

true

Only return PRs that are DIRTY (merge conflict), CHANGES_REQUESTED, or have a failing CI check; false returns everything

Each entry: title, url, state, mergedAt, mergeable, mergeStateStatus, reviewDecision, ciStatus (success / failure / pending / none), comments ({total, last: {author, at} | null} — top-level issue comments), reviewComments (same shape, inline review comments), needsAttention. A PR that fails to fetch (bad repo/number) gets {repo, number, error} instead of throwing and losing the rest of the batch.

classifyPr() in src/pr-status.js is the pure decision logic (given already-fetched raw data, no network) — unit-tested with fixtures separately from the real gh calls, which are verified against actual merged PRs instead.

Architecture

  • src/sweep.js — all the logic: finds .git folders one level under a root (exported as findGitRepos, reused by docs.js), then for each one runs git branch/git status/git rev-list and (optionally) gh run list in parallel, batched at 8 repos at a time to avoid hammering the GitHub API. sweepStatus()'s roots (plural) runs findGitRepos per root and merges the results before filtering by repos/only_attention — so a repo name filter matches regardless of which root it actually lives under.

  • src/arch.js — where a repo's docs live (locate(): in-repo .arch/ or the old central docsRoot), which files they consist of (listDocFiles(), README first, changelog last) and the file-name guard.

  • src/docs.js — checkDocs(): prefers commit-based tracking (.meta.json, written by write_doc) when available; falls back to comparing git log -1 --format=%ct against the newest doc file's mtime for docs written directly. Skips Architecture / Architecture-* folders (the doc stores themselves). A repo with no commits yet reports no-commits rather than being silently lumped into missing or current. points (plural) checks each independent projectsRoot+docsRoot pair in turn and tags every result with which one it came from.

  • src/write-doc.js — writeDoc(): writes one .arch/<file> plus .arch/.meta.json ({commitHash, writtenAt}, HEAD at write time). Doesn't generate the text itself — understanding a codebase well enough to document it stays an LLM/human job.

  • src/pr-status.js — checkPrStatus(): fetches gh pr view plus both comment endpoints (issues/{n}/comments for top-level, pulls/{n}/comments for inline review comments — deliberately separate, since a PR reviewed only with inline comments looks untouched if you check just the review body) per PR, batched at 6 at a time. classifyPr() is the pure part (decides needsAttention, picks the latest comment of each kind) — no network, so it's tested with fixtures independently of the real gh calls.

  • src/server.js — registers all six tools with the MCP SDK over the stdio transport.

  • test/smoke.mjs — sweep_status against real local repos for the clean/dirty cases (no synthetic fixtures needed — the workspace itself already has both to test against), a throwaway local git repo for the no-upstream case (an incidental empty folder under ~/Projects used to serve this, until it got renamed away mid-session and silently broke the test — not something to depend on again), plus a roots (plural) case against a real root and a throwaway empty one.

  • test/pr-status.mjs — classifyPr() against fixture data (clean/DIRTY/ CHANGES_REQUESTED/CI-failure/CI-pending, comments counted separately per kind, a merged PR never flagged regardless of leftover DIRTY state), plus checkPrStatus() against real, permanently-merged PRs in two different repos (mergedAt/state won't change), a multi-PR batch call, and a nonexistent PR number returning {error} in its own entry instead of failing the whole batch.

  • test/docs.mjs — check_docs against temporary, throwaway git repos with controlled commit/file timestamps (real ~/Projects drifts over time, which would make a fixed test flaky), including both tracking methods and a points (plural) case.

  • test/write-doc.mjs — writeDoc() against a temporary git repo: correct HEAD captured, .meta/ created on demand, empty content rejected.

  • test/hook.mjs — hooks/check-docs-reminder.mjs as a real subprocess (it's a CLI entry point, not a library function): silent outside any watch point, silent when no repo has .arch/ yet, reminds and resolves the right repo from a nested subdirectory, the watch-points config file driving two independent points at once, and an empty/absent points array in that file falling through to the next source, and Stop spawning the backup.

  • bin/arch-backup.mjs — the backup (see Doc layout). hooks/git/dispatch.sh + install.sh — the global git hooks.

  • test/arch-backup.mjs — backup in throwaway folders only: copies and pushes to a local bare remote, never deletes (lost .arch/ and a deleted project both survive and restore), ignores symlinks, lock and push throttling, and the real pre-push dispatcher end-to-end (local hook still runs; a failing local hook still blocks the push).

  • src/release-drift.js — checkReleaseDrift(): finds the release repo's most recent tag via git for-each-ref --sort=-creatordate (sorted by actual tag time, not the semver-string sort -v:refname would give — v1.10 would otherwise sort before v1.9), then counts git log --since=@<tagTimestamp> in the source repo. The source↔release relationship isn't guessable from folder structure (no general rule "folder X releases folder Y"), so the caller passes pairs explicitly.

  • test/release-drift.mjs — temporary repos with explicit GIT_AUTHOR_DATE/GIT_COMMITTER_DATE per commit (not relying on real wall-clock gaps between commits made milliseconds apart in a test run, which git log --since's second-level granularity could otherwise make flaky), plus one live check against the real NyxilumLang→NyxilumNode pair that only asserts it doesn't throw.

Docker

docker build -t workspace-status-mcp .
docker run -i --rm -e GH_TOKEN=<token> -v "$HOME/Projects:/projects" workspace-status-mcp

The server speaks MCP over stdio, so keep -i. Needs: your projects folder mounted (pass /projects as projectsRoot) and GH_TOKEN for PR/release checks. In an MCP client config use "command": "docker" with the same arguments.

Startup timeout

The server itself starts in about a second, but a client launching several MCP servers at once on a cold machine (right after boot) can hit Claude Code's default 30-second startup limit and mark it "failed". That limit belongs to the client, not to the server, so raise it there - in ~/.claude/settings.json:

{ "env": { "MCP_TIMEOUT": "120000" } }

(or start Claude Code as MCP_TIMEOUT=120000 claude). Takes effect in the next session.

License

MIT — Faneraiy14.

Available Tools

4 tools
check_docsДетектор застарілої/відсутньої архітектурної документаціїA

Перевіряє актуальність ~/Projects/Architecture/.txt кожного git-репозиторія: "missing" (документації нема взагалі), "stale" (репо мав нові зміни після документа), "current" (актуально), "no-commits" (репо ще без жодного коміту). Для документів, записаних через write_doc, рахує ТОЧНУ кількість комітів з моменту запису (trackingMethod: "commit"); для решти - грубший запасний варіант за mtime файлу (trackingMethod: "mtime"). НЕ генерує/переписує документацію сам - лише каже, куди дивитись, щоб AI-асистент (чи людина) писав/оновлював цілеспрямовано, а не перечитував усе підряд щосесії. Документація розкидана між кількома незалежними парами корінь+Architecture-тека (напр. репо, винесене з ~/Projects, з документом прямо поруч на новому місці)? Передай points замість projectsRoot/docsRoot.

Inputs: repos, points, docsRoot, projectsRoot, +1 more.

check_release_driftДетектор "реліз відстав від джерела"A

Для явних пар (репо з вихідним кодом, репо, що з нього тегує релізи) рахує, скільки комітів з'явилось у джерелі відколи реліз востаннє тегувався, і наскільки давно найстарший із них. Зв'язок джерело->реліз не вгадується автоматично зі структури тек - передавай пари явно (напр. {source:"NyxilumLang", release:"NyxilumNode"}).

Inputs: pairs (required), projectsRoot (required), only_attention.

sweep_statusЗнімок стану всіх git-репозиторіїв у теціA

Сканує всі git-репозиторії під заданим коренем (типово ~/Projects) і одним викликом повертає, які з них "потребують уваги": незакомічені зміни, неопубліковані коміти, або невдалий/ще не завершений останній CI-запуск. Заміняє ручний цикл git status + gh run list по кожному репо окремо. Репо розкидані між кількома коренями (напр. частина в ~/Projects, частина деінде)? Передай roots замість root - скановуються всі разом, одним викликом.

Inputs: root, repos, roots, check_ci, +1 more.

write_docЗаписати архітектурну документацію репо з прив'язкою до комітуA

Записує ~/Projects/Architecture/.txt і поруч фіксує commit-хеш репозиторія на момент запису. Після цього check_docs може рахувати РЕАЛЬНУ кількість комітів з моменту запису (git rev-list --count) замість грубого порівняння за mtime файлу. Текст документації інструмент не генерує - лише зберігає вже готовий текст (розуміння коду для документування лишається завданням AI/людини).

Inputs: repo (required), content (required), projectsRoot (required), docsRoot.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.4/5.0

    Scored across 4 tools

    Disambiguation5/5

    Each tool targets a distinct concern: sweep_status covers git/CI status, check_docs covers architecture-doc freshness, check_release_drift covers release lag, and write_doc supports doc tracking. Any conceptual overlap, such as staleness, is clearly separated by the resource being checked.

    Naming Consistency5/5

    All tool names follow a consistent snake_case verb_noun pattern: sweep_status, check_docs, check_release_drift, write_doc. The check_ prefix is used uniformly for health/diagnostic operations.

    Tool Count5/5

    Four tools is well-scoped for a workspace-status server: one general status sweep, two specialized checks, and one supporting write operation. No tool is redundant or extraneous.

    Completeness5/5

    The set covers the full workflow for its domain: identifying repo status, checking and writing architecture docs, and measuring release drift. The release-drift pair requirement is an explicit design boundary rather than a missing operation.

    Related MCP Connectors

    Related MCP Servers