@imboard.ai/mcp-server 0.1.0 → 0.1.3

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.
@@ -1,49 +1,64 @@
1
1
  import { z } from 'zod';
2
- import { boardIdParam, resourceIdParam, paginationParams, buildQueryParams, formatResult, handleToolError, RegisterToolsFn } from './shared.js';
2
+ import {
3
+ boardIdParam,
4
+ resourceIdParam,
5
+ paginationParams,
6
+ buildQueryParams,
7
+ formatResult,
8
+ handleToolError,
9
+ RegisterToolsFn,
10
+ } from './shared.js';
3
11
 
4
12
  export const registerMeetingTools: RegisterToolsFn = (server, client) => {
5
13
  server.tool(
6
14
  'list_board_meetings',
7
- 'Lists meetings for a board. Supports filtering by status and date range, and sorting by start time.',
15
+ 'Lists meetings for a board. Supports filtering by status and date range, and sorting by start time. Includes a top-level `users` map resolving any userId references in the response (e.g. createdByUserId) to name + positions.',
8
16
  {
9
17
  boardId: boardIdParam,
10
18
  ...paginationParams,
11
- sort: z.enum(['updatedAt', 'startTime']).optional().describe('Sort field (default: startTime)'),
19
+ sort: z
20
+ .enum(['updatedAt', 'startTime'])
21
+ .optional()
22
+ .describe('Sort field (default: startTime)'),
12
23
  status: z.string().optional().describe('Filter by meeting status'),
13
- startAfter: z.string().optional().describe('ISO-8601 date — only meetings starting after this'),
14
- startBefore: z.string().optional().describe('ISO-8601 date — only meetings starting before this'),
24
+ startAfter: z
25
+ .string()
26
+ .optional()
27
+ .describe('ISO-8601 date — only meetings starting after this'),
28
+ startBefore: z
29
+ .string()
30
+ .optional()
31
+ .describe('ISO-8601 date — only meetings starting before this'),
15
32
  },
16
33
  async ({ boardId, ...rest }) => {
17
34
  try {
18
35
  const params = buildQueryParams(rest, ['status', 'startAfter', 'startBefore']);
19
- const result = await client.getCollection(
20
- `/api/boards/${boardId}/meetings`,
21
- params,
22
- );
23
- return formatResult({ data: result.data, meta: result.meta });
36
+ params.include = 'users';
37
+ const result = await client.getCollection(`/api/boards/${boardId}/meetings`, params);
38
+ return formatResult({ data: result.data, meta: result.meta, users: result.users });
24
39
  } catch (error) {
25
40
  return handleToolError(error);
26
41
  }
27
- },
42
+ }
28
43
  );
29
44
 
30
45
  server.tool(
31
46
  'get_meeting',
32
- 'Returns details for a specific meeting including title, status, location, and scheduled time.',
47
+ 'Returns details for a specific meeting including title, status, location, and scheduled time. Includes a top-level `users` map resolving any userId references in the response.',
33
48
  {
34
49
  boardId: boardIdParam,
35
50
  meetingId: resourceIdParam.describe('The meeting ID'),
36
51
  },
37
52
  async ({ boardId, meetingId }) => {
38
53
  try {
39
- const result = await client.get(
40
- `/api/boards/${boardId}/meetings/${meetingId}`,
41
- );
42
- return formatResult({ data: result.data });
54
+ const result = await client.get(`/api/boards/${boardId}/meetings/${meetingId}`, {
55
+ include: 'users',
56
+ });
57
+ return formatResult({ data: result.data, users: result.users });
43
58
  } catch (error) {
44
59
  return handleToolError(error);
45
60
  }
46
- },
61
+ }
47
62
  );
48
63
 
49
64
  server.tool(
@@ -52,14 +67,25 @@ export const registerMeetingTools: RegisterToolsFn = (server, client) => {
52
67
  {
53
68
  boardId: boardIdParam,
54
69
  title: z.string().describe('Meeting title (max 200 characters)'),
55
- status: z.string().describe('Meeting status: draft, planned, scheduled, completed, or finalized'),
70
+ status: z
71
+ .string()
72
+ .describe('Meeting status: draft, planned, scheduled, completed, or finalized'),
56
73
  locationType: z.string().describe('Location type: physical, virtual, or hybrid'),
57
74
  startTime: z.string().describe('Meeting start time as ISO-8601 date string'),
58
75
  endTime: z.string().describe('Meeting end time as ISO-8601 date string'),
59
76
  location: z.string().optional().describe('Physical location (max 255 characters)'),
60
77
  virtualMeetingUrl: z.string().optional().describe('Virtual meeting URL (max 255 characters)'),
61
78
  },
62
- async ({ boardId, title, status, locationType, startTime, endTime, location, virtualMeetingUrl }) => {
79
+ async ({
80
+ boardId,
81
+ title,
82
+ status,
83
+ locationType,
84
+ startTime,
85
+ endTime,
86
+ location,
87
+ virtualMeetingUrl,
88
+ }) => {
63
89
  try {
64
90
  const body: Record<string, string> = {
65
91
  title,
@@ -71,15 +97,12 @@ export const registerMeetingTools: RegisterToolsFn = (server, client) => {
71
97
  if (location !== undefined) body.location = location;
72
98
  if (virtualMeetingUrl !== undefined) body.virtualMeetingUrl = virtualMeetingUrl;
73
99
 
74
- const result = await client.post(
75
- `/api/boards/${boardId}/meetings`,
76
- body,
77
- );
100
+ const result = await client.post(`/api/boards/${boardId}/meetings`, body);
78
101
  return formatResult({ data: result.data });
79
102
  } catch (error) {
80
103
  return handleToolError(error);
81
104
  }
82
- },
105
+ }
83
106
  );
84
107
 
85
108
  server.tool(
@@ -89,14 +112,27 @@ export const registerMeetingTools: RegisterToolsFn = (server, client) => {
89
112
  boardId: boardIdParam,
90
113
  meetingId: resourceIdParam.describe('The meeting ID'),
91
114
  title: z.string().optional().describe('Meeting title (max 200 characters)'),
92
- status: z.string().optional().describe('Meeting status: draft, planned, scheduled, completed, or finalized'),
115
+ status: z
116
+ .string()
117
+ .optional()
118
+ .describe('Meeting status: draft, planned, scheduled, completed, or finalized'),
93
119
  locationType: z.string().optional().describe('Location type: physical, virtual, or hybrid'),
94
120
  location: z.string().optional().describe('Physical location (max 255 characters)'),
95
121
  virtualMeetingUrl: z.string().optional().describe('Virtual meeting URL (max 255 characters)'),
96
122
  startTime: z.string().optional().describe('Meeting start time as ISO-8601 date string'),
97
123
  endTime: z.string().optional().describe('Meeting end time as ISO-8601 date string'),
98
124
  },
99
- async ({ boardId, meetingId, title, status, locationType, location, virtualMeetingUrl, startTime, endTime }) => {
125
+ async ({
126
+ boardId,
127
+ meetingId,
128
+ title,
129
+ status,
130
+ locationType,
131
+ location,
132
+ virtualMeetingUrl,
133
+ startTime,
134
+ endTime,
135
+ }) => {
100
136
  try {
101
137
  const body: Record<string, string> = {};
102
138
  if (title !== undefined) body.title = title;
@@ -107,15 +143,12 @@ export const registerMeetingTools: RegisterToolsFn = (server, client) => {
107
143
  if (startTime !== undefined) body.startTime = startTime;
108
144
  if (endTime !== undefined) body.endTime = endTime;
109
145
 
110
- const result = await client.patch(
111
- `/api/boards/${boardId}/meetings/${meetingId}`,
112
- body,
113
- );
146
+ const result = await client.patch(`/api/boards/${boardId}/meetings/${meetingId}`, body);
114
147
  return formatResult({ data: result.data });
115
148
  } catch (error) {
116
149
  return handleToolError(error);
117
150
  }
118
- },
151
+ }
119
152
  );
120
153
 
121
154
  server.tool(
@@ -127,14 +160,12 @@ export const registerMeetingTools: RegisterToolsFn = (server, client) => {
127
160
  },
128
161
  async ({ boardId, meetingId }) => {
129
162
  try {
130
- const result = await client.delete(
131
- `/api/meetings/${boardId}/${meetingId}/`,
132
- );
163
+ const result = await client.delete(`/api/meetings/${boardId}/${meetingId}/`);
133
164
  return formatResult({ data: result.data });
134
165
  } catch (error) {
135
166
  return handleToolError(error);
136
167
  }
137
- },
168
+ }
138
169
  );
139
170
 
140
171
  server.tool(
@@ -143,18 +174,19 @@ export const registerMeetingTools: RegisterToolsFn = (server, client) => {
143
174
  {
144
175
  boardId: boardIdParam,
145
176
  meetingId: resourceIdParam.describe('The meeting ID'),
146
- meetingStatus: z.string().describe('New meeting status: draft, planned, scheduled, completed, or finalized'),
177
+ meetingStatus: z
178
+ .string()
179
+ .describe('New meeting status: draft, planned, scheduled, completed, or finalized'),
147
180
  },
148
181
  async ({ boardId, meetingId, meetingStatus }) => {
149
182
  try {
150
- const result = await client.put(
151
- `/api/meetings/${boardId}/${meetingId}/status`,
152
- { meetingStatus },
153
- );
183
+ const result = await client.put(`/api/meetings/${boardId}/${meetingId}/status`, {
184
+ meetingStatus,
185
+ });
154
186
  return formatResult({ data: result.data });
155
187
  } catch (error) {
156
188
  return handleToolError(error);
157
189
  }
158
- },
190
+ }
159
191
  );
160
192
  };
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ import { boardIdParam, formatResult, handleToolError, RegisterToolsFn } from './shared.js';
3
+
4
+ export const registerPersonaDossierTools: RegisterToolsFn = (server, client) => {
5
+ server.tool(
6
+ 'get_persona_dossier',
7
+ 'Get a per-persona dossier for a board function (e.g., finance, sales, hr, r-and-d, product). Returns a markdown guide with section overview, field-by-field guidance, data acquisition hints, and common pitfalls — designed to help you assist the persona in filling their board section.',
8
+ {
9
+ boardId: boardIdParam,
10
+ functionSlug: z
11
+ .string()
12
+ .describe(
13
+ 'The function slug: finance, sales, hr, r-and-d, product, legal, operations',
14
+ ),
15
+ },
16
+ async ({ boardId, functionSlug }) => {
17
+ try {
18
+ const response = await client.get(
19
+ `/api/boards/${boardId}/dossiers/${functionSlug}`,
20
+ );
21
+ return formatResult({ data: response.data });
22
+ } catch (error) {
23
+ return handleToolError(error);
24
+ }
25
+ },
26
+ );
27
+ };
@@ -1,47 +1,72 @@
1
1
  import { z } from 'zod';
2
- import { boardIdParam, resourceIdParam, paginationParams, buildQueryParams, formatResult, handleToolError, RegisterToolsFn } from './shared.js';
2
+ import {
3
+ boardIdParam,
4
+ resourceIdParam,
5
+ paginationParams,
6
+ buildQueryParams,
7
+ formatResult,
8
+ handleToolError,
9
+ RegisterToolsFn,
10
+ } from './shared.js';
3
11
 
4
12
  export const registerReportTools: RegisterToolsFn = (server, client) => {
5
13
  server.tool(
6
14
  'list_board_reports',
7
- 'Lists reports for a board. Supports filtering by status.',
15
+ 'Lists reports for a board. Supports filtering by status. Includes a top-level `users` map resolving any userId references in the response (e.g. createdByUserId).',
8
16
  {
9
17
  boardId: boardIdParam,
10
18
  ...paginationParams,
11
- status: z.enum(['draft', 'publishedForReview', 'finalized', 'archived', 'ongoing']).optional()
19
+ status: z
20
+ .enum(['draft', 'publishedForReview', 'finalized', 'archived', 'ongoing'])
21
+ .optional()
12
22
  .describe('Filter by report status'),
13
23
  },
14
24
  async ({ boardId, ...rest }) => {
15
25
  try {
16
26
  const params = buildQueryParams(rest, ['status']);
17
- const result = await client.getCollection(
18
- `/api/boards/${boardId}/reports`,
19
- params,
20
- );
21
- return formatResult({ data: result.data, meta: result.meta });
27
+ params.include = 'users';
28
+ const result = await client.getCollection(`/api/boards/${boardId}/reports`, params);
29
+ return formatResult({ data: result.data, meta: result.meta, users: result.users });
22
30
  } catch (error) {
23
31
  return handleToolError(error);
24
32
  }
25
- },
33
+ }
26
34
  );
27
35
 
28
36
  server.tool(
29
37
  'get_report',
30
- 'Returns details for a specific board report including title, status, and publication date.',
38
+ 'Returns details for a specific board report including title, status, and publication date. Includes a top-level `users` map resolving any userId references in the response.',
31
39
  {
32
40
  boardId: boardIdParam,
33
41
  reportId: resourceIdParam.describe('The report ID'),
34
42
  },
35
43
  async ({ boardId, reportId }) => {
36
44
  try {
37
- const result = await client.get(
38
- `/api/boards/${boardId}/reports/${reportId}`,
39
- );
40
- return formatResult({ data: result.data });
45
+ const result = await client.get(`/api/boards/${boardId}/reports/${reportId}`, {
46
+ include: 'users',
47
+ });
48
+ return formatResult({ data: result.data, users: result.users });
41
49
  } catch (error) {
42
50
  return handleToolError(error);
43
51
  }
52
+ }
53
+ );
54
+
55
+ server.tool(
56
+ 'get_report_full',
57
+ 'Returns the ENTIRE board pack in one call: report metadata plus every dashboard’s latest normalized KPI content (all types — cash, sales, customers, pipeline, fundraising, HR, product, and custom narrative) and deterministic data-quality flags. Prefer this over fetching dashboards one by one — it avoids a slow multi-call fan-out.',
58
+ {
59
+ boardId: boardIdParam,
60
+ reportId: resourceIdParam.describe('The report ID'),
44
61
  },
62
+ async ({ boardId, reportId }) => {
63
+ try {
64
+ const result = await client.get(`/api/boards/${boardId}/reports/${reportId}/full`);
65
+ return formatResult({ data: result.data });
66
+ } catch (error) {
67
+ return handleToolError(error);
68
+ }
69
+ }
45
70
  );
46
71
 
47
72
  server.tool(
@@ -67,7 +92,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
67
92
  } catch (error) {
68
93
  return handleToolError(error);
69
94
  }
70
- },
95
+ }
71
96
  );
72
97
 
73
98
  server.tool(
@@ -83,7 +108,16 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
83
108
  periodStart: z.string().optional().describe('Period start date (YYYY-MM-DD)'),
84
109
  periodEnd: z.string().optional().describe('Period end date (YYYY-MM-DD)'),
85
110
  },
86
- async ({ boardId, reportId, name, description, status, meetingIds, periodStart, periodEnd }) => {
111
+ async ({
112
+ boardId,
113
+ reportId,
114
+ name,
115
+ description,
116
+ status,
117
+ meetingIds,
118
+ periodStart,
119
+ periodEnd,
120
+ }) => {
87
121
  try {
88
122
  const body: Record<string, unknown> = {};
89
123
  if (name !== undefined) body.name = name;
@@ -97,7 +131,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
97
131
  } catch (error) {
98
132
  return handleToolError(error);
99
133
  }
100
- },
134
+ }
101
135
  );
102
136
 
103
137
  server.tool(
@@ -115,7 +149,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
115
149
  } catch (error) {
116
150
  return handleToolError(error);
117
151
  }
118
- },
152
+ }
119
153
  );
120
154
 
121
155
  server.tool(
@@ -142,7 +176,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
142
176
  } catch (error) {
143
177
  return handleToolError(error);
144
178
  }
145
- },
179
+ }
146
180
  );
147
181
 
148
182
  server.tool(
@@ -163,7 +197,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
163
197
  } catch (error) {
164
198
  return handleToolError(error);
165
199
  }
166
- },
200
+ }
167
201
  );
168
202
 
169
203
  server.tool(
@@ -173,7 +207,9 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
173
207
  boardId: boardIdParam,
174
208
  reportId: resourceIdParam.describe('The report ID'),
175
209
  dashboardId: resourceIdParam.describe('The dashboard ID'),
176
- content: z.record(z.string(), z.unknown()).describe('Dashboard content object (must include _type field)'),
210
+ content: z
211
+ .record(z.string(), z.unknown())
212
+ .describe('Dashboard content object (must include _type field)'),
177
213
  notes: z.array(z.string()).optional().describe('Version notes'),
178
214
  },
179
215
  async ({ boardId, reportId, dashboardId, content, notes }) => {
@@ -182,13 +218,13 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
182
218
  if (notes !== undefined) body.notes = notes;
183
219
  const result = await client.post(
184
220
  `/api/reports/${boardId}/${reportId}/dashboards/${dashboardId}/versions`,
185
- body,
221
+ body
186
222
  );
187
223
  return formatResult({ data: result.data });
188
224
  } catch (error) {
189
225
  return handleToolError(error);
190
226
  }
191
- },
227
+ }
192
228
  );
193
229
 
194
230
  server.tool(
@@ -204,13 +240,13 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
204
240
  try {
205
241
  const result = await client.put(
206
242
  `/api/reports/${boardId}/${reportId}/dashboards/${dashboardId}/review-status`,
207
- { newStatus },
243
+ { newStatus }
208
244
  );
209
245
  return formatResult({ data: result.data });
210
246
  } catch (error) {
211
247
  return handleToolError(error);
212
248
  }
213
- },
249
+ }
214
250
  );
215
251
 
216
252
  server.tool(
@@ -228,13 +264,13 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
228
264
  if (chatType !== undefined) params.chatType = chatType;
229
265
  const result = await client.get(
230
266
  `/api/reports/${boardId}/${reportId}/dashboards/${dashboardId}/chat`,
231
- params,
267
+ params
232
268
  );
233
269
  return formatResult({ data: result.data });
234
270
  } catch (error) {
235
271
  return handleToolError(error);
236
272
  }
237
- },
273
+ }
238
274
  );
239
275
 
240
276
  server.tool(
@@ -245,7 +281,9 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
245
281
  reportId: resourceIdParam.describe('The report ID'),
246
282
  dashboardId: resourceIdParam.describe('The dashboard ID'),
247
283
  message: z.string().describe('Comment text (1-5000 characters)'),
248
- chatType: z.enum(['internal', 'boardFeedback']).describe('Chat type: internal or boardFeedback'),
284
+ chatType: z
285
+ .enum(['internal', 'boardFeedback'])
286
+ .describe('Chat type: internal or boardFeedback'),
249
287
  recipientId: z.string().optional().describe('Recipient user ID (for directed feedback)'),
250
288
  },
251
289
  async ({ boardId, reportId, dashboardId, message, chatType, recipientId }) => {
@@ -254,13 +292,13 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
254
292
  if (recipientId !== undefined) body.recipientId = recipientId;
255
293
  const result = await client.post(
256
294
  `/api/reports/${boardId}/${reportId}/dashboards/${dashboardId}/chat`,
257
- body,
295
+ body
258
296
  );
259
297
  return formatResult({ data: result.data });
260
298
  } catch (error) {
261
299
  return handleToolError(error);
262
300
  }
263
- },
301
+ }
264
302
  );
265
303
 
266
304
  server.tool(
@@ -277,13 +315,13 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
277
315
  if (recipientId !== undefined) params.recipientId = recipientId;
278
316
  const result = await client.get(
279
317
  `/api/reports/${boardId}/${reportId}/board-feedback`,
280
- params,
318
+ params
281
319
  );
282
320
  return formatResult({ data: result.data });
283
321
  } catch (error) {
284
322
  return handleToolError(error);
285
323
  }
286
- },
324
+ }
287
325
  );
288
326
 
289
327
  server.tool(
@@ -297,15 +335,15 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
297
335
  },
298
336
  async ({ boardId, reportId, message, recipientId }) => {
299
337
  try {
300
- const result = await client.post(
301
- `/api/reports/${boardId}/${reportId}/board-feedback`,
302
- { message, recipientId },
303
- );
338
+ const result = await client.post(`/api/reports/${boardId}/${reportId}/board-feedback`, {
339
+ message,
340
+ recipientId,
341
+ });
304
342
  return formatResult({ data: result.data });
305
343
  } catch (error) {
306
344
  return handleToolError(error);
307
345
  }
308
- },
346
+ }
309
347
  );
310
348
 
311
349
  server.tool(
@@ -325,7 +363,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
325
363
  } catch (error) {
326
364
  return handleToolError(error);
327
365
  }
328
- },
366
+ }
329
367
  );
330
368
 
331
369
  server.tool(
@@ -342,7 +380,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
342
380
  } catch (error) {
343
381
  return handleToolError(error);
344
382
  }
345
- },
383
+ }
346
384
  );
347
385
 
348
386
  server.tool(
@@ -360,7 +398,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
360
398
  } catch (error) {
361
399
  return handleToolError(error);
362
400
  }
363
- },
401
+ }
364
402
  );
365
403
 
366
404
  server.tool(
@@ -368,7 +406,10 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
368
406
  'Gets historical KPI data for a board. Supports filtering by period type, date range, and number of periods.',
369
407
  {
370
408
  boardId: boardIdParam,
371
- periodType: z.enum(['monthly', 'quarterly', 'yearly']).optional().describe('Period type (default: monthly)'),
409
+ periodType: z
410
+ .enum(['monthly', 'quarterly', 'yearly'])
411
+ .optional()
412
+ .describe('Period type (default: monthly)'),
372
413
  periods: z.number().int().min(1).max(48).optional().describe('Number of periods (1-48)'),
373
414
  startDate: z.string().optional().describe('Start date (ISO 8601)'),
374
415
  endDate: z.string().optional().describe('End date (ISO 8601)'),
@@ -387,7 +428,7 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
387
428
  } catch (error) {
388
429
  return handleToolError(error);
389
430
  }
390
- },
431
+ }
391
432
  );
392
433
 
393
434
  server.tool(
@@ -409,6 +450,6 @@ export const registerReportTools: RegisterToolsFn = (server, client) => {
409
450
  } catch (error) {
410
451
  return handleToolError(error);
411
452
  }
412
- },
453
+ }
413
454
  );
414
455
  };
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ paginationParams,
4
+ buildQueryParams,
5
+ formatResult,
6
+ handleToolError,
7
+ RegisterToolsFn,
8
+ } from './shared.js';
9
+
10
+ export const registerRogueKpiTools: RegisterToolsFn = (server, client) => {
11
+ server.tool(
12
+ 'browse_rogue_kpis',
13
+ 'Browse the Rogue KPI catalog — the master list of standardized KPIs that boards can adopt. Filter by domain (finance, sales, hr, product, customers, fundraising, operations), maturity level (draft, emerging, general), company stage, or free-text search.',
14
+ {
15
+ domain: z
16
+ .string()
17
+ .optional()
18
+ .describe(
19
+ 'Filter by KPI domain: finance, sales, hr, product, customers, fundraising, operations'
20
+ ),
21
+ maturity: z.string().optional().describe('Filter by maturity: draft, emerging, general'),
22
+ suggestedForStages: z
23
+ .string()
24
+ .optional()
25
+ .describe('Filter by company stage: preSeed, seed, seriesA, seriesB, seriesC, public'),
26
+ search: z.string().optional().describe('Free-text search on KPI label and description'),
27
+ ...paginationParams,
28
+ },
29
+ async ({ domain, maturity, suggestedForStages, search, limit, cursor, sort, order }) => {
30
+ try {
31
+ const params = buildQueryParams(
32
+ { domain, maturity, suggestedForStages, search, limit, cursor, sort, order },
33
+ ['domain', 'maturity', 'suggestedForStages', 'search']
34
+ );
35
+ const response = await client.getCollection('/api/rogue-kpis', params);
36
+ return formatResult({ data: response.data, meta: response.meta });
37
+ } catch (error) {
38
+ return handleToolError(error);
39
+ }
40
+ }
41
+ );
42
+
43
+ server.tool(
44
+ 'get_rogue_kpi',
45
+ 'Get a single Rogue KPI by its canonical rogueId (e.g., "finance.arr", "sales.pipeline_value"). Returns full details including description, field type, suggested stages, owning functions, and the required `definitionSource` attribution. When `definitionSource.tier === "published"`, the response carries an `attributionNotice` string that consumers must surface in any UI/output that renders the KPI definition. When `calculationPolicy` is present (added in ontology v1.1.0), an agent asked to COMPUTE the KPI from messy company data MUST surface `calculationPolicy.commonMiscomputations` to the user before running the calculation, apply `inclusionRules`/`exclusionRules` as boundary conditions on the source data, and run `validationChecks` against the result before returning it.',
46
+ {
47
+ rogueId: z
48
+ .string()
49
+ .describe('The canonical Rogue KPI ID (e.g., "finance.arr", "hr.headcount")'),
50
+ },
51
+ async ({ rogueId }) => {
52
+ try {
53
+ const response = await client.get(`/api/rogue-kpis/${rogueId}`);
54
+ return formatResult({ data: response.data });
55
+ } catch (error) {
56
+ return handleToolError(error);
57
+ }
58
+ }
59
+ );
60
+ };
@@ -7,12 +7,14 @@ export const registerUserTools: RegisterToolsFn = (server, client) => {
7
7
  {},
8
8
  async () => {
9
9
  try {
10
- const response = await client.get('/api/user/allboards');
11
- return formatResult({ data: response.data });
10
+ // Legacy non-enveloped route: returns { boardMemberships, boardInvites,
11
+ // personalGoogleConnection } with no `data` wrapper.
12
+ const data = await client.getRaw('/api/user/allboards');
13
+ return formatResult({ data });
12
14
  } catch (error) {
13
15
  return handleToolError(error);
14
16
  }
15
- },
17
+ }
16
18
  );
17
19
 
18
20
  server.tool(
@@ -21,11 +23,12 @@ export const registerUserTools: RegisterToolsFn = (server, client) => {
21
23
  {},
22
24
  async () => {
23
25
  try {
24
- const response = await client.get('/api/user/storage');
25
- return formatResult({ data: response.data });
26
+ // Legacy non-enveloped route: returns { unassignedStorageUsed, lastCalculated }.
27
+ const data = await client.getRaw('/api/user/storage');
28
+ return formatResult({ data });
26
29
  } catch (error) {
27
30
  return handleToolError(error);
28
31
  }
29
- },
32
+ }
30
33
  );
31
34
  };