@glie/mcp-polaris 0.14.0 → 0.19.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 +20 -8
  2. package/dist/index.js +157 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -48,8 +48,10 @@ it. The same token authenticates this server and the `polaris` CLI as you.
48
48
  ## Tools
49
49
 
50
50
  **Roadmap.** `polaris.me`, `polaris.project.*`, `polaris.milestone.*` (incl.
51
- `update` to refine a spec, `attachment_*` to list/attach/remove the visual
52
- references anchored in its body), `polaris.task.*` (incl. `update`,
51
+ `update` to refine a spec or move its dates, `demote` to send one with no tasks back to the
52
+ idea list, `attachment_*` to list/attach/remove the visual
53
+ references anchored in its body), `polaris.idea.*` (incl. `promote` into a milestone and
54
+ `promote_task` into a standalone task with no milestone), `polaris.task.*` (incl. `update`,
53
55
  `transition`, `approve`, `schedule`, `attachment_*` — same as the milestone's,
54
56
  plus how a milestone reference gets forwarded here via
55
57
  `source_attachment_id`), `polaris.stack.*` (`list` / `get` / `release` for the
@@ -57,12 +59,22 @@ approval Release Candidate flow), `polaris.agent.*`, `polaris.team.*`,
57
59
  `polaris.claude_md.get`, `polaris.framework.list`.
58
60
 
59
61
  **Helpdesk.** `polaris.ticket.*` (the customer conversation: `reply`, `pass`,
60
- `frame_issue`, `link_issue`) and `polaris.issue.*` (the problem: `open`,
61
- `transition`, `add_task`). N tickets can dedup onto one issue, and one issue
62
- fans out into N tasks.
63
-
64
- **Context, read-only.** `polaris.activity.list` (what happened) and
65
- `polaris.chat.get` / `polaris.chat.messages` (comments agents left on a scope).
62
+ `frame_issue`, `link_issue`, `attachment_list`) and `polaris.issue.*` (the problem:
63
+ `open`, `transition`, `add_task`). N tickets can dedup onto one issue, and one issue
64
+ fans out into N tasks. A reply carries files through `attachment_ids`: upload each
65
+ one to `POST /api/tickets/:id/attachments` first (this server does not carry bytes),
66
+ then paste its `marker` into `body_md` to draw it inside the message.
67
+
68
+ **Context, read-only.** `polaris.activity.list` (what happened),
69
+ `polaris.chat.get` / `polaris.chat.messages` (comments agents left on a scope),
70
+ and `polaris.task.run_lines` (the thread of an agent's run on a task: what it was
71
+ asked, said, ran and got back; human tokens only).
72
+
73
+ **What a creative delivered.** `polaris.task.creatives` lists every version of a
74
+ creative task with its files and the words that go out beside them (caption,
75
+ title, copy). `polaris.task.creative_download` saves one version to this machine
76
+ as a zip of its slides, so a video can be opened, its frames pulled and its
77
+ sound measured.
66
78
 
67
79
  Run `tools/list` against the server for the full, current set.
68
80
 
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  // @bun
3
3
 
4
4
  // ../../src/mcp/index.ts
5
+ import { mkdirSync, writeFileSync } from "fs";
6
+ import { tmpdir } from "os";
7
+ import { join, resolve } from "path";
5
8
  import { createInterface } from "readline";
6
9
 
7
10
  // ../../src/lib/media-types.ts
@@ -13,11 +16,22 @@ var MEDIA_TYPES = [
13
16
  { mime: "video/quicktime", kind: "video", ext: ".mov", aliases: [] },
14
17
  { mime: "video/webm", kind: "video", ext: ".webm", aliases: [] }
15
18
  ];
19
+ function mimesOfKind(kind) {
20
+ return MEDIA_TYPES.filter((t) => t.kind === kind).map((t) => t.mime);
21
+ }
16
22
  function allMimes() {
17
23
  return MEDIA_TYPES.map((t) => t.mime);
18
24
  }
25
+ var RECEIVED_IMAGE_MIMES = ["image/gif"];
26
+ var INLINE_MIMES = new Set([
27
+ ...mimesOfKind("image"),
28
+ ...mimesOfKind("video"),
29
+ ...RECEIVED_IMAGE_MIMES
30
+ ]);
19
31
 
20
32
  // ../../src/mcp/index.ts
33
+ var VERSION = "0.19.0";
34
+ var FRAMEWORK_KEY_DOC = "optional \u2014 a key from polaris.framework.list. Leave it out when none applies: a framework is for founding a large domain, and most milestones take none. An unknown key is refused (400) with the keys that exist.";
21
35
  var BASE = process.env.POLARIS_URL ?? "http://127.0.0.1:7843";
22
36
  var TOKEN = process.env.POLARIS_TOKEN ?? "";
23
37
  var PROJECT = process.env.POLARIS_PROJECT ?? "";
@@ -84,13 +98,15 @@ var INSTRUCTIONS = [
84
98
  "\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).",
85
99
  "\u2022 Flow: ticket.create \u2192 ticket.reply / ticket.pass (communicate) \u2192 ticket.frame_issue (or issue.open standalone; ticket.link_issue to dedup)",
86
100
  " \u2192 issue.add_task \xD7N (a planner decomposes) \u2192 task.transition the tasks through dev\u2192test\u2192approval\u2192release.",
87
- "\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.",
101
+ "\u2022 The ball: ticket.pass sets whose move it is (customer|team). A team message never moves it; a customer reply brings it back to the team and auto-reopens a resolved/closed ticket.",
88
102
  "",
89
103
  "WHICH TOOL WHEN:",
90
104
  `\u2022 "What's mine / what next?" \u2192 polaris.task.list (assignee \u2026="self", status=\u2026)`,
91
105
  '\u2022 "Read X in full" \u2192 polaris.task.get \xB7 milestone.get \xB7 project.get \xB7 stack.get',
92
106
  '\u2022 "Fix an unclear brief" \u2192 polaris.task.update / milestone.update (PATCH description_md)',
93
107
  '\u2022 "Catch up \u2014 what happened/said?"\u2192 polaris.activity.list (events) \xB7 polaris.chat.messages (comments left by agents)',
108
+ `\u2022 "What is the agent doing / why?" \u2192 polaris.task.run_lines (the run's thread: prompt, reasoning, commands, results; human-only)`,
109
+ '\u2022 "What did this creative deliver?" \u2192 polaris.task.creatives (versions, files, caption) \xB7 polaris.task.creative_download (the file, saved locally)',
94
110
  '\u2022 "Move my task forward" \u2192 polaris.task.transition(id, status)',
95
111
  `\u2022 "It's reviewed, ship it" \u2192 polaris.task.approve (human-only); ship several together via polaris.stack.* \u2192 stack.release`,
96
112
  `\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.`,
@@ -98,6 +114,8 @@ var INSTRUCTIONS = [
98
114
  '\u2022 "Jot something down for later" \u2192 polaris.idea.create (a title is enough). NO dates: an idea is not a milestone.',
99
115
  '\u2022 "What have we already thought of?" \u2192 polaris.idea.list \u2014 read it before proposing work; it also holds what was turned down',
100
116
  '\u2022 "This idea is now real" \u2192 polaris.idea.promote (needs end_date + team) \u2014 the one door from the idea list to the timeline',
117
+ '\u2022 "This idea is one small piece of dev work" \u2192 polaris.idea.promote_task \u2014 a standalone task, no milestone, straight into the dev backlog (human-only)',
118
+ '\u2022 "This milestone was only a hunch" \u2192 polaris.milestone.demote \u2014 back to the idea list, only while it has no tasks (human-only)',
101
119
  `\u2022 "Triage support / what's waiting?" \u2192 polaris.ticket.list (status, project_key) \u2014 P1 first`,
102
120
  '\u2022 "Reply to a customer / hand off" \u2192 polaris.ticket.reply then polaris.ticket.pass(to)',
103
121
  '\u2022 "Turn a report into work" \u2192 polaris.ticket.frame_issue \u2192 polaris.issue.add_task \xD7N (framing is HUMAN-only; the helpdesker proposes it)',
@@ -227,13 +245,27 @@ var TOOLS = [
227
245
  start_date: strSchema("YYYY-MM-DD, optional"),
228
246
  name: strSchema("optional: overrides the idea's title"),
229
247
  description_md: strSchema("optional: overrides the idea's note"),
230
- framework_key: strSchema("optional: e.g. feature, marketing")
248
+ framework_key: strSchema(FRAMEWORK_KEY_DOC)
231
249
  }, ["project_key", "id", "end_date"]),
232
250
  call: async (args) => {
233
251
  const { project_key, id, ...body } = args;
234
252
  return (await apiCall("POST", `/api/projects/${project_key}/ideas/${id}/promote`, body)).body;
235
253
  }
236
254
  },
255
+ {
256
+ name: "polaris.idea.promote_task",
257
+ description: "Promote a dev idea into a STANDALONE task \u2014 one piece of work with no milestone and no deadline, born in the idea's project and straight into the dev backlog, where the project's developer agents pick it up. For work that is a goal with several tasks, use polaris.idea.promote (a milestone). A mkt idea is refused: a creative needs a campaign. Promoting twice returns the first task and writes nothing. Human-only.",
258
+ inputSchema: objSchema({
259
+ project_key: strSchema(),
260
+ id: intSchema("idea id"),
261
+ name: strSchema("optional: overrides the idea's title"),
262
+ description_md: strSchema("optional: overrides the idea's note (the developer's brief)")
263
+ }, ["project_key", "id"]),
264
+ call: async (args) => {
265
+ const { project_key, id, ...body } = args;
266
+ return (await apiCall("POST", `/api/projects/${project_key}/ideas/${id}/promote-task`, body)).body;
267
+ }
268
+ },
237
269
  {
238
270
  name: "polaris.idea.drop",
239
271
  description: "Drop an idea, with the reason it was dropped. Reversible (polaris.idea.reopen) \u2014 dropping is not a one-way door, because almost every good idea looks bad at first. Idempotent: dropping twice keeps the first reason. Needs a token.",
@@ -275,9 +307,21 @@ var TOOLS = [
275
307
  },
276
308
  {
277
309
  name: "polaris.milestone.list",
278
- description: "List milestones in a project.",
279
- inputSchema: objSchema({ project_key: strSchema() }, ["project_key"]),
280
- call: async (args) => (await apiCall("GET", `/api/projects/${args.project_key}/milestones?limit=200`)).body
310
+ description: "List milestones in a project. Each one comes with its whole description, so a project with a long history answers with a lot: narrow it with `status` (e.g. loop for the campaigns running now) and `team` (dev|mkt).",
311
+ inputSchema: objSchema({
312
+ project_key: strSchema(),
313
+ status: strSchema("draft|discovery|loop|release|done|canceled"),
314
+ team: strSchema("dev|mkt")
315
+ }, ["project_key"]),
316
+ call: async (args) => {
317
+ const qs = new URLSearchParams({ limit: "200" });
318
+ for (const k of ["status", "team"]) {
319
+ const v = args[k];
320
+ if (v !== undefined && v !== null)
321
+ qs.set(k, String(v));
322
+ }
323
+ return (await apiCall("GET", `/api/projects/${args.project_key}/milestones?${qs}`)).body;
324
+ }
281
325
  },
282
326
  {
283
327
  name: "polaris.milestone.get",
@@ -287,11 +331,13 @@ var TOOLS = [
287
331
  },
288
332
  {
289
333
  name: "polaris.milestone.update",
290
- description: "Correct or refine a milestone's spec \u2014 PATCH its description_md (and optionally name), same idea as polaris.task.update. On a mkt campaign the market can also be corrected, while it is still in draft or discovery: after that the creatives are already bound to accounts. The campaign's rhythm, budget and channels live in the description \u2014 write them there and the briefer reads them. Needs a token.",
334
+ description: "Correct or refine a milestone \u2014 PATCH its description_md, name, or dates (start_date / end_date move it on the timeline; send only the one that changes, start can never fall after end), same idea as polaris.task.update. On a mkt campaign the market can also be corrected, while it is still in draft or discovery: after that the creatives are already bound to accounts. The campaign's rhythm, budget and channels live in the description \u2014 write them there and the briefer reads them. Needs a token.",
291
335
  inputSchema: objSchema({
292
336
  id: intSchema(),
293
337
  name: strSchema(),
294
338
  description_md: strSchema("full markdown brief; replaces the current description"),
339
+ start_date: strSchema("ISO date, e.g. 2026-06-01"),
340
+ end_date: strSchema("ISO date, the deadline, e.g. 2026-06-30"),
295
341
  market: {
296
342
  type: "array",
297
343
  items: { type: "string", enum: ["BR", "US", "EU"] },
@@ -304,6 +350,10 @@ var TOOLS = [
304
350
  body.name = args.name;
305
351
  if (typeof args.description_md === "string")
306
352
  body.description_md = args.description_md;
353
+ if (typeof args.start_date === "string")
354
+ body.start_date = args.start_date;
355
+ if (typeof args.end_date === "string")
356
+ body.end_date = args.end_date;
307
357
  if ("market" in args)
308
358
  body.market = args.market;
309
359
  return (await apiCall("PATCH", `/api/milestones/${args.id}`, body)).body;
@@ -315,6 +365,12 @@ var TOOLS = [
315
365
  inputSchema: objSchema({ id: intSchema(), status: strSchema() }, ["id", "status"]),
316
366
  call: async (args) => (await apiCall("POST", `/api/milestones/${args.id}/state`, { status: args.status })).body
317
367
  },
368
+ {
369
+ name: "polaris.milestone.demote",
370
+ description: "Send a milestone back to the idea list. Only a milestone with NO tasks (not even canceled ones), still in draft, discovery or loop, and not while a planner is breaking it into tasks. The idea it was promoted from reopens (or a new idea is captured from it) carrying the milestone's name and context; the milestone is canceled and cannot be reopened \u2014 promote the idea again instead. Demoting twice returns the first idea. Human-only.",
371
+ inputSchema: objSchema({ id: intSchema("milestone id") }, ["id"]),
372
+ call: async (args) => (await apiCall("POST", `/api/milestones/${args.id}/demote`, {})).body
373
+ },
318
374
  {
319
375
  name: "polaris.milestone.qa_list",
320
376
  description: "List framework Q&A rows for a milestone.",
@@ -389,10 +445,10 @@ var TOOLS = [
389
445
  },
390
446
  {
391
447
  name: "polaris.task.list",
392
- description: `Find tasks \u2014 the agent's entry point. Pass assignee_agent_id="self" (or assignee_human_id="self" for a human) to get what is YOURS; add status=dev (etc.) to see only active work. Other filters: milestone_id, product_key, team.`,
448
+ description: `Find tasks \u2014 the agent's entry point. Pass assignee_agent_id="self" (or assignee_human_id="self" for a human) to get what is YOURS; add status=dev (etc.) to see only active work. Other filters: milestone_id (or "none" for the standalone tasks, which have no milestone), product_key, team.`,
393
449
  inputSchema: objSchema({
394
450
  status: strSchema("comma-separated subset of: draft,backlog,dev,test,approval,release,done,canceled"),
395
- milestone_id: intSchema(),
451
+ milestone_id: strSchema('a milestone id, or "none" for the standalone tasks (no milestone)'),
396
452
  product_key: strSchema("project key, e.g. saasA"),
397
453
  team: strSchema("dev|mkt"),
398
454
  assignee_agent_id: strSchema('agent id, or "self" for the calling agent'),
@@ -426,9 +482,10 @@ var TOOLS = [
426
482
  },
427
483
  {
428
484
  name: "polaris.task.create",
429
- description: "Create a task within a milestone. A marketing CREATIVE (a task in an mkt campaign) carries WHERE IT GOES: `presence_id`, one of the destinations from polaris.destination.list. The channel and the market are read from that destination, and sending them here is refused. It also takes format + scheduled_date (the publish day / calendar slot) + platform (if this creative is individually paid). A CAROUSEL (format=carrossel) also requires `slides` (2..10) \u2014 without it the call is refused, because a carousel with no declared size ships one image.",
485
+ description: "Create a task within a milestone \u2014 or, for one small piece of DEV work with no milestone, a STANDALONE task in a project: send project_key instead of milestone_id (human-only, team dev, no creative fields). A marketing CREATIVE (a task in an mkt campaign) carries WHERE IT GOES: `presence_id`, one of the destinations from polaris.destination.list. The channel and the market are read from that destination, and sending them here is refused. It also takes format + scheduled_date (the publish day / calendar slot) + platform (if this creative is individually paid). A CAROUSEL (format=carrossel) also requires `slides` (2..10) \u2014 without it the call is refused, because a carousel with no declared size ships one image.",
430
486
  inputSchema: objSchema({
431
- milestone_id: intSchema(),
487
+ milestone_id: intSchema("the milestone it belongs to \u2014 or omit it and send project_key"),
488
+ project_key: strSchema("instead of milestone_id: a standalone dev task in this project, with no milestone (human-only)"),
432
489
  name: strSchema(),
433
490
  description_md: strSchema(),
434
491
  status: strSchema("draft \u2014 the only status a task can be created in; it reaches the rest by being moved, one legal step at a time (polaris.task.transition)"),
@@ -443,7 +500,7 @@ var TOOLS = [
443
500
  scheduled_date: strSchema("mkt: publish day (ISO date) \u2014 the calendar slot"),
444
501
  platform: strSchema("mkt: ad platform if this creative is individually paid"),
445
502
  asset_url: strSchema("mkt: URL of the produced creative asset (image/video) for the reviewer")
446
- }, ["milestone_id", "name"]),
503
+ }, ["name"]),
447
504
  call: async (args) => (await apiCall("POST", "/api/tasks", args)).body
448
505
  },
449
506
  {
@@ -572,6 +629,18 @@ var TOOLS = [
572
629
  inputSchema: objSchema({ id: intSchema(), dep_task_id: intSchema() }, ["id", "dep_task_id"]),
573
630
  call: async (args) => (await apiCall("POST", `/api/tasks/${args.id}/deps`, { dep_task_id: args.dep_task_id })).body
574
631
  },
632
+ {
633
+ name: "polaris.task.together_add",
634
+ description: "Pair two Blog articles so they go out together \u2014 they cite each other. Not a dependency: nothing waits, both are written at once, and publishing either publishes both (each must be in release). Same product, same blog language, neither out yet.",
635
+ inputSchema: objSchema({ id: intSchema(), task_id: intSchema() }, ["id", "task_id"]),
636
+ call: async (args) => (await apiCall("POST", `/api/tasks/${args.id}/together`, { task_id: args.task_id })).body
637
+ },
638
+ {
639
+ name: "polaris.task.together_remove",
640
+ description: "Unpair two articles (idempotent).",
641
+ inputSchema: objSchema({ id: intSchema(), task_id: intSchema() }, ["id", "task_id"]),
642
+ call: async (args) => (await apiCall("DELETE", `/api/tasks/${args.id}/together/${args.task_id}`)).body
643
+ },
575
644
  {
576
645
  name: "polaris.task.dep_remove",
577
646
  description: "Remove a dependency edge (idempotent).",
@@ -619,13 +688,29 @@ var TOOLS = [
619
688
  },
620
689
  {
621
690
  name: "polaris.ticket.reply",
622
- 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.",
623
- inputSchema: objSchema({ id: intSchema(), body_md: strSchema(), side: strSchema("customer|team") }, ["id", "body_md"]),
691
+ 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. To send FILES with the reply (a screenshot, a screencast of the thing you are describing): upload each one first \u2014 `curl -sS -H \"Authorization: Bearer $POLARIS_TOKEN\" -F file=@clip.mp4 $POLARIS_URL/api/tickets/<id>/attachments` \u2014 then pass the ids it returns as attachment_ids. Paste each one's `marker` into body_md where it belongs and the image or video is drawn THERE, between your paragraphs; an attachment whose marker you never placed falls to a strip at the foot of the message, which for a screencast is the wrong place. Up to 15MB per file.",
692
+ inputSchema: objSchema({
693
+ id: intSchema(),
694
+ body_md: strSchema(),
695
+ side: strSchema("customer|team"),
696
+ attachment_ids: {
697
+ type: "array",
698
+ items: { type: "integer" },
699
+ description: "Ids from the attachment upload above (or from ticket.attachment_list, where sent=false). They ride on THIS message and become visible with it."
700
+ }
701
+ }, ["id", "body_md"]),
624
702
  call: async (args) => (await apiCall("POST", `/api/tickets/${args.id}/messages`, {
625
703
  body_md: args.body_md,
626
- side: args.side
704
+ side: args.side,
705
+ attachment_ids: args.attachment_ids
627
706
  })).body
628
707
  },
708
+ {
709
+ name: "polaris.ticket.attachment_list",
710
+ description: 'Every file on this ticket \u2014 what the customer sent and what we have attached. Each row carries a `marker` (`![name](ref:N)` for an image, `[name](ref:N)` for a video) to paste into a reply\'s body_md so the file is drawn inside the message instead of in a list under it, and `sent` (false while it has been uploaded and no message names it yet \u2014 nobody sees it until a reply does). A file (PDF, log) has no marker: there is nothing to draw, and it rides as a download chip. Upload with `curl -sS -H "Authorization: Bearer $POLARIS_TOKEN" -F file=@shot.png $POLARIS_URL/api/tickets/<id>/attachments` \u2014 this call does not carry bytes.',
711
+ inputSchema: objSchema({ id: intSchema() }, ["id"]),
712
+ call: async (args) => (await apiCall("GET", `/api/tickets/${args.id}/attachments`)).body
713
+ },
629
714
  {
630
715
  name: "polaris.ticket.pass",
631
716
  description: "Pass the ball \u2014 whose move it is. to=customer after you reply; to=team to pull it back. Manual + idempotent.",
@@ -836,7 +921,7 @@ var TOOLS = [
836
921
  },
837
922
  {
838
923
  name: "polaris.chat.messages",
839
- 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).",
924
+ description: "Read the comments/context agents and humans left on a scope (e.g. task 42) \u2014 free-text notes, decisions, @mentions, and the handover notes that travel with a task's moves (what the maker delivered, why a reviewer sent it back). Pass the scope; the chat id is resolved for you. To write one, polaris.chat.post.",
840
925
  inputSchema: objSchema({
841
926
  scope_type: strSchema("task|milestone|project|agent_dm|human_dm|general"),
842
927
  scope_id: intSchema(),
@@ -851,6 +936,58 @@ var TOOLS = [
851
936
  return (await apiCall("GET", `/api/chats/${chatId}/messages${qs}`)).body;
852
937
  }
853
938
  },
939
+ {
940
+ name: "polaris.task.run_lines",
941
+ description: "Watch an agent work: the THREAD of one run on a task \u2014 what it was asked (`prompt`), what it said (`said`), what it ran (`ran`, tool + full input), what came back (`back`), and the rest (`other`). This is the backstage of a run; for the comments left on the task use polaris.chat.messages. HUMAN ONLY: an agent token gets 403 (one agent reading another's reasoning changes what it can judge). A task has one run per agent session (the brief, whoever made the piece, the reviewer); the response's `runs` lists them, newest first, and without `session` you get the newest. Bodies are whole, never truncated, and the first line is the entire prompt \u2014 page with `limit`, then pass the response's `next_after` as `after` to read on or to follow a live run.",
942
+ inputSchema: objSchema({
943
+ id: intSchema("task id"),
944
+ session: strSchema("a session_id from `runs` \u2014 which run to read; omit for the newest"),
945
+ after: intSchema("return only lines with seq greater than this (the previous `next_after`)"),
946
+ limit: intSchema("how many lines (default 500, max 2000)")
947
+ }, ["id"]),
948
+ call: async (args) => {
949
+ const qs = new URLSearchParams;
950
+ for (const k of ["session", "after", "limit"]) {
951
+ const v = args[k];
952
+ if (v !== undefined && v !== null)
953
+ qs.set(k, String(v));
954
+ }
955
+ const r = await apiCall("GET", `/api/tasks/${args.id}/run-lines${qs.toString() ? `?${qs}` : ""}`);
956
+ return r.body;
957
+ }
958
+ },
959
+ {
960
+ name: "polaris.task.creatives",
961
+ description: "What a creative task has actually delivered: every version, newest first, each slot with its file (`mime`, `url`, `status`) and the words that go out beside it (`caption`, `title`, `copy`, `description`). Read this to judge a piece; the maker's handover note is in polaris.chat.messages on the task. The files sit behind the same login as the API, so to look at one, polaris.task.creative_download.",
962
+ inputSchema: objSchema({ id: intSchema("task id") }, ["id"]),
963
+ call: async (args) => (await apiCall("GET", `/api/tasks/${args.id}/creatives`)).body
964
+ },
965
+ {
966
+ name: "polaris.task.creative_download",
967
+ description: "Save one version of a creative to THIS machine, as a zip of its slides in slot order (slide-01.mp4, slide-02.png, ...), so its frames can be pulled and its sound measured. Newest version unless `version` is given; only a version whose slides are all ready exists to download (404 otherwise). Writes into `dir` (default: the system temp folder) and returns the file's absolute path and size; unzip it to look inside.",
968
+ inputSchema: objSchema({
969
+ id: intSchema("task id"),
970
+ version: intSchema("which version (polaris.task.creatives lists them); omit for the newest"),
971
+ dir: strSchema("folder to save into; created if missing. Default: the system temp folder")
972
+ }, ["id"]),
973
+ call: async (args) => {
974
+ const qs = typeof args.version === "number" ? `?version=${args.version}` : "";
975
+ const headers = {};
976
+ if (TOKEN)
977
+ headers.Authorization = `Bearer ${TOKEN}`;
978
+ const res = await fetch(`${BASE}/api/tasks/${args.id}/creative.zip${qs}`, { headers });
979
+ if (!res.ok) {
980
+ return { status: res.status, body: await res.text() };
981
+ }
982
+ const bytes = new Uint8Array(await res.arrayBuffer());
983
+ const dir = resolve(typeof args.dir === "string" && args.dir ? args.dir : tmpdir());
984
+ mkdirSync(dir, { recursive: true });
985
+ const name = `task-${args.id}-${typeof args.version === "number" ? `v${args.version}` : "latest"}.zip`;
986
+ const path = join(dir, name);
987
+ writeFileSync(path, bytes);
988
+ return { data: { path, bytes: bytes.length } };
989
+ }
990
+ },
854
991
  {
855
992
  name: "polaris.project.update",
856
993
  description: "Update a project (human-only): repo_url, name, status (active|paused|archived), owner_human_id. Brand/context live in the CLAUDE.md \u2014 use polaris.project.claude_md_set.",
@@ -1071,7 +1208,7 @@ var TOOLS = [
1071
1208
  team: strSchema("dev|mkt"),
1072
1209
  end_date: strSchema("ISO date, e.g. 2026-06-30"),
1073
1210
  start_date: strSchema("ISO date (optional)"),
1074
- framework_key: strSchema(),
1211
+ framework_key: strSchema(FRAMEWORK_KEY_DOC),
1075
1212
  description_md: strSchema(),
1076
1213
  market: {
1077
1214
  type: "array",
@@ -1178,7 +1315,7 @@ var TOOLS = [
1178
1315
  items: { type: "string" },
1179
1316
  description: "developer|tester|planner|helpdesker"
1180
1317
  },
1181
- framework_key: strSchema()
1318
+ framework_key: strSchema("optional \u2014 the team's default framework: a key from polaris.framework.list")
1182
1319
  }, ["key", "name"]),
1183
1320
  call: async (args) => (await apiCall("POST", "/api/teams", args)).body
1184
1321
  },
@@ -1190,7 +1327,7 @@ var TOOLS = [
1190
1327
  name: strSchema(),
1191
1328
  requires: { type: "array", items: { type: "string" } },
1192
1329
  functions: { type: "array", items: { type: "string" } },
1193
- framework_key: strSchema()
1330
+ framework_key: strSchema("the team's default framework: a key from polaris.framework.list")
1194
1331
  }, ["key"]),
1195
1332
  call: async (args) => {
1196
1333
  const { key, ...patch } = args;
@@ -1216,7 +1353,7 @@ async function handle(req) {
1216
1353
  return ok(id, {
1217
1354
  protocolVersion: "2024-11-05",
1218
1355
  capabilities: { tools: {} },
1219
- serverInfo: { name: "mcp-polaris", version: "0.3.0" },
1356
+ serverInfo: { name: "mcp-polaris", version: VERSION },
1220
1357
  instructions: INSTRUCTIONS
1221
1358
  });
1222
1359
  case "initialized":
@@ -1276,5 +1413,6 @@ if (true) {
1276
1413
  rl.on("close", () => log("stdin closed, exiting"));
1277
1414
  }
1278
1415
  export {
1416
+ VERSION,
1279
1417
  TOOLS
1280
1418
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glie/mcp-polaris",
3
- "version": "0.14.0",
3
+ "version": "0.19.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>",