@trylighthouse/mcp-server 0.2.16 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/build/server.d.ts +1 -4
- package/build/server.js +337 -76
- package/package.json +2 -2
package/build/server.js
CHANGED
|
@@ -57,6 +57,9 @@ function textWithImage(res, image, declaredType) {
|
|
|
57
57
|
const mimeType = IMAGE_CONTENT_TYPES.has(image.contentType) ? image.contentType : declaredType;
|
|
58
58
|
return { ...base, content: [...base.content, { type: "image", data: image.bytes.toString("base64"), mimeType }] };
|
|
59
59
|
}
|
|
60
|
+
const FILE_BLOCKED_TOOL_RULE = "A file whose blocked_reason is threat_detected cannot be read or downloaded.";
|
|
61
|
+
const FILE_BLOCKED_KEYS = "A file a threat was found in also carries blocked_at and blocked_reason.";
|
|
62
|
+
const FILE_BLOCKED_ANSWER = "Reading or downloading such a file answers 409 file_blocked (\"This file could not be read: a threat was detected in it.\").";
|
|
60
63
|
const FILE_CONTENT_RULES = "kind is pdf, document, spreadsheet, presentation, html, csv, markdown, text or image (null for a kind that cannot be read); pages_total is a PDF's pages or a presentation's slides. " +
|
|
61
64
|
"A spreadsheet reads as one \"Sheet: <name>\" block per sheet, a row per line with its cells tab separated (a date shows as its stored serial number); a presentation as one \"Slide <n>\" block per slide. " +
|
|
62
65
|
`A long file comes back in windows of max_chars (default ${FILE_CONTENT_WINDOW_CHARS}): meta.has_more true means the file continues, so pass meta.next_offset_chars as offset_chars to read on, and never treat a window with has_more as the end of the file. ` +
|
|
@@ -81,11 +84,15 @@ const ADDRESS_INPUT = z.union([
|
|
|
81
84
|
}),
|
|
82
85
|
]);
|
|
83
86
|
const RECIPIENTS = z.union([ADDRESS_INPUT, z.array(ADDRESS_INPUT)]);
|
|
84
|
-
|
|
87
|
+
const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
88
|
+
const CREATES = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
89
|
+
const ADDS_IF_MISSING = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
90
|
+
const DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false };
|
|
91
|
+
export function createServer(apiKey) {
|
|
85
92
|
const api = new LighthouseAPI(apiKey);
|
|
86
93
|
const server = new McpServer({
|
|
87
94
|
name: "lighthouse",
|
|
88
|
-
version: "0.2.
|
|
95
|
+
version: "0.2.20",
|
|
89
96
|
});
|
|
90
97
|
const originalRegister = server.registerTool.bind(server);
|
|
91
98
|
server.registerTool = (name, schemaOrConfig, handler) => {
|
|
@@ -259,6 +266,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
259
266
|
"(an outer AND of two OR groups; country codes from list_locations).";
|
|
260
267
|
server.registerTool("lighthouse_crm_list_attributes", {
|
|
261
268
|
title: "List CRM Attributes",
|
|
269
|
+
annotations: READ_ONLY,
|
|
262
270
|
description: "Get a lightweight catalog of the fields for a CRM record type: each field's permalink, label, data_type, links_to, read_only, is_custom, has_options, source_field and list_id. " +
|
|
263
271
|
"`source_field` names the status attribute a `duration` attribute is derived from (null on every other attribute); such attributes (time_in_stage on deals, time_in_status on tasks, time_in_<status> beside a custom status field) are read-only and computed, never written. " +
|
|
264
272
|
"ALWAYS call this before building filters for search_records / list_records or creating/updating views. Use the exact permalinks it returns. " +
|
|
@@ -292,6 +300,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
292
300
|
});
|
|
293
301
|
server.registerTool("lighthouse_crm_get_attribute", {
|
|
294
302
|
title: "Get CRM Attribute",
|
|
303
|
+
annotations: READ_ONLY,
|
|
295
304
|
description: "Get a single custom field definition. Response returns 'permalink', 'is_custom', 'data_type', 'links_to'. " +
|
|
296
305
|
"For custom objects, use the object's permalink as record_type (e.g. 'lp').",
|
|
297
306
|
inputSchema: {
|
|
@@ -309,6 +318,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
309
318
|
});
|
|
310
319
|
server.registerTool("lighthouse_crm_create_attribute", {
|
|
311
320
|
title: "Create CRM Attribute",
|
|
321
|
+
annotations: CREATES,
|
|
312
322
|
description: "Create a new custom field in your CRM workspace. " +
|
|
313
323
|
"Types: text, number, currency, percentage, date, datetimez, select, multi_select, status, checkbox, url, email, phone, rating, record, multi_record, user, multi_user. " +
|
|
314
324
|
"Supports custom objects, use the object's permalink as record_type (e.g. 'lp'). " +
|
|
@@ -360,6 +370,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
360
370
|
});
|
|
361
371
|
server.registerTool("lighthouse_crm_update_attribute", {
|
|
362
372
|
title: "Update CRM Attribute",
|
|
373
|
+
annotations: DESTRUCTIVE,
|
|
363
374
|
description: "Update a custom field's display label (the field type, linked record type, and options are fixed after creation). " +
|
|
364
375
|
"For custom objects, use the object's permalink as record_type.",
|
|
365
376
|
inputSchema: {
|
|
@@ -379,6 +390,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
379
390
|
});
|
|
380
391
|
server.registerTool("lighthouse_crm_delete_attribute", {
|
|
381
392
|
title: "Delete CRM Attribute",
|
|
393
|
+
annotations: DESTRUCTIVE,
|
|
382
394
|
description: "Delete a custom field and all its data. Only custom fields can be deleted, not system fields. " +
|
|
383
395
|
"For custom objects, use the object's permalink as record_type.",
|
|
384
396
|
inputSchema: {
|
|
@@ -396,6 +408,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
396
408
|
});
|
|
397
409
|
server.registerTool("lighthouse_crm_list_colors", {
|
|
398
410
|
title: "List Colors",
|
|
411
|
+
annotations: READ_ONLY,
|
|
399
412
|
description: "Get the available color palette for select/multi_select/status field options. " +
|
|
400
413
|
"Returns an array of {hex_code, label} objects. " +
|
|
401
414
|
"Use this to discover valid hex color codes when creating or updating select options.",
|
|
@@ -411,7 +424,8 @@ export function createServer(apiKey, options = {}) {
|
|
|
411
424
|
});
|
|
412
425
|
server.registerTool("lighthouse_crm_list_options", {
|
|
413
426
|
title: "List CRM Field Options",
|
|
414
|
-
|
|
427
|
+
annotations: READ_ONLY,
|
|
428
|
+
description: "Get the allowed values for a CRM select / multi_select field. Each row is { value, label, color, priority, is_default, enabled }, the same object the options[] of an attribute carries; a built-in value the workspace switched off is not listed, and a write naming it is refused. " +
|
|
415
429
|
"Call this when lighthouse_crm_list_attributes shows has_options=true for a field and you need its values before filtering or setting it. " +
|
|
416
430
|
"Results are paginated (max 100 per page): use `search` to narrow by label, and `limit` / `offset` to page through a field with many options. " +
|
|
417
431
|
"For custom objects, use the object's permalink as record_type.",
|
|
@@ -434,9 +448,10 @@ export function createServer(apiKey, options = {}) {
|
|
|
434
448
|
});
|
|
435
449
|
server.registerTool("lighthouse_crm_create_option", {
|
|
436
450
|
title: "Create CRM Field Option",
|
|
451
|
+
annotations: CREATES,
|
|
437
452
|
description: "Add a new option to a select/multi_select field. The option value is auto-generated from the label. " +
|
|
438
453
|
"Re-creating a system default option that was previously removed restores it (its original color and label are kept). " +
|
|
439
|
-
"Returns the updated list of ALL options for the field, each row { value, label, color, priority, is_default, enabled },
|
|
454
|
+
"Returns the updated list of ALL options for the field, each row { value, label, color, priority, is_default, enabled }, without a built-in value the workspace switched off, plus meta.total. The list comes back in the order the field offers its options, so do not read the first row as the option you just created: find it by the label you sent. " +
|
|
440
455
|
"For custom objects, use the object's permalink as record_type. " +
|
|
441
456
|
"Options can only be created on company, person, deal, and custom objects.",
|
|
442
457
|
inputSchema: {
|
|
@@ -457,6 +472,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
457
472
|
});
|
|
458
473
|
server.registerTool("lighthouse_crm_delete_option", {
|
|
459
474
|
title: "Delete CRM Field Option",
|
|
475
|
+
annotations: DESTRUCTIVE,
|
|
460
476
|
description: "Delete an option value from a select/multi_select field. Custom options are removed permanently; a system default option is disabled for the workspace instead (re-create it to restore). " +
|
|
461
477
|
"For custom objects, use the object's permalink as record_type.",
|
|
462
478
|
inputSchema: {
|
|
@@ -475,6 +491,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
475
491
|
});
|
|
476
492
|
server.registerTool("lighthouse_crm_search_records", {
|
|
477
493
|
title: "Search CRM Records",
|
|
494
|
+
annotations: READ_ONLY,
|
|
478
495
|
description: "Search and filter records in YOUR CRM workspace (companies, people, or deals you've added). " +
|
|
479
496
|
"Supports filters, sorting, pagination, and saved views. " +
|
|
480
497
|
"A search by MEANING over people or company records is a semantic_search condition in filters; it needs the ready vector of a saved view (lighthouse_crm_get_view) or of a condition copied from the app, never text and never a vector you wrote (the filters description has the rule). " +
|
|
@@ -520,6 +537,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
520
537
|
});
|
|
521
538
|
server.registerTool("lighthouse_crm_get_record", {
|
|
522
539
|
title: "Get CRM Record",
|
|
540
|
+
annotations: READ_ONLY,
|
|
523
541
|
description: "Get a single CRM record by ID with all fields and relationships. Custom fields are returned as top-level keys, not nested under an attributes object. " +
|
|
524
542
|
"Signal fields (company_signals on companies, person_signals on people) are arrays of signal objects, one per signal: { signal (the value), picked_up_on (ISO 8601 date, the day the feed picked it up), occurrences (the number of windows or registrations behind it, 1 otherwise), and what the feed knows: " +
|
|
525
543
|
"for a company measured_on, timeframe, change_percent, value (the metric's value on measured_on, from the most recently measured window), windows (every window, each its own snapshot: [{timeframe, change_percent, value, measured_on, picked_up_on}]), money_raised_usd, investment_type, announced_on, investment_stage, num_investors, pre_money_valuation_usd; " +
|
|
@@ -546,6 +564,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
546
564
|
});
|
|
547
565
|
server.registerTool("lighthouse_crm_list_record_activity", {
|
|
548
566
|
title: "List Record Activity",
|
|
567
|
+
annotations: READ_ONLY,
|
|
549
568
|
description: "Read the activity feed of one CRM record, newest first: who changed what, when, and from where. Use it to answer questions about history (\"what changed on this deal last week\", \"who moved it to won\", \"which fields has nobody touched\") instead of guessing from the record's current values. " +
|
|
550
569
|
"Each entry has: action (created / updated / deleted / merged), occurred_at, summary (a ready-made sentence, ending \"via <the source>\" when a workflow or an API key made the change rather than a person, since both log a human as the actor), target ({ record_type, id, name }; the feed also carries the notes and tasks attached to the record, so the target is not always the record itself), actor (null for automated changes), and source. " +
|
|
551
570
|
"source always has { type, label }: type is app / mobile / api / mcp / workflow / ai_assistant / notetaker / email_sync / calendar_sync / linkedin_sync / inbound_email / inbound_whatsapp / scout / lp_portal / sync, and label names the exact origin. The four trace keys are always present and null when the origin did not record them: workflow is { id, name, run_id, node_id } when a workflow made the change (tracing an entry back to the exact run and step), api_key_id / mcp_tool identify the key or AI tool behind an API change, and trigger is automatic or manual where the origin recorded whether a person asked for the change (meeting notes written on their own and notes a member asked for both log that member as the actor, so trigger is what tells them apart). " +
|
|
@@ -573,6 +592,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
573
592
|
});
|
|
574
593
|
server.registerTool("lighthouse_crm_list_ai_reports", {
|
|
575
594
|
title: "List AI Reports on a Record",
|
|
595
|
+
annotations: READ_ONLY,
|
|
576
596
|
description: "The AI reports generated on one CRM record (any record type: company, person, deal, or a custom object permalink), newest first: the memos, SWOTs, teardowns, pass memos and briefs a member generated from the record's AI Reports tab in Lighthouse. " +
|
|
577
597
|
"WHEN TO USE: the user asks whether a memo or report already exists on a record, what the last one said, wants to compare or build on an earlier report, or asks about a company's \"memo\" / \"analysis\" / \"write-up\" (check here before writing one from scratch). Not for dashboards or charts (those are lighthouse_reports_*), and nothing here generates a report: that happens in the app. " +
|
|
578
598
|
"Each row: id, name, status (processing / success / error), depth (short / standard / extensive), template, partial (true when the run hit a cap), summary (one paragraph of the report's own prose), created_by ({ id, first_name, last_name, full_name, avatar_url, email }, the same member object every other row carries), created_at, completed_at. Only a success report has a document: read it with lighthouse_crm_get_ai_report. " +
|
|
@@ -596,6 +616,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
596
616
|
});
|
|
597
617
|
server.registerTool("lighthouse_crm_get_ai_report", {
|
|
598
618
|
title: "Get AI Report",
|
|
619
|
+
annotations: READ_ONLY,
|
|
599
620
|
description: "One AI report by id (from lighthouse_crm_list_ai_reports): the same metadata plus record_type / record_id and content, the full document as Markdown with its section headings (citation markers removed). " +
|
|
600
621
|
"WHEN TO USE: the user wants what an earlier memo or report actually says (a section, a number, a recommendation), or a comparison with it. A report can be long: quote or summarise the part the question is about rather than echoing it whole. " +
|
|
601
622
|
"content is null while the report is still processing or failed. Answers 403 naming the channel when the report draws on a shared mailbox, calendar, meeting notes, LinkedIn account or document the API key's user cannot read, or on " + REPORT_ROLE_SOURCES + " (its author is never refused for its sources), and 404 for an unknown id.",
|
|
@@ -613,6 +634,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
613
634
|
});
|
|
614
635
|
server.registerTool("lighthouse_crm_create_record", {
|
|
615
636
|
title: "Create CRM Record",
|
|
637
|
+
annotations: CREATES,
|
|
616
638
|
description: "Create a new CRM record. Pass all fields (standard + custom) in a single flat data object, " +
|
|
617
639
|
"the API splits standard vs custom fields automatically. " +
|
|
618
640
|
"BEFORE calling this tool, call lighthouse_crm_list_attributes to discover available field keys and types for the record_type. " +
|
|
@@ -647,6 +669,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
647
669
|
});
|
|
648
670
|
server.registerTool("lighthouse_crm_update_record", {
|
|
649
671
|
title: "Update CRM Record",
|
|
672
|
+
annotations: DESTRUCTIVE,
|
|
650
673
|
description: "Update an existing CRM record. Only include the fields you want to change. " +
|
|
651
674
|
"Standard and custom fields can be mixed in a single flat data object, the API splits them automatically. " +
|
|
652
675
|
"BEFORE calling this tool, call lighthouse_crm_list_attributes to discover available field keys and types. " +
|
|
@@ -684,6 +707,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
684
707
|
});
|
|
685
708
|
server.registerTool("lighthouse_crm_upsert_record", {
|
|
686
709
|
title: "Upsert CRM Record",
|
|
710
|
+
annotations: DESTRUCTIVE,
|
|
687
711
|
description: "Find-or-create a CRM record by a UNIQUE attribute: if a record already matches the value it is updated, otherwise a new one is created. " +
|
|
688
712
|
"Only person and company support upsert, person matches on email or linkedin, company on domain or linkedin (set matching_attribute accordingly). " +
|
|
689
713
|
"Pass the value to match on as `matching_value` (\"jane@acme.com\", \"acme.com\", a LinkedIn address). It may also sit inside data as the attribute itself (emails: [\"jane@acme.com\"], domain: [\"acme.com\"], linkedin: \"...\"), which is what this tool used to require; matching_value wins when both carry it, and a request with neither is refused. A record created this way still stores the value. " +
|
|
@@ -711,6 +735,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
711
735
|
});
|
|
712
736
|
server.registerTool("lighthouse_crm_delete_record", {
|
|
713
737
|
title: "Delete CRM Record",
|
|
738
|
+
annotations: DESTRUCTIVE,
|
|
714
739
|
description: "Permanently delete a CRM record. For custom objects, use the object's permalink as record_type.",
|
|
715
740
|
inputSchema: {
|
|
716
741
|
record_type: z.string().describe("Record type: company, person, deal, or a custom object permalink"),
|
|
@@ -727,6 +752,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
727
752
|
});
|
|
728
753
|
server.registerTool("lighthouse_crm_bulk_create_records", {
|
|
729
754
|
title: "Bulk Create CRM Records",
|
|
755
|
+
annotations: CREATES,
|
|
730
756
|
description: "Create up to 100 CRM records of the same record_type in one call. " +
|
|
731
757
|
"Each item in `records` is a flat data object, the SAME shape you'd pass as `data` to lighthouse_crm_create_record. " +
|
|
732
758
|
"BEFORE calling, call lighthouse_crm_list_attributes to discover field keys and types. " +
|
|
@@ -752,6 +778,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
752
778
|
});
|
|
753
779
|
server.registerTool("lighthouse_crm_bulk_update_records", {
|
|
754
780
|
title: "Bulk Update CRM Records",
|
|
781
|
+
annotations: DESTRUCTIVE,
|
|
755
782
|
description: "Update up to 100 CRM records of the same record_type in one call. " +
|
|
756
783
|
"Each item in `records` is { id, data }, `id` is the record UUID and `data` is a flat object of the fields to change " +
|
|
757
784
|
"(same shape as lighthouse_crm_update_record). " +
|
|
@@ -779,6 +806,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
779
806
|
});
|
|
780
807
|
server.registerTool("lighthouse_crm_bulk_upsert_records", {
|
|
781
808
|
title: "Bulk Upsert CRM Records",
|
|
809
|
+
annotations: DESTRUCTIVE,
|
|
782
810
|
description: "Find-or-create up to 100 records of the same record_type in one call, each matched on a unique attribute. " +
|
|
783
811
|
"For each item, if a record already matches the value it is updated, otherwise a new one is created. " +
|
|
784
812
|
"Person matches on email or linkedin, company on domain or linkedin (set matching_attribute for the whole batch). " +
|
|
@@ -808,6 +836,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
808
836
|
});
|
|
809
837
|
server.registerTool("lighthouse_crm_bulk_delete_records", {
|
|
810
838
|
title: "Bulk Delete CRM Records",
|
|
839
|
+
annotations: DESTRUCTIVE,
|
|
811
840
|
description: "Permanently delete up to 100 CRM records of the same record_type in one call. " +
|
|
812
841
|
"Pass `ids` as an array of record UUIDs. " +
|
|
813
842
|
"Partial success: the response returns a `results` array (one entry per id, in order, with success/error) and a `summary` ({total, succeeded, failed}).",
|
|
@@ -826,6 +855,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
826
855
|
});
|
|
827
856
|
server.registerTool("lighthouse_crm_create_list", {
|
|
828
857
|
title: "Create CRM List",
|
|
858
|
+
annotations: CREATES,
|
|
829
859
|
description: "Create a new CRM list to organize records. Supports companies, people, deals, and custom objects. " +
|
|
830
860
|
"Defaults to 'private' visibility. " +
|
|
831
861
|
"To share with specific teams after creation, use lighthouse_crm_share_list_with_teams. " +
|
|
@@ -847,6 +877,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
847
877
|
});
|
|
848
878
|
server.registerTool("lighthouse_crm_list_lists", {
|
|
849
879
|
title: "List CRM Lists",
|
|
880
|
+
annotations: READ_ONLY,
|
|
850
881
|
description: "Get all CRM lists in your workspace. Each list's record_type is 'company', 'person', 'deal', or a custom object permalink (e.g. 'lp', 'project'), and that is the record type of everything in it. " +
|
|
851
882
|
"Each row carries owner (the member who created it) and is_owner (true when that member is the API key's user), so 'my lists' and 'lists shared with me' can be told apart without a second call. " +
|
|
852
883
|
"WHEN TO USE: to find a list someone names (\"the Deals Reviewed list\"), pass search with the words of its name and read the id off the matching row, instead of paging through every list; leave search out to page through all of them. meta.total counts the lists that matched.",
|
|
@@ -867,6 +898,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
867
898
|
});
|
|
868
899
|
server.registerTool("lighthouse_crm_get_list", {
|
|
869
900
|
title: "Get CRM List",
|
|
901
|
+
annotations: READ_ONLY,
|
|
870
902
|
description: "Get a single CRM list by ID with its configuration. Its record_type is 'company', 'person', 'deal', or a custom object permalink (e.g. 'lp', 'project'). " +
|
|
871
903
|
"is_owner says whether the API key's user owns the list, so 'my lists' and 'lists shared with me' can be told apart without a second call. A list you cannot open answers 'List not found', the same as one that does not exist.",
|
|
872
904
|
inputSchema: {
|
|
@@ -884,6 +916,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
884
916
|
});
|
|
885
917
|
server.registerTool("lighthouse_crm_update_list", {
|
|
886
918
|
title: "Update CRM List",
|
|
919
|
+
annotations: DESTRUCTIVE,
|
|
887
920
|
description: "Update a CRM list's name or configuration. A list you cannot open answers 'List not found', the same as one that does not exist.",
|
|
888
921
|
inputSchema: {
|
|
889
922
|
list_id: z.string().uuid().optional().describe("List ID"),
|
|
@@ -904,6 +937,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
904
937
|
});
|
|
905
938
|
server.registerTool("lighthouse_crm_delete_list", {
|
|
906
939
|
title: "Delete CRM List",
|
|
940
|
+
annotations: DESTRUCTIVE,
|
|
907
941
|
description: "Permanently delete a CRM list. A list you cannot open answers 'List not found', the same as one that does not exist.",
|
|
908
942
|
inputSchema: {
|
|
909
943
|
list_id: z.string().uuid().optional().describe("List ID"),
|
|
@@ -920,6 +954,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
920
954
|
});
|
|
921
955
|
server.registerTool("lighthouse_crm_list_list_records", {
|
|
922
956
|
title: "List Records in a List",
|
|
957
|
+
annotations: READ_ONLY,
|
|
923
958
|
description: "Get records in a CRM list with optional filtering, sorting, and view support. " +
|
|
924
959
|
"Same filter format as lighthouse_crm_search_records. To get valid filter keys, call lighthouse_crm_list_attributes with this list's record_type, which is 'company', 'person', 'deal', or a custom object permalink. " +
|
|
925
960
|
"The response's `meta` includes `total` (an integer match count), `ignored_filter_fields` (any filter key that is not a valid attribute for this list's record type, each with a did_you_mean suggestion when a close match can be computed), `invalid_operators` (a condition whose operator its field does not take, left out of the results), `invalid_logical_operators` (a group joined by something other than AND/OR, read as AND) and `invalid_sort_fields` (a sort this list cannot apply, reported rather than refused, so the records come back in their default order). The saved filters of a view_id are reported on the same way. Check every array to confirm the whole filter and the sort were applied; a value in the wrong shape is refused instead, with a 400. " +
|
|
@@ -946,6 +981,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
946
981
|
});
|
|
947
982
|
server.registerTool("lighthouse_crm_add_to_list", {
|
|
948
983
|
title: "Add Record to List",
|
|
984
|
+
annotations: ADDS_IF_MISSING,
|
|
949
985
|
description: "Add a CRM record to a list. The record type is inferred from the list, so the record must be of the list's record_type: 'company', 'person', 'deal', or a custom object permalink. Answers `reason` beside the flag: the sentence \"This record was already on the list.\" / \"This record was not on the list.\" when the call changed nothing, and null when it did. Both writes succeed either way, so this key is what tells a no-op from a real change.",
|
|
950
986
|
inputSchema: {
|
|
951
987
|
list_id: z.string().uuid().describe("List ID"),
|
|
@@ -964,6 +1000,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
964
1000
|
});
|
|
965
1001
|
server.registerTool("lighthouse_crm_remove_from_list", {
|
|
966
1002
|
title: "Remove Record from List",
|
|
1003
|
+
annotations: DESTRUCTIVE,
|
|
967
1004
|
description: "Remove a record from a CRM list. The record type is inferred from the list, whose record_type is 'company', 'person', 'deal', or a custom object permalink. Answers `reason` beside the flag: the sentence \"This record was already on the list.\" / \"This record was not on the list.\" when the call changed nothing, and null when it did. Both writes succeed either way, so this key is what tells a no-op from a real change.",
|
|
968
1005
|
inputSchema: {
|
|
969
1006
|
list_id: z.string().uuid().describe("List ID"),
|
|
@@ -980,6 +1017,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
980
1017
|
});
|
|
981
1018
|
server.registerTool("lighthouse_crm_bulk_add_to_list", {
|
|
982
1019
|
title: "Bulk Add Records to List",
|
|
1020
|
+
annotations: ADDS_IF_MISSING,
|
|
983
1021
|
description: "Add up to 100 CRM records to a list in one call. The record type is inferred from the list, so every id must be of the list's record_type: 'company', 'person', 'deal', or a custom object permalink. " +
|
|
984
1022
|
"Partial success: the response returns a `results` array (one entry per id, in order, with success/error) and a `summary` ({total, succeeded, failed}). " +
|
|
985
1023
|
"A failed id does NOT prevent the others from being added. Every result entry also carries `reason`, ONE key for \"why did this record not change\": the no-op sentence on a success that wrote nothing, the refusal in words on a failure, null when the record really changed. `error` stays beside it for a client that branches on a code.",
|
|
@@ -998,6 +1036,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
998
1036
|
});
|
|
999
1037
|
server.registerTool("lighthouse_crm_bulk_remove_from_list", {
|
|
1000
1038
|
title: "Bulk Remove Records from List",
|
|
1039
|
+
annotations: DESTRUCTIVE,
|
|
1001
1040
|
description: "Remove up to 100 CRM records from a list in one call. Every id must be of the list's record_type: 'company', 'person', 'deal', or a custom object permalink. " +
|
|
1002
1041
|
"Partial success: the response returns a `results` array (one entry per id, in order, with success/error) and a `summary` ({total, succeeded, failed}). Every result entry also carries `reason`, ONE key for \"why did this record not change\": the no-op sentence on a success that wrote nothing, the refusal in words on a failure, null when the record really changed. `error` stays beside it for a client that branches on a code.",
|
|
1003
1042
|
inputSchema: {
|
|
@@ -1015,6 +1054,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1015
1054
|
});
|
|
1016
1055
|
server.registerTool("lighthouse_crm_share_list_with_teams", {
|
|
1017
1056
|
title: "Share List with Teams",
|
|
1057
|
+
annotations: ADDS_IF_MISSING,
|
|
1018
1058
|
description: "Grant team-level access to a CRM list. Only the list owner can share. " +
|
|
1019
1059
|
"Use lighthouse_workspace_list_teams to find team IDs first. " +
|
|
1020
1060
|
"Requires lists.share permission. " +
|
|
@@ -1036,6 +1076,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1036
1076
|
});
|
|
1037
1077
|
server.registerTool("lighthouse_crm_revoke_list_from_teams", {
|
|
1038
1078
|
title: "Revoke List Team Access",
|
|
1079
|
+
annotations: DESTRUCTIVE,
|
|
1039
1080
|
description: "Revoke team-level access from a CRM list. Only the list owner can revoke. " +
|
|
1040
1081
|
"Requires lists.share permission. " +
|
|
1041
1082
|
"Returns formatted list: {id, name, record_type, sharing, color, count, owner, is_owner, shared_with_teams, created_at, updated_at}, where record_type is 'company', 'person', 'deal', or a custom object permalink and is_owner says whether the API key's user owns the list.",
|
|
@@ -1056,6 +1097,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1056
1097
|
});
|
|
1057
1098
|
server.registerTool("lighthouse_crm_update_list_sharing", {
|
|
1058
1099
|
title: "Update List Sharing Status",
|
|
1100
|
+
annotations: DESTRUCTIVE,
|
|
1059
1101
|
description: "Change a CRM list's sharing visibility. Setting to 'private' also removes all team permissions. " +
|
|
1060
1102
|
"Only the list owner can change sharing. Requires lists.share permission. " +
|
|
1061
1103
|
"Returns formatted list: {id, name, record_type, sharing, color, count, owner, is_owner, shared_with_teams, created_at, updated_at}, where record_type is 'company', 'person', 'deal', or a custom object permalink and is_owner says whether the API key's user owns the list.",
|
|
@@ -1076,6 +1118,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1076
1118
|
});
|
|
1077
1119
|
server.registerTool("lighthouse_crm_list_notes", {
|
|
1078
1120
|
title: "List Notes",
|
|
1121
|
+
annotations: READ_ONLY,
|
|
1079
1122
|
description: "Get notes for a CRM record. Each note in the response includes a `records` map, every record (company/person/deal/custom object) the note is currently linked to that you can view, plus a `tags` array of any workspace tags attached to the note (each with id, label, color). " +
|
|
1080
1123
|
LINKED_RECORDS_LEFT_OUT + " " +
|
|
1081
1124
|
"`tagged_users` is the teammates the note MENTIONS, each the member object this API answers for a person ({ id, first_name, last_name, full_name, avatar_url, email }), and [] when it mentions nobody. It is read from the mention spans in `content`, not from the ids a write sent, so a note written with tagged_users ids and no mention span reads back []: always write both, as lighthouse_crm_create_note and lighthouse_crm_update_note say. " +
|
|
@@ -1096,6 +1139,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1096
1139
|
});
|
|
1097
1140
|
server.registerTool("lighthouse_crm_search_notes", {
|
|
1098
1141
|
title: "Search Notes",
|
|
1142
|
+
annotations: READ_ONLY,
|
|
1099
1143
|
description: "Search ALL notes in the workspace (unlike List Notes, which needs one record). Combine a free-text title search with a CRM-style filter tree and sort. " +
|
|
1100
1144
|
'filters: {"operator":"AND","conditions":[{"field":"tags","operator":"contains_any","value":["<tag_permalink>"]}]}: nested groups supported. ' +
|
|
1101
1145
|
"Filterable fields and their operators: title (text: contains/not_contains/starts_with/ends_with/eq/neq/empty/not_empty), " +
|
|
@@ -1125,6 +1169,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1125
1169
|
});
|
|
1126
1170
|
server.registerTool("lighthouse_crm_create_note", {
|
|
1127
1171
|
title: "Create Note",
|
|
1172
|
+
annotations: CREATES,
|
|
1128
1173
|
description: "Create a note, optionally linked to one or more CRM records. Records are optional: omit them to create a standalone note that still appears in the workspace Notes section. " +
|
|
1129
1174
|
"Link records via the `records` param, keyed by type. " +
|
|
1130
1175
|
"The company/person/deal ids you pass MUST be EXISTING CRM record ids (the ids returned by lighthouse_crm_* tools), NOT discovery ids from lighthouse_discovery_* search results. To note on a discovery result, first add it to your CRM and use the resulting CRM record id. " +
|
|
@@ -1164,6 +1209,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1164
1209
|
});
|
|
1165
1210
|
server.registerTool("lighthouse_crm_get_note", {
|
|
1166
1211
|
title: "Get Note",
|
|
1212
|
+
annotations: READ_ONLY,
|
|
1167
1213
|
description: "Get a single note by ID. Response includes a `records` object keyed by record type (or custom object permalink) with every linked record you can view, plus a `tags` array of any workspace tags attached to the note (each with permalink, label, color). " +
|
|
1168
1214
|
LINKED_RECORDS_LEFT_OUT + " " +
|
|
1169
1215
|
"`tagged_users` is the teammates the note MENTIONS, each the member object this API answers for a person ({ id, first_name, last_name, full_name, avatar_url, email }), and [] when it mentions nobody. It is read from the mention spans in `content`, not from the ids a write sent, so a note written with tagged_users ids and no mention span reads back []: always write both, as lighthouse_crm_create_note and lighthouse_crm_update_note say. " +
|
|
@@ -1183,6 +1229,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1183
1229
|
});
|
|
1184
1230
|
server.registerTool("lighthouse_crm_update_note", {
|
|
1185
1231
|
title: "Update Note",
|
|
1232
|
+
annotations: DESTRUCTIVE,
|
|
1186
1233
|
description: "Change an existing note's title, content, tags or mentions by note ID. Pass only what changes: a key that is absent is left untouched. " +
|
|
1187
1234
|
"content REPLACES the whole body: to ADD to a note, read it with lighthouse_crm_get_note first and pass the full new content with the addition, never the addition alone (anything you leave out, an existing @mention span included, is gone). " +
|
|
1188
1235
|
"Content is HTML, the same format lighthouse_crm_create_note takes: <p> for paragraphs, <ul><li> for bullets, <h2> for a heading, <strong> for emphasis; plain text works too and is stored as one paragraph. " +
|
|
@@ -1211,6 +1258,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1211
1258
|
});
|
|
1212
1259
|
server.registerTool("lighthouse_crm_delete_note", {
|
|
1213
1260
|
title: "Delete Note",
|
|
1261
|
+
annotations: DESTRUCTIVE,
|
|
1214
1262
|
description: "Permanently delete a note and all of its record links. Meeting notes are deleted the same way, together with every version of them; the meeting's recording and transcript are not affected.",
|
|
1215
1263
|
inputSchema: {
|
|
1216
1264
|
note_id: z.string().uuid().optional().describe("Note ID"),
|
|
@@ -1227,6 +1275,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1227
1275
|
});
|
|
1228
1276
|
server.registerTool("lighthouse_crm_link_note_record", {
|
|
1229
1277
|
title: "Link Record to Note",
|
|
1278
|
+
annotations: ADDS_IF_MISSING,
|
|
1230
1279
|
description: "Attach a record to an existing note. Call once per record. " +
|
|
1231
1280
|
"The company/person/deal record_id MUST be an EXISTING CRM record id (an id returned by lighthouse_crm_* tools), NOT a discovery id from lighthouse_discovery_* search results. To link a discovery result, first add it to your CRM and use the resulting CRM record id. " +
|
|
1232
1281
|
LINK_NOT_VIEWABLE_RULE + " " +
|
|
@@ -1247,6 +1296,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1247
1296
|
});
|
|
1248
1297
|
server.registerTool("lighthouse_crm_unlink_note_record", {
|
|
1249
1298
|
title: "Unlink Record from Note",
|
|
1299
|
+
annotations: DESTRUCTIVE,
|
|
1250
1300
|
description: "Remove a record link from a note. Call once per record. " +
|
|
1251
1301
|
"Removing the last linked record is allowed and leaves a standalone note (it stays in the workspace Notes section). " +
|
|
1252
1302
|
UNLINK_NOT_VIEWABLE_KEPT,
|
|
@@ -1266,6 +1316,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1266
1316
|
});
|
|
1267
1317
|
server.registerTool("lighthouse_crm_list_note_tags", {
|
|
1268
1318
|
title: "List Note Tags",
|
|
1319
|
+
annotations: READ_ONLY,
|
|
1269
1320
|
description: "List the workspace's note tags: labeled, colored tags that can be attached to notes as a multi-select. " +
|
|
1270
1321
|
"Returns an array of { permalink, label, color }. " +
|
|
1271
1322
|
"Use this to find tag permalinks before attaching them to a note (via lighthouse_crm_create_note `tags` or lighthouse_crm_manage_note_tags). " +
|
|
@@ -1282,6 +1333,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1282
1333
|
});
|
|
1283
1334
|
server.registerTool("lighthouse_crm_create_note_tag", {
|
|
1284
1335
|
title: "Create Note Tag",
|
|
1336
|
+
annotations: CREATES,
|
|
1285
1337
|
description: "Create a new note tag in the workspace vocabulary. The tag can then be attached to notes. " +
|
|
1286
1338
|
"Returns the created tag as { permalink, label, color }. " +
|
|
1287
1339
|
"Color is an optional palette code (get available colors from lighthouse_crm_list_colors); omit it to auto-assign one.",
|
|
@@ -1300,6 +1352,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1300
1352
|
});
|
|
1301
1353
|
server.registerTool("lighthouse_crm_update_note_tag", {
|
|
1302
1354
|
title: "Update Note Tag",
|
|
1355
|
+
annotations: DESTRUCTIVE,
|
|
1303
1356
|
description: "Update a note tag's label and/or color. Only the fields you pass are changed. " +
|
|
1304
1357
|
"Returns the updated tag as { permalink, label, color }.",
|
|
1305
1358
|
inputSchema: {
|
|
@@ -1318,6 +1371,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1318
1371
|
});
|
|
1319
1372
|
server.registerTool("lighthouse_crm_delete_note_tag", {
|
|
1320
1373
|
title: "Delete Note Tag",
|
|
1374
|
+
annotations: DESTRUCTIVE,
|
|
1321
1375
|
description: "Delete a note tag from the workspace vocabulary. The tag is also detached from any notes it was attached to.",
|
|
1322
1376
|
inputSchema: {
|
|
1323
1377
|
permalink: z.string().describe("Tag permalink"),
|
|
@@ -1333,6 +1387,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1333
1387
|
});
|
|
1334
1388
|
server.registerTool("lighthouse_crm_manage_note_tags", {
|
|
1335
1389
|
title: "Attach or Detach Note Tags",
|
|
1390
|
+
annotations: DESTRUCTIVE,
|
|
1336
1391
|
description: "Attach or detach workspace tags on an existing note. Pass operation ADD to attach the tag permalinks, or REMOVE to detach them. " +
|
|
1337
1392
|
"Find tag permalinks with lighthouse_crm_list_note_tags. Returns the updated note (including its current `tags`).",
|
|
1338
1393
|
inputSchema: {
|
|
@@ -1353,6 +1408,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1353
1408
|
});
|
|
1354
1409
|
server.registerTool("lighthouse_crm_list_tasks", {
|
|
1355
1410
|
title: "List Tasks",
|
|
1411
|
+
annotations: READ_ONLY,
|
|
1356
1412
|
description: "List CRM tasks with optional filtering and sorting. " +
|
|
1357
1413
|
'filters (CRM-style): {"operator":"AND","conditions":[{"field":"status","operator":"contains_any","value":["<status_value>"]}]} (status AND priority are option fields whose values are workspace-specific; fetch them with lighthouse_crm_list_options(record_type:\'task\', permalink:\'status\' | \'priority\'), and filter priority with contains_any like status, not with a numeric comparison); ' +
|
|
1358
1414
|
'sort: [{"field":"due_date","direction":"asc"}]; ' +
|
|
@@ -1379,6 +1435,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1379
1435
|
});
|
|
1380
1436
|
server.registerTool("lighthouse_crm_get_task", {
|
|
1381
1437
|
title: "Get Task",
|
|
1438
|
+
annotations: READ_ONLY,
|
|
1382
1439
|
description: "Get a single CRM task by ID. " +
|
|
1383
1440
|
"status and priority answer the stored VALUE and nothing else. The words your workspace puts on those values come from lighthouse_crm_list_options(record_type:'task', permalink:'status' | 'priority'), one call that covers every task you will ever read.",
|
|
1384
1441
|
inputSchema: {
|
|
@@ -1395,6 +1452,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1395
1452
|
});
|
|
1396
1453
|
server.registerTool("lighthouse_crm_create_task", {
|
|
1397
1454
|
title: "Create Task",
|
|
1455
|
+
annotations: CREATES,
|
|
1398
1456
|
description: "Create a CRM task, optionally linked to records and assigned to users. " +
|
|
1399
1457
|
"Status values are configured per workspace and are NOT fixed: call lighthouse_crm_list_options with record_type 'task' and permalink 'status' to get the allowed values, then pass the option's value (not its label) in `status`. " +
|
|
1400
1458
|
"status and priority answer the stored VALUE and nothing else. The words your workspace puts on those values come from lighthouse_crm_list_options(record_type:'task', permalink:'status' | 'priority'), one call that covers every task you will ever read.",
|
|
@@ -1424,6 +1482,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1424
1482
|
});
|
|
1425
1483
|
server.registerTool("lighthouse_crm_update_task", {
|
|
1426
1484
|
title: "Update Task",
|
|
1485
|
+
annotations: DESTRUCTIVE,
|
|
1427
1486
|
description: "Update a CRM task. Provide only fields you want to change. " +
|
|
1428
1487
|
"assigned_to replaces all current assignees. In records, " + TASK_LINKS_PER_TYPE.charAt(0).toLowerCase() + TASK_LINKS_PER_TYPE.slice(1) + " " +
|
|
1429
1488
|
"Status values are configured per workspace and are NOT fixed: call lighthouse_crm_list_options with record_type 'task' and permalink 'status' to get the allowed values, then pass the option's value (not its label) in `status`. " +
|
|
@@ -1456,6 +1515,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1456
1515
|
});
|
|
1457
1516
|
server.registerTool("lighthouse_crm_delete_task", {
|
|
1458
1517
|
title: "Delete Task",
|
|
1518
|
+
annotations: DESTRUCTIVE,
|
|
1459
1519
|
description: "Permanently delete a CRM task.",
|
|
1460
1520
|
inputSchema: {
|
|
1461
1521
|
id: z.string().uuid().describe("Task ID"),
|
|
@@ -1471,6 +1531,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1471
1531
|
});
|
|
1472
1532
|
server.registerTool("lighthouse_documents_list", {
|
|
1473
1533
|
title: "List Documents",
|
|
1534
|
+
annotations: READ_ONLY,
|
|
1474
1535
|
description: "List folders and files at the workspace root, or inside a specific folder. " +
|
|
1475
1536
|
"Returns { folders, files, breadcrumb } plus meta.folders_next_cursor / " +
|
|
1476
1537
|
"meta.files_next_cursor for cursor-based pagination. Pass those cursors " +
|
|
@@ -1478,7 +1539,8 @@ export function createServer(apiKey, options = {}) {
|
|
|
1478
1539
|
"Each row includes my_role ('owner' | 'editor' | 'viewer'), is_owner, " +
|
|
1479
1540
|
"owner (the member who owns it: { id, first_name, last_name, full_name, avatar_url, email }), " +
|
|
1480
1541
|
"sharing ('private' or 'workspace'), linked_records_count, and " +
|
|
1481
|
-
"shared_with_teams[] (each entry has { team_id, team_name, role, granted_at })."
|
|
1542
|
+
"shared_with_teams[] (each entry has { team_id, team_name, role, granted_at }). " +
|
|
1543
|
+
`${FILE_BLOCKED_KEYS} ${FILE_BLOCKED_TOOL_RULE}`,
|
|
1482
1544
|
inputSchema: {
|
|
1483
1545
|
folder_id: z.string().uuid().optional().describe("Folder UUID to list contents of (omit for workspace root)"),
|
|
1484
1546
|
limit: z.number().int().min(1).max(500).optional().describe("Rows per sub-list (folders, files). Default 200, max 500"),
|
|
@@ -1496,6 +1558,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1496
1558
|
});
|
|
1497
1559
|
server.registerTool("lighthouse_documents_create_folder", {
|
|
1498
1560
|
title: "Create Folder",
|
|
1561
|
+
annotations: CREATES,
|
|
1499
1562
|
description: "Create a folder. At the workspace root, sharing is required. " +
|
|
1500
1563
|
"Inside a parent folder, sharing is automatically inherited from the parent. " +
|
|
1501
1564
|
"The folder row comes back with owner (the member who created it, { id, first_name, last_name, full_name, avatar_url, email }), is_owner, my_role, sharing ('private' or 'workspace'), shared_with_teams[] (each { team_id, team_name, role, granted_at }), linked_records_count, created_at and updated_at.",
|
|
@@ -1518,6 +1581,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1518
1581
|
});
|
|
1519
1582
|
server.registerTool("lighthouse_documents_get_folder", {
|
|
1520
1583
|
title: "Get Folder",
|
|
1584
|
+
annotations: READ_ONLY,
|
|
1521
1585
|
description: "WHEN TO USE: the user asks about ONE folder they already know (\"who can see the Investor Updates folder\", \"what is that folder linked to\"), or you have a folder id from lighthouse_documents_list and need its full row. " +
|
|
1522
1586
|
"Returns the folder itself, not its contents: name, parent_folder_id, sharing ('private' or 'workspace'), shared_with_teams[] (each { team_id, team_name, role, granted_at }), is_owner, my_role ('owner' | 'editor' | 'viewer'), owner ({ id, first_name, last_name, full_name, avatar_url, email }), linked_records_count, linked_records[] (every link you can view, each { record_type, record_id, record_name, avatar_url, created_at }), created_at and updated_at. " +
|
|
1523
1587
|
"To list what is INSIDE it, call lighthouse_documents_list with this folder_id. A folder you cannot open answers the same not found as one that does not exist.",
|
|
@@ -1536,6 +1600,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1536
1600
|
});
|
|
1537
1601
|
server.registerTool("lighthouse_documents_update_folder", {
|
|
1538
1602
|
title: "Update Folder",
|
|
1603
|
+
annotations: DESTRUCTIVE,
|
|
1539
1604
|
description: "Rename, move, or change sharing on a folder. Editors can rename the folder; only owners can move it, change its sharing, or grant team access. A folder that holds files or folders other people own can only be moved down, into a folder below where it is now that you own, with every folder in between yours too; any other move of it answers 403 forbidden. " +
|
|
1540
1605
|
"Pass team_ids as the full desired list, anything not in the list is revoked. " +
|
|
1541
1606
|
"Note the two meanings of folder_id across the document tools: here it is the folder being changed, while on lighthouse_documents_update_file it is the folder the file is moved INTO. " +
|
|
@@ -1562,6 +1627,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1562
1627
|
});
|
|
1563
1628
|
server.registerTool("lighthouse_documents_delete_folder", {
|
|
1564
1629
|
title: "Delete Folder",
|
|
1630
|
+
annotations: DESTRUCTIVE,
|
|
1565
1631
|
description: "Restricted to the folder owner. Deletes the folder and its entire subtree only when you own every descendant folder and file. If any descendant is owned by another user, the request is rejected (CANNOT_DELETE_OTHERS_CONTENT_IN_FOLDER) and nothing is deleted. Editors on the folder cannot delete it. Irreversible: there is no recycle bin and the underlying stored files are permanently removed.",
|
|
1566
1632
|
inputSchema: {
|
|
1567
1633
|
folder_id: z.string().uuid().describe("Folder UUID"),
|
|
@@ -1577,6 +1643,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1577
1643
|
});
|
|
1578
1644
|
server.registerTool("lighthouse_documents_link_folder_to_record", {
|
|
1579
1645
|
title: "Link Folder to Record",
|
|
1646
|
+
annotations: ADDS_IF_MISSING,
|
|
1580
1647
|
description: "Link a folder to a CRM record, associating the whole folder with the record. The folder then appears on the record (in lighthouse_documents_list_for_record's `folders`); its individual files are not flattened into the record's direct `files` list. " +
|
|
1581
1648
|
"Answers THE FOLDER itself, the same row lighthouse_documents_get_folder returns (owner, is_owner, my_role, sharing, shared_with_teams[] each { team_id, team_name, role, granted_at }, linked_records_count and linked_records[] as they are after the link), so there is no second call to make.",
|
|
1582
1649
|
inputSchema: {
|
|
@@ -1596,6 +1663,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1596
1663
|
});
|
|
1597
1664
|
server.registerTool("lighthouse_documents_unlink_folder_from_record", {
|
|
1598
1665
|
title: "Unlink Folder from Record",
|
|
1666
|
+
annotations: DESTRUCTIVE,
|
|
1599
1667
|
description: "Remove a folder→record link. Files inside stop appearing on the record's Files tab. Answers THE FOLDER itself, the same row lighthouse_documents_get_folder returns, with its links as they are after the call.",
|
|
1600
1668
|
inputSchema: {
|
|
1601
1669
|
folder_id: z.string().uuid().describe("Folder UUID"),
|
|
@@ -1614,6 +1682,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1614
1682
|
});
|
|
1615
1683
|
server.registerTool("lighthouse_documents_upload_file", {
|
|
1616
1684
|
title: "Upload File",
|
|
1685
|
+
annotations: CREATES,
|
|
1617
1686
|
description: "Reserve a file row and get a signed upload URL: " +
|
|
1618
1687
|
"1) call this; 2) POST the file to upload_url as multipart/form-data, first append every key/value " +
|
|
1619
1688
|
"from the returned upload_fields, then append the file last under the field name 'file'. " +
|
|
@@ -1641,7 +1710,9 @@ export function createServer(apiKey, options = {}) {
|
|
|
1641
1710
|
});
|
|
1642
1711
|
server.registerTool("lighthouse_documents_get_file", {
|
|
1643
1712
|
title: "Get File",
|
|
1644
|
-
|
|
1713
|
+
annotations: READ_ONLY,
|
|
1714
|
+
description: "Get file metadata. Response includes my_role ('owner' | 'editor' | 'viewer'), is_owner, owner (the member who owns it: { id, first_name, last_name, full_name, avatar_url, email }), sharing ('private' or 'workspace'), linked_records_count, linked_records[] and shared_with_teams[] (each entry has { team_id, team_name, role, granted_at }). Pass download_url=true to also receive a short-lived signed read URL (expires in 15 minutes). To read what the file SAYS, use lighthouse_documents_get_file_content. " +
|
|
1715
|
+
`${FILE_BLOCKED_KEYS} ${FILE_BLOCKED_TOOL_RULE} ${FILE_BLOCKED_ANSWER}`,
|
|
1645
1716
|
inputSchema: {
|
|
1646
1717
|
file_id: z.string().uuid().describe("File UUID"),
|
|
1647
1718
|
download_url: z.boolean().optional().describe("Include a short-lived signed download URL"),
|
|
@@ -1658,12 +1729,14 @@ export function createServer(apiKey, options = {}) {
|
|
|
1658
1729
|
});
|
|
1659
1730
|
server.registerTool("lighthouse_documents_get_file_content", {
|
|
1660
1731
|
title: "Read File",
|
|
1732
|
+
annotations: READ_ONLY,
|
|
1661
1733
|
description: "What one stored file SAYS, as text: PDFs, Word, Excel and PowerPoint files (and their OpenDocument versions), text, CSV and HTML. " +
|
|
1662
1734
|
"WHEN TO USE: the answer is inside a document the workspace keeps (\"what are the conditions in the term sheet\", \"summarise the memo on this deal\", \"what does the deck say about revenue\"). " +
|
|
1663
1735
|
"Take the file id from lighthouse_documents_list or lighthouse_documents_list_for_record. " +
|
|
1664
1736
|
"Returns the file's row (the one lighthouse_documents_get_file returns: id, name, folder_id, size_bytes, content_type, sharing, owner, ...) followed by kind, pages_total, text, reason and note. " +
|
|
1665
1737
|
FILE_CONTENT_RULES +
|
|
1666
|
-
" A file the user may not open answers forbidden, one that does not exist not found."
|
|
1738
|
+
" A file the user may not open answers forbidden, one that does not exist not found. " +
|
|
1739
|
+
`${FILE_BLOCKED_TOOL_RULE} ${FILE_BLOCKED_ANSWER}`,
|
|
1667
1740
|
inputSchema: {
|
|
1668
1741
|
file_id: z.string().uuid().optional().describe("File UUID. The name every other file tool takes."),
|
|
1669
1742
|
id: z.string().uuid().optional().describe("Same as file_id. Send either one."),
|
|
@@ -1691,9 +1764,11 @@ export function createServer(apiKey, options = {}) {
|
|
|
1691
1764
|
});
|
|
1692
1765
|
server.registerTool("lighthouse_documents_update_file", {
|
|
1693
1766
|
title: "Update File",
|
|
1767
|
+
annotations: DESTRUCTIVE,
|
|
1694
1768
|
description: "Rename, move, or change sharing on a file. Editors can rename and delete it. Only the owner can move it (into another folder or to the root), change sharing, or grant team access, with one exception: the owner of the folder the file is in can also move it down into a sub-folder of that folder, when they own that sub-folder and every folder in between. " +
|
|
1695
1769
|
"team_ids replaces the current set when supplied. " +
|
|
1696
|
-
"Returns the file row: owner, is_owner, my_role, sharing, shared_with_teams[] (each { team_id, team_name, role, granted_at }), linked_records_count, size_bytes, content_type, created_at, updated_at."
|
|
1770
|
+
"Returns the file row: owner, is_owner, my_role, sharing, shared_with_teams[] (each { team_id, team_name, role, granted_at }), linked_records_count, size_bytes, content_type, created_at, updated_at. " +
|
|
1771
|
+
FILE_BLOCKED_TOOL_RULE,
|
|
1697
1772
|
inputSchema: {
|
|
1698
1773
|
file_id: z.string().uuid().describe("File UUID, the file being changed"),
|
|
1699
1774
|
name: z.string().optional().describe("New name"),
|
|
@@ -1716,7 +1791,8 @@ export function createServer(apiKey, options = {}) {
|
|
|
1716
1791
|
});
|
|
1717
1792
|
server.registerTool("lighthouse_documents_delete_file", {
|
|
1718
1793
|
title: "Delete File",
|
|
1719
|
-
|
|
1794
|
+
annotations: DESTRUCTIVE,
|
|
1795
|
+
description: "Owner or editor (file-level editor OR editor-on-parent-folder) can delete, provided they also have the files.delete workspace permission. Permanently deletes the file and purges the underlying object from storage. Irreversible. " + FILE_BLOCKED_TOOL_RULE,
|
|
1720
1796
|
inputSchema: {
|
|
1721
1797
|
file_id: z.string().uuid().describe("File UUID"),
|
|
1722
1798
|
},
|
|
@@ -1731,7 +1807,8 @@ export function createServer(apiKey, options = {}) {
|
|
|
1731
1807
|
});
|
|
1732
1808
|
server.registerTool("lighthouse_documents_link_file_to_record", {
|
|
1733
1809
|
title: "Link File to Record",
|
|
1734
|
-
|
|
1810
|
+
annotations: ADDS_IF_MISSING,
|
|
1811
|
+
description: "Make a file appear on a CRM record's Files tab. Answers THE FILE itself, the same row lighthouse_documents_get_file returns, with its links as they are after the call. " + FILE_BLOCKED_TOOL_RULE,
|
|
1735
1812
|
inputSchema: {
|
|
1736
1813
|
file_id: z.string().uuid().describe("File UUID"),
|
|
1737
1814
|
record_type: z.string().describe("company, person, deal, or a custom object permalink"),
|
|
@@ -1749,7 +1826,8 @@ export function createServer(apiKey, options = {}) {
|
|
|
1749
1826
|
});
|
|
1750
1827
|
server.registerTool("lighthouse_documents_unlink_file_from_record", {
|
|
1751
1828
|
title: "Unlink File from Record",
|
|
1752
|
-
|
|
1829
|
+
annotations: DESTRUCTIVE,
|
|
1830
|
+
description: "Remove a file→record link. Answers THE FILE itself, the same row lighthouse_documents_get_file returns, with its links as they are after the call. " + FILE_BLOCKED_TOOL_RULE,
|
|
1753
1831
|
inputSchema: {
|
|
1754
1832
|
file_id: z.string().uuid().describe("File UUID"),
|
|
1755
1833
|
record_type: z.string().describe("company, person, deal, or a custom object permalink"),
|
|
@@ -1767,9 +1845,11 @@ export function createServer(apiKey, options = {}) {
|
|
|
1767
1845
|
});
|
|
1768
1846
|
server.registerTool("lighthouse_documents_list_for_record", {
|
|
1769
1847
|
title: "List a Record's Documents",
|
|
1848
|
+
annotations: READ_ONLY,
|
|
1770
1849
|
description: "List folders and files linked to a CRM record. Returns { folders, files }. `files` are files linked DIRECTLY to the record; files that live inside a linked folder are NOT flattened into `files`. To read a linked folder's contents, list that folder by its id with lighthouse_documents_list. " +
|
|
1771
1850
|
"Each row includes my_role ('owner' | 'editor' | 'viewer'), is_owner, owner (the member who owns it: { id, first_name, last_name, full_name, avatar_url, email }), sharing ('private' or 'workspace'), linked_records_count, linked_at, and shared_with_teams[] (each entry has { team_id, team_name, role, granted_at }). " +
|
|
1772
|
-
"Cursor-based pagination via meta.folders_next_cursor / meta.files_next_cursor; pass them back as folders_cursor / files_cursor to fetch the next page."
|
|
1851
|
+
"Cursor-based pagination via meta.folders_next_cursor / meta.files_next_cursor; pass them back as folders_cursor / files_cursor to fetch the next page. " +
|
|
1852
|
+
`${FILE_BLOCKED_KEYS} ${FILE_BLOCKED_TOOL_RULE}`,
|
|
1773
1853
|
inputSchema: {
|
|
1774
1854
|
record_type: z.string().describe("company, person, deal, or a custom object permalink"),
|
|
1775
1855
|
record_id: z.string().uuid().describe("Record UUID"),
|
|
@@ -1796,6 +1876,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1796
1876
|
});
|
|
1797
1877
|
server.registerTool("lighthouse_crm_list_views", {
|
|
1798
1878
|
title: "List CRM Views",
|
|
1879
|
+
annotations: READ_ONLY,
|
|
1799
1880
|
description: "Get all saved CRM views, or list views scoped to a specific list. " +
|
|
1800
1881
|
"Supports built-in types and custom objects. " +
|
|
1801
1882
|
"Views store filter/sort configurations that can be loaded in search_records. " +
|
|
@@ -1815,6 +1896,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1815
1896
|
});
|
|
1816
1897
|
server.registerTool("lighthouse_crm_update_view", {
|
|
1817
1898
|
title: "Update CRM View",
|
|
1899
|
+
annotations: DESTRUCTIVE,
|
|
1818
1900
|
description: "Update a saved CRM view or list view's name, filters, sort, or layout. " +
|
|
1819
1901
|
"Automatically detects whether it's a CRM view or list view. " +
|
|
1820
1902
|
"To change sharing settings, use lighthouse_crm_update_view_sharing instead. " +
|
|
@@ -1852,6 +1934,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1852
1934
|
});
|
|
1853
1935
|
server.registerTool("lighthouse_crm_get_view", {
|
|
1854
1936
|
title: "Get CRM View",
|
|
1937
|
+
annotations: READ_ONLY,
|
|
1855
1938
|
description: "Get a single saved CRM view or list view by ID. Automatically detects the view type. The filters come back as saved, a semantic_search condition with its vector included: reuse such a condition verbatim in lighthouse_crm_search_records or lighthouse_crm_list_list_records. Conditions on lists you cannot open are left out, and so are conditions on custom fields that belong to such a list. The row carries record_type, owner and is_owner (true when the API key's user owns the view). A kanban view's board settings come with it: hidden_kanban_groups (the values whose column is collapsed, always an array) and kanban_summary (the number at the top of each column, or null), both writable through lighthouse_crm_update_view exactly as they are read.",
|
|
1856
1939
|
inputSchema: {
|
|
1857
1940
|
id: z.string().uuid().describe("View ID"),
|
|
@@ -1867,6 +1950,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1867
1950
|
});
|
|
1868
1951
|
server.registerTool("lighthouse_crm_delete_view", {
|
|
1869
1952
|
title: "Delete CRM View",
|
|
1953
|
+
annotations: DESTRUCTIVE,
|
|
1870
1954
|
description: "Permanently delete a saved CRM view or list view. Automatically detects the view type.",
|
|
1871
1955
|
inputSchema: {
|
|
1872
1956
|
id: z.string().uuid().describe("View ID"),
|
|
@@ -1882,6 +1966,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1882
1966
|
});
|
|
1883
1967
|
server.registerTool("lighthouse_workspace_me", {
|
|
1884
1968
|
title: "Get Current User",
|
|
1969
|
+
annotations: READ_ONLY,
|
|
1885
1970
|
description: "Get the profile of the user this key acts as: id, name, email, avatar, phone_number, status, created_at, workspace_role, permissions and `teams` ([{ id, name, role, joined_at }], [] when they are on none). " +
|
|
1886
1971
|
"It is the same object lighthouse_workspace_get_user answers for anyone else, so read it once to learn who you are acting as and what that role may do, rather than calling the user tool on yourself.",
|
|
1887
1972
|
inputSchema: {},
|
|
@@ -1896,6 +1981,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1896
1981
|
});
|
|
1897
1982
|
server.registerTool("lighthouse_workspace_list_users", {
|
|
1898
1983
|
title: "List Workspace Users",
|
|
1984
|
+
annotations: READ_ONLY,
|
|
1899
1985
|
description: "List users in the workspace. Use this to find user IDs for task assignment, note tagging, or filtering by user. " +
|
|
1900
1986
|
"Supports search by name/email.",
|
|
1901
1987
|
inputSchema: {
|
|
@@ -1916,6 +2002,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1916
2002
|
});
|
|
1917
2003
|
server.registerTool("lighthouse_workspace_get_user", {
|
|
1918
2004
|
title: "Get Workspace User",
|
|
2005
|
+
annotations: READ_ONLY,
|
|
1919
2006
|
description: "Get a specific workspace user's profile by ID.",
|
|
1920
2007
|
inputSchema: {
|
|
1921
2008
|
id: z.string().uuid().describe("User ID"),
|
|
@@ -1931,6 +2018,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1931
2018
|
});
|
|
1932
2019
|
server.registerTool("lighthouse_workspace_list_teams", {
|
|
1933
2020
|
title: "List Workspace Teams",
|
|
2021
|
+
annotations: READ_ONLY,
|
|
1934
2022
|
description: "List all teams in the workspace.",
|
|
1935
2023
|
inputSchema: {
|
|
1936
2024
|
limit: z.number().min(1).max(100).default(25),
|
|
@@ -1947,6 +2035,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1947
2035
|
});
|
|
1948
2036
|
server.registerTool("lighthouse_workspace_get_team", {
|
|
1949
2037
|
title: "Get Workspace Team",
|
|
2038
|
+
annotations: READ_ONLY,
|
|
1950
2039
|
description: "Get a specific team by ID with its members.",
|
|
1951
2040
|
inputSchema: {
|
|
1952
2041
|
id: z.string().uuid().describe("Team ID"),
|
|
@@ -1966,6 +2055,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1966
2055
|
const WEBHOOK_SUBSCRIPTION_WRITES = "This tool only reads: creating and deleting webhook subscriptions is done with the REST API (POST /v1/webhook-subscriptions and DELETE /v1/webhook-subscriptions/{id}) or the SDKs, with an API key, and a subscription can also be created or deleted in the app, under Settings > Developer > Webhooks. A turned-off subscription is not turned back on: delete it and create a new one.";
|
|
1967
2056
|
server.registerTool("lighthouse_workspace_list_webhook_subscriptions", {
|
|
1968
2057
|
title: "List Webhook Subscriptions",
|
|
2058
|
+
annotations: READ_ONLY,
|
|
1969
2059
|
description: "List the webhook subscriptions of the user this key acts as (only their own), newest first, active and turned off alike: each one sends a signed POST to its url whenever a record of its record_type is created or updated. " +
|
|
1970
2060
|
WEBHOOK_SUBSCRIPTION_SHAPE + " " +
|
|
1971
2061
|
"WHEN TO USE: \"which webhooks do I have\", \"where are our company updates being sent\", \"why did our integration stop receiving changes\" (read status, disabled_reason and last_delivery). " +
|
|
@@ -1985,6 +2075,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
1985
2075
|
});
|
|
1986
2076
|
server.registerTool("lighthouse_workspace_get_webhook_subscription", {
|
|
1987
2077
|
title: "Get Webhook Subscription",
|
|
2078
|
+
annotations: READ_ONLY,
|
|
1988
2079
|
description: "Get one of the webhook subscriptions of the user this key acts as, by id. " +
|
|
1989
2080
|
WEBHOOK_SUBSCRIPTION_SHAPE + " " +
|
|
1990
2081
|
"WHEN TO USE: \"is that webhook still on\", \"when did it last deliver\", for one subscription whose id you already have (from lighthouse_workspace_list_webhook_subscriptions). " +
|
|
@@ -2003,6 +2094,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2003
2094
|
});
|
|
2004
2095
|
server.registerTool("lighthouse_discovery_list_attributes", {
|
|
2005
2096
|
title: "List Discovery Data Fields",
|
|
2097
|
+
annotations: READ_ONLY,
|
|
2006
2098
|
description: "Get a lightweight catalog of the DATA FIELDS discovery results carry for companies or people: each field's permalink, label, data_type, links_to, read_only, and has_options. " +
|
|
2007
2099
|
"These are OUTPUT/data fields, NOT filter keys. To FILTER a search you MUST use the prefixed company_* / person_* keys listed in the `filters` parameter of the discovery search tools. " +
|
|
2008
2100
|
"To keep the response small it does NOT include option values: when a field has has_options=true and you need its allowed values, call lighthouse_discovery_list_options with that permalink (or the dedicated lighthouse_discovery_list_industries / lighthouse_discovery_list_locations / lighthouse_discovery_list_funding_types / lighthouse_discovery_list_tags tools). " +
|
|
@@ -2029,6 +2121,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2029
2121
|
});
|
|
2030
2122
|
server.registerTool("lighthouse_discovery_list_options", {
|
|
2031
2123
|
title: "List Discovery Field Options",
|
|
2124
|
+
annotations: READ_ONLY,
|
|
2032
2125
|
description: "Get the allowed values (value, label, color) for a single discovery select / multi_select field. " +
|
|
2033
2126
|
"Call this when lighthouse_discovery_list_attributes shows has_options=true for a field and you need its valid values. " +
|
|
2034
2127
|
"Results are paginated (max 100 per page): use `search` to narrow by label, and `limit` / `offset` to page through a field with many values (e.g. locations). " +
|
|
@@ -2052,6 +2145,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2052
2145
|
});
|
|
2053
2146
|
server.registerTool("lighthouse_discovery_search_companies", {
|
|
2054
2147
|
title: "Search Discovery Companies",
|
|
2148
|
+
annotations: READ_ONLY,
|
|
2055
2149
|
description: "Search Lighthouse's global company database to DISCOVER new companies. " +
|
|
2056
2150
|
"This searches Lighthouse's enriched database (millions of companies), NOT your CRM. " +
|
|
2057
2151
|
"WORKFLOW: 1) Build filters using ONLY the company_* filter keys listed in the `filters` parameter below. " +
|
|
@@ -2082,6 +2176,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2082
2176
|
});
|
|
2083
2177
|
server.registerTool("lighthouse_discovery_search_people", {
|
|
2084
2178
|
title: "Search Discovery People",
|
|
2179
|
+
annotations: READ_ONLY,
|
|
2085
2180
|
description: "Search Lighthouse's global people database to DISCOVER new people. " +
|
|
2086
2181
|
"This searches Lighthouse's enriched database, NOT your CRM. " +
|
|
2087
2182
|
"WORKFLOW: 1) Build filters using ONLY the person_* filter keys listed in the `filters` parameter below. " +
|
|
@@ -2112,6 +2207,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2112
2207
|
});
|
|
2113
2208
|
server.registerTool("lighthouse_discovery_semantic_search_companies", {
|
|
2114
2209
|
title: "Semantic Search Companies",
|
|
2210
|
+
annotations: READ_ONLY,
|
|
2115
2211
|
description: "Find companies by MEANING, not exact keywords. Describe the kind of company you are looking for in plain language (what it does, who it serves, its sector, its business model) and Lighthouse returns the most semantically similar companies from its global database (NOT your CRM), ranked by relevance. " +
|
|
2116
2212
|
"USE THIS when the request is conceptual or hard to express as exact filters, e.g. 'companies like Stripe but for healthcare', 'startups building developer tools for AI agents', 'sustainable packaging for e-commerce'. " +
|
|
2117
2213
|
"You CAN combine it with structured filters: pass `filters` using the same company_* keys as lighthouse_discovery_search_companies and they apply together (semantic match AND your filters), e.g. semantically-similar companies that are ALSO headquartered in Germany. " +
|
|
@@ -2146,6 +2242,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2146
2242
|
});
|
|
2147
2243
|
server.registerTool("lighthouse_discovery_semantic_search_people", {
|
|
2148
2244
|
title: "Semantic Search People",
|
|
2245
|
+
annotations: READ_ONLY,
|
|
2149
2246
|
description: "Find people by MEANING, not exact keywords. Describe the ideal person in plain language (their role, skills, the industry they work in, their background) and Lighthouse returns the most semantically similar people from its global database (NOT your CRM), ranked by relevance. " +
|
|
2150
2247
|
"USE THIS when the request is conceptual or hard to express as exact filters, e.g. 'technical co-founders who worked on payments infrastructure', 'ex-FAANG ML engineers now in climate tech'. " +
|
|
2151
2248
|
"You CAN combine it with structured filters: pass `filters` using the same person_* keys as lighthouse_discovery_search_people and they apply together (semantic match AND your filters), e.g. semantically-similar people who are ALSO based in the UK. " +
|
|
@@ -2180,6 +2277,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2180
2277
|
});
|
|
2181
2278
|
server.registerTool("lighthouse_discovery_lookup_by_domain", {
|
|
2182
2279
|
title: "Lookup Companies by Domain",
|
|
2280
|
+
annotations: READ_ONLY,
|
|
2183
2281
|
description: "Look up companies in Lighthouse's global database by their website domain(s). " +
|
|
2184
2282
|
"Returns enriched company profiles for matching domains. Max 1000 domains per request. " +
|
|
2185
2283
|
"Each returned profile is condensed (the long growth and workforce history and the activity-signal " +
|
|
@@ -2203,6 +2301,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2203
2301
|
});
|
|
2204
2302
|
server.registerTool("lighthouse_discovery_lookup_by_linkedin", {
|
|
2205
2303
|
title: "Lookup Companies by LinkedIn",
|
|
2304
|
+
annotations: READ_ONLY,
|
|
2206
2305
|
description: "Look up companies in Lighthouse's global database by LinkedIn URL(s). " +
|
|
2207
2306
|
"Returns enriched company profiles for matching LinkedIn company pages. Max 1000 URLs per request. " +
|
|
2208
2307
|
"Each returned profile is condensed (the long growth and workforce history and the activity-signal " +
|
|
@@ -2226,6 +2325,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2226
2325
|
});
|
|
2227
2326
|
server.registerTool("lighthouse_discovery_lookup_people_by_linkedin", {
|
|
2228
2327
|
title: "Lookup People by LinkedIn",
|
|
2328
|
+
annotations: READ_ONLY,
|
|
2229
2329
|
description: "Look up people in Lighthouse's global database by LinkedIn profile URL(s). " +
|
|
2230
2330
|
"Returns enriched person profiles for matching LinkedIn pages. Max 1000 URLs per request. " +
|
|
2231
2331
|
"Each returned profile is condensed (the activity-signal feed is omitted and the associated-companies " +
|
|
@@ -2249,6 +2349,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2249
2349
|
});
|
|
2250
2350
|
server.registerTool("lighthouse_discovery_list_saved_searches", {
|
|
2251
2351
|
title: "List Saved Searches",
|
|
2352
|
+
annotations: READ_ONLY,
|
|
2252
2353
|
description: "List all saved discovery searches in the workspace. " +
|
|
2253
2354
|
"Returns saved searches you own and workspace-shared searches. " +
|
|
2254
2355
|
"Each search includes its filters and type (company or person). " +
|
|
@@ -2265,6 +2366,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2265
2366
|
});
|
|
2266
2367
|
server.registerTool("lighthouse_discovery_list_search_results", {
|
|
2267
2368
|
title: "List Saved Search Results",
|
|
2369
|
+
annotations: READ_ONLY,
|
|
2268
2370
|
description: "Run a saved discovery search and get its results. " +
|
|
2269
2371
|
"The search's saved filters are applied automatically. " +
|
|
2270
2372
|
"Optionally filter by signal creation date range. " +
|
|
@@ -2288,6 +2390,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2288
2390
|
});
|
|
2289
2391
|
server.registerTool("lighthouse_discovery_list_locations", {
|
|
2290
2392
|
title: "List Location Options",
|
|
2393
|
+
annotations: READ_ONLY,
|
|
2291
2394
|
description: "Get available location options (countries and regions) for discovery filters. " +
|
|
2292
2395
|
"ALWAYS call this to get valid country/region values before filtering by location fields " +
|
|
2293
2396
|
"(company_hq_country, company_hq_region, person_country, person_region, company_investor_country, etc.). " +
|
|
@@ -2308,6 +2411,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2308
2411
|
});
|
|
2309
2412
|
server.registerTool("lighthouse_discovery_list_industries", {
|
|
2310
2413
|
title: "List Industry & Category Options",
|
|
2414
|
+
annotations: READ_ONLY,
|
|
2311
2415
|
description: "Get available industry and category options for discovery filters. " +
|
|
2312
2416
|
"ALWAYS call this to get valid category values before filtering by company_categories, company_sdg_impact, " +
|
|
2313
2417
|
"person_current_categories, person_past_categories, person_current_sdg_impact, or person_past_sdg_impact, " +
|
|
@@ -2329,6 +2433,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2329
2433
|
});
|
|
2330
2434
|
server.registerTool("lighthouse_discovery_list_tags", {
|
|
2331
2435
|
title: "List Tag Options",
|
|
2436
|
+
annotations: READ_ONLY,
|
|
2332
2437
|
description: "Get the tag options for discovery filters. " +
|
|
2333
2438
|
"ALWAYS call this to get valid tag values before filtering by company_tags or person_tags. " +
|
|
2334
2439
|
"Returns {company_tags: [{value, name}], person_tags: [{value, name}]}. " +
|
|
@@ -2345,6 +2450,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2345
2450
|
});
|
|
2346
2451
|
server.registerTool("lighthouse_discovery_list_funding_types", {
|
|
2347
2452
|
title: "List Funding Type Options",
|
|
2453
|
+
annotations: READ_ONLY,
|
|
2348
2454
|
description: "Get the funding round types available for the funding discovery filters " +
|
|
2349
2455
|
"(company_last_funding_type, person_company_last_funding_type). " +
|
|
2350
2456
|
"Returns an array of { value, label, name } ordered by stage. " +
|
|
@@ -2363,6 +2469,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2363
2469
|
});
|
|
2364
2470
|
server.registerTool("lighthouse_discovery_search_investors", {
|
|
2365
2471
|
title: "Search Investors by Name",
|
|
2472
|
+
annotations: READ_ONLY,
|
|
2366
2473
|
description: "Search for investors by name to get their UUIDs for use in discovery filters. " +
|
|
2367
2474
|
"Use the returned 'id' values in company_investor filter conditions (investor_multi_select type). " +
|
|
2368
2475
|
"Returns [{id, name, avatar_url}]. Pass the id (or [{id, label}] objects) as the filter value. " +
|
|
@@ -2381,6 +2488,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2381
2488
|
});
|
|
2382
2489
|
server.registerTool("lighthouse_discovery_search_organizations", {
|
|
2383
2490
|
title: "Search Organizations by Name",
|
|
2491
|
+
annotations: READ_ONLY,
|
|
2384
2492
|
description: "Search for organizations (companies) by name to get their UUIDs for use in discovery filters. " +
|
|
2385
2493
|
"Use the returned 'id' values in work_experience_multi_select filter conditions " +
|
|
2386
2494
|
"(company_team_past_companies, person_past_companies). " +
|
|
@@ -2401,6 +2509,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2401
2509
|
});
|
|
2402
2510
|
server.registerTool("lighthouse_discovery_search_schools", {
|
|
2403
2511
|
title: "Search Schools by Name",
|
|
2512
|
+
annotations: READ_ONLY,
|
|
2404
2513
|
description: "Search for educational institutions by name to get their UUIDs for use in discovery filters. " +
|
|
2405
2514
|
"Use the returned 'id' values in education_multi_select filter conditions " +
|
|
2406
2515
|
"(company_team_education, person_education). " +
|
|
@@ -2420,6 +2529,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2420
2529
|
});
|
|
2421
2530
|
server.registerTool("lighthouse_discovery_search_entities", {
|
|
2422
2531
|
title: "Search Entities (companies, people, investors, schools)",
|
|
2532
|
+
annotations: READ_ONLY,
|
|
2423
2533
|
description: "Similarity-ranked, typo-tolerant search across companies, people, investors, and schools. " +
|
|
2424
2534
|
"Returns lightweight matches (id, record_type, name, avatar_url, is_discoverable, plus a few " +
|
|
2425
2535
|
"disambiguators like domain/employees/location) ranked by relevance then popularity (e.g. " +
|
|
@@ -2456,6 +2566,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2456
2566
|
});
|
|
2457
2567
|
server.registerTool("lighthouse_discovery_get_company", {
|
|
2458
2568
|
title: "Get Company Profile by ID",
|
|
2569
|
+
annotations: READ_ONLY,
|
|
2459
2570
|
description: "Returns the profile for a single company by id: firmographics, funding, categories, tags, links, " +
|
|
2460
2571
|
"and the first several associated people. " +
|
|
2461
2572
|
"To stay within response limits, this returns a condensed view: the long growth and workforce " +
|
|
@@ -2482,6 +2593,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2482
2593
|
});
|
|
2483
2594
|
server.registerTool("lighthouse_discovery_get_person", {
|
|
2484
2595
|
title: "Get Person Profile by ID",
|
|
2596
|
+
annotations: READ_ONLY,
|
|
2485
2597
|
description: "Returns the profile for a single person by id: bio, links, founder score, person tags, " +
|
|
2486
2598
|
"the first several associated companies / work history, and education. " +
|
|
2487
2599
|
"To stay within response limits, this returns a condensed view: the activity-signal feed is omitted, " +
|
|
@@ -2506,6 +2618,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2506
2618
|
});
|
|
2507
2619
|
server.registerTool("lighthouse_discovery_get_company_traction", {
|
|
2508
2620
|
title: "Get Company Traction History",
|
|
2621
|
+
annotations: READ_ONLY,
|
|
2509
2622
|
description: "The traction history of one company in the global database, by company id: the two blocks lighthouse_discovery_get_company omits to stay small. " +
|
|
2510
2623
|
"workforce_data is the month-by-month headcount and open-job-post history behind the traction charts (series of { date, value } points, oldest first where present); growth_metrics is a summary per tracked metric (headcount, job posts, web traffic, social following and similar, where recorded): { last_value, change_1m, change_3m, change_6m, change_1y, change_2y, change_3y, change_5y, date_last_value }. " +
|
|
2511
2624
|
"WHEN TO USE: \"what was their headcount in March\", \"is this company growing\", \"when did hiring pick up\", \"how has traffic moved this year\": anything about a value over time or a rate of change. The profile tool only carries the current headcount. " +
|
|
@@ -2524,6 +2637,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2524
2637
|
});
|
|
2525
2638
|
server.registerTool("lighthouse_discovery_get_company_signals", {
|
|
2526
2639
|
title: "Get Company Signals",
|
|
2640
|
+
annotations: READ_ONLY,
|
|
2527
2641
|
description: "The detected events for one company in the global database, by company id: the activity-signal feed lighthouse_discovery_get_company omits. " +
|
|
2528
2642
|
"signals is a list of { type, created_at, timeframe, percentage_change }: a funding round, a headcount or traffic jump, a hiring spike and similar, each with when it was detected and, for a change, over which timeframe and by how much. Sort by created_at yourself; the list is not ordered. " +
|
|
2529
2643
|
"WHEN TO USE: \"what happened at X recently\", \"did they raise\", \"is headcount spiking\", \"any momentum signals\". For the numbers behind a signal read lighthouse_discovery_get_company_traction. " +
|
|
@@ -2542,6 +2656,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2542
2656
|
});
|
|
2543
2657
|
server.registerTool("lighthouse_discovery_get_person_signals", {
|
|
2544
2658
|
title: "Get Person Signals",
|
|
2659
|
+
annotations: READ_ONLY,
|
|
2545
2660
|
description: "The detected events for one person in the global database, by person id: the signal feed lighthouse_discovery_get_person omits. " +
|
|
2546
2661
|
"signals is a list of { type, created_at, new_company, domain, org_id, predicted_areas }: a new company founded or registered, a stealth period, a departure and similar; org_id is a company id you can pass to lighthouse_discovery_get_company, domain its website. Sort by created_at yourself. " +
|
|
2547
2662
|
"predicted_areas is what Lighthouse predicts the founder is building, on a stealth or out_of_stealth signal: [{ permalink, label, origin }], permalink being a value of lighthouse_discovery_list_industries → stealth_predicted_areas and origin what the prediction was read from; it is [] on every other signal and when nothing was predicted. " +
|
|
@@ -2561,6 +2676,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2561
2676
|
});
|
|
2562
2677
|
server.registerTool("lighthouse_discovery_get_entity_by_name", {
|
|
2563
2678
|
title: "Get Entity Profile by Name",
|
|
2679
|
+
annotations: READ_ONLY,
|
|
2564
2680
|
description: "DEFAULT way to get the COMPLETE profile of ONE company or person when you have its name. " +
|
|
2565
2681
|
"Resolves the single best-ranked match and returns its full profile in one call (equivalent to " +
|
|
2566
2682
|
"lighthouse_discovery_search_entities followed by lighthouse_discovery_get_company / get_person, combined). " +
|
|
@@ -2607,6 +2723,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2607
2723
|
"ALWAYS call lighthouse_crm_list_attributes first to discover valid field keys for dimension, measure, and stage_field.";
|
|
2608
2724
|
server.registerTool("lighthouse_reports_list_dashboards", {
|
|
2609
2725
|
title: "List Dashboards",
|
|
2726
|
+
annotations: READ_ONLY,
|
|
2610
2727
|
description: "List all report dashboards accessible to the current user. " +
|
|
2611
2728
|
"Returns dashboards you own, workspace-shared dashboards, and team-shared dashboards. " +
|
|
2612
2729
|
"Each dashboard includes: id, name, description, sharing (private/workspace), is_owner, " +
|
|
@@ -2623,6 +2740,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2623
2740
|
});
|
|
2624
2741
|
server.registerTool("lighthouse_reports_create_dashboard", {
|
|
2625
2742
|
title: "Create Dashboard",
|
|
2743
|
+
annotations: CREATES,
|
|
2626
2744
|
description: "Create a new report dashboard. Dashboards contain report widgets (charts, tables, KPIs). " +
|
|
2627
2745
|
"Requires reports.create permission. Returns the full dashboard object (same format as get dashboard).",
|
|
2628
2746
|
inputSchema: {
|
|
@@ -2648,6 +2766,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2648
2766
|
});
|
|
2649
2767
|
server.registerTool("lighthouse_reports_get_dashboard", {
|
|
2650
2768
|
title: "Get Dashboard",
|
|
2769
|
+
annotations: READ_ONLY,
|
|
2651
2770
|
description: "Get a single dashboard by ID with all its widgets. " +
|
|
2652
2771
|
"Returns: id, name, description, sharing, is_owner, owner {id, first_name, last_name, full_name, avatar_url, email}, " +
|
|
2653
2772
|
"widget_count, shared_with_teams, widgets [{id, name, widget_type, config, created_at, updated_at}], created_at, updated_at.",
|
|
@@ -2665,6 +2784,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2665
2784
|
});
|
|
2666
2785
|
server.registerTool("lighthouse_reports_update_dashboard", {
|
|
2667
2786
|
title: "Update Dashboard",
|
|
2787
|
+
annotations: DESTRUCTIVE,
|
|
2668
2788
|
description: "Update a dashboard's name, description, or sharing. Only the dashboard owner can update. " +
|
|
2669
2789
|
"Returns the full dashboard object (same format as get dashboard), plus shared_along and audience_cannot_open. " +
|
|
2670
2790
|
SHARE_ALONG_RULE,
|
|
@@ -2690,6 +2810,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2690
2810
|
});
|
|
2691
2811
|
server.registerTool("lighthouse_reports_delete_dashboard", {
|
|
2692
2812
|
title: "Delete Dashboard",
|
|
2813
|
+
annotations: DESTRUCTIVE,
|
|
2693
2814
|
description: "Permanently delete a dashboard and all its widgets. Only the dashboard owner can delete.",
|
|
2694
2815
|
inputSchema: {
|
|
2695
2816
|
id: z.string().uuid().describe("Dashboard ID"),
|
|
@@ -2705,6 +2826,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2705
2826
|
});
|
|
2706
2827
|
server.registerTool("lighthouse_reports_list_widgets", {
|
|
2707
2828
|
title: "List Dashboard Widgets",
|
|
2829
|
+
annotations: READ_ONLY,
|
|
2708
2830
|
description: "Get all widgets for a dashboard. " +
|
|
2709
2831
|
"Each widget includes: id, name, widget_type, config, created_at, updated_at.",
|
|
2710
2832
|
inputSchema: {
|
|
@@ -2722,6 +2844,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2722
2844
|
});
|
|
2723
2845
|
server.registerTool("lighthouse_reports_create_widget", {
|
|
2724
2846
|
title: "Create Report Widget",
|
|
2847
|
+
annotations: CREATES,
|
|
2725
2848
|
description: "Create a report widget (chart/table/KPI) on a dashboard. " +
|
|
2726
2849
|
"WORKFLOW: 1) Call lighthouse_crm_list_attributes to discover field keys for the entity type, " +
|
|
2727
2850
|
"2) Build a config object using those keys, 3) Create the widget. " +
|
|
@@ -2750,6 +2873,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2750
2873
|
});
|
|
2751
2874
|
server.registerTool("lighthouse_reports_update_widget", {
|
|
2752
2875
|
title: "Rename Report Widget",
|
|
2876
|
+
annotations: DESTRUCTIVE,
|
|
2753
2877
|
description: "Rename a report widget. Only the name can be changed, a widget's type and " +
|
|
2754
2878
|
"configuration are fixed once created. To change what a widget reports, delete " +
|
|
2755
2879
|
"it and create a new one. " +
|
|
@@ -2771,6 +2895,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2771
2895
|
});
|
|
2772
2896
|
server.registerTool("lighthouse_reports_delete_widget", {
|
|
2773
2897
|
title: "Delete Report Widget",
|
|
2898
|
+
annotations: DESTRUCTIVE,
|
|
2774
2899
|
description: "Permanently delete a report widget from a dashboard.",
|
|
2775
2900
|
inputSchema: {
|
|
2776
2901
|
dashboard_id: z.string().uuid().describe("Dashboard ID"),
|
|
@@ -2787,6 +2912,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2787
2912
|
});
|
|
2788
2913
|
server.registerTool("lighthouse_reports_get_widget_data", {
|
|
2789
2914
|
title: "Get Widget Report Data",
|
|
2915
|
+
annotations: READ_ONLY,
|
|
2790
2916
|
description: "Fetch the aggregated report data for a specific widget. " +
|
|
2791
2917
|
"Returns the computed data based on the widget's config (dimensions, measures, segments, filters). " +
|
|
2792
2918
|
"For standard reports: array of {label, label_is_empty, value, segment?, segment_is_empty?} rows (label = the grouped dimension value, value = the aggregated measure, segment = present only when segment_by is set). " +
|
|
@@ -2808,6 +2934,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2808
2934
|
});
|
|
2809
2935
|
server.registerTool("lighthouse_reports_share_dashboard_with_teams", {
|
|
2810
2936
|
title: "Share Dashboard with Teams",
|
|
2937
|
+
annotations: ADDS_IF_MISSING,
|
|
2811
2938
|
description: "Grant team-level access to a dashboard. Only the dashboard owner can share. " +
|
|
2812
2939
|
"Use lighthouse_workspace_list_teams to find team IDs first. " +
|
|
2813
2940
|
"Requires reports.edit permission. " +
|
|
@@ -2832,6 +2959,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2832
2959
|
});
|
|
2833
2960
|
server.registerTool("lighthouse_reports_revoke_dashboard_from_teams", {
|
|
2834
2961
|
title: "Revoke Dashboard Team Access",
|
|
2962
|
+
annotations: DESTRUCTIVE,
|
|
2835
2963
|
description: "Revoke team-level access from a dashboard. Only the dashboard owner can revoke. " +
|
|
2836
2964
|
"Requires reports.edit permission.",
|
|
2837
2965
|
inputSchema: {
|
|
@@ -2850,6 +2978,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2850
2978
|
});
|
|
2851
2979
|
server.registerTool("lighthouse_crm_share_view_with_teams", {
|
|
2852
2980
|
title: "Share View with Teams",
|
|
2981
|
+
annotations: ADDS_IF_MISSING,
|
|
2853
2982
|
description: "Grant team-level access to a CRM view or list view. Only the view owner can share. " +
|
|
2854
2983
|
"Use lighthouse_workspace_list_teams to find team IDs first. " +
|
|
2855
2984
|
"Requires views.share permission. " +
|
|
@@ -2875,6 +3004,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2875
3004
|
});
|
|
2876
3005
|
server.registerTool("lighthouse_crm_revoke_view_from_teams", {
|
|
2877
3006
|
title: "Revoke View Team Access",
|
|
3007
|
+
annotations: DESTRUCTIVE,
|
|
2878
3008
|
description: "Revoke team-level access from a CRM view or list view. Only the view owner can revoke. " +
|
|
2879
3009
|
"Requires views.share permission. " +
|
|
2880
3010
|
"Returns formatted view: {id, name, record_type, list_id, view_type, filters, sort, grouped_by, sharing, owner, is_owner, shared_with_teams, created_at}, where is_owner says whether the API key's user owns the view.",
|
|
@@ -2894,6 +3024,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2894
3024
|
});
|
|
2895
3025
|
server.registerTool("lighthouse_crm_update_view_sharing", {
|
|
2896
3026
|
title: "Update View Sharing Status",
|
|
3027
|
+
annotations: DESTRUCTIVE,
|
|
2897
3028
|
description: "Change a CRM view or list view's sharing visibility. Setting to 'private' also removes all team permissions. " +
|
|
2898
3029
|
"Automatically detects view type. Only the view owner can change sharing. Requires views.share permission. " +
|
|
2899
3030
|
"Returns formatted view: {id, name, record_type, list_id, view_type, filters, sort, grouped_by, sharing, owner, is_owner, shared_with_teams, created_at, shared_along, audience_cannot_open}, where is_owner says whether the API key's user owns the view. " +
|
|
@@ -2916,8 +3047,189 @@ export function createServer(apiKey, options = {}) {
|
|
|
2916
3047
|
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2917
3048
|
}
|
|
2918
3049
|
});
|
|
3050
|
+
const SEQUENCE_PEOPLE_PER_CALL_MAX = 100;
|
|
3051
|
+
const SEQUENCE_FINISHED_ELSEWHERE_DAYS = 30;
|
|
3052
|
+
const SEQUENCE_END_REASONS = "finished (went through every step), replied (their reply ended the run, or they replied after its last step), colleague_replied (someone else from their company replied), wrote_first (they wrote in before this sequence contacted them; not a reply), accepted_invitation, already_connected, not_accepted (did not accept the LinkedIn invitation), stopped_manually, unsubscribed, bounced, failed or stopped";
|
|
3053
|
+
const SEQUENCE_PERSON_STATUS = "status is the person's own place in the sequence, never the sequence's: in_review (waiting in the sequence's Review for a member to add them), ready (added from Review, waiting to start), active, paused and account_disconnected (waiting for its sending account, or for a check of any reply sent while it was disconnected) are still going; completed, stopped, failed, bounced and unsubscribed have ended. " +
|
|
3054
|
+
"end_reason only says why a run ENDED: a reply the sequence's rules let it carry on through shows only in outcome, so for everyone who replied read outcome too.";
|
|
3055
|
+
const SEQUENCE_STOPPABLE_RUNS = "ready, active, paused, or waiting for an account that can send or for a check of their replies";
|
|
3056
|
+
const SEQUENCE_RESUME_CHECK_RULE = "once any check of the replies they sent while an account was disconnected is done; a check that could not be made is ended, so they go on without it";
|
|
3057
|
+
const SEQUENCE_MEMBER = "{ id, first_name, last_name, full_name, avatar_url, email }";
|
|
3058
|
+
const SEQUENCE_PERSON_KEYS = "id (this person's run in the sequence), crm_person { id, first_name, last_name, avatar_url } (the CRM person: its id is what lighthouse_sequences_add_people, lighthouse_sequences_update_people and lighthouse_sequences_remove_people take, and what lighthouse_crm_get_record reads), " +
|
|
3059
|
+
"email (the address the run sends to), company { id, name, avatar_url, is_deleted } (the company its messages name, null when none), status, step_status (what the current step is doing, null when nothing), current_step { number (from 1), type } (null before the first step), " +
|
|
3060
|
+
"outcome (how their reply reads: positive, negative, neutral or automated; null with no reply), outcome_source (ai or manual), stop_reason, stopped_by { id, first_name, last_name } (the person from their company whose reply stopped this run; id is that colleague's own run in this sequence, not a CRM person id), " +
|
|
3061
|
+
"end_reason (why the run ended: " + SEQUENCE_END_REASONS + "; null while it is still going), ended_by (the member who ended it by hand, " + SEQUENCE_MEMBER + "; null when the sequence or the person ended it), last_error_code, " +
|
|
3062
|
+
"added_via (manual, csv, list, find_leads, crm_action, api, workflow or assistant), started_at, ended_at, created_at";
|
|
3063
|
+
const SEQUENCE_NOT_FOUND_RULE = "A sequence the API key's user cannot open answers 'Sequence not found', the same as one that does not exist.";
|
|
3064
|
+
server.registerTool("lighthouse_sequences_list", {
|
|
3065
|
+
title: "List Sequences",
|
|
3066
|
+
annotations: READ_ONLY,
|
|
3067
|
+
description: "List the outbound sequences the API key's user can open (their own, and those shared with the workspace or with one of their teams), newest first. A sequence sends each person in it a series of steps (emails, LinkedIn connection requests and LinkedIn messages) from connected accounts. " +
|
|
3068
|
+
"Each row: id, name, status (draft, active or paused), sharing (private or workspace), shared_with_teams [{ team_id, team_name, granted_at }], is_owner (true when the API key's user owns it), has_unpublished_changes (the app holds an edit not published yet; what is read here is the published sequence, what runs), owner (the member who created it, " + SEQUENCE_MEMBER + "), step_count, stats, created_at and updated_at. " +
|
|
3069
|
+
"stats counts its people: people (everyone, ended runs included), in_review (waiting in the sequence's Review), not_started (added from Review, waiting to start), active, ended, errors, contacted (sent at least one message or connection request, or wrote back), responded (wrote back; an out-of-office or a delivery notice never counts) and positive_responses. The response rate is responded / contacted. " +
|
|
3070
|
+
"WHEN TO USE: \"which sequences do we have\", \"how is our outreach doing\", or to find a sequence someone names: pass query with part of its name and read the id off the matching row instead of paging through all of them. meta.total counts the sequences that matched. " +
|
|
3071
|
+
"For one sequence's steps, settings and sending accounts use lighthouse_sequences_get; for who is in it, lighthouse_sequences_list_people. Whether a given person, company or deal is in a sequence is lighthouse_crm_list_record_sequences (a person's record also carries a sequences field), never a walk through every sequence. " +
|
|
3072
|
+
"Creating a sequence, editing its steps or settings, sharing it and turning it on or off are done in the app: no tool does them.",
|
|
3073
|
+
inputSchema: {
|
|
3074
|
+
query: z.string().optional().describe("Part of the sequence's name, in any case"),
|
|
3075
|
+
search: z.string().optional().describe("Same as query. Either name works; query wins when both are sent."),
|
|
3076
|
+
status: z.enum(["draft", "active", "paused"]).optional().describe("Only the sequences with this status. All of them when omitted"),
|
|
3077
|
+
limit: z.number().min(1).max(100).default(25).describe("Sequences per page (max 100)"),
|
|
3078
|
+
offset: z.number().min(0).default(0).describe("Sequences to skip; meta.total and meta.has_more say whether more remain"),
|
|
3079
|
+
},
|
|
3080
|
+
}, async (params) => {
|
|
3081
|
+
try {
|
|
3082
|
+
const res = await api.get("/sequences", params);
|
|
3083
|
+
return text(res);
|
|
3084
|
+
}
|
|
3085
|
+
catch (e) {
|
|
3086
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3087
|
+
}
|
|
3088
|
+
});
|
|
3089
|
+
server.registerTool("lighthouse_sequences_get", {
|
|
3090
|
+
title: "Get Sequence",
|
|
3091
|
+
annotations: READ_ONLY,
|
|
3092
|
+
description: "One sequence in full, by id: every key of a lighthouse_sequences_list row plus settings, people_not_started (people added from Review and still waiting to start), steps and senders. The steps and settings are the PUBLISHED sequence, what runs; has_unpublished_changes says the app holds an edit not published yet. " +
|
|
3093
|
+
"steps, in order, each { id, number (from 1), type (email, linkedin_connection_request or linkedin_message), channel (email or linkedin), delay { amount, unit (hours, days or weeks) } (the wait before the step, counted from when a person reaches it; null when it runs right away), message_mode (shared: one message for everyone, per_person: a draft written for each person, or ai: each person's message written by AI when they are added), subject (email steps only), message (the shared text, or a connection request's note; null when none, and on a per_person or ai step), include_signature (null on a connection request), sender_id (the sending account this step always uses, null when the sequence chooses), if_accepted and if_not_accepted (a connection request's rules, null on other steps), people_at_step, errors_at_step, drafts_written }. " +
|
|
3094
|
+
"senders, the accounts it sends from, each { id (what a step's sender_id points at), channel, account { id, name, email, is_owner }, owner (the member, " + SEQUENCE_MEMBER + "), is_connected, can_send (false: the people it would send to wait), daily_limit (this sequence's own, null when the account's applies), account_daily_limit, account_sent_today, sequence_sent_today }. " +
|
|
3095
|
+
"settings: schedule { days (ISO weekdays, 1 = Monday), window_start, window_end (HH:MM), timezone }, enrollment { daily_starts, prioritise_new_people }, senders { email, linkedin: { mode (rotate or fixed), fixed_sender_id } }, email_min_gap_minutes, linkedin_min_gap_minutes, stop_on_bounce, include_unsubscribe_link, unsubscribe_link_text and step_defaults (the rules every step follows); a value that is null was never changed, so the product default applies. " +
|
|
3096
|
+
"WHEN TO USE: what a sequence does (its steps in order, the wait before each, its messages, when it sends and from which accounts), or why its people are waiting (a sender that cannot send). For who is in it use lighthouse_sequences_list_people. " + SEQUENCE_NOT_FOUND_RULE,
|
|
3097
|
+
inputSchema: {
|
|
3098
|
+
id: z.string().uuid().describe("Sequence ID, from lighthouse_sequences_list or the sequence of a lighthouse_crm_list_record_sequences row"),
|
|
3099
|
+
},
|
|
3100
|
+
}, async (params) => {
|
|
3101
|
+
try {
|
|
3102
|
+
const res = await api.get(`/sequences/${params.id}`);
|
|
3103
|
+
return text(res);
|
|
3104
|
+
}
|
|
3105
|
+
catch (e) {
|
|
3106
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
server.registerTool("lighthouse_sequences_list_people", {
|
|
3110
|
+
title: "List Sequence People",
|
|
3111
|
+
annotations: READ_ONLY,
|
|
3112
|
+
description: "The people in ONE sequence, newest added first, one page at a time. Each row: " + SEQUENCE_PERSON_KEYS + ", linkedin (the LinkedIn handle the run uses) and sender (the member currently assigned to send to this person, null until one is picked and once that member has left the workspace). " +
|
|
3113
|
+
SEQUENCE_PERSON_STATUS + " " +
|
|
3114
|
+
"meta.counts { running, ended, by_reason { <end_reason>: n } } follows query but never state or end_reason, so one call says how many there are of each. " +
|
|
3115
|
+
"WHEN TO USE: \"who is in this sequence\", \"who is still going\" (state running), \"who replied\" (end_reason replied, then outcome on the others), \"who bounced\", or finding one person in it by name or address (query). " +
|
|
3116
|
+
"Whether one person, company or deal is in any sequence is lighthouse_crm_list_record_sequences. Needs a role that may view both sequences and people. " + SEQUENCE_NOT_FOUND_RULE,
|
|
3117
|
+
inputSchema: {
|
|
3118
|
+
sequence_id: z.string().uuid().describe("Sequence ID, from lighthouse_sequences_list"),
|
|
3119
|
+
query: z.string().optional().describe("Part of the person's name or of the address the sequence sends to, in any case"),
|
|
3120
|
+
search: z.string().optional().describe("Same as query. Either name works; query wins when both are sent."),
|
|
3121
|
+
state: z.enum(["running", "ended"]).optional().describe("running: their run is still going (Review included); ended: it is over. Both when omitted"),
|
|
3122
|
+
end_reason: z.string().optional().describe("Only the people whose run ended this way (implies ended, so state must be ended or left out): " + SEQUENCE_END_REASONS),
|
|
3123
|
+
limit: z.number().min(1).max(100).default(25).describe("People per page (max 100)"),
|
|
3124
|
+
offset: z.number().min(0).default(0).describe("People to skip; meta.total and meta.has_more say whether more remain"),
|
|
3125
|
+
},
|
|
3126
|
+
}, async (params) => {
|
|
3127
|
+
try {
|
|
3128
|
+
const { sequence_id, ...rest } = params;
|
|
3129
|
+
const res = await api.get(`/sequences/${sequence_id}/people`, rest);
|
|
3130
|
+
return text(res);
|
|
3131
|
+
}
|
|
3132
|
+
catch (e) {
|
|
3133
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
server.registerTool("lighthouse_sequences_add_people", {
|
|
3137
|
+
title: "Add People to Sequence",
|
|
3138
|
+
annotations: ADDS_IF_MISSING,
|
|
3139
|
+
description: `Add CRM PEOPLE to a sequence, up to ${SEQUENCE_PEOPLE_PER_CALL_MAX} per call: crm_person_ids are person record ids, never companies, deals or lists. ` +
|
|
3140
|
+
"Everyone added lands in the sequence's Review, exactly like an add made in the app, and NOTHING IS SENT until a member opens the sequence in the app and adds them from Review; no tool starts anyone right away, so tell the user the people are waiting in Review, not contacted. " +
|
|
3141
|
+
"When the sequence has a step written by AI (message_mode ai), each person added has their message on it written straight away, while they wait in Review. That uses the workspace's AI credits, shown as Sequence messages on the Credits page, unless the step writes on one of the workspace's own models; a step with no model chosen writes on the workspace's default model when it has one, which uses none. " +
|
|
3142
|
+
"Answers { added, counts, results, other_sequences, other_sequences_hidden }: results has one { crm_person_id, result } per id sent, in order (a repeated id is answered once), and counts carries every result word, 0 when none. " +
|
|
3143
|
+
"result is added (now in the sequence's Review), already_in_sequence (a person is in a sequence at most once, ever: an ended run is not restarted), in_other_sequence (still going or in Review in another sequence; send include_in_other_sequences true to add them anyway), duplicate_email or duplicate_linkedin (someone else in this sequence is reached at the same address or LinkedIn profile), unsubscribed (opted out, or their address bounced and the sequence has no LinkedIn step), finished_other_sequence (only with skip_finished_elsewhere) or crm_person_not_found (no person with that id in the workspace: a company or deal id, or a deleted person). " +
|
|
3144
|
+
"other_sequences names the sequences ({ id, name }) holding the people skipped as in_other_sequence that the API key's user can open; other_sequences_hidden counts the others. " +
|
|
3145
|
+
"WHEN TO USE: the user asks to put people into a sequence (\"add these founders to our outreach\"). Find the sequence with lighthouse_sequences_list and the people with lighthouse_crm_search_records first. Adding the same people again changes nothing (already_in_sequence). Needs a role that may edit sequences and view people. " + SEQUENCE_NOT_FOUND_RULE,
|
|
3146
|
+
inputSchema: {
|
|
3147
|
+
sequence_id: z.string().uuid().describe("Sequence ID, from lighthouse_sequences_list"),
|
|
3148
|
+
crm_person_ids: z.array(z.string().uuid()).min(1).max(SEQUENCE_PEOPLE_PER_CALL_MAX).describe(`CRM person record IDs to add (max ${SEQUENCE_PEOPLE_PER_CALL_MAX}). People only: a company or deal id comes back as crm_person_not_found`),
|
|
3149
|
+
include_in_other_sequences: z.boolean().optional().describe("true also adds the people still going or waiting in Review in another sequence. Default false: they are skipped as in_other_sequence"),
|
|
3150
|
+
skip_finished_elsewhere: z.boolean().optional().describe(`true leaves out the people who were contacted in another sequence and whose run there ended in the last ${SEQUENCE_FINISHED_ELSEWHERE_DAYS} days (result finished_other_sequence); with include_in_other_sequences also true, someone still live in another sequence is added all the same. Default false`),
|
|
3151
|
+
},
|
|
3152
|
+
}, async (params) => {
|
|
3153
|
+
try {
|
|
3154
|
+
const { sequence_id, ...body } = params;
|
|
3155
|
+
const res = await api.post(`/sequences/${sequence_id}/people`, body);
|
|
3156
|
+
return text(res);
|
|
3157
|
+
}
|
|
3158
|
+
catch (e) {
|
|
3159
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3160
|
+
}
|
|
3161
|
+
});
|
|
3162
|
+
server.registerTool("lighthouse_sequences_update_people", {
|
|
3163
|
+
title: "Update People in Sequence",
|
|
3164
|
+
annotations: DESTRUCTIVE,
|
|
3165
|
+
description: `Stop, pause or resume CRM people in a sequence, up to ${SEQUENCE_PEOPLE_PER_CALL_MAX} per call, by their CRM person ids (a person is in a sequence at most once, so the id names their run). ` +
|
|
3166
|
+
"stop: a run still going (" + SEQUENCE_STOPPABLE_RUNS + ") ends, stopped by the API key's user, and keeps its history. It ends THIS sequence only: the person's runs in other sequences go on. pause: a run that is ready or active holds where it is. resume: a paused run goes on " + SEQUENCE_RESUME_CHECK_RULE + ". " +
|
|
3167
|
+
"Answers { action, changed, results }: results has one { crm_person_id, result, status } per id sent, in order; result is stopped, paused or resumed when their run changed, not_in_sequence when they have no run in it, or not_applicable when their run does not take the action (a person still in Review stopped, an ended run paused, a run that is not paused resumed); status is their run's status now (null when they have none); changed counts the runs that changed. " +
|
|
3168
|
+
"WHEN TO USE: \"stop the sequence for Jane\", \"pause everyone at Acme\", \"resume the people I paused\". To take someone out entirely, history included, or out of Review before anyone is contacted, use lighthouse_sequences_remove_people. Needs a role that may edit sequences and view people. " + SEQUENCE_NOT_FOUND_RULE,
|
|
3169
|
+
inputSchema: {
|
|
3170
|
+
sequence_id: z.string().uuid().describe("Sequence ID, from lighthouse_sequences_list"),
|
|
3171
|
+
crm_person_ids: z.array(z.string().uuid()).min(1).max(SEQUENCE_PEOPLE_PER_CALL_MAX).describe(`CRM person record IDs (max ${SEQUENCE_PEOPLE_PER_CALL_MAX})`),
|
|
3172
|
+
action: z.enum(["stop", "pause", "resume"]).describe("stop ends their run in this sequence only (its history kept), pause holds it, resume continues a paused run " + SEQUENCE_RESUME_CHECK_RULE),
|
|
3173
|
+
},
|
|
3174
|
+
}, async (params) => {
|
|
3175
|
+
try {
|
|
3176
|
+
const { sequence_id, ...body } = params;
|
|
3177
|
+
const res = await api.patch(`/sequences/${sequence_id}/people`, body);
|
|
3178
|
+
return text(res);
|
|
3179
|
+
}
|
|
3180
|
+
catch (e) {
|
|
3181
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3182
|
+
}
|
|
3183
|
+
});
|
|
3184
|
+
server.registerTool("lighthouse_sequences_remove_people", {
|
|
3185
|
+
title: "Remove People from Sequence",
|
|
3186
|
+
annotations: DESTRUCTIVE,
|
|
3187
|
+
description: `Remove CRM people from a sequence, up to ${SEQUENCE_PEOPLE_PER_CALL_MAX} per call, by their CRM person ids: their run in it is DELETED together with its history, at any stage, Review included, and cannot be brought back. ` +
|
|
3188
|
+
"To end a run and keep its history, use lighthouse_sequences_update_people with action stop instead. " +
|
|
3189
|
+
"Answers { deleted: true, removed, results }: results has one { crm_person_id, result } per id sent, in order, result being removed or not_in_sequence; removed counts the runs deleted. " +
|
|
3190
|
+
"WHEN TO USE: the user asks to take someone out of a sequence altogether, to clear people out of its Review before anyone is contacted, or to undo an add. Needs a role that may edit sequences and view people. " + SEQUENCE_NOT_FOUND_RULE,
|
|
3191
|
+
inputSchema: {
|
|
3192
|
+
sequence_id: z.string().uuid().describe("Sequence ID, from lighthouse_sequences_list"),
|
|
3193
|
+
crm_person_ids: z.array(z.string().uuid()).min(1).max(SEQUENCE_PEOPLE_PER_CALL_MAX).describe(`CRM person record IDs to remove (max ${SEQUENCE_PEOPLE_PER_CALL_MAX})`),
|
|
3194
|
+
},
|
|
3195
|
+
}, async (params) => {
|
|
3196
|
+
try {
|
|
3197
|
+
const { sequence_id, ...body } = params;
|
|
3198
|
+
const res = await api.delete(`/sequences/${sequence_id}/people`, body);
|
|
3199
|
+
return text(res);
|
|
3200
|
+
}
|
|
3201
|
+
catch (e) {
|
|
3202
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3203
|
+
}
|
|
3204
|
+
});
|
|
3205
|
+
server.registerTool("lighthouse_crm_list_record_sequences", {
|
|
3206
|
+
title: "List Record Sequences",
|
|
3207
|
+
annotations: READ_ONLY,
|
|
3208
|
+
description: "The sequence runs ONE CRM person, company or deal reaches (the record's Sequences tab), runs still going first, then the latest activity: a person's own runs; for a company, the runs that name it as the person's company and the runs of the people on its current team; for a deal, its people's runs and the runs of its companies. A custom object reaches no sequence. " +
|
|
3209
|
+
"Each run: " + SEQUENCE_PERSON_KEYS + ", sequence { id, name, status }, step_count, reached_through (how the record reaches the run: any of person, company, team, deal and deal_company), at_company_now (on a company: whether the person is still on its current team; null on a person or a deal), last_sent_at (the last message or invitation that went out), last_reply_at (their last reply; an out-of-office or a delivery notice never counts) and added_by (the member who added them). Who and when only, never a message. " +
|
|
3210
|
+
SEQUENCE_PERSON_STATUS + " " +
|
|
3211
|
+
"meta.counts { live, ended } counts both groups whatever group asks. " +
|
|
3212
|
+
"WHEN TO USE: \"which sequences is Jane in\", \"is anyone at Acme being contacted\", \"when did we last write to them\", \"who added them\". For a quick yes or no on one person, the sequences field of lighthouse_crm_get_record already lists theirs; for a SET of records (everyone in a sequence, the companies whose team is in one) use one lighthouse_crm_search_records call with a sequences condition (associated_people.sequences on companies and deals) rather than reading records one by one. " +
|
|
3213
|
+
"Only the sequences the API key's user can open are read: a run in any other sequence is left out and not counted, so an empty answer means none they can see. A person can have more than one CRM record: check each. Needs a role that may view sequences, people and the record's type. A record that does not exist answers 'Record not found'.",
|
|
3214
|
+
inputSchema: {
|
|
3215
|
+
record_type: z.enum(["person", "company", "deal"]).describe("person, company or deal (a custom object reaches no sequence)"),
|
|
3216
|
+
record_id: z.string().uuid().describe("The record's ID"),
|
|
3217
|
+
group: z.enum(["live", "ended"]).optional().describe("live: still going (Review included); ended: over. Both when omitted"),
|
|
3218
|
+
limit: z.number().min(1).max(100).default(25).describe("Runs per page (max 100)"),
|
|
3219
|
+
offset: z.number().min(0).default(0).describe("Runs to skip; meta.total and meta.has_more say whether more remain"),
|
|
3220
|
+
},
|
|
3221
|
+
}, async ({ record_type, record_id, group, limit, offset }) => {
|
|
3222
|
+
try {
|
|
3223
|
+
const res = await api.get(`/records/${record_type}/${record_id}/sequences`, { group, limit, offset });
|
|
3224
|
+
return text(res);
|
|
3225
|
+
}
|
|
3226
|
+
catch (e) {
|
|
3227
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3228
|
+
}
|
|
3229
|
+
});
|
|
2919
3230
|
server.registerTool("lighthouse_inbox_email_accounts", {
|
|
2920
3231
|
title: "List Email Accounts",
|
|
3232
|
+
annotations: READ_ONLY,
|
|
2921
3233
|
description: "List the email accounts the user can access: their own connected mailbox plus mailboxes other workspace " +
|
|
2922
3234
|
"members shared with them. One account object on all three channels: { id, name (the owner's display name), " +
|
|
2923
3235
|
"email (the mailbox address), is_owner (true when the account belongs to the API key's user, false for a colleague's), " +
|
|
@@ -2943,6 +3255,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2943
3255
|
});
|
|
2944
3256
|
server.registerTool("lighthouse_inbox_linkedin_accounts", {
|
|
2945
3257
|
title: "List LinkedIn Accounts",
|
|
3258
|
+
annotations: READ_ONLY,
|
|
2946
3259
|
description: "List the LinkedIn messaging accounts the user can access: their own connected account plus accounts other " +
|
|
2947
3260
|
"workspace members shared with them. The same account object the email and calendar channels return: " +
|
|
2948
3261
|
"{ id, name (the owner's display name), email (always null on LinkedIn), is_owner (true when the account belongs " +
|
|
@@ -2967,6 +3280,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
2967
3280
|
});
|
|
2968
3281
|
server.registerTool("lighthouse_inbox_email_threads", {
|
|
2969
3282
|
title: "List Email Threads",
|
|
3283
|
+
annotations: READ_ONLY,
|
|
2970
3284
|
description: "List email threads (conversations) across every email account the user can read, newest activity first. Filters are " +
|
|
2971
3285
|
"all optional and combinable: record_id + record_type ('company' or 'person' ONLY) for correspondence with " +
|
|
2972
3286
|
"a CRM record (person = their email addresses, company = its domains); emails / domains (comma separated lists); " +
|
|
@@ -3007,6 +3321,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3007
3321
|
});
|
|
3008
3322
|
server.registerTool("lighthouse_inbox_email_thread", {
|
|
3009
3323
|
title: "Get Email Thread",
|
|
3324
|
+
annotations: READ_ONLY,
|
|
3010
3325
|
description: "Read one email thread: data.thread (the list's row) plus data.messages, newest first by default. Each message: " +
|
|
3011
3326
|
"{ id, thread_id, date, direction (sent | received), from { name, email }, to / cc / bcc / reply_to [{ name, email }], " +
|
|
3012
3327
|
"subject, snippet, text (the plain words of THIS message, with the quoted history of a reply removed and a forwarded block kept), " +
|
|
@@ -3046,6 +3361,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3046
3361
|
});
|
|
3047
3362
|
server.registerTool("lighthouse_inbox_email_attachment", {
|
|
3048
3363
|
title: "Read Email Attachment",
|
|
3364
|
+
annotations: READ_ONLY,
|
|
3049
3365
|
description: "What one email attachment SAYS, as text: PDFs, Word, Excel and PowerPoint files (and their OpenDocument versions), text, CSV and HTML. " +
|
|
3050
3366
|
"WHEN TO USE: the user asks what an attached file contains (\"summarise the report Jane sent\", \"what are the numbers in the attached model\", \"check the attachment on that email\"). " +
|
|
3051
3367
|
"Find the message first: lighthouse_inbox_email_threads (a subject, a sender, a date), then lighthouse_inbox_email_thread, whose messages carry attachments [{ id, message_id, filename, ... }] and account { id }. Pass that attachment's id as attachment_id, its message_id, and the message's account.id as account_id. " +
|
|
@@ -3078,6 +3394,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3078
3394
|
});
|
|
3079
3395
|
server.registerTool("lighthouse_inbox_email_create_draft", {
|
|
3080
3396
|
title: "Create Email Draft",
|
|
3397
|
+
annotations: CREATES,
|
|
3081
3398
|
description: "Save an email draft in one of the user's mailboxes. The draft is NOT sent: it lands in the mailbox for the " +
|
|
3082
3399
|
"user to review, edit and send themselves, and there is no tool that can send it. Use this to prepare a reply " +
|
|
3083
3400
|
"or a new email on the user's behalf. account_id is REQUIRED (from lighthouse_inbox_email_accounts) and must " +
|
|
@@ -3113,6 +3430,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3113
3430
|
});
|
|
3114
3431
|
server.registerTool("lighthouse_inbox_linkedin_chats", {
|
|
3115
3432
|
title: "List LinkedIn Chats",
|
|
3433
|
+
annotations: READ_ONLY,
|
|
3116
3434
|
description: "List LinkedIn conversations across every LinkedIn account the user can view, newest activity first, " +
|
|
3117
3435
|
"paginated. Filters are all optional and combinable: record_id + record_type ('company' or 'person' ONLY, where a " +
|
|
3118
3436
|
"company matches its team members and company page), person_linkedin / company_linkedin (LinkedIn URL or " +
|
|
@@ -3154,6 +3472,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3154
3472
|
});
|
|
3155
3473
|
server.registerTool("lighthouse_inbox_linkedin_chat_messages", {
|
|
3156
3474
|
title: "Get LinkedIn Chat Messages",
|
|
3475
|
+
annotations: READ_ONLY,
|
|
3157
3476
|
description: "Read one LinkedIn conversation: returns the chat object (the same object lighthouse_inbox_linkedin_chats " +
|
|
3158
3477
|
"returns) plus its messages, paginated, newest first by default (order 'asc' for oldest first), both sides " +
|
|
3159
3478
|
"or one (direction 'sent' = what the account sent, 'received' = what the other people sent), with optional " +
|
|
@@ -3196,6 +3515,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3196
3515
|
});
|
|
3197
3516
|
server.registerTool("lighthouse_inbox_calendar_accounts", {
|
|
3198
3517
|
title: "List Calendar Accounts",
|
|
3518
|
+
annotations: READ_ONLY,
|
|
3199
3519
|
description: "Calendars the API key's user can read: their own connected calendar (is_owner true) and colleagues' calendars shared with them. " +
|
|
3200
3520
|
"The same account object the email and LinkedIn channels return: { id, name, email, is_owner, provider (google | microsoft | other), " +
|
|
3201
3521
|
"status (connected | pending | disconnected), status_reason (credentials | error | stopped | other; null unless disconnected), " +
|
|
@@ -3218,6 +3538,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3218
3538
|
});
|
|
3219
3539
|
server.registerTool("lighthouse_inbox_calendar_meetings", {
|
|
3220
3540
|
title: "List Meetings",
|
|
3541
|
+
annotations: READ_ONLY,
|
|
3221
3542
|
description: "Calendar meetings across every calendar the API key's user can read (their own, plus colleagues' calendars shared with them), read live from the calendar. PAST meetings newest first by default (the last 6 months; pass months up to 36, or after / before, to move the window), or the SCHEDULED ones soonest first with upcoming=true (90 days ahead by default; an explicit before reaches further). " +
|
|
3222
3543
|
"Narrow by CRM record (record_id + record_type, company or person: the record's addresses and domains are matched), by emails / domains lists (comma separated), by words in the meeting TITLE (query; never the notes), or by one calendar (account_id from lighthouse_inbox_calendar_accounts). No filter lists the user's whole calendar over the window. " +
|
|
3223
3544
|
"WHEN TO USE: \"when did we last meet X\", \"what meetings do I have next week\", \"have we talked to anyone at acme.com\", \"which calls with this company were recorded\". To find what was DISCUSSED about a topic, list the meetings and read the notes of the likely ones. " +
|
|
@@ -3251,6 +3572,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3251
3572
|
});
|
|
3252
3573
|
server.registerTool("lighthouse_inbox_calendar_meeting", {
|
|
3253
3574
|
title: "Get Recorded Meeting",
|
|
3575
|
+
annotations: READ_ONLY,
|
|
3254
3576
|
description: "One recorded meeting's metadata by meeting_key (" + MEETING_KEY_SOURCES + "): { meeting_key, ical_uid, title, occurred_at, meeting_url (the video link), recording { has_recording, has_transcript, has_notes, duration_seconds, language, recorded_by } (the same object the meetings list carries; recorded_by is the recorder as a workspace member, or null), notes { id, status, created_at }: the note lighthouse_inbox_calendar_meeting_notes returns, or null when there is none the user may read }. The title and the time are the same here as on the meetings list and inside the notes. " +
|
|
3255
3577
|
"WHEN TO USE: to check whether a transcript or notes exist before reading them, or to answer how long a call lasted. It never returns the transcript or the notes themselves, and the API never returns the recording itself. Not found when the meeting was not recorded or the API key's user may not read it. A recurring meeting keeps the recording of its most recently recorded occurrence. " + MEETING_ACCESS_RULE,
|
|
3256
3578
|
inputSchema: {
|
|
@@ -3267,7 +3589,8 @@ export function createServer(apiKey, options = {}) {
|
|
|
3267
3589
|
});
|
|
3268
3590
|
server.registerTool("lighthouse_inbox_calendar_meeting_notes", {
|
|
3269
3591
|
title: "Get Meeting Notes",
|
|
3270
|
-
|
|
3592
|
+
annotations: READ_ONLY,
|
|
3593
|
+
description: "The meeting notes written from one recorded meeting, by meeting_key (" + MEETING_KEY_SOURCES + "): the note in the same shape as lighthouse_crm_get_note (type 'meeting', with the meeting object and the records it is linked to that you can view), plus content_markdown (what was generated: Meeting Context first, then the sections, then Next steps last) and is_edited (true when a member changed the note since, in which case content is the version to trust). " +
|
|
3271
3594
|
"WHEN TO USE: the user asks what was discussed, decided or agreed in a specific meeting, the next steps from a call, or who attended. Lighter than the transcript: prefer it unless the answer depends on exactly what was said. " +
|
|
3272
3595
|
"Not found when the meeting has no notes yet (see has_notes on the meeting) or the API key's user may not read them; for a recurring meeting the newest occurrence the user may read is returned. " + MEETING_ACCESS_RULE,
|
|
3273
3596
|
inputSchema: {
|
|
@@ -3284,6 +3607,7 @@ export function createServer(apiKey, options = {}) {
|
|
|
3284
3607
|
});
|
|
3285
3608
|
server.registerTool("lighthouse_inbox_calendar_meeting_transcript", {
|
|
3286
3609
|
title: "Get Meeting Transcript",
|
|
3610
|
+
annotations: READ_ONLY,
|
|
3287
3611
|
description: "The transcript of one recorded meeting by meeting_key (" + MEETING_KEY_SOURCES + "), as text: one \"Speaker: text\" line per utterance, in order. A note's meeting_key opens this transcript when that note's meeting.has_transcript is true. " +
|
|
3288
3612
|
"WHEN TO USE: only when the answer depends on what was actually SAID (an exact quote, a number, who said what, a nuance the notes would flatten). For what was discussed or decided, lighthouse_inbox_calendar_meeting_notes is enough and far smaller. " +
|
|
3289
3613
|
`A long meeting comes back in windows of max_chars (default ${TRANSCRIPT_WINDOW_CHARS}): meta.has_more true means the meeting continues past this window, so pass meta.next_offset_chars as offset_chars to read on, and never treat a window with has_more as the end of the meeting. ` +
|
|
@@ -3302,68 +3626,5 @@ export function createServer(apiKey, options = {}) {
|
|
|
3302
3626
|
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3303
3627
|
}
|
|
3304
3628
|
});
|
|
3305
|
-
if (options.privateEquity !== true)
|
|
3306
|
-
return server;
|
|
3307
|
-
server.registerTool("lighthouse_private_equity_list_saved_searches", {
|
|
3308
|
-
title: "List Private Equity Saved Searches",
|
|
3309
|
-
description: "List the Private Equity saved searches the user can see in this workspace (their own, the ones shared with the workspace, and the ones shared with one of their teams), ordered by name. " +
|
|
3310
|
-
"Each search carries id, name, sharing (private | workspace), shared_with_teams, is_owner, owner, created_at, updated_at, last_run_at and last_run: the stored run its results come from ({ id, created_at, last_fetched_at, result_count, match_count, created_by }, where result_count is how many companies are stored and match_count how many the search matched in the company database, or null), or null when it never ran. The search's filters are not returned. " +
|
|
3311
|
-
"READ ONLY: this reads what the workspace already stored. It never runs a search, never fetches new companies and never spends credits; a Private Equity search is run, and paid for, only in the Lighthouse app. " +
|
|
3312
|
-
"WHEN TO USE: the user asks which Private Equity searches exist, or names one whose results they want; pass its id to lighthouse_private_equity_list_saved_search_results. " +
|
|
3313
|
-
"These are separate from the Discovery saved searches (lighthouse_discovery_list_saved_searches), which search a different database.",
|
|
3314
|
-
inputSchema: {
|
|
3315
|
-
offset: z.number().int().min(0).optional().describe("Number of saved searches to skip (default 0)"),
|
|
3316
|
-
limit: z.number().int().min(1).max(100).optional().describe("Saved searches per page, 1 to 100 (default 25)"),
|
|
3317
|
-
},
|
|
3318
|
-
}, async ({ offset, limit }) => {
|
|
3319
|
-
try {
|
|
3320
|
-
const res = await api.get("/private-equity/saved-searches", { offset, limit });
|
|
3321
|
-
return text(res);
|
|
3322
|
-
}
|
|
3323
|
-
catch (e) {
|
|
3324
|
-
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3325
|
-
}
|
|
3326
|
-
});
|
|
3327
|
-
server.registerTool("lighthouse_private_equity_list_saved_search_results", {
|
|
3328
|
-
title: "List Private Equity Saved Search Results",
|
|
3329
|
-
description: "Read one page of the companies a Private Equity saved search's LAST run stored, in the order that run returned them. " +
|
|
3330
|
-
"Each row is a stored company: id, name, domain, website_url, linkedin_url, short_description, avatar_url, hq ({ country, country_code, region, city }) and hq_label, industry, category, sector, naics ({ code, label }), business_models, employees and revenue_usd ({ value, estimated }), founded_year, company_type, num_locations, crm_id (the id of this workspace's CRM company for it, or null), lighthouse_company (the matching company in Lighthouse's own data, or null), from_lighthouse (the keys whose value was filled from Lighthouse's own data), fetched_at, profile_updated_at, and match ({ snippets, headline }: why a keyword or product search matched it, or null). " +
|
|
3331
|
-
"meta carries total, limit, offset, has_more, has_results and run (the stored run the page is read from). A search that never ran, or whose last run is no longer stored, answers an empty page with meta.has_results false. " +
|
|
3332
|
-
"READ ONLY: the rows are exactly what the search stored when someone last ran it in the app, even if its filters were edited since. This never runs the search again, never fetches more companies and never spends credits; for newer results, ask the user to run the search in the Lighthouse app. " +
|
|
3333
|
-
"WHEN TO USE: the user asks what a Private Equity search found. Page with offset while meta.has_more is true. For one company's full stored profile use lighthouse_private_equity_get_company; when crm_id is set, lighthouse_crm_get_record (record_type 'company') opens the CRM company. " +
|
|
3334
|
-
"Not found when the saved search does not exist or the user cannot see it.",
|
|
3335
|
-
inputSchema: {
|
|
3336
|
-
search_id: z.string().uuid().describe("Private Equity saved search ID (from lighthouse_private_equity_list_saved_searches)"),
|
|
3337
|
-
offset: z.number().int().min(0).optional().describe("Number of companies to skip (default 0)"),
|
|
3338
|
-
limit: z.number().int().min(1).max(100).optional().describe("Companies per page, 1 to 100 (default 25)"),
|
|
3339
|
-
},
|
|
3340
|
-
}, async ({ search_id, offset, limit }) => {
|
|
3341
|
-
try {
|
|
3342
|
-
const res = await api.get(`/private-equity/saved-searches/${encodeURIComponent(search_id)}/results`, { offset, limit });
|
|
3343
|
-
return text(res);
|
|
3344
|
-
}
|
|
3345
|
-
catch (e) {
|
|
3346
|
-
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3347
|
-
}
|
|
3348
|
-
});
|
|
3349
|
-
server.registerTool("lighthouse_private_equity_get_company", {
|
|
3350
|
-
title: "Get Private Equity Company",
|
|
3351
|
-
description: "Read one company's stored Private Equity profile by company_id: the id of a row returned by lighthouse_private_equity_list_saved_search_results (a text id, not a UUID, and not a Discovery or CRM id). " +
|
|
3352
|
-
"Returns the keys of a results row (without match) plus the full profile: legal_names, commercial_names, long_description, long_description_extracted, locations, classification ({ naics_secondary, nace, sic, isic }), target_markets, core_offerings, technology_focus, certification_focus, supply_chain_focus, business_tags, technologies and technologies_total, contact ({ primary_phone, phone_numbers, primary_email, emails }), socials, website_language, profile_created_at, is_fresh and run_count. " +
|
|
3353
|
-
"READ ONLY: the profile as a Private Equity search of this workspace stored it, never refreshed. It never fetches the company again and never spends credits. " +
|
|
3354
|
-
"WHEN TO USE: the user asks for details on one company from a Private Equity search's results. When crm_id is set, lighthouse_crm_get_record (record_type 'company') opens the CRM company. " +
|
|
3355
|
-
"Not found when none of this workspace's Private Equity searches returned that company.",
|
|
3356
|
-
inputSchema: {
|
|
3357
|
-
company_id: z.string().min(1).describe("The company's id, as a row of lighthouse_private_equity_list_saved_search_results gives it"),
|
|
3358
|
-
},
|
|
3359
|
-
}, async ({ company_id }) => {
|
|
3360
|
-
try {
|
|
3361
|
-
const res = await api.get(`/private-equity/companies/${encodeURIComponent(company_id)}`);
|
|
3362
|
-
return text(res);
|
|
3363
|
-
}
|
|
3364
|
-
catch (e) {
|
|
3365
|
-
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3366
|
-
}
|
|
3367
|
-
});
|
|
3368
3629
|
return server;
|
|
3369
3630
|
}
|