@aioproductoscom/mcp 0.15.6 → 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/index.js +23 -6
- package/dist/update-notifier.js +84 -0
- package/package.json +1 -1
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 " +
|
|
@@ -273,6 +282,14 @@ server.prompt("daily", "Daily PM briefing — your plate, blockers, and the one
|
|
|
273
282
|
`Output a tight briefing: top priorities today, blockers needing a decision, and ONE recommended next action. ` +
|
|
274
283
|
`Read-only unless they ask you to act.`));
|
|
275
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(() => { });
|
|
276
293
|
await server.connect(new StdioServerTransport());
|
|
277
294
|
}
|
|
278
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",
|