Agent Standup
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Agent StandupCreate a task: implement login page"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Agent Standup
A task tracker for AI coding agents: a database, a rules engine every change goes through, an MCP so agents can talk to it, a CLI for the parts MCP can't do, and a web front end.
The point: the rules live in the backend and are enforced rather than requested. An agent can't skip a step, because the server refuses the change.
Docs
Everything is in docs/plans/:
Doc | What it is |
The readable plan — how it works, in plain terms | |
Tables, config, MCP tools, HTTP endpoints | |
Every decision with its reasoning | |
The work, broken into pull requests, in order |
Related MCP server: projscan
Stack
Next.js (front end and API in one bundle) · Prisma · Postgres. The image is built in CI, pushed to GHCR, and pulled wherever it runs — never built on the deploy host, no bind mounts.
Local development
Requires Node 24 and Docker (for local Postgres).
cp .env.example .env # fill in DATABASE_URL etc.
npm install
npm run db:up # starts local Postgres on a non-default port
npx prisma migrate deploy # apply the committed migrations
npx prisma generate
npm run dev # http://localhost:3000Configuration
Only what must be known before the process can reach a database is an
environment variable — DATABASE_URL, plus HOSTNAME and PORT for what
interface and port the server listens on. .env.example lists these and the
handful of others that are genuinely bootstrap (the local Postgres readiness
wait, the disposable shadow database the migration drift check uses).
Authentication is the other bootstrap value: STANDUP_TOKENS holds one
bearer token per machine, and clients present theirs as STANDUP_TOKEN.
It has no default — with it unset the server refuses every authenticated
call, which is deliberate (a gate that switched itself off when its
configuration was missing would be open exactly when a deployment had gone
wrong). It is an environment variable rather than a setting for the same
reason as the rest of this list, plus one specific to it: settings are
served to the front end and printed by the command line, with no redaction
path, so a credential cannot live there.
The front end needs one of those tokens too, and it must be its own. A
browser is not a machine: it holds no configuration, and anything handed to a
page is readable by whoever opens the developer tools, which would make
revoking one machine's access meaningless. So the browser is never given a
credential. It calls /api/ui/*, a server-side route that attaches the token
for the machine named browser (override with STANDUP_BROWSER_MACHINE) and
forwards to the same authenticated handlers every other client reaches — so
the call is authenticated by the ordinary gate rather than exempted from it,
and the token stays in the server process. Configure one alongside the rest:
STANDUP_TOKENS=browser:TOKEN-A,laptop:TOKEN-BWith no token configured for that machine the front end serves a 503 saying so, rather than falling back to calling the API without one.
Everything else is a setting: typed, defaulted in code, and readable and
writable once the app is running, from /settings in the front end or
standup config set on the command line. A fresh database boots fully
working with no settings configured at all — each one has a default. Setting
an old environment variable that has moved into settings does nothing; a
startup check catches this — it fails immediately in development, and logs
loudly (without stopping the process) in production.
Useful scripts:
Command | What it does |
| Next.js dev server |
| Production build / run it |
|
|
| ESLint / Prettier ( |
| Vitest, wrapped so a failing run cannot look green — it also fails on the printed summary, so an empty or failing run is caught even through a pipe. Use this one. |
| Bare |
| Create/apply a dev migration ( |
| Apply committed migrations without prompting ( |
| Fail if |
| Prisma Studio |
The initial baseline migration (the whole schema in one shot — see
SCHEMA.md) lives in prisma/migrations/. CI applies it to a
throwaway Postgres on every run and fails if schema.prisma and the migration history
have drifted apart.
Deployment
The image is built by .github/workflows/release.yml
on a version tag or manual dispatch, and pushed to ghcr.io/<owner>/agent-standup
tagged latest and the version. The package is public, so pulling it needs no
registry credential. Wherever it runs, pull and run it with
docker-compose.prod.yml:
GHCR_IMAGE=ghcr.io/<owner>/agent-standup:latest
DATABASE_URL=postgres://user:password@host:5432/agent_standup
docker compose --env-file .env.production -f docker-compose.prod.yml pull
docker compose --env-file .env.production -f docker-compose.prod.yml up -ddocker-compose.prod.yml has no build: block and no bind mounts by design — it
only ever pulls. It ships a health check on GET /api/health (liveness only —
deliberately doesn't touch the database, so a slow DB doesn't make the process
report unhealthy).
Two probes, answering two different questions. Point each consumer at the one it actually needs, because giving either the other's answer is wrong in a way that is quiet:
Endpoint | Asks | Reads the database | For |
| Is this process alive | No | Restart policies — a container that has stopped serving |
| Can I use this yet | Yes | Deployment gates, |
A process whose Postgres is still starting is alive and not ready, which is normal and common. Report that as unhealthy and a restart policy kills a container that was about to work; report it as ready and a load balancer sends traffic to a process that cannot serve it.
/api/ready answers 200 when it can query the database and no migration is
half-applied, and 503 otherwise, with a body carrying the migration counts:
connected but two migrations behind and migrated and ready are different
answers, and only one is safe to send traffic to. Both probes are
unauthenticated — the things that ask them run before an installation is
configured and hold no credential — and both report only booleans and counts.
Many machines, one server
The schema is built for a fleet: machines is a first-class entity, work is
claimed per session, and assignments records which machine holds what. A
single-host compose file is the simplest deployment of that design, not the
limit of it — the usual shape is one server and its database, and a client
on every machine doing the work.
A remote client talks to the API. It never opens a connection to the database. This is the one deployment rule worth stating outright, because the alternative is available and looks equivalent from the outside:
Every rule this product enforces — a merge needing an approving review at tip, a completion needing a structured summary, a transition needing an approved plan — is application code in the service layer. Postgres does not know those rules exist and cannot be taught them: allowed only with an approving review at tip is conditional on state a grant cannot evaluate.
So a client on
DATABASE_URLdoes not defeat those checks; it never reaches the code that performs them. An item can land inmergedwith no commit, no review and no summary, and nothing in the system is wrong about anything — the rules were simply never consulted.Database-level permissions are not a substitute. A restricted role can refuse a write to a table. It cannot express the condition above, which is the one that matters.
Point each machine at the server and give it its own token:
STANDUP_URL=https://standup.example.internal
STANDUP_TOKEN=<this machine's token>Both the command line and the MCP client use the API when STANDUP_URL is
set. DATABASE_URL belongs to the server alone; a client that has one is
configured as though it were the server.
Tokens are per machine rather than one shared secret, which buys two things: a machine can be revoked without rotating every other machine's configuration, and the actor a client declares stops being an unverified self-report — the server knows which machine presented the token, so an attributed write means something.
The liveness sweep has to be run by something
A deployment that never runs the sweep leaks claims that can never be handed back. A session takes ownership of an item by claiming it; if that session crashes rather than releasing, the claim outlives it and every later claim on that item is refused as already-held. The liveness sweep is what notices — it ages quiet sessions, releases what died, and escalates what is stuck — and it runs only when something invokes it. Measured on an installation running without one: the first manual sweep released 174 stale claims that had been sitting for three days, every one of them blocking ownership of its item.
Running it is the deployment's job, and this compose file ships nothing to do it. The application deliberately has no internal timer. It runs as a bundle that may be one replica or several, so a timer inside it fires once per replica — a multiple of the intended rate on a scaled deployment, or not at all if the replica holding it is the one that restarted — and neither mistake produces any output to notice. Invoke it from outside the process, where there is exactly one of whatever you choose.
Either surface works, and nothing in the application distinguishes the callers:
# Host cron, every five minutes — over HTTP:
*/5 * * * * curl -fsS -X POST -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/sweep >/dev/null
# …or over the command line, which reports what it released:
*/5 * * * * standup sweep --jsonPOST /api/sweep authenticates like every other route, so a scheduler calling
it needs a token in STANDUP_TOKENS the same as any machine. The endpoint is
POST rather than GET on purpose: it writes, and a GET that releases other
sessions' claims is one a crawler or a browser prefetch will invoke without
anyone asking it to. It takes no input, so an empty body is fine.
Worth knowing before you automate it. A timer reclaims on the strength of a liveness signal that may not be written — heartbeats are optional, and the process check is what usually answers — so a session that claims an item and then works for half an hour can look the same as one that crashed. Reclaiming at the point of contention, when another session actually wants that item, is a safer place to be wrong than a fixed tick. Escalation is the part that genuinely needs a push, because nobody is reading by definition.
Postgres
This app needs its own Postgres reachable via DATABASE_URL. Prefer a
dedicated Postgres instance over adding a database to one that already
serves another app — it keeps credentials, backups, and version upgrades
independent, and the cost of one more small container is low. Only share an
existing instance if there's a specific reason to (e.g. a hosting limit on
how many database services are allowed).
If Postgres runs as its own container next to this one, order startup with
depends_on: condition: service_healthy — the entrypoint runs
prisma migrate deploy at boot, which opens a real database connection even
when there are zero pending migrations (expect and ignore
No migration found in prisma/migrations until the baseline migration
ships — see MILESTONES.md). Give Postgres's own health check a generous
start_period: a cold first boot (initdb plus the official image's own
internal restart) can take noticeably longer than a short window allows,
which can make depends_on give up right before Postgres would have come up
healthy on its own.
Deploying alongside other services
Some hosts run several unrelated apps under one shared Docker Compose
project rather than one compose file per app — a shared .env holding
per-service location/config variables, one compose file defining every
service, sub-folders per service holding data only. If that's the target,
fold this app's service block (and a Postgres block per the section above)
into the shared file instead of running docker-compose.prod.yml standalone
— the service definitions are the same either way, only which file they live
in changes. In that setup:
Back up the shared compose file first, before editing it.
Never run a bare
up,down, orrestartwith no service names in a directory that already has other services running from that file — always name the services you mean to affect explicitly, e.g.docker compose up -d agent-standup agent-standup-db. An unscoped command recreates (or stops) everything the file defines, not just what you're deploying.Pick a host port that isn't already in use — check what the shared compose file and the host's listening ports already claim before adding
APP_PORT.Keep real secrets (the generated
DATABASE_URLpassword, etc.) only in that host's own.env— never copied into this repo.
What is built
The service layer holds 69 registered operations (src/lib/service/registry.ts). Every rule
lives there, so an adapter is a thin shell over one service call and adds no rule of its own —
which is what makes a refusal the same refusal whichever way in you came.
The four adapters do not all expose the same set, and the difference is worth knowing before you
pick one. MCP derives its tools from the registry and so carries 66 of the 69, declining three by
written waiver (src/lib/adapters/waivers.ts). The command line routes 46 and the web API 49,
because each maps operations through its own table and those tables lag the registry — service_info
and describe_tool, for instance, are reachable from MCP and the command line but have no HTTP
route. Ask a running instance rather than taking any of this on trust:
standup service info --json # the operation catalogue, and the limits a caller must respect
standup --help # every noun and verb, built from the command table itselfSurface | What it is |
Web API | 45 operations over JSON routes under |
MCP | The agent-facing surface, over streamable HTTP ( |
Command line |
|
Front end | The board, an item detail view, a since-your-last-visit ledger, a settings editor and an admin section |
An item minted through the product walks the full state machine on service calls alone —
plan_review → executing → in_review → merged — because the artifacts each transition guard reads
are writable through the service. The rules are enforced in the service layer, so a refusal is the
same refusal on every surface: a missing approving review at tip, a claim already held, or a
completion with no structured summary is rejected identically whether it arrived from an agent, a
terminal or the API.
The schema ships as one baseline migration, and a one-time bulk import (docs/plans/BACKFILL.md)
loads a backlog held in an external file-based store.
Where the edges are. MILESTONES.md is the honest inventory: it
carries every row with its status, and the queue is worked in dependency order rather than
front-to-back. One limit is worth knowing before deploying: the liveness sweep only runs when
something invokes it — see above, because claims leak while nothing does.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
Official MCP server for Agentwork — delegate tasks to AI agents with human-in-the-loop
MCP Server for an Agent Task Marketplace
Task management your AI agents can actually run. One line becomes a context-ready task over MCP.
Related MCP Servers
- AlicenseAqualityBmaintenanceA portable MCP server that provides a shared persistent working state for AI coding agents, managing tasks, plans, notepads, memory, and project rules across different tools like Claude Code, OpenCode, and Cursor.69MIT
- AlicenseNot gradedqualityAmaintenanceAn MCP server that provides AI coding agents with AST-accurate, context-budget-aware codebase querying, safety gates, and team policy integration via structured tools and a local plugin layer.5624MIT
- AlicenseAqualityCmaintenanceAn MCP server that turns independent AI agents into a coordinated engineering team with shared task board, context, review loop, and enforced plan-implement-review-iterate workflow.24MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for task management that enables AI agents to read, create, update tasks, and track work sessions, allowing agents and humans to collaborate on the same task board.38MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Zaida-3dO/agent-standup'
If you have feedback or need assistance with the MCP directory API, please join our Discord server