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 +47 -0
- package/README.md +56 -3
- package/dist/{chunk-J5LAGA3U.js → chunk-3C3PBI3T.js} +8 -8
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/server.js +59 -28
- package/llms-install.md +5 -0
- package/mcp/README.md +6 -1
- package/package.json +6 -3
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
|
-
##
|
|
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/`) —
|
|
74
|
-
|
|
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
|
-
|
|
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 [
|
|
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 =
|
|
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
package/dist/index.js
CHANGED
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
-
|
|
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 [
|
|
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 =
|
|
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
|
|
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.
|
|
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.
|
|
68
|
-
"zod": "^4.
|
|
70
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
71
|
+
"zod": "^4.6.6"
|
|
69
72
|
},
|
|
70
73
|
"devDependencies": {
|
|
71
74
|
"@eslint/js": "^9.39.1",
|