@trylighthouse/mcp-server 0.2.20 → 0.2.23
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/build/index.js +5 -4
- package/build/server.js +188 -187
- package/package.json +1 -1
package/build/server.js
CHANGED
|
@@ -62,14 +62,14 @@ const FILE_BLOCKED_KEYS = "A file a threat was found in also carries blocked_at
|
|
|
62
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.\").";
|
|
63
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. " +
|
|
64
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. " +
|
|
65
|
-
`A long file comes back in windows of max_chars (default ${FILE_CONTENT_WINDOW_CHARS}): meta.has_more true means the file continues,
|
|
65
|
+
`A long file comes back in windows of max_chars (default ${FILE_CONTENT_WINDOW_CHARS}): meta.has_more true means the file continues, meta.next_offset_chars sent as offset_chars reads on from there, and a window with has_more is not the end of the file. ` +
|
|
66
66
|
"meta.complete false means the file is longer than can be read here and the text stops before its end. " +
|
|
67
|
-
"When text is null, reason says why (unsupported_type, image, too_large, no_text for a scan or an empty file, unreadable for a damaged or password protected one, timed_out) and note says it in words
|
|
68
|
-
`An image (PNG, JPEG, GIF or WebP up to ${IMAGE_LIMIT_WORDS}) comes back as the image itself after the JSON
|
|
67
|
+
"When text is null, reason says why (unsupported_type, image, too_large, no_text for a scan or an empty file, unreadable for a damaged or password protected one, timed_out) and note says it in words. " +
|
|
68
|
+
`An image (PNG, JPEG, GIF or WebP up to ${IMAGE_LIMIT_WORDS}) comes back as the image itself after the JSON. ` +
|
|
69
69
|
"Older .doc, .xls and .ppt files, archives, audio and video cannot be read.";
|
|
70
70
|
const MEETING_ACCESS_RULE = "The API key's user may read a recorded meeting (its notes and transcript) when they recorded it, were invited to it (as a guest, declined included, or as its organizer), or a teammate who recorded it shares their meeting notes with them; each occurrence of a recurring meeting is judged on its own.";
|
|
71
71
|
const MEETING_KEY_SOURCES = "from a lighthouse_inbox_calendar_meetings row, or from the meeting object of a meeting note read with lighthouse_crm_get_note, lighthouse_crm_list_notes or lighthouse_crm_search_notes";
|
|
72
|
-
const NOTE_MEETING_KEY_RULE = "meeting.meeting_key is the recorded meeting's handle, the same one a lighthouse_inbox_calendar_meetings row carries: when meeting.has_transcript is true,
|
|
72
|
+
const NOTE_MEETING_KEY_RULE = "meeting.meeting_key is the recorded meeting's handle, the same one a lighthouse_inbox_calendar_meetings row carries: when meeting.has_transcript is true, lighthouse_inbox_calendar_meeting_transcript takes it and answers what was actually said. meeting.has_transcript and meeting.has_recording are answered for the API key's user: each is true only when that transcript or recording belongs to this note's occurrence and they may read it, which needs no access to the recorder's calendar (their role permitting). A recurring meeting shares one key and keeps only the transcript of its latest recorded occurrence, so the note of an earlier occurrence says false. lighthouse_inbox_calendar_meeting describes that same latest occurrence, so it answers for the key when either flag is true; lighthouse_inbox_calendar_meeting_notes returns the newest notes of that meeting the user may read. The API returns no recording, only whether one exists.";
|
|
73
73
|
const THREAD_MESSAGES_PAGE_DEFAULT = 15;
|
|
74
74
|
const RETIRED_INPUTS = {
|
|
75
75
|
email: "email is no longer a parameter. Use emails, a comma separated list of email addresses.",
|
|
@@ -92,7 +92,7 @@ export function createServer(apiKey) {
|
|
|
92
92
|
const api = new LighthouseAPI(apiKey);
|
|
93
93
|
const server = new McpServer({
|
|
94
94
|
name: "lighthouse",
|
|
95
|
-
version: "0.2.
|
|
95
|
+
version: "0.2.23",
|
|
96
96
|
});
|
|
97
97
|
const originalRegister = server.registerTool.bind(server);
|
|
98
98
|
server.registerTool = (name, schemaOrConfig, handler) => {
|
|
@@ -109,7 +109,7 @@ export function createServer(apiKey) {
|
|
|
109
109
|
};
|
|
110
110
|
const CRM_FILTER_DESCRIPTION = "CRM filter object. Format: " +
|
|
111
111
|
'{"operator":"AND","conditions":[{"field":"<key>","operator":"<op>","value":<val>}],"groups":[]}. ' +
|
|
112
|
-
"
|
|
112
|
+
"Field keys and types come from lighthouse_crm_list_attributes. " +
|
|
113
113
|
"Operators by field type, " +
|
|
114
114
|
"text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty; " +
|
|
115
115
|
"number/currency/rating/percentage: gt, gte, lt, lte, eq, neq, empty, not_empty; " +
|
|
@@ -123,12 +123,12 @@ export function createServer(apiKey) {
|
|
|
123
123
|
"investors (a company's investors, a read-only multi_record of the GLOBAL DATABASE's companies and people): contains_any, not_contains_any, contains_all, empty, not_empty; the value is a list of investor ids from lighthouse_discovery_search_entities with record_type 'investor', never CRM record ids; " +
|
|
124
124
|
"experience / education (a person's work history and schools, read-only multi_records of the GLOBAL DATABASE's companies and schools): contains_any, not_contains_any, contains_all, empty, not_empty; the value is a list of ids from lighthouse_discovery_search_entities with record_type 'company' (record_type 'school' for education), never CRM record ids; " +
|
|
125
125
|
"domain/email/phone: contains_any, not_contains_any, contains_all, empty, not_empty; " +
|
|
126
|
-
"list_entries (the record's list memberships; the value is an array of list ids from lighthouse_crm_list_lists, and the list's record_type must be the type of the record the condition is on: this record type, or the linked record's type on a dotted path): contains_any, not_contains_any, contains_all, empty, not_empty (empty = in no list, not_empty = in at least one list; both take no value). On a dotted path it filters by the linked records' lists, quantified like every dotted field: associated_people.list_entries contains_any [<people list id>] on a company = the companies with at least one person in that list, associated_people.list_entries empty = the companies none of whose people is in a list, companies with no people included; a custom record-link field works the same way (<link field>.list_entries, the lists of the record type it links to). A custom field can belong to ONE list: lighthouse_crm_list_attributes gives it that list's list_id, it can be set only on a record already in that list, its value sits inside that list's entry under the record's list_entries rather than at the top level, and naming it in a condition or a sort answers 'List not found' when you cannot open the list. A question about the records IN a list with further conditions or an order (\"companies in list X created this month, newest first\") is ONE call: either lighthouse_crm_list_list_records with the same filters and sort, or this search with a list_entries contains_any condition on that list's id beside the other conditions
|
|
126
|
+
"list_entries (the record's list memberships; the value is an array of list ids from lighthouse_crm_list_lists, and the list's record_type must be the type of the record the condition is on: this record type, or the linked record's type on a dotted path): contains_any, not_contains_any, contains_all, empty, not_empty (empty = in no list, not_empty = in at least one list; both take no value). On a dotted path it filters by the linked records' lists, quantified like every dotted field: associated_people.list_entries contains_any [<people list id>] on a company = the companies with at least one person in that list, associated_people.list_entries empty = the companies none of whose people is in a list, companies with no people included; a custom record-link field works the same way (<link field>.list_entries, the lists of the record type it links to). A custom field can belong to ONE list: lighthouse_crm_list_attributes gives it that list's list_id, it can be set only on a record already in that list, its value sits inside that list's entry under the record's list_entries rather than at the top level, and naming it in a condition or a sort answers 'List not found' when you cannot open the list. A question about the records IN a list with further conditions or an order (\"companies in list X created this month, newest first\") is ONE call: either lighthouse_crm_list_list_records with the same filters and sort, or this search with a list_entries contains_any condition on that list's id beside the other conditions, with no need to read the whole list; " +
|
|
127
127
|
"sequences (people only: the outbound sequences a person is in, a read-only multi_select whose options are the sequences you can open; the value is an array of sequence ids, from that field's options (lighthouse_crm_list_options) or the id of an entry of a person's sequences): contains_any, not_contains_any, contains_all, empty, not_empty (empty = in none of the sequences you can open, not_empty = in at least one of them; both take no value). The id of a sequence you cannot open, or an id that names no sequence, matches no one, so it adds no one to contains_any, makes contains_all match no one and removes no one with not_contains_any. A value that is not a sequence id is refused. On a company or a deal, associated_people.sequences filters by the sequences of its people, quantified like every dotted field (associated_people.sequences contains_any [<sequence id>] = the records with at least one person in that sequence); " +
|
|
128
128
|
"checkbox: eq (value is true or false); " +
|
|
129
129
|
"interaction (first/last email & linkedin interaction, overall interaction-with): eq, neq, empty, not_empty; " +
|
|
130
130
|
"interaction-type (first/last interaction type): contains_any, not_contains_any, empty, not_empty. " +
|
|
131
|
-
"Value formats: text ops → string; number ops → number (a rating field's value is
|
|
131
|
+
"Value formats: text ops → string; number ops → number (a rating field's value is a number from 0 to 5, or null when unrated); " +
|
|
132
132
|
"contains_any/not_contains_any/contains_all → array of values; " +
|
|
133
133
|
"empty/not_empty → omit value; date ops → ISO date string \"YYYY-MM-DD\" or a rolling token resolved against the current time on every run: \"7 days\" or \"3 months ago\" = that period back (bare token means ago), \"2 weeks from now\" = that period forward (e.g. next_meeting_date before \"60 days from now\" = within the next 60 days). " +
|
|
134
134
|
"interaction eq/neq → {\"members\":[teammateUserId,...],\"directions\":[\"outbound\"|\"inbound\"|\"none\"]} " +
|
|
@@ -137,25 +137,25 @@ export function createServer(apiKey) {
|
|
|
137
137
|
"interaction date fields before/after/on → ISO date or a rolling token (\"60 days\", \"60 days from now\"), empty/not_empty → omit value. " +
|
|
138
138
|
"Linked-record fields filter via dotted paths <relation>.<field> (e.g. associated_people.emails on a company, associated_companies.name on a person; custom record-link fields and two-level paths like associated_deals.associated_people.emails work too) using the leaf field type's operators. Dotted paths work on company, person, deal and custom object records (a custom object's own record-link fields are its relations); on tasks and notes a dotted condition is ignored, so never use dotted fields there. " +
|
|
139
139
|
"On a dotted path every operator quantifies across ALL linked records: positive operators (contains, eq, contains_any, contains_all, the date and number comparisons, not_empty) match when at least one linked record matches; negative operators (neq, not_contains, not_contains_any, empty) match when NO linked record matches, INCLUDING records with no linked records at all, so not_contains_any with every option listed returns the same records as empty. " +
|
|
140
|
-
"semantic_search (people and companies ONLY; a FILTER, not an attribute: lighthouse_crm_list_attributes never lists it and no record carries it): operator matches, value {\"vector\":\"[...]\",\"min_similarity\"?:0 to 0.99}. It matches each record's linked global profile against a READY embedding. This API does NOT build embeddings from text: a {\"text\":...} value is refused with a 400 that says so, and
|
|
140
|
+
"semantic_search (people and companies ONLY; a FILTER, not an attribute: lighthouse_crm_list_attributes never lists it and no record carries it): operator matches, value {\"vector\":\"[...]\",\"min_similarity\"?:0 to 0.99}. It matches each record's linked global profile against a READY embedding. This API does NOT build embeddings from text: a {\"text\":...} value is refused with a 400 that says so, and a vector the app did not produce matches nothing meaningful. The vector comes from the app: a saved view's filters (lighthouse_crm_get_view returns them with the vector) or a condition the user copied with Copy for API in the filter builder. Such a condition works as it is, at any depth of the tree and on a linked path (associated_companies.semantic_search on a person = people whose companies match the description, associated_people.semantic_search on a company). With no sort the page is ranked by similarity; only records linked to the global database can match. With no saved view or copied condition carrying a vector, a description-based search of the CRM is not available; the attribute filters still are, and lighthouse_discovery_semantic_search_companies / _people search the global database (NOT the CRM) by description. " +
|
|
141
141
|
"Dotted linked interaction filters, e.g. {\"field\":\"associated_companies.last_email_interaction\",\"operator\":\"eq\",\"value\":{\"members\":[\"<id>\"],\"directions\":[\"outbound\"]}}; dotted interaction keys support eq, neq, empty and not_empty. " +
|
|
142
142
|
"The `operator` of a block (the top level OR any group) is \"AND\" or \"OR\" and joins that block's conditions AND its nested groups: \"AND\" = every one must match, \"OR\" = at least one matches. Set the top-level operator to \"OR\" to OR the top-level conditions, or nest a group to mix logic like (A OR B) AND C. " +
|
|
143
143
|
'Nested-group example for "(domain contains acme OR name contains acme) AND created after 2025-01-01": ' +
|
|
144
144
|
'{"operator":"AND","conditions":[{"field":"created_at","operator":"after","value":"2025-01-01"}],"groups":[{"operator":"OR","conditions":[{"field":"domain","operator":"contains_any","value":["acme"]},{"field":"name","operator":"contains","value":"acme"}],"groups":[]}]} ' +
|
|
145
|
-
"(field keys above are illustrative
|
|
145
|
+
"(field keys above are illustrative; the real keys come from lighthouse_crm_list_attributes). " +
|
|
146
146
|
"Dates have NO range operator, express a window as two conditions (after <start> AND before <end>) in an AND group, computing the bounds from today's date. " +
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"RESPONSE VALIDATION META (searching records, and reading a list's records): an unknown field, an operator the field does not take (list_entries included; on a dotted path, the leaf field's) or a group join other than AND/OR does not fail the read; instead the response carries, alongside `records`/`total`, three arrays
|
|
147
|
+
"Field keys and option values are matched exactly against what lighthouse_crm_list_attributes / lighthouse_crm_list_options return, and the chosen values for a select/multi_select condition are a subset of that field's options. A concept with no matching field or option (e.g. a 'competitor'/'portfolio' status that no field actually holds) cannot be filtered on. " +
|
|
148
|
+
"Discovery field keys do not apply here; CRM fields come from lighthouse_crm_list_attributes. " +
|
|
149
|
+
"RESPONSE VALIDATION META (searching records, and reading a list's records): an unknown field, an operator the field does not take (list_entries included; on a dotted path, the leaf field's) or a group join other than AND/OR does not fail the read; instead the response carries, alongside `records`/`total`, three arrays (collected recursively across nested groups): " +
|
|
150
150
|
"`ignored_filter_fields` (field keys not recognised; that condition was dropped), " +
|
|
151
151
|
"`invalid_operators` ([{field, operator}] where the operator is not valid for that field's type; that condition was dropped), and " +
|
|
152
152
|
"`invalid_logical_operators` ([{operator}] where a group's join was not AND/OR; treated as AND). " +
|
|
153
|
-
"
|
|
154
|
-
"A dotted path is judged by its leaf, and only for its operator: a relation, or a linked record's field, that does not exist is in none of these arrays, and that condition matches no record
|
|
153
|
+
"A non-empty array means part of the filter did NOT apply, so the results are broader or narrower than it asked for. A `warnings` array summarises the same in plain text. " +
|
|
154
|
+
"A dotted path is judged by its leaf, and only for its operator: a relation, or a linked record's field, that does not exist is in none of these arrays, and that condition matches no record; the keys of a dotted path come from lighthouse_crm_list_attributes of the record type it is on. " +
|
|
155
155
|
"The saved filters of a view_id are checked the same way, as if you had sent them. " +
|
|
156
156
|
"A VALUE the field cannot take is never dropped: a date that is neither YYYY-MM-DD nor a rolling period, and every value the field types above say is refused, refuses the whole call with a message that says what is wrong with it.";
|
|
157
157
|
const SAVED_FILTER_REFUSAL = "A filter the record search would not run as written is refused here with a 400 and nothing is saved: an unknown field, an operator the field does not take (list_entries included; on a dotted path, the leaf field's), a group join other than AND/OR, or a value in the wrong shape, such as a sequence name where sequence ids go.";
|
|
158
|
-
const DOTTED_PATH_NAMES_UNCHECKED = "On a dotted path only the operator is checked: a relation, or a linked record's field, that does not exist is neither refused nor reported, and that condition matches no record
|
|
158
|
+
const DOTTED_PATH_NAMES_UNCHECKED = "On a dotted path only the operator is checked: a relation, or a linked record's field, that does not exist is neither refused nor reported, and that condition matches no record; a dotted path's fields come from lighthouse_crm_list_attributes of the linked record type.";
|
|
159
159
|
const LINK_NOT_VIEWABLE_RULE = "A record of a type you cannot view, or of a custom object you cannot open, answers 404 not_found, exactly like an id that does not exist.";
|
|
160
160
|
const NOTE_CREATE_LINK_RULE = "A record of a type you cannot view answers 404 not_found, exactly like an id that does not exist, and a record of a custom object you cannot open answers 400 invalid_input, exactly like a record type that does not exist.";
|
|
161
161
|
const TASK_LINKS_PER_TYPE = "Each type you send with at least one id replaces that type's links, and a type you leave out, or send with no ids, keeps them.";
|
|
@@ -165,7 +165,7 @@ export function createServer(apiKey) {
|
|
|
165
165
|
const ACTIVITY_RELATED_RECORDS_LEFT_OUT = "A record of a type you cannot view, or of a custom object you cannot open, is left out, as if the activity had not touched it.";
|
|
166
166
|
const REPORT_ROLE_SOURCES = "notes, tasks or a meeting transcript their role does not let them read";
|
|
167
167
|
const BOARD_GROUPING_RULE = "Kanban views only: the field the board's columns come from. It must be a status or select field stored on the view's records (lighthouse_crm_list_attributes gives each field's data_type), and a list's own field works only on that list's views. A field Lighthouse computes, such as tags, sequences or the first and last interaction type, cannot group a board, and neither can a text, number, date or user field. Any other key is refused with a 400 and nothing is saved, with the message: \"<field>\" cannot group this kanban view. Use a status or select field stored on its records (a list's own field works on that list's views only). Sending the view's current grouped_by back, or leaving it out, is never checked. A table view ignores it.";
|
|
168
|
-
const SHARE_ALONG_RULE = "
|
|
168
|
+
const SHARE_ALONG_RULE = "share_along: true is for a request to share the lists and sequences it filters on as well, and nothing is shared along without it; a list is shared along only when the user's role can share lists, and a sequence only when it can share sequences. The response always carries shared_along ([{ type, id, name }]) and audience_cannot_open ([{ type, id, name, is_owner, is_parent }]), lists first, then sequences (type list or sequence): for the people it is now shared with, a filter on a list they cannot open is not applied, and a filter on a sequence they cannot open counts it as a sequence nobody is in.";
|
|
169
169
|
const shareAlongInput = (where) => `Also share the lists and sequences you own that ${where} with the same people. Nothing is shared along unless you pass true.`;
|
|
170
170
|
const CRM_SORT_DESCRIPTION = 'CRM sort array. Format: [{"field":"<key>","direction":"asc"|"desc"}]. ' +
|
|
171
171
|
"Multiple entries sort by the first, then tie-break by the next. " +
|
|
@@ -177,15 +177,15 @@ export function createServer(apiKey) {
|
|
|
177
177
|
"duration (time_in_stage, time_in_status, every time_in_<status field> twin) orders by the time in the current status: asc = fewest days first, records whose status is empty last; the sort item is the plain {\"field\",\"direction\"} with no extra key. " +
|
|
178
178
|
"NOT sortable (never put these in sort): multi-value fields (multi_select, signal_select, category, multi_user, multi_record), domain, email, phone, list_entries, " +
|
|
179
179
|
"linked/lookup fields, and interaction object & interaction-type fields (the first/last email & linkedin interaction objects and the interaction-type selects); sort by the interaction's date sub-field instead. " +
|
|
180
|
-
"
|
|
181
|
-
"RESPONSE VALIDATION META: an unsortable field does NOT fail the search; it comes back in the response's `invalid_sort_fields` array ([{field, reason}]) and the search runs in the default order.
|
|
180
|
+
"Field keys and types come from lighthouse_crm_list_attributes; a field whose type is not in the sortable list above cannot be sorted. " +
|
|
181
|
+
"RESPONSE VALIDATION META: an unsortable field does NOT fail the search; it comes back in the response's `invalid_sort_fields` array ([{field, reason}]) and the search runs in the default order. A non-empty `invalid_sort_fields` means the sort did not apply.";
|
|
182
182
|
const DISCOVERY_FILTER_DESCRIPTION = "Discovery filter object. Format: " +
|
|
183
183
|
'{"operator":"AND","conditions":[{"field":"<permalink>","operator":"<op>","value":<val>}],"groups":[]}. ' +
|
|
184
|
-
"
|
|
184
|
+
"`field` is one of the prefixed discovery filter keys listed for this entity at the end of this description (company_* or person_*). " +
|
|
185
185
|
"Those keys are NOT the data fields returned by lighthouse_discovery_list_attributes: a condition whose `field` is a raw data column (e.g. 'categories', 'headquarter_country', 'founded_on') is dropped (it filters nothing) and reported back in the response's `ignored_filter_fields` so you can catch it. " +
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
"
|
|
186
|
+
"Category/industry values come from lighthouse_discovery_list_industries. " +
|
|
187
|
+
"Location values come from the `value` field of lighthouse_discovery_list_locations: countries are ISO 3166-1 alpha-2 codes in UPPERCASE (e.g. \"US\", \"GB\", \"DE\"), regions are the region codes it returns. A lowercase code (\"us\") or a country name (\"United States\") is not a valid value. This applies to EVERY country/region filter key (company_hq_country, company_investor_country, person_country, person_work_country, and the rest). " +
|
|
188
|
+
"Tag/highlight values come from lighthouse_discovery_list_tags. " +
|
|
189
189
|
"Fields use prefixed keys (company_name, company_headcount, person_country, etc.). " +
|
|
190
190
|
"Operators by field type, " +
|
|
191
191
|
"short_text: contains, is; " +
|
|
@@ -197,11 +197,11 @@ export function createServer(apiKey) {
|
|
|
197
197
|
"select: contains_any, not_contains_any, empty, not_empty; " +
|
|
198
198
|
"multi_select: contains_any, not_contains_any, contains_all, empty, not_empty; " +
|
|
199
199
|
"investor_multi_select: contains_any, not_contains_any, contains_all, empty, not_empty " +
|
|
200
|
-
"(value is array of investor UUIDs or objects [{\"id\":\"uuid\",\"label\":\"Name\"}].
|
|
200
|
+
"(value is array of investor UUIDs or objects [{\"id\":\"uuid\",\"label\":\"Name\"}]. lighthouse_discovery_search_entities with record_type 'investor' finds investor IDs by name); " +
|
|
201
201
|
"work_experience_multi_select: contains_any, not_contains_any, contains_all, empty, not_empty " +
|
|
202
|
-
"(value is array of organisation UUIDs or objects [{\"id\":\"uuid\",\"label\":\"Name\"}].
|
|
202
|
+
"(value is array of organisation UUIDs or objects [{\"id\":\"uuid\",\"label\":\"Name\"}]. lighthouse_discovery_search_entities with record_type 'company' finds organisation IDs by name); " +
|
|
203
203
|
"education_multi_select: contains_any, not_contains_any, contains_all, empty, not_empty " +
|
|
204
|
-
"(value is array of institution UUIDs or objects [{\"id\":\"uuid\",\"label\":\"Name\"}].
|
|
204
|
+
"(value is array of institution UUIDs or objects [{\"id\":\"uuid\",\"label\":\"Name\"}]. lighthouse_discovery_search_entities with record_type 'school' finds school IDs by name); " +
|
|
205
205
|
"funding_type_multi_select: contains_any, not_contains_any, empty, not_empty; " +
|
|
206
206
|
"multi_user: contains_any, not_contains_any, contains_all, empty, not_empty (value is array of user UUIDs); " +
|
|
207
207
|
"boolean/checkbox: eq; " +
|
|
@@ -219,22 +219,21 @@ export function createServer(apiKey) {
|
|
|
219
219
|
"date ops → ISO date string \"YYYY-MM-DD\". " +
|
|
220
220
|
"The `operator` of a block (the top level OR any group) is \"AND\" or \"OR\" and joins that block's conditions AND its nested groups: \"AND\" = every one must match, \"OR\" = at least one matches. Set the top-level operator to \"OR\" to OR the top-level conditions, or nest a group to mix logic, e.g. (A OR B) AND C = {\"operator\":\"AND\",\"conditions\":[C],\"groups\":[{\"operator\":\"OR\",\"conditions\":[A,B],\"groups\":[]}]}. A worked example with real keys is in the field-key list below. " +
|
|
221
221
|
"company_signals value is a plain array of strings; person_signals value is an object {\"signals\":[...],\"registration_countries\"?:[...],\"signal_dates\"?:{...},\"stealth_predicted_areas\"?:[...]}, these are the only contains_any fields whose value is not a bare array. " +
|
|
222
|
-
"
|
|
223
|
-
"NOT EXPRESSIBLE on this surface (it searches the global database, NOT your CRM): CRM list membership, CRM record owner / unassigned, CRM custom fields, deal stage, role-change dates, and geography finer than country (no US state).
|
|
224
|
-
"
|
|
225
|
-
"RESPONSE VALIDATION META: this search
|
|
222
|
+
"The company_*/person_* keys listed below are matched exactly, with no variants (no _usd suffix, no abbreviation like hq for headquarter), and the bare permalinks returned by lighthouse_discovery_list_attributes are not filter keys; any unrecognised key is dropped (the search runs as if that condition were absent) and echoed in the response's `ignored_filter_fields`, the one place a mistyped key shows, because the result otherwise looks fine but is unfiltered. " +
|
|
223
|
+
"NOT EXPRESSIBLE on this surface (it searches the global database, NOT your CRM): CRM list membership, CRM record owner / unassigned, CRM custom fields, deal stage, role-change dates, and geography finer than country (no US state). " +
|
|
224
|
+
"CRM attribute keys do not apply here. The complete set of valid discovery filter keys for this entity is listed at the end of this description. " +
|
|
225
|
+
"RESPONSE VALIDATION META: this search does not fail on a bad filter; instead the response carries, alongside `items`/`total`, three arrays (collected recursively across nested groups): " +
|
|
226
226
|
"`ignored_filter_fields` (keys not recognised; that condition was dropped), " +
|
|
227
227
|
"`invalid_operators` ([{field, operator}] where the operator is not valid for that field's type; that condition was dropped), and " +
|
|
228
228
|
"`invalid_logical_operators` ([{operator}] where a group's join was not AND/OR; treated as AND). " +
|
|
229
|
-
"
|
|
229
|
+
"A non-empty array means part of the filter did NOT apply, so the results are broader than it asked for. A `warnings` array summarises the same in plain text.";
|
|
230
230
|
const COMPANY_DISCOVERY_SORT_DESCRIPTION = 'Discovery company sort. Format: {"by":"<field>","dir":"asc"|"desc"}. ' +
|
|
231
231
|
"ONLY two fields are sortable: founded_on (founding date) and total_funding_amount_usd (total funding raised, in USD). " +
|
|
232
232
|
"Omit sort to use the default: relevance score, descending. " +
|
|
233
233
|
"No other field is sortable here (NOT headcount, name, last-funding, etc.); an unsupported `by` is ignored and results fall back to relevance order, with a note in meta.warnings. " +
|
|
234
|
-
"If the user asks to sort companies by anything else, tell them only founding date and total funding are supported. " +
|
|
235
234
|
"RESPONSE VALIDATION META: an unsupported sort does NOT fail the search; it comes back in the response's `invalid_sort_fields` array (and `warnings`) and results fall back to relevance order.";
|
|
236
|
-
const DISCOVERY_CRM_ID_NOTE = " EVERY result row carries crm_id: the id of the CRM record this workspace already has for that company or person, and null when it has none.
|
|
237
|
-
const COMPANY_DISCOVERY_FIELDS = " COMPANY FILTER KEYS (
|
|
235
|
+
const DISCOVERY_CRM_ID_NOTE = " EVERY result row carries crm_id: the id of the CRM record this workspace already has for that company or person, and null when it has none. No per-row search of the CRM is needed: \"companies like this one that we are NOT tracking yet\" is a filter on this key. When it is set, lighthouse_crm_get_record (record_type 'company' or 'person') opens that record and lighthouse_crm_update_record edits it; when it is null, lighthouse_crm_upsert_record is what brings the entity into the CRM without making a duplicate.";
|
|
236
|
+
const COMPANY_DISCOVERY_FIELDS = " COMPANY FILTER KEYS (the values the condition `field` takes; any other key is ignored): " +
|
|
238
237
|
"Basics: company_name (short_text), company_domain (url), company_description (long_text), company_headcount (number), company_founded_on (date, year), company_created_at (date). " +
|
|
239
238
|
"Industry: company_categories (multi_select; values from lighthouse_discovery_list_industries → categories), company_sdg_impact (multi_select; values from list_industries → sdg_categories). " +
|
|
240
239
|
"Signals: company_signals (multi_select; values: new_funding_round, growth_website, growth_headcount, growth_job_posts, growth_lnk_followers, growth_tw_followers, growth_npm, growth_app, growth_patents, bl_launch, hn_launch, gh_launch, prhu_launch, prhu_award). " +
|
|
@@ -249,7 +248,7 @@ export function createServer(apiKey) {
|
|
|
249
248
|
'NESTED-GROUP EXAMPLE for "headcount 10-50 AND (HQ in US or UK) AND raised Series A": ' +
|
|
250
249
|
'{"operator":"AND","conditions":[{"field":"company_headcount","operator":"between","value":{"min":10,"max":50}},{"field":"company_last_funding_type","operator":"contains_any","value":["series_a"]}],"groups":[{"operator":"OR","conditions":[{"field":"company_hq_country","operator":"contains_any","value":["US","GB"]}],"groups":[]}]} ' +
|
|
251
250
|
"(country codes from list_locations, funding-type values from lighthouse_discovery_list_funding_types).";
|
|
252
|
-
const PERSON_DISCOVERY_FIELDS = " PERSON FILTER KEYS (
|
|
251
|
+
const PERSON_DISCOVERY_FIELDS = " PERSON FILTER KEYS (the values the condition `field` takes; any other key is ignored): " +
|
|
253
252
|
"Basics: person_name (short_text), person_created_at (date). " +
|
|
254
253
|
"Investments (the person AS an investor, e.g. an angel; the investments_top_* keys match the investor's TOP-RANKED preference only, e.g. their #1 country, not every investment they ever made): person_is_investor (checkbox; eq true = only investors, eq false = exclude investors), person_num_investments (number; total recorded investments), person_investments_top_category (multi_select; list_industries categories), person_investments_top_sdg (multi_select; list_industries sdg_categories), person_investments_top_country (multi_select; list_locations countries), person_investments_top_region (multi_select; list_locations regions), person_investments_top_stage (funding_type_multi_select; lighthouse_discovery_list_funding_types `value`, no_funding not applicable). " +
|
|
255
254
|
"Industry: person_current_categories, person_past_categories (multi_select; values from lighthouse_discovery_list_industries → categories), person_current_sdg_impact, person_past_sdg_impact (multi_select; list_industries → sdg_categories). " +
|
|
@@ -258,7 +257,7 @@ export function createServer(apiKey) {
|
|
|
258
257
|
"Language & origin: person_language (language_select), person_language_proficiency (multi_select; use with person_language to require a proficiency level), person_name_origin (multi_select; list_locations countries). " +
|
|
259
258
|
"Current location: person_country (multi_select; UPPERCASE ISO alpha-2 codes e.g. \"US\", \"GB\" from lighthouse_discovery_list_locations → countries), person_region (multi_select; list_locations → regions), person_german_state (multi_select; German federal states). " +
|
|
260
259
|
"Experience: person_past_companies (work_experience_multi_select; ids from lighthouse_discovery_search_entities record_type 'company'), person_education (education_multi_select; ids from lighthouse_discovery_search_entities record_type 'school'), person_experience_start_date, person_experience_end_date (date; matches people with at least one work experience whose start/end date passes the bound, independent of the other experience conditions), person_work_country, person_work_region, person_edu_country, person_edu_region (multi_select; list_locations). " +
|
|
261
|
-
"Stealth: person_stealth_start_date, person_stealth_end_date (date), person_stealth_predicted_area (multi_select; the predicted area a stealth founder is building in, values from lighthouse_discovery_list_industries → stealth_predicted_areas, the same list as stealth_predicted_areas of person_signals; it matches everyone the classifier gave that area, while stealth_predicted_areas inside person_signals also requires the stealth signal and takes its since date, which is how the app filters
|
|
260
|
+
"Stealth: person_stealth_start_date, person_stealth_end_date (date), person_stealth_predicted_area (multi_select; the predicted area a stealth founder is building in, values from lighthouse_discovery_list_industries → stealth_predicted_areas, the same list as stealth_predicted_areas of person_signals; it matches everyone the classifier gave that area, while stealth_predicted_areas inside person_signals also requires the stealth signal and takes its since date, which is how the app filters). " +
|
|
262
261
|
"Primary company: person_company_hq_country, person_company_hq_region (multi_select; list_locations), person_company_headcount (number), person_company_founded_on (date), person_company_total_funding, person_company_last_funding_amount (number, USD), person_company_last_funding_date (date), person_company_last_funding_type (funding_type_multi_select; lighthouse_discovery_list_funding_types `value`, including no_funding). " +
|
|
263
262
|
"CRM: person_internal_crm_status (checkbox; eq true/false), person_crm_network (multi_user; ids from lighthouse_workspace_list_users), person_external_crm_status (checkbox; needs a connected external CRM), person_external_crm_lists (multi_select; needs a connected external CRM). " +
|
|
264
263
|
'NESTED-GROUP EXAMPLE for "(based in Germany or France) AND a technical or AI/data background": ' +
|
|
@@ -269,11 +268,11 @@ export function createServer(apiKey) {
|
|
|
269
268
|
annotations: READ_ONLY,
|
|
270
269
|
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. " +
|
|
271
270
|
"`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. " +
|
|
272
|
-
"
|
|
273
|
-
"To keep the response small it does NOT include select/multi_select option values: when a field has has_options=true,
|
|
271
|
+
"WHEN TO USE: building filters for search_records / list_records or creating/updating views, which take the exact permalinks it returns. " +
|
|
272
|
+
"To keep the response small it does NOT include select/multi_select option values: when a field has has_options=true, lighthouse_crm_list_options with that permalink returns its allowed values. " +
|
|
274
273
|
"Supports built-in types (company, person, deal, task) and custom objects (use the object's permalink, e.g. 'lp', 'project'). " +
|
|
275
274
|
"When called without record_type, returns all types including custom objects as top-level keys. " +
|
|
276
|
-
"A custom field that belongs to a single list carries that list's list_id: it can be set only on a record already in that list (
|
|
275
|
+
"A custom field that belongs to a single list carries that list's list_id: it can be set only on a record already in that list (lighthouse_crm_add_to_list adds it), and its value sits in that list's entry under the record's list_entries. Without list_id every list's fields are listed; passing list_id keeps the fields of every record plus that list's own. A list you cannot open answers 'List not found', and so does filtering or sorting on a field of such a list.",
|
|
277
276
|
inputSchema: {
|
|
278
277
|
record_type: z
|
|
279
278
|
.string()
|
|
@@ -426,7 +425,7 @@ export function createServer(apiKey) {
|
|
|
426
425
|
title: "List CRM Field Options",
|
|
427
426
|
annotations: READ_ONLY,
|
|
428
427
|
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. " +
|
|
429
|
-
"
|
|
428
|
+
"WHEN TO USE: lighthouse_crm_list_attributes shows has_options=true for a field and its values are needed for filtering or setting it. " +
|
|
430
429
|
"Results are paginated (max 100 per page): use `search` to narrow by label, and `limit` / `offset` to page through a field with many options. " +
|
|
431
430
|
"For custom objects, use the object's permalink as record_type.",
|
|
432
431
|
inputSchema: {
|
|
@@ -451,7 +450,7 @@ export function createServer(apiKey) {
|
|
|
451
450
|
annotations: CREATES,
|
|
452
451
|
description: "Add a new option to a select/multi_select field. The option value is auto-generated from the label. " +
|
|
453
452
|
"Re-creating a system default option that was previously removed restores it (its original color and label are kept). " +
|
|
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
|
|
453
|
+
"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 the first row is not necessarily the option just created: its label is what identifies it. " +
|
|
455
454
|
"For custom objects, use the object's permalink as record_type. " +
|
|
456
455
|
"Options can only be created on company, person, deal, and custom objects.",
|
|
457
456
|
inputSchema: {
|
|
@@ -495,12 +494,11 @@ export function createServer(apiKey) {
|
|
|
495
494
|
description: "Search and filter records in YOUR CRM workspace (companies, people, or deals you've added). " +
|
|
496
495
|
"Supports filters, sorting, pagination, and saved views. " +
|
|
497
496
|
"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). " +
|
|
498
|
-
"
|
|
499
|
-
"for
|
|
500
|
-
"The response's `meta` includes `total` (an integer match count), `ignored_filter_fields` (any filter key that is not a valid attribute for this 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), `invalid_logical_operators` (a group joined by something other than AND/OR, read as AND) and `invalid_sort_fields`. Check every array to confirm the whole filter and the sort were applied. " +
|
|
497
|
+
"The exact field keys for the record_type come from lighthouse_crm_list_attributes. " +
|
|
498
|
+
"The response's `meta` includes `total` (an integer match count), `ignored_filter_fields` (any filter key that is not a valid attribute for this 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), `invalid_logical_operators` (a group joined by something other than AND/OR, read as AND) and `invalid_sort_fields`; empty arrays mean the whole filter and the sort were applied. " +
|
|
501
499
|
DOTTED_PATH_NAMES_UNCHECKED + " " +
|
|
502
500
|
"A list_entries condition naming a list you cannot open answers 'List not found', and so does a condition or a sort on a custom field that belongs to such a list: the field is the membership. " +
|
|
503
|
-
"NOT for discovering new companies/people
|
|
501
|
+
"NOT for discovering new companies/people: the lighthouse_discovery_* tools cover that.",
|
|
504
502
|
inputSchema: {
|
|
505
503
|
record_type: z.string().describe("Record type (company, person, deal, or custom object permalink)"),
|
|
506
504
|
filters: z
|
|
@@ -565,14 +563,14 @@ export function createServer(apiKey) {
|
|
|
565
563
|
server.registerTool("lighthouse_crm_list_record_activity", {
|
|
566
564
|
title: "List Record Activity",
|
|
567
565
|
annotations: READ_ONLY,
|
|
568
|
-
description: "Read the activity feed of one CRM record, newest first: who changed what, when, and from where.
|
|
566
|
+
description: "Read the activity feed of one CRM record, newest first: who changed what, when, and from where. WHEN TO USE: questions about history (\"what changed on this deal last week\", \"who moved it to won\", \"which fields has nobody touched\"), which the record's current values cannot answer. " +
|
|
569
567
|
"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. " +
|
|
570
568
|
"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). " +
|
|
571
569
|
"changes[] holds the field edits: { field, label, data_type, is_custom, list, previous, current }, where previous and current are each { value, display }: value is what is stored (an option value, a uuid, a number) and display is the same thing resolved for humans (an option's label, a linked record or user object). Consecutive edits to the same field by the same person on the same day are collapsed into one entry, so previous is the value before the burst and current the value after it. " +
|
|
572
570
|
"Every entry has id, activity_id, action, occurred_at, summary, actor, source, target and changes. One save that changed several fields is reported as one entry per field: id stays unique per entry, activity_id is shared by the entries from that same save. The five detail keys are on every entry and null on the entries they do not describe: note and task carry that note's / task's body inline (no follow-up call needed), merge carries { merged_record, kept_record }, document carries { type, id, name } for an attached file or folder, and related_records (an empty array when there are none) lists the OTHER records when one activity spanned several (never the entry's own target, which is already under target, and never the record whose feed this is). " +
|
|
573
571
|
ACTIVITY_RELATED_RECORDS_LEFT_OUT + " " +
|
|
574
572
|
"record_type is company, person, deal, or a custom object permalink; pass task or note to read a task's or note's own history. " +
|
|
575
|
-
"SCOPE: the feed covers records, notes and tasks being created, updated, deleted and merged, plus files and folders being attached. It does NOT record list membership changes or detaching a document, so
|
|
573
|
+
"SCOPE: the feed covers records, notes and tasks being created, updated, deleted and merged, plus files and folders being attached. It does NOT record list membership changes or detaching a document, so it is not a complete audit trail.",
|
|
576
574
|
inputSchema: {
|
|
577
575
|
record_type: z.string().describe("Record type (company, person, deal, a custom object permalink, or task / note)"),
|
|
578
576
|
record_id: z.string().uuid().optional().describe("Record ID whose feed to read"),
|
|
@@ -594,8 +592,8 @@ export function createServer(apiKey) {
|
|
|
594
592
|
title: "List AI Reports on a Record",
|
|
595
593
|
annotations: READ_ONLY,
|
|
596
594
|
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. " +
|
|
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\" (
|
|
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
|
|
595
|
+
"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\" (this shows whether one exists before another is written from scratch). Not for dashboards or charts (those are lighthouse_reports_*), and nothing here generates a report: that happens in the app. " +
|
|
596
|
+
"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, which lighthouse_crm_get_ai_report returns. " +
|
|
599
597
|
"A report quoting a mailbox, calendar, meeting notes, LinkedIn account or file the API key's user cannot read, or " + REPORT_ROLE_SOURCES + ", is left out of the list (its author always sees it).",
|
|
600
598
|
inputSchema: {
|
|
601
599
|
record_type: z.string().describe("Record type: company, person, deal, or a custom object permalink"),
|
|
@@ -618,7 +616,7 @@ export function createServer(apiKey) {
|
|
|
618
616
|
title: "Get AI Report",
|
|
619
617
|
annotations: READ_ONLY,
|
|
620
618
|
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). " +
|
|
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
|
|
619
|
+
"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. " +
|
|
622
620
|
"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.",
|
|
623
621
|
inputSchema: {
|
|
624
622
|
id: z.string().uuid().describe("Report ID from lighthouse_crm_list_ai_reports"),
|
|
@@ -637,20 +635,23 @@ export function createServer(apiKey) {
|
|
|
637
635
|
annotations: CREATES,
|
|
638
636
|
description: "Create a new CRM record. Pass all fields (standard + custom) in a single flat data object, " +
|
|
639
637
|
"the API splits standard vs custom fields automatically. " +
|
|
640
|
-
"
|
|
638
|
+
"Field keys and types for the record_type come from lighthouse_crm_list_attributes. " +
|
|
641
639
|
"A name is strongly recommended for every record: company → name (string). person → fn or first_name (string). deal → name (string). " +
|
|
642
640
|
"custom objects → name (string) is the only standard field; all other fields are custom. " +
|
|
643
641
|
"If you omit the name, the record is still created but stored with the name \"Unnamed\", so always provide one when you can. " +
|
|
644
|
-
"
|
|
645
|
-
"domain (company) →
|
|
642
|
+
"Data format rules (company/person/deal ONLY, custom objects do NOT have relation fields): " +
|
|
643
|
+
"domain (company) → an array of strings, even for one domain: [\"acme.com\"]. " +
|
|
646
644
|
"associated_people (company) → array of person UUIDs (or {id} objects). " +
|
|
647
645
|
"associated_deals (company) → array of deal UUIDs. " +
|
|
648
646
|
"emails (person) → array of email strings: [\"john@acme.com\"]. " +
|
|
649
|
-
"phones (person) → array of objects: [{\"prefix\"
|
|
647
|
+
"phones (person) → array of objects: [{\"prefix\":1,\"number\":\"5551234\"}]. " +
|
|
650
648
|
"associated_companies (person/deal) → array of company UUIDs. " +
|
|
651
649
|
"associated_people (deal) → array of person UUIDs. " +
|
|
652
650
|
"owner (deal) → UUID string of the owner user. " +
|
|
653
|
-
"Custom fields use their field permalink as the key.
|
|
651
|
+
"Custom fields use their field permalink as the key. " +
|
|
652
|
+
"A custom email, phone, multi_select, multi_record or multi_user field holds a list on every record type, custom objects included: always pass an array, even for one value. " +
|
|
653
|
+
"A custom email field → [\"jane@acme.com\"]. A custom phone field → [{\"prefix\":1,\"number\":\"5551234\"}] or \"+1 555 1234\"; a phone needs a country code. " +
|
|
654
|
+
"A custom rating field's value is a number from 0 to 5 (or null to clear). " +
|
|
654
655
|
"The response returns all fields as top-level keys (custom fields are NOT nested under an attributes object).",
|
|
655
656
|
inputSchema: {
|
|
656
657
|
record_type: z.string().describe("Record type (company, person, deal, or custom object permalink)"),
|
|
@@ -672,22 +673,23 @@ export function createServer(apiKey) {
|
|
|
672
673
|
annotations: DESTRUCTIVE,
|
|
673
674
|
description: "Update an existing CRM record. Only include the fields you want to change. " +
|
|
674
675
|
"Standard and custom fields can be mixed in a single flat data object, the API splits them automatically. " +
|
|
675
|
-
"
|
|
676
|
-
"Relation and multi-value fields (domain, associated_people, associated_deals, emails, phones, associated_companies, and custom multi_select/multi_record/multi_user) " +
|
|
677
|
-
"support
|
|
678
|
-
"(1) pass a bare ARRAY to REPLACE the whole field, e.g. domain: [\"acme.com\"], or an empty array [] to clear it (remove all); " +
|
|
676
|
+
"Field keys and types come from lighthouse_crm_list_attributes. " +
|
|
677
|
+
"Relation and multi-value fields (domain, associated_people, associated_deals, emails, phones, associated_companies, and custom email/phone/multi_select/multi_record/multi_user) " +
|
|
678
|
+
"support these modes (custom objects have only 'name' and custom fields, so the custom ones apply to them): " +
|
|
679
|
+
"(1) pass a bare ARRAY to REPLACE the whole field, e.g. domain: [\"acme.com\"], or an empty array [] to clear it (remove all); one value on its own is refused, so one domain is still an array; " +
|
|
679
680
|
"(2) pass { \"add\": [...] } and/or { \"remove\": [...] } to append or remove items WITHOUT fetching first, " +
|
|
680
|
-
"e.g. domain: { \"add\": [\"new.com\"] } appends one domain, associated_people: { \"remove\": [\"<uuid>\"] } removes one member. " +
|
|
681
|
-
"
|
|
681
|
+
"e.g. domain: { \"add\": [\"new.com\"] } appends one domain, associated_people: { \"remove\": [\"<uuid>\"] } removes one member, a custom email field: { \"add\": [\"jane@acme.com\"] } appends one address. " +
|
|
682
|
+
"add/remove avoids a get-then-merge round-trip; it is also the only safe way to change one relation member without disturbing the others. " +
|
|
682
683
|
"Field formats: " +
|
|
683
684
|
"domain (company) → array of strings: [\"acme.com\"]. " +
|
|
684
685
|
"associated_people (company) → array of person UUIDs. " +
|
|
685
686
|
"emails (person) → array of email strings. " +
|
|
686
|
-
"phones (person) → array of objects: [{\"prefix\"
|
|
687
|
+
"phones (person) and a custom phone field → array of objects: [{\"prefix\":1,\"number\":\"5551234\"}]; a phone needs a country code. " +
|
|
688
|
+
"A custom email field → array of email strings. " +
|
|
687
689
|
"associated_companies (person/deal) → array of company UUIDs. " +
|
|
688
690
|
"associated_people (deal) → array of person UUIDs. " +
|
|
689
691
|
"associated_deals (company/person) → array of deal UUIDs. " +
|
|
690
|
-
"A custom rating field's value →
|
|
692
|
+
"A custom rating field's value → a number from 0 to 5 (or null to clear). " +
|
|
691
693
|
"The response returns all fields as top-level keys (custom fields are NOT nested under an attributes object).",
|
|
692
694
|
inputSchema: {
|
|
693
695
|
record_type: z.string().describe("Record type (company, person, deal, or custom object permalink)"),
|
|
@@ -755,7 +757,7 @@ export function createServer(apiKey) {
|
|
|
755
757
|
annotations: CREATES,
|
|
756
758
|
description: "Create up to 100 CRM records of the same record_type in one call. " +
|
|
757
759
|
"Each item in `records` is a flat data object, the SAME shape you'd pass as `data` to lighthouse_crm_create_record. " +
|
|
758
|
-
"
|
|
760
|
+
"Field keys and types come from lighthouse_crm_list_attributes. " +
|
|
759
761
|
"Partial success: the response returns a `results` array (one entry per item, in order, with success/data/error) and a `summary` " +
|
|
760
762
|
"({total, succeeded, failed}). A failed item does NOT roll back the others. " +
|
|
761
763
|
"Same per-record field rules as lighthouse_crm_create_record (domain as array, emails as array, owner as UUID, etc.).",
|
|
@@ -856,9 +858,9 @@ export function createServer(apiKey) {
|
|
|
856
858
|
server.registerTool("lighthouse_crm_create_list", {
|
|
857
859
|
title: "Create CRM List",
|
|
858
860
|
annotations: CREATES,
|
|
859
|
-
description: "Create a new CRM list to
|
|
861
|
+
description: "Create a new CRM list to organise records. Supports companies, people, deals, and custom objects. " +
|
|
860
862
|
"Defaults to 'private' visibility. " +
|
|
861
|
-
"
|
|
863
|
+
"lighthouse_crm_share_list_with_teams shares a list with specific teams after creation. " +
|
|
862
864
|
"Requires lists.edit permission.",
|
|
863
865
|
inputSchema: {
|
|
864
866
|
name: z.string().describe("List name"),
|
|
@@ -956,8 +958,8 @@ export function createServer(apiKey) {
|
|
|
956
958
|
title: "List Records in a List",
|
|
957
959
|
annotations: READ_ONLY,
|
|
958
960
|
description: "Get records in a CRM list with optional filtering, sorting, and view support. " +
|
|
959
|
-
"Same filter format as lighthouse_crm_search_records.
|
|
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.
|
|
961
|
+
"Same filter format as lighthouse_crm_search_records. Valid filter keys come from lighthouse_crm_list_attributes with this list's record_type, which is 'company', 'person', 'deal', or a custom object permalink. " +
|
|
962
|
+
"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. Empty arrays mean the whole filter and the sort were applied; a value in the wrong shape is refused instead, with a 400. " +
|
|
961
963
|
DOTTED_PATH_NAMES_UNCHECKED + " " +
|
|
962
964
|
"A list you cannot open answers 'List not found', the same as one that does not exist, and so does a condition or a sort on a custom field that belongs to such a list.",
|
|
963
965
|
inputSchema: {
|
|
@@ -1056,7 +1058,7 @@ export function createServer(apiKey) {
|
|
|
1056
1058
|
title: "Share List with Teams",
|
|
1057
1059
|
annotations: ADDS_IF_MISSING,
|
|
1058
1060
|
description: "Grant team-level access to a CRM list. Only the list owner can share. " +
|
|
1059
|
-
"
|
|
1061
|
+
"Team IDs come from lighthouse_workspace_list_teams. " +
|
|
1060
1062
|
"Requires lists.share permission. " +
|
|
1061
1063
|
"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.",
|
|
1062
1064
|
inputSchema: {
|
|
@@ -1172,15 +1174,12 @@ export function createServer(apiKey) {
|
|
|
1172
1174
|
annotations: CREATES,
|
|
1173
1175
|
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. " +
|
|
1174
1176
|
"Link records via the `records` param, keyed by type. " +
|
|
1175
|
-
"The company/person/deal ids
|
|
1177
|
+
"The company/person/deal ids are EXISTING CRM record ids (the ids returned by lighthouse_crm_* tools), NOT discovery ids from lighthouse_discovery_* search results; a discovery result has to be in the CRM before a note can link it. " +
|
|
1176
1178
|
"Content is HTML; plain text works too. " +
|
|
1177
|
-
"
|
|
1178
|
-
"1) Call lighthouse_workspace_list_users first to find the user's ID, " +
|
|
1179
|
-
"2) Include their user IDs in tagged_users, " +
|
|
1180
|
-
"3) Include a mention span in the HTML content: " +
|
|
1179
|
+
"A @mention/tag of a workspace user takes their user ID (from lighthouse_workspace_list_users) in tagged_users and a mention span in the HTML content: " +
|
|
1181
1180
|
'<span data-type="mention" data-id="USER_UUID" data-label="First Last">@First Last</span>. ' +
|
|
1182
1181
|
"Tagged users receive an in-app notification. " +
|
|
1183
|
-
"Optionally attach workspace tags by permalink via `tags` (
|
|
1182
|
+
"Optionally attach workspace tags by permalink via `tags` (lighthouse_crm_list_note_tags lists the available tags); these are labeled, colored tags shared across the workspace and are separate from tagged_users. " +
|
|
1184
1183
|
"This always creates a standard note (type 'standard', meeting null): there is no type input (one sent in the request is ignored), and meeting notes are written by the Notetaker from a recording, never through this tool.",
|
|
1185
1184
|
inputSchema: {
|
|
1186
1185
|
records: z
|
|
@@ -1231,14 +1230,14 @@ export function createServer(apiKey) {
|
|
|
1231
1230
|
title: "Update Note",
|
|
1232
1231
|
annotations: DESTRUCTIVE,
|
|
1233
1232
|
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. " +
|
|
1234
|
-
"content REPLACES the whole body:
|
|
1233
|
+
"content REPLACES the whole body: anything left out, an existing @mention span included, is gone, so an addition to a note is the full current content (from lighthouse_crm_get_note) plus the new part. " +
|
|
1235
1234
|
"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. " +
|
|
1236
|
-
"
|
|
1235
|
+
"A @mention/tag of a workspace user takes their user ID (from lighthouse_workspace_list_users) in tagged_users and a mention span in the HTML content: " +
|
|
1237
1236
|
'<span data-type="mention" data-id="USER_UUID" data-label="First Last">@First Last</span>. ' +
|
|
1238
|
-
"A user newly named in tagged_users receives one in-app notification;
|
|
1239
|
-
"tags are the workspace's labeled, colored note tags (permalinks from lighthouse_crm_list_note_tags), separate from tagged_users: the array REPLACES the tag set (an empty array clears it);
|
|
1237
|
+
"A user newly named in tagged_users receives one in-app notification; the spans of people already mentioned stay only if the content sent still has them. " +
|
|
1238
|
+
"tags are the workspace's labeled, colored note tags (permalinks from lighthouse_crm_list_note_tags), separate from tagged_users: the array REPLACES the tag set (an empty array clears it); lighthouse_crm_manage_note_tags adds or removes single tags without touching the rest. " +
|
|
1240
1239
|
"Works on meeting notes too: the edit is kept on the current version, and lighthouse_inbox_calendar_meeting_notes then reports is_edited true for that meeting. " +
|
|
1241
|
-
"Returns the updated note in the same shape as lighthouse_crm_get_note.
|
|
1240
|
+
"Returns the updated note in the same shape as lighthouse_crm_get_note. lighthouse_crm_link_note_record / lighthouse_crm_unlink_note_record change which records a note is attached to, and lighthouse_crm_delete_note removes it.",
|
|
1242
1241
|
inputSchema: {
|
|
1243
1242
|
note_id: z.string().uuid().optional().describe("Note ID"),
|
|
1244
1243
|
id: z.string().uuid().optional().describe("Same as note_id, the name this tool has always taken. Send either one."),
|
|
@@ -1277,9 +1276,9 @@ export function createServer(apiKey) {
|
|
|
1277
1276
|
title: "Link Record to Note",
|
|
1278
1277
|
annotations: ADDS_IF_MISSING,
|
|
1279
1278
|
description: "Attach a record to an existing note. Call once per record. " +
|
|
1280
|
-
"The company/person/deal record_id
|
|
1279
|
+
"The company/person/deal record_id is an EXISTING CRM record id (an id returned by lighthouse_crm_* tools), NOT a discovery id from lighthouse_discovery_* search results; a discovery result has to be in the CRM before it can be linked. " +
|
|
1281
1280
|
LINK_NOT_VIEWABLE_RULE + " " +
|
|
1282
|
-
"
|
|
1281
|
+
"lighthouse_crm_get_note shows the current set of linked records (the ones you can view).",
|
|
1283
1282
|
inputSchema: {
|
|
1284
1283
|
note_id: z.string().uuid().describe("Note ID"),
|
|
1285
1284
|
record_type: z.string().describe("Record type: company, person, deal, or a custom object permalink"),
|
|
@@ -1419,7 +1418,7 @@ export function createServer(apiKey) {
|
|
|
1419
1418
|
sort: z.array(z.record(z.any())).optional().describe('Task sort array: [{"field":"<key>","direction":"asc"|"desc"}], applied in order (default: due_date asc). ' +
|
|
1420
1419
|
"Sortable task fields are title, status, priority, due_date, created_at, created_by, time_in_status (asc = fewest days in the current status first). Tasks have no custom fields. " +
|
|
1421
1420
|
"Multi-value fields are NOT sortable (assigned_to is multi_user; records and multi_select fields can't be sorted). " +
|
|
1422
|
-
'An unknown or unsortable sort field is rejected with a clear error (field "x" is not sortable)
|
|
1421
|
+
'An unknown or unsortable sort field is rejected with a clear error (field "x" is not sortable). ' +
|
|
1423
1422
|
"status and priority sort by their position in the field's option list, not alphabetically. Priority positions are its magnitude, so ascending on priority is least urgent first and descending puts the urgent work on top."),
|
|
1424
1423
|
limit: z.number().min(1).max(100).default(25),
|
|
1425
1424
|
offset: z.number().min(0).default(0),
|
|
@@ -1454,12 +1453,12 @@ export function createServer(apiKey) {
|
|
|
1454
1453
|
title: "Create Task",
|
|
1455
1454
|
annotations: CREATES,
|
|
1456
1455
|
description: "Create a CRM task, optionally linked to records and assigned to users. " +
|
|
1457
|
-
"Status values are configured per workspace and are NOT fixed:
|
|
1456
|
+
"Status values are configured per workspace and are NOT fixed: lighthouse_crm_list_options with record_type 'task' and permalink 'status' returns the allowed values, and `status` takes the option's value (not its label). " +
|
|
1458
1457
|
"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.",
|
|
1459
1458
|
inputSchema: {
|
|
1460
1459
|
title: z.string().describe("Task title"),
|
|
1461
1460
|
description: z.string().optional().describe("Task description"),
|
|
1462
|
-
status: z.string().optional().describe("Task status
|
|
1461
|
+
status: z.string().optional().describe("Task status: the option value (not the label) from lighthouse_crm_list_options (record_type 'task', permalink 'status'). Valid values are workspace-specific."),
|
|
1463
1462
|
priority: z.string().nullable().optional().describe("Priority option value from lighthouse_crm_list_options (record_type 'task', permalink 'priority'): urgent, high, medium, low or very_low unless this workspace configured its own. Speak to the user in the label, never the value. Send null to clear it."),
|
|
1464
1463
|
due_date: z.string().optional().describe("Due date (ISO 8601)"),
|
|
1465
1464
|
assigned_to: z
|
|
@@ -1484,20 +1483,23 @@ export function createServer(apiKey) {
|
|
|
1484
1483
|
title: "Update Task",
|
|
1485
1484
|
annotations: DESTRUCTIVE,
|
|
1486
1485
|
description: "Update a CRM task. Provide only fields you want to change. " +
|
|
1487
|
-
"assigned_to replaces all current assignees. In records, " + TASK_LINKS_PER_TYPE.charAt(0).toLowerCase() + TASK_LINKS_PER_TYPE.slice(1) + " " +
|
|
1488
|
-
"Status values are configured per workspace and are NOT fixed:
|
|
1486
|
+
"assigned_to takes an array, which replaces all current assignees, or { add: [...] } / { remove: [...] }, which change those people and keep the others. One id on its own is refused. In records, " + TASK_LINKS_PER_TYPE.charAt(0).toLowerCase() + TASK_LINKS_PER_TYPE.slice(1) + " " +
|
|
1487
|
+
"Status values are configured per workspace and are NOT fixed: lighthouse_crm_list_options with record_type 'task' and permalink 'status' returns the allowed values, and `status` takes the option's value (not its label). " +
|
|
1489
1488
|
"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.",
|
|
1490
1489
|
inputSchema: {
|
|
1491
1490
|
id: z.string().uuid().describe("Task ID"),
|
|
1492
1491
|
title: z.string().optional(),
|
|
1493
1492
|
description: z.string().optional(),
|
|
1494
|
-
status: z.string().optional().describe("Task status
|
|
1493
|
+
status: z.string().optional().describe("Task status: the option value (not the label) from lighthouse_crm_list_options (record_type 'task', permalink 'status'). Valid values are workspace-specific."),
|
|
1495
1494
|
priority: z.string().nullable().optional().describe("Priority option value from lighthouse_crm_list_options (record_type 'task', permalink 'priority'): urgent, high, medium, low or very_low unless this workspace configured its own. Speak to the user in the label, never the value. Send null to clear it."),
|
|
1496
1495
|
due_date: z.string().optional(),
|
|
1497
1496
|
assigned_to: z
|
|
1498
|
-
.
|
|
1497
|
+
.union([
|
|
1498
|
+
z.array(z.string().uuid()),
|
|
1499
|
+
z.object({ add: z.array(z.string().uuid()).optional(), remove: z.array(z.string().uuid()).optional() }),
|
|
1500
|
+
])
|
|
1499
1501
|
.optional()
|
|
1500
|
-
.describe("
|
|
1502
|
+
.describe("An array of user UUIDs, even for one person (replaces current assignees), or { add: [...] } / { remove: [...] } to change those people and keep the others"),
|
|
1501
1503
|
records: z
|
|
1502
1504
|
.record(z.array(z.string().uuid()))
|
|
1503
1505
|
.optional()
|
|
@@ -1584,7 +1586,7 @@ export function createServer(apiKey) {
|
|
|
1584
1586
|
annotations: READ_ONLY,
|
|
1585
1587
|
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. " +
|
|
1586
1588
|
"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. " +
|
|
1587
|
-
"
|
|
1589
|
+
"lighthouse_documents_list with this folder_id lists what is INSIDE it. A folder you cannot open answers the same not found as one that does not exist.",
|
|
1588
1590
|
inputSchema: {
|
|
1589
1591
|
folder_id: z.string().uuid().optional().describe("Folder UUID. The name every other folder tool takes."),
|
|
1590
1592
|
id: z.string().uuid().optional().describe("Same as folder_id, the name the tool this one is named after would take. Send either one."),
|
|
@@ -1683,10 +1685,10 @@ export function createServer(apiKey) {
|
|
|
1683
1685
|
server.registerTool("lighthouse_documents_upload_file", {
|
|
1684
1686
|
title: "Upload File",
|
|
1685
1687
|
annotations: CREATES,
|
|
1686
|
-
description: "Reserve a file row and get a signed upload URL
|
|
1687
|
-
"
|
|
1688
|
-
"from the returned upload_fields, then
|
|
1689
|
-
"
|
|
1688
|
+
description: "Reserve a file row and get a signed upload URL. " +
|
|
1689
|
+
"The upload is a POST of the file to upload_url as multipart/form-data: every key/value " +
|
|
1690
|
+
"from the returned upload_fields first, then the file last under the field name 'file'. " +
|
|
1691
|
+
"The upload takes no Authorization header. Max 200MB, enforced by storage (oversized uploads are rejected). " +
|
|
1690
1692
|
"The file is confirmed and sized automatically the first time you read it " +
|
|
1691
1693
|
"(lighthouse_documents_get_file or a folder listing), no separate finalize step. " +
|
|
1692
1694
|
"The URL expires in 30 minutes.",
|
|
@@ -1711,7 +1713,7 @@ export function createServer(apiKey) {
|
|
|
1711
1713
|
server.registerTool("lighthouse_documents_get_file", {
|
|
1712
1714
|
title: "Get File",
|
|
1713
1715
|
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).
|
|
1716
|
+
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). lighthouse_documents_get_file_content reads what the file SAYS. " +
|
|
1715
1717
|
`${FILE_BLOCKED_KEYS} ${FILE_BLOCKED_TOOL_RULE} ${FILE_BLOCKED_ANSWER}`,
|
|
1716
1718
|
inputSchema: {
|
|
1717
1719
|
file_id: z.string().uuid().describe("File UUID"),
|
|
@@ -1899,9 +1901,9 @@ export function createServer(apiKey) {
|
|
|
1899
1901
|
annotations: DESTRUCTIVE,
|
|
1900
1902
|
description: "Update a saved CRM view or list view's name, filters, sort, or layout. " +
|
|
1901
1903
|
"Automatically detects whether it's a CRM view or list view. " +
|
|
1902
|
-
"
|
|
1903
|
-
"Uses CRM filter format
|
|
1904
|
-
"A semantic_search condition in the new filters must carry its vector (
|
|
1904
|
+
"Sharing settings are changed by lighthouse_crm_update_view_sharing. " +
|
|
1905
|
+
"Uses CRM filter format; filter keys come from lighthouse_crm_list_attributes. " +
|
|
1906
|
+
"A semantic_search condition in the new filters must carry its vector (the one the view already has, from lighthouse_crm_get_view, or one copied from the app): this API never builds an embedding from text. " +
|
|
1905
1907
|
"Every key is optional and a key you leave out keeps the value the view already has, the board settings included, so changing a filter never reopens the columns someone collapsed. " +
|
|
1906
1908
|
SAVED_FILTER_REFUSAL + " " + DOTTED_PATH_NAMES_UNCHECKED,
|
|
1907
1909
|
inputSchema: {
|
|
@@ -1915,7 +1917,7 @@ export function createServer(apiKey) {
|
|
|
1915
1917
|
.array(z.unknown())
|
|
1916
1918
|
.nullable()
|
|
1917
1919
|
.optional()
|
|
1918
|
-
.describe("Kanban views: the values whose column is collapsed. An array of column values, or null to show every column again.
|
|
1920
|
+
.describe("Kanban views: the values whose column is collapsed. An array of column values, or null to show every column again. lighthouse_crm_get_view returns the current ones."),
|
|
1919
1921
|
kanban_summary: z
|
|
1920
1922
|
.record(z.unknown())
|
|
1921
1923
|
.nullable()
|
|
@@ -1968,7 +1970,7 @@ export function createServer(apiKey) {
|
|
|
1968
1970
|
title: "Get Current User",
|
|
1969
1971
|
annotations: READ_ONLY,
|
|
1970
1972
|
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). " +
|
|
1971
|
-
"It is the same object lighthouse_workspace_get_user answers for anyone else
|
|
1973
|
+
"It is the same object lighthouse_workspace_get_user answers for anyone else.",
|
|
1972
1974
|
inputSchema: {},
|
|
1973
1975
|
}, async () => {
|
|
1974
1976
|
try {
|
|
@@ -2096,8 +2098,8 @@ export function createServer(apiKey) {
|
|
|
2096
2098
|
title: "List Discovery Data Fields",
|
|
2097
2099
|
annotations: READ_ONLY,
|
|
2098
2100
|
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. " +
|
|
2099
|
-
"These are OUTPUT/data fields, NOT filter keys.
|
|
2100
|
-
"To keep the response small it does NOT include option values: when a field has has_options=true
|
|
2101
|
+
"These are OUTPUT/data fields, NOT filter keys. A search FILTERS on the prefixed company_* / person_* keys listed in the `filters` parameter of the discovery search tools. " +
|
|
2102
|
+
"To keep the response small it does NOT include option values: when a field has has_options=true, lighthouse_discovery_list_options with that permalink returns its allowed values (as do the dedicated lighthouse_discovery_list_industries / lighthouse_discovery_list_locations / lighthouse_discovery_list_funding_types / lighthouse_discovery_list_tags tools). " +
|
|
2101
2103
|
"Calling this is optional and NOT required before searching. These are Lighthouse's global database fields, not your CRM fields.",
|
|
2102
2104
|
inputSchema: {
|
|
2103
2105
|
record_type: z
|
|
@@ -2123,9 +2125,9 @@ export function createServer(apiKey) {
|
|
|
2123
2125
|
title: "List Discovery Field Options",
|
|
2124
2126
|
annotations: READ_ONLY,
|
|
2125
2127
|
description: "Get the allowed values (value, label, color) for a single discovery select / multi_select field. " +
|
|
2126
|
-
"
|
|
2128
|
+
"WHEN TO USE: lighthouse_discovery_list_attributes shows has_options=true for a field and its valid values are needed. " +
|
|
2127
2129
|
"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). " +
|
|
2128
|
-
"
|
|
2130
|
+
"The large shared taxonomies also have dedicated tools: lighthouse_discovery_list_industries, lighthouse_discovery_list_locations, lighthouse_discovery_list_funding_types, lighthouse_discovery_list_tags.",
|
|
2129
2131
|
inputSchema: {
|
|
2130
2132
|
record_type: z.enum(["company", "person"]).describe("Entity type the field belongs to"),
|
|
2131
2133
|
field_permalink: z.string().describe("Field permalink (from lighthouse_discovery_list_attributes)"),
|
|
@@ -2148,13 +2150,13 @@ export function createServer(apiKey) {
|
|
|
2148
2150
|
annotations: READ_ONLY,
|
|
2149
2151
|
description: "Search Lighthouse's global company database to DISCOVER new companies. " +
|
|
2150
2152
|
"This searches Lighthouse's enriched database (millions of companies), NOT your CRM. " +
|
|
2151
|
-
"
|
|
2152
|
-
"
|
|
2153
|
-
"
|
|
2154
|
-
"
|
|
2155
|
-
"
|
|
2156
|
-
"The COMPLETE, authoritative list of company_* filter keys (every field with its data type and where its values come from) is at the END of the `filters` parameter description below.
|
|
2157
|
-
"The response's `meta` includes `total` (the match count, e.g. \"247\", or \"1k+\" when there are 1000+) and `ignored_filter_fields` (any filter key that was NOT recognised, each with a did_you_mean suggestion when a close match can be computed).
|
|
2153
|
+
"Filters take ONLY the company_* filter keys listed in the `filters` parameter below. " +
|
|
2154
|
+
"lighthouse_discovery_list_attributes returns the data fields each result carries (e.g. categories, headquarter_country), which are NOT valid filter keys; if used to filter they are dropped and reported in the response's `ignored_filter_fields`. " +
|
|
2155
|
+
"Valid values for category filters (company_categories, company_sdg_impact) come from lighthouse_discovery_list_industries. " +
|
|
2156
|
+
"Country/region codes for location filters (company_hq_country, company_hq_region, and any other *_country/*_region key) come from lighthouse_discovery_list_locations. " +
|
|
2157
|
+
"Investor/past-company/education filters take ids, which lighthouse_discovery_search_entities (record_type 'investor', 'company', or 'school') resolves from names. " +
|
|
2158
|
+
"The COMPLETE, authoritative list of company_* filter keys (every field with its data type and where its values come from) is at the END of the `filters` parameter description below. Keys are matched exactly, with no variants (no _usd suffix) or abbreviations (hq vs headquarter). " +
|
|
2159
|
+
"The response's `meta` includes `total` (the match count, e.g. \"247\", or \"1k+\" when there are 1000+) and `ignored_filter_fields` (any filter key that was NOT recognised, each with a did_you_mean suggestion when a close match can be computed). An empty `ignored_filter_fields` confirms every filter was applied." +
|
|
2158
2160
|
DISCOVERY_CRM_ID_NOTE,
|
|
2159
2161
|
inputSchema: {
|
|
2160
2162
|
filters: z
|
|
@@ -2179,13 +2181,13 @@ export function createServer(apiKey) {
|
|
|
2179
2181
|
annotations: READ_ONLY,
|
|
2180
2182
|
description: "Search Lighthouse's global people database to DISCOVER new people. " +
|
|
2181
2183
|
"This searches Lighthouse's enriched database, NOT your CRM. " +
|
|
2182
|
-
"
|
|
2183
|
-
"
|
|
2184
|
-
"
|
|
2185
|
-
"
|
|
2186
|
-
"
|
|
2187
|
-
"The COMPLETE, authoritative list of person_* filter keys (every field with its data type and where its values come from) is at the END of the `filters` parameter description below.
|
|
2188
|
-
"The response's `meta` includes `total` (the match count, e.g. \"247\", or \"1k+\" when there are 1000+) and `ignored_filter_fields` (any filter key that was NOT recognised, each with a did_you_mean suggestion when a close match can be computed).
|
|
2184
|
+
"Filters take ONLY the person_* filter keys listed in the `filters` parameter below. " +
|
|
2185
|
+
"lighthouse_discovery_list_attributes returns the data fields each result carries (e.g. country, categories), which are NOT valid filter keys; if used to filter they are dropped and reported in the response's `ignored_filter_fields`. " +
|
|
2186
|
+
"Valid values for category filters (person_current_categories, person_past_categories) come from lighthouse_discovery_list_industries; the same call returns stealth_predicted_areas, the values of the stealth signal's predicted area. " +
|
|
2187
|
+
"Country/region codes for location filters (person_country, person_region, and any other *_country/*_region key) come from lighthouse_discovery_list_locations. " +
|
|
2188
|
+
"Past-company/education filters take ids, which lighthouse_discovery_search_entities (record_type 'company' or 'school') resolves from names. " +
|
|
2189
|
+
"The COMPLETE, authoritative list of person_* filter keys (every field with its data type and where its values come from) is at the END of the `filters` parameter description below. Keys are matched exactly, with no variants or abbreviations. " +
|
|
2190
|
+
"The response's `meta` includes `total` (the match count, e.g. \"247\", or \"1k+\" when there are 1000+) and `ignored_filter_fields` (any filter key that was NOT recognised, each with a did_you_mean suggestion when a close match can be computed). An empty `ignored_filter_fields` confirms every filter was applied. " +
|
|
2189
2191
|
"People results are returned by founder relevance; custom sorting is NOT supported for people, so there is no sort parameter." +
|
|
2190
2192
|
DISCOVERY_CRM_ID_NOTE,
|
|
2191
2193
|
inputSchema: {
|
|
@@ -2209,11 +2211,11 @@ export function createServer(apiKey) {
|
|
|
2209
2211
|
title: "Semantic Search Companies",
|
|
2210
2212
|
annotations: READ_ONLY,
|
|
2211
2213
|
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. " +
|
|
2212
|
-
"
|
|
2213
|
-
"
|
|
2214
|
-
"
|
|
2215
|
-
"
|
|
2216
|
-
"The response's `meta` includes `total` (the match count, e.g. \"247\", or \"1k+\" when there are 1000+) and `ignored_filter_fields` (any filter key that was NOT recognised, each with a did_you_mean suggestion when a close match can be computed).
|
|
2214
|
+
"WHEN TO USE: 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'. " +
|
|
2215
|
+
"It combines with structured filters: `filters` takes 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. " +
|
|
2216
|
+
"lighthouse_discovery_search_companies (no `query`) covers exact attribute filters (a specific country, funding stage, headcount range) with no conceptual component. " +
|
|
2217
|
+
"This cannot satisfy an exact field the database does not expose, a guarantee of completeness, or ordering by a specific metric. " +
|
|
2218
|
+
"The response's `meta` includes `total` (the match count, e.g. \"247\", or \"1k+\" when there are 1000+) and `ignored_filter_fields` (any filter key that was NOT recognised, each with a did_you_mean suggestion when a close match can be computed). An empty `ignored_filter_fields` confirms every filter was applied. " +
|
|
2217
2219
|
"`query` is required; everything else is optional." +
|
|
2218
2220
|
DISCOVERY_CRM_ID_NOTE,
|
|
2219
2221
|
inputSchema: {
|
|
@@ -2244,11 +2246,11 @@ export function createServer(apiKey) {
|
|
|
2244
2246
|
title: "Semantic Search People",
|
|
2245
2247
|
annotations: READ_ONLY,
|
|
2246
2248
|
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. " +
|
|
2247
|
-
"
|
|
2248
|
-
"
|
|
2249
|
-
"
|
|
2250
|
-
"
|
|
2251
|
-
"The response's `meta` includes `total` (the match count, e.g. \"247\", or \"1k+\" when there are 1000+) and `ignored_filter_fields` (any filter key that was NOT recognised, each with a did_you_mean suggestion when a close match can be computed).
|
|
2249
|
+
"WHEN TO USE: 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'. " +
|
|
2250
|
+
"It combines with structured filters: `filters` takes 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. " +
|
|
2251
|
+
"lighthouse_discovery_search_people (no `query`) covers exact attribute filters with no conceptual component. " +
|
|
2252
|
+
"This cannot satisfy an exact field the database does not expose, a guarantee of completeness, or ordering by a specific metric. " +
|
|
2253
|
+
"The response's `meta` includes `total` (the match count, e.g. \"247\", or \"1k+\" when there are 1000+) and `ignored_filter_fields` (any filter key that was NOT recognised, each with a did_you_mean suggestion when a close match can be computed). An empty `ignored_filter_fields` confirms every filter was applied. " +
|
|
2252
2254
|
"`query` is required; everything else is optional." +
|
|
2253
2255
|
DISCOVERY_CRM_ID_NOTE,
|
|
2254
2256
|
inputSchema: {
|
|
@@ -2282,7 +2284,7 @@ export function createServer(apiKey) {
|
|
|
2282
2284
|
"Returns enriched company profiles for matching domains. Max 1000 domains per request. " +
|
|
2283
2285
|
"Each returned profile is condensed (the long growth and workforce history and the activity-signal " +
|
|
2284
2286
|
"feed are omitted, and the associated-people list is capped) to keep the response compact; for a " +
|
|
2285
|
-
"single company's full profile
|
|
2287
|
+
"single company's full profile, see lighthouse_discovery_get_company. " +
|
|
2286
2288
|
"Consumes API entity credits for each new company returned." +
|
|
2287
2289
|
DISCOVERY_CRM_ID_NOTE,
|
|
2288
2290
|
inputSchema: {
|
|
@@ -2306,7 +2308,7 @@ export function createServer(apiKey) {
|
|
|
2306
2308
|
"Returns enriched company profiles for matching LinkedIn company pages. Max 1000 URLs per request. " +
|
|
2307
2309
|
"Each returned profile is condensed (the long growth and workforce history and the activity-signal " +
|
|
2308
2310
|
"feed are omitted, and the associated-people list is capped) to keep the response compact; for a " +
|
|
2309
|
-
"single company's full profile
|
|
2311
|
+
"single company's full profile, see lighthouse_discovery_get_company. " +
|
|
2310
2312
|
"Consumes API entity credits for each new company returned." +
|
|
2311
2313
|
DISCOVERY_CRM_ID_NOTE,
|
|
2312
2314
|
inputSchema: {
|
|
@@ -2329,7 +2331,7 @@ export function createServer(apiKey) {
|
|
|
2329
2331
|
description: "Look up people in Lighthouse's global database by LinkedIn profile URL(s). " +
|
|
2330
2332
|
"Returns enriched person profiles for matching LinkedIn pages. Max 1000 URLs per request. " +
|
|
2331
2333
|
"Each returned profile is condensed (the activity-signal feed is omitted and the associated-companies " +
|
|
2332
|
-
"and education lists are capped) to keep the response compact; for a single person's full profile
|
|
2334
|
+
"and education lists are capped) to keep the response compact; for a single person's full profile, see " +
|
|
2333
2335
|
"lighthouse_discovery_get_person. " +
|
|
2334
2336
|
"Consumes API entity credits for each new person returned." +
|
|
2335
2337
|
DISCOVERY_CRM_ID_NOTE,
|
|
@@ -2353,7 +2355,7 @@ export function createServer(apiKey) {
|
|
|
2353
2355
|
description: "List all saved discovery searches in the workspace. " +
|
|
2354
2356
|
"Returns saved searches you own and workspace-shared searches. " +
|
|
2355
2357
|
"Each search includes its filters and type (company or person). " +
|
|
2356
|
-
"
|
|
2358
|
+
"lighthouse_discovery_list_search_results runs a saved search by this search ID.",
|
|
2357
2359
|
inputSchema: {},
|
|
2358
2360
|
}, async () => {
|
|
2359
2361
|
try {
|
|
@@ -2392,7 +2394,7 @@ export function createServer(apiKey) {
|
|
|
2392
2394
|
title: "List Location Options",
|
|
2393
2395
|
annotations: READ_ONLY,
|
|
2394
2396
|
description: "Get available location options (countries and regions) for discovery filters. " +
|
|
2395
|
-
"
|
|
2397
|
+
"WHEN TO USE: getting valid country/region values for filtering by location fields " +
|
|
2396
2398
|
"(company_hq_country, company_hq_region, person_country, person_region, company_investor_country, etc.). " +
|
|
2397
2399
|
"Returns {countries: [{name, value, flag}], regions: [{name, value, flag}], registration_countries: [{name, value, flag}]}. " +
|
|
2398
2400
|
"Use 'value' (alpha-2 codes for countries e.g. 'DE' for Germany, IDs for regions) " +
|
|
@@ -2413,9 +2415,9 @@ export function createServer(apiKey) {
|
|
|
2413
2415
|
title: "List Industry & Category Options",
|
|
2414
2416
|
annotations: READ_ONLY,
|
|
2415
2417
|
description: "Get available industry and category options for discovery filters. " +
|
|
2416
|
-
"
|
|
2418
|
+
"WHEN TO USE: getting valid category values for filtering by company_categories, company_sdg_impact, " +
|
|
2417
2419
|
"person_current_categories, person_past_categories, person_current_sdg_impact, or person_past_sdg_impact, " +
|
|
2418
|
-
"and
|
|
2420
|
+
"and for filtering stealth founders by predicted area (stealth_predicted_areas of person_signals, person_stealth_predicted_area). " +
|
|
2419
2421
|
"Returns {categories: [{name, value}], sdg_categories: [{name, value}], stealth_predicted_areas: [{name, value}]}. " +
|
|
2420
2422
|
"Categories include sectors like SaaS, B2B, FinTech, HealthTech, etc. " +
|
|
2421
2423
|
"Use 'value' (permalink string) in filter conditions with contains_any/not_contains_any/contains_all operators. " +
|
|
@@ -2435,7 +2437,7 @@ export function createServer(apiKey) {
|
|
|
2435
2437
|
title: "List Tag Options",
|
|
2436
2438
|
annotations: READ_ONLY,
|
|
2437
2439
|
description: "Get the tag options for discovery filters. " +
|
|
2438
|
-
"
|
|
2440
|
+
"WHEN TO USE: getting valid tag values for filtering by company_tags or person_tags. " +
|
|
2439
2441
|
"Returns {company_tags: [{value, name}], person_tags: [{value, name}]}. " +
|
|
2440
2442
|
"Use 'value' in filter conditions with contains_any/not_contains_any/contains_all operators.",
|
|
2441
2443
|
inputSchema: {},
|
|
@@ -2471,9 +2473,9 @@ export function createServer(apiKey) {
|
|
|
2471
2473
|
title: "Search Investors by Name",
|
|
2472
2474
|
annotations: READ_ONLY,
|
|
2473
2475
|
description: "Search for investors by name to get their UUIDs for use in discovery filters. " +
|
|
2474
|
-
"
|
|
2475
|
-
"Returns [{id, name, avatar_url}].
|
|
2476
|
-
"DEPRECATED:
|
|
2476
|
+
"The returned 'id' values are what company_investor filter conditions take (investor_multi_select type). " +
|
|
2477
|
+
"Returns [{id, name, avatar_url}]. The filter value is the id (or [{id, label}] objects). " +
|
|
2478
|
+
"DEPRECATED: lighthouse_discovery_search_entities with record_type 'investor' replaces it (better, typo-tolerant ranking). Kept for backward compatibility.",
|
|
2477
2479
|
inputSchema: {
|
|
2478
2480
|
query: z.string().describe("Investor name to search for (e.g. 'Sequoia', 'a16z')"),
|
|
2479
2481
|
},
|
|
@@ -2490,11 +2492,11 @@ export function createServer(apiKey) {
|
|
|
2490
2492
|
title: "Search Organizations by Name",
|
|
2491
2493
|
annotations: READ_ONLY,
|
|
2492
2494
|
description: "Search for organizations (companies) by name to get their UUIDs for use in discovery filters. " +
|
|
2493
|
-
"
|
|
2495
|
+
"The returned 'id' values are what work_experience_multi_select filter conditions take " +
|
|
2494
2496
|
"(company_team_past_companies, person_past_companies). " +
|
|
2495
2497
|
"Returns a THIN identity row [{id, name, avatar_url, domain, short_description, headcount, location, total_funding_amount_usd}]. This is NOT a full company profile. " +
|
|
2496
|
-
"
|
|
2497
|
-
"DEPRECATED:
|
|
2498
|
+
"The complete profile comes from lighthouse_discovery_get_company with this id (or lighthouse_discovery_get_entity_by_name). " +
|
|
2499
|
+
"DEPRECATED: lighthouse_discovery_search_entities with record_type 'company' replaces it (better, typo-tolerant ranking). Kept for backward compatibility.",
|
|
2498
2500
|
inputSchema: {
|
|
2499
2501
|
query: z.string().describe("Organization name to search for (e.g. 'Google', 'McKinsey')"),
|
|
2500
2502
|
},
|
|
@@ -2511,10 +2513,10 @@ export function createServer(apiKey) {
|
|
|
2511
2513
|
title: "Search Schools by Name",
|
|
2512
2514
|
annotations: READ_ONLY,
|
|
2513
2515
|
description: "Search for educational institutions by name to get their UUIDs for use in discovery filters. " +
|
|
2514
|
-
"
|
|
2516
|
+
"The returned 'id' values are what education_multi_select filter conditions take " +
|
|
2515
2517
|
"(company_team_education, person_education). " +
|
|
2516
2518
|
"Returns [{id, name, avatar_url}]. " +
|
|
2517
|
-
"DEPRECATED:
|
|
2519
|
+
"DEPRECATED: lighthouse_discovery_search_entities with record_type 'school' replaces it (better, typo-tolerant ranking). Kept for backward compatibility.",
|
|
2518
2520
|
inputSchema: {
|
|
2519
2521
|
query: z.string().describe("Institution name to search for (e.g. 'MIT', 'Stanford', 'Harvard')"),
|
|
2520
2522
|
},
|
|
@@ -2536,10 +2538,10 @@ export function createServer(apiKey) {
|
|
|
2536
2538
|
"searching 'Apple' returns the largest Apple first). " +
|
|
2537
2539
|
"Use this to: (a) get an id to drop into a discovery filter (past companies, education, investor " +
|
|
2538
2540
|
"filters), or (b) locate an entity before reading its full profile. " +
|
|
2539
|
-
"It searches ALL entities, including ones that do not appear in discovery result lists, so
|
|
2540
|
-
"
|
|
2541
|
-
"
|
|
2542
|
-
"lighthouse_discovery_get_person (people). " +
|
|
2541
|
+
"It searches ALL entities, including ones that do not appear in discovery result lists, so a match " +
|
|
2542
|
+
"is found if one exists. An id for a filter needs nothing further. " +
|
|
2543
|
+
"Full details come from lighthouse_discovery_get_company (companies) or " +
|
|
2544
|
+
"lighthouse_discovery_get_person (people) with the returned id. " +
|
|
2543
2545
|
"is_discoverable means the entity also appears in discovery result lists; when false you can still " +
|
|
2544
2546
|
"read its full profile by id, it just will not show up in list/search-results endpoints.",
|
|
2545
2547
|
inputSchema: {
|
|
@@ -2573,7 +2575,7 @@ export function createServer(apiKey) {
|
|
|
2573
2575
|
"history and the activity-signal feed are omitted, and the associated-people list is capped. " +
|
|
2574
2576
|
"Always returns data for an entity that exists, including companies not shown in discovery result " +
|
|
2575
2577
|
"lists, so there is no dead-end. " +
|
|
2576
|
-
"
|
|
2578
|
+
"The id comes from lighthouse_discovery_search_entities (record_type 'company'). " +
|
|
2577
2579
|
"This accepts only a company id (not a person, investor, or school id), and does not search by name. " +
|
|
2578
2580
|
"Each investor on a funding round carries is_lead: true when that investor led the round, false when " +
|
|
2579
2581
|
"it did not, never null. is_lead_inv is the same fact under the older name and can be null where " +
|
|
@@ -2600,7 +2602,7 @@ export function createServer(apiKey) {
|
|
|
2600
2602
|
"and the associated-companies and education lists are capped. " +
|
|
2601
2603
|
"Always returns data for an entity that exists, including people not shown in discovery result lists, " +
|
|
2602
2604
|
"so there is no dead-end. " +
|
|
2603
|
-
"
|
|
2605
|
+
"The id comes from lighthouse_discovery_search_entities (record_type 'person'). " +
|
|
2604
2606
|
"This accepts only a person id, and does not search by name. " +
|
|
2605
2607
|
"Each entry of associated_companies and education carries id, the company's or school's own id, which is what lighthouse_discovery_get_company takes. The same value is also under org_id, the name those two arrays have always used and which is kept for compatibility." +
|
|
2606
2608
|
DISCOVERY_CRM_ID_NOTE,
|
|
@@ -2660,7 +2662,7 @@ export function createServer(apiKey) {
|
|
|
2660
2662
|
description: "The detected events for one person in the global database, by person id: the signal feed lighthouse_discovery_get_person omits. " +
|
|
2661
2663
|
"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. " +
|
|
2662
2664
|
"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. " +
|
|
2663
|
-
"WHEN TO USE: \"is this founder starting something new\", \"did they leave their company\", \"what is this stealth founder building\", \"any recent moves by X\".
|
|
2665
|
+
"WHEN TO USE: \"is this founder starting something new\", \"did they leave their company\", \"what is this stealth founder building\", \"any recent moves by X\". lighthouse_discovery_get_person has the person's work history. " +
|
|
2664
2666
|
"A null or empty list means no events are recorded. Same id as lighthouse_discovery_get_person.",
|
|
2665
2667
|
inputSchema: {
|
|
2666
2668
|
id: z.string().describe("The person id (UUID) returned by lighthouse_discovery_search_entities"),
|
|
@@ -2681,10 +2683,10 @@ export function createServer(apiKey) {
|
|
|
2681
2683
|
"Resolves the single best-ranked match and returns its full profile in one call (equivalent to " +
|
|
2682
2684
|
"lighthouse_discovery_search_entities followed by lighthouse_discovery_get_company / get_person, combined). " +
|
|
2683
2685
|
"For investors and schools it returns the lightweight identity row (there is no deep profile for those). " +
|
|
2684
|
-
"It picks the single top match
|
|
2685
|
-
"
|
|
2686
|
-
"
|
|
2687
|
-
"or get_company / get_person with the id." +
|
|
2686
|
+
"It picks the single top match; lighthouse_discovery_search_entities lists the candidates for an ambiguous name, " +
|
|
2687
|
+
"and get_company / get_person read a chosen id. " +
|
|
2688
|
+
"A search/lookup result row is a thin identity row, not a profile: this tool, " +
|
|
2689
|
+
"or get_company / get_person with the id, returns the profile." +
|
|
2688
2690
|
DISCOVERY_CRM_ID_NOTE,
|
|
2689
2691
|
inputSchema: {
|
|
2690
2692
|
record_type: z
|
|
@@ -2705,22 +2707,22 @@ export function createServer(apiKey) {
|
|
|
2705
2707
|
"NAMES: you can send record_type ('company' | 'person' | 'deal' | a custom object permalink) instead of data_source / entity_type, is_custom instead of is_custom_field, and an aggregation in any case. Each is translated into the stored spelling the keys below describe. A widget answers BOTH: record_type beside data_source and entity_type, is_custom beside is_custom_field (aggregation is answered uppercase). Read record_type when you want one word whatever the widget was created with; the stored keys never move, and the config a widget answers can be sent back as it is. " +
|
|
2706
2708
|
"STANDARD (bar/line/pie/table/kpi): {data_source:'companies'|'deals'|'people' (the plural form is what the app stores; the singular is accepted too, and whichever you send is echoed back unchanged, with record_type beside it) or a custom object permalink (e.g. 'lp'; then ALSO set custom_object_permalink to the same permalink; it is required), report_type:'standard' (always set it explicitly), " +
|
|
2707
2709
|
"dimension:{field:'<field_key>', is_custom_field:boolean, time_grouping?:'day'|'week'|'month'|'quarter'|'year'}. REQUIRED for EVERY standard widget INCLUDING kpi (a kpi groups its measure by this dimension); omitting it fails with DIMENSION_REQUIRED. For a line chart the dimension field must be a date/datetime type. " +
|
|
2708
|
-
"measure:{aggregation:'COUNT'|'SUM'|'AVG'|'MIN'|'MAX'|'COUNT_DISTINCT', field:'*'|'<field_key>', is_custom_field:boolean}. Aggregations are stored UPPERCASE and accepted in any case. SUM/AVG/MIN/MAX require a numeric field (number/currency/rating/percentage); COUNT always counts records, so pair it with field:'*' only.
|
|
2710
|
+
"measure:{aggregation:'COUNT'|'SUM'|'AVG'|'MIN'|'MAX'|'COUNT_DISTINCT', field:'*'|'<field_key>', is_custom_field:boolean}. Aggregations are stored UPPERCASE and accepted in any case. SUM/AVG/MIN/MAX require a numeric field (number/currency/rating/percentage); COUNT always counts records, so pair it with field:'*' only. SUM/AVG over a multi-value (multi_select/multi_user/multi_record) dimension or segment over-counts the value; COUNT does not. " +
|
|
2709
2711
|
"segment_by?:{field:'<field_key>', is_custom_field:boolean} splits each bar into stacked series. It ONLY applies to a bar widget; it is ignored for line/pie/table/kpi. " +
|
|
2710
2712
|
"filter?:{operator:'AND'|'OR', conditions:[...], groups:[...]}, " +
|
|
2711
2713
|
"date_range?:{type:'relative', relative_period:'all_time'|'last_7_days'|'last_30_days'|'last_90_days'|'this_month'|'last_month'|'this_quarter'|'last_quarter'|'this_year'|'last_year'} " +
|
|
2712
|
-
"OR {type:'absolute', start_date:'YYYY-MM-DD', end_date:'YYYY-MM-DD'}. NOTE: date_range scopes the date FIELD you group by (the x-axis) when the dimension is a date/datetime/datetimez field, so it only makes sense for a date-dimension report; for a non-date dimension it falls back to the record's creation date,
|
|
2714
|
+
"OR {type:'absolute', start_date:'YYYY-MM-DD', end_date:'YYYY-MM-DD'}. NOTE: date_range scopes the date FIELD you group by (the x-axis) when the dimension is a date/datetime/datetimez field, so it only makes sense for a date-dimension report; for a non-date dimension it falls back to the record's creation date, and a filter condition scopes the report instead. " +
|
|
2713
2715
|
"included_stages?:['value1','value2'], show_axis_labels?:boolean, show_zeros?:boolean, sort_by_stage_order?:boolean}. " +
|
|
2714
2716
|
"INTERACTIONS (person/company data_source only): you can group or segment by an interaction date " +
|
|
2715
2717
|
"(first_email_date, last_email_date, first_linkedin_message_date, last_linkedin_message_date, first_meeting_date, last_meeting_date, first_interaction_date, last_interaction_date) " +
|
|
2716
2718
|
"or by first_interaction_type/last_interaction_type, all with is_custom_field:false; and you can measure lifetime interaction totals with " +
|
|
2717
2719
|
"measure.field:'__ix_count_all__'|'__ix_count_email__'|'__ix_count_linkedin__'|'__ix_count_meeting__' (is_custom_field:false; the aggregation is ignored). " +
|
|
2718
|
-
"FUNNEL: {report_type:'funnel', entity_type:'company'|'deal'|'person' (custom objects are NOT supported for funnel), data_source:'companies'|'deals'|'people' (plural form of entity_type;
|
|
2720
|
+
"FUNNEL: {report_type:'funnel', entity_type:'company'|'deal'|'person' (custom objects are NOT supported for funnel), data_source:'companies'|'deals'|'people' (plural form of entity_type; without it the widget opens with the wrong data source in the in-app editor; sending record_type instead fills both), stage_field:'<status field_key>', stage_field_is_custom:boolean (true when stage_field is a custom status field), " +
|
|
2719
2721
|
"included_stages?:['stage1','stage2'], date_range?:{...}, filter?:{...} (same format and field support as the standard filter above, including relation fields like list_entries and custom fields; it restricts which records the funnel counts)}. " +
|
|
2720
|
-
"HISTORICAL: {report_type:'stage_changes', entity_type:'company'|'deal'|'person' (custom objects are NOT supported for historical), data_source:'companies'|'deals'|'people' (plural form of entity_type
|
|
2722
|
+
"HISTORICAL: {report_type:'stage_changes', entity_type:'company'|'deal'|'person' (custom objects are NOT supported for historical), data_source:'companies'|'deals'|'people' (plural form of entity_type, required; sending record_type instead fills both), " +
|
|
2721
2723
|
"field_permalink:'<status or select field_key>' (one value at a time, standard or custom, such as a deal's stage; a multi-select field, or a field Lighthouse computes such as relationship_status, is refused), time_grouping?:'day'|'week'|'month'|'quarter'|'year', included_stages?:['stage1','stage2'], date_range?:{...}}. " +
|
|
2722
2724
|
"included_stages values must be the option VALUES from lighthouse_crm_list_attributes (not display labels). " +
|
|
2723
|
-
"
|
|
2725
|
+
"Valid field keys for dimension, measure, and stage_field come from lighthouse_crm_list_attributes.";
|
|
2724
2726
|
server.registerTool("lighthouse_reports_list_dashboards", {
|
|
2725
2727
|
title: "List Dashboards",
|
|
2726
2728
|
annotations: READ_ONLY,
|
|
@@ -2846,8 +2848,7 @@ export function createServer(apiKey) {
|
|
|
2846
2848
|
title: "Create Report Widget",
|
|
2847
2849
|
annotations: CREATES,
|
|
2848
2850
|
description: "Create a report widget (chart/table/KPI) on a dashboard. " +
|
|
2849
|
-
"
|
|
2850
|
-
"2) Build a config object using those keys, 3) Create the widget. " +
|
|
2851
|
+
"The config's field keys for the entity type come from lighthouse_crm_list_attributes. " +
|
|
2851
2852
|
"Widget types: bar, line, pie, table, kpi, funnel, area. " +
|
|
2852
2853
|
"Report types: standard (aggregate by field), funnel (pipeline conversion), stage_changes (historical transitions). " +
|
|
2853
2854
|
"The config's filter (and the older flat filters) uses the CRM filter format of lighthouse_crm_search_records, on the widget's record type. " +
|
|
@@ -2918,7 +2919,7 @@ export function createServer(apiKey) {
|
|
|
2918
2919
|
"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). " +
|
|
2919
2920
|
"For funnel reports: array of {stage, count} rows. " +
|
|
2920
2921
|
"For historical reports: array of {period, from_stage, to_stage, transition_count} rows. " +
|
|
2921
|
-
"Records with no value for the grouped field are gathered into one group whose label depends on the field and on where the grouping happens: '(empty)' when the field is the dimension, '(none)' when it is segment_by or a stage on a stage-changes report, '(unassigned)' on a person field (owner, created by, assignee, any user field) in either position, and '(not connected)' on the relationship-connector dimension. These are labels, not values. On a standard report `label_is_empty` (and `segment_is_empty` when the report is segmented) is true on that group and false on a group of real values
|
|
2922
|
+
"Records with no value for the grouped field are gathered into one group whose label depends on the field and on where the grouping happens: '(empty)' when the field is the dimension, '(none)' when it is segment_by or a stage on a stage-changes report, '(unassigned)' on a person field (owner, created by, assignee, any user field) in either position, and '(not connected)' on the relationship-connector dimension. These are labels, not values. On a standard report `label_is_empty` (and `segment_is_empty` when the report is segmented) is true on that group and false on a group of real values, so the flag, not the label, says a row has no value. A stored value spelled like one of those sentences shares that group, so the flag describes the group and not a single value. A funnel or stage-changes report has no flag, because '(none)' is a stage value there.",
|
|
2922
2923
|
inputSchema: {
|
|
2923
2924
|
dashboard_id: z.string().uuid().describe("Dashboard ID"),
|
|
2924
2925
|
widget_id: z.string().uuid().describe("Widget ID"),
|
|
@@ -2936,7 +2937,7 @@ export function createServer(apiKey) {
|
|
|
2936
2937
|
title: "Share Dashboard with Teams",
|
|
2937
2938
|
annotations: ADDS_IF_MISSING,
|
|
2938
2939
|
description: "Grant team-level access to a dashboard. Only the dashboard owner can share. " +
|
|
2939
|
-
"
|
|
2940
|
+
"Team IDs come from lighthouse_workspace_list_teams. " +
|
|
2940
2941
|
"Requires reports.edit permission. " +
|
|
2941
2942
|
SHARE_ALONG_RULE,
|
|
2942
2943
|
inputSchema: {
|
|
@@ -2980,7 +2981,7 @@ export function createServer(apiKey) {
|
|
|
2980
2981
|
title: "Share View with Teams",
|
|
2981
2982
|
annotations: ADDS_IF_MISSING,
|
|
2982
2983
|
description: "Grant team-level access to a CRM view or list view. Only the view owner can share. " +
|
|
2983
|
-
"
|
|
2984
|
+
"Team IDs come from lighthouse_workspace_list_teams. " +
|
|
2984
2985
|
"Requires views.share permission. " +
|
|
2985
2986
|
"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. " +
|
|
2986
2987
|
SHARE_ALONG_RULE,
|
|
@@ -3068,7 +3069,7 @@ export function createServer(apiKey) {
|
|
|
3068
3069
|
"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
3070
|
"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
3071
|
"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
|
-
"
|
|
3072
|
+
"lighthouse_sequences_get returns one sequence's steps, settings and sending accounts, and lighthouse_sequences_list_people who is in it. 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
3073
|
"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
3074
|
inputSchema: {
|
|
3074
3075
|
query: z.string().optional().describe("Part of the sequence's name, in any case"),
|
|
@@ -3093,7 +3094,7 @@ export function createServer(apiKey) {
|
|
|
3093
3094
|
"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
3095
|
"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
3096
|
"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).
|
|
3097
|
+
"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). lighthouse_sequences_list_people returns who is in it. " + SEQUENCE_NOT_FOUND_RULE,
|
|
3097
3098
|
inputSchema: {
|
|
3098
3099
|
id: z.string().uuid().describe("Sequence ID, from lighthouse_sequences_list or the sequence of a lighthouse_crm_list_record_sequences row"),
|
|
3099
3100
|
},
|
|
@@ -3137,12 +3138,12 @@ export function createServer(apiKey) {
|
|
|
3137
3138
|
title: "Add People to Sequence",
|
|
3138
3139
|
annotations: ADDS_IF_MISSING,
|
|
3139
3140
|
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
|
|
3141
|
+
"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 the people are waiting in Review, not contacted. " +
|
|
3141
3142
|
"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
3143
|
"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
3144
|
"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
3145
|
"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\").
|
|
3146
|
+
"WHEN TO USE: the user asks to put people into a sequence (\"add these founders to our outreach\"). lighthouse_sequences_list finds the sequence and lighthouse_crm_search_records the people. 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
3147
|
inputSchema: {
|
|
3147
3148
|
sequence_id: z.string().uuid().describe("Sequence ID, from lighthouse_sequences_list"),
|
|
3148
3149
|
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`),
|
|
@@ -3165,7 +3166,7 @@ export function createServer(apiKey) {
|
|
|
3165
3166
|
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
3167
|
"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
3168
|
"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\".
|
|
3169
|
+
"WHEN TO USE: \"stop the sequence for Jane\", \"pause everyone at Acme\", \"resume the people I paused\". lighthouse_sequences_remove_people takes someone out entirely, history included, or out of Review before anyone is contacted. Needs a role that may edit sequences and view people. " + SEQUENCE_NOT_FOUND_RULE,
|
|
3169
3170
|
inputSchema: {
|
|
3170
3171
|
sequence_id: z.string().uuid().describe("Sequence ID, from lighthouse_sequences_list"),
|
|
3171
3172
|
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})`),
|
|
@@ -3185,7 +3186,7 @@ export function createServer(apiKey) {
|
|
|
3185
3186
|
title: "Remove People from Sequence",
|
|
3186
3187
|
annotations: DESTRUCTIVE,
|
|
3187
3188
|
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
|
-
"
|
|
3189
|
+
"lighthouse_sequences_update_people with action stop ends a run and keeps its history. " +
|
|
3189
3190
|
"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
3191
|
"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
3192
|
inputSchema: {
|
|
@@ -3210,7 +3211,7 @@ export function createServer(apiKey) {
|
|
|
3210
3211
|
SEQUENCE_PERSON_STATUS + " " +
|
|
3211
3212
|
"meta.counts { live, ended } counts both groups whatever group asks. " +
|
|
3212
3213
|
"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
|
|
3214
|
+
"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, each with its own runs. Needs a role that may view sequences, people and the record's type. A record that does not exist answers 'Record not found'.",
|
|
3214
3215
|
inputSchema: {
|
|
3215
3216
|
record_type: z.enum(["person", "company", "deal"]).describe("person, company or deal (a custom object reaches no sequence)"),
|
|
3216
3217
|
record_id: z.string().uuid().describe("The record's ID"),
|
|
@@ -3286,14 +3287,14 @@ export function createServer(apiKey) {
|
|
|
3286
3287
|
"a CRM record (person = their email addresses, company = its domains); emails / domains (comma separated lists); " +
|
|
3287
3288
|
"query (matches WORDS in any order; a value in quotes stays a phrase); " +
|
|
3288
3289
|
"account_id; after / before dates (a date-only before includes that whole day); is_read. No filters = most recent threads. " +
|
|
3289
|
-
"
|
|
3290
|
+
"record_id or domains cover all correspondence with a company. " +
|
|
3290
3291
|
"Each row: { id, subject, date (the thread's latest activity), is_read, is_starred, has_attachments, has_drafts, " +
|
|
3291
3292
|
"message_count (messages, never drafts; null when there are none), participants [{ name, email }] (the OTHER people, " +
|
|
3292
3293
|
"the account's own address left out), last_message { id, date, direction (sent | received | draft), from { name, email }, snippet } " +
|
|
3293
3294
|
"(the preview lives here, there is none at the row's root), account { id, name, email, is_owner } (the mailbox it was read through) }. " +
|
|
3294
3295
|
"Live provider data, so no offset: after/before pick the window and a page continues with cursor = meta.next_cursor (same filters) while meta.has_more is true. Thread ids " +
|
|
3295
3296
|
"are account-specific: read one with lighthouse_inbox_email_thread, passing the row's account.id. " +
|
|
3296
|
-
"meta.accounts holds one entry per account the user can see, { id, name, email, is_owner, ok, error, count }
|
|
3297
|
+
"meta.accounts holds one entry per account the user can see, { id, name, email, is_owner, ok, error, count }, which shows per-account failures. " +
|
|
3297
3298
|
"meta.note explains an empty page (a record with no addresses, no mailbox connected).",
|
|
3298
3299
|
inputSchema: {
|
|
3299
3300
|
record_id: z.string().uuid().optional().describe("CRM record ID (requires record_type)"),
|
|
@@ -3328,7 +3329,7 @@ export function createServer(apiKey) {
|
|
|
3328
3329
|
"body (the full HTML, null when there is none), reply_to_message_id (the message this one answers, when the mailbox states it), " +
|
|
3329
3330
|
"is_read, is_starred, attachments [{ id, message_id, type (image | video | audio | file | other), filename, content_type, size }], " +
|
|
3330
3331
|
"inline_images (the files embedded in the body: the same keys plus cid), account { id, name, email, is_owner } }. Every key is always present. " +
|
|
3331
|
-
"
|
|
3332
|
+
"text is what was said and body carries the markup. lighthouse_inbox_email_attachment reads what an attached file SAYS, given its id, its message_id and the message's account.id. " +
|
|
3332
3333
|
"When the user can see only the metadata of that account, text and body are null, both file lists are empty and data.has_limited_access is true (false otherwise). Thread ids are " +
|
|
3333
3334
|
"account-specific: BOTH thread_id AND account_id (the thread row's account.id from " +
|
|
3334
3335
|
"lighthouse_inbox_email_threads) are required. " +
|
|
@@ -3364,7 +3365,7 @@ export function createServer(apiKey) {
|
|
|
3364
3365
|
annotations: READ_ONLY,
|
|
3365
3366
|
description: "What one email attachment SAYS, as text: PDFs, Word, Excel and PowerPoint files (and their OpenDocument versions), text, CSV and HTML. " +
|
|
3366
3367
|
"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\"). " +
|
|
3367
|
-
"
|
|
3368
|
+
"lighthouse_inbox_email_threads finds the message (a subject, a sender, a date) and lighthouse_inbox_email_thread carries its attachments [{ id, message_id, filename, ... }] and account { id }. This takes that attachment's id as attachment_id, its message_id, and the message's account.id as account_id. " +
|
|
3368
3369
|
"Returns { attachment { id, message_id, type, filename, content_type, size }, message { id, thread_id, subject }, account { id, name, email, is_owner }, kind, pages_total, text, reason, note }. " +
|
|
3369
3370
|
FILE_CONTENT_RULES +
|
|
3370
3371
|
" An attachment in an account the user may see only the metadata of answers not found.",
|
|
@@ -3404,7 +3405,7 @@ export function createServer(apiKey) {
|
|
|
3404
3405
|
"To draft a reply, pass reply_to_message_id (a MESSAGE id from lighthouse_inbox_email_thread, " +
|
|
3405
3406
|
"not a thread id) plus thread_id, and set the recipients explicitly: a reply does not inherit them. " +
|
|
3406
3407
|
"Write the message as text (plain words, kept as lines and paragraphs) or as body (HTML), never both. " +
|
|
3407
|
-
"Attachments are not supported here;
|
|
3408
|
+
"Attachments are not supported here; the REST API handles those. " +
|
|
3408
3409
|
"Returns the saved draft as a message with direction 'draft', the same key set a message has: " +
|
|
3409
3410
|
"{ id, thread_id, date, direction, from, to, cc, bcc, reply_to, subject, snippet, text, body, reply_to_message_id, " +
|
|
3410
3411
|
"is_read, is_starred, attachments, inline_images, account }.",
|
|
@@ -3414,8 +3415,8 @@ export function createServer(apiKey) {
|
|
|
3414
3415
|
cc: RECIPIENTS.optional().describe("Cc recipients: an address, a { name, email } object, or an array of either"),
|
|
3415
3416
|
bcc: RECIPIENTS.optional().describe("Bcc recipients: an address, a { name, email } object, or an array of either"),
|
|
3416
3417
|
subject: z.string().optional().describe("Subject line"),
|
|
3417
|
-
text: z.string().optional().describe("Message as plain words; lines and paragraphs are kept.
|
|
3418
|
-
body: z.string().optional().describe("Message body as HTML.
|
|
3418
|
+
text: z.string().optional().describe("Message as plain words; lines and paragraphs are kept. Either this or body, not both"),
|
|
3419
|
+
body: z.string().optional().describe("Message body as HTML. Either this or text, not both"),
|
|
3419
3420
|
thread_id: z.string().optional().describe("Save the draft inside this existing thread (id from lighthouse_inbox_email_threads)"),
|
|
3420
3421
|
reply_to_message_id: z.string().optional().describe("Message id this draft replies to, from lighthouse_inbox_email_thread. Required for the reply to thread correctly"),
|
|
3421
3422
|
},
|
|
@@ -3441,9 +3442,9 @@ export function createServer(apiKey) {
|
|
|
3441
3442
|
"{ name, linkedin (bare handle or null), linkedin_url, avatar_url, headline, connection_degree " +
|
|
3442
3443
|
"(first | second | third | out_of_network | self | null) }), " +
|
|
3443
3444
|
"last_message { id, date, direction (sent | received), from (the same person object), text }, " +
|
|
3444
|
-
"account { id, name, email (always null on LinkedIn), is_owner } }.
|
|
3445
|
-
"lighthouse_inbox_linkedin_chat_messages
|
|
3446
|
-
"
|
|
3445
|
+
"account { id, name, email (always null on LinkedIn), is_owner } }. " +
|
|
3446
|
+
"lighthouse_inbox_linkedin_chat_messages reads a conversation by chat id. " +
|
|
3447
|
+
"record_id and company_linkedin narrow the chats to one company. WHEN TO USE: 'what did we " +
|
|
3447
3448
|
"discuss with this founder on LinkedIn', 'LinkedIn conversations with anyone at Acme', 'my unread LinkedIn " +
|
|
3448
3449
|
"messages' (is_read false). Whether a chat sits in the user's own account or a colleague's is its " +
|
|
3449
3450
|
"account.is_owner, and account.id matches lighthouse_inbox_linkedin_accounts.",
|
|
@@ -3541,9 +3542,9 @@ export function createServer(apiKey) {
|
|
|
3541
3542
|
annotations: READ_ONLY,
|
|
3542
3543
|
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). " +
|
|
3543
3544
|
"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. " +
|
|
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\".
|
|
3545
|
+
"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\". What was DISCUSSED about a topic is in the notes of the likely meetings. " +
|
|
3545
3546
|
"Each row: { id, meeting_key (only when the meeting was recorded), ical_uid, title, description, location, start, end, is_all_day, is_recurring (a recurring meeting appears once per occurrence), status (confirmed | tentative | cancelled | null), meeting_url (the video link), organizer { name, email }, attendees [{ name, email, response_status }] (everyone invited, the calendar's own account included; response_status is accepted | declined | tentative | needs_action, where needs_action means that attendee has not answered yet; a missing title, description, location, name or email is null, never an empty string), account { id, name, email, is_owner } (the calendar it was read from), recording: null when not recorded, else { has_recording, has_transcript, has_notes, duration_seconds, language, recorded_by }, the same object lighthouse_inbox_calendar_meeting returns (recorded_by is the recorder as a workspace member { id, first_name, last_name, full_name, avatar_url, email }, or null; has_notes is true only when notes are ready and the user may read them) }. " +
|
|
3546
|
-
"A row with meeting_key is a recorded meeting:
|
|
3547
|
+
"A row with meeting_key is a recorded meeting: lighthouse_inbox_calendar_meeting_notes takes it and answers what was discussed and decided, lighthouse_inbox_calendar_meeting_transcript what was said. " +
|
|
3547
3548
|
"Paged with a CURSOR: pass meta.next_cursor as cursor (same filters) while meta.has_more is true; the window is walked from its near end in 30-day steps, so the first page always holds the newest (or soonest) meetings. A date-only before includes that whole day. meta.window { after, before } is the window that was read. meta.accounts lists every calendar the user can see, one entry of { id, name, email, is_owner, ok, error, count } (a calendar that could not be read is ok false with its error code while the others are still read). meta.complete false means a step could not be fully scanned in time or a calendar failed: filter by record, emails or domains, or narrow the window. meta.note explains an empty or partial result (no calendar connected, a record with no addresses, a calendar that could not be read).",
|
|
3548
3549
|
inputSchema: {
|
|
3549
3550
|
record_id: z.string().uuid().optional().describe("Meetings with this CRM record's people (requires record_type)"),
|
|
@@ -3591,7 +3592,7 @@ export function createServer(apiKey) {
|
|
|
3591
3592
|
title: "Get Meeting Notes",
|
|
3592
3593
|
annotations: READ_ONLY,
|
|
3593
3594
|
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). " +
|
|
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
|
|
3595
|
+
"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, which is only needed when the answer depends on exactly what was said. " +
|
|
3595
3596
|
"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,
|
|
3596
3597
|
inputSchema: {
|
|
3597
3598
|
meeting_key: z.string().describe("The meeting_key of a recorded meeting, " + MEETING_KEY_SOURCES),
|
|
@@ -3610,7 +3611,7 @@ export function createServer(apiKey) {
|
|
|
3610
3611
|
annotations: READ_ONLY,
|
|
3611
3612
|
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. " +
|
|
3612
3613
|
"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. " +
|
|
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,
|
|
3614
|
+
`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, meta.next_offset_chars sent as offset_chars reads on from there, and a window with has_more is not the end of the meeting. ` +
|
|
3614
3615
|
"Not found when the meeting has no transcript or the API key's user may not read it. " + MEETING_ACCESS_RULE,
|
|
3615
3616
|
inputSchema: {
|
|
3616
3617
|
meeting_key: z.string().describe("The meeting_key of a recorded meeting, " + MEETING_KEY_SOURCES),
|