@aioproductoscom/mcp 0.15.9 → 0.16.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.
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, 38 tools) — add the custom connector URL
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
- | Tool | What it does |
53
- |---|---|
54
- | `whoami` | the connected org + member |
55
- | `pm_meta` | lists / statuses / members / features (resolve names → ids) |
56
- | `list_tasks` | list tasks (filter by `status_id`, `list_id`) |
57
- | `get_task` | one task + comments + assignees |
58
- | `create_task` | new task; `feature_id` / `insight_id` link it to the spine |
59
- | `update_task` | change status, priority, fields, assignees |
60
- | `comment_on_task` | add a comment |
61
- | `get_product_brain` | a grounded read-only snapshot of your product (revenue, accounts, web + product analytics, features, signals, open work) |
62
- | `analyze_funnel` | build an ordered conversion funnel from your events (users per step, conversion, drop-off); omit steps to list available events |
63
- | `get_retention` | weekly cohort retention who comes back, week over week |
64
- | `analyze_paths` | user paths (Sankey) — what people do after a start event, with drop-off |
65
- | `list_conversations` | the support-chat inbox (open threads, visitor, topic, status) |
66
- | `get_conversation` | one support thread + its full message history |
67
- | `reply_to_conversation` | reply to a visitor (**visible in their chat widget**, sent as you) |
68
- | `add_note` | internal triage note on a conversation (never shown to the visitor) |
69
- | `resolve_conversation` | mark a conversation resolved |
70
- | `list_bookings` | scheduled calls/meetings (upcoming by default) |
71
- | `cancel_booking` | cancel a booking (frees the slot, cancels the meeting) |
72
- | `reschedule_booking` | move a booking to a new open slot |
73
- | `list_channels` | the team Comms channels you belong to |
74
- | `read_channel` | recent messages in a channel (catch up before replying) |
75
- | `post_to_channel` | post into a team channel — visible live to teammates, as you |
76
- | `reply_in_channel` | reply in a thread under a message |
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
@@ -0,0 +1,16 @@
1
+ import type { RestCall } from "./registry.js";
2
+ export interface RestResult {
3
+ ok: boolean;
4
+ status: number;
5
+ /** Raw response body. Passed through verbatim — same contract as the hosted
6
+ * dispatcher, and it sidesteps the old failure mode where a non-JSON error
7
+ * page (a 502 from the edge) crashed JSON.parse with "Unexpected token '<'"
8
+ * instead of reporting what actually happened. */
9
+ text: string;
10
+ }
11
+ export declare class PlatformClient {
12
+ private readonly baseUrl;
13
+ private readonly token;
14
+ constructor(baseUrl: string, token: string);
15
+ request(call: RestCall): Promise<RestResult>;
16
+ }
package/dist/client.js CHANGED
@@ -1,5 +1,14 @@
1
- // Thin typed HTTP client over the platform's token-authed PM endpoints.
2
- // Uses global fetch (Node 18+). The PAT is the only credential; nothing is stored.
1
+ // One hardened HTTP executor over the platform's token-authed REST endpoints.
2
+ // The registry's RestCall describes WHAT to call; this describes HOW the PAT
3
+ // header, a timeout, and honest failure text. The PAT is the only credential;
4
+ // nothing is stored.
5
+ //
6
+ // This file used to be 70 near-identical methods (one per tool). The shared
7
+ // registry made them redundant: every tool already declares its own
8
+ // method/path/body, so the client collapses to a single request function.
9
+ /** A hung platform request must fail the TOOL CALL, not hang the agent's turn
10
+ * forever — 30s is far beyond any healthy endpoint here. */
11
+ const TIMEOUT_MS = 30_000;
3
12
  export class PlatformClient {
4
13
  baseUrl;
5
14
  token;
@@ -7,202 +16,25 @@ export class PlatformClient {
7
16
  this.baseUrl = baseUrl;
8
17
  this.token = token;
9
18
  }
10
- async req(path, init) {
11
- const res = await fetch(`${this.baseUrl}${path}`, {
12
- ...init,
13
- headers: {
14
- "Content-Type": "application/json",
15
- Authorization: `Bearer ${this.token}`,
16
- ...(init?.headers ?? {}),
17
- },
18
- });
19
- const body = await res.text();
20
- const json = body ? JSON.parse(body) : {};
21
- if (!res.ok) {
22
- const o = (json ?? {});
23
- const msg = o.error ? `${o.error}${o.detail ? `: ${o.detail}` : ""}` : `HTTP ${res.status}`;
24
- const e = new Error(msg);
25
- e.status = res.status;
26
- throw e;
19
+ async request(call) {
20
+ try {
21
+ const res = await fetch(`${this.baseUrl}${call.path}`, {
22
+ method: call.method,
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ Authorization: `Bearer ${this.token}`,
26
+ },
27
+ body: call.body !== undefined ? JSON.stringify(call.body) : undefined,
28
+ signal: AbortSignal.timeout(TIMEOUT_MS),
29
+ });
30
+ return { ok: res.ok, status: res.status, text: await res.text() };
31
+ }
32
+ catch (e) {
33
+ // Transport-level failure (offline, DNS, timeout) — return it as an
34
+ // honest error result rather than throwing, so the tool result tells the
35
+ // model what happened instead of surfacing a stack trace.
36
+ const why = e instanceof Error && e.name === "TimeoutError" ? `platform did not respond within ${TIMEOUT_MS / 1000}s` : String(e);
37
+ return { ok: false, status: 0, text: `Platform unreachable: ${why}` };
27
38
  }
28
- return json;
29
- }
30
- whoami() {
31
- return this.req("/api/me");
32
- }
33
- meta() {
34
- return this.req("/api/pm/meta");
35
- }
36
- listTasks(q) {
37
- const params = new URLSearchParams();
38
- if (q.status_id)
39
- params.set("status_id", q.status_id);
40
- if (q.list_id)
41
- params.set("list_id", q.list_id);
42
- const qs = params.toString();
43
- return this.req(`/api/pm/tasks${qs ? `?${qs}` : ""}`);
44
- }
45
- getTask(id) {
46
- return this.req(`/api/pm/tasks/${encodeURIComponent(id)}`);
47
- }
48
- createTask(body) {
49
- return this.req("/api/pm/tasks", { method: "POST", body: JSON.stringify(body) });
50
- }
51
- createFeature(body) {
52
- return this.req("/api/me/features", { method: "POST", body: JSON.stringify(body) });
53
- }
54
- createObjective(body) {
55
- return this.req("/api/me/objectives", { method: "POST", body: JSON.stringify(body) });
56
- }
57
- createSprint(body) {
58
- return this.req("/api/me/sprints", { method: "POST", body: JSON.stringify(body) });
59
- }
60
- createPage(body) {
61
- return this.req("/api/me/pages", { method: "POST", body: JSON.stringify(body) });
62
- }
63
- updateFeature(body) {
64
- return this.req("/api/me/features", { method: "PATCH", body: JSON.stringify(body) });
65
- }
66
- updateObjective(body) {
67
- return this.req("/api/me/objectives", { method: "PATCH", body: JSON.stringify(body) });
68
- }
69
- updateKeyResult(body) {
70
- return this.req("/api/me/key-results", { method: "PATCH", body: JSON.stringify(body) });
71
- }
72
- updateSprint(body) {
73
- return this.req("/api/me/sprints", { method: "PATCH", body: JSON.stringify(body) });
74
- }
75
- updatePage(body) {
76
- return this.req("/api/me/pages", { method: "PATCH", body: JSON.stringify(body) });
77
- }
78
- createRelease(body) {
79
- return this.req("/api/me/releases", { method: "POST", body: JSON.stringify(body) });
80
- }
81
- updateRelease(body) {
82
- return this.req("/api/me/releases", { method: "PATCH", body: JSON.stringify(body) });
83
- }
84
- createExperiment(body) {
85
- return this.req("/api/me/experiments", { method: "POST", body: JSON.stringify(body) });
86
- }
87
- updateExperiment(body) {
88
- return this.req("/api/me/experiments", { method: "PATCH", body: JSON.stringify(body) });
89
- }
90
- listDecisions(status) {
91
- return this.req(`/api/me/decisions${status ? `?status=${encodeURIComponent(status)}` : ""}`);
92
- }
93
- createDecision(body) {
94
- return this.req("/api/me/decisions", { method: "POST", body: JSON.stringify(body) });
95
- }
96
- updateDecision(body) {
97
- return this.req("/api/me/decisions", { method: "PATCH", body: JSON.stringify(body) });
98
- }
99
- updateTask(id, body) {
100
- return this.req(`/api/pm/tasks/${encodeURIComponent(id)}`, { method: "PATCH", body: JSON.stringify(body) });
101
- }
102
- deleteTask(id) {
103
- return this.req(`/api/pm/tasks/${encodeURIComponent(id)}`, { method: "DELETE" });
104
- }
105
- comment(id, body) {
106
- return this.req(`/api/pm/tasks/${encodeURIComponent(id)}/comments`, {
107
- method: "POST",
108
- body: JSON.stringify({ body }),
109
- });
110
- }
111
- brain(productId) {
112
- const qs = productId ? `?product_id=${encodeURIComponent(productId)}` : "";
113
- return this.req(`/api/me/brain${qs}`);
114
- }
115
- captureInsight(body) {
116
- return this.req("/api/me/insight", { method: "POST", body: JSON.stringify(body) });
117
- }
118
- customer360(query) {
119
- return this.req(`/api/me/customer?q=${encodeURIComponent(query)}`);
120
- }
121
- nps(opts) {
122
- const p = new URLSearchParams();
123
- if (opts.productId)
124
- p.set("product_id", opts.productId);
125
- if (opts.windowDays)
126
- p.set("window_days", String(opts.windowDays));
127
- const qs = p.toString();
128
- return this.req(`/api/me/nps${qs ? `?${qs}` : ""}`);
129
- }
130
- nrr(windowDays) {
131
- const qs = windowDays ? `?window_days=${windowDays}` : "";
132
- return this.req(`/api/me/nrr${qs}`);
133
- }
134
- funnel(opts) {
135
- const p = new URLSearchParams();
136
- if (opts.productId)
137
- p.set("product_id", opts.productId);
138
- if (opts.window)
139
- p.set("window", String(opts.window));
140
- for (const s of opts.steps ?? [])
141
- p.append("step", s);
142
- const qs = p.toString();
143
- return this.req(`/api/me/funnel${qs ? `?${qs}` : ""}`);
144
- }
145
- retention(opts) {
146
- const p = new URLSearchParams();
147
- if (opts.productId)
148
- p.set("product_id", opts.productId);
149
- if (opts.window)
150
- p.set("window", String(opts.window));
151
- const qs = p.toString();
152
- return this.req(`/api/me/retention${qs ? `?${qs}` : ""}`);
153
- }
154
- paths(opts) {
155
- const p = new URLSearchParams();
156
- if (opts.productId)
157
- p.set("product_id", opts.productId);
158
- if (opts.window)
159
- p.set("window", String(opts.window));
160
- if (opts.start)
161
- p.set("start", opts.start);
162
- const qs = p.toString();
163
- return this.req(`/api/me/paths${qs ? `?${qs}` : ""}`);
164
- }
165
- listConversations(opts) {
166
- const p = new URLSearchParams();
167
- if (opts.productId)
168
- p.set("product_id", opts.productId);
169
- if (opts.status)
170
- p.set("status", opts.status);
171
- const qs = p.toString();
172
- return this.req(`/api/me/inbox${qs ? `?${qs}` : ""}`);
173
- }
174
- getConversation(id) {
175
- return this.req(`/api/me/inbox?conversation_id=${encodeURIComponent(id)}`);
176
- }
177
- inboxAction(body) {
178
- return this.req("/api/me/inbox", { method: "POST", body: JSON.stringify(body) });
179
- }
180
- listBookings(opts) {
181
- const qs = opts.include === "all" ? "?include=all" : "";
182
- return this.req(`/api/me/scheduling${qs}`);
183
- }
184
- schedulingAction(body) {
185
- return this.req("/api/me/scheduling", { method: "POST", body: JSON.stringify(body) });
186
- }
187
- listChannels() {
188
- return this.req("/api/me/comms");
189
- }
190
- readChannel(channelId, limit) {
191
- const p = new URLSearchParams({ channel_id: channelId });
192
- if (limit)
193
- p.set("limit", String(limit));
194
- return this.req(`/api/me/comms?${p.toString()}`);
195
- }
196
- postToChannel(channelId, body) {
197
- return this.req("/api/me/comms", {
198
- method: "POST",
199
- body: JSON.stringify({ action: "post", channel_id: channelId, body }),
200
- });
201
- }
202
- replyInChannel(channelId, parentId, body) {
203
- return this.req("/api/me/comms", {
204
- method: "POST",
205
- body: JSON.stringify({ action: "reply", channel_id: channelId, parent_id: parentId, body }),
206
- });
207
39
  }
208
40
  }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};