@glie/mcp-polaris 0.1.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 +321 -40
  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
@@ -5,6 +5,7 @@
5
5
  import { createInterface } from "readline";
6
6
  var BASE = process.env.POLARIS_URL ?? "http://127.0.0.1:7843";
7
7
  var TOKEN = process.env.POLARIS_TOKEN ?? "";
8
+ var PROJECT = process.env.POLARIS_PROJECT ?? "";
8
9
  var log = (msg, ctx) => process.stderr.write(`mcp-polaris: ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ""}
9
10
  `);
10
11
  async function apiCall(method, path, body) {
@@ -41,10 +42,54 @@ function objSchema(properties, required = []) {
41
42
  additionalProperties: false
42
43
  };
43
44
  }
45
+ var INSTRUCTIONS = [
46
+ `Polaris is the studio's operating system. All product work lives as Projects \u2192 Milestones \u2192 Tasks, and agents ("employees") move it forward. Each polaris.* tool wraps one REST call.`,
47
+ PROJECT ? `You are scoped to project \`${PROJECT}\` \u2014 use it as project_key/product_key unless told otherwise.` : "This connector is not pinned to a project; use polaris.project.list to find yours.",
48
+ "",
49
+ "START HERE \u2014 get what is YOURS (resolved from your token, no id lookup needed):",
50
+ '\u2022 agent \u2192 polaris.task.list { assignee_agent_id: "self" }',
51
+ '\u2022 human \u2192 polaris.task.list { assignee_human_id: "self" } (and polaris.me for your identity)',
52
+ " Narrow with status (e.g. dev) once you know what's assigned to you.",
53
+ "\u2022 polaris.claude_md.get(project_key) \u2014 read the project's conventions BEFORE you touch its tasks.",
54
+ "",
55
+ "STATE MACHINES \u2014 transitions are enforced; only legal moves succeed:",
56
+ "\u2022 Task: draft \u2192 backlog \u2192 dev \u2192 test \u2192 approval \u2192 release \u2192 done (any \u2192 canceled)",
57
+ " dev = an agent implements it & opens a PR \xB7 test = QA \xB7 approval = a human reviews \xB7 release = merged to dev/shipped.",
58
+ "\u2022 Milestone: draft \u2192 discovery \u2192 loop \u2192 release \u2192 done (any \u2192 canceled)",
59
+ " discovery = answer the framework Q&A to split it into tasks \xB7 loop = build/test/approve \xB7 release = ship.",
60
+ "\u2022 Stack (Release Candidate): 2+ approval tasks of ONE milestone on an rc branch, tested & shipped together \u2014",
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.",
71
+ "",
72
+ "WHICH TOOL WHEN:",
73
+ `\u2022 "What's mine / what next?" \u2192 polaris.task.list (assignee \u2026="self", status=\u2026)`,
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)',
77
+ '\u2022 "Move my task forward" \u2192 polaris.task.transition(id, status)',
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.`,
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)',
85
+ "",
86
+ "Identifiers: projects by `key` (string, e.g. saasA); milestones/tasks/agents/stacks by numeric `id`. Writes need a token; reads work anonymously."
87
+ ].join(`
88
+ `);
44
89
  var TOOLS = [
45
90
  {
46
91
  name: "polaris.me",
47
- description: "Get the current authenticated human (PAT owner).",
92
+ description: `Resolve the human your PAT belongs to (identity + org). Humans call this first; agents don't need it \u2014 they pass "self" to other tools.`,
48
93
  inputSchema: objSchema({}),
49
94
  call: async () => {
50
95
  const r = await apiCall("GET", "/api/humans/me");
@@ -95,6 +140,23 @@ var TOOLS = [
95
140
  inputSchema: objSchema({ id: intSchema() }, ["id"]),
96
141
  call: async (args) => (await apiCall("GET", `/api/milestones/${args.id}`)).body
97
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
+ },
98
160
  {
99
161
  name: "polaris.milestone.transition",
100
162
  description: "Transition milestone status (state machine enforced: draft\u2192discovery\u2192loop\u2192release\u2192done, *\u2192canceled).",
@@ -121,17 +183,27 @@ var TOOLS = [
121
183
  },
122
184
  {
123
185
  name: "polaris.task.list",
124
- description: "List tasks across milestones. Filter by status/product/team/assignee.",
186
+ 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.`,
125
187
  inputSchema: objSchema({
126
- status: strSchema("comma-separated: backlog,dev,test,..."),
188
+ status: strSchema("comma-separated subset of: draft,backlog,dev,test,approval,release,done,canceled"),
127
189
  milestone_id: intSchema(),
128
- product_key: strSchema(),
190
+ product_key: strSchema("project key, e.g. saasA"),
129
191
  team: strSchema("dev|mkt"),
192
+ assignee_agent_id: strSchema('agent id, or "self" for the calling agent'),
193
+ assignee_human_id: strSchema('human id, or "self" for the calling human'),
130
194
  limit: intSchema()
131
195
  }),
132
196
  call: async (args) => {
133
197
  const qs = new URLSearchParams;
134
- for (const k of ["status", "milestone_id", "product_key", "team", "limit"]) {
198
+ for (const k of [
199
+ "status",
200
+ "milestone_id",
201
+ "product_key",
202
+ "team",
203
+ "assignee_agent_id",
204
+ "assignee_human_id",
205
+ "limit"
206
+ ]) {
135
207
  const v = args[k];
136
208
  if (v !== undefined && v !== null)
137
209
  qs.set(k, String(v));
@@ -152,15 +224,32 @@ var TOOLS = [
152
224
  inputSchema: objSchema({ milestone_id: intSchema(), name: strSchema(), description_md: strSchema() }, ["milestone_id", "name"]),
153
225
  call: async (args) => (await apiCall("POST", "/api/tasks", args)).body
154
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
+ },
155
244
  {
156
245
  name: "polaris.task.transition",
157
- description: "Transition task status (state machine enforced).",
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).",
158
247
  inputSchema: objSchema({ id: intSchema(), status: strSchema() }, ["id", "status"]),
159
248
  call: async (args) => (await apiCall("POST", `/api/tasks/${args.id}/state`, { status: args.status })).body
160
249
  },
161
250
  {
162
251
  name: "polaris.task.approve",
163
- description: "Approve a task in approval state (human-only).",
252
+ description: "The human review gate: approve a task in `approval` \u2192 it advances toward release. Human-only. To ship several approval tasks of one milestone together, group them into a stack and use polaris.stack.release instead.",
164
253
  inputSchema: objSchema({ id: intSchema() }, ["id"]),
165
254
  call: async (args) => (await apiCall("POST", `/api/tasks/${args.id}/approve`)).body
166
255
  },
@@ -176,10 +265,159 @@ var TOOLS = [
176
265
  inputSchema: objSchema({ id: intSchema(), dep_task_id: intSchema() }, ["id", "dep_task_id"]),
177
266
  call: async (args) => (await apiCall("DELETE", `/api/tasks/${args.id}/deps/${args.dep_task_id}`)).body
178
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
+ },
179
417
  {
180
418
  name: "polaris.stack.list",
181
- description: "List approval stacks (Release Candidates). Filter by status: liquid|frozen|released|canceled.",
182
- inputSchema: objSchema({ status: strSchema("liquid|frozen|released|canceled") }),
419
+ description: "List Release-Candidate stacks (groups of approval tasks shipped together). Filter by status: liquid|blocked|frozen|released|canceled.",
420
+ inputSchema: objSchema({ status: strSchema("liquid|blocked|frozen|released|canceled") }),
183
421
  call: async (args) => {
184
422
  const qs = typeof args.status === "string" ? `?status=${encodeURIComponent(args.status)}` : "";
185
423
  return (await apiCall("GET", `/api/stacks${qs}`)).body;
@@ -187,22 +425,16 @@ var TOOLS = [
187
425
  },
188
426
  {
189
427
  name: "polaris.stack.get",
190
- description: "Get one approval stack by id \u2014 includes its member tasks and the rc integration branch.",
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.",
191
429
  inputSchema: objSchema({ id: intSchema() }, ["id"]),
192
430
  call: async (args) => (await apiCall("GET", `/api/stacks/${args.id}`)).body
193
431
  },
194
432
  {
195
433
  name: "polaris.stack.release",
196
- description: "Release a stack \u2014 merge its rc branch to dev and settle members to release (human-only).",
434
+ description: "Ship a stack \u2014 merge its rc PR into dev, settle members to release, then close & delete the member branches (their commits land via the rc). Human-only; barred while blocked.",
197
435
  inputSchema: objSchema({ id: intSchema() }, ["id"]),
198
436
  call: async (args) => (await apiCall("POST", `/api/stacks/${args.id}/release`)).body
199
437
  },
200
- {
201
- name: "polaris.stack.reject",
202
- description: "Reject a stack \u2014 return its member tasks to their prior state (human-only).",
203
- inputSchema: objSchema({ id: intSchema() }, ["id"]),
204
- call: async (args) => (await apiCall("POST", `/api/stacks/${args.id}/reject`)).body
205
- },
206
438
  {
207
439
  name: "polaris.agent.list",
208
440
  description: "List agents.",
@@ -223,7 +455,7 @@ var TOOLS = [
223
455
  },
224
456
  {
225
457
  name: "polaris.claude_md.get",
226
- description: "Read the CLAUDE.md of a project (via polaris-bot).",
458
+ description: "Read a project's CLAUDE.md \u2014 its conventions, stack and rules (served from main via polaris-bot). Do this BEFORE working that project's tasks.",
227
459
  inputSchema: objSchema({ project_key: strSchema() }, ["project_key"]),
228
460
  call: async (args) => (await apiCall("GET", `/api/projects/${args.project_key}/claude-md`)).body
229
461
  },
@@ -232,6 +464,52 @@ var TOOLS = [
232
464
  description: "List the curated frameworks (knowledge bases).",
233
465
  inputSchema: objSchema({}),
234
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
+ }
235
513
  }
236
514
  ];
237
515
  var TOOLS_BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
@@ -252,7 +530,8 @@ async function handle(req) {
252
530
  return ok(id, {
253
531
  protocolVersion: "2024-11-05",
254
532
  capabilities: { tools: {} },
255
- serverInfo: { name: "mcp-polaris", version: "0.1.0" }
533
+ serverInfo: { name: "mcp-polaris", version: "0.3.0" },
534
+ instructions: INSTRUCTIONS
256
535
  });
257
536
  case "initialized":
258
537
  return null;
@@ -287,27 +566,29 @@ async function handle(req) {
287
566
  return fail(id, -32601, `method not found: ${req.method}`);
288
567
  }
289
568
  }
290
- log(`starting against ${BASE} (token=${TOKEN ? "set" : "none"})`);
291
- var rl = createInterface({ input: process.stdin, crlfDelay: Number.POSITIVE_INFINITY });
292
- rl.on("line", async (line) => {
293
- if (!line.trim())
294
- return;
295
- let req;
296
- try {
297
- req = JSON.parse(line);
298
- } catch (_err) {
299
- send(fail(null, -32700, "parse error", { line }));
300
- return;
301
- }
302
- try {
303
- const res = await handle(req);
304
- if (res)
305
- send(res);
306
- } catch (err) {
307
- send(fail(req.id ?? null, -32603, err.message));
308
- }
309
- });
310
- 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
+ }
311
592
  export {
312
593
  TOOLS
313
594
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glie/mcp-polaris",
3
- "version": "0.1.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>",