standup-mr 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,37 @@ All notable changes to standup-mr are recorded here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
5
  [semantic versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.3.0] - 2026-09-01
8
+
9
+ ### Added
10
+
11
+ - `standup mcp` — starts the stdio MCP server directly from the CLI, so
12
+ `npx -y standup-mr mcp` works without a local clone.
13
+ - `standup instructions` — prints the note-writing playbook (the standup
14
+ skill body, minus its YAML frontmatter) to stdout, for teaching a
15
+ non-Claude assistant the same rules, e.g.
16
+ `npx standup-mr instructions >> AGENTS.md`.
17
+ - README section for using the MCP server from Cursor, Codex, or another
18
+ assistant.
19
+
20
+ ### Fixed
21
+
22
+ - The skill wrote the note in English even when the user was speaking Turkish.
23
+ The language rule was buried as a clause inside a sentence about section
24
+ structure, and `fetch` was called with no `--lang`, so every date label in
25
+ the JSON came back English and outweighed it. The rule is now a standalone,
26
+ unmissable line, and the skill passes `--lang` to match the user's language
27
+ so the payload reinforces the note instead of fighting it.
28
+ - On a machine where both `gh` and `glab` are authenticated, every run failed
29
+ once before succeeding: the skill called `fetch` with no `--provider`, hit
30
+ the "both authenticated" error, then retried with the flag. The skill now
31
+ passes `--provider` on the first call whenever it's known, and only asks the
32
+ user (instead of guessing) when it genuinely isn't.
33
+
34
+ Nothing breaking. `@modelcontextprotocol/sdk` moves from a dev dependency to
35
+ a regular dependency, since the CLI can now start the MCP server itself; the
36
+ CLI's `fetch`/`post`/`instructions` path still pulls nothing.
37
+
7
38
  ## [0.2.1] - 2026-08-31
8
39
 
9
40
  ### Fixed
package/README.md CHANGED
@@ -76,6 +76,52 @@ skill. From inside Claude Code:
76
76
 
77
77
  Then type `/standup`. Updates come with `/plugin marketplace update standup-mr`.
78
78
 
79
+ ### Using it from Cursor, Codex, or another assistant
80
+
81
+ If you're not using Claude Code, wire up the MCP server for live data and
82
+ paste in the note-writing rules separately.
83
+
84
+ **Cursor** — add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json`
85
+ (project-local):
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "standup": {
91
+ "command": "npx",
92
+ "args": ["-y", "standup-mr", "mcp"],
93
+ "env": {
94
+ "GITHUB_TOKEN": "ghp_..."
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ **Codex** — add to `~/.codex/config.toml`:
102
+
103
+ ```toml
104
+ [mcp_servers.standup]
105
+ command = "npx"
106
+ args = ["-y", "standup-mr", "mcp"]
107
+
108
+ [mcp_servers.standup.env]
109
+ GITHUB_TOKEN = "ghp_..."
110
+ ```
111
+
112
+ The exact config key and file path are version-dependent for both clients —
113
+ if a snippet above doesn't work, check [Cursor's MCP
114
+ docs](https://docs.cursor.com/context/mcp) or Codex's own config
115
+ documentation for the current format rather than trusting this file blindly.
116
+
117
+ The tool only returns data; the assistant still needs the note-writing rules
118
+ that the Claude Code skill carries. Paste them into whatever instructions
119
+ file your assistant reads (e.g. `AGENTS.md`):
120
+
121
+ ```bash
122
+ npx standup-mr instructions >> AGENTS.md
123
+ ```
124
+
79
125
  ## `--markdown` is a digest, not a written note
80
126
 
81
127
  `--markdown` organizes the raw material into readable sections. It does **not**
@@ -162,7 +208,9 @@ the `standup` skill changed along with the report shape.
162
208
 
163
209
  ## Requirements
164
210
 
165
- Node 20 or newer. No runtime dependencies.
211
+ Node 20 or newer. The CLI core (`fetch`, `post`, `instructions`) pulls no
212
+ runtime dependencies. The MCP server (`mcp` command) brings one:
213
+ `@modelcontextprotocol/sdk`.
166
214
 
167
215
  ## License
168
216
 
package/dist/cli.js CHANGED
@@ -8,8 +8,9 @@ import {
8
8
  } from "./chunk-DL2P3XR6.js";
9
9
 
10
10
  // src/cli/cli.ts
11
- import { realpathSync } from "fs";
12
- import { pathToFileURL } from "url";
11
+ import { existsSync, readFileSync, realpathSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ import { fileURLToPath, pathToFileURL } from "url";
13
14
  import { parseArgs } from "util";
14
15
 
15
16
  // src/cli/cli.constants.ts
@@ -19,13 +20,20 @@ Usage:
19
20
  standup fetch [--provider github|gitlab] [--host H] [--token T]
20
21
  [--lang en|tr] [--markdown]
21
22
  standup post (--slack URL | --discord URL) [--text TEXT]
23
+ standup mcp
24
+ standup instructions
22
25
 
23
26
  The provider is taken from --provider, then STANDUP_PROVIDER, then whichever of
24
27
  GITHUB_* / GITLAB_* is set, then whichever of gh / glab is logged in.
25
28
 
26
29
  Credentials resolve as: flag, then GITHUB_HOST / GITHUB_TOKEN (or GITLAB_HOST /
27
30
  GITLAB_TOKEN), then the gh or glab config.
28
- --text defaults to '-', meaning read stdin.`;
31
+ --text defaults to '-', meaning read stdin.
32
+
33
+ standup mcp starts the stdio MCP server, exposing get_standup_data.
34
+ standup instructions prints the note-writing playbook (the standup skill body)
35
+ to stdout, for teaching a non-Claude assistant the same rules, e.g.:
36
+ npx standup-mr instructions >> AGENTS.md`;
29
37
 
30
38
  // src/cli/cli.ts
31
39
  async function readStdin() {
@@ -33,6 +41,27 @@ async function readStdin() {
33
41
  for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
34
42
  return Buffer.concat(chunks).toString("utf8");
35
43
  }
44
+ function findPackageRoot(startDir) {
45
+ let dir = startDir;
46
+ while (!existsSync(join(dir, "package.json"))) {
47
+ const parent = dirname(dir);
48
+ if (parent === dir) {
49
+ throw new Error(`Could not locate package.json above ${startDir}`);
50
+ }
51
+ dir = parent;
52
+ }
53
+ return dir;
54
+ }
55
+ function readStandupSkillBody() {
56
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
57
+ const skillPath = join(findPackageRoot(moduleDir), "skills", "standup", "SKILL.md");
58
+ if (!existsSync(skillPath)) {
59
+ throw new Error(`Cannot find the standup skill file at ${skillPath}`);
60
+ }
61
+ const content = readFileSync(skillPath, "utf8");
62
+ const withoutFrontmatter = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
63
+ return withoutFrontmatter.replace(/^\n+/, "");
64
+ }
36
65
  async function main(argv) {
37
66
  const [command, ...rest] = argv;
38
67
  if (!command || command === "--help" || command === "-h") {
@@ -80,6 +109,18 @@ async function main(argv) {
80
109
  await postWebhook(url, text, values.slack ? "slack" : "discord");
81
110
  return 0;
82
111
  }
112
+ if (command === "mcp") {
113
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
114
+ const serverPath = join(findPackageRoot(moduleDir), "dist", "mcp", "server.js");
115
+ const serverUrl = pathToFileURL(serverPath).href;
116
+ const { main: startMcpServer } = await import(serverUrl);
117
+ await startMcpServer();
118
+ return 0;
119
+ }
120
+ if (command === "instructions") {
121
+ process.stdout.write(readStandupSkillBody());
122
+ return 0;
123
+ }
83
124
  process.stderr.write(`Unknown command: ${command}
84
125
 
85
126
  ${USAGE}
@@ -3,6 +3,8 @@
3
3
  // mcp/server.ts
4
4
  import { realpathSync } from "fs";
5
5
  import { pathToFileURL } from "url";
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
8
 
7
9
  // src/config/config.ts
8
10
  import { execFileSync } from "child_process";
@@ -964,9 +966,7 @@ async function collect(options = {}) {
964
966
  return buildReport(provider, /* @__PURE__ */ new Date(), options.lang ?? "en");
965
967
  }
966
968
  async function main() {
967
- const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
968
- const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
969
- const server = new McpServer({ name: "standup-mr", version: "0.2.1" });
969
+ const server = new McpServer({ name: "standup-mr", version: "0.3.0" });
970
970
  server.tool(
971
971
  "get_standup_data",
972
972
  "Collect merge-request-based standup data from GitLab or GitHub. Returns the previous working day activity, open merge requests or pull requests bucketed by state (ready / blocked / draft / stale), pending reviews, and the error lines from any failed pipeline or check.",
@@ -987,14 +987,9 @@ try {
987
987
  }
988
988
  if (entryUrl && import.meta.url === entryUrl) {
989
989
  main().catch((error) => {
990
- if (error instanceof Error && error.code === "ERR_MODULE_NOT_FOUND") {
991
- process.stderr.write(
992
- "standup-mr MCP server requires the optional dependency @modelcontextprotocol/sdk. Install it with: npm install @modelcontextprotocol/sdk\n"
993
- );
994
- process.exitCode = 1;
995
- return;
996
- }
997
- throw error;
990
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
991
+ `);
992
+ process.exitCode = 1;
998
993
  });
999
994
  }
1000
995
  export {
package/mcp/README.md CHANGED
@@ -3,12 +3,6 @@
3
3
  Exposes one tool, `get_standup_data`, for either GitLab or GitHub, returning
4
4
  the same JSON as `standup fetch`.
5
5
 
6
- ## Install
7
-
8
- ```bash
9
- npm install standup-mr @modelcontextprotocol/sdk
10
- ```
11
-
12
6
  ## Configure
13
7
 
14
8
  GitLab:
@@ -17,8 +11,8 @@ GitLab:
17
11
  {
18
12
  "mcpServers": {
19
13
  "standup": {
20
- "command": "node",
21
- "args": ["/absolute/path/to/standup-mr/dist/mcp/server.js"],
14
+ "command": "npx",
15
+ "args": ["-y", "standup-mr", "mcp"],
22
16
  "env": {
23
17
  "GITLAB_HOST": "gitlab.example.com",
24
18
  "GITLAB_TOKEN": "glpat-..."
@@ -30,6 +24,26 @@ GitLab:
30
24
 
31
25
  GitHub:
32
26
 
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "standup": {
31
+ "command": "npx",
32
+ "args": ["-y", "standup-mr", "mcp"],
33
+ "env": {
34
+ "GITHUB_HOST": "github.com",
35
+ "GITHUB_TOKEN": "ghp_..."
36
+ }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### Running from a clone
43
+
44
+ If you're working from a local clone instead of the published package, point
45
+ `command` at `node` and `args` at the absolute path to the built server:
46
+
33
47
  ```json
34
48
  {
35
49
  "mcpServers": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "standup-mr",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Standup notes from merge request state, not commit logs.",
5
5
  "keywords": [
6
6
  "standup",
@@ -58,19 +58,13 @@
58
58
  "typecheck": "tsc --noEmit",
59
59
  "prepublishOnly": "tsup"
60
60
  },
61
+ "dependencies": {
62
+ "@modelcontextprotocol/sdk": "^1.0.0"
63
+ },
61
64
  "devDependencies": {
62
- "@modelcontextprotocol/sdk": "^1.0.0",
63
65
  "@types/bun": "^1.0.0",
64
66
  "@types/node": "^22.0.0",
65
67
  "tsup": "^8.0.0",
66
68
  "typescript": "^5.5.0"
67
- },
68
- "peerDependencies": {
69
- "@modelcontextprotocol/sdk": "^1.0.0"
70
- },
71
- "peerDependenciesMeta": {
72
- "@modelcontextprotocol/sdk": {
73
- "optional": true
74
- }
75
69
  }
76
70
  }
@@ -19,22 +19,43 @@ Not a commit log — what finished, what is waiting, what is stuck.
19
19
  npx standup-mr fetch
20
20
  ```
21
21
 
22
- Prints one JSON document. The command picks GitLab or GitHub on its own — from
23
- flags, host, environment, or whichever of `gh`/`glab` is logged in. If the user
24
- already said which one they mean, pass it explicitly instead of guessing:
22
+ Prints one JSON document. Nothing else needs running: the command resolves
23
+ the host and token, works out which day to report on, and reads failed job
24
+ logs itself.
25
+
26
+ **Know the provider before the first call, don't discover it from a failure.**
27
+ If the user has named GitHub or GitLab, or it is otherwise obvious from
28
+ context, pass it on the very first invocation:
25
29
 
26
30
  ```bash
27
31
  npx standup-mr fetch --provider github
28
32
  npx standup-mr fetch --provider gitlab
29
33
  ```
30
34
 
31
- Add `--lang tr` for Turkish date labels. Nothing else needs running: the
32
- command resolves the host and token, works out which day to report on, and
33
- reads failed job logs itself.
34
-
35
- If the command exits non-zero, relay its stderr verbatim do not invent a
36
- cause. The usual fix is `gh auth login` or `glab auth login`, depending on
37
- which provider it was trying to reach.
35
+ If it is not known, do not guess. The command resolves it itself, in order:
36
+ `--provider` a recognisable `--host` `STANDUP_PROVIDER` the
37
+ `GITHUB_*`/`GITLAB_*` env pair whichever of `gh`/`glab` is logged in → an
38
+ error naming what it found. On a machine where both `gh` and `glab` are
39
+ authenticated, that resolution is ambiguous and the bare call fails with
40
+ `Both gh and glab are authenticated. Pass --provider github or --provider
41
+ gitlab.` — if you hit that, ask the user which one they mean rather than
42
+ picking one silently, unless the conversation already makes it obvious, in
43
+ which case pass that provider and say so in the note.
44
+
45
+ If the command exits non-zero for any other reason, relay its stderr verbatim
46
+ — do not invent a cause. The usual fix is `gh auth login` or `glab auth
47
+ login`, depending on which provider it was trying to reach.
48
+
49
+ **Pass `--lang` to match the language the user is speaking** — `--lang tr`
50
+ for Turkish. It only affects the date labels inside the JSON (`"label":
51
+ "Friday, 28 August"` → `"Cuma, 28 Ağustos"`); the prose of the note is always
52
+ your own work, in the user's language, regardless of this flag. Passing it
53
+ matters because the payload is otherwise ~1000 lines of English labels
54
+ fighting a note you're trying to write in another language. Today `--lang`
55
+ supports only `en` (default) and `tr` — any other value silently falls back
56
+ to English labels. If the user speaks a language `--lang` doesn't cover,
57
+ still write the note in their language; just leave the date labels in
58
+ English rather than passing a value that won't take effect.
38
59
 
39
60
  ## 2. Shape of the JSON
40
61
 
@@ -57,8 +78,12 @@ under its own `label`, never merged into a single "yesterday".
57
78
 
58
79
  ## 3. Write the note
59
80
 
60
- Three sections: **Previous day · Today · Blockers**. Short, in the language the
61
- user is speaking, phrased the way they would say it out loud.
81
+ **Write in the language the user is speaking, not the language of the JSON.**
82
+ The payload's labels and field names are English regardless of `--lang`
83
+ coverage — that is source data, not a cue for what language to write in.
84
+
85
+ Three sections: **Previous day · Today · Blockers**. Short, phrased the way
86
+ the user would say it out loud.
62
87
 
63
88
  ### Previous day
64
89