@glie/mcp-polaris 0.2.0 → 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.
Files changed (3) hide show
  1. package/README.md +6 -4
  2. package/dist/index.js +270 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,10 +47,12 @@ it. The same token authenticates this server and the `polaris` CLI as you.
47
47
 
48
48
  ## Tools
49
49
 
50
- `polaris.me`, `polaris.project.*`, `polaris.milestone.*`, `polaris.task.*`
51
- (incl. `approve`), `polaris.stack.*` (`list` / `get` / `release` / `reject` for
52
- the approval Release Candidate flow), `polaris.agent.*`, `polaris.claude_md.get`,
53
- `polaris.framework.list`.
50
+ `polaris.me`, `polaris.project.*`, `polaris.milestone.*` (incl. `update` to
51
+ refine a spec), `polaris.task.*` (incl. `update`, `transition`, `approve`),
52
+ `polaris.stack.*` (`list` / `get` / `release` for the approval Release Candidate
53
+ flow), `polaris.agent.*`, `polaris.claude_md.get`, `polaris.framework.list`, plus
54
+ read-only context from the feed: `polaris.activity.list` (what happened) and
55
+ `polaris.chat.get` / `polaris.chat.messages` (comments agents left on a scope).
54
56
 
55
57
  Run `tools/list` against the server for the full, current set.
56
58
 
package/dist/index.js CHANGED
@@ -59,13 +59,29 @@ var INSTRUCTIONS = [
59
59
  " discovery = answer the framework Q&A to split it into tasks \xB7 loop = build/test/approve \xB7 release = ship.",
60
60
  "\u2022 Stack (Release Candidate): 2+ approval tasks of ONE milestone on an rc branch, tested & shipped together \u2014",
61
61
  " liquid (assembling) \u2192 blocked (a member conflicts) | frozen (human-resolved) \u2192 released (any \u2192 canceled).",
62
+ " The rc has its own PR (rc/stack-N \u2192 dev); while blocked it's a DRAFT (un-mergeable) and auto-flips to ready the moment the blocking member integrates.",
63
+ "\u2022 Issue (reactive/helpdesk): open \u2192 triaging \u2192 fixing \u2192 resolved (any \u2192 canceled). resolved is manual.",
64
+ "",
65
+ "TWO MODES \u2014 roadmap is proactive (milestone \u2192 tasks); HELPDESK is reactive (tickets \u2192 issues \u2192 tasks):",
66
+ "\u2022 A `ticket` is the customer conversation/report; an `issue` is the PROBLEM (the helpdesk twin of a milestone); `task`s are the work.",
67
+ "\u2022 N tickets \u2192 1 issue (duplicates dedup onto one problem) \xB7 1 ticket \u2192 N issues \xB7 1 issue \u2192 N tasks (iterative fix).",
68
+ "\u2022 Flow: ticket.create \u2192 ticket.reply / ticket.pass (communicate) \u2192 ticket.frame_issue (or issue.open standalone; ticket.link_issue to dedup)",
69
+ " \u2192 issue.add_task \xD7N (a planner decomposes) \u2192 task.transition the tasks through dev\u2192test\u2192approval\u2192release.",
70
+ "\u2022 The ball: ticket.pass sets whose move it is (customer|team); a message never flips it. A customer reply auto-reopens a resolved/closed ticket.",
62
71
  "",
63
72
  "WHICH TOOL WHEN:",
64
73
  `\u2022 "What's mine / what next?" \u2192 polaris.task.list (assignee \u2026="self", status=\u2026)`,
65
74
  '\u2022 "Read X in full" \u2192 polaris.task.get \xB7 milestone.get \xB7 project.get \xB7 stack.get',
75
+ '\u2022 "Fix an unclear brief" \u2192 polaris.task.update / milestone.update (PATCH description_md)',
76
+ '\u2022 "Catch up \u2014 what happened/said?"\u2192 polaris.activity.list (events) \xB7 polaris.chat.messages (comments left by agents)',
66
77
  '\u2022 "Move my task forward" \u2192 polaris.task.transition(id, status)',
67
78
  `\u2022 "It's reviewed, ship it" \u2192 polaris.task.approve (human-only); ship several together via polaris.stack.* \u2192 stack.release`,
79
+ `\u2022 "My task is stuck in a blocked stack" \u2192 fix the conflict on YOUR branch (rebase agent/task-N onto dev, push) \u2014 the rc PR flips to ready automatically. A stacked task can't advance individually; never merge the rc PR by hand.`,
68
80
  '\u2022 "Break a milestone into tasks" \u2192 polaris.milestone.qa_list / qa_answer (while in discovery)',
81
+ `\u2022 "Triage support / what's waiting?" \u2192 polaris.ticket.list (status, project_key) \u2014 P1 first`,
82
+ '\u2022 "Reply to a customer / hand off" \u2192 polaris.ticket.reply then polaris.ticket.pass(to)',
83
+ '\u2022 "Turn a report into work" \u2192 polaris.ticket.frame_issue \u2192 polaris.issue.add_task \xD7N',
84
+ '\u2022 "Same bug reported again" \u2192 polaris.ticket.link_issue (dedup onto the open issue)',
69
85
  "",
70
86
  "Identifiers: projects by `key` (string, e.g. saasA); milestones/tasks/agents/stacks by numeric `id`. Writes need a token; reads work anonymously."
71
87
  ].join(`
@@ -124,6 +140,23 @@ var TOOLS = [
124
140
  inputSchema: objSchema({ id: intSchema() }, ["id"]),
125
141
  call: async (args) => (await apiCall("GET", `/api/milestones/${args.id}`)).body
126
142
  },
143
+ {
144
+ name: "polaris.milestone.update",
145
+ description: "Correct or refine a milestone's spec \u2014 PATCH its description_md (and optionally name), same idea as polaris.task.update. Use it to sharpen the brief for better scoping. Needs a token.",
146
+ inputSchema: objSchema({
147
+ id: intSchema(),
148
+ name: strSchema(),
149
+ description_md: strSchema("full markdown brief; replaces the current description")
150
+ }, ["id"]),
151
+ call: async (args) => {
152
+ const body = {};
153
+ if (typeof args.name === "string")
154
+ body.name = args.name;
155
+ if (typeof args.description_md === "string")
156
+ body.description_md = args.description_md;
157
+ return (await apiCall("PATCH", `/api/milestones/${args.id}`, body)).body;
158
+ }
159
+ },
127
160
  {
128
161
  name: "polaris.milestone.transition",
129
162
  description: "Transition milestone status (state machine enforced: draft\u2192discovery\u2192loop\u2192release\u2192done, *\u2192canceled).",
@@ -191,6 +224,23 @@ var TOOLS = [
191
224
  inputSchema: objSchema({ milestone_id: intSchema(), name: strSchema(), description_md: strSchema() }, ["milestone_id", "name"]),
192
225
  call: async (args) => (await apiCall("POST", "/api/tasks", args)).body
193
226
  },
227
+ {
228
+ name: "polaris.task.update",
229
+ description: "Correct or refine a task's spec \u2014 PATCH its description_md (and optionally name). Use this to sharpen an underspecified brief so the implementing agent gets better results; it is NOT a log of what was done (leave that as a comment). Needs a token.",
230
+ inputSchema: objSchema({
231
+ id: intSchema(),
232
+ name: strSchema(),
233
+ description_md: strSchema("full markdown brief; replaces the current description")
234
+ }, ["id"]),
235
+ call: async (args) => {
236
+ const body = {};
237
+ if (typeof args.name === "string")
238
+ body.name = args.name;
239
+ if (typeof args.description_md === "string")
240
+ body.description_md = args.description_md;
241
+ return (await apiCall("PATCH", `/api/tasks/${args.id}`, body)).body;
242
+ }
243
+ },
194
244
  {
195
245
  name: "polaris.task.transition",
196
246
  description: "Move a task to its next status when you finish a stage (e.g. dev\u2192test once your PR is up). Enforced flow: draft\u2192backlog\u2192dev\u2192test\u2192approval\u2192release\u2192done (any\u2192canceled). Illegal jumps are rejected. The approval\u2192release gate is polaris.task.approve (human-only).",
@@ -215,6 +265,155 @@ var TOOLS = [
215
265
  inputSchema: objSchema({ id: intSchema(), dep_task_id: intSchema() }, ["id", "dep_task_id"]),
216
266
  call: async (args) => (await apiCall("DELETE", `/api/tasks/${args.id}/deps/${args.dep_task_id}`)).body
217
267
  },
268
+ {
269
+ name: "polaris.ticket.list",
270
+ description: "List the support queue (reactive mode), ordered by the P1\u2192P4 priority (a customer waiting on our move is P1). Filter by project_key, status (new|triaging|open|resolved|closed), include_closed.",
271
+ inputSchema: objSchema({
272
+ project_key: strSchema(),
273
+ status: strSchema("new|triaging|open|resolved|closed"),
274
+ include_closed: strSchema('"1" to include closed tickets')
275
+ }),
276
+ call: async (args) => {
277
+ const qs = new URLSearchParams;
278
+ if (typeof args.project_key === "string")
279
+ qs.set("project_key", args.project_key);
280
+ if (typeof args.status === "string")
281
+ qs.set("status", args.status);
282
+ if (args.include_closed === "1" || args.include_closed === true)
283
+ qs.set("include_closed", "1");
284
+ const s = qs.toString();
285
+ return (await apiCall("GET", `/api/tickets${s ? `?${s}` : ""}`)).body;
286
+ }
287
+ },
288
+ {
289
+ name: "polaris.ticket.get",
290
+ description: "Get one ticket by id \u2014 its conversation (messages), the issues it reports, and its attachments.",
291
+ inputSchema: objSchema({ id: intSchema() }, ["id"]),
292
+ call: async (args) => (await apiCall("GET", `/api/tickets/${args.id}`)).body
293
+ },
294
+ {
295
+ name: "polaris.ticket.create",
296
+ description: "Open a support ticket (a customer report) + its first message. Scope it with project_key (e.g. saasA).",
297
+ inputSchema: objSchema({
298
+ project_key: strSchema(),
299
+ type: strSchema("bug|question|suggestion|feedback"),
300
+ subject: strSchema(),
301
+ requester: strSchema("email/handle of who reported it"),
302
+ body_md: strSchema(),
303
+ channel: strSchema("email|discord|slack|portal|in_product")
304
+ }, ["project_key", "subject", "requester", "body_md"]),
305
+ call: async (args) => (await apiCall("POST", "/api/tickets", args)).body
306
+ },
307
+ {
308
+ name: "polaris.ticket.reply",
309
+ description: "Add a message to a ticket's conversation. Default side=team (you replying); an adapter relaying a customer message passes side=customer. Sending a message does NOT pass the ball.",
310
+ inputSchema: objSchema({ id: intSchema(), body_md: strSchema(), side: strSchema("customer|team") }, ["id", "body_md"]),
311
+ call: async (args) => (await apiCall("POST", `/api/tickets/${args.id}/messages`, {
312
+ body_md: args.body_md,
313
+ side: args.side
314
+ })).body
315
+ },
316
+ {
317
+ name: "polaris.ticket.pass",
318
+ description: "Pass the ball \u2014 whose move it is. to=customer after you reply; to=team to pull it back. Manual + idempotent.",
319
+ inputSchema: objSchema({ id: intSchema(), to: strSchema("customer|team") }, ["id", "to"]),
320
+ call: async (args) => (await apiCall("POST", `/api/tickets/${args.id}/pass`, { to: args.to })).body
321
+ },
322
+ {
323
+ name: "polaris.ticket.transition",
324
+ description: "Move a ticket through its lifecycle: new \u2192 triaging \u2192 open \u2192 resolved \u2192 closed. A customer reply auto-reopens a resolved/closed ticket.",
325
+ inputSchema: objSchema({ id: intSchema(), status: strSchema("new|triaging|open|resolved|closed") }, ["id", "status"]),
326
+ call: async (args) => (await apiCall("POST", `/api/tickets/${args.id}/status`, { status: args.status })).body
327
+ },
328
+ {
329
+ name: "polaris.ticket.frame_issue",
330
+ description: "Frame a NEW issue (the problem) from this ticket's conversation \u2014 the helpdesk twin of a milestone. A note is added to the thread. Then decompose it via polaris.issue.add_task.",
331
+ inputSchema: objSchema({ id: intSchema(), title: strSchema() }, ["id", "title"]),
332
+ call: async (args) => (await apiCall("POST", `/api/tickets/${args.id}/issues`, { title: args.title })).body
333
+ },
334
+ {
335
+ name: "polaris.ticket.link_issue",
336
+ description: "Link this ticket to an EXISTING issue (dedup \u2014 it's a duplicate report of a problem already being worked).",
337
+ inputSchema: objSchema({ id: intSchema(), issue_id: intSchema() }, ["id", "issue_id"]),
338
+ call: async (args) => (await apiCall("POST", `/api/tickets/${args.id}/issues/link`, { issue_id: args.issue_id })).body
339
+ },
340
+ {
341
+ name: "polaris.ticket.update",
342
+ description: "Update a ticket's type, its human assignee, and/or its guidance (the communicator's orientation on what to do/say next).",
343
+ inputSchema: objSchema({
344
+ id: intSchema(),
345
+ type: strSchema("bug|question|suggestion|feedback"),
346
+ assignee_human_id: intSchema(),
347
+ guidance: strSchema("The communicator's guidance on what to do/say next \u2014 orientation (e.g. create an issue, inform, reassure), researched from the customer's history + related issues. Shown to the human as advice; NOT inserted into the reply box.")
348
+ }, ["id"]),
349
+ call: async (args) => (await apiCall("PATCH", `/api/tickets/${args.id}`, {
350
+ type: args.type,
351
+ assignee_human_id: args.assignee_human_id,
352
+ guidance: args.guidance
353
+ })).body
354
+ },
355
+ {
356
+ name: "polaris.issue.list",
357
+ description: "List issues (the problems) for a project. Filter by status: open|triaging|fixing|resolved|canceled.",
358
+ inputSchema: objSchema({
359
+ project_key: strSchema(),
360
+ status: strSchema("open|triaging|fixing|resolved|canceled")
361
+ }),
362
+ call: async (args) => {
363
+ const qs = new URLSearchParams;
364
+ if (typeof args.project_key === "string")
365
+ qs.set("project_key", args.project_key);
366
+ if (typeof args.status === "string")
367
+ qs.set("status", args.status);
368
+ const s = qs.toString();
369
+ return (await apiCall("GET", `/api/issues${s ? `?${s}` : ""}`)).body;
370
+ }
371
+ },
372
+ {
373
+ name: "polaris.issue.get",
374
+ description: "Get one issue \u2014 its linked tickets (the reports) and its tasks (the work).",
375
+ inputSchema: objSchema({ id: intSchema() }, ["id"]),
376
+ call: async (args) => (await apiCall("GET", `/api/issues/${args.id}`)).body
377
+ },
378
+ {
379
+ name: "polaris.issue.open",
380
+ description: "Open a STANDALONE issue (no ticket needed) \u2014 e.g. an internal dev files a bug. Scope with project_key.",
381
+ inputSchema: objSchema({ project_key: strSchema(), title: strSchema() }, [
382
+ "project_key",
383
+ "title"
384
+ ]),
385
+ call: async (args) => (await apiCall("POST", "/api/issues", args)).body
386
+ },
387
+ {
388
+ name: "polaris.issue.update",
389
+ description: "Update an issue \u2014 set its title and/or assign it to an agent (assignee_agent_id, e.g. hand it to a planner).",
390
+ inputSchema: objSchema({ id: intSchema(), title: strSchema(), assignee_agent_id: intSchema() }, ["id"]),
391
+ call: async (args) => (await apiCall("PATCH", `/api/issues/${args.id}`, {
392
+ title: args.title,
393
+ assignee_agent_id: args.assignee_agent_id
394
+ })).body
395
+ },
396
+ {
397
+ name: "polaris.issue.transition",
398
+ description: "Move an issue through its state: open \u2192 triaging \u2192 fixing \u2192 resolved (any \u2192 canceled). resolved is manual.",
399
+ inputSchema: objSchema({ id: intSchema(), status: strSchema("open|triaging|fixing|resolved|canceled") }, ["id", "status"]),
400
+ call: async (args) => (await apiCall("POST", `/api/issues/${args.id}/status`, { status: args.status })).body
401
+ },
402
+ {
403
+ name: "polaris.issue.add_task",
404
+ description: "The planner's move: decompose an issue into a task (no milestone \u2014 it belongs to the issue, enters draft). A helpdesk planner also does this autonomously once the issue is in 'triaging'. When the issue is in 'fixing', a helpdesk-domain developer/tester claims and runs the tasks; you can also drive them with polaris.task.transition. hotfix=true is claimed first, branches off main, and auto-merges to main at release once CI passes (the prod gate is CI).",
405
+ inputSchema: objSchema({
406
+ id: intSchema(),
407
+ name: strSchema(),
408
+ description_md: strSchema(),
409
+ hotfix: { type: "boolean" }
410
+ }, ["id", "name"]),
411
+ call: async (args) => (await apiCall("POST", `/api/issues/${args.id}/tasks`, {
412
+ name: args.name,
413
+ description_md: args.description_md,
414
+ hotfix: args.hotfix
415
+ })).body
416
+ },
218
417
  {
219
418
  name: "polaris.stack.list",
220
419
  description: "List Release-Candidate stacks (groups of approval tasks shipped together). Filter by status: liquid|blocked|frozen|released|canceled.",
@@ -226,7 +425,7 @@ var TOOLS = [
226
425
  },
227
426
  {
228
427
  name: "polaris.stack.get",
229
- description: "Inspect one stack by id \u2014 its member tasks, the rc branch + PR, and (when blocked) which member failed to integrate and why.",
428
+ description: "Inspect one stack by id \u2014 its member tasks, the rc branch + PR, and (when blocked) which member failed to integrate and why. While blocked the rc PR is a draft (can't be merged); resolve the blocking member's branch (rebase onto dev + push) or commit the fix on the rc and it flips to ready automatically.",
230
429
  inputSchema: objSchema({ id: intSchema() }, ["id"]),
231
430
  call: async (args) => (await apiCall("GET", `/api/stacks/${args.id}`)).body
232
431
  },
@@ -265,6 +464,52 @@ var TOOLS = [
265
464
  description: "List the curated frameworks (knowledge bases).",
266
465
  inputSchema: objSchema({}),
267
466
  call: async () => (await apiCall("GET", "/api/frameworks")).body
467
+ },
468
+ {
469
+ name: "polaris.activity.list",
470
+ description: 'Read the activity feed \u2014 the system event log of what HAPPENED (state changes, approvals, etc.). Filter by scope_type+scope_id (e.g. task 42) and/or event_type. Answers "what changed here?"; for what agents WROTE, use polaris.chat.messages.',
471
+ inputSchema: objSchema({
472
+ scope_type: strSchema("task|milestone|project|agent"),
473
+ scope_id: intSchema(),
474
+ event_type: strSchema(),
475
+ limit: intSchema()
476
+ }),
477
+ call: async (args) => {
478
+ const qs = new URLSearchParams;
479
+ for (const k of ["scope_type", "scope_id", "event_type", "limit"]) {
480
+ const v = args[k];
481
+ if (v !== undefined && v !== null)
482
+ qs.set(k, String(v));
483
+ }
484
+ const r = await apiCall("GET", `/api/activities${qs.toString() ? `?${qs}` : ""}`);
485
+ return r.body;
486
+ }
487
+ },
488
+ {
489
+ name: "polaris.chat.get",
490
+ description: "Resolve the chat thread for a scope (e.g. task 42) and return its id. Idempotent (get-or-create). You rarely need this directly \u2014 polaris.chat.messages takes the scope and resolves the id for you.",
491
+ inputSchema: objSchema({
492
+ scope_type: strSchema("task|milestone|project|agent_dm|human_dm|general"),
493
+ scope_id: intSchema()
494
+ }, ["scope_type", "scope_id"]),
495
+ call: async (args) => (await apiCall("GET", `/api/chats/${args.scope_type}/${args.scope_id}`)).body
496
+ },
497
+ {
498
+ name: "polaris.chat.messages",
499
+ description: "Read the comments/context agents and humans left on a scope (e.g. task 42) \u2014 free-text notes, decisions, @mentions. Pass the scope; the chat id is resolved for you. Read-only (posting isn't exposed yet).",
500
+ inputSchema: objSchema({
501
+ scope_type: strSchema("task|milestone|project|agent_dm|human_dm|general"),
502
+ scope_id: intSchema(),
503
+ limit: intSchema()
504
+ }, ["scope_type", "scope_id"]),
505
+ call: async (args) => {
506
+ const chat = (await apiCall("GET", `/api/chats/${args.scope_type}/${args.scope_id}`)).body;
507
+ const chatId = chat?.data?.id;
508
+ if (!chatId)
509
+ throw new Error(`no chat for ${args.scope_type}/${args.scope_id}`);
510
+ const qs = typeof args.limit === "number" ? `?limit=${args.limit}` : "";
511
+ return (await apiCall("GET", `/api/chats/${chatId}/messages${qs}`)).body;
512
+ }
268
513
  }
269
514
  ];
270
515
  var TOOLS_BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
@@ -285,7 +530,7 @@ async function handle(req) {
285
530
  return ok(id, {
286
531
  protocolVersion: "2024-11-05",
287
532
  capabilities: { tools: {} },
288
- serverInfo: { name: "mcp-polaris", version: "0.1.0" },
533
+ serverInfo: { name: "mcp-polaris", version: "0.3.0" },
289
534
  instructions: INSTRUCTIONS
290
535
  });
291
536
  case "initialized":
@@ -321,27 +566,29 @@ async function handle(req) {
321
566
  return fail(id, -32601, `method not found: ${req.method}`);
322
567
  }
323
568
  }
324
- log(`starting against ${BASE} (token=${TOKEN ? "set" : "none"})`);
325
- var rl = createInterface({ input: process.stdin, crlfDelay: Number.POSITIVE_INFINITY });
326
- rl.on("line", async (line) => {
327
- if (!line.trim())
328
- return;
329
- let req;
330
- try {
331
- req = JSON.parse(line);
332
- } catch (_err) {
333
- send(fail(null, -32700, "parse error", { line }));
334
- return;
335
- }
336
- try {
337
- const res = await handle(req);
338
- if (res)
339
- send(res);
340
- } catch (err) {
341
- send(fail(req.id ?? null, -32603, err.message));
342
- }
343
- });
344
- rl.on("close", () => log("stdin closed, exiting"));
569
+ if (__require.main == __require.module) {
570
+ log(`starting against ${BASE} (token=${TOKEN ? "set" : "none"})`);
571
+ const rl = createInterface({ input: process.stdin, crlfDelay: Number.POSITIVE_INFINITY });
572
+ rl.on("line", async (line) => {
573
+ if (!line.trim())
574
+ return;
575
+ let req;
576
+ try {
577
+ req = JSON.parse(line);
578
+ } catch (_err) {
579
+ send(fail(null, -32700, "parse error", { line }));
580
+ return;
581
+ }
582
+ try {
583
+ const res = await handle(req);
584
+ if (res)
585
+ send(res);
586
+ } catch (err) {
587
+ send(fail(req.id ?? null, -32603, err.message));
588
+ }
589
+ });
590
+ rl.on("close", () => log("stdin closed, exiting"));
591
+ }
345
592
  export {
346
593
  TOOLS
347
594
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glie/mcp-polaris",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Model Context Protocol (stdio) server exposing Polaris — glie's ops platform — as native tools in Claude Code and any MCP client.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "glie <carlos@glie.ai>",