standup-mr 0.6.1 → 1.0.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.
@@ -24,21 +24,15 @@ function findPackageRoot(startDir) {
24
24
  }
25
25
  function packageVersion(moduleUrl) {
26
26
  const root = findPackageRoot(dirname(fileURLToPath(moduleUrl)));
27
- const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
27
+ const manifest = JSON.parse(
28
+ readFileSync(join(root, "package.json"), "utf8")
29
+ );
28
30
  if (!manifest.version) {
29
31
  throw new Error(`package.json at ${root} has no version`);
30
32
  }
31
33
  return manifest.version;
32
34
  }
33
35
 
34
- // src/notify/notify.constants.ts
35
- var PAYLOAD_FIELD = {
36
- slack: "text",
37
- discord: "content",
38
- "google-chat": "text"
39
- };
40
- var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
41
-
42
36
  // src/render/chat.ts
43
37
  var MARKDOWN_NATIVE = ["discord"];
44
38
  var FENCE = /^```/;
@@ -59,6 +53,14 @@ function toChatText(markdown, kind) {
59
53
  }).join("\n");
60
54
  }
61
55
 
56
+ // src/notify/notify.constants.ts
57
+ var PAYLOAD_FIELD = {
58
+ "slack": "text",
59
+ "discord": "content",
60
+ "google-chat": "text"
61
+ };
62
+ var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
63
+
62
64
  // src/notify/notify.ts
63
65
  async function postWebhook(url, text2, kind = "slack", fetchImpl = fetch) {
64
66
  const field = PAYLOAD_FIELD[kind];
@@ -73,7 +75,9 @@ async function postWebhook(url, text2, kind = "slack", fetchImpl = fetch) {
73
75
  body: JSON.stringify({ [field]: toChatText(text2, kind) })
74
76
  });
75
77
  if (!response.ok) {
76
- throw new Error(`Webhook rejected the message: HTTP ${response.status}.`);
78
+ throw new Error(
79
+ `Webhook rejected the message: HTTP ${response.status}.`
80
+ );
77
81
  }
78
82
  }
79
83
  var WEBHOOK_HOSTS = {
@@ -168,7 +172,9 @@ function cliStatus(cli) {
168
172
  }
169
173
  }
170
174
  function parseLoggedInHosts(statusOutput) {
171
- const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map((match) => match[1]);
175
+ const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map(
176
+ (match) => match[1]
177
+ );
172
178
  return [...new Set(hosts)].sort();
173
179
  }
174
180
  function glabHosts() {
@@ -184,13 +190,26 @@ function ghToken(host) {
184
190
  return cliCapture("gh", ["auth", "token", "--hostname", host]);
185
191
  }
186
192
 
187
- // src/buckets/buckets.constants.ts
188
- var STALE_DAYS = 14;
189
-
190
193
  // src/dates/dates.constants.ts
191
194
  var DAYS = {
192
- en: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
193
- tr: ["Pazar", "Pazartesi", "Sal\u0131", "\xC7ar\u015Famba", "Per\u015Fembe", "Cuma", "Cumartesi"]
195
+ en: [
196
+ "Sunday",
197
+ "Monday",
198
+ "Tuesday",
199
+ "Wednesday",
200
+ "Thursday",
201
+ "Friday",
202
+ "Saturday"
203
+ ],
204
+ tr: [
205
+ "Pazar",
206
+ "Pazartesi",
207
+ "Sal\u0131",
208
+ "\xC7ar\u015Famba",
209
+ "Per\u015Fembe",
210
+ "Cuma",
211
+ "Cumartesi"
212
+ ]
194
213
  };
195
214
  var MONTHS = {
196
215
  en: [
@@ -261,10 +280,14 @@ function previousActiveDays(eventDates, today) {
261
280
  }));
262
281
  }
263
282
 
283
+ // src/buckets/buckets.constants.ts
284
+ var STALE_DAYS = 14;
285
+
264
286
  // src/buckets/buckets.ts
265
287
  function classify(mr, today, staleDays = STALE_DAYS) {
266
288
  if (mr.draft) return "draft";
267
- if (mr.pipeline === "failed" || mr.pipeline === "canceled" || mr.unresolved > 0) return "blocked";
289
+ if (mr.pipeline === "failed" || mr.pipeline === "canceled" || mr.unresolved > 0)
290
+ return "blocked";
268
291
  const age = Math.round(
269
292
  (Date.parse(`${isoDay(today)}T00:00:00Z`) - Date.parse(`${mr.updated}T00:00:00Z`)) / MS_PER_DAY
270
293
  );
@@ -350,17 +373,25 @@ function retryAfter(response) {
350
373
  const value = response.headers.get("retry-after");
351
374
  if (!value) return "";
352
375
  const seconds = Number(value);
353
- if (!Number.isFinite(seconds) || seconds <= 0) return `; retry after ${value}`;
376
+ if (!Number.isFinite(seconds) || seconds <= 0)
377
+ return `; retry after ${value}`;
354
378
  return `; retry after ${seconds}s`;
355
379
  }
356
380
  function assertUsable(response, host) {
357
381
  if (response.ok || response.status === 404) return;
358
382
  if (response.status === 403 || response.status === 429) {
359
383
  if (remaining(response) === "0") {
360
- throw new ApiError(`${host} rate limit reached${resetAt(response)}.`, response.status);
384
+ throw new ApiError(
385
+ `${host} rate limit reached${resetAt(response)}.`,
386
+ response.status
387
+ );
361
388
  }
362
389
  const retry = retryAfter(response);
363
- if (retry) throw new ApiError(`${host} rate limit reached${retry}.`, response.status);
390
+ if (retry)
391
+ throw new ApiError(
392
+ `${host} rate limit reached${retry}.`,
393
+ response.status
394
+ );
364
395
  }
365
396
  if (response.status === 401) {
366
397
  throw new ApiError(
@@ -520,8 +551,8 @@ var GitHubProvider = class {
520
551
  }
521
552
  headers() {
522
553
  return {
523
- authorization: `Bearer ${this.token}`,
524
- accept: "application/vnd.github+json",
554
+ "authorization": `Bearer ${this.token}`,
555
+ "accept": "application/vnd.github+json",
525
556
  "x-github-api-version": API_VERSION
526
557
  };
527
558
  }
@@ -573,11 +604,14 @@ var GitHubProvider = class {
573
604
  return rows;
574
605
  }
575
606
  async getLogText(path) {
576
- const first = await this.send(buildUrl(this.api, path), { redirect: "manual" });
607
+ const first = await this.send(buildUrl(this.api, path), {
608
+ redirect: "manual"
609
+ });
577
610
  if (first.status === 404) return "";
578
611
  const location = first.headers.get("location");
579
612
  if (!location) {
580
- if (first.status === 0 || first.status >= 300 && first.status < 400) return "";
613
+ if (first.status === 0 || first.status >= 300 && first.status < 400)
614
+ return "";
581
615
  assertUsable(first, this.host);
582
616
  return first.ok ? await first.text() : "";
583
617
  }
@@ -620,7 +654,9 @@ var GitHubProvider = class {
620
654
  async shapePr(item) {
621
655
  const project = repoFromUrl(item.repository_url);
622
656
  const iid = item.number;
623
- const pull = await this.getJson(`repos/${project}/pulls/${iid}`);
657
+ const pull = await this.getJson(
658
+ `repos/${project}/pulls/${iid}`
659
+ );
624
660
  if (!pull) return null;
625
661
  const sha = pull.head?.sha ?? "";
626
662
  const [checks, reviews] = await Promise.all([
@@ -628,9 +664,15 @@ var GitHubProvider = class {
628
664
  `repos/${project}/commits/${sha}/check-runs`,
629
665
  { per_page: PAGE_SIZE }
630
666
  ) : Promise.resolve(null),
631
- this.getPaged(`repos/${project}/pulls/${iid}/reviews`, {}, 2)
667
+ this.getPaged(
668
+ `repos/${project}/pulls/${iid}/reviews`,
669
+ {},
670
+ 2
671
+ )
632
672
  ]);
633
- const { pipeline, pipelineId } = normalizeChecks(checks?.check_runs ?? []);
673
+ const { pipeline, pipelineId } = normalizeChecks(
674
+ checks?.check_runs ?? []
675
+ );
634
676
  return {
635
677
  provider: "github",
636
678
  project,
@@ -669,7 +711,9 @@ var GitHubProvider = class {
669
711
  `is:pr is:open review-requested:${identity.username}`,
670
712
  SEARCH_CAP
671
713
  );
672
- const cutoff = isoDay(new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY));
714
+ const cutoff = isoDay(
715
+ new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY)
716
+ );
673
717
  const rows = await Promise.all(
674
718
  items.map(async (item) => {
675
719
  const project = repoFromUrl(item.repository_url);
@@ -735,7 +779,10 @@ var GitHubProvider = class {
735
779
  (run) => run.conclusion !== null && FAILED_CONCLUSIONS.has(run.conclusion)
736
780
  );
737
781
  if (!failed) return null;
738
- const summary = [failed.output?.summary ?? "", failed.output?.text ?? ""].filter(Boolean).join("\n");
782
+ const summary = [
783
+ failed.output?.summary ?? "",
784
+ failed.output?.text ?? ""
785
+ ].filter(Boolean).join("\n");
739
786
  const errors = extractErrors(summary);
740
787
  if (errors.length === 0) return null;
741
788
  return {
@@ -795,7 +842,10 @@ var GitLabProvider = class {
795
842
  const response = await sendWithRetry(
796
843
  this.fetchImpl,
797
844
  url,
798
- { 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}` } },
799
849
  this.host
800
850
  );
801
851
  assertUsable(response, this.host);
@@ -848,7 +898,9 @@ var GitLabProvider = class {
848
898
  const raw = await this.getPaged("events", {
849
899
  after: isoDay(since)
850
900
  });
851
- const ids = [...new Set(raw.map((e) => e.project_id).filter(Boolean))];
901
+ const ids = [
902
+ ...new Set(raw.map((e) => e.project_id).filter(Boolean))
903
+ ];
852
904
  const paths = new Map(
853
905
  await Promise.all(
854
906
  ids.map(async (id) => [id, await this.projectPath(id)])
@@ -859,7 +911,7 @@ var GitLabProvider = class {
859
911
  return {
860
912
  at: String(event.created_at).slice(0, 16),
861
913
  action: event.action_name,
862
- project: paths.get(event.project_id) ?? "",
914
+ project: paths.get(event.project_id ?? -1) ?? "",
863
915
  targetType: event.target_type ?? "",
864
916
  title: event.target_title ?? "",
865
917
  branch: push.ref ?? "",
@@ -871,7 +923,9 @@ var GitLabProvider = class {
871
923
  async countUnresolved(projectId, iid) {
872
924
  const discussions = await this.getJson(
873
925
  `projects/${projectId}/merge_requests/${iid}/discussions`,
874
- { per_page: PAGE_SIZE2 }
926
+ {
927
+ per_page: PAGE_SIZE2
928
+ }
875
929
  );
876
930
  if (!discussions) return 0;
877
931
  return discussions.filter((discussion) => {
@@ -924,7 +978,9 @@ var GitLabProvider = class {
924
978
  async approvedByMe(projectId, iid, uid) {
925
979
  const approvals = await this.getJson(`projects/${projectId}/merge_requests/${iid}/approvals`);
926
980
  if (!approvals) return false;
927
- return (approvals.approved_by ?? []).some((entry2) => entry2.user?.id === uid);
981
+ return (approvals.approved_by ?? []).some(
982
+ (entry2) => entry2.user?.id === uid
983
+ );
928
984
  }
929
985
  async getReviews(identity, today) {
930
986
  const raw = await this.getPaged("merge_requests", {
@@ -932,7 +988,9 @@ var GitLabProvider = class {
932
988
  state: "opened",
933
989
  reviewer_id: identity.id
934
990
  });
935
- const cutoff = isoDay(new Date(today.getTime() - FRESH_REVIEW_DAYS2 * MS_PER_DAY));
991
+ const cutoff = isoDay(
992
+ new Date(today.getTime() - FRESH_REVIEW_DAYS2 * MS_PER_DAY)
993
+ );
936
994
  const rows = await Promise.all(
937
995
  raw.map(async (mr) => ({
938
996
  provider: "gitlab",
@@ -944,7 +1002,11 @@ var GitLabProvider = class {
944
1002
  draft: mr.draft,
945
1003
  url: mr.web_url,
946
1004
  fresh: String(mr.updated_at).slice(0, 10) >= cutoff,
947
- approvedByMe: await this.approvedByMe(mr.project_id, mr.iid, identity.id)
1005
+ approvedByMe: await this.approvedByMe(
1006
+ mr.project_id,
1007
+ mr.iid,
1008
+ identity.id
1009
+ )
948
1010
  }))
949
1011
  );
950
1012
  return rows.sort((a, b) => b.updated.localeCompare(a.updated));
@@ -956,7 +1018,9 @@ var GitLabProvider = class {
956
1018
  ) ?? [];
957
1019
  const job = jobs.find((j) => j.status === "failed");
958
1020
  if (!job) return null;
959
- const trace = await this.getText(`projects/${mr.projectId}/jobs/${job.id}/trace`);
1021
+ const trace = await this.getText(
1022
+ `projects/${mr.projectId}/jobs/${job.id}/trace`
1023
+ );
960
1024
  return {
961
1025
  provider: "gitlab",
962
1026
  project: mr.project,
@@ -989,7 +1053,12 @@ function hostFrom(cliHost, envHost, probe, fallback, labels) {
989
1053
  if (cliHost) return cliHost;
990
1054
  if (envHost) return envHost;
991
1055
  const found = probe();
992
- return resolveHost(void 0, void 0, found.length > 0 ? found : fallback, labels);
1056
+ return resolveHost(
1057
+ void 0,
1058
+ void 0,
1059
+ found.length > 0 ? found : fallback,
1060
+ labels
1061
+ );
993
1062
  }
994
1063
  function chooseKind(options = {}) {
995
1064
  const env = options.env ?? process.env;
@@ -1042,7 +1111,13 @@ function connect(options = {}) {
1042
1111
  const env = options.env ?? process.env;
1043
1112
  const probe = options.probe ?? DEFAULT_PROBE;
1044
1113
  if (chooseKind(options) === "github") {
1045
- const host2 = hostFrom(options.host, env.GITHUB_HOST, probe.github, [DOT_COM], GITHUB_LABELS);
1114
+ const host2 = hostFrom(
1115
+ options.host,
1116
+ env.GITHUB_HOST,
1117
+ probe.github,
1118
+ [DOT_COM],
1119
+ GITHUB_LABELS
1120
+ );
1046
1121
  const token2 = resolveToken(
1047
1122
  host2,
1048
1123
  options.token,
@@ -1052,7 +1127,13 @@ function connect(options = {}) {
1052
1127
  );
1053
1128
  return new GitHubProvider(host2, token2);
1054
1129
  }
1055
- const host = hostFrom(options.host, env.GITLAB_HOST, probe.gitlab, [], GITLAB_LABELS);
1130
+ const host = hostFrom(
1131
+ options.host,
1132
+ env.GITLAB_HOST,
1133
+ probe.gitlab,
1134
+ [],
1135
+ GITLAB_LABELS
1136
+ );
1056
1137
  const token = resolveToken(
1057
1138
  host,
1058
1139
  options.token,
@@ -1063,21 +1144,6 @@ function connect(options = {}) {
1063
1144
  return new GitLabProvider(host, token);
1064
1145
  }
1065
1146
 
1066
- // src/skill/skill.ts
1067
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1068
- import { dirname as dirname2, join as join2 } from "path";
1069
- import { fileURLToPath as fileURLToPath2 } from "url";
1070
- function readStandupSkillBody() {
1071
- const moduleDir = dirname2(fileURLToPath2(import.meta.url));
1072
- const skillPath = join2(findPackageRoot(moduleDir), "skills", "standup", "SKILL.md");
1073
- if (!existsSync2(skillPath)) {
1074
- throw new Error(`Cannot find the standup skill file at ${skillPath}`);
1075
- }
1076
- const content = readFileSync2(skillPath, "utf8");
1077
- const withoutFrontmatter = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
1078
- return withoutFrontmatter.replace(/^\n+/, "");
1079
- }
1080
-
1081
1147
  // src/report/report.constants.ts
1082
1148
  var LOOKBACK_DAYS = 21;
1083
1149
 
@@ -1115,9 +1181,36 @@ async function buildReport(provider, today, lang = "en", lookbackDays = LOOKBACK
1115
1181
  };
1116
1182
  }
1117
1183
 
1184
+ // src/skill/skill.ts
1185
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1186
+ import { dirname as dirname2, join as join2 } from "path";
1187
+ import { fileURLToPath as fileURLToPath2 } from "url";
1188
+ function readStandupSkillBody() {
1189
+ const moduleDir = dirname2(fileURLToPath2(import.meta.url));
1190
+ const skillPath = join2(
1191
+ findPackageRoot(moduleDir),
1192
+ "skills",
1193
+ "standup",
1194
+ "SKILL.md"
1195
+ );
1196
+ if (!existsSync2(skillPath)) {
1197
+ throw new Error(`Cannot find the standup skill file at ${skillPath}`);
1198
+ }
1199
+ const content = readFileSync2(skillPath, "utf8");
1200
+ const withoutFrontmatter = content.replace(
1201
+ /^---\r?\n[\s\S]*?\r?\n---\r?\n/,
1202
+ ""
1203
+ );
1204
+ return withoutFrontmatter.replace(/^\n+/, "");
1205
+ }
1206
+
1118
1207
  // mcp/server.ts
1119
1208
  async function collect(options = {}) {
1120
- const provider = options.providerImpl ?? connect({ provider: options.provider, host: options.host, token: options.token });
1209
+ const provider = options.providerImpl ?? connect({
1210
+ provider: options.provider,
1211
+ host: options.host,
1212
+ token: options.token
1213
+ });
1121
1214
  return buildReport(provider, /* @__PURE__ */ new Date(), options.lang ?? "en");
1122
1215
  }
1123
1216
  var STANDUP_TOOL_DESCRIPTION = 'Collect merge-request-based standup data from GitLab or GitHub. Returns one JSON object: `today`, `previousDays[]` with the events of each, `todayEvents[]`, `myMrs[]` bucketed ready / blocked / draft / stale, `reviews[]` waiting on you, and `blockers[]` carrying the error lines read out of each failed pipeline job log.\n\nCall it once at the start of a working day, to write a standup note. It is a snapshot, not a search API: it cannot fetch one named merge request, reach further back than the previous working day, or filter by project.\n\nRead-only, and credentials never come from an argument \u2014 they come from the environment or a logged-in gh / glab session. A rejected token, a refused resource or a rate limit fails the call with the host\'s own message, after two retries on transient server errors. A blocker whose diagnosis could not be fetched is still returned, with `job: "unknown"`, so a red pipeline is never silently dropped.';
@@ -1138,7 +1231,9 @@ async function runStandupTool(args, collector = collect) {
1138
1231
  host: args.host,
1139
1232
  lang: args.lang
1140
1233
  });
1141
- return { content: [{ type: "text", text: JSON.stringify(report) }] };
1234
+ return {
1235
+ content: [{ type: "text", text: JSON.stringify(report) }]
1236
+ };
1142
1237
  }
1143
1238
  var WEBHOOK_URL_ENV = "STANDUP_WEBHOOK_URL";
1144
1239
  var POST_TOOL_DESCRIPTION = `Posts a finished standup note to a chat webhook. This one has a side effect: it sends a message other people will see, so only call it on a note the user has agreed to send.
@@ -1185,27 +1280,72 @@ async function runPostTool(args, deps = {}) {
1185
1280
  async function runInstructionsTool() {
1186
1281
  return text(readStandupSkillBody());
1187
1282
  }
1188
- async function main() {
1189
- const server = new McpServer({ name: "standup-mr", version: packageVersion(import.meta.url) });
1190
- server.tool(
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
+ ];
1313
+ function createServer() {
1314
+ const server = new McpServer({
1315
+ name: "standup-mr",
1316
+ version: packageVersion(import.meta.url),
1317
+ icons: SERVER_ICONS
1318
+ });
1319
+ server.registerTool(
1191
1320
  "get_standup_data",
1192
- STANDUP_TOOL_DESCRIPTION,
1193
- STANDUP_TOOL_SCHEMA,
1321
+ {
1322
+ description: STANDUP_TOOL_DESCRIPTION,
1323
+ inputSchema: STANDUP_TOOL_SCHEMA,
1324
+ annotations: STANDUP_TOOL_ANNOTATIONS
1325
+ },
1194
1326
  async (args) => await runStandupTool(args)
1195
1327
  );
1196
- server.tool(
1328
+ server.registerTool(
1197
1329
  "post_standup_note",
1198
- POST_TOOL_DESCRIPTION,
1199
- POST_TOOL_SCHEMA,
1330
+ {
1331
+ description: POST_TOOL_DESCRIPTION,
1332
+ inputSchema: POST_TOOL_SCHEMA,
1333
+ annotations: POST_TOOL_ANNOTATIONS
1334
+ },
1200
1335
  async (args) => await runPostTool(args)
1201
1336
  );
1202
- server.tool(
1337
+ server.registerTool(
1203
1338
  "get_note_instructions",
1204
- INSTRUCTIONS_TOOL_DESCRIPTION,
1205
- {},
1339
+ {
1340
+ description: INSTRUCTIONS_TOOL_DESCRIPTION,
1341
+ annotations: INSTRUCTIONS_TOOL_ANNOTATIONS
1342
+ },
1206
1343
  async () => await runInstructionsTool()
1207
1344
  );
1208
- await server.connect(new StdioServerTransport());
1345
+ return server;
1346
+ }
1347
+ async function main() {
1348
+ await createServer().connect(new StdioServerTransport());
1209
1349
  }
1210
1350
  var entry = process.argv[1];
1211
1351
  var entryUrl;
@@ -1216,21 +1356,30 @@ try {
1216
1356
  }
1217
1357
  if (entryUrl && import.meta.url === entryUrl) {
1218
1358
  main().catch((error) => {
1219
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}
1220
- `);
1359
+ process.stderr.write(
1360
+ `${error instanceof Error ? error.message : String(error)}
1361
+ `
1362
+ );
1221
1363
  process.exitCode = 1;
1222
1364
  });
1223
1365
  }
1224
1366
  export {
1367
+ INSTRUCTIONS_TOOL_ANNOTATIONS,
1225
1368
  INSTRUCTIONS_TOOL_DESCRIPTION,
1369
+ POST_TOOL_ANNOTATIONS,
1226
1370
  POST_TOOL_DESCRIPTION,
1227
1371
  POST_TOOL_SCHEMA,
1372
+ SERVER_ICONS,
1373
+ STANDUP_TOOL_ANNOTATIONS,
1228
1374
  STANDUP_TOOL_DESCRIPTION,
1229
1375
  STANDUP_TOOL_SCHEMA,
1230
1376
  WEBHOOK_URL_ENV,
1231
1377
  collect,
1378
+ createServer,
1379
+ failure,
1232
1380
  main,
1233
1381
  runInstructionsTool,
1234
1382
  runPostTool,
1235
- runStandupTool
1383
+ runStandupTool,
1384
+ text
1236
1385
  };
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
@@ -18,13 +23,13 @@ Add this to the MCP settings file (for Cline,
18
23
 
19
24
  ```json
20
25
  {
21
- "mcpServers": {
22
- "standup": {
23
- "command": "npx",
24
- "args": ["-y", "standup-mr", "mcp"],
25
- "env": {}
26
+ "mcpServers": {
27
+ "standup": {
28
+ "command": "npx",
29
+ "args": ["-y", "standup-mr", "mcp"],
30
+ "env": {}
31
+ }
26
32
  }
27
- }
28
33
  }
29
34
  ```
30
35
 
@@ -35,12 +40,12 @@ That is the whole install when the user already has `gh` or `glab` logged in
35
40
 
36
41
  Add only the pair the user actually needs.
37
42
 
38
- | Variable | When |
39
- |---|---|
40
- | `GITHUB_TOKEN` | GitHub, no `gh` session |
41
- | `GITHUB_HOST` | GitHub Enterprise only; defaults to `github.com` |
42
- | `GITLAB_TOKEN` | GitLab, no `glab` session |
43
- | `GITLAB_HOST` | **Required for GitLab** — there is no default host |
43
+ | Variable | When |
44
+ | ------------------ | -------------------------------------------------------------------------- |
45
+ | `GITHUB_TOKEN` | GitHub, no `gh` session |
46
+ | `GITHUB_HOST` | GitHub Enterprise only; defaults to `github.com` |
47
+ | `GITLAB_TOKEN` | GitLab, no `glab` session |
48
+ | `GITLAB_HOST` | **Required for GitLab** — there is no default host |
44
49
  | `STANDUP_PROVIDER` | `github` or `gitlab`, when both are configured and the choice is ambiguous |
45
50
 
46
51
  Ask the user for a token; do not invent one, and do not put a token anywhere
package/mcp/README.md CHANGED
@@ -1,24 +1,29 @@
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:
9
14
 
10
15
  ```json
11
16
  {
12
- "mcpServers": {
13
- "standup": {
14
- "command": "npx",
15
- "args": ["-y", "standup-mr", "mcp"],
16
- "env": {
17
- "GITLAB_HOST": "gitlab.example.com",
18
- "GITLAB_TOKEN": "glpat-..."
19
- }
17
+ "mcpServers": {
18
+ "standup": {
19
+ "command": "npx",
20
+ "args": ["-y", "standup-mr", "mcp"],
21
+ "env": {
22
+ "GITLAB_HOST": "gitlab.example.com",
23
+ "GITLAB_TOKEN": "glpat-..."
24
+ }
25
+ }
20
26
  }
21
- }
22
27
  }
23
28
  ```
24
29
 
@@ -26,16 +31,16 @@ GitHub:
26
31
 
27
32
  ```json
28
33
  {
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
- }
34
+ "mcpServers": {
35
+ "standup": {
36
+ "command": "npx",
37
+ "args": ["-y", "standup-mr", "mcp"],
38
+ "env": {
39
+ "GITHUB_HOST": "github.com",
40
+ "GITHUB_TOKEN": "ghp_..."
41
+ }
42
+ }
37
43
  }
38
- }
39
44
  }
40
45
  ```
41
46
 
@@ -46,16 +51,16 @@ If you're working from a local clone instead of the published package, point
46
51
 
47
52
  ```json
48
53
  {
49
- "mcpServers": {
50
- "standup": {
51
- "command": "node",
52
- "args": ["/absolute/path/to/standup-mr/dist/mcp/server.js"],
53
- "env": {
54
- "GITHUB_HOST": "github.com",
55
- "GITHUB_TOKEN": "ghp_..."
56
- }
54
+ "mcpServers": {
55
+ "standup": {
56
+ "command": "node",
57
+ "args": ["/absolute/path/to/standup-mr/dist/mcp/server.js"],
58
+ "env": {
59
+ "GITHUB_HOST": "github.com",
60
+ "GITHUB_TOKEN": "ghp_..."
61
+ }
62
+ }
57
63
  }
58
- }
59
64
  }
60
65
  ```
61
66