standup-mr 0.7.0 → 1.0.1

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,53 @@ 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
+ ## [1.0.1] - 2026-10-12
8
+
9
+ ### Fixed
10
+
11
+ - GitLab merge requests take their pipeline from `head_pipeline`, the one
12
+ GitLab shows on the MR. The `/pipelines` list is not ordered newest first,
13
+ so a merge request could be reported blocked by a stale red pipeline from an
14
+ earlier commit, and the current failure's log was never read.
15
+ - Error lines from GitLab's timestamped job logs no longer start with the
16
+ stream marker (`01O`, `00E+`) that follows each timestamp.
17
+
18
+ ### Added
19
+
20
+ - Every MCP tool declares a human-readable `title`, which clients show in
21
+ their tool lists and permission prompts.
22
+
23
+ ## [1.0.0] - 2026-10-12
24
+
25
+ First stable release. From here on the CLI commands and flags, the JSON that
26
+ `fetch` and `get_standup_data` return, the MCP tool names and arguments, and
27
+ the environment variables follow semantic versioning: anything that would
28
+ break an existing setup waits for 2.0.
29
+
30
+ ### Added
31
+
32
+ - **Remote MCP server for claude.ai.** Add
33
+ `https://standup.ilkerbalcilar.com/mcp` as a custom connector, sign in with
34
+ GitHub or gitlab.com — or paste a token for a self-hosted host — and use
35
+ standup-mr from claude.ai on the web and on mobile, with no environment
36
+ variables. One connector can hold several accounts and an optional chat
37
+ webhook. The server lives in `remote/` and is not part of the npm package.
38
+ - **Sign in to your company's GitLab or GitHub Enterprise.** On the connect
39
+ page, type the host: if someone there already set up sign-in, use it;
40
+ otherwise register an OAuth application once, following the exact redirect
41
+ URI and scopes the page shows, and everyone at that host can sign in from
42
+ then on.
43
+ - `server.json` lists the remote endpoint, so registries show both the npm
44
+ package and the hosted server.
45
+ - Both servers declare the project icon in `serverInfo.icons`.
46
+
47
+ ### Fixed
48
+
49
+ - GitLab requests send the token as `Authorization: Bearer` instead of
50
+ `PRIVATE-TOKEN`. Personal access tokens work either way, but OAuth tokens —
51
+ from `glab`'s browser login or from the remote server's sign-in — were
52
+ rejected with 401.
53
+
7
54
  ## [0.7.0] - 2026-10-10
8
55
 
9
56
  ### Added
package/README.md CHANGED
@@ -66,12 +66,55 @@ So if you already use `gh` or `glab`, there is nothing to configure.
66
66
  npx standup-mr fetch --markdown | npx standup-mr post --slack "$SLACK_WEBHOOK_URL"
67
67
  ```
68
68
 
69
- ## The three surfaces
69
+ ## claude.ai (web and mobile)
70
+
71
+ No token, no config file — sign in instead. In claude.ai open **Settings →
72
+ Connectors → Add custom connector** and paste:
73
+
74
+ ```
75
+ https://standup.ilkerbalcilar.com/mcp
76
+ ```
77
+
78
+ Press **Connect**. A page opens where you link the accounts the note should
79
+ read:
80
+
81
+ - **GitHub** — sign in through the standup-mr GitHub App. It only sees the
82
+ repositories it is installed on, so install it on your org too; if the org
83
+ won't approve it, add github.com with a fine-grained token instead.
84
+ - **gitlab.com** — sign in with GitLab.
85
+ - **Your company's GitLab or GitHub Enterprise** — type the host. If someone
86
+ at your company already set up sign-in, you get a "Sign in with …" button.
87
+ Otherwise set it up once: the page shows the exact redirect URI and scopes
88
+ for an OAuth application on your host, you paste its ID and secret, and
89
+ everyone at that host can sign in from then on. A read-only token works too.
90
+ - **Chat webhook** _(optional)_ — Slack, Discord or Google Chat, so Claude can
91
+ post the finished note when you ask it to.
92
+
93
+ Link as many as you like — say, GitHub for side projects and your company's
94
+ GitLab for work — then ask for "my standup" or "my work standup". With
95
+ several accounts linked, Claude asks which one you mean.
96
+
97
+ The connector then works from the claude.ai mobile app as well.
98
+
99
+ **What is stored.** Your provider tokens and webhook URL, encrypted with
100
+ AES-256-GCM, and only to read your merge requests and post your note. The
101
+ access tokens Claude holds are stored as hashes. Disconnecting the connector
102
+ deletes everything; connections unused for 30 days are deleted. The server is
103
+ the code in [`remote/`](remote/).
104
+
105
+ **When to use the local server instead.** A GitLab behind a VPN or on a
106
+ private network is out of reach of a hosted server. Run the stdio server
107
+ below on your own machine — the token never leaves it.
108
+
109
+ ## The four surfaces
70
110
 
71
111
  **CLI** — the core. Emits JSON; zero runtime dependencies.
72
112
 
73
- **MCP server** (`mcp/`) — one tool, `get_standup_data`, for Claude Desktop,
74
- Cursor, or any MCP client. See [`mcp/README.md`](mcp/README.md).
113
+ **MCP server** (`mcp/`) — three tools for Claude Desktop, Claude Code, Cursor,
114
+ or any MCP client that runs a local process. See [`mcp/README.md`](mcp/README.md).
115
+
116
+ **Remote MCP server** (`remote/`) — the same tools over HTTPS with sign-in, for
117
+ claude.ai on the web and on mobile. See [above](#claudeai-web-and-mobile).
75
118
 
76
119
  **Claude Code plugin** — the note-writing playbook, shipped as the `standup`
77
120
  skill. From inside Claude Code:
@@ -205,6 +248,16 @@ that pipeline is red; this one opens the failed job's log and shows you the
205
248
  404 — and a 404 rather than a 403 usually means the token's scope is wrong, not
206
249
  that the package is missing.
207
250
 
251
+ ## Stability
252
+
253
+ Since 1.0.0 the CLI commands and flags, the JSON that `fetch` and
254
+ `get_standup_data` return, the MCP tool names and arguments, and the
255
+ environment variables follow [semantic versioning](https://semver.org/): a
256
+ change that would break an existing setup waits for a major release. New
257
+ fields in the JSON may appear in minor releases, so read it by name rather
258
+ than by position. The hosted server at `standup.ilkerbalcilar.com` is
259
+ updated continuously and is not tied to the npm version.
260
+
208
261
  ## Known limits
209
262
 
210
263
  - **GitHub's events feed is shallow.** It is capped at roughly 300 events over
@@ -383,7 +383,7 @@ var SECTION = /section_(start|end):\d+:\S*/g;
383
383
  var SIGNAL = /(\berror\b|Error:|fatal:|npm ERR!|\bfailed\b)/i;
384
384
  var NOISE = /^(Cleaning up|Job failed: exit status|ERROR: Job failed|Uploading artifacts|Job succeeded)/i;
385
385
  var MAX_LINE = 200;
386
- var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+/;
386
+ var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+(?:[0-9a-f]{2}[OE]\+?\s)?/;
387
387
  var ERROR_MARKER = /^##\[error\]\s*/;
388
388
  var GROUP_START = /^##\[group\]/;
389
389
  var GROUP_END = /^##\[endgroup\]/;
@@ -810,7 +810,10 @@ var GitLabProvider = class {
810
810
  const response = await sendWithRetry(
811
811
  this.fetchImpl,
812
812
  url,
813
- { headers: { "PRIVATE-TOKEN": this.token } },
813
+ // Bearer, not PRIVATE-TOKEN: GitLab accepts personal access tokens
814
+ // either way, but OAuth tokens — glab's browser login, and the
815
+ // remote server's sign-in — only as a bearer.
816
+ { headers: { Authorization: `Bearer ${this.token}` } },
814
817
  this.host
815
818
  );
816
819
  assertUsable(response, this.host);
@@ -901,14 +904,11 @@ var GitLabProvider = class {
901
904
  async shapeMr(mr) {
902
905
  const projectId = mr.project_id;
903
906
  const iid = mr.iid;
904
- const [pipelines, unresolved] = await Promise.all([
905
- this.getJson(
906
- `projects/${projectId}/merge_requests/${iid}/pipelines`,
907
- { per_page: 1 }
908
- ),
907
+ const [detail, unresolved] = await Promise.all([
908
+ this.getJson(`projects/${projectId}/merge_requests/${iid}`),
909
909
  this.countUnresolved(projectId, iid)
910
910
  ]);
911
- const latest = pipelines?.[0];
911
+ const latest = detail?.head_pipeline ?? void 0;
912
912
  return {
913
913
  provider: "gitlab",
914
914
  project: String(mr.references.full).split("!")[0],
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  postWebhook,
9
9
  readStandupSkillBody,
10
10
  toMarkdown
11
- } from "./chunk-J5LAGA3U.js";
11
+ } from "./chunk-3C3PBI3T.js";
12
12
 
13
13
  // src/cli/cli.ts
14
14
  import { realpathSync } from "fs";
package/dist/index.js CHANGED
@@ -45,7 +45,7 @@ import {
45
45
  toMarkdown,
46
46
  undiagnosed,
47
47
  unreachable
48
- } from "./chunk-J5LAGA3U.js";
48
+ } from "./chunk-3C3PBI3T.js";
49
49
  export {
50
50
  ApiError,
51
51
  ConfigError,
@@ -306,7 +306,7 @@ var SECTION = /section_(start|end):\d+:\S*/g;
306
306
  var SIGNAL = /(\berror\b|Error:|fatal:|npm ERR!|\bfailed\b)/i;
307
307
  var NOISE = /^(Cleaning up|Job failed: exit status|ERROR: Job failed|Uploading artifacts|Job succeeded)/i;
308
308
  var MAX_LINE = 200;
309
- var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+/;
309
+ var TIMESTAMP = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s+(?:[0-9a-f]{2}[OE]\+?\s)?/;
310
310
  var ERROR_MARKER = /^##\[error\]\s*/;
311
311
  var GROUP_START = /^##\[group\]/;
312
312
  var GROUP_END = /^##\[endgroup\]/;
@@ -842,7 +842,10 @@ var GitLabProvider = class {
842
842
  const response = await sendWithRetry(
843
843
  this.fetchImpl,
844
844
  url,
845
- { headers: { "PRIVATE-TOKEN": this.token } },
845
+ // Bearer, not PRIVATE-TOKEN: GitLab accepts personal access tokens
846
+ // either way, but OAuth tokens — glab's browser login, and the
847
+ // remote server's sign-in — only as a bearer.
848
+ { headers: { Authorization: `Bearer ${this.token}` } },
846
849
  this.host
847
850
  );
848
851
  assertUsable(response, this.host);
@@ -933,14 +936,11 @@ var GitLabProvider = class {
933
936
  async shapeMr(mr) {
934
937
  const projectId = mr.project_id;
935
938
  const iid = mr.iid;
936
- const [pipelines, unresolved] = await Promise.all([
937
- this.getJson(
938
- `projects/${projectId}/merge_requests/${iid}/pipelines`,
939
- { per_page: 1 }
940
- ),
939
+ const [detail, unresolved] = await Promise.all([
940
+ this.getJson(`projects/${projectId}/merge_requests/${iid}`),
941
941
  this.countUnresolved(projectId, iid)
942
942
  ]);
943
- const latest = pipelines?.[0];
943
+ const latest = detail?.head_pipeline ?? void 0;
944
944
  return {
945
945
  provider: "gitlab",
946
946
  project: String(mr.references.full).split("!")[0],
@@ -1277,49 +1277,71 @@ async function runPostTool(args, deps = {}) {
1277
1277
  async function runInstructionsTool() {
1278
1278
  return text(readStandupSkillBody());
1279
1279
  }
1280
+ var STANDUP_TOOL_TITLE = "Get standup data";
1281
+ var POST_TOOL_TITLE = "Post standup note";
1282
+ var INSTRUCTIONS_TOOL_TITLE = "Get note instructions";
1283
+ var STANDUP_TOOL_ANNOTATIONS = {
1284
+ readOnlyHint: true,
1285
+ destructiveHint: false,
1286
+ idempotentHint: true,
1287
+ openWorldHint: true
1288
+ };
1289
+ var POST_TOOL_ANNOTATIONS = {
1290
+ readOnlyHint: false,
1291
+ destructiveHint: false,
1292
+ idempotentHint: false,
1293
+ openWorldHint: true
1294
+ };
1295
+ var INSTRUCTIONS_TOOL_ANNOTATIONS = {
1296
+ readOnlyHint: true,
1297
+ destructiveHint: false,
1298
+ idempotentHint: true,
1299
+ openWorldHint: false
1300
+ };
1301
+ var SERVER_ICONS = [
1302
+ {
1303
+ src: "https://raw.githubusercontent.com/Jubstaaa/standup-mr/main/assets/icon-512.png",
1304
+ mimeType: "image/png",
1305
+ sizes: ["512x512"]
1306
+ },
1307
+ {
1308
+ src: "https://raw.githubusercontent.com/Jubstaaa/standup-mr/main/assets/icon.svg",
1309
+ mimeType: "image/svg+xml",
1310
+ sizes: ["any"]
1311
+ }
1312
+ ];
1280
1313
  function createServer() {
1281
1314
  const server = new McpServer({
1282
1315
  name: "standup-mr",
1283
- version: packageVersion(import.meta.url)
1316
+ version: packageVersion(import.meta.url),
1317
+ icons: SERVER_ICONS
1284
1318
  });
1285
1319
  server.registerTool(
1286
1320
  "get_standup_data",
1287
1321
  {
1322
+ title: STANDUP_TOOL_TITLE,
1288
1323
  description: STANDUP_TOOL_DESCRIPTION,
1289
1324
  inputSchema: STANDUP_TOOL_SCHEMA,
1290
- annotations: {
1291
- readOnlyHint: true,
1292
- destructiveHint: false,
1293
- idempotentHint: true,
1294
- openWorldHint: true
1295
- }
1325
+ annotations: STANDUP_TOOL_ANNOTATIONS
1296
1326
  },
1297
1327
  async (args) => await runStandupTool(args)
1298
1328
  );
1299
1329
  server.registerTool(
1300
1330
  "post_standup_note",
1301
1331
  {
1332
+ title: POST_TOOL_TITLE,
1302
1333
  description: POST_TOOL_DESCRIPTION,
1303
1334
  inputSchema: POST_TOOL_SCHEMA,
1304
- annotations: {
1305
- readOnlyHint: false,
1306
- destructiveHint: false,
1307
- idempotentHint: false,
1308
- openWorldHint: true
1309
- }
1335
+ annotations: POST_TOOL_ANNOTATIONS
1310
1336
  },
1311
1337
  async (args) => await runPostTool(args)
1312
1338
  );
1313
1339
  server.registerTool(
1314
1340
  "get_note_instructions",
1315
1341
  {
1342
+ title: INSTRUCTIONS_TOOL_TITLE,
1316
1343
  description: INSTRUCTIONS_TOOL_DESCRIPTION,
1317
- annotations: {
1318
- readOnlyHint: true,
1319
- destructiveHint: false,
1320
- idempotentHint: true,
1321
- openWorldHint: false
1322
- }
1344
+ annotations: INSTRUCTIONS_TOOL_ANNOTATIONS
1323
1345
  },
1324
1346
  async () => await runInstructionsTool()
1325
1347
  );
@@ -1345,16 +1367,25 @@ if (entryUrl && import.meta.url === entryUrl) {
1345
1367
  });
1346
1368
  }
1347
1369
  export {
1370
+ INSTRUCTIONS_TOOL_ANNOTATIONS,
1348
1371
  INSTRUCTIONS_TOOL_DESCRIPTION,
1372
+ INSTRUCTIONS_TOOL_TITLE,
1373
+ POST_TOOL_ANNOTATIONS,
1349
1374
  POST_TOOL_DESCRIPTION,
1350
1375
  POST_TOOL_SCHEMA,
1376
+ POST_TOOL_TITLE,
1377
+ SERVER_ICONS,
1378
+ STANDUP_TOOL_ANNOTATIONS,
1351
1379
  STANDUP_TOOL_DESCRIPTION,
1352
1380
  STANDUP_TOOL_SCHEMA,
1381
+ STANDUP_TOOL_TITLE,
1353
1382
  WEBHOOK_URL_ENV,
1354
1383
  collect,
1355
1384
  createServer,
1385
+ failure,
1356
1386
  main,
1357
1387
  runInstructionsTool,
1358
1388
  runPostTool,
1359
- runStandupTool
1389
+ runStandupTool,
1390
+ text
1360
1391
  };
package/llms-install.md CHANGED
@@ -3,6 +3,11 @@
3
3
  Instructions for an AI assistant setting this MCP server up on a user's
4
4
  machine. Everything here is verified against the published package.
5
5
 
6
+ If the user is on claude.ai (web or mobile) rather than a client that runs
7
+ local processes, none of this applies: tell them to add
8
+ `https://standup.ilkerbalcilar.com/mcp` under Settings → Connectors → Add custom
9
+ connector and sign in on the page that opens.
10
+
6
11
  ## What it needs
7
12
 
8
13
  - Node.js 20 or newer. Nothing to clone, nothing to build — the server runs
package/mcp/README.md CHANGED
@@ -1,8 +1,13 @@
1
1
  # MCP server
2
2
 
3
- Exposes one tool, `get_standup_data`, for either GitLab or GitHub, returning
3
+ Exposes three tools — `get_standup_data`, `get_note_instructions` and
4
+ `post_standup_note` — for either GitLab or GitHub. `get_standup_data` returns
4
5
  the same JSON as `standup fetch`.
5
6
 
7
+ This is the local, stdio server: it runs on your machine and reads tokens from
8
+ its environment. On claude.ai (web or mobile), use the hosted connector instead
9
+ — see [claude.ai (web and mobile)](../README.md#claudeai-web-and-mobile).
10
+
6
11
  ## Configure
7
12
 
8
13
  GitLab:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "standup-mr",
3
- "version": "0.7.0",
3
+ "version": "1.0.1",
4
4
  "mcpName": "io.github.Jubstaaa/standup-mr",
5
5
  "description": "Standup notes from merge request state, not commit logs.",
6
6
  "keywords": [
@@ -53,6 +53,9 @@
53
53
  "CHANGELOG.md",
54
54
  "LICENSE"
55
55
  ],
56
+ "workspaces": [
57
+ "remote"
58
+ ],
56
59
  "scripts": {
57
60
  "build": "tsup",
58
61
  "lint": "eslint .",
@@ -64,8 +67,8 @@
64
67
  "prepublishOnly": "tsup"
65
68
  },
66
69
  "dependencies": {
67
- "@modelcontextprotocol/sdk": "^1.30.0",
68
- "zod": "^4.4.3"
70
+ "@modelcontextprotocol/sdk": "^1.32.1",
71
+ "zod": "^4.6.6"
69
72
  },
70
73
  "devDependencies": {
71
74
  "@eslint/js": "^9.39.1",