standup-mr
by Jubstaaa
README.md
# standup-mr
[](https://glama.ai/mcp/servers/Jubstaaa/standup-mr)
[](https://mcpservers.org/servers/jubstaaa/standup-mr)
[](https://smithery.ai/servers/ilkerbalcilartr/standup-mr)
[](https://mcprush.com/jubstaaa/standup-mr-mcp)
Standup notes from **merge request state**, not commit logs.
Most standup tools read your local `git log`. That answers "what did I type",
which is not what anyone asks in a standup. This one reads GitLab or GitHub:
what is ready to merge, what is blocked, what is waiting on you — and when a
pipeline is red, it opens the job log and tells you **why**.

## What makes it different
| | commit-log tools | standup-mr |
| --------------------------------------------- | ---------------- | ----------------------------------------------------- |
| Source | local `git log` | GitLab or GitHub API |
| Merge request state | ✗ | ready / blocked / draft / stale |
| Review queue | ✗ | pending only, approvals filtered out |
| Failed pipeline | ✗ | error lines from the job trace or the Actions job log |
| Self-hosted (GitLab CE/EE, GitHub Enterprise) | varies | first class |
## Use
```bash
npx standup-mr fetch # JSON, provider auto-detected
npx standup-mr fetch --provider github # GitHub
npx standup-mr fetch --provider gitlab # GitLab
npx standup-mr fetch --markdown # structured digest
npx standup-mr fetch --lang tr # Turkish date labels
```
```bash
npx standup-mr fetch --markdown | npx standup-mr post --google-chat "$URL"
```
### Identity
| | GitHub | GitLab |
| ----------- | ------------------------------------------- | ------------------------------------------------------- |
| Flags | `--host` / `--token` | `--host` / `--token` |
| Env | `GITHUB_HOST` / `GITHUB_TOKEN` | `GITLAB_HOST` / `GITLAB_TOKEN` |
| CLI session | [`gh`](https://cli.github.com/) auth config | [`glab`](https://gitlab.com/gitlab-org/cli) auth config |
GitHub defaults to `github.com` when no host is given. GitLab has no default —
self-hosted is the norm there, so a host must come from a flag, env var, or
`glab`'s own config.
The provider itself is picked in this order:
1. `--provider github` / `--provider gitlab`, if passed
2. a recognizable `--host` (`github.com`, `gitlab.com`, or a hostname
containing `github`/`gitlab`)
3. `STANDUP_PROVIDER`, or whichever of the `GITHUB_*` / `GITLAB_*` env pairs
is set
4. whichever of `gh` / `glab` is logged in
If none of these resolve — or both do, ambiguously — the command fails with a
clear error instead of guessing.
So if you already use `gh` or `glab`, there is nothing to configure.
### Post it to chat
```bash
npx standup-mr fetch --markdown | npx standup-mr post --slack "$SLACK_WEBHOOK_URL"
```
## Hosted connector: any AI assistant
No token, no config file — sign in instead. The hosted server speaks the
standard MCP OAuth flow, so any assistant that supports remote MCP servers can
use it: add the URL, and a browser page opens where you sign in.
```
https://standup.ilkerbalcilar.com/mcp
```
[](https://cursor.com/en/install-mcp?name=standup&config=eyJ1cmwiOiJodHRwczovL3N0YW5kdXAuaWxrZXJiYWxjaWxhci5jb20vbWNwIn0%3D)
[](https://insiders.vscode.dev/redirect/mcp/install?name=standup&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fstandup.ilkerbalcilar.com%2Fmcp%22%7D)
| Assistant | How |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| **claude.ai** (web, mobile, Desktop) | Settings → Connectors → Add custom connector → paste the URL → Connect |
| **Claude Code** | `claude mcp add --scope user --transport http standup https://standup.ilkerbalcilar.com/mcp`, then `/mcp` to sign in |
| **Cursor** | The button above, or `"standup": { "url": "https://standup.ilkerbalcilar.com/mcp" }` in `~/.cursor/mcp.json` |
| **VS Code** (Copilot) | The button above, or `"standup": { "type": "http", "url": "https://standup.ilkerbalcilar.com/mcp" }` in `mcp.json` |
| **Gemini CLI** | `gemini extensions install https://github.com/Jubstaaa/standup-mr`, then `/mcp auth standup` |
| **Codex CLI** | `codex mcp add standup --url https://standup.ilkerbalcilar.com/mcp`, then `codex mcp login standup` |
| **ChatGPT** | With developer mode on: Settings → Apps → Create → paste the URL, OAuth |
claude.ai and Claude Code are tested end to end; the others use the same
standard flow. If one of them doesn't sign in, please
[open an issue](https://github.com/Jubstaaa/standup-mr/issues).
On the sign-in page you link the accounts the note should read:
- **GitHub** — sign in through the standup-mr GitHub App. It only sees the
repositories it is installed on, so install it on your org too; if the org
won't approve it, add github.com with a fine-grained token instead.
- **gitlab.com** — sign in with GitLab.
- **Your company's GitLab or GitHub Enterprise** — type the host. If someone
at your company already set up sign-in, you get a "Sign in with …" button.
Otherwise set it up once: the page shows the exact redirect URI and scopes
for an OAuth application on your host, you paste its ID and secret, and
everyone at that host can sign in from then on. A read-only token works too.
- **Chat webhook** _(optional)_ — Slack, Discord or Google Chat, so your assistant
can post the finished note when you ask it to.
Link as many as you like — say, GitHub for side projects and your company's
GitLab for work — then ask for "my standup" or "my work standup". With
several accounts linked, the assistant asks which one you mean.
Added in an assistant with a mobile app — Claude or ChatGPT — it works from
your phone too: ask for your standup on the way to the meeting.
**What is stored.** Your provider tokens and webhook URL, encrypted with
AES-256-GCM, and only to read your merge requests and post your note. The
access tokens your assistant holds are stored as hashes. Disconnecting it
deletes everything; connections unused for 30 days are deleted. The server is
the code in [`remote/`](remote/).
**When to use the local server instead.** A GitLab behind a VPN or on a
private network is out of reach of a hosted server. Run the stdio server
below on your own machine — the token never leaves it.
## The four surfaces
**CLI** — the core. Emits JSON; zero runtime dependencies.
**MCP server** (`mcp/`) — three tools for Claude Desktop, Claude Code, Cursor,
or any MCP client that runs a local process. See [`mcp/README.md`](mcp/README.md).
**Remote MCP server** (`remote/`) — the same tools over HTTPS with sign-in, for
any assistant that supports remote MCP servers, including the web and mobile
apps. See [above](#hosted-connector-any-ai-assistant).
**Claude Code plugin** — the note-writing playbook, shipped as the `standup`
skill. From inside Claude Code:
```
/plugin marketplace add Jubstaaa/standup-mr
/plugin install standup@standup-mr
```
Then type `/standup`. Updates come with `/plugin marketplace update standup-mr`.
### Using it from Cursor, Codex, or another assistant
If you're not using Claude Code, wire up the MCP server for live data and
paste in the note-writing rules separately.
**Cursor** — add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json`
(project-local):
```json
{
"mcpServers": {
"standup": {
"command": "npx",
"args": ["-y", "standup-mr", "mcp"],
"env": {
"GITHUB_TOKEN": "ghp_..."
}
}
}
}
```
**Codex** — add to `~/.codex/config.toml`:
```toml
[mcp_servers.standup]
command = "npx"
args = ["-y", "standup-mr", "mcp"]
[mcp_servers.standup.env]
GITHUB_TOKEN = "ghp_..."
```
The exact config key and file path are version-dependent for both clients —
if a snippet above doesn't work, check [Cursor's MCP
docs](https://docs.cursor.com/context/mcp) or Codex's own config
documentation for the current format rather than trusting this file blindly.
The server exposes three tools:
| Tool | What it does |
| ----------------------- | -------------------------------------------------------------------------------------------- |
| `get_standup_data` | Reads the provider and returns the report as JSON. Optional `provider`, `host`, `lang`. |
| `get_note_instructions` | Returns the note-writing rules, so the assistant can write the note the way the skill would. |
| `post_standup_note` | Posts a finished note to a Slack, Discord or Google Chat webhook. |
`post_standup_note` reads the webhook URL from `STANDUP_WEBHOOK_URL` and never
takes it as an argument — anyone holding that URL can post to the channel, so
it belongs with the tokens, not in a transcript. The payload shape is inferred
from the URL host; `kind` is only needed when a proxy hides it.
Three shapes are implemented: `slack` and `google-chat` both post `{"text"}`,
`discord` posts `{"content"}`.
Slack and Google Chat do not render standard Markdown, so the note is rewritten
on the way out: `**bold**` becomes `*bold*` and a `##` heading becomes a bold
line. Inline code, fenced blocks and their contents are left alone. Discord
speaks Markdown natively and is sent untouched. Anything else that accepts a Slack-shaped body —
Mattermost, Rocket.Chat, an n8n or Zapier endpoint — works today by passing
`kind: "slack"`, or `--slack URL` on the CLI.
Outside MCP, the same rules are available on stdout:
```bash
npx standup-mr instructions >> AGENTS.md
```
## `--markdown` is a digest, not a written note
`--markdown` organizes the raw material into readable sections. It does **not**
group events into themes or diagnose blockers — that is the model's work, and it
lives in the MCP client's prompt or in the Claude Code skill.
- Without AI: a structured digest.
- With AI: a note you can read out.
## What the digest looks like
Anonymised output from a real Monday run — note that Friday and Saturday each
get their own section, and that a merge request GitLab has not evaluated is not
called ready:
```markdown
# Monday, 31 August — dev
_Structured digest — not a written note._
## Previous working day: Friday, 28 August
- `acme/ui` pushed to — fix(keyboard): scale keys to viewport (4 commits)
- `acme/ui` accepted — chore(deps): bump @acme/ui to 0.5.18
- `acme/api` opened — feat: package subscription sales
## Previous working day: Saturday, 29 August
- `acme/ui` pushed to — fix(keyboard): close the autofill bar (1 commit)
## Ready to merge (2)
- `acme/api` !196 fix: normalise the +90 trunk prefix
- `acme/web` !194 refactor: loading state — **no pipeline ran**
## Blocked (1)
- `acme/terminal` !49 fix: relative date chips — **1 unresolved comment(s)**
## Reviews (2 pending)
- `acme/mobile` !501 chore: upgrade to RN 0.87 — Teammate
## Blockers
- `acme/mobile` !6 — job `quality`
- `npm ERR! code E404`
- `npm ERR! 404 Not Found - GET https://registry.example.com/@acme%2fui`
```
The last section is the point of the tool. Every other standup tool can tell you
that pipeline is red; this one opens the failed job's log and shows you the
404 — and a 404 rather than a 403 usually means the token's scope is wrong, not
that the package is missing.
## Stability
Since 1.0.0 the CLI commands and flags, the JSON that `fetch` and
`get_standup_data` return, the MCP tool names and arguments, and the
environment variables follow [semantic versioning](https://semver.org/): a
change that would break an existing setup waits for a major release. New
fields in the JSON may appear in minor releases, so read it by name rather
than by position. The hosted server at `standup.ilkerbalcilar.com` is
updated continuously and is not tied to the npm version.
## Known limits
- **GitHub's events feed is shallow.** It is capped at roughly 300 events over
the last 90 days, so a very active account or an old gap can silently lose
the earliest events. GitLab has no comparable documented cap.
- **GitHub activity is only visible to a token belonging to that same
account**, and private-repository events do not show up for anyone else's
token, even with otherwise sufficient scopes.
- **On GitHub, CI that reports only through the legacy commit-statuses API**
— still how some vendors integrate — shows up as `pipelineMissing`. Check
state is read from check-runs only.
- **A blocker whose diagnosis could not be fetched is still reported**, as
`job: "unknown"` with a `diagnosis unavailable: …` error line. The merge
request is blocked either way; only the explanation is missing. Server
errors are retried twice first, and a rejected token still fails the run.
## Upgrading from 0.1.x
0.2.0 changes the JSON output, the library API, and the MCP options. If you
pipe `standup fetch` into anything, or import the package, read
[`CHANGELOG.md`](CHANGELOG.md) before upgrading. The short version:
- `previous` and `previousEvents` are replaced by `previousDays[]`, one entry
per active day, so a weekend no longer swallows Friday. `jq .previous` now
returns `null` with no error.
- `MergeRequest`, `Review` and `Blocker` carry a required `provider` field, and
`Provider.getReviews` takes an `Identity` rather than a numeric id.
- The MCP `CollectOptions.provider` is now a provider _name_; inject a
`Provider` instance through `providerImpl`.
Claude Code plugin users should run `/plugin marketplace update standup-mr` —
the `standup` skill changed along with the report shape.
## Requirements
Node 20 or newer. The CLI core (`fetch`, `post`, `instructions`) pulls no
runtime dependencies. The MCP server (`mcp` command) brings one:
`@modelcontextprotocol/sdk`.
## License
MIT
TDQS
A4.5/5.0
Scored across 3 tools
Disambiguation5/5
The three tools form a clear pipeline: fetch data, fetch writing rules, then post the note. Each has a distinct purpose and no two could reasonably be confused (read vs write, data vs rules vs delivery).
Naming Consistency5/5
All three follow a consistent verb_noun pattern (get_standup_data, post_standup_note, get_note_instructions) in snake_case, with no deviations.
Tool Count5/5
Three tools map exactly onto the workflow's essential steps—fetch snapshot, fetch playbook, post note—with nothing redundant or missing.
Completeness4/5
The surface covers the full standup workflow end to end. Minor gaps are inherent to the stated scope (read-only snapshot, no MR search/filter), which is documented rather than an oversight.
Maintenance
ActivityMaintained
ResponsivenessNo issues