debreu-mcp-admin 2.1.0 → 3.0.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/dist/index.js CHANGED
@@ -11,13 +11,21 @@ if (!API_KEY) {
11
11
  // ---------------------------------------------------------------------------
12
12
  // HTTP helpers
13
13
  // ---------------------------------------------------------------------------
14
- function authHeaders() {
15
- return {
14
+ /**
15
+ * Build auth headers. When actAsUser is provided, an X-Act-As-User header
16
+ * is included so the backend resolves context for that user instead of the
17
+ * API key owner. The backend verifies the caller is an admin (god_mode).
18
+ */
19
+ function authHeaders(actAsUser) {
20
+ const h = {
16
21
  Authorization: `Bearer ${API_KEY}`,
17
22
  "Content-Type": "application/json",
18
23
  };
24
+ if (actAsUser)
25
+ h["X-Act-As-User"] = actAsUser;
26
+ return h;
19
27
  }
20
- async function apiGet(path, params) {
28
+ async function apiGet(path, params, actAsUser) {
21
29
  const url = new URL(`${API_URL}${path}`);
22
30
  if (params) {
23
31
  for (const [k, v] of Object.entries(params)) {
@@ -25,14 +33,14 @@ async function apiGet(path, params) {
25
33
  url.searchParams.set(k, v);
26
34
  }
27
35
  }
28
- const resp = await fetch(url.toString(), { headers: authHeaders() });
36
+ const resp = await fetch(url.toString(), { headers: authHeaders(actAsUser) });
29
37
  if (!resp.ok) {
30
38
  const body = await resp.text();
31
39
  throw new Error(`HTTP ${resp.status}: ${body}`);
32
40
  }
33
41
  return resp.json();
34
42
  }
35
- async function apiPost(path, body, params) {
43
+ async function apiPost(path, body, params, actAsUser) {
36
44
  const url = new URL(`${API_URL}${path}`);
37
45
  if (params) {
38
46
  for (const [k, v] of Object.entries(params)) {
@@ -42,7 +50,7 @@ async function apiPost(path, body, params) {
42
50
  }
43
51
  const resp = await fetch(url.toString(), {
44
52
  method: "POST",
45
- headers: authHeaders(),
53
+ headers: authHeaders(actAsUser),
46
54
  body: JSON.stringify(body),
47
55
  });
48
56
  if (!resp.ok) {
@@ -51,7 +59,7 @@ async function apiPost(path, body, params) {
51
59
  }
52
60
  return resp.json();
53
61
  }
54
- async function apiPut(path, body, params) {
62
+ async function apiPut(path, body, params, actAsUser) {
55
63
  const url = new URL(`${API_URL}${path}`);
56
64
  if (params) {
57
65
  for (const [k, v] of Object.entries(params)) {
@@ -61,7 +69,7 @@ async function apiPut(path, body, params) {
61
69
  }
62
70
  const resp = await fetch(url.toString(), {
63
71
  method: "PUT",
64
- headers: authHeaders(),
72
+ headers: authHeaders(actAsUser),
65
73
  body: JSON.stringify(body),
66
74
  });
67
75
  if (!resp.ok) {
@@ -70,7 +78,7 @@ async function apiPut(path, body, params) {
70
78
  }
71
79
  return resp.json();
72
80
  }
73
- async function apiPatch(path, body, params) {
81
+ async function apiPatch(path, body, params, actAsUser) {
74
82
  const url = new URL(`${API_URL}${path}`);
75
83
  if (params) {
76
84
  for (const [k, v] of Object.entries(params)) {
@@ -80,7 +88,7 @@ async function apiPatch(path, body, params) {
80
88
  }
81
89
  const resp = await fetch(url.toString(), {
82
90
  method: "PATCH",
83
- headers: authHeaders(),
91
+ headers: authHeaders(actAsUser),
84
92
  body: JSON.stringify(body),
85
93
  });
86
94
  if (!resp.ok) {
@@ -89,7 +97,7 @@ async function apiPatch(path, body, params) {
89
97
  }
90
98
  return resp.json();
91
99
  }
92
- async function apiDelete(path, params, body) {
100
+ async function apiDelete(path, params, body, actAsUser) {
93
101
  const url = new URL(`${API_URL}${path}`);
94
102
  if (params) {
95
103
  for (const [k, v] of Object.entries(params)) {
@@ -97,7 +105,7 @@ async function apiDelete(path, params, body) {
97
105
  url.searchParams.set(k, v);
98
106
  }
99
107
  }
100
- const opts = { method: "DELETE", headers: authHeaders() };
108
+ const opts = { method: "DELETE", headers: authHeaders(actAsUser) };
101
109
  if (body !== undefined) {
102
110
  opts.body = JSON.stringify(body);
103
111
  }
@@ -141,9 +149,847 @@ function pickDefinedBody(args, keys) {
141
149
  return result;
142
150
  }
143
151
  // ---------------------------------------------------------------------------
144
- // Tool definitions
152
+ // pipeline_user_id property — added to every user-facing tool
153
+ // ---------------------------------------------------------------------------
154
+ const PIPELINE_USER_ID_PROP = {
155
+ pipeline_user_id: {
156
+ type: "string",
157
+ description: "Act as this user (UUID). Omit to act as the API key owner. " +
158
+ "Reads will return that user's data; writes will be owned by them.",
159
+ },
160
+ };
161
+ // ---------------------------------------------------------------------------
162
+ // User-facing read tool names — all POST to /api/v1/ext/tools/{slug}
145
163
  // ---------------------------------------------------------------------------
146
- const TOOLS = [
164
+ const USER_READ_TOOL_NAMES = new Set([
165
+ "lookup_company",
166
+ "query_companies",
167
+ "query_metric_history",
168
+ "get_metric_schema",
169
+ "get_status_definitions",
170
+ "lookup_person",
171
+ "get_people",
172
+ "get_team",
173
+ "get_tasks",
174
+ "report_issue",
175
+ "get_sentiments",
176
+ "query_emails",
177
+ "get_email",
178
+ "query_calendars",
179
+ "get_calendar",
180
+ "query_files",
181
+ "get_file",
182
+ "get_my_day",
183
+ "get_my_week",
184
+ "get_meeting_prep",
185
+ "query_projects",
186
+ "get_project",
187
+ ]);
188
+ // ---------------------------------------------------------------------------
189
+ // User-facing tool definitions (with pipeline_user_id added to each)
190
+ // ---------------------------------------------------------------------------
191
+ const USER_TOOLS = [
192
+ // ---- Companies (read) ----
193
+ {
194
+ name: "lookup_company",
195
+ description: "Look up a company by name, UUID, or website URL. Returns profile and all latest metric values. " +
196
+ "Name lookup tries exact match, then aliases, then fuzzy match (partial/informal names work). " +
197
+ "If no exact match is found, returns up to 10 substring-match candidates.",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {
201
+ ...PIPELINE_USER_ID_PROP,
202
+ identifier: {
203
+ type: "string",
204
+ description: "Company name, website domain, or UUID.",
205
+ },
206
+ identifier_type: {
207
+ type: "string",
208
+ enum: ["uuid", "name", "website"],
209
+ description: "Type of identifier being provided.",
210
+ },
211
+ },
212
+ required: ["identifier", "identifier_type"],
213
+ },
214
+ },
215
+ {
216
+ name: "query_companies",
217
+ description: "Filter companies by metric values. Requires exact metric names from get_metric_schema.\n" +
218
+ "Operators: string metrics use contains/equals/not_equals; number metrics use gt/lt/gte/lte/equals " +
219
+ "(values like '$1.5B' or '500M' are auto-parsed); any metric supports exists/not_exists.\n" +
220
+ "Multiple filters use AND logic. Returns up to 50 companies.",
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ ...PIPELINE_USER_ID_PROP,
225
+ filters: {
226
+ type: "array",
227
+ description: "Metric filters (AND logic).",
228
+ items: {
229
+ type: "object",
230
+ properties: {
231
+ metric_name: {
232
+ type: "string",
233
+ description: "Exact metric name from get_metric_schema.",
234
+ },
235
+ operator: {
236
+ type: "string",
237
+ enum: [
238
+ "contains", "equals", "not_equals",
239
+ "gt", "lt", "gte", "lte",
240
+ "exists", "not_exists",
241
+ ],
242
+ },
243
+ value: {
244
+ type: "string",
245
+ description: "Value to compare against. Not needed for exists/not_exists.",
246
+ },
247
+ },
248
+ required: ["metric_name", "operator"],
249
+ },
250
+ },
251
+ return_metrics: {
252
+ type: "array",
253
+ items: { type: "string" },
254
+ description: "Additional metric names to include in results beyond filtered ones.",
255
+ },
256
+ sort_by: {
257
+ type: "string",
258
+ description: "Sort by a metric name or 'name' for alphabetical. Sort metric is auto-included in results.",
259
+ },
260
+ sort_order: {
261
+ type: "string",
262
+ enum: ["asc", "desc"],
263
+ description: "Sort direction (default 'asc'). Ignored when sort_by is 'name'.",
264
+ },
265
+ limit: { type: "integer", description: "Max companies to return (default 20, max 50)." },
266
+ offset: { type: "integer", description: "Number of results to skip for pagination (default 0)." },
267
+ },
268
+ required: ["filters"],
269
+ },
270
+ },
271
+ {
272
+ name: "query_metric_history",
273
+ description: "Find companies where a numeric metric changed significantly over a date range. " +
274
+ "Computes change (absolute or percent) between first and last values in range, " +
275
+ "returns companies meeting the threshold with their full history.\n" +
276
+ "Only numeric metric types are supported. Call get_metric_schema first for valid names. " +
277
+ "Values like '$1.5B' or '500M' are auto-parsed. Use negative thresholds for decreases. " +
278
+ "Returns up to 25 companies sorted by largest absolute change.",
279
+ inputSchema: {
280
+ type: "object",
281
+ properties: {
282
+ ...PIPELINE_USER_ID_PROP,
283
+ metric_name: { type: "string", description: "Exact metric name from get_metric_schema (must be numeric type)." },
284
+ date_from: { type: "string", description: "Start of date range (YYYY-MM-DD)." },
285
+ date_to: { type: "string", description: "End of date range (YYYY-MM-DD)." },
286
+ change_operator: { type: "string", enum: ["gt", "lt", "gte", "lte"], description: "How to compare computed change to threshold." },
287
+ change_value: { type: "string", description: "Threshold (e.g. '$50M', '10', '-5'). For percent mode, just a number." },
288
+ change_type: { type: "string", enum: ["absolute", "percent"], description: "'absolute' (default): last - first. 'percent': (last-first)/first*100." },
289
+ filters: {
290
+ type: "array",
291
+ description: "Optional: narrow company universe (same format as query_companies filters).",
292
+ items: {
293
+ type: "object",
294
+ properties: {
295
+ metric_name: { type: "string" },
296
+ operator: { type: "string", enum: ["contains", "equals", "not_equals", "gt", "lt", "gte", "lte", "exists", "not_exists"] },
297
+ value: { type: "string" },
298
+ },
299
+ required: ["metric_name", "operator"],
300
+ },
301
+ },
302
+ limit: { type: "integer", description: "Max companies (default 25, max 25)." },
303
+ },
304
+ required: ["metric_name", "date_from", "date_to", "change_operator", "change_value"],
305
+ },
306
+ },
307
+ {
308
+ name: "get_metric_schema",
309
+ description: "Get available metric definitions for the user's team. Returns each metric's name, " +
310
+ "display_name, type, category, and description. Also returns distinct categories. " +
311
+ "Call this before query_companies or query_metric_history to learn valid metric names and types.",
312
+ inputSchema: {
313
+ type: "object",
314
+ properties: {
315
+ ...PIPELINE_USER_ID_PROP,
316
+ category: { type: "string", description: "Filter to a specific category. Omit on first call to discover categories." },
317
+ type: { type: "string", description: "Filter by metric value type (e.g. 'text', 'money_millions', 'rate', 'integer')." },
318
+ },
319
+ },
320
+ },
321
+ {
322
+ name: "get_status_definitions",
323
+ description: "Get the allowed status values for a status type. Call this before filtering by status " +
324
+ "or setting a status on a record. Common status_type values: 'task', 'project', " +
325
+ "'workflow_account_outreach', 'workflow_account_acquisition_targets', " +
326
+ "'workflow_account_buyer_universe', 'workflow_account_company_list', " +
327
+ "'workflow_account_people_list'.",
328
+ inputSchema: {
329
+ type: "object",
330
+ properties: {
331
+ ...PIPELINE_USER_ID_PROP,
332
+ status_type: { type: "string", description: "The status category to look up (e.g. 'task', 'project')." },
333
+ },
334
+ required: ["status_type"],
335
+ },
336
+ },
337
+ // ---- People (read) ----
338
+ {
339
+ name: "lookup_person",
340
+ description: "Look up a person by name, email, or UUID. Returns profile (name, email, role, phone, " +
341
+ "location, alma_mater, previous_company), company, relationship data (interaction dates, " +
342
+ "team relationship scores), and tags. Name uses substring matching; multiple matches " +
343
+ "return a candidates list.",
344
+ inputSchema: {
345
+ type: "object",
346
+ properties: {
347
+ ...PIPELINE_USER_ID_PROP,
348
+ identifier: { type: "string", description: "Person's name, email address, or UUID." },
349
+ identifier_type: { type: "string", enum: ["uuid", "name", "email"], description: "Type of identifier being provided." },
350
+ },
351
+ required: ["identifier", "identifier_type"],
352
+ },
353
+ },
354
+ {
355
+ name: "get_people",
356
+ description: "Get all contacts at a specific company with relationship data (interaction dates, team " +
357
+ "relationship scores) and tags. Requires company UUID from lookup_company or query_companies.",
358
+ inputSchema: {
359
+ type: "object",
360
+ properties: {
361
+ ...PIPELINE_USER_ID_PROP,
362
+ company_id: { type: "string", description: "Company UUID." },
363
+ },
364
+ required: ["company_id"],
365
+ },
366
+ },
367
+ // ---- Team ----
368
+ {
369
+ name: "get_team",
370
+ description: "Get the current user's team members. Returns each member's UUID, name, email, " +
371
+ "and whether they are the current user. Use this to discover valid assignee_id " +
372
+ "values for create_task and update_task.",
373
+ inputSchema: {
374
+ type: "object",
375
+ properties: { ...PIPELINE_USER_ID_PROP },
376
+ },
377
+ },
378
+ // ---- Tasks (read) ----
379
+ {
380
+ name: "get_tasks",
381
+ description: "Query tasks (action items, follow-ups, to-dos). Each task includes description, " +
382
+ "status, due_date, pending_from, assignee, company, person, project, " +
383
+ "and source (resolved to the originating email/file/calendar with name, uuid, url; " +
384
+ "event-sourced tasks also include the reference snippet). Call get_status_definitions with " +
385
+ "status_type='task' first to discover valid status values. Returns up to 50 tasks.",
386
+ inputSchema: {
387
+ type: "object",
388
+ properties: {
389
+ ...PIPELINE_USER_ID_PROP,
390
+ company_id: { type: "string", description: "Filter to tasks for this company UUID." },
391
+ due_date_start: { type: "string", description: "Earliest due date (YYYY-MM-DD)." },
392
+ due_date_end: { type: "string", description: "Latest due date (YYYY-MM-DD)." },
393
+ status: { type: "string", description: "Filter by task status. Call get_status_definitions(status_type='task') for valid values." },
394
+ search: { type: "string", description: "Substring search in task description." },
395
+ limit: { type: "integer", description: "Max tasks to return (default 30, max 50)." },
396
+ offset: { type: "integer", description: "Number of results to skip for pagination (default 0)." },
397
+ },
398
+ },
399
+ },
400
+ // ---- Sentiments (read) ----
401
+ {
402
+ name: "get_sentiments",
403
+ description: "Query sentiment signals extracted from emails, files, and calendar events. " +
404
+ "Each has actor (from company), target (about company/theme), action_value, score, " +
405
+ "reference text, and confidence. Requires company UUIDs from lookup_company.",
406
+ inputSchema: {
407
+ type: "object",
408
+ properties: {
409
+ ...PIPELINE_USER_ID_PROP,
410
+ actor_company_id: { type: "string", description: "Sentiments FROM this company UUID." },
411
+ target_company_id: { type: "string", description: "Sentiments ABOUT this company UUID." },
412
+ theme: { type: "string", description: "Filter by theme (substring match)." },
413
+ limit: { type: "integer", description: "Max results (default 30, max 50)." },
414
+ offset: { type: "integer", description: "Number of results to skip for pagination (default 0)." },
415
+ },
416
+ },
417
+ },
418
+ // ---- Emails (read) ----
419
+ {
420
+ name: "query_emails",
421
+ description: "Search emails. Use 'search' for keyword matching across subject and body. " +
422
+ "Use 'person' with 'me' to match the current user. " +
423
+ "Use get_email with a result UUID to get full content.",
424
+ inputSchema: {
425
+ type: "object",
426
+ properties: {
427
+ ...PIPELINE_USER_ID_PROP,
428
+ search: { type: "string", description: "Keyword search across subject and body." },
429
+ date_start: { type: "string", description: "Earliest email date (YYYY-MM-DD)." },
430
+ date_end: { type: "string", description: "Latest email date (YYYY-MM-DD)." },
431
+ company_id: { type: "string", description: "Filter to emails linked to this company UUID." },
432
+ person: { type: "string", description: "Person name, email, or UUID. Use 'me' for current user." },
433
+ person_role: { type: "string", enum: ["sender", "receiver"], description: "Narrow person filter to sender or receiver only." },
434
+ tag: { type: "string", description: "Filter to emails with this tag (e.g. 'outreach', 'recruiting')." },
435
+ limit: { type: "integer", description: "Max results (default 20, max 50)." },
436
+ offset: { type: "integer", description: "Number of results to skip for pagination (default 0)." },
437
+ },
438
+ },
439
+ },
440
+ {
441
+ name: "get_email",
442
+ description: "Get full email content by UUID from a query_emails result.",
443
+ inputSchema: {
444
+ type: "object",
445
+ properties: {
446
+ ...PIPELINE_USER_ID_PROP,
447
+ uuid: { type: "string", description: "Email UUID." },
448
+ },
449
+ required: ["uuid"],
450
+ },
451
+ },
452
+ // ---- Calendars (read) ----
453
+ {
454
+ name: "query_calendars",
455
+ description: "Search calendar events. Use 'person' with 'me' for the current user. " +
456
+ "Use get_calendar with a result UUID to get full details.",
457
+ inputSchema: {
458
+ type: "object",
459
+ properties: {
460
+ ...PIPELINE_USER_ID_PROP,
461
+ date_start: { type: "string", description: "Earliest event date (YYYY-MM-DD)." },
462
+ date_end: { type: "string", description: "Latest event date (YYYY-MM-DD)." },
463
+ company_id: { type: "string", description: "Filter to events linked to this company UUID." },
464
+ person: { type: "string", description: "Attendee/organizer name, email, or UUID. Use 'me' for current user." },
465
+ search: { type: "string", description: "Substring search in title/description/attendees/location." },
466
+ tag: { type: "string", description: "Filter to events with this tag (e.g. 'outreach', 'recruiting')." },
467
+ limit: { type: "integer", description: "Max results (default 20, max 50)." },
468
+ offset: { type: "integer", description: "Number of results to skip for pagination (default 0)." },
469
+ },
470
+ },
471
+ },
472
+ {
473
+ name: "get_calendar",
474
+ description: "Get full calendar event details by UUID from a query_calendars result.",
475
+ inputSchema: {
476
+ type: "object",
477
+ properties: {
478
+ ...PIPELINE_USER_ID_PROP,
479
+ uuid: { type: "string", description: "Calendar event UUID." },
480
+ },
481
+ required: ["uuid"],
482
+ },
483
+ },
484
+ // ---- Files (read) ----
485
+ {
486
+ name: "query_files",
487
+ description: "Search uploaded files and email attachments. Use get_file with a result UUID to get full content.",
488
+ inputSchema: {
489
+ type: "object",
490
+ properties: {
491
+ ...PIPELINE_USER_ID_PROP,
492
+ date_start: { type: "string", description: "Earliest file date (YYYY-MM-DD)." },
493
+ date_end: { type: "string", description: "Latest file date (YYYY-MM-DD)." },
494
+ company_id: { type: "string", description: "Filter to files linked to this company UUID." },
495
+ search: { type: "string", description: "Substring search in title/description/content." },
496
+ limit: { type: "integer", description: "Max results (default 20, max 50)." },
497
+ offset: { type: "integer", description: "Number of results to skip for pagination (default 0)." },
498
+ },
499
+ },
500
+ },
501
+ {
502
+ name: "get_file",
503
+ description: "Get full file content by UUID from a query_files result.",
504
+ inputSchema: {
505
+ type: "object",
506
+ properties: {
507
+ ...PIPELINE_USER_ID_PROP,
508
+ uuid: { type: "string", description: "File UUID." },
509
+ },
510
+ required: ["uuid"],
511
+ },
512
+ },
513
+ // ---- Briefings (read) ----
514
+ {
515
+ name: "get_my_day",
516
+ description: "Get the user's 'My Day' briefing -- today's agenda, key meetings, action items, and priorities.",
517
+ inputSchema: {
518
+ type: "object",
519
+ properties: { ...PIPELINE_USER_ID_PROP },
520
+ },
521
+ },
522
+ {
523
+ name: "get_my_week",
524
+ description: "Get the user's 'My Week' briefing -- the week ahead including meetings, deal updates, and deadlines.",
525
+ inputSchema: {
526
+ type: "object",
527
+ properties: { ...PIPELINE_USER_ID_PROP },
528
+ },
529
+ },
530
+ {
531
+ name: "get_meeting_prep",
532
+ description: "Get AI-generated meeting prep for a company: background, recent activity, key metrics, " +
533
+ "and talking points. May take a moment if generated fresh. Requires company UUID from lookup_company.",
534
+ inputSchema: {
535
+ type: "object",
536
+ properties: {
537
+ ...PIPELINE_USER_ID_PROP,
538
+ company_id: { type: "string", description: "Company UUID." },
539
+ },
540
+ required: ["company_id"],
541
+ },
542
+ },
543
+ // ---- Projects (read) ----
544
+ {
545
+ name: "query_projects",
546
+ description: "Search projects (deals). Returns project name, type, status, client company, " +
547
+ "and tracker summary. Use get_project for full details. " +
548
+ "Call get_status_definitions with status_type='project' for valid status values.",
549
+ inputSchema: {
550
+ type: "object",
551
+ properties: {
552
+ ...PIPELINE_USER_ID_PROP,
553
+ client_id: { type: "string", description: "Filter to projects for this client company UUID." },
554
+ deal_type: { type: "string", enum: ["general", "buy_side", "sell_side", "financing"] },
555
+ status: { type: "string", description: "Filter by project status. Call get_status_definitions(status_type='project') for valid values." },
556
+ date_start: { type: "string", description: "Earliest creation date (YYYY-MM-DD)." },
557
+ date_end: { type: "string", description: "Latest creation date (YYYY-MM-DD)." },
558
+ limit: { type: "integer", description: "Max results (default 20, max 50)." },
559
+ offset: { type: "integer", description: "Number of results to skip for pagination (default 0)." },
560
+ },
561
+ },
562
+ },
563
+ {
564
+ name: "get_project",
565
+ description: "Get full project details by UUID. Includes tasks and tracker accounts. " +
566
+ "For company trackers: companies with account_status, next_steps, sentiment, reasoning. " +
567
+ "For people_list trackers: people with account_status. " +
568
+ "For Acquisition Targets / Buyer Universe: also includes generated_companies and generated_status.",
569
+ inputSchema: {
570
+ type: "object",
571
+ properties: {
572
+ ...PIPELINE_USER_ID_PROP,
573
+ uuid: { type: "string", description: "Project UUID." },
574
+ },
575
+ required: ["uuid"],
576
+ },
577
+ },
578
+ // ==== Write tools ====
579
+ // ---- Companies (write) ----
580
+ {
581
+ name: "create_company",
582
+ description: "Create a new company. Name and website are required. " +
583
+ "Company will be auto-hydrated with enrichment data asynchronously after creation.",
584
+ inputSchema: {
585
+ type: "object",
586
+ properties: {
587
+ ...PIPELINE_USER_ID_PROP,
588
+ name: { type: "string", description: "Company name." },
589
+ website: { type: "string", description: "Company website (e.g. acme.com)." },
590
+ description: { type: "string", description: "Company description." },
591
+ city: { type: "string", description: "City." },
592
+ state: { type: "string", description: "US state 2-letter code (e.g. NY, CA)." },
593
+ country: { type: "string", description: "ISO 3166-1 alpha-3 country code (e.g. USA, GBR)." },
594
+ ticker: { type: "string", description: "Stock ticker symbol." },
595
+ tags: { type: "string", description: "Tags as comma-separated string." },
596
+ },
597
+ required: ["name", "website"],
598
+ },
599
+ },
600
+ {
601
+ name: "update_company",
602
+ description: "Update a company by UUID. Only provided fields are changed.",
603
+ inputSchema: {
604
+ type: "object",
605
+ properties: {
606
+ ...PIPELINE_USER_ID_PROP,
607
+ uuid: { type: "string", description: "Company UUID." },
608
+ name: { type: "string", description: "Company name." },
609
+ website: { type: "string", description: "Company website." },
610
+ description: { type: "string", description: "Company description." },
611
+ city: { type: "string", description: "City." },
612
+ state: { type: "string", description: "US state 2-letter code." },
613
+ country: { type: "string", description: "ISO 3166-1 alpha-3 country code." },
614
+ ticker: { type: "string", description: "Stock ticker symbol." },
615
+ tags: { type: "string", description: "Tags as comma-separated string." },
616
+ },
617
+ required: ["uuid"],
618
+ },
619
+ },
620
+ // ---- People (write) ----
621
+ {
622
+ name: "create_person",
623
+ description: "Create a new person (contact). Name and company_id are required.",
624
+ inputSchema: {
625
+ type: "object",
626
+ properties: {
627
+ ...PIPELINE_USER_ID_PROP,
628
+ name: { type: "string", description: "Person name." },
629
+ company_id: { type: "string", description: "UUID of the company this person belongs to." },
630
+ email: { type: "string", description: "Email address." },
631
+ phone: { type: "string", description: "Phone number." },
632
+ role: { type: "string", description: "Job title or role." },
633
+ alma_mater: { type: "string", description: "University or school attended." },
634
+ previous_company: { type: "string", description: "Previous company name." },
635
+ location: { type: "string", description: "City, state, or region." },
636
+ },
637
+ required: ["name", "company_id"],
638
+ },
639
+ },
640
+ {
641
+ name: "update_person",
642
+ description: "Update a person by UUID. Only provided fields are changed.",
643
+ inputSchema: {
644
+ type: "object",
645
+ properties: {
646
+ ...PIPELINE_USER_ID_PROP,
647
+ uuid: { type: "string", description: "Person UUID." },
648
+ name: { type: "string", description: "Person name." },
649
+ company_id: { type: "string", description: "UUID of the company." },
650
+ email: { type: "string", description: "Email address." },
651
+ phone: { type: "string", description: "Phone number." },
652
+ role: { type: "string", description: "Job title or role." },
653
+ alma_mater: { type: "string", description: "University or school attended." },
654
+ previous_company: { type: "string", description: "Previous company name." },
655
+ location: { type: "string", description: "City, state, or region." },
656
+ },
657
+ required: ["uuid"],
658
+ },
659
+ },
660
+ // ---- Tasks (write) ----
661
+ {
662
+ name: "create_task",
663
+ description: "Create a new task (action item / follow-up). Description is required. " +
664
+ "Assignee defaults to the current user; call get_team to discover team member UUIDs.",
665
+ inputSchema: {
666
+ type: "object",
667
+ properties: {
668
+ ...PIPELINE_USER_ID_PROP,
669
+ description: { type: "string", description: "Task description." },
670
+ company_id: { type: "string", description: "UUID of the associated company." },
671
+ person_id: { type: "string", description: "Person UUID or raw name string." },
672
+ assignee_id: { type: "string", description: "Assignee pipeline user UUID. Defaults to the current user. Call get_team for valid UUIDs." },
673
+ due_date: { type: "string", description: "Due date (YYYY-MM-DD)." },
674
+ status: { type: "string", description: "Task status (default 'open'). Call get_status_definitions(status_type='task') for valid values." },
675
+ pending_from: { type: "string", description: "Who the task is pending on: 'us', 'counterparty', or 'client'." },
676
+ source_type: { type: "string", description: "Source type (email, file, calendar, user, or event). Defaults to 'user'." },
677
+ project_id: { type: "string", description: "Optional project UUID." },
678
+ },
679
+ required: ["description"],
680
+ },
681
+ },
682
+ {
683
+ name: "update_task",
684
+ description: "Update a task by UUID. Only provided fields are changed.",
685
+ inputSchema: {
686
+ type: "object",
687
+ properties: {
688
+ ...PIPELINE_USER_ID_PROP,
689
+ uuid: { type: "string", description: "Task UUID." },
690
+ description: { type: "string", description: "Task description." },
691
+ company_id: { type: "string", description: "UUID of the associated company." },
692
+ person_id: { type: "string", description: "Person UUID or raw name string." },
693
+ assignee_id: { type: "string", description: "Assignee pipeline user UUID. Call get_team for valid UUIDs." },
694
+ due_date: { type: "string", description: "Due date (YYYY-MM-DD)." },
695
+ status: { type: "string", description: "Task status. Call get_status_definitions(status_type='task') for valid values." },
696
+ pending_from: { type: "string", description: "Who the task is pending on: 'us', 'counterparty', or 'client'. Set to null to clear." },
697
+ project_id: { type: "string", description: "Project UUID." },
698
+ },
699
+ required: ["uuid"],
700
+ },
701
+ },
702
+ // ---- Projects (write) ----
703
+ {
704
+ name: "create_project",
705
+ description: "Create a new project (deal) with a tracker. A tracker is automatically created " +
706
+ "(defaults to company_list if tracker_type not specified). " +
707
+ "Tracker types: acquisition_targets (AI-generated target companies for buy-side), " +
708
+ "buyer_universe (AI-generated potential buyers for sell-side), " +
709
+ "outreach (track email outreach activity), company_list (simple company tracking). " +
710
+ "acquisition_targets and buyer_universe require client_id and trigger AI generation. " +
711
+ "outreach optionally uses outreach_start_date and/or outreach_end_date to auto-sync companies from email/calendar activity; " +
712
+ "no dates = manual only, start only = open-ended forward, both = date range filter. " +
713
+ "people_list optionally uses sync_tags (comma-separated) to auto-sync tagged people; tag changes are reflected automatically.",
714
+ inputSchema: {
715
+ type: "object",
716
+ properties: {
717
+ ...PIPELINE_USER_ID_PROP,
718
+ name: { type: "string", description: "Project name." },
719
+ description: { type: "string", description: "Project description." },
720
+ keywords: { type: "string", description: "Comma-separated keywords for matching emails/files/calendars." },
721
+ client_id: { type: "string", description: "Client company UUID." },
722
+ owner_id: { type: "string", description: "Owner pipeline user UUID. Defaults to the authenticated user." },
723
+ deal_type: { type: "string", enum: ["general", "buy_side", "sell_side", "financing"], description: "Deal type (default 'general')." },
724
+ status: { type: "string", description: "Project status (default 'new'). Call get_status_definitions(status_type='project') for valid values." },
725
+ priority: { type: "string", enum: ["low", "med", "high"], description: "Priority level (default 'med')." },
726
+ start_date: { type: "string", description: "Start date (YYYY-MM-DD)." },
727
+ end_date: { type: "string", description: "End date (YYYY-MM-DD)." },
728
+ tracker_type: {
729
+ type: "string",
730
+ enum: ["acquisition_targets", "buyer_universe", "outreach", "company_list", "people_list"],
731
+ description: "Type of tracker to create (default 'company_list').",
732
+ },
733
+ tracker_description: { type: "string", description: "Tracker description. Used as context for AI generation and account status analysis." },
734
+ buyer_type: { type: "string", enum: ["strategic", "financial_sponsor"], description: "For acquisition_targets/buyer_universe: type of buyer." },
735
+ target_landscape: {
736
+ type: "array",
737
+ items: {
738
+ type: "object",
739
+ properties: {
740
+ sector: { type: "string", description: "GICS sector (e.g. 'Information Technology')." },
741
+ industry_group: { type: "string", description: "GICS industry group (e.g. 'Software & Services')." },
742
+ industry: { type: "string", description: "GICS industry (e.g. 'Software')." },
743
+ sub_industry: { type: "string", description: "GICS sub-industry (e.g. 'Application Software')." },
744
+ },
745
+ required: ["sector"],
746
+ },
747
+ description: "For acquisition_targets/buyer_universe: target landscape as GICS taxonomy combinations. Each entry must have at least a sector.",
748
+ },
749
+ outreach_start_date: { type: "string", description: "For outreach tracker: optional start date filter (YYYY-MM-DD)." },
750
+ outreach_end_date: { type: "string", description: "For outreach tracker: optional end date filter (YYYY-MM-DD)." },
751
+ sync_tags: { type: "string", description: "For people_list tracker: comma-separated tags for auto-syncing tagged people." },
752
+ initial_company_ids: { type: "array", items: { type: "string" }, description: "Company UUIDs to seed into the tracker's primary view." },
753
+ initial_person_ids: { type: "array", items: { type: "string" }, description: "For people_list tracker: person UUIDs to seed into the tracker." },
754
+ },
755
+ required: ["name"],
756
+ },
757
+ },
758
+ {
759
+ name: "update_project",
760
+ description: "Update a project by UUID. Can update project fields (name, description, keywords, status, etc.), " +
761
+ "tracker fields (tracker_name, tracker_description, dates, taxonomy), " +
762
+ "and manage tracker accounts. " +
763
+ "For people_list trackers use person_id in account_operations; for all other trackers use company_id. " +
764
+ "Call get_status_definitions with the appropriate workflow_account_* status_type for valid account_status values.",
765
+ inputSchema: {
766
+ type: "object",
767
+ properties: {
768
+ ...PIPELINE_USER_ID_PROP,
769
+ uuid: { type: "string", description: "Project UUID." },
770
+ name: { type: "string", description: "Project name." },
771
+ description: { type: "string", description: "Project description." },
772
+ keywords: { type: "string", description: "Comma-separated keywords." },
773
+ client_id: { type: "string", description: "Client company UUID." },
774
+ owner_id: { type: "string", description: "Owner pipeline user UUID." },
775
+ deal_type: { type: "string", enum: ["general", "buy_side", "sell_side", "financing"] },
776
+ status: { type: "string", description: "Project status. Call get_status_definitions(status_type='project') for valid values." },
777
+ priority: { type: "string", enum: ["low", "med", "high"] },
778
+ start_date: { type: "string", description: "Start date (YYYY-MM-DD)." },
779
+ end_date: { type: "string", description: "End date (YYYY-MM-DD)." },
780
+ tracker_name: { type: "string", description: "Tracker name." },
781
+ tracker_description: { type: "string", description: "Tracker description." },
782
+ outreach_start_date: { type: "string", description: "For outreach tracker: optional start date filter (YYYY-MM-DD)." },
783
+ outreach_end_date: { type: "string", description: "For outreach tracker: optional end date filter (YYYY-MM-DD)." },
784
+ sync_tags: { type: "string", description: "For people_list tracker: comma-separated tags for auto-syncing tagged people." },
785
+ target_landscape: {
786
+ type: "array",
787
+ items: {
788
+ type: "object",
789
+ properties: {
790
+ sector: { type: "string", description: "GICS sector (e.g. 'Information Technology')." },
791
+ industry_group: { type: "string", description: "GICS industry group (e.g. 'Software & Services')." },
792
+ industry: { type: "string", description: "GICS industry (e.g. 'Software')." },
793
+ sub_industry: { type: "string", description: "GICS sub-industry (e.g. 'Application Software')." },
794
+ },
795
+ required: ["sector"],
796
+ },
797
+ description: "For acquisition_targets/buyer_universe: target landscape as GICS taxonomy combinations.",
798
+ },
799
+ buyer_type: { type: "string", enum: ["strategic", "financial_sponsor"], description: "For acquisition_targets/buyer_universe: type of buyer." },
800
+ account_operations: {
801
+ type: "array",
802
+ description: "Operations on tracker accounts. For people_list trackers use person_id; for all other trackers use company_id.",
803
+ items: {
804
+ type: "object",
805
+ properties: {
806
+ action: { type: "string", enum: ["add", "update", "remove"], description: "'add' an entry to the tracker, 'update' its fields, or 'remove' it." },
807
+ company_id: { type: "string", description: "Company UUID (for non-people_list trackers)." },
808
+ person_id: { type: "string", description: "Person UUID (for people_list trackers)." },
809
+ account_status: { type: "string", description: "Account status (for add/update). Call get_status_definitions for valid values." },
810
+ next_steps: { type: "string", description: "Next steps text (for add/update, company trackers only)." },
811
+ sentiment: { type: "string", description: "Relationship sentiment summary (for add/update, company trackers only)." },
812
+ },
813
+ required: ["action"],
814
+ },
815
+ },
816
+ },
817
+ required: ["uuid"],
818
+ },
819
+ },
820
+ // ---- Comments (read + write) ----
821
+ {
822
+ name: "query_comments",
823
+ description: "Get comments on a record. Requires data_model (e.g. 'company', 'person', 'project') and record_id.",
824
+ inputSchema: {
825
+ type: "object",
826
+ properties: {
827
+ ...PIPELINE_USER_ID_PROP,
828
+ data_model: { type: "string", description: "Record type (e.g. 'company', 'person', 'project', 'task')." },
829
+ record_id: { type: "string", description: "UUID of the record to get comments for." },
830
+ skip: { type: "integer", description: "Offset (default 0)." },
831
+ limit: { type: "integer", description: "Max results (default 50)." },
832
+ },
833
+ required: ["data_model", "record_id"],
834
+ },
835
+ },
836
+ {
837
+ name: "create_comment",
838
+ description: "Add a comment to a record (company, person, project, task, etc.).",
839
+ inputSchema: {
840
+ type: "object",
841
+ properties: {
842
+ ...PIPELINE_USER_ID_PROP,
843
+ data_model: { type: "string", description: "Record type (e.g. 'company', 'person', 'project', 'task')." },
844
+ record_id: { type: "string", description: "UUID of the record to comment on." },
845
+ content: { type: "string", description: "Comment text." },
846
+ },
847
+ required: ["data_model", "record_id", "content"],
848
+ },
849
+ },
850
+ {
851
+ name: "update_comment",
852
+ description: "Update a comment's content by UUID. Only the author can update.",
853
+ inputSchema: {
854
+ type: "object",
855
+ properties: {
856
+ ...PIPELINE_USER_ID_PROP,
857
+ uuid: { type: "string", description: "Comment UUID." },
858
+ content: { type: "string", description: "Updated comment text." },
859
+ },
860
+ required: ["uuid", "content"],
861
+ },
862
+ },
863
+ // ---- Metric Schemas (user-facing write — team-scoped) ----
864
+ {
865
+ name: "create_metric_schema",
866
+ description: "Create a new metric schema definition. Name and type are required. " +
867
+ "Name will be normalized to lowercase with underscores.",
868
+ inputSchema: {
869
+ type: "object",
870
+ properties: {
871
+ ...PIPELINE_USER_ID_PROP,
872
+ name: { type: "string", description: "Metric name (lowercase, underscores, e.g. 'annual_revenue')." },
873
+ type: { type: "string", description: "Value type (e.g. 'text', 'money_millions', 'rate', 'integer')." },
874
+ description: { type: "string", description: "What this metric measures." },
875
+ category: { type: "string", description: "Grouping category." },
876
+ display_name: { type: "string", description: "Human-readable name. Auto-generated from name if omitted." },
877
+ should_parse: { type: "boolean", description: "Whether AI should auto-extract this metric (default true)." },
878
+ examples: { type: "string", description: "Example values to guide AI extraction." },
879
+ },
880
+ required: ["name", "type"],
881
+ },
882
+ },
883
+ {
884
+ name: "update_metric_schema",
885
+ description: "Update a metric schema by UUID. Only provided fields are changed. " +
886
+ "Name cannot be changed after creation. System metrics cannot be modified.",
887
+ inputSchema: {
888
+ type: "object",
889
+ properties: {
890
+ ...PIPELINE_USER_ID_PROP,
891
+ uuid: { type: "string", description: "Metric schema UUID." },
892
+ type: { type: "string", description: "Value type." },
893
+ description: { type: "string", description: "What this metric measures." },
894
+ category: { type: "string", description: "Grouping category." },
895
+ display_name: { type: "string", description: "Human-readable name." },
896
+ should_parse: { type: "boolean", description: "Whether AI should auto-extract this metric." },
897
+ examples: { type: "string", description: "Example values to guide AI extraction." },
898
+ },
899
+ required: ["uuid"],
900
+ },
901
+ },
902
+ // ---- Status Definitions (user-facing write) ----
903
+ {
904
+ name: "create_status_definition",
905
+ description: "Create a new status definition for a status type (e.g. task, workflow_account).",
906
+ inputSchema: {
907
+ type: "object",
908
+ properties: {
909
+ ...PIPELINE_USER_ID_PROP,
910
+ status_type: { type: "string", description: "Status category (e.g. 'task', 'workflow_account', 'project')." },
911
+ value: { type: "string", description: "Machine-readable value (e.g. 'in_progress')." },
912
+ display_name: { type: "string", description: "Human-readable label (e.g. 'In Progress')." },
913
+ description: { type: "string", description: "Description of this status." },
914
+ is_terminal: { type: "boolean", description: "Whether this status represents an end state (default false)." },
915
+ phase: { type: "string", description: "Phase grouping (e.g. 'active', 'closed')." },
916
+ sort_order: { type: "integer", description: "Display order (lower = first)." },
917
+ },
918
+ required: ["status_type", "value", "display_name"],
919
+ },
920
+ },
921
+ {
922
+ name: "update_status_definition",
923
+ description: "Update a status definition by UUID. Only provided fields are changed.",
924
+ inputSchema: {
925
+ type: "object",
926
+ properties: {
927
+ ...PIPELINE_USER_ID_PROP,
928
+ uuid: { type: "string", description: "Status definition UUID." },
929
+ value: { type: "string", description: "Machine-readable value." },
930
+ display_name: { type: "string", description: "Human-readable label." },
931
+ description: { type: "string", description: "Description." },
932
+ is_terminal: { type: "boolean", description: "Whether this is an end state." },
933
+ phase: { type: "string", description: "Phase grouping." },
934
+ sort_order: { type: "integer", description: "Display order." },
935
+ },
936
+ required: ["uuid"],
937
+ },
938
+ },
939
+ // ---- Unified delete ----
940
+ {
941
+ name: "delete_record",
942
+ description: "Delete a record by type and UUID. Supported types: company, person, task, " +
943
+ "metric_schema, project, comment, status_definition. Performs soft delete.",
944
+ inputSchema: {
945
+ type: "object",
946
+ properties: {
947
+ ...PIPELINE_USER_ID_PROP,
948
+ record_type: {
949
+ type: "string",
950
+ enum: ["company", "person", "task", "metric_schema", "project", "comment", "status_definition"],
951
+ description: "Type of record to delete.",
952
+ },
953
+ uuid: { type: "string", description: "UUID of the record to delete." },
954
+ },
955
+ required: ["record_type", "uuid"],
956
+ },
957
+ },
958
+ // ---- Entity Tags (write) ----
959
+ {
960
+ name: "set_entity_tags",
961
+ description: "Set tags on a record (email, calendar, file, or person). Replaces all existing tags " +
962
+ "with the provided list. Pass an empty array to clear all tags. Idempotent.",
963
+ inputSchema: {
964
+ type: "object",
965
+ properties: {
966
+ ...PIPELINE_USER_ID_PROP,
967
+ entity_type: { type: "string", enum: ["email", "calendar", "file", "person"], description: "Type of record to tag." },
968
+ entity_id: { type: "string", description: "UUID of the record." },
969
+ tags: { type: "array", items: { type: "string" }, description: "Tags to set (replaces existing tags)." },
970
+ },
971
+ required: ["entity_type", "entity_id", "tags"],
972
+ },
973
+ },
974
+ // ---- Issue Reports ----
975
+ {
976
+ name: "report_issue",
977
+ description: "Report an issue or bug. Creates an issue report tied to the current user.",
978
+ inputSchema: {
979
+ type: "object",
980
+ properties: {
981
+ ...PIPELINE_USER_ID_PROP,
982
+ description: { type: "string", description: "Details about the issue." },
983
+ page_url: { type: "string", description: "URL or page name where the issue was observed." },
984
+ },
985
+ required: ["description"],
986
+ },
987
+ },
988
+ ];
989
+ // ---------------------------------------------------------------------------
990
+ // Admin-only tool definitions
991
+ // ---------------------------------------------------------------------------
992
+ const ADMIN_TOOLS = [
147
993
  // -- Fake Emails --
148
994
  {
149
995
  name: "seed_fake_email",
@@ -256,9 +1102,7 @@ const TOOLS = [
256
1102
  description: "Get a profile by UUID.",
257
1103
  inputSchema: {
258
1104
  type: "object",
259
- properties: {
260
- uuid: { type: "string", description: "Profile UUID" },
261
- },
1105
+ properties: { uuid: { type: "string", description: "Profile UUID" } },
262
1106
  required: ["uuid"],
263
1107
  },
264
1108
  },
@@ -294,15 +1138,13 @@ const TOOLS = [
294
1138
  description: "Delete a profile. Fails if active users reference it.",
295
1139
  inputSchema: {
296
1140
  type: "object",
297
- properties: {
298
- uuid: { type: "string", description: "Profile UUID" },
299
- },
1141
+ properties: { uuid: { type: "string", description: "Profile UUID" } },
300
1142
  required: ["uuid"],
301
1143
  },
302
1144
  },
303
- // -- Metric Schema --
1145
+ // -- Metric Schema (admin — profile-scoped) --
304
1146
  {
305
- name: "list_metric_schemas",
1147
+ name: "admin_list_metric_schemas",
306
1148
  description: "List metric schemas, optionally filtered by profile_id.",
307
1149
  inputSchema: {
308
1150
  type: "object",
@@ -314,8 +1156,8 @@ const TOOLS = [
314
1156
  },
315
1157
  },
316
1158
  {
317
- name: "create_metric_schema",
318
- description: "Create a metric schema.",
1159
+ name: "admin_create_metric_schema",
1160
+ description: "Create a metric schema (admin — with profile_id, team_id, is_system).",
319
1161
  inputSchema: {
320
1162
  type: "object",
321
1163
  properties: {
@@ -334,8 +1176,8 @@ const TOOLS = [
334
1176
  },
335
1177
  },
336
1178
  {
337
- name: "update_metric_schema",
338
- description: "Update a metric schema by UUID. Cannot modify system metrics.",
1179
+ name: "admin_update_metric_schema",
1180
+ description: "Update a metric schema by UUID (admin). Cannot modify system metrics.",
339
1181
  inputSchema: {
340
1182
  type: "object",
341
1183
  properties: {
@@ -354,19 +1196,17 @@ const TOOLS = [
354
1196
  },
355
1197
  },
356
1198
  {
357
- name: "delete_metric_schema",
1199
+ name: "admin_delete_metric_schema",
358
1200
  description: "Delete a metric schema by UUID. Cannot delete system metrics.",
359
1201
  inputSchema: {
360
1202
  type: "object",
361
- properties: {
362
- uuid: { type: "string", description: "Metric schema UUID" },
363
- },
1203
+ properties: { uuid: { type: "string", description: "Metric schema UUID" } },
364
1204
  required: ["uuid"],
365
1205
  },
366
1206
  },
367
- // -- Status Definitions --
1207
+ // -- Status Definitions (admin — profile-scoped) --
368
1208
  {
369
- name: "list_status_definitions",
1209
+ name: "admin_list_status_definitions",
370
1210
  description: "List status definitions for a profile, optionally filtered by type.",
371
1211
  inputSchema: {
372
1212
  type: "object",
@@ -378,7 +1218,7 @@ const TOOLS = [
378
1218
  },
379
1219
  },
380
1220
  {
381
- name: "create_status_definition",
1221
+ name: "admin_create_status_definition",
382
1222
  description: "Create a status definition for a profile.",
383
1223
  inputSchema: {
384
1224
  type: "object",
@@ -396,8 +1236,8 @@ const TOOLS = [
396
1236
  },
397
1237
  },
398
1238
  {
399
- name: "update_status_definition",
400
- description: "Update a status definition.",
1239
+ name: "admin_update_status_definition",
1240
+ description: "Update a status definition (admin).",
401
1241
  inputSchema: {
402
1242
  type: "object",
403
1243
  properties: {
@@ -414,7 +1254,7 @@ const TOOLS = [
414
1254
  },
415
1255
  },
416
1256
  {
417
- name: "delete_status_definition",
1257
+ name: "admin_delete_status_definition",
418
1258
  description: "Delete a status definition, remapping entities to a replacement value.",
419
1259
  inputSchema: {
420
1260
  type: "object",
@@ -426,43 +1266,6 @@ const TOOLS = [
426
1266
  required: ["uuid", "profile_id", "replacement_value"],
427
1267
  },
428
1268
  },
429
- // -- Companies (for any user) --
430
- {
431
- name: "create_company",
432
- description: "Create a company, optionally scoped to a specific user.",
433
- inputSchema: {
434
- type: "object",
435
- properties: {
436
- name: { type: "string", description: "Company name" },
437
- website: { type: "string", description: "Company website (e.g. acme.com)" },
438
- description: { type: "string", description: "Company description" },
439
- city: { type: "string", description: "City" },
440
- state: { type: "string", description: "US state 2-letter code" },
441
- country: { type: "string", description: "ISO 3166-1 alpha-3 country code" },
442
- ticker: { type: "string", description: "Stock ticker symbol" },
443
- tags: { type: "string", description: "Tags (comma-separated)" },
444
- pipeline_user_id: { type: "string", description: "Target user UUID (defaults to caller)" },
445
- },
446
- required: ["name", "website"],
447
- },
448
- },
449
- // -- People (for any user) --
450
- {
451
- name: "create_person",
452
- description: "Create a person, optionally scoped to a specific user.",
453
- inputSchema: {
454
- type: "object",
455
- properties: {
456
- name: { type: "string", description: "Person name" },
457
- company_id: { type: "string", description: "UUID of the company this person belongs to" },
458
- email: { type: "string", description: "Email address" },
459
- phone: { type: "string", description: "Phone number" },
460
- role: { type: "string", description: "Job title or role" },
461
- pipeline_user_id: { type: "string", description: "Target user UUID (defaults to caller)" },
462
- },
463
- required: ["name", "company_id"],
464
- },
465
- },
466
1269
  // -- AI Prompts --
467
1270
  {
468
1271
  name: "get_prompt_options",
@@ -532,9 +1335,7 @@ const TOOLS = [
532
1335
  description: "Get a single AI call by UUID with full details including prompt and response.",
533
1336
  inputSchema: {
534
1337
  type: "object",
535
- properties: {
536
- uuid: { type: "string", description: "AI call UUID" },
537
- },
1338
+ properties: { uuid: { type: "string", description: "AI call UUID" } },
538
1339
  required: ["uuid"],
539
1340
  },
540
1341
  },
@@ -574,9 +1375,7 @@ const TOOLS = [
574
1375
  description: "Create a new team.",
575
1376
  inputSchema: {
576
1377
  type: "object",
577
- properties: {
578
- name: { type: "string", description: "Team name" },
579
- },
1378
+ properties: { name: { type: "string", description: "Team name" } },
580
1379
  required: ["name"],
581
1380
  },
582
1381
  },
@@ -597,9 +1396,7 @@ const TOOLS = [
597
1396
  description: "Delete a team. Fails if team has active users.",
598
1397
  inputSchema: {
599
1398
  type: "object",
600
- properties: {
601
- uuid: { type: "string", description: "Team UUID" },
602
- },
1399
+ properties: { uuid: { type: "string", description: "Team UUID" } },
603
1400
  required: ["uuid"],
604
1401
  },
605
1402
  },
@@ -619,7 +1416,7 @@ const TOOLS = [
619
1416
  required: ["pipeline_user_id", "name", "content"],
620
1417
  },
621
1418
  },
622
- // -- Issue Reports --
1419
+ // -- Issue Reports (admin) --
623
1420
  {
624
1421
  name: "list_issue_reports",
625
1422
  description: "List all issue reports with user info.",
@@ -658,216 +1455,329 @@ const TOOLS = [
658
1455
  },
659
1456
  },
660
1457
  ];
1458
+ // All tools combined
1459
+ const TOOLS = [...USER_TOOLS, ...ADMIN_TOOLS];
1460
+ // ---------------------------------------------------------------------------
1461
+ // User-facing tool handler body key constants
1462
+ // ---------------------------------------------------------------------------
1463
+ const COMPANY_BODY_KEYS = ["name", "website", "description", "city", "state", "country", "ticker", "tags"];
1464
+ const PERSON_BODY_KEYS = ["name", "company_id", "email", "phone", "role", "alma_mater", "previous_company", "location"];
1465
+ const TASK_BODY_KEYS = ["description", "company_id", "person_id", "assignee_id", "due_date", "status", "source_type", "source_id", "project_id", "workflow_id", "pending_from"];
1466
+ const METRIC_SCHEMA_BODY_KEYS = ["name", "type", "description", "category", "display_name", "should_parse", "examples"];
1467
+ const STATUS_DEF_BODY_KEYS = ["status_type", "value", "display_name", "description", "is_terminal", "phase", "sort_order"];
1468
+ const COMMENT_BODY_KEYS = ["data_model", "record_id", "content"];
1469
+ // Names of all user-facing tools (for routing in handleTool)
1470
+ const USER_TOOL_NAMES = new Set(USER_TOOLS.map((t) => t.name));
661
1471
  // ---------------------------------------------------------------------------
662
1472
  // Tool handlers
663
1473
  // ---------------------------------------------------------------------------
664
1474
  const EXT_ADMIN = "/api/v1/ext/admin";
1475
+ /**
1476
+ * Extract pipeline_user_id from args (used as X-Act-As-User header for
1477
+ * user-facing tools) and return cleaned args without it.
1478
+ */
1479
+ function extractActAsUser(args) {
1480
+ const actAsUser = args.pipeline_user_id;
1481
+ const cleanArgs = { ...args };
1482
+ delete cleanArgs.pipeline_user_id;
1483
+ return { actAsUser, cleanArgs };
1484
+ }
1485
+ async function handleUserTool(name, args) {
1486
+ const { actAsUser, cleanArgs } = extractActAsUser(args);
1487
+ // Read tools all route through /api/v1/ext/tools/{slug}
1488
+ if (USER_READ_TOOL_NAMES.has(name)) {
1489
+ const slug = name.replace(/_/g, "-");
1490
+ return textResult(await apiPost(`/api/v1/ext/tools/${slug}`, cleanArgs, undefined, actAsUser));
1491
+ }
1492
+ switch (name) {
1493
+ // ---- Companies (write) ----
1494
+ case "create_company": {
1495
+ const body = pickDefinedBody(cleanArgs, COMPANY_BODY_KEYS);
1496
+ return textResult(await apiPost("/api/v1/ext/companies/", body, undefined, actAsUser));
1497
+ }
1498
+ case "update_company": {
1499
+ const { uuid, ...rest } = cleanArgs;
1500
+ const body = pickDefinedBody(rest, COMPANY_BODY_KEYS);
1501
+ return textResult(await apiPut(`/api/v1/ext/companies/${uuid}`, body, undefined, actAsUser));
1502
+ }
1503
+ // ---- People (write) ----
1504
+ case "create_person": {
1505
+ const body = pickDefinedBody(cleanArgs, PERSON_BODY_KEYS);
1506
+ return textResult(await apiPost("/api/v1/ext/people/", body, undefined, actAsUser));
1507
+ }
1508
+ case "update_person": {
1509
+ const { uuid, ...rest } = cleanArgs;
1510
+ const body = pickDefinedBody(rest, PERSON_BODY_KEYS);
1511
+ return textResult(await apiPut(`/api/v1/ext/people/${uuid}`, body, undefined, actAsUser));
1512
+ }
1513
+ // ---- Tasks (write) ----
1514
+ case "create_task": {
1515
+ const body = pickDefinedBody(cleanArgs, TASK_BODY_KEYS);
1516
+ if (!body.source_type)
1517
+ body.source_type = "user";
1518
+ if (!body.source_id)
1519
+ body.source_id = "mcp";
1520
+ return textResult(await apiPost("/api/v1/ext/tasks/", body, undefined, actAsUser));
1521
+ }
1522
+ case "update_task": {
1523
+ const { uuid, ...rest } = cleanArgs;
1524
+ const body = pickDefinedBody(rest, TASK_BODY_KEYS);
1525
+ return textResult(await apiPut(`/api/v1/ext/tasks/${uuid}`, body, undefined, actAsUser));
1526
+ }
1527
+ // ---- Projects (write) ----
1528
+ case "create_project": {
1529
+ const body = { ...cleanArgs };
1530
+ return textResult(await apiPost("/api/v1/ext/tools/create-project", body, undefined, actAsUser));
1531
+ }
1532
+ case "update_project": {
1533
+ const body = { ...cleanArgs };
1534
+ return textResult(await apiPost("/api/v1/ext/tools/update-project", body, undefined, actAsUser));
1535
+ }
1536
+ // ---- Comments ----
1537
+ case "query_comments": {
1538
+ const params = pickDefined(cleanArgs, ["data_model", "record_id", "skip", "limit"]);
1539
+ return textResult(await apiGet("/api/v1/ext/comments/", params, actAsUser));
1540
+ }
1541
+ case "create_comment": {
1542
+ const body = pickDefinedBody(cleanArgs, COMMENT_BODY_KEYS);
1543
+ return textResult(await apiPost("/api/v1/ext/comments/", body, undefined, actAsUser));
1544
+ }
1545
+ case "update_comment": {
1546
+ const { uuid, ...rest } = cleanArgs;
1547
+ return textResult(await apiPut(`/api/v1/ext/comments/${uuid}`, pickDefinedBody(rest, ["content"]), undefined, actAsUser));
1548
+ }
1549
+ // ---- Metric Schemas (write) ----
1550
+ case "create_metric_schema": {
1551
+ const body = pickDefinedBody(cleanArgs, METRIC_SCHEMA_BODY_KEYS);
1552
+ return textResult(await apiPost("/api/v1/ext/metric-schemas/", body, undefined, actAsUser));
1553
+ }
1554
+ case "update_metric_schema": {
1555
+ const { uuid, ...rest } = cleanArgs;
1556
+ const body = pickDefinedBody(rest, METRIC_SCHEMA_BODY_KEYS);
1557
+ return textResult(await apiPut(`/api/v1/ext/metric-schemas/${uuid}`, body, undefined, actAsUser));
1558
+ }
1559
+ // ---- Status Definitions (write) ----
1560
+ case "create_status_definition": {
1561
+ const body = pickDefinedBody(cleanArgs, STATUS_DEF_BODY_KEYS);
1562
+ return textResult(await apiPost("/api/v1/ext/status-definitions/", body, undefined, actAsUser));
1563
+ }
1564
+ case "update_status_definition": {
1565
+ const { uuid, ...rest } = cleanArgs;
1566
+ const body = pickDefinedBody(rest, STATUS_DEF_BODY_KEYS);
1567
+ return textResult(await apiPut(`/api/v1/ext/status-definitions/${uuid}`, body, undefined, actAsUser));
1568
+ }
1569
+ // ---- Unified delete ----
1570
+ case "delete_record":
1571
+ return textResult(await apiDelete(`/api/v1/ext/records/${cleanArgs.record_type}/${cleanArgs.uuid}`, undefined, undefined, actAsUser));
1572
+ // ---- Entity Tags (write) ----
1573
+ case "set_entity_tags":
1574
+ return textResult(await apiPut("/api/v1/ext/entity-tags/", { entity_type: cleanArgs.entity_type, entity_id: cleanArgs.entity_id, tags: cleanArgs.tags }, undefined, actAsUser));
1575
+ default:
1576
+ return errorResult(`Unknown user tool: ${name}`);
1577
+ }
1578
+ }
1579
+ async function handleAdminTool(name, args) {
1580
+ switch (name) {
1581
+ // -- Fake Emails --
1582
+ case "seed_fake_email": {
1583
+ const body = pickDefinedBody(args, [
1584
+ "sender", "receiver", "subject", "content", "cc", "time", "pipeline_user_id",
1585
+ ]);
1586
+ return textResult(await apiPost(`${EXT_ADMIN}/fake-emails`, body));
1587
+ }
1588
+ // -- Fake Calendars --
1589
+ case "seed_fake_calendar": {
1590
+ const body = pickDefinedBody(args, [
1591
+ "title", "description", "start_time", "end_time", "organizer",
1592
+ "attendees", "location", "pipeline_user_id",
1593
+ ]);
1594
+ return textResult(await apiPost(`${EXT_ADMIN}/fake-calendars`, body));
1595
+ }
1596
+ // -- Pipeline Users --
1597
+ case "list_users": {
1598
+ const params = pickDefined(args, ["skip", "limit"]);
1599
+ return textResult(await apiGet(`${EXT_ADMIN}/users`, params));
1600
+ }
1601
+ case "get_user":
1602
+ return textResult(await apiGet(`${EXT_ADMIN}/users/${args.uuid}`));
1603
+ case "create_user": {
1604
+ const body = pickDefinedBody(args, [
1605
+ "email", "password", "name", "company_id", "profile_id", "timezone",
1606
+ ]);
1607
+ return textResult(await apiPost(`${EXT_ADMIN}/users`, body));
1608
+ }
1609
+ case "update_user": {
1610
+ const { uuid, ...rest } = args;
1611
+ const body = pickDefinedBody(rest, [
1612
+ "name", "profile_id", "team_id", "batch_parsing_enabled",
1613
+ "batch_provider", "batch_model", "is_active", "is_deleted",
1614
+ ]);
1615
+ return textResult(await apiPatch(`${EXT_ADMIN}/users/${uuid}`, body));
1616
+ }
1617
+ // -- Profiles --
1618
+ case "list_profiles": {
1619
+ const params = pickDefined(args, ["skip", "limit"]);
1620
+ return textResult(await apiGet(`${EXT_ADMIN}/profiles`, params));
1621
+ }
1622
+ case "get_profile":
1623
+ return textResult(await apiGet(`${EXT_ADMIN}/profiles/${args.uuid}`));
1624
+ case "create_profile": {
1625
+ const body = pickDefinedBody(args, ["name", "description", "config"]);
1626
+ return textResult(await apiPost(`${EXT_ADMIN}/profiles`, body));
1627
+ }
1628
+ case "update_profile": {
1629
+ const { uuid, ...rest } = args;
1630
+ const body = pickDefinedBody(rest, ["name", "description", "config"]);
1631
+ return textResult(await apiPut(`${EXT_ADMIN}/profiles/${uuid}`, body));
1632
+ }
1633
+ case "delete_profile":
1634
+ return textResult(await apiDelete(`${EXT_ADMIN}/profiles/${args.uuid}`));
1635
+ // -- Metric Schema (admin) --
1636
+ case "admin_list_metric_schemas": {
1637
+ const params = pickDefined(args, ["profile_id", "skip", "limit"]);
1638
+ return textResult(await apiGet(`${EXT_ADMIN}/metric-schema`, params));
1639
+ }
1640
+ case "admin_create_metric_schema": {
1641
+ const body = pickDefinedBody(args, [
1642
+ "name", "description", "type", "category", "profile_id", "team_id",
1643
+ "examples", "display_name", "should_parse", "is_system",
1644
+ ]);
1645
+ return textResult(await apiPost(`${EXT_ADMIN}/metric-schema`, body));
1646
+ }
1647
+ case "admin_update_metric_schema": {
1648
+ const { uuid, ...rest } = args;
1649
+ const body = pickDefinedBody(rest, [
1650
+ "name", "description", "type", "category", "profile_id", "team_id",
1651
+ "examples", "display_name", "should_parse",
1652
+ ]);
1653
+ return textResult(await apiPut(`${EXT_ADMIN}/metric-schema/${uuid}`, body));
1654
+ }
1655
+ case "admin_delete_metric_schema":
1656
+ return textResult(await apiDelete(`${EXT_ADMIN}/metric-schema/${args.uuid}`));
1657
+ // -- Status Definitions (admin) --
1658
+ case "admin_list_status_definitions": {
1659
+ const params = pickDefined(args, ["profile_id", "status_type"]);
1660
+ return textResult(await apiGet(`${EXT_ADMIN}/status-definitions`, params));
1661
+ }
1662
+ case "admin_create_status_definition": {
1663
+ const { profile_id, ...rest } = args;
1664
+ const body = pickDefinedBody(rest, [
1665
+ "status_type", "value", "display_name", "description",
1666
+ "is_terminal", "phase", "is_default",
1667
+ ]);
1668
+ const params = pickDefined({ profile_id }, ["profile_id"]);
1669
+ return textResult(await apiPost(`${EXT_ADMIN}/status-definitions`, body, params));
1670
+ }
1671
+ case "admin_update_status_definition": {
1672
+ const { uuid, profile_id, ...rest } = args;
1673
+ const body = pickDefinedBody(rest, [
1674
+ "display_name", "description", "is_terminal", "phase", "is_default", "value",
1675
+ ]);
1676
+ const params = pickDefined({ profile_id }, ["profile_id"]);
1677
+ return textResult(await apiPatch(`${EXT_ADMIN}/status-definitions/${uuid}`, body, params));
1678
+ }
1679
+ case "admin_delete_status_definition": {
1680
+ const { uuid, profile_id, replacement_value } = args;
1681
+ const params = pickDefined({ profile_id }, ["profile_id"]);
1682
+ return textResult(await apiDelete(`${EXT_ADMIN}/status-definitions/${uuid}`, params, { replacement_value }));
1683
+ }
1684
+ // -- AI Prompts --
1685
+ case "get_prompt_options":
1686
+ return textResult(await apiGet(`${EXT_ADMIN}/prompts/options`));
1687
+ case "list_prompts": {
1688
+ const params = pickDefined(args, ["profile_id", "include_history", "skip", "limit"]);
1689
+ return textResult(await apiGet(`${EXT_ADMIN}/prompts`, params));
1690
+ }
1691
+ case "get_prompt": {
1692
+ const { flow, ...rest } = args;
1693
+ const params = pickDefined(rest, ["profile_id", "include_history"]);
1694
+ return textResult(await apiGet(`${EXT_ADMIN}/prompts/${flow}`, params));
1695
+ }
1696
+ case "update_prompt": {
1697
+ const { flow, profile_id, ...rest } = args;
1698
+ const body = pickDefinedBody(rest, ["config", "commit_message"]);
1699
+ const params = pickDefined({ profile_id }, ["profile_id"]);
1700
+ return textResult(await apiPut(`${EXT_ADMIN}/prompts/${flow}`, body, params));
1701
+ }
1702
+ // -- AI Calls --
1703
+ case "list_ai_calls": {
1704
+ const params = pickDefined(args, [
1705
+ "skip", "limit", "provider", "model", "success",
1706
+ "ai_flow", "pipeline_user_name", "call_status",
1707
+ ]);
1708
+ return textResult(await apiGet(`${EXT_ADMIN}/ai-calls`, params));
1709
+ }
1710
+ case "get_ai_call":
1711
+ return textResult(await apiGet(`${EXT_ADMIN}/ai-calls/${args.uuid}`));
1712
+ // -- Feature Flags --
1713
+ case "list_feature_flags":
1714
+ return textResult(await apiGet(`${EXT_ADMIN}/feature-flags`));
1715
+ case "update_feature_flag": {
1716
+ const body = pickDefinedBody(args, ["pipeline_user_id", "name", "value"]);
1717
+ return textResult(await apiPatch(`${EXT_ADMIN}/feature-flags`, body));
1718
+ }
1719
+ // -- Teams --
1720
+ case "list_teams": {
1721
+ const params = pickDefined(args, ["skip", "limit"]);
1722
+ return textResult(await apiGet(`${EXT_ADMIN}/teams`, params));
1723
+ }
1724
+ case "create_team": {
1725
+ const body = pickDefinedBody(args, ["name"]);
1726
+ return textResult(await apiPost(`${EXT_ADMIN}/teams`, body));
1727
+ }
1728
+ case "update_team": {
1729
+ const { uuid, ...rest } = args;
1730
+ const body = pickDefinedBody(rest, ["name"]);
1731
+ return textResult(await apiPut(`${EXT_ADMIN}/teams/${uuid}`, body));
1732
+ }
1733
+ case "delete_team":
1734
+ return textResult(await apiDelete(`${EXT_ADMIN}/teams/${args.uuid}`));
1735
+ // -- Notifications --
1736
+ case "send_notification": {
1737
+ const body = pickDefinedBody(args, [
1738
+ "pipeline_user_id", "name", "content", "linked_entity_type", "linked_entity_id",
1739
+ ]);
1740
+ return textResult(await apiPost(`${EXT_ADMIN}/notifications`, body));
1741
+ }
1742
+ // -- Issue Reports --
1743
+ case "list_issue_reports": {
1744
+ const params = pickDefined(args, ["skip", "limit"]);
1745
+ return textResult(await apiGet(`${EXT_ADMIN}/issue-reports`, params));
1746
+ }
1747
+ case "submit_issue_report": {
1748
+ const body = pickDefinedBody(args, ["description", "page_url", "pipeline_user_id"]);
1749
+ return textResult(await apiPost(`${EXT_ADMIN}/issue-reports`, body));
1750
+ }
1751
+ case "update_issue_report": {
1752
+ const { uuid, ...rest } = args;
1753
+ const body = pickDefinedBody(rest, ["status", "description"]);
1754
+ return textResult(await apiPatch(`${EXT_ADMIN}/issue-reports/${uuid}`, body));
1755
+ }
1756
+ default:
1757
+ return errorResult(`Unknown tool: ${name}`);
1758
+ }
1759
+ }
665
1760
  async function handleTool(name, args) {
666
1761
  try {
667
- switch (name) {
668
- // -- Fake Emails --
669
- case "seed_fake_email": {
670
- const body = pickDefinedBody(args, [
671
- "sender", "receiver", "subject", "content", "cc", "time", "pipeline_user_id",
672
- ]);
673
- return textResult(await apiPost(`${EXT_ADMIN}/fake-emails`, body));
674
- }
675
- // -- Fake Calendars --
676
- case "seed_fake_calendar": {
677
- const body = pickDefinedBody(args, [
678
- "title", "description", "start_time", "end_time", "organizer",
679
- "attendees", "location", "pipeline_user_id",
680
- ]);
681
- return textResult(await apiPost(`${EXT_ADMIN}/fake-calendars`, body));
682
- }
683
- // -- Pipeline Users --
684
- case "list_users": {
685
- const params = pickDefined(args, ["skip", "limit"]);
686
- return textResult(await apiGet(`${EXT_ADMIN}/users`, params));
687
- }
688
- case "get_user":
689
- return textResult(await apiGet(`${EXT_ADMIN}/users/${args.uuid}`));
690
- case "create_user": {
691
- const body = pickDefinedBody(args, [
692
- "email", "password", "name", "company_id", "profile_id", "timezone",
693
- ]);
694
- return textResult(await apiPost(`${EXT_ADMIN}/users`, body));
695
- }
696
- case "update_user": {
697
- const { uuid, ...rest } = args;
698
- const body = pickDefinedBody(rest, [
699
- "name", "profile_id", "team_id", "batch_parsing_enabled",
700
- "batch_provider", "batch_model", "is_active", "is_deleted",
701
- ]);
702
- return textResult(await apiPatch(`${EXT_ADMIN}/users/${uuid}`, body));
703
- }
704
- // -- Profiles --
705
- case "list_profiles": {
706
- const params = pickDefined(args, ["skip", "limit"]);
707
- return textResult(await apiGet(`${EXT_ADMIN}/profiles`, params));
708
- }
709
- case "get_profile":
710
- return textResult(await apiGet(`${EXT_ADMIN}/profiles/${args.uuid}`));
711
- case "create_profile": {
712
- const body = pickDefinedBody(args, ["name", "description", "config"]);
713
- return textResult(await apiPost(`${EXT_ADMIN}/profiles`, body));
714
- }
715
- case "update_profile": {
716
- const { uuid, ...rest } = args;
717
- const body = pickDefinedBody(rest, [
718
- "name", "description", "config",
719
- ]);
720
- return textResult(await apiPut(`${EXT_ADMIN}/profiles/${uuid}`, body));
721
- }
722
- case "delete_profile":
723
- return textResult(await apiDelete(`${EXT_ADMIN}/profiles/${args.uuid}`));
724
- // -- Metric Schema --
725
- case "list_metric_schemas": {
726
- const params = pickDefined(args, ["profile_id", "skip", "limit"]);
727
- return textResult(await apiGet(`${EXT_ADMIN}/metric-schema`, params));
728
- }
729
- case "create_metric_schema": {
730
- const body = pickDefinedBody(args, [
731
- "name", "description", "type", "category", "profile_id", "team_id",
732
- "examples", "display_name", "should_parse", "is_system",
733
- ]);
734
- return textResult(await apiPost(`${EXT_ADMIN}/metric-schema`, body));
735
- }
736
- case "update_metric_schema": {
737
- const { uuid, ...rest } = args;
738
- const body = pickDefinedBody(rest, [
739
- "name", "description", "type", "category", "profile_id", "team_id",
740
- "examples", "display_name", "should_parse",
741
- ]);
742
- return textResult(await apiPut(`${EXT_ADMIN}/metric-schema/${uuid}`, body));
743
- }
744
- case "delete_metric_schema":
745
- return textResult(await apiDelete(`${EXT_ADMIN}/metric-schema/${args.uuid}`));
746
- // -- Status Definitions --
747
- case "list_status_definitions": {
748
- const params = pickDefined(args, ["profile_id", "status_type"]);
749
- return textResult(await apiGet(`${EXT_ADMIN}/status-definitions`, params));
750
- }
751
- case "create_status_definition": {
752
- const { profile_id, ...rest } = args;
753
- const body = pickDefinedBody(rest, [
754
- "status_type", "value", "display_name", "description",
755
- "is_terminal", "phase", "is_default",
756
- ]);
757
- const params = pickDefined({ profile_id }, ["profile_id"]);
758
- return textResult(await apiPost(`${EXT_ADMIN}/status-definitions`, body, params));
759
- }
760
- case "update_status_definition": {
761
- const { uuid, profile_id, ...rest } = args;
762
- const body = pickDefinedBody(rest, [
763
- "display_name", "description", "is_terminal", "phase", "is_default", "value",
764
- ]);
765
- const params = pickDefined({ profile_id }, ["profile_id"]);
766
- return textResult(await apiPatch(`${EXT_ADMIN}/status-definitions/${uuid}`, body, params));
767
- }
768
- case "delete_status_definition": {
769
- const { uuid, profile_id, replacement_value } = args;
770
- const params = pickDefined({ profile_id }, ["profile_id"]);
771
- return textResult(await apiDelete(`${EXT_ADMIN}/status-definitions/${uuid}`, params, { replacement_value }));
772
- }
773
- // -- Companies (for any user) --
774
- case "create_company": {
775
- const body = pickDefinedBody(args, [
776
- "name", "website", "description", "city", "state", "country", "ticker", "tags",
777
- ]);
778
- const params = pickDefined(args, ["pipeline_user_id"]);
779
- return textResult(await apiPost(`${EXT_ADMIN}/companies`, body, params));
780
- }
781
- // -- People (for any user) --
782
- case "create_person": {
783
- const body = pickDefinedBody(args, ["name", "company_id", "email", "phone", "role"]);
784
- const params = pickDefined(args, ["pipeline_user_id"]);
785
- return textResult(await apiPost(`${EXT_ADMIN}/people`, body, params));
786
- }
787
- // -- AI Prompts --
788
- case "get_prompt_options":
789
- return textResult(await apiGet(`${EXT_ADMIN}/prompts/options`));
790
- case "list_prompts": {
791
- const params = pickDefined(args, ["profile_id", "include_history", "skip", "limit"]);
792
- return textResult(await apiGet(`${EXT_ADMIN}/prompts`, params));
793
- }
794
- case "get_prompt": {
795
- const { flow, ...rest } = args;
796
- const params = pickDefined(rest, ["profile_id", "include_history"]);
797
- return textResult(await apiGet(`${EXT_ADMIN}/prompts/${flow}`, params));
798
- }
799
- case "update_prompt": {
800
- const { flow, profile_id, ...rest } = args;
801
- const body = pickDefinedBody(rest, ["config", "commit_message"]);
802
- const params = pickDefined({ profile_id }, ["profile_id"]);
803
- return textResult(await apiPut(`${EXT_ADMIN}/prompts/${flow}`, body, params));
804
- }
805
- // -- AI Calls --
806
- case "list_ai_calls": {
807
- const params = pickDefined(args, [
808
- "skip", "limit", "provider", "model", "success",
809
- "ai_flow", "pipeline_user_name", "call_status",
810
- ]);
811
- return textResult(await apiGet(`${EXT_ADMIN}/ai-calls`, params));
812
- }
813
- case "get_ai_call":
814
- return textResult(await apiGet(`${EXT_ADMIN}/ai-calls/${args.uuid}`));
815
- // -- Feature Flags --
816
- case "list_feature_flags":
817
- return textResult(await apiGet(`${EXT_ADMIN}/feature-flags`));
818
- case "update_feature_flag": {
819
- const body = pickDefinedBody(args, ["pipeline_user_id", "name", "value"]);
820
- return textResult(await apiPatch(`${EXT_ADMIN}/feature-flags`, body));
821
- }
822
- // -- Teams --
823
- case "list_teams": {
824
- const params = pickDefined(args, ["skip", "limit"]);
825
- return textResult(await apiGet(`${EXT_ADMIN}/teams`, params));
826
- }
827
- case "create_team": {
828
- const body = pickDefinedBody(args, ["name"]);
829
- return textResult(await apiPost(`${EXT_ADMIN}/teams`, body));
830
- }
831
- case "update_team": {
832
- const { uuid, ...rest } = args;
833
- const body = pickDefinedBody(rest, ["name"]);
834
- return textResult(await apiPut(`${EXT_ADMIN}/teams/${uuid}`, body));
835
- }
836
- case "delete_team":
837
- return textResult(await apiDelete(`${EXT_ADMIN}/teams/${args.uuid}`));
838
- // -- Notifications --
839
- case "send_notification": {
840
- const body = pickDefinedBody(args, [
841
- "pipeline_user_id", "name", "content", "linked_entity_type", "linked_entity_id",
842
- ]);
843
- return textResult(await apiPost(`${EXT_ADMIN}/notifications`, body));
844
- }
845
- // -- Issue Reports --
846
- case "list_issue_reports": {
847
- const params = pickDefined(args, ["skip", "limit"]);
848
- return textResult(await apiGet(`${EXT_ADMIN}/issue-reports`, params));
849
- }
850
- case "submit_issue_report": {
851
- const body = pickDefinedBody(args, ["description", "page_url", "pipeline_user_id"]);
852
- return textResult(await apiPost(`${EXT_ADMIN}/issue-reports`, body));
853
- }
854
- case "update_issue_report": {
855
- const { uuid, ...rest } = args;
856
- const body = pickDefinedBody(rest, ["status", "description"]);
857
- return textResult(await apiPatch(`${EXT_ADMIN}/issue-reports/${uuid}`, body));
858
- }
859
- default:
860
- return errorResult(`Unknown tool: ${name}`);
1762
+ if (USER_TOOL_NAMES.has(name)) {
1763
+ return await handleUserTool(name, args);
861
1764
  }
1765
+ return await handleAdminTool(name, args);
862
1766
  }
863
1767
  catch (e) {
864
- return errorResult(e);
1768
+ // Sanitize error messages to avoid leaking internal backend details
1769
+ const msg = e instanceof Error ? e.message : String(e);
1770
+ const statusMatch = msg.match(/^HTTP (\d{3})/);
1771
+ const safeMsg = statusMatch
1772
+ ? `HTTP ${statusMatch[1]}: Request failed`
1773
+ : "Request failed";
1774
+ return errorResult(safeMsg);
865
1775
  }
866
1776
  }
867
1777
  // ---------------------------------------------------------------------------
868
1778
  // Server setup
869
1779
  // ---------------------------------------------------------------------------
870
- const server = new Server({ name: "debreu-mcp-admin", version: "2.1.0" }, { capabilities: { tools: {} } });
1780
+ const server = new Server({ name: "debreu-mcp-admin", version: "3.0.0" }, { capabilities: { tools: {} } });
871
1781
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
872
1782
  tools: TOOLS,
873
1783
  }));