@aioproductoscom/mcp 0.15.5 → 0.15.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -99,6 +99,9 @@ export class PlatformClient {
99
99
  updateTask(id, body) {
100
100
  return this.req(`/api/pm/tasks/${encodeURIComponent(id)}`, { method: "PATCH", body: JSON.stringify(body) });
101
101
  }
102
+ deleteTask(id) {
103
+ return this.req(`/api/pm/tasks/${encodeURIComponent(id)}`, { method: "DELETE" });
104
+ }
102
105
  comment(id, body) {
103
106
  return this.req(`/api/pm/tasks/${encodeURIComponent(id)}/comments`, {
104
107
  method: "POST",
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { z } from "zod";
5
5
  import { PlatformClient } from "./client.js";
6
6
  import { PM_PLAYBOOK, PM_SKILL_VERSION } from "./pm-skill.js";
7
+ import { checkForUpdate, updateBanner } from "./update-notifier.js";
8
+ const VERSION = "0.15.7";
7
9
  const token = process.env.PRODUCTOS_TOKEN;
8
10
  const baseUrl = (process.env.PRODUCTOS_URL ?? "https://platform.aioproductos.com").replace(/\/$/, "");
9
11
  if (!token) {
@@ -11,19 +13,26 @@ if (!token) {
11
13
  process.exit(1);
12
14
  }
13
15
  const client = new PlatformClient(baseUrl, token);
16
+ // Set once the startup npm check finds a newer release; the banner then rides
17
+ // on the NEXT tool result (the model reads it and tells the user) — exactly once.
18
+ let updateNotice = null;
19
+ let updateNoticeShown = false;
14
20
  function text(data) {
15
- return {
16
- content: [
17
- { type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) },
18
- ],
19
- };
21
+ const content = [
22
+ { type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) },
23
+ ];
24
+ if (updateNotice && !updateNoticeShown) {
25
+ updateNoticeShown = true;
26
+ content.push({ type: "text", text: updateNotice });
27
+ }
28
+ return { content };
20
29
  }
21
30
  /** A prompt result — one user-role message the host injects when the slash
22
31
  * command runs. Surfaces as a named slash command in the AI host. */
23
32
  function promptMsg(body) {
24
33
  return { messages: [{ role: "user", content: { type: "text", text: body } }] };
25
34
  }
26
- const server = new McpServer({ name: "AIOProductOS", version: "0.15.5" }, {
35
+ const server = new McpServer({ name: "AIOProductOS", version: VERSION }, {
27
36
  instructions: "You manage product work on AIOProductOS for the connected member — board, insights, features, and " +
28
37
  "analytics all hang off one spine. Call get_pm_playbook first for how to operate: ground in " +
29
38
  "get_product_brain, keep work tied to the spine (insight→feature→task→outcome), and prioritize on " +
@@ -180,6 +189,7 @@ server.tool("update_task", "Update one or more of a task's fields and return the
180
189
  insight_id: z.string().nullable().optional().describe("New insight id; null unlinks (optional)."),
181
190
  assignee_member_ids: z.array(z.string()).optional().describe("Member ids to assign, from pm_meta (optional)."),
182
191
  }, async ({ id, ...body }) => text(await client.updateTask(id, body)));
192
+ server.tool("delete_task", "PERMANENTLY delete a task and return the deleted id. Irreversible — there is no undo. Cascades: the task's comments, assignees, tags, attachments, time entries, outcomes, events, relations, and its SUBTASKS are deleted with it; experiment/insight/meeting links to it are cleared. Resolve the id via list_tasks and confirm intent first — prefer update_task (move it to a done/archived status) when you only want it off the active board.", { id: z.string().describe("Task id to permanently delete, from list_tasks (required).") }, async (a) => text(await client.deleteTask(a.id)));
183
193
  server.tool("comment_on_task", "Add a comment to a task, authored as the connected member, and return the created comment. Use to record progress, a decision, or a handoff — the comment is visible to the whole org, so keep it work-relevant. Resolve the task id first with list_tasks or get_task; both id and body are required.", {
184
194
  id: z.string().describe("Task id, from list_tasks or get_task."),
185
195
  body: z.string().describe("Comment body."),
@@ -272,6 +282,14 @@ server.prompt("daily", "Daily PM briefing — your plate, blockers, and the one
272
282
  `Output a tight briefing: top priorities today, blockers needing a decision, and ONE recommended next action. ` +
273
283
  `Read-only unless they ask you to act.`));
274
284
  async function main() {
285
+ // Fire-and-forget: if a newer version is on npm, arm the one-time banner that
286
+ // rides on the next tool result. Never blocks the transport, never throws.
287
+ void checkForUpdate(VERSION)
288
+ .then((latest) => {
289
+ if (latest)
290
+ updateNotice = updateBanner(latest, VERSION);
291
+ })
292
+ .catch(() => { });
275
293
  await server.connect(new StdioServerTransport());
276
294
  }
277
295
  main().catch((err) => {
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Update notifier — lets a stdio user (Cursor / Claude / Codex …) know when a
3
+ * newer @aioproductoscom/mcp is on npm. The MCP host shows tool RESULTS to the
4
+ * model, so the notice rides along on the first tool call (see index.ts) and the
5
+ * assistant relays it. Everything here is best-effort and silent: it never
6
+ * throws, never blocks the transport, and never writes to stdout (which would
7
+ * corrupt the JSON-RPC stream). Cached ~daily in the temp dir so we don't hit
8
+ * npm on every launch. Opt out with NO_UPDATE_NOTIFIER / PRODUCTOS_NO_UPDATE_NOTIFIER,
9
+ * and it's disabled under CI.
10
+ */
11
+ import { readFileSync, writeFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { tmpdir } from "node:os";
14
+ const PKG = "@aioproductoscom/mcp";
15
+ const CACHE = join(tmpdir(), "aioproductos-mcp-update.json");
16
+ const TTL_MS = 24 * 60 * 60 * 1000; // check npm at most once a day
17
+ const FETCH_TIMEOUT_MS = 2500;
18
+ /** true when `latest` is a higher release than `current` (patch/minor/major; pre-release tags ignored). */
19
+ export function isNewer(latest, current) {
20
+ const parts = (v) => v.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
21
+ const a = parts(latest);
22
+ const b = parts(current);
23
+ for (let i = 0; i < 3; i++) {
24
+ const x = a[i] ?? 0;
25
+ const y = b[i] ?? 0;
26
+ if (x > y)
27
+ return true;
28
+ if (x < y)
29
+ return false;
30
+ }
31
+ return false;
32
+ }
33
+ async function fetchLatest() {
34
+ try {
35
+ const ctrl = new AbortController();
36
+ const to = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
37
+ const res = await fetch(`https://registry.npmjs.org/${PKG}/latest`, {
38
+ signal: ctrl.signal,
39
+ headers: { accept: "application/json" },
40
+ });
41
+ clearTimeout(to);
42
+ if (!res.ok)
43
+ return null;
44
+ const body = (await res.json());
45
+ return typeof body.version === "string" ? body.version : null;
46
+ }
47
+ catch {
48
+ return null; // offline / timeout / registry hiccup — stay quiet
49
+ }
50
+ }
51
+ /** Latest published version if it's newer than `current`, else null. Never throws. */
52
+ export async function checkForUpdate(current) {
53
+ if (process.env.NO_UPDATE_NOTIFIER || process.env.PRODUCTOS_NO_UPDATE_NOTIFIER || process.env.CI) {
54
+ return null;
55
+ }
56
+ let latest = null;
57
+ try {
58
+ const cached = JSON.parse(readFileSync(CACHE, "utf8"));
59
+ if (cached && Date.now() - cached.at < TTL_MS && typeof cached.latest === "string") {
60
+ latest = cached.latest;
61
+ }
62
+ }
63
+ catch {
64
+ /* no cache yet or unreadable — fall through to a live check */
65
+ }
66
+ if (!latest) {
67
+ latest = await fetchLatest();
68
+ if (latest) {
69
+ try {
70
+ writeFileSync(CACHE, JSON.stringify({ at: Date.now(), latest }));
71
+ }
72
+ catch {
73
+ /* temp dir not writable — fine, we just re-check next launch */
74
+ }
75
+ }
76
+ }
77
+ return latest && isNewer(latest, current) ? latest : null;
78
+ }
79
+ /** The one-line banner appended to a tool result when an update is available. */
80
+ export function updateBanner(latest, current) {
81
+ return (`\n\n— ⬆️ A newer AIOProductOS MCP is available: ${latest} (you're on ${current}). ` +
82
+ `Update by pointing your MCP config at \`@aioproductoscom/mcp@latest\` (or \`@${latest}\`) ` +
83
+ `and restarting your MCP client. New tools and fixes ship regularly. —`);
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aioproductoscom/mcp",
3
- "version": "0.15.5",
3
+ "version": "0.15.7",
4
4
  "mcpName": "com.aioproductos/mcp",
5
5
  "description": "AIOProductOS MCP — manage the board, read your product brain + analytics, and run the support inbox from Claude Code, Cursor, Codex, and any MCP host.",
6
6
  "type": "module",