@aioproductoscom/mcp 0.15.9 → 0.15.10
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/README.md +57 -28
- package/dist/client.js +149 -0
- package/dist/index.js +111 -1
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -41,39 +41,68 @@ at a self-hosted platform.
|
|
|
41
41
|
### Prefer no local install? Use the hosted remote
|
|
42
42
|
|
|
43
43
|
AIOProductOS is also a **hosted remote MCP** (OAuth 2.1 sign-in, no token
|
|
44
|
-
pasting,
|
|
44
|
+
pasting, the same 71 tools) — add the custom connector URL
|
|
45
45
|
`https://platform.aioproductos.com/api/mcp` in Claude.ai, Claude Code, or any
|
|
46
46
|
remote-capable MCP host. Listed on the
|
|
47
47
|
[official MCP Registry](https://registry.modelcontextprotocol.io) as
|
|
48
48
|
`com.aioproductos/mcp` and [verified on Glama](https://glama.ai/mcp/connectors/com.aioproductos/mcp).
|
|
49
49
|
|
|
50
|
-
## Tools
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
50
|
+
## Tools — 71
|
|
51
|
+
|
|
52
|
+
It reads **and** writes across the whole strategy-to-delivery ladder, so an
|
|
53
|
+
assistant can run product work end to end instead of reporting on it. Identical
|
|
54
|
+
tool set to the hosted remote connector.
|
|
55
|
+
|
|
56
|
+
**Orientation** — `whoami` (org + member), `get_pm_playbook` (how to operate),
|
|
57
|
+
`pm_meta` (lists / statuses / members / features — resolve names → ids)
|
|
58
|
+
|
|
59
|
+
**Grounding** — `get_product_brain` (revenue, accounts, analytics, features,
|
|
60
|
+
signals, open work in one read), `get_weekly_signal_memo`, `get_customer_360`
|
|
61
|
+
(money + people + voice for one account), `get_codebase_map`
|
|
62
|
+
|
|
63
|
+
**Discovery — ideas** — `list_ideas`, `create_idea`, `update_idea`, `vote_idea`,
|
|
64
|
+
`promote_idea` (idea → feature)
|
|
65
|
+
|
|
66
|
+
**Strategy — initiatives & OKRs** — `list_initiatives`, `create_initiative`,
|
|
67
|
+
`update_initiative`, `list_objectives`, `create_objective`, `update_objective`,
|
|
68
|
+
`update_key_result`
|
|
69
|
+
|
|
70
|
+
**Roadmap** — `list_features`, `create_feature`, `update_feature`,
|
|
71
|
+
`list_releases`, `create_release`, `update_release`, `get_roadmap_drift`
|
|
72
|
+
(planned vs shipped, scored)
|
|
73
|
+
|
|
74
|
+
**Delivery — board & sprints** — `list_tasks`, `get_task`, `create_task`,
|
|
75
|
+
`update_task`, `delete_task`, `comment_on_task`, `list_sprints`, `create_sprint`,
|
|
76
|
+
`update_sprint`
|
|
77
|
+
|
|
78
|
+
**Docs & decisions** — `list_pages`, `get_page`, `create_page`, `update_page`,
|
|
79
|
+
`list_decisions`, `create_decision`, `update_decision`
|
|
80
|
+
|
|
81
|
+
**Experiments** — `list_experiments`, `create_experiment`, `update_experiment`
|
|
82
|
+
|
|
83
|
+
**Voice of customer** — `list_insights`, `capture_insight`
|
|
84
|
+
|
|
85
|
+
**Analytics** — `analyze_nps`, `analyze_nrr` (both revenue-weighted),
|
|
86
|
+
`analyze_funnel`, `get_retention`, `analyze_paths`
|
|
87
|
+
|
|
88
|
+
**Support inbox** — `list_conversations`, `get_conversation`,
|
|
89
|
+
`reply_to_conversation` (**visible in the visitor's chat widget**), `add_note`
|
|
90
|
+
(internal only), `resolve_conversation`
|
|
91
|
+
|
|
92
|
+
**Scheduling** — `list_bookings`, `cancel_booking`, `reschedule_booking`
|
|
93
|
+
|
|
94
|
+
**Team comms** — `list_channels`, `read_channel`, `post_to_channel`,
|
|
95
|
+
`reply_in_channel`
|
|
96
|
+
|
|
97
|
+
**Identity graph** — `get_device_candidates`, `merge_end_users`,
|
|
98
|
+
`unmerge_end_users` (reversible 30 days), `list_identity_merges`
|
|
99
|
+
|
|
100
|
+
**AI artifacts** — `review_artifact` (critic vs your PM bar),
|
|
101
|
+
`list_artifact_versions`, `revert_to_version`
|
|
102
|
+
|
|
103
|
+
Every tool declares read-only / write / destructive hints, so a host can gate
|
|
104
|
+
them. `delete_task` is the only destructive one. Writes are scoped to your org
|
|
105
|
+
and respect your role's permissions — the token can never reach another tenant.
|
|
77
106
|
|
|
78
107
|
> For an assignable **coding teammate** (Backend / Frontend / Mobile / QA that
|
|
79
108
|
> opens PRs in your repo), install the separate
|
package/dist/client.js
CHANGED
|
@@ -205,4 +205,153 @@ export class PlatformClient {
|
|
|
205
205
|
body: JSON.stringify({ action: "reply", channel_id: channelId, parent_id: parentId, body }),
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
|
+
// ---- Strategy ladder: initiatives + ideas (goal → initiative → feature) ----
|
|
209
|
+
listInitiatives(productId) {
|
|
210
|
+
const qs = productId ? `?product_id=${encodeURIComponent(productId)}` : "";
|
|
211
|
+
return this.req(`/api/me/initiatives${qs}`);
|
|
212
|
+
}
|
|
213
|
+
createInitiative(body) {
|
|
214
|
+
return this.req("/api/me/initiatives", { method: "POST", body: JSON.stringify(body) });
|
|
215
|
+
}
|
|
216
|
+
updateInitiative(id, body) {
|
|
217
|
+
return this.req(`/api/me/initiatives/${encodeURIComponent(id)}`, { method: "PATCH", body: JSON.stringify(body) });
|
|
218
|
+
}
|
|
219
|
+
listIdeas(opts) {
|
|
220
|
+
const p = new URLSearchParams();
|
|
221
|
+
if (opts.status)
|
|
222
|
+
p.set("status", opts.status);
|
|
223
|
+
if (opts.productId)
|
|
224
|
+
p.set("product_id", opts.productId);
|
|
225
|
+
const qs = p.toString();
|
|
226
|
+
return this.req(`/api/me/ideas${qs ? `?${qs}` : ""}`);
|
|
227
|
+
}
|
|
228
|
+
createIdea(body) {
|
|
229
|
+
return this.req("/api/me/ideas", { method: "POST", body: JSON.stringify(body) });
|
|
230
|
+
}
|
|
231
|
+
updateIdea(id, body) {
|
|
232
|
+
return this.req(`/api/me/ideas/${encodeURIComponent(id)}`, { method: "PATCH", body: JSON.stringify(body) });
|
|
233
|
+
}
|
|
234
|
+
voteIdea(id, remove) {
|
|
235
|
+
return this.req(`/api/me/ideas/${encodeURIComponent(id)}/vote`, { method: remove ? "DELETE" : "POST" });
|
|
236
|
+
}
|
|
237
|
+
promoteIdea(id, body) {
|
|
238
|
+
return this.req(`/api/me/ideas/${encodeURIComponent(id)}/promote`, { method: "POST", body: JSON.stringify(body) });
|
|
239
|
+
}
|
|
240
|
+
// ---- Read parity with the hosted connector: catalogue + strategy + docs ----
|
|
241
|
+
listFeatures(opts) {
|
|
242
|
+
const p = new URLSearchParams();
|
|
243
|
+
if (opts.q)
|
|
244
|
+
p.set("q", opts.q);
|
|
245
|
+
if (opts.productId)
|
|
246
|
+
p.set("product_id", opts.productId);
|
|
247
|
+
const qs = p.toString();
|
|
248
|
+
return this.req(`/api/me/features${qs ? `?${qs}` : ""}`);
|
|
249
|
+
}
|
|
250
|
+
listObjectives(productId) {
|
|
251
|
+
const qs = productId ? `?product_id=${encodeURIComponent(productId)}` : "";
|
|
252
|
+
return this.req(`/api/me/objectives${qs}`);
|
|
253
|
+
}
|
|
254
|
+
listExperiments(opts) {
|
|
255
|
+
const p = new URLSearchParams();
|
|
256
|
+
if (opts.productId)
|
|
257
|
+
p.set("product_id", opts.productId);
|
|
258
|
+
if (opts.state)
|
|
259
|
+
p.set("state", opts.state);
|
|
260
|
+
const qs = p.toString();
|
|
261
|
+
return this.req(`/api/me/experiments${qs ? `?${qs}` : ""}`);
|
|
262
|
+
}
|
|
263
|
+
listReleases(productId) {
|
|
264
|
+
const qs = productId ? `?product_id=${encodeURIComponent(productId)}` : "";
|
|
265
|
+
return this.req(`/api/me/releases${qs}`);
|
|
266
|
+
}
|
|
267
|
+
listSprints(state) {
|
|
268
|
+
const qs = state ? `?state=${encodeURIComponent(state)}` : "";
|
|
269
|
+
return this.req(`/api/me/sprints${qs}`);
|
|
270
|
+
}
|
|
271
|
+
listPages(productId) {
|
|
272
|
+
const qs = productId ? `?product_id=${encodeURIComponent(productId)}` : "";
|
|
273
|
+
return this.req(`/api/me/pages${qs}`);
|
|
274
|
+
}
|
|
275
|
+
getPage(id) {
|
|
276
|
+
return this.req(`/api/me/pages?id=${encodeURIComponent(id)}`);
|
|
277
|
+
}
|
|
278
|
+
listInsights(opts) {
|
|
279
|
+
const p = new URLSearchParams();
|
|
280
|
+
if (opts.q)
|
|
281
|
+
p.set("q", opts.q);
|
|
282
|
+
if (opts.status)
|
|
283
|
+
p.set("status", opts.status);
|
|
284
|
+
if (opts.kind)
|
|
285
|
+
p.set("kind", opts.kind);
|
|
286
|
+
if (opts.featureId)
|
|
287
|
+
p.set("feature_id", opts.featureId);
|
|
288
|
+
if (opts.accountId)
|
|
289
|
+
p.set("account_id", opts.accountId);
|
|
290
|
+
if (opts.productId)
|
|
291
|
+
p.set("product_id", opts.productId);
|
|
292
|
+
if (opts.limit)
|
|
293
|
+
p.set("limit", String(opts.limit));
|
|
294
|
+
const qs = p.toString();
|
|
295
|
+
return this.req(`/api/me/insights${qs ? `?${qs}` : ""}`);
|
|
296
|
+
}
|
|
297
|
+
roadmapDrift(opts) {
|
|
298
|
+
const p = new URLSearchParams();
|
|
299
|
+
if (opts.window)
|
|
300
|
+
p.set("window", opts.window);
|
|
301
|
+
if (opts.productId)
|
|
302
|
+
p.set("product_id", opts.productId);
|
|
303
|
+
const qs = p.toString();
|
|
304
|
+
return this.req(`/api/me/roadmap-drift${qs ? `?${qs}` : ""}`);
|
|
305
|
+
}
|
|
306
|
+
weeklySignalMemo(opts) {
|
|
307
|
+
const p = new URLSearchParams();
|
|
308
|
+
if (opts.week)
|
|
309
|
+
p.set("week", opts.week);
|
|
310
|
+
if (opts.generate)
|
|
311
|
+
p.set("generate", "1");
|
|
312
|
+
const qs = p.toString();
|
|
313
|
+
return this.req(`/api/me/weekly-signal${qs ? `?${qs}` : ""}`);
|
|
314
|
+
}
|
|
315
|
+
codebaseMap(productId) {
|
|
316
|
+
const qs = productId ? `?product_id=${encodeURIComponent(productId)}` : "";
|
|
317
|
+
return this.req(`/api/me/codebase${qs}`);
|
|
318
|
+
}
|
|
319
|
+
listArtifactVersions(opts) {
|
|
320
|
+
const p = new URLSearchParams();
|
|
321
|
+
if (opts.targetId)
|
|
322
|
+
p.set("target_id", opts.targetId);
|
|
323
|
+
if (opts.targetType)
|
|
324
|
+
p.set("target_type", opts.targetType);
|
|
325
|
+
const qs = p.toString();
|
|
326
|
+
return this.req(`/api/me/artifact-versions${qs ? `?${qs}` : ""}`);
|
|
327
|
+
}
|
|
328
|
+
// ---- Identity graph: candidates, merge history, merge / unmerge ----
|
|
329
|
+
deviceCandidates() {
|
|
330
|
+
return this.req("/api/me/identity");
|
|
331
|
+
}
|
|
332
|
+
listIdentityMerges(limit) {
|
|
333
|
+
const p = new URLSearchParams({ view: "merges" });
|
|
334
|
+
if (limit)
|
|
335
|
+
p.set("limit", String(limit));
|
|
336
|
+
return this.req(`/api/me/identity?${p.toString()}`);
|
|
337
|
+
}
|
|
338
|
+
mergeEndUsers(body) {
|
|
339
|
+
return this.req("/api/me/identity", {
|
|
340
|
+
method: "POST",
|
|
341
|
+
body: JSON.stringify({ action: "merge_end_users", ...body }),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
unmergeEndUsers(eventId) {
|
|
345
|
+
return this.req("/api/me/identity", {
|
|
346
|
+
method: "POST",
|
|
347
|
+
body: JSON.stringify({ action: "unmerge_end_users", event_id: eventId }),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
// ---- AI artifacts: critic review + version revert ----
|
|
351
|
+
reviewArtifact(body) {
|
|
352
|
+
return this.req("/api/me/review-artifact", { method: "POST", body: JSON.stringify(body) });
|
|
353
|
+
}
|
|
354
|
+
revertToVersion(body) {
|
|
355
|
+
return this.req("/api/me/revert-version", { method: "POST", body: JSON.stringify(body) });
|
|
356
|
+
}
|
|
208
357
|
}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import { PlatformClient } from "./client.js";
|
|
6
6
|
import { PM_PLAYBOOK, PM_SKILL_VERSION } from "./pm-skill.js";
|
|
7
7
|
import { checkForUpdate, updateBanner } from "./update-notifier.js";
|
|
8
|
-
const VERSION = "0.15.
|
|
8
|
+
const VERSION = "0.15.10";
|
|
9
9
|
const token = process.env.PRODUCTOS_TOKEN;
|
|
10
10
|
const baseUrl = (process.env.PRODUCTOS_URL ?? "https://platform.aioproductos.com").replace(/\/$/, "");
|
|
11
11
|
if (!token) {
|
|
@@ -62,6 +62,8 @@ server.tool("create_feature", "Create a feature on the product spine and return
|
|
|
62
62
|
name: z.string().describe("Feature name (the only required field), e.g. 'SAML SSO'."),
|
|
63
63
|
description: z.string().optional().describe("What the feature is / why it matters (optional)."),
|
|
64
64
|
product_id: z.string().optional().describe("Product to create it under, from whoami (optional; the org's primary product when omitted)."),
|
|
65
|
+
initiative_id: z.string().optional().describe("Initiative to align this feature under for line-of-sight, from list_initiatives (optional)."),
|
|
66
|
+
objective_id: z.string().optional().describe("Objective (goal) to align this feature under directly when there's no intermediate initiative, from list_objectives (optional)."),
|
|
65
67
|
}, async (a) => text(await client.createFeature(a)));
|
|
66
68
|
server.tool("create_objective", "Create an objective (OKR), optionally with key results, and return it. period is free text (e.g. 'Q3 2026'); product_id and parent_id (a parent objective) are optional and verified in-org. Each key result takes name + optional unit / start_value / target_value. Only name is required.", {
|
|
67
69
|
name: z.string().describe("Objective name (the only required field), e.g. 'Reach $50k MRR'."),
|
|
@@ -100,6 +102,8 @@ server.tool("update_feature", "Update a feature and return it; omitted fields ar
|
|
|
100
102
|
status: z.enum(["active", "discovered", "archived"]).optional().describe("Lifecycle status (optional)."),
|
|
101
103
|
target_date: z.string().nullable().optional().describe("Target ship date 'YYYY-MM-DD', or null to clear (optional)."),
|
|
102
104
|
mark_shipped: z.boolean().optional().describe("true stamps the ship date now; false clears it (optional)."),
|
|
105
|
+
initiative_id: z.string().nullable().optional().describe("Align under this initiative (line-of-sight), from list_initiatives; null unlinks (optional)."),
|
|
106
|
+
objective_id: z.string().nullable().optional().describe("Align directly under this objective, from list_objectives; null unlinks (optional)."),
|
|
103
107
|
}, async (a) => text(await client.updateFeature(a)));
|
|
104
108
|
server.tool("update_objective", "Update an objective's name / description / period and return it; omitted fields are unchanged (null clears description or period). Resolve the id via list_objectives; only id is required. To move a key result's value use update_key_result.", {
|
|
105
109
|
id: z.string().describe("Objective id, from list_objectives (required)."),
|
|
@@ -115,6 +119,49 @@ server.tool("update_key_result", "Update a key result — most often to move cur
|
|
|
115
119
|
name: z.string().optional().describe("New name (optional)."),
|
|
116
120
|
unit: z.string().nullable().optional().describe("New unit, e.g. 'USD'; null clears it (optional)."),
|
|
117
121
|
}, async (a) => text(await client.updateKeyResult(a)));
|
|
122
|
+
server.tool("list_initiatives", "List the org's initiatives — the strategic layer between goals and features (goal → initiative → feature → epic → release). Each returns its name, status, timeframe, the objective it rolls up to (if any), and its linked-feature count. Read-only. Resolve an initiative id here before create_feature / update_feature (initiative_id) or update_initiative.", {
|
|
123
|
+
product_id: z.string().optional().describe("Only initiatives for this product, from whoami (optional)."),
|
|
124
|
+
}, async (a) => text(await client.listInitiatives(a.product_id)));
|
|
125
|
+
server.tool("create_initiative", "Create an initiative — a strategic effort that groups features and rolls up to a goal — and return it. Link it to a goal with objective_id (from list_objectives) to build line-of-sight. status is planned|active|paused|done|abandoned (default planned); timeframe is free text ('H2 2026'). product_id defaults to the primary product. Only name is required. Then align features to it via create_feature / update_feature (initiative_id).", {
|
|
126
|
+
name: z.string().describe("Initiative name (the only required field), e.g. 'Win enterprise'."),
|
|
127
|
+
description: z.string().optional().describe("What the initiative is / why it matters (optional)."),
|
|
128
|
+
objective_id: z.string().optional().describe("Goal this rolls up to, from list_objectives (optional; builds line-of-sight)."),
|
|
129
|
+
status: z.enum(["planned", "active", "paused", "done", "abandoned"]).optional().describe("Lifecycle status (optional; default 'planned')."),
|
|
130
|
+
timeframe: z.string().optional().describe("Free-text timeframe, e.g. 'Q3 2026' or 'H2 2026' (optional)."),
|
|
131
|
+
product_id: z.string().optional().describe("Product to scope it to, from whoami (optional)."),
|
|
132
|
+
}, async (a) => text(await client.createInitiative(a)));
|
|
133
|
+
server.tool("update_initiative", "Update an initiative and return it; omitted fields are unchanged. Re-point it to a different goal with objective_id (null unlinks). status is planned|active|paused|done|abandoned. Resolve the id via list_initiatives; only id is required.", {
|
|
134
|
+
id: z.string().describe("Initiative id, from list_initiatives (required)."),
|
|
135
|
+
name: z.string().optional().describe("New name (optional)."),
|
|
136
|
+
description: z.string().nullable().optional().describe("New description; null clears it (optional)."),
|
|
137
|
+
objective_id: z.string().nullable().optional().describe("New parent goal, from list_objectives; null unlinks (optional)."),
|
|
138
|
+
status: z.enum(["planned", "active", "paused", "done", "abandoned"]).optional().describe("Lifecycle status (optional)."),
|
|
139
|
+
timeframe: z.string().nullable().optional().describe("New timeframe; null clears it (optional)."),
|
|
140
|
+
}, async (a) => text(await client.updateInitiative(a.id, a)));
|
|
141
|
+
server.tool("list_ideas", "List the org's ideas — the native, votable idea backlog — ranked by vote count (highest first). Each returns title, status, vote count, author, and the feature it was promoted to (if any). status is new|under_review|planned|promoted|declined (optional filter). Read-only. Ideas are distinct from insights: an idea is a proposal a team votes on; an insight is customer evidence. Resolve an idea id here before update_idea / vote_idea / promote_idea.", {
|
|
142
|
+
status: z.enum(["new", "under_review", "planned", "promoted", "declined"]).optional().describe("Filter by status (optional)."),
|
|
143
|
+
product_id: z.string().optional().describe("Only ideas for this product, from whoami (optional)."),
|
|
144
|
+
}, async (a) => text(await client.listIdeas({ status: a.status, productId: a.product_id })));
|
|
145
|
+
server.tool("create_idea", "Create an idea in the backlog and return it (starts with 0 votes, status 'new'). Link the evidence it came from with insight_id (from list_insights). product_id defaults to the primary product. Only title is required. Grow it with vote_idea, then promote_idea turns the winner into a roadmap feature.", {
|
|
146
|
+
title: z.string().describe("Idea title (the only required field), e.g. 'Bulk-edit tasks'."),
|
|
147
|
+
body: z.string().optional().describe("The idea in more detail (optional)."),
|
|
148
|
+
insight_id: z.string().optional().describe("Customer insight this idea came from, from list_insights (optional; welds evidence to the idea)."),
|
|
149
|
+
product_id: z.string().optional().describe("Product to scope it to, from whoami (optional)."),
|
|
150
|
+
}, async (a) => text(await client.createIdea(a)));
|
|
151
|
+
server.tool("update_idea", "Update an idea's title / body / status and return it; omitted fields unchanged. status is new|under_review|planned|promoted|declined (set 'promoted' via promote_idea instead, so a feature is actually created). Resolve the id via list_ideas; only id is required.", {
|
|
152
|
+
id: z.string().describe("Idea id, from list_ideas (required)."),
|
|
153
|
+
title: z.string().optional().describe("New title (optional)."),
|
|
154
|
+
body: z.string().nullable().optional().describe("New body; null clears it (optional)."),
|
|
155
|
+
status: z.enum(["new", "under_review", "planned", "promoted", "declined"]).optional().describe("New status (optional; prefer promote_idea over setting 'promoted' by hand)."),
|
|
156
|
+
}, async (a) => text(await client.updateIdea(a.id, a)));
|
|
157
|
+
server.tool("vote_idea", "Cast (or remove) the connected member's vote on an idea and return the new vote state. Adds your vote by default; pass remove:true to take it back. One vote per member — voting twice is a no-op. Resolve the id via list_ideas; only id is required.", {
|
|
158
|
+
id: z.string().describe("Idea id, from list_ideas (required)."),
|
|
159
|
+
remove: z.boolean().optional().describe("true removes your vote instead of adding it (optional; default false)."),
|
|
160
|
+
}, async (a) => text(await client.voteIdea(a.id, a.remove)));
|
|
161
|
+
server.tool("promote_idea", "Promote an idea into a roadmap feature: creates a feature from the idea (name + description), stamps the idea 'promoted' and links it to the new feature, and returns the feature id. Idempotent — an already-promoted idea returns its existing feature. Resolve the id via list_ideas; only id is required. Align the new feature to an initiative/goal afterwards with update_feature.", {
|
|
162
|
+
id: z.string().describe("Idea id to promote, from list_ideas (required)."),
|
|
163
|
+
product_id: z.string().optional().describe("Product to create the feature under, from whoami (optional)."),
|
|
164
|
+
}, async (a) => text(await client.promoteIdea(a.id, a)));
|
|
118
165
|
server.tool("update_sprint", "Update a sprint and return it. state is 'future' | 'active' | 'closed' — moving to 'closed' stamps the completion time, reopening clears it. start_date / end_date are ISO 8601 (or null to clear). Resolve the id via list_sprints; only id is required.", {
|
|
119
166
|
id: z.string().describe("Sprint id, from list_sprints (required)."),
|
|
120
167
|
name: z.string().optional().describe("New name (optional)."),
|
|
@@ -257,6 +304,69 @@ server.tool("reply_in_channel", "Reply in a thread under a specific message in a
|
|
|
257
304
|
parent_id: z.string().describe("Parent message id, from read_channel."),
|
|
258
305
|
body: z.string().describe("Reply text, visible to all channel members."),
|
|
259
306
|
}, async (a) => text(await client.replyInChannel(a.channel_id, a.parent_id, a.body)));
|
|
307
|
+
// ── Read parity with the hosted connector ───────────────────────────────────
|
|
308
|
+
// The full catalogue + strategy ladder + docs + identity + artifact reads, so
|
|
309
|
+
// the stdio agent grounds on exactly what the hosted connector sees.
|
|
310
|
+
server.tool("list_features", "The product's feature catalogue with description, status, and when each was last touched — richer than pm_meta (id+name only). Read-only; empty when none. Optional product_id (from whoami) and free-text q over name+key. Use a feature id from here to link a task or insight on the spine.", {
|
|
311
|
+
q: z.string().optional().describe("Free-text search over feature name + key (optional)."),
|
|
312
|
+
product_id: z.string().optional().describe("Product id to scope to, from whoami (optional; spans all products when omitted)."),
|
|
313
|
+
}, async (a) => text(await client.listFeatures({ q: a.q, productId: a.product_id })));
|
|
314
|
+
server.tool("list_objectives", "List the org's OKRs — each objective with its key results and live progress (0..1 between start and target). Read-only; empty when none set. Read it before prioritising; tie proposed tasks to the objective they move and cite live progress when arguing priority. Optional product_id, from whoami.", { product_id: z.string().optional().describe("Product id to scope to, from whoami (optional; spans all products when omitted).") }, async (a) => text(await client.listObjectives(a.product_id)));
|
|
315
|
+
server.tool("list_experiments", "List the org's experiments with their state and linked feature. Read-only; empty when none. Optional product_id (from whoami) and state filter. Use an experiment id from here with review_artifact or list_artifact_versions.", {
|
|
316
|
+
product_id: z.string().optional().describe("Product id to scope to, from whoami (optional; spans all products when omitted)."),
|
|
317
|
+
state: z.string().optional().describe("Only experiments in this state (optional)."),
|
|
318
|
+
}, async (a) => text(await client.listExperiments({ productId: a.product_id, state: a.state })));
|
|
319
|
+
server.tool("list_releases", "List the org's releases (version, date, linked features). Read-only; empty when none. Optional product_id, from whoami. Use it to see what shipped and when, then open slipped work with list_features or get_roadmap_drift.", { product_id: z.string().optional().describe("Product id to scope to, from whoami (optional; spans all products when omitted).") }, async (a) => text(await client.listReleases(a.product_id)));
|
|
320
|
+
server.tool("list_sprints", "List the org's sprints with their state and dates. Read-only; empty when none. Optional state filter (e.g. active | planned | closed). Resolve a sprint id here before scheduling tasks into it via update_task / update_sprint.", { state: z.string().optional().describe("Only sprints in this state, e.g. active | planned | closed (optional).") }, async (a) => text(await client.listSprints(a.state)));
|
|
321
|
+
server.tool("list_pages", "List the org's Pages (docs / PRDs) with title and last-updated — id+title for resolution. Read-only; empty when none. Optional product_id, from whoami. Get one page's full content with get_page.", { product_id: z.string().optional().describe("Product id to scope to, from whoami (optional; spans all products when omitted).") }, async (a) => text(await client.listPages(a.product_id)));
|
|
322
|
+
server.tool("get_page", "Read one Page (doc / PRD) by id and return its full content. Read-only. Resolve the id first with list_pages — never guess it.", { id: z.string().describe("Page id, from list_pages.") }, async (a) => text(await client.getPage(a.id)));
|
|
323
|
+
server.tool("list_insights", "Search the captured insight backlog (voice of customer) — the read twin of capture_insight. Read-only; matching insights newest first, empty when nothing matches. Filters: status, kind (insight|opportunity), feature_id, account_id, product_id, free-text q over title+body; limit default 50, max 200. Survey the evidence behind a feature or account before prioritising.", {
|
|
324
|
+
q: z.string().optional().describe("Free-text search over title + body (optional)."),
|
|
325
|
+
status: z.string().optional().describe("Only insights in this workflow status (optional)."),
|
|
326
|
+
kind: z.enum(["insight", "opportunity"]).optional().describe("'insight' = raw signal; 'opportunity' = a prioritisable ask (optional)."),
|
|
327
|
+
feature_id: z.string().optional().describe("Only insights linked to this feature; id via list_features or pm_meta (optional)."),
|
|
328
|
+
account_id: z.string().optional().describe("Only insights about this account; id via get_customer_360 (optional)."),
|
|
329
|
+
product_id: z.string().optional().describe("Product id to scope to, from whoami (optional; spans all products when omitted)."),
|
|
330
|
+
limit: z.number().int().min(1).max(200).optional().describe("Max rows to return (optional; default 50, max 200)."),
|
|
331
|
+
}, async (a) => text(await client.listInsights({
|
|
332
|
+
q: a.q,
|
|
333
|
+
status: a.status,
|
|
334
|
+
kind: a.kind,
|
|
335
|
+
featureId: a.feature_id,
|
|
336
|
+
accountId: a.account_id,
|
|
337
|
+
productId: a.product_id,
|
|
338
|
+
limit: a.limit,
|
|
339
|
+
})));
|
|
340
|
+
server.tool("get_roadmap_drift", "Planned vs shipped features over a window: a drift score (0-100, 100 = perfect alignment), counts (planned / shipped / on-time / slipped / unplanned / orphaned), median slip days, and the top slipped + unplanned ships. Deterministic, no LLM cost. window = week | month | quarter (default quarter); optional product_id. Read-only; zeroed when nothing was planned or shipped. Use it in planning reviews, then open slipped features with list_features.", {
|
|
341
|
+
window: z.enum(["week", "month", "quarter"]).optional().describe("Lookback window to compare planned vs shipped (optional; default quarter)."),
|
|
342
|
+
product_id: z.string().optional().describe("Product id, from whoami (optional; spans all the org's products when omitted)."),
|
|
343
|
+
}, async (a) => text(await client.roadmapDrift({ window: a.window, productId: a.product_id })));
|
|
344
|
+
server.tool("get_weekly_signal_memo", "The deterministic weekly signal memo — what moved on the spine this week (revenue, top accounts, fresh verbatim signal, shipped + slipped work). Read-only. Optional week (ISO week, e.g. 2026-W29; defaults to the current week); set generate=true to (re)compute it if this week's memo isn't cached yet.", {
|
|
345
|
+
week: z.string().optional().describe("ISO week, e.g. '2026-W29' (optional; defaults to the current week)."),
|
|
346
|
+
generate: z.boolean().optional().describe("Recompute the memo if it isn't cached yet (optional)."),
|
|
347
|
+
}, async (a) => text(await client.weeklySignalMemo({ week: a.week, generate: a.generate })));
|
|
348
|
+
server.tool("get_codebase_map", "The product's codebase map — modules and how they connect, with live spine pulses (recent deploys / activity). Read-only. Optional product_id, from whoami. Use it to ground engineering-adjacent questions in the actual code structure.", { product_id: z.string().optional().describe("Product id to scope to, from whoami (optional; spans all products when omitted).") }, async (a) => text(await client.codebaseMap(a.product_id)));
|
|
349
|
+
server.tool("list_artifact_versions", "List the immutable version history of one AI artifact (a feature spec, experiment plan, or page) — each version with its score and when it was written. Read-only. Resolve target_id via list_features / list_experiments / list_pages first. Pair with revert_to_version to roll one back, or review_artifact to add a new reviewed version.", {
|
|
350
|
+
target_id: z.string().optional().describe("Id of the feature / experiment / page whose versions to list, from list_features / list_experiments / list_pages."),
|
|
351
|
+
target_type: z.enum(["feature", "experiment", "page"]).optional().describe("What kind of artifact target_id is (optional)."),
|
|
352
|
+
}, async (a) => text(await client.listArtifactVersions({ targetId: a.target_id, targetType: a.target_type })));
|
|
353
|
+
server.tool("get_device_candidates", "Clusters of ≥2 end-users seen on the same device: 'anon_bridge' (high confidence — an anonymous visitor later identified) or 'device_shared' (low confidence — review only). Read-only; empty when none found. Use it to find merge targets, then act with merge_end_users.", {}, async () => text(await client.deviceCandidates()));
|
|
354
|
+
server.tool("list_identity_merges", "List the org's end-user merge history, newest first — each event with its id, kind (merge | unmerge), target + source ids, reason, who ran it, when, and whether a merge was already reverted. Read-only; empty when none have run. Use it to audit identity changes and find the event id for unmerge_end_users (only un-reverted merges can be undone).", { limit: z.number().int().min(1).max(200).optional().describe("Max events to return (optional; default 50, max 200).") }, async (a) => text(await client.listIdentityMerges(a.limit)));
|
|
355
|
+
server.tool("merge_end_users", "Merge source end-users into a target and return the result, including the merge event id (also in list_identity_merges): all FK rows (events, insights, tasks, …) re-point onto the target and the sources are tombstoned. A write; reversible for 30 days via unmerge_end_users. Get the candidate ids from get_device_candidates first — never guess which users to fold together.", {
|
|
356
|
+
target_end_user_id: z.string().describe("UUID of the end-user to keep, from get_device_candidates."),
|
|
357
|
+
source_end_user_ids: z.array(z.string()).describe("UUIDs of end-users to fold into the target, from get_device_candidates."),
|
|
358
|
+
reason: z.string().optional().describe("Why the merge (optional, recorded)."),
|
|
359
|
+
}, async (a) => text(await client.mergeEndUsers({ target_end_user_id: a.target_end_user_id, source_end_user_ids: a.source_end_user_ids, reason: a.reason })));
|
|
360
|
+
server.tool("unmerge_end_users", "Undo a previous end-user merge by its event id and return the result — the tombstoned sources are restored and rows re-split. A write; only an un-reverted merge event can be undone. Find the event id with list_identity_merges — never guess it.", { event_id: z.string().describe("Merge event id to undo, from list_identity_merges.") }, async (a) => text(await client.unmergeEndUsers(a.event_id)));
|
|
361
|
+
server.tool("review_artifact", "Agent-as-critic over a DRAFT artifact (a feature spec, experiment plan, or page): checks it against a baseline PM bar — clear problem/hypothesis, a measurable success metric, evidence cited, risks named, a rollout/experiment plan — and returns structured findings (section, severity, concrete fix, verbatim evidence quote) plus a 0-100 score. A write: each call re-runs the review and persists a new version (see list_artifact_versions). Resolve target_id first via list_features / list_experiments / list_pages. One small LLM call; use it before sending a draft for sign-off.", {
|
|
362
|
+
target_id: z.string().describe("Id of the feature / experiment / page to review, from list_features / list_experiments / list_pages."),
|
|
363
|
+
target_type: z.enum(["feature", "experiment", "page"]).describe("What kind of artifact target_id is: a feature (spec), experiment (plan), or page (doc/PRD)."),
|
|
364
|
+
rubric_id: z.string().optional().describe("Score against a specific rubric; omit to use the org's default (or the built-in baseline)."),
|
|
365
|
+
}, async (a) => text(await client.reviewArtifact({ target_id: a.target_id, target_type: a.target_type, rubric_id: a.rubric_id })));
|
|
366
|
+
server.tool("revert_to_version", "Roll an AI artifact back to a prior version by version_id and return the result — the artifact's live content is replaced with that version's, recorded as a new version so nothing is lost. A write. Find the version_id with list_artifact_versions — never guess it.", {
|
|
367
|
+
version_id: z.string().describe("Version id to revert to, from list_artifact_versions."),
|
|
368
|
+
reason: z.string().optional().describe("Why the revert (optional, recorded)."),
|
|
369
|
+
}, async (a) => text(await client.revertToVersion({ version_id: a.version_id, reason: a.reason })));
|
|
260
370
|
// ── Routines (slash-command prompts) ────────────────────────────────────────
|
|
261
371
|
// Surface in the AI host as named slash commands (e.g. Claude Code:
|
|
262
372
|
// /productos:standup), so the human — or a scheduled headless run — can run a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aioproductoscom/mcp",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.10",
|
|
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",
|
|
@@ -37,11 +37,6 @@
|
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "tsc",
|
|
42
|
-
"prepublishOnly": "npm run build",
|
|
43
|
-
"start": "node dist/index.js"
|
|
44
|
-
},
|
|
45
40
|
"dependencies": {
|
|
46
41
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
47
42
|
"zod": "^3.23.8"
|
|
@@ -49,5 +44,9 @@
|
|
|
49
44
|
"devDependencies": {
|
|
50
45
|
"@types/node": "^22.7.0",
|
|
51
46
|
"typescript": "^5.6.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc",
|
|
50
|
+
"start": "node dist/index.js"
|
|
52
51
|
}
|
|
53
|
-
}
|
|
52
|
+
}
|