@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 +3 -0
- package/dist/index.js +24 -6
- package/dist/update-notifier.js +84 -0
- package/package.json +1 -1
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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:
|
|
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.
|
|
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",
|