@trylighthouse/mcp-server 0.1.3 → 0.1.4
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/api.d.ts +2 -2
- package/build/api.js +3 -3
- package/build/index.d.ts +2 -1
- package/build/index.js +83 -1390
- package/build/server.d.ts +7 -0
- package/build/server.js +1397 -0
- package/package.json +2 -1
package/build/server.js
ADDED
|
@@ -0,0 +1,1397 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { LighthouseAPI } from "./api.js";
|
|
4
|
+
function text(data) {
|
|
5
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
6
|
+
}
|
|
7
|
+
function err(message) {
|
|
8
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates an MCP server wired to a Lighthouse API client.
|
|
12
|
+
* For stdio (local/npm): apiKey is read from LIGHTHOUSE_API_KEY env var.
|
|
13
|
+
* For HTTP (deployed): apiKey is passed from the query param per session.
|
|
14
|
+
*/
|
|
15
|
+
export function createServer(apiKey) {
|
|
16
|
+
const api = new LighthouseAPI(apiKey);
|
|
17
|
+
const server = new McpServer({
|
|
18
|
+
name: "lighthouse",
|
|
19
|
+
version: "0.1.4",
|
|
20
|
+
});
|
|
21
|
+
// ─────────────────────────────────────────────────────────
|
|
22
|
+
// FILTER FORMAT REFERENCE (used in tool descriptions)
|
|
23
|
+
// ─────────────────────────────────────────────────────────
|
|
24
|
+
//
|
|
25
|
+
// Shared filter structure (CRM & Discovery):
|
|
26
|
+
// { "operator":"AND"|"OR", "conditions":[...], "groups":[...] }
|
|
27
|
+
// conditions: [{ "field":"<key>", "operator":"<op>", "value":<val> }]
|
|
28
|
+
// groups: nested filter blocks with their own operator/conditions/groups
|
|
29
|
+
//
|
|
30
|
+
// Value formats:
|
|
31
|
+
// - text operators (contains, eq, etc.): string value
|
|
32
|
+
// - number operators (gt, lt, eq, etc.): numeric value
|
|
33
|
+
// - CRM between (number): { "from": <num>, "to": <num> } OR [min, max]
|
|
34
|
+
// - CRM between (date): { "from": "YYYY-MM-DD", "to": "YYYY-MM-DD" }
|
|
35
|
+
// - Discovery between: { "min": <num>, "max": <num> }
|
|
36
|
+
// - contains_any / not_contains_any / contains_all: array of values e.g. ["US","GB"]
|
|
37
|
+
// - empty / not_empty: no value needed (omit value key)
|
|
38
|
+
// - boolean eq: true or false
|
|
39
|
+
// - date (before, after, on): ISO date string "2024-01-15"
|
|
40
|
+
//
|
|
41
|
+
// CRM operators (from FilterBuilder.jsx):
|
|
42
|
+
// text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty
|
|
43
|
+
// number/currency/rating: gt, gte, lt, lte, eq, neq, empty, not_empty
|
|
44
|
+
// date/datetimez: before, after, on
|
|
45
|
+
// select/status/user: contains_any, not_contains_any, empty, not_empty
|
|
46
|
+
// multi_select/category/multi_user/multi_record: contains_any, not_contains_any, contains_all, empty, not_empty
|
|
47
|
+
// record: contains_any, not_contains_any, empty, not_empty
|
|
48
|
+
// domain/email/phone: contains_any, not_contains_any, contains_all, empty, not_empty
|
|
49
|
+
// list_entries: contains_any, not_contains_any, contains_all
|
|
50
|
+
//
|
|
51
|
+
// Discovery operators (from resourceFilterConfig.js):
|
|
52
|
+
// short_text: contains, is
|
|
53
|
+
// long_text: contains, not_contains, empty, not_empty
|
|
54
|
+
// text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty
|
|
55
|
+
// number/currency: between, gt, gte, lt, lte, eq, neq, empty, not_empty
|
|
56
|
+
// range: between, gt, lt, empty, not_empty
|
|
57
|
+
// date: after, before
|
|
58
|
+
// select: contains_any, not_contains_any, empty, not_empty
|
|
59
|
+
// multi_select/investor_multi_select/work_experience_multi_select/education_multi_select/signal_select/language_select: contains_any, not_contains_any, contains_all, empty, not_empty
|
|
60
|
+
// funding_type_multi_select: contains_any, not_contains_any, empty, not_empty
|
|
61
|
+
// multi_user: contains_any, not_contains_any, contains_all, empty, not_empty
|
|
62
|
+
// boolean/checkbox: eq
|
|
63
|
+
// json: contains, not_contains, empty, not_empty
|
|
64
|
+
// ─────────────────────────────────────────────────────────
|
|
65
|
+
const CRM_FILTER_DESCRIPTION = "CRM filter object. Format: " +
|
|
66
|
+
'{"operator":"AND","conditions":[{"field":"<key>","operator":"<op>","value":<val>}],"groups":[]}. ' +
|
|
67
|
+
"IMPORTANT: First call lighthouse_crm_get_attributes to discover available field keys and types. " +
|
|
68
|
+
"Operators by field type — " +
|
|
69
|
+
"text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty; " +
|
|
70
|
+
"number/currency/rating: gt, gte, lt, lte, eq, neq, empty, not_empty; " +
|
|
71
|
+
"date: before, after, on; " +
|
|
72
|
+
"select/status/user: contains_any, not_contains_any, empty, not_empty; " +
|
|
73
|
+
"multi_select/category/multi_user/multi_record: contains_any, not_contains_any, contains_all, empty, not_empty; " +
|
|
74
|
+
"record: contains_any, not_contains_any, empty, not_empty; " +
|
|
75
|
+
"domain/email/phone: contains_any, not_contains_any, contains_all, empty, not_empty; " +
|
|
76
|
+
"list_entries: contains_any, not_contains_any, contains_all. " +
|
|
77
|
+
"Value formats: text ops → string; number ops → number; between (number) → {\"from\": min, \"to\": max}; " +
|
|
78
|
+
"between (date) → {\"from\": \"YYYY-MM-DD\", \"to\": \"YYYY-MM-DD\"}; " +
|
|
79
|
+
"contains_any/not_contains_any/contains_all → array of values; " +
|
|
80
|
+
"empty/not_empty → omit value; date ops → ISO date string. " +
|
|
81
|
+
"Groups allow nested AND/OR filter blocks with their own operator/conditions/groups. " +
|
|
82
|
+
"Do NOT use discovery field keys here — CRM fields come from lighthouse_crm_get_attributes.";
|
|
83
|
+
const CRM_SORT_DESCRIPTION = 'CRM sort array. Format: [{"field":"<key>","direction":"asc"|"desc"}]. ' +
|
|
84
|
+
"Field keys must match a sortable CRM attribute (text, number, date, select, status, currency, rating).";
|
|
85
|
+
const DISCOVERY_FILTER_DESCRIPTION = "Discovery filter object. Format: " +
|
|
86
|
+
'{"operator":"AND","conditions":[{"field":"<filter_id>","operator":"<op>","value":<val>}],"groups":[]}. ' +
|
|
87
|
+
"IMPORTANT: First call lighthouse_discovery_get_attributes to discover available filter_id values and types. " +
|
|
88
|
+
"Fields use prefixed keys (company_name, company_headcount, person_country, etc.). " +
|
|
89
|
+
"Operators by field type — " +
|
|
90
|
+
"short_text: contains, is; " +
|
|
91
|
+
"long_text: contains, not_contains, empty, not_empty; " +
|
|
92
|
+
"text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty; " +
|
|
93
|
+
"number/currency: between, gt, gte, lt, lte, eq, neq, empty, not_empty; " +
|
|
94
|
+
"range: between, gt, lt, empty, not_empty; " +
|
|
95
|
+
"date: after, before; " +
|
|
96
|
+
"select: contains_any, not_contains_any, empty, not_empty; " +
|
|
97
|
+
"multi_select/investor_multi_select/work_experience_multi_select/education_multi_select/signal_select/language_select: contains_any, not_contains_any, contains_all, empty, not_empty; " +
|
|
98
|
+
"funding_type_multi_select: contains_any, not_contains_any, empty, not_empty; " +
|
|
99
|
+
"multi_user: contains_any, not_contains_any, contains_all, empty, not_empty; " +
|
|
100
|
+
"boolean/checkbox: eq; " +
|
|
101
|
+
"json: contains, not_contains, empty, not_empty. " +
|
|
102
|
+
"Value formats: text ops → string; number ops → number; between → {\"min\": <num>, \"max\": <num>}; " +
|
|
103
|
+
"contains_any/not_contains_any/contains_all → array of values; empty/not_empty → omit value; boolean eq → true/false. " +
|
|
104
|
+
"Do NOT use CRM attribute keys here — discovery fields come from lighthouse_discovery_get_attributes.";
|
|
105
|
+
const DISCOVERY_SORT_DESCRIPTION = 'Discovery sort object. Format: {"by":"<field_name>","dir":"asc"|"desc"}. ' +
|
|
106
|
+
"Common sort fields: founded_on, total_funding_amount_usd, score. Default: score descending.";
|
|
107
|
+
// ─── CRM: Attributes ────────────────────────────────────
|
|
108
|
+
server.registerTool("lighthouse_crm_get_attributes", {
|
|
109
|
+
title: "Get CRM Attributes",
|
|
110
|
+
description: "Get custom field definitions for a CRM record type. " +
|
|
111
|
+
"ALWAYS call this before building filters for search_records, list_records, or creating/updating views. " +
|
|
112
|
+
"Returns field keys, types, and options needed to construct valid filter conditions. " +
|
|
113
|
+
"This queries YOUR WORKSPACE's custom fields (CRM data you manage).",
|
|
114
|
+
inputSchema: {
|
|
115
|
+
entity_type: z
|
|
116
|
+
.enum(["company", "person", "deal"])
|
|
117
|
+
.describe("Record type to get attributes for"),
|
|
118
|
+
},
|
|
119
|
+
}, async (params) => {
|
|
120
|
+
try {
|
|
121
|
+
const res = await api.get("/attributes", params);
|
|
122
|
+
return text(res);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
server.registerTool("lighthouse_crm_get_attribute", {
|
|
129
|
+
title: "Get CRM Attribute",
|
|
130
|
+
description: "Get a single custom field definition by its field_permalink.",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
field_permalink: z.string().describe("Field permalink (unique key) of the attribute"),
|
|
133
|
+
entity_type: z.enum(["company", "person", "deal", "task"]).describe("Record type (required)"),
|
|
134
|
+
},
|
|
135
|
+
}, async (params) => {
|
|
136
|
+
try {
|
|
137
|
+
const res = await api.get(`/attributes/${params.entity_type}/${params.field_permalink}`);
|
|
138
|
+
return text(res);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
server.registerTool("lighthouse_crm_create_attribute", {
|
|
145
|
+
title: "Create CRM Attribute",
|
|
146
|
+
description: "Create a new custom field in your CRM workspace. " +
|
|
147
|
+
"Types: text, number, currency, percentage, date, datetimez, select, multi_select, status, checkbox, url, email, phone, rating, record, multi_record, user, multi_user.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
label: z.string().describe("Display label for the field"),
|
|
150
|
+
entity_type: z.enum(["company", "person", "deal", "task"]).describe("Record type this field belongs to"),
|
|
151
|
+
data_type: z.string().describe("Field type (text, number, currency, percentage, date, datetimez, select, multi_select, status, checkbox, url, email, phone, rating, record, multi_record, user, multi_user)"),
|
|
152
|
+
record_type: z.enum(["company", "person", "deal", "task"]).optional().describe("For record/multi_record fields: which record type to link to"),
|
|
153
|
+
options: z
|
|
154
|
+
.array(z.record(z.unknown()))
|
|
155
|
+
.optional()
|
|
156
|
+
.describe("For select/multi_select/status fields: array of {value, label} option objects"),
|
|
157
|
+
},
|
|
158
|
+
}, async (params) => {
|
|
159
|
+
try {
|
|
160
|
+
const res = await api.post("/attributes", params);
|
|
161
|
+
return text(res);
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
server.registerTool("lighthouse_crm_update_attribute", {
|
|
168
|
+
title: "Update CRM Attribute",
|
|
169
|
+
description: "Update a custom field's label or configuration.",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
field_permalink: z.string().describe("Field permalink of the attribute to update"),
|
|
172
|
+
entity_type: z.enum(["company", "person", "deal", "task"]).describe("Record type (required)"),
|
|
173
|
+
label: z.string().optional().describe("New display label"),
|
|
174
|
+
},
|
|
175
|
+
}, async (params) => {
|
|
176
|
+
try {
|
|
177
|
+
const { field_permalink, entity_type, ...body } = params;
|
|
178
|
+
const res = await api.patch(`/attributes/${entity_type}/${field_permalink}`, body);
|
|
179
|
+
return text(res);
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
server.registerTool("lighthouse_crm_delete_attribute", {
|
|
186
|
+
title: "Delete CRM Attribute",
|
|
187
|
+
description: "Delete (archive) a custom field. Only custom fields can be deleted, not system fields.",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
field_permalink: z.string().describe("Field permalink of the attribute to delete"),
|
|
190
|
+
entity_type: z.enum(["company", "person", "deal", "task"]).describe("Record type (required)"),
|
|
191
|
+
},
|
|
192
|
+
}, async (params) => {
|
|
193
|
+
try {
|
|
194
|
+
const res = await api.delete(`/attributes/${params.entity_type}/${params.field_permalink}`);
|
|
195
|
+
return text(res);
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
server.registerTool("lighthouse_crm_get_color_palette", {
|
|
202
|
+
title: "Get Color Palette",
|
|
203
|
+
description: "Get the available color palette for select/multi_select/status field options. " +
|
|
204
|
+
"Returns an array of {hex_code, label} objects. " +
|
|
205
|
+
"Use this to discover valid hex color codes when creating or updating select options.",
|
|
206
|
+
inputSchema: {},
|
|
207
|
+
}, async () => {
|
|
208
|
+
try {
|
|
209
|
+
const res = await api.get("/attributes/colors");
|
|
210
|
+
return text(res);
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
// ─── CRM: Options (select field values) ─────────────────
|
|
217
|
+
server.registerTool("lighthouse_crm_list_options", {
|
|
218
|
+
title: "List CRM Field Options",
|
|
219
|
+
description: "Get select/multi_select field options. " +
|
|
220
|
+
"Use this to discover available values for select fields before setting them on records.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
entity_type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
223
|
+
field_permalink: z.string().describe("Field permalink of the select/multi_select field"),
|
|
224
|
+
},
|
|
225
|
+
}, async (params) => {
|
|
226
|
+
try {
|
|
227
|
+
const res = await api.get(`/options/${params.entity_type}/${params.field_permalink}`);
|
|
228
|
+
return text(res);
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
server.registerTool("lighthouse_crm_create_option", {
|
|
235
|
+
title: "Create CRM Field Option",
|
|
236
|
+
description: "Add a new option to a select/multi_select field. The option value is auto-generated from the label. Returns the updated list of options for the field.",
|
|
237
|
+
inputSchema: {
|
|
238
|
+
entity_type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
239
|
+
field_permalink: z.string().describe("Field permalink of the select/multi_select field"),
|
|
240
|
+
label: z.string().describe("Option display label"),
|
|
241
|
+
color: z.string().optional().describe("Option color (hex code from palette, auto-assigned if omitted)"),
|
|
242
|
+
},
|
|
243
|
+
}, async (params) => {
|
|
244
|
+
try {
|
|
245
|
+
const { entity_type, field_permalink, ...body } = params;
|
|
246
|
+
const res = await api.post(`/options/${entity_type}/${field_permalink}`, body);
|
|
247
|
+
return text(res);
|
|
248
|
+
}
|
|
249
|
+
catch (e) {
|
|
250
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
server.registerTool("lighthouse_crm_delete_option", {
|
|
254
|
+
title: "Delete CRM Field Option",
|
|
255
|
+
description: "Delete an option value from a select/multi_select field. Cannot delete system default options.",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
entity_type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
258
|
+
field_permalink: z.string().describe("Field permalink of the select/multi_select field"),
|
|
259
|
+
value: z.string().describe("Option value to delete"),
|
|
260
|
+
},
|
|
261
|
+
}, async (params) => {
|
|
262
|
+
try {
|
|
263
|
+
const res = await api.delete(`/options/${params.entity_type}/${params.field_permalink}/${params.value}`);
|
|
264
|
+
return text(res);
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
// ─── CRM: Records ────────────────────────────────────────
|
|
271
|
+
server.registerTool("lighthouse_crm_search_records", {
|
|
272
|
+
title: "Search CRM Records",
|
|
273
|
+
description: "Search and filter records in YOUR CRM workspace (companies, people, or deals you've added). " +
|
|
274
|
+
"Supports filters, sorting, pagination, and saved views. " +
|
|
275
|
+
"NEVER guess field keys — ALWAYS call lighthouse_crm_get_attributes first to get the exact field keys " +
|
|
276
|
+
"for the entity_type, then use those keys in your filter conditions. " +
|
|
277
|
+
"NOT for discovering new companies/people — use lighthouse_discovery_* tools for that.",
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
280
|
+
filters: z
|
|
281
|
+
.record(z.unknown())
|
|
282
|
+
.optional()
|
|
283
|
+
.describe(CRM_FILTER_DESCRIPTION),
|
|
284
|
+
sort: z
|
|
285
|
+
.array(z.record(z.unknown()))
|
|
286
|
+
.optional()
|
|
287
|
+
.describe(CRM_SORT_DESCRIPTION),
|
|
288
|
+
limit: z.number().min(1).max(100).default(25).describe("Records per page (max 100)"),
|
|
289
|
+
offset: z.number().min(0).default(0).describe("Number of records to skip"),
|
|
290
|
+
view_id: z
|
|
291
|
+
.string()
|
|
292
|
+
.uuid()
|
|
293
|
+
.optional()
|
|
294
|
+
.describe("Load saved filters/sort from a view. User-provided filters override view filters."),
|
|
295
|
+
},
|
|
296
|
+
}, async (params) => {
|
|
297
|
+
try {
|
|
298
|
+
const res = await api.post(`/records/${params.type}/search`, {
|
|
299
|
+
filters: params.filters,
|
|
300
|
+
sort: params.sort,
|
|
301
|
+
limit: params.limit,
|
|
302
|
+
offset: params.offset,
|
|
303
|
+
view_id: params.view_id,
|
|
304
|
+
});
|
|
305
|
+
return text(res);
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
return err(`Search failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
server.registerTool("lighthouse_crm_get_record", {
|
|
312
|
+
title: "Get CRM Record",
|
|
313
|
+
description: "Get a single CRM record by ID with all fields and relationships.",
|
|
314
|
+
inputSchema: {
|
|
315
|
+
type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
316
|
+
id: z.string().uuid().describe("Record ID"),
|
|
317
|
+
},
|
|
318
|
+
}, async (params) => {
|
|
319
|
+
try {
|
|
320
|
+
const res = await api.get(`/records/${params.type}/${params.id}`);
|
|
321
|
+
return text(res);
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
server.registerTool("lighthouse_crm_create_record", {
|
|
328
|
+
title: "Create CRM Record",
|
|
329
|
+
description: "Create a new CRM record. Required fields: " +
|
|
330
|
+
"company (name, domain), person (first_name, last_name), deal (name).",
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
333
|
+
data: z.record(z.unknown()).describe("Record fields (name, domain, first_name, last_name, etc.)"),
|
|
334
|
+
attributes: z
|
|
335
|
+
.record(z.unknown())
|
|
336
|
+
.optional()
|
|
337
|
+
.describe("Custom field values keyed by field_permalink (from lighthouse_crm_get_attributes)"),
|
|
338
|
+
},
|
|
339
|
+
}, async (params) => {
|
|
340
|
+
try {
|
|
341
|
+
const res = await api.post(`/records/${params.type}`, {
|
|
342
|
+
data: params.data,
|
|
343
|
+
attributes: params.attributes,
|
|
344
|
+
});
|
|
345
|
+
return text(res);
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
server.registerTool("lighthouse_crm_update_record", {
|
|
352
|
+
title: "Update CRM Record",
|
|
353
|
+
description: "Update an existing CRM record's fields.",
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
356
|
+
id: z.string().uuid().describe("Record ID"),
|
|
357
|
+
data: z.record(z.unknown()).describe("Fields to update"),
|
|
358
|
+
attributes: z.record(z.unknown()).optional().describe("Custom field values to update (keyed by field_permalink)"),
|
|
359
|
+
},
|
|
360
|
+
}, async (params) => {
|
|
361
|
+
try {
|
|
362
|
+
const res = await api.patch(`/records/${params.type}/${params.id}`, {
|
|
363
|
+
data: params.data,
|
|
364
|
+
attributes: params.attributes,
|
|
365
|
+
});
|
|
366
|
+
return text(res);
|
|
367
|
+
}
|
|
368
|
+
catch (e) {
|
|
369
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
server.registerTool("lighthouse_crm_delete_record", {
|
|
373
|
+
title: "Delete CRM Record",
|
|
374
|
+
description: "Permanently delete a CRM record.",
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
377
|
+
id: z.string().uuid().describe("Record ID"),
|
|
378
|
+
},
|
|
379
|
+
}, async (params) => {
|
|
380
|
+
try {
|
|
381
|
+
const res = await api.delete(`/records/${params.type}/${params.id}`);
|
|
382
|
+
return text(res);
|
|
383
|
+
}
|
|
384
|
+
catch (e) {
|
|
385
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
// ─── CRM: Lists ──────────────────────────────────────────
|
|
389
|
+
server.registerTool("lighthouse_crm_create_list", {
|
|
390
|
+
title: "Create CRM List",
|
|
391
|
+
description: "Create a new CRM list to organize records. Defaults to 'private' visibility. " +
|
|
392
|
+
"To share with specific teams after creation, use lighthouse_crm_share_list_with_teams. " +
|
|
393
|
+
"Requires lists.edit permission.",
|
|
394
|
+
inputSchema: {
|
|
395
|
+
name: z.string().describe("List name"),
|
|
396
|
+
type: z.enum(["company", "person", "deal"]).describe("Record type for this list"),
|
|
397
|
+
sharing: z.enum(["private", "workspace"]).default("private").optional().describe("Visibility: 'private' (default) or 'workspace'"),
|
|
398
|
+
color: z.string().optional().describe("Hex color code for the list"),
|
|
399
|
+
},
|
|
400
|
+
}, async (params) => {
|
|
401
|
+
try {
|
|
402
|
+
const res = await api.post("/lists", params);
|
|
403
|
+
return text(res);
|
|
404
|
+
}
|
|
405
|
+
catch (e) {
|
|
406
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
server.registerTool("lighthouse_crm_list_lists", {
|
|
410
|
+
title: "List CRM Lists",
|
|
411
|
+
description: "Get all CRM lists in your workspace.",
|
|
412
|
+
inputSchema: {},
|
|
413
|
+
}, async (params) => {
|
|
414
|
+
try {
|
|
415
|
+
const res = await api.get("/lists", params);
|
|
416
|
+
return text(res);
|
|
417
|
+
}
|
|
418
|
+
catch (e) {
|
|
419
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
server.registerTool("lighthouse_crm_get_list", {
|
|
423
|
+
title: "Get CRM List",
|
|
424
|
+
description: "Get a single CRM list by ID with its configuration.",
|
|
425
|
+
inputSchema: {
|
|
426
|
+
id: z.string().uuid().describe("List ID"),
|
|
427
|
+
},
|
|
428
|
+
}, async (params) => {
|
|
429
|
+
try {
|
|
430
|
+
const res = await api.get(`/lists/${params.id}`);
|
|
431
|
+
return text(res);
|
|
432
|
+
}
|
|
433
|
+
catch (e) {
|
|
434
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
server.registerTool("lighthouse_crm_update_list", {
|
|
438
|
+
title: "Update CRM List",
|
|
439
|
+
description: "Update a CRM list's name or configuration.",
|
|
440
|
+
inputSchema: {
|
|
441
|
+
id: z.string().uuid().describe("List ID"),
|
|
442
|
+
name: z.string().optional().describe("New list name"),
|
|
443
|
+
sharing: z.enum(["private", "workspace"]).optional().describe("New visibility setting"),
|
|
444
|
+
color: z.string().optional().describe("New hex color code"),
|
|
445
|
+
},
|
|
446
|
+
}, async (params) => {
|
|
447
|
+
try {
|
|
448
|
+
const { id, ...body } = params;
|
|
449
|
+
const res = await api.patch(`/lists/${id}`, body);
|
|
450
|
+
return text(res);
|
|
451
|
+
}
|
|
452
|
+
catch (e) {
|
|
453
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
server.registerTool("lighthouse_crm_delete_list", {
|
|
457
|
+
title: "Delete CRM List",
|
|
458
|
+
description: "Permanently delete a CRM list.",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
id: z.string().uuid().describe("List ID"),
|
|
461
|
+
},
|
|
462
|
+
}, async (params) => {
|
|
463
|
+
try {
|
|
464
|
+
const res = await api.delete(`/lists/${params.id}`);
|
|
465
|
+
return text(res);
|
|
466
|
+
}
|
|
467
|
+
catch (e) {
|
|
468
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
server.registerTool("lighthouse_crm_get_list_records", {
|
|
472
|
+
title: "Get List Records",
|
|
473
|
+
description: "Get records in a CRM list with optional filtering, sorting, and view support. " +
|
|
474
|
+
"Same filter format as search_records.",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
id: z.string().uuid().describe("List ID"),
|
|
477
|
+
filters: z.record(z.unknown()).optional().describe(CRM_FILTER_DESCRIPTION),
|
|
478
|
+
sort: z.array(z.record(z.unknown())).optional().describe(CRM_SORT_DESCRIPTION),
|
|
479
|
+
limit: z.number().min(1).max(100).default(25),
|
|
480
|
+
offset: z.number().min(0).default(0),
|
|
481
|
+
view_id: z.string().uuid().optional().describe("Load saved filters/sort from a view. User-provided filters override view filters."),
|
|
482
|
+
},
|
|
483
|
+
}, async (params) => {
|
|
484
|
+
try {
|
|
485
|
+
const { id, ...query } = params;
|
|
486
|
+
const res = await api.get(`/lists/${id}/records`, query);
|
|
487
|
+
return text(res);
|
|
488
|
+
}
|
|
489
|
+
catch (e) {
|
|
490
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
server.registerTool("lighthouse_crm_add_to_list", {
|
|
494
|
+
title: "Add Record to List",
|
|
495
|
+
description: "Add a CRM record to a list.",
|
|
496
|
+
inputSchema: {
|
|
497
|
+
list_id: z.string().uuid().describe("List ID"),
|
|
498
|
+
record_id: z.string().uuid().describe("Record ID to add"),
|
|
499
|
+
type: z.enum(["company", "person", "deal"]).describe("Record type"),
|
|
500
|
+
},
|
|
501
|
+
}, async (params) => {
|
|
502
|
+
try {
|
|
503
|
+
const res = await api.post(`/lists/${params.list_id}/records`, {
|
|
504
|
+
record_id: params.record_id,
|
|
505
|
+
type: params.type,
|
|
506
|
+
});
|
|
507
|
+
return text(res);
|
|
508
|
+
}
|
|
509
|
+
catch (e) {
|
|
510
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
server.registerTool("lighthouse_crm_remove_from_list", {
|
|
514
|
+
title: "Remove Record from List",
|
|
515
|
+
description: "Remove a record from a CRM list.",
|
|
516
|
+
inputSchema: {
|
|
517
|
+
list_id: z.string().uuid().describe("List ID"),
|
|
518
|
+
record_id: z.string().uuid().describe("Record ID to remove"),
|
|
519
|
+
},
|
|
520
|
+
}, async (params) => {
|
|
521
|
+
try {
|
|
522
|
+
const res = await api.delete(`/lists/${params.list_id}/records/${params.record_id}`);
|
|
523
|
+
return text(res);
|
|
524
|
+
}
|
|
525
|
+
catch (e) {
|
|
526
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
// ─── Sharing: List Teams ──────────────────────────────────
|
|
530
|
+
server.registerTool("lighthouse_crm_share_list_with_teams", {
|
|
531
|
+
title: "Share List with Teams",
|
|
532
|
+
description: "Grant team-level access to a CRM list. Only the list owner can share. " +
|
|
533
|
+
"Use lighthouse_workspace_list_teams to find team IDs first. " +
|
|
534
|
+
"Requires lists.share permission. " +
|
|
535
|
+
"Returns formatted list: {id, name, type, sharing, color, count, owner, shared_with_teams, created_at, updated_at}.",
|
|
536
|
+
inputSchema: {
|
|
537
|
+
id: z.string().uuid().describe("List ID"),
|
|
538
|
+
team_ids: z.array(z.string().uuid()).describe("Array of team UUIDs to grant access to"),
|
|
539
|
+
},
|
|
540
|
+
}, async (params) => {
|
|
541
|
+
try {
|
|
542
|
+
const { id, ...body } = params;
|
|
543
|
+
const res = await api.post(`/lists/${id}/teams`, body);
|
|
544
|
+
return text(res);
|
|
545
|
+
}
|
|
546
|
+
catch (e) {
|
|
547
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
server.registerTool("lighthouse_crm_revoke_list_from_teams", {
|
|
551
|
+
title: "Revoke List Team Access",
|
|
552
|
+
description: "Revoke team-level access from a CRM list. Only the list owner can revoke. " +
|
|
553
|
+
"Requires lists.share permission. " +
|
|
554
|
+
"Returns formatted list: {id, name, type, sharing, color, count, owner, shared_with_teams, created_at, updated_at}.",
|
|
555
|
+
inputSchema: {
|
|
556
|
+
id: z.string().uuid().describe("List ID"),
|
|
557
|
+
team_ids: z.array(z.string().uuid()).describe("Array of team UUIDs to revoke access from"),
|
|
558
|
+
},
|
|
559
|
+
}, async (params) => {
|
|
560
|
+
try {
|
|
561
|
+
const { id, ...body } = params;
|
|
562
|
+
const res = await api.delete(`/lists/${id}/teams`, body);
|
|
563
|
+
return text(res);
|
|
564
|
+
}
|
|
565
|
+
catch (e) {
|
|
566
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
server.registerTool("lighthouse_crm_update_list_sharing", {
|
|
570
|
+
title: "Update List Sharing Status",
|
|
571
|
+
description: "Change a CRM list's sharing visibility. Setting to 'private' also removes all team permissions. " +
|
|
572
|
+
"Only the list owner can change sharing. Requires lists.share permission. " +
|
|
573
|
+
"Returns formatted list: {id, name, type, sharing, color, count, owner, shared_with_teams, created_at, updated_at}.",
|
|
574
|
+
inputSchema: {
|
|
575
|
+
id: z.string().uuid().describe("List ID"),
|
|
576
|
+
sharing: z.enum(["private", "workspace"]).describe("New sharing visibility"),
|
|
577
|
+
},
|
|
578
|
+
}, async (params) => {
|
|
579
|
+
try {
|
|
580
|
+
const { id, ...body } = params;
|
|
581
|
+
const res = await api.patch(`/lists/${id}/sharing`, body);
|
|
582
|
+
return text(res);
|
|
583
|
+
}
|
|
584
|
+
catch (e) {
|
|
585
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
// ─── CRM: Notes ──────────────────────────────────────────
|
|
589
|
+
server.registerTool("lighthouse_crm_list_notes", {
|
|
590
|
+
title: "List Notes",
|
|
591
|
+
description: "Get notes for a CRM record.",
|
|
592
|
+
inputSchema: {
|
|
593
|
+
record_id: z.string().uuid().optional().describe("Record ID to filter notes by (required)"),
|
|
594
|
+
record_type: z.enum(["company", "person", "deal", "task"]).optional().describe("Record type: company, person, or deal (required)"),
|
|
595
|
+
limit: z.number().min(1).max(100).default(25),
|
|
596
|
+
offset: z.number().min(0).default(0),
|
|
597
|
+
},
|
|
598
|
+
}, async (params) => {
|
|
599
|
+
try {
|
|
600
|
+
const res = await api.get("/notes", params);
|
|
601
|
+
return text(res);
|
|
602
|
+
}
|
|
603
|
+
catch (e) {
|
|
604
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
server.registerTool("lighthouse_crm_create_note", {
|
|
608
|
+
title: "Create Note",
|
|
609
|
+
description: "Create a note on a CRM record. " +
|
|
610
|
+
"Content is HTML. For plain text, just pass it as-is. " +
|
|
611
|
+
"To @mention/tag workspace users in the note, you MUST: " +
|
|
612
|
+
"1) Call lighthouse_workspace_list_users first to find the user's ID, " +
|
|
613
|
+
"2) Include their user IDs in the tagged_users array, " +
|
|
614
|
+
"3) Include a mention span in the HTML content: " +
|
|
615
|
+
'<span data-type="mention" data-id="USER_UUID" data-label="First Last">@First Last</span>. ' +
|
|
616
|
+
"Tagged users receive an in-app notification.",
|
|
617
|
+
inputSchema: {
|
|
618
|
+
record_id: z.string().uuid().describe("Record ID to attach the note to"),
|
|
619
|
+
record_type: z.enum(["company", "person", "deal", "task"]).describe("Record type"),
|
|
620
|
+
title: z.string().describe("Note title"),
|
|
621
|
+
content: z.string().describe("Note content (plain text or HTML). Use <p> tags for paragraphs. For @mentions, include mention spans (see description)."),
|
|
622
|
+
tagged_users: z
|
|
623
|
+
.array(z.string().uuid())
|
|
624
|
+
.optional()
|
|
625
|
+
.describe("Array of workspace user UUIDs to tag/mention in the note. Must match mention spans in content."),
|
|
626
|
+
},
|
|
627
|
+
}, async (params) => {
|
|
628
|
+
try {
|
|
629
|
+
const res = await api.post("/notes", params);
|
|
630
|
+
return text(res);
|
|
631
|
+
}
|
|
632
|
+
catch (e) {
|
|
633
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
server.registerTool("lighthouse_crm_get_note", {
|
|
637
|
+
title: "Get Note",
|
|
638
|
+
description: "Get a single note by ID.",
|
|
639
|
+
inputSchema: {
|
|
640
|
+
id: z.string().uuid().describe("Note ID"),
|
|
641
|
+
},
|
|
642
|
+
}, async (params) => {
|
|
643
|
+
try {
|
|
644
|
+
const res = await api.get(`/notes/${params.id}`);
|
|
645
|
+
return text(res);
|
|
646
|
+
}
|
|
647
|
+
catch (e) {
|
|
648
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
server.registerTool("lighthouse_crm_delete_note", {
|
|
652
|
+
title: "Delete Note",
|
|
653
|
+
description: "Permanently delete a note.",
|
|
654
|
+
inputSchema: {
|
|
655
|
+
id: z.string().uuid().describe("Note ID"),
|
|
656
|
+
},
|
|
657
|
+
}, async (params) => {
|
|
658
|
+
try {
|
|
659
|
+
const res = await api.delete(`/notes/${params.id}`);
|
|
660
|
+
return text(res);
|
|
661
|
+
}
|
|
662
|
+
catch (e) {
|
|
663
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
// ─── CRM: Tasks ──────────────────────────────────────────
|
|
667
|
+
server.registerTool("lighthouse_crm_list_tasks", {
|
|
668
|
+
title: "List Tasks",
|
|
669
|
+
description: "List CRM tasks with optional filtering and sorting. " +
|
|
670
|
+
"The filters object supports: " +
|
|
671
|
+
'filter (CRM-style): {"operator":"AND","conditions":[{"field":"status","operator":"contains_any","value":["pending"]}]}; ' +
|
|
672
|
+
'sort: [{"field":"due_date","direction":"asc"}]; ' +
|
|
673
|
+
"Filterable fields: title, status, priority, due_date, description, assigned_to, records (person/company/deal), plus custom fields.",
|
|
674
|
+
inputSchema: {
|
|
675
|
+
filters: z
|
|
676
|
+
.object({
|
|
677
|
+
filter: z.record(z.any()).optional().describe("CRM filter object with operator/conditions/groups"),
|
|
678
|
+
sort: z.array(z.record(z.any())).optional().describe('Sort array: [{"field":"due_date","direction":"asc"}]'),
|
|
679
|
+
})
|
|
680
|
+
.optional()
|
|
681
|
+
.describe("Filter and sort options"),
|
|
682
|
+
limit: z.number().min(1).max(100).default(25),
|
|
683
|
+
offset: z.number().min(0).default(0),
|
|
684
|
+
},
|
|
685
|
+
}, async (params) => {
|
|
686
|
+
try {
|
|
687
|
+
const res = await api.get("/tasks", params);
|
|
688
|
+
return text(res);
|
|
689
|
+
}
|
|
690
|
+
catch (e) {
|
|
691
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
server.registerTool("lighthouse_crm_get_task", {
|
|
695
|
+
title: "Get Task",
|
|
696
|
+
description: "Get a single CRM task by ID.",
|
|
697
|
+
inputSchema: {
|
|
698
|
+
id: z.string().uuid().describe("Task ID"),
|
|
699
|
+
},
|
|
700
|
+
}, async (params) => {
|
|
701
|
+
try {
|
|
702
|
+
const res = await api.get(`/tasks/${params.id}`);
|
|
703
|
+
return text(res);
|
|
704
|
+
}
|
|
705
|
+
catch (e) {
|
|
706
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
server.registerTool("lighthouse_crm_create_task", {
|
|
710
|
+
title: "Create Task",
|
|
711
|
+
description: "Create a CRM task, optionally linked to records and assigned to users. " +
|
|
712
|
+
"The SQL expects assigned_to as [{id: uuid}] and records as {company: [{id}], person: [{id}], deal: [{id}]}.",
|
|
713
|
+
inputSchema: {
|
|
714
|
+
title: z.string().describe("Task title"),
|
|
715
|
+
description: z.string().optional().describe("Task description"),
|
|
716
|
+
status: z.string().optional().describe("Task status (e.g. 'pending', 'in_progress', 'completed')"),
|
|
717
|
+
priority: z.number().int().optional().describe("Priority as integer (e.g. 1=low, 2=medium, 3=high)"),
|
|
718
|
+
due_date: z.string().optional().describe("Due date (ISO 8601)"),
|
|
719
|
+
assigned_to: z
|
|
720
|
+
.array(z.string().uuid())
|
|
721
|
+
.optional()
|
|
722
|
+
.describe("Array of user UUIDs to assign the task to"),
|
|
723
|
+
records: z
|
|
724
|
+
.object({
|
|
725
|
+
company: z.array(z.string().uuid()).optional(),
|
|
726
|
+
person: z.array(z.string().uuid()).optional(),
|
|
727
|
+
deal: z.array(z.string().uuid()).optional(),
|
|
728
|
+
})
|
|
729
|
+
.optional()
|
|
730
|
+
.describe("Records to link: {company: [ids], person: [ids], deal: [ids]}"),
|
|
731
|
+
attributes: z.record(z.any()).optional().describe("Custom task field values as {field_permalink: value}"),
|
|
732
|
+
},
|
|
733
|
+
}, async (params) => {
|
|
734
|
+
try {
|
|
735
|
+
const { attributes, assigned_to, records, ...rest } = params;
|
|
736
|
+
const data = { ...rest };
|
|
737
|
+
if (assigned_to)
|
|
738
|
+
data.assigned_to = assigned_to.map((id) => ({ id }));
|
|
739
|
+
if (records) {
|
|
740
|
+
data.records = {
|
|
741
|
+
company: records.company?.map((id) => ({ id })) || [],
|
|
742
|
+
person: records.person?.map((id) => ({ id })) || [],
|
|
743
|
+
deal: records.deal?.map((id) => ({ id })) || [],
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
const res = await api.post("/tasks", { data, attributes });
|
|
747
|
+
return text(res);
|
|
748
|
+
}
|
|
749
|
+
catch (e) {
|
|
750
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
server.registerTool("lighthouse_crm_update_task", {
|
|
754
|
+
title: "Update Task",
|
|
755
|
+
description: "Update a CRM task. Provide only fields you want to change. " +
|
|
756
|
+
"assigned_to replaces all current assignees. records replaces all linked records.",
|
|
757
|
+
inputSchema: {
|
|
758
|
+
id: z.string().uuid().describe("Task ID"),
|
|
759
|
+
title: z.string().optional(),
|
|
760
|
+
description: z.string().optional(),
|
|
761
|
+
status: z.string().optional().describe("Task status (e.g. 'pending', 'in_progress', 'completed')"),
|
|
762
|
+
priority: z.number().int().optional().describe("Priority as integer (e.g. 1=low, 2=medium, 3=high)"),
|
|
763
|
+
due_date: z.string().optional(),
|
|
764
|
+
assigned_to: z
|
|
765
|
+
.array(z.string().uuid())
|
|
766
|
+
.optional()
|
|
767
|
+
.describe("Array of user UUIDs to assign (replaces current assignees)"),
|
|
768
|
+
records: z
|
|
769
|
+
.object({
|
|
770
|
+
company: z.array(z.string().uuid()).optional(),
|
|
771
|
+
person: z.array(z.string().uuid()).optional(),
|
|
772
|
+
deal: z.array(z.string().uuid()).optional(),
|
|
773
|
+
})
|
|
774
|
+
.optional()
|
|
775
|
+
.describe("Records to link (replaces current): {company: [ids], person: [ids], deal: [ids]}"),
|
|
776
|
+
attributes: z.record(z.any()).optional().describe("Custom task field values as {field_permalink: value}"),
|
|
777
|
+
},
|
|
778
|
+
}, async (params) => {
|
|
779
|
+
try {
|
|
780
|
+
const { id, attributes, assigned_to, records, ...rest } = params;
|
|
781
|
+
const data = { ...rest };
|
|
782
|
+
if (assigned_to)
|
|
783
|
+
data.assigned_to = assigned_to.map((id) => ({ id }));
|
|
784
|
+
if (records) {
|
|
785
|
+
data.records = {
|
|
786
|
+
company: records.company?.map((id) => ({ id })) || [],
|
|
787
|
+
person: records.person?.map((id) => ({ id })) || [],
|
|
788
|
+
deal: records.deal?.map((id) => ({ id })) || [],
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const res = await api.patch(`/tasks/${id}`, { data, attributes });
|
|
792
|
+
return text(res);
|
|
793
|
+
}
|
|
794
|
+
catch (e) {
|
|
795
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
server.registerTool("lighthouse_crm_delete_task", {
|
|
799
|
+
title: "Delete Task",
|
|
800
|
+
description: "Permanently delete a CRM task.",
|
|
801
|
+
inputSchema: {
|
|
802
|
+
id: z.string().uuid().describe("Task ID"),
|
|
803
|
+
},
|
|
804
|
+
}, async (params) => {
|
|
805
|
+
try {
|
|
806
|
+
const res = await api.delete(`/tasks/${params.id}`);
|
|
807
|
+
return text(res);
|
|
808
|
+
}
|
|
809
|
+
catch (e) {
|
|
810
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
// ─── CRM: Views ──────────────────────────────────────────
|
|
814
|
+
server.registerTool("lighthouse_crm_list_views", {
|
|
815
|
+
title: "List CRM Views",
|
|
816
|
+
description: "Get all saved CRM views. Views store filter/sort configurations that can be loaded in search_records.",
|
|
817
|
+
inputSchema: {
|
|
818
|
+
entity_type: z.enum(["company", "person", "deal"]).optional().describe("Filter by record type"),
|
|
819
|
+
},
|
|
820
|
+
}, async (params) => {
|
|
821
|
+
try {
|
|
822
|
+
const res = await api.get("/views", params);
|
|
823
|
+
return text(res);
|
|
824
|
+
}
|
|
825
|
+
catch (e) {
|
|
826
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
server.registerTool("lighthouse_crm_create_view", {
|
|
830
|
+
title: "Create CRM View",
|
|
831
|
+
description: "Create a saved CRM view with filters and sort configuration. " +
|
|
832
|
+
"Views are for YOUR CRM data only (not discovery). " +
|
|
833
|
+
"WORKFLOW: 1) Call lighthouse_crm_get_attributes to get field keys, " +
|
|
834
|
+
"2) Build a filter object using those keys, " +
|
|
835
|
+
"3) Create the view with the filter. " +
|
|
836
|
+
"The saved filters will auto-apply when search_records is called with this view_id.",
|
|
837
|
+
inputSchema: {
|
|
838
|
+
name: z.string().describe("View name"),
|
|
839
|
+
entity_type: z.enum(["company", "person", "deal"]).describe("Record type"),
|
|
840
|
+
filters: z
|
|
841
|
+
.record(z.unknown())
|
|
842
|
+
.optional()
|
|
843
|
+
.describe(CRM_FILTER_DESCRIPTION),
|
|
844
|
+
sort: z.array(z.record(z.unknown())).optional().describe(CRM_SORT_DESCRIPTION),
|
|
845
|
+
view_type: z.enum(["table", "kanban"]).default("table").describe("View layout type"),
|
|
846
|
+
grouped_by: z.string().optional().describe("Field key to group by (required for kanban views)"),
|
|
847
|
+
sharing: z.enum(["private", "workspace"]).default("private").describe("Visibility"),
|
|
848
|
+
team_ids: z.array(z.string().uuid()).optional().describe("Team UUIDs to share the view with. Replaces any existing team shares."),
|
|
849
|
+
},
|
|
850
|
+
}, async (params) => {
|
|
851
|
+
try {
|
|
852
|
+
const res = await api.post("/views", params);
|
|
853
|
+
return text(res);
|
|
854
|
+
}
|
|
855
|
+
catch (e) {
|
|
856
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
server.registerTool("lighthouse_crm_update_view", {
|
|
860
|
+
title: "Update CRM View",
|
|
861
|
+
description: "Update a saved CRM view's name, filters, sort, or sharing settings. " +
|
|
862
|
+
"Uses CRM filter format — call lighthouse_crm_get_attributes first if modifying filters.",
|
|
863
|
+
inputSchema: {
|
|
864
|
+
id: z.string().uuid().describe("View ID"),
|
|
865
|
+
name: z.string().optional(),
|
|
866
|
+
filters: z.record(z.unknown()).optional().describe(CRM_FILTER_DESCRIPTION),
|
|
867
|
+
sort: z.array(z.record(z.unknown())).optional().describe(CRM_SORT_DESCRIPTION),
|
|
868
|
+
view_type: z.enum(["table", "kanban"]).optional(),
|
|
869
|
+
grouped_by: z.string().optional().describe("Field key to group by (required for kanban views)"),
|
|
870
|
+
sharing: z.enum(["private", "workspace"]).optional(),
|
|
871
|
+
team_ids: z.array(z.string().uuid()).optional().describe("Team UUIDs to share the view with. Replaces any existing team shares."),
|
|
872
|
+
},
|
|
873
|
+
}, async (params) => {
|
|
874
|
+
try {
|
|
875
|
+
const { id, ...body } = params;
|
|
876
|
+
const res = await api.patch(`/views/${id}`, body);
|
|
877
|
+
return text(res);
|
|
878
|
+
}
|
|
879
|
+
catch (e) {
|
|
880
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
server.registerTool("lighthouse_crm_get_view", {
|
|
884
|
+
title: "Get CRM View",
|
|
885
|
+
description: "Get a single saved CRM view by ID.",
|
|
886
|
+
inputSchema: {
|
|
887
|
+
id: z.string().uuid().describe("View ID"),
|
|
888
|
+
},
|
|
889
|
+
}, async (params) => {
|
|
890
|
+
try {
|
|
891
|
+
const res = await api.get(`/views/${params.id}`);
|
|
892
|
+
return text(res);
|
|
893
|
+
}
|
|
894
|
+
catch (e) {
|
|
895
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
server.registerTool("lighthouse_crm_delete_view", {
|
|
899
|
+
title: "Delete CRM View",
|
|
900
|
+
description: "Permanently delete a saved CRM view.",
|
|
901
|
+
inputSchema: {
|
|
902
|
+
id: z.string().uuid().describe("View ID"),
|
|
903
|
+
},
|
|
904
|
+
}, async (params) => {
|
|
905
|
+
try {
|
|
906
|
+
const res = await api.delete(`/views/${params.id}`);
|
|
907
|
+
return text(res);
|
|
908
|
+
}
|
|
909
|
+
catch (e) {
|
|
910
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
// ─── Workspace: Users ───────────────────────────────────
|
|
914
|
+
server.registerTool("lighthouse_workspace_me", {
|
|
915
|
+
title: "Get Current User",
|
|
916
|
+
description: "Get the current authenticated user's profile (name, email, role, etc.).",
|
|
917
|
+
inputSchema: {},
|
|
918
|
+
}, async () => {
|
|
919
|
+
try {
|
|
920
|
+
const res = await api.get("/users/me");
|
|
921
|
+
return text(res);
|
|
922
|
+
}
|
|
923
|
+
catch (e) {
|
|
924
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
server.registerTool("lighthouse_workspace_list_users", {
|
|
928
|
+
title: "List Workspace Users",
|
|
929
|
+
description: "List users in the workspace. Use this to find user IDs for task assignment, note tagging, or filtering by user. " +
|
|
930
|
+
"Supports search by name/email.",
|
|
931
|
+
inputSchema: {
|
|
932
|
+
search: z.string().optional().describe("Search by name or email"),
|
|
933
|
+
team_id: z.string().uuid().optional().describe("Filter by team ID"),
|
|
934
|
+
role_id: z.string().uuid().optional().describe("Filter by role ID"),
|
|
935
|
+
limit: z.number().min(1).max(100).default(25),
|
|
936
|
+
offset: z.number().min(0).default(0),
|
|
937
|
+
},
|
|
938
|
+
}, async (params) => {
|
|
939
|
+
try {
|
|
940
|
+
const res = await api.get("/users", params);
|
|
941
|
+
return text(res);
|
|
942
|
+
}
|
|
943
|
+
catch (e) {
|
|
944
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
server.registerTool("lighthouse_workspace_get_user", {
|
|
948
|
+
title: "Get Workspace User",
|
|
949
|
+
description: "Get a specific workspace user's profile by ID.",
|
|
950
|
+
inputSchema: {
|
|
951
|
+
id: z.string().uuid().describe("User ID"),
|
|
952
|
+
},
|
|
953
|
+
}, async (params) => {
|
|
954
|
+
try {
|
|
955
|
+
const res = await api.get(`/users/${params.id}`);
|
|
956
|
+
return text(res);
|
|
957
|
+
}
|
|
958
|
+
catch (e) {
|
|
959
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
// ─── Workspace: Teams ───────────────────────────────────
|
|
963
|
+
server.registerTool("lighthouse_workspace_list_teams", {
|
|
964
|
+
title: "List Workspace Teams",
|
|
965
|
+
description: "List all teams in the workspace.",
|
|
966
|
+
inputSchema: {
|
|
967
|
+
limit: z.number().min(1).max(100).default(25),
|
|
968
|
+
offset: z.number().min(0).default(0),
|
|
969
|
+
},
|
|
970
|
+
}, async (params) => {
|
|
971
|
+
try {
|
|
972
|
+
const res = await api.get("/teams", params);
|
|
973
|
+
return text(res);
|
|
974
|
+
}
|
|
975
|
+
catch (e) {
|
|
976
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
server.registerTool("lighthouse_workspace_get_team", {
|
|
980
|
+
title: "Get Workspace Team",
|
|
981
|
+
description: "Get a specific team by ID with its members.",
|
|
982
|
+
inputSchema: {
|
|
983
|
+
id: z.string().uuid().describe("Team ID"),
|
|
984
|
+
},
|
|
985
|
+
}, async (params) => {
|
|
986
|
+
try {
|
|
987
|
+
const res = await api.get(`/teams/${params.id}`);
|
|
988
|
+
return text(res);
|
|
989
|
+
}
|
|
990
|
+
catch (e) {
|
|
991
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
// ─── Discovery: Attributes ───────────────────────────────
|
|
995
|
+
server.registerTool("lighthouse_discovery_get_attributes", {
|
|
996
|
+
title: "Get Discovery Attributes",
|
|
997
|
+
description: "Get available filter fields for Lighthouse's company/people database (discovery). " +
|
|
998
|
+
"ALWAYS call this before building filters for discovery_search_companies or discovery_search_people. " +
|
|
999
|
+
"Returns field definitions with filter_id, type, and options. " +
|
|
1000
|
+
"These are NOT your CRM fields — these are Lighthouse's global database fields.",
|
|
1001
|
+
inputSchema: {
|
|
1002
|
+
type: z
|
|
1003
|
+
.enum(["company", "person"])
|
|
1004
|
+
.optional()
|
|
1005
|
+
.describe("Entity type: 'company' or 'person'. If omitted, returns both as { company: [...], person: [...] }."),
|
|
1006
|
+
},
|
|
1007
|
+
}, async (params) => {
|
|
1008
|
+
try {
|
|
1009
|
+
const res = await api.get("/discovery/attributes", params);
|
|
1010
|
+
return text(res);
|
|
1011
|
+
}
|
|
1012
|
+
catch (e) {
|
|
1013
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
// ─── Discovery: Search ───────────────────────────────────
|
|
1017
|
+
server.registerTool("lighthouse_discovery_search_companies", {
|
|
1018
|
+
title: "Search Discovery Companies",
|
|
1019
|
+
description: "Search Lighthouse's global company database to DISCOVER new companies. " +
|
|
1020
|
+
"This searches Lighthouse's enriched database (millions of companies), NOT your CRM. " +
|
|
1021
|
+
"NEVER guess field keys — ALWAYS call lighthouse_discovery_get_attributes with type=company first " +
|
|
1022
|
+
"to get the exact filter_id values, then use those in your filter conditions. " +
|
|
1023
|
+
"Common fields: company_name, company_headcount, company_hq_country, company_total_funding, company_categories.",
|
|
1024
|
+
inputSchema: {
|
|
1025
|
+
filters: z
|
|
1026
|
+
.record(z.unknown())
|
|
1027
|
+
.optional()
|
|
1028
|
+
.describe(DISCOVERY_FILTER_DESCRIPTION),
|
|
1029
|
+
sort: z.record(z.unknown()).optional().describe(DISCOVERY_SORT_DESCRIPTION),
|
|
1030
|
+
skip: z.number().min(0).default(0).describe("Number of results to skip"),
|
|
1031
|
+
limit: z.number().min(1).max(100).default(25).describe("Results per page"),
|
|
1032
|
+
},
|
|
1033
|
+
}, async (params) => {
|
|
1034
|
+
try {
|
|
1035
|
+
const res = await api.post("/database/get-companies", params);
|
|
1036
|
+
return text(res);
|
|
1037
|
+
}
|
|
1038
|
+
catch (e) {
|
|
1039
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
server.registerTool("lighthouse_discovery_search_people", {
|
|
1043
|
+
title: "Search Discovery People",
|
|
1044
|
+
description: "Search Lighthouse's global people database to DISCOVER new people. " +
|
|
1045
|
+
"This searches Lighthouse's enriched database, NOT your CRM. " +
|
|
1046
|
+
"NEVER guess field keys — ALWAYS call lighthouse_discovery_get_attributes with type=person first " +
|
|
1047
|
+
"to get the exact filter_id values, then use those in your filter conditions. " +
|
|
1048
|
+
"Common fields: person_name, person_country, person_highlights, person_current_categories.",
|
|
1049
|
+
inputSchema: {
|
|
1050
|
+
filters: z
|
|
1051
|
+
.record(z.unknown())
|
|
1052
|
+
.optional()
|
|
1053
|
+
.describe(DISCOVERY_FILTER_DESCRIPTION),
|
|
1054
|
+
skip: z.number().min(0).default(0).describe("Number of results to skip"),
|
|
1055
|
+
limit: z.number().min(1).max(100).default(25).describe("Results per page"),
|
|
1056
|
+
},
|
|
1057
|
+
}, async (params) => {
|
|
1058
|
+
try {
|
|
1059
|
+
const res = await api.post("/database/get-people", params);
|
|
1060
|
+
return text(res);
|
|
1061
|
+
}
|
|
1062
|
+
catch (e) {
|
|
1063
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
// ─── Reports: Dashboards ─────────────────────────────────
|
|
1067
|
+
const WIDGET_CONFIG_DESCRIPTION = "Widget config object. Structure depends on report_type: " +
|
|
1068
|
+
"STANDARD: {data_source:'companies'|'deals'|'people', report_type:'standard', " +
|
|
1069
|
+
"dimension:{field:'<field_key>', is_custom_field:boolean, time_grouping?:'day'|'week'|'month'|'quarter'|'year'}, " +
|
|
1070
|
+
"measure:{aggregation:'COUNT'|'SUM'|'AVG'|'MIN'|'MAX', field:'*'|'<field_key>', is_custom_field:boolean}, " +
|
|
1071
|
+
"segment_by?:{field:'<field_key>', is_custom_field:boolean}, " +
|
|
1072
|
+
"filter?:{operator:'AND'|'OR', conditions:[...], groups:[...]}, " +
|
|
1073
|
+
"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'} " +
|
|
1074
|
+
"OR {type:'absolute', start_date:'YYYY-MM-DD', end_date:'YYYY-MM-DD'}, " +
|
|
1075
|
+
"included_stages?:['value1','value2'], show_axis_labels?:boolean, show_zeros?:boolean, sort_by_stage_order?:boolean}. " +
|
|
1076
|
+
"FUNNEL: {report_type:'funnel', entity_type:'company'|'deal'|'person', stage_field:'<field_key>', " +
|
|
1077
|
+
"included_stages?:['stage1','stage2'], date_range?:{...}, filter?:{...}}. " +
|
|
1078
|
+
"HISTORICAL: {report_type:'stage_changes', entity_type:'company'|'deal'|'person', " +
|
|
1079
|
+
"field_permalink:'<field_key>', time_grouping?:'day'|'week'|'month'|'quarter'|'year', date_range?:{...}}. " +
|
|
1080
|
+
"ALWAYS call lighthouse_crm_get_attributes first to discover valid field keys for dimension, measure, and stage_field.";
|
|
1081
|
+
server.registerTool("lighthouse_reports_list_dashboards", {
|
|
1082
|
+
title: "List Dashboards",
|
|
1083
|
+
description: "List all report dashboards accessible to the current user. " +
|
|
1084
|
+
"Returns dashboards you own, workspace-shared dashboards, and team-shared dashboards. " +
|
|
1085
|
+
"Each dashboard includes: id, name, description, sharing (private/workspace), is_owner, " +
|
|
1086
|
+
"owner {id, first_name, last_name, full_name}, widget_count, shared_with_teams, created_at, updated_at.",
|
|
1087
|
+
inputSchema: {},
|
|
1088
|
+
}, async () => {
|
|
1089
|
+
try {
|
|
1090
|
+
const res = await api.get("/dashboards");
|
|
1091
|
+
return text(res);
|
|
1092
|
+
}
|
|
1093
|
+
catch (e) {
|
|
1094
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
server.registerTool("lighthouse_reports_create_dashboard", {
|
|
1098
|
+
title: "Create Dashboard",
|
|
1099
|
+
description: "Create a new report dashboard. Dashboards contain report widgets (charts, tables, KPIs). " +
|
|
1100
|
+
"Requires reports.create permission. Returns the full dashboard object (same format as get dashboard).",
|
|
1101
|
+
inputSchema: {
|
|
1102
|
+
name: z.string().describe("Dashboard name"),
|
|
1103
|
+
description: z.string().optional().describe("Dashboard description"),
|
|
1104
|
+
sharing: z
|
|
1105
|
+
.enum(["private", "workspace"])
|
|
1106
|
+
.default("private")
|
|
1107
|
+
.describe("Visibility: 'private' (only you) or 'workspace' (all members)"),
|
|
1108
|
+
team_ids: z
|
|
1109
|
+
.array(z.string().uuid())
|
|
1110
|
+
.optional()
|
|
1111
|
+
.describe("Team IDs to share this dashboard with"),
|
|
1112
|
+
},
|
|
1113
|
+
}, async (params) => {
|
|
1114
|
+
try {
|
|
1115
|
+
const res = await api.post("/dashboards", params);
|
|
1116
|
+
return text(res);
|
|
1117
|
+
}
|
|
1118
|
+
catch (e) {
|
|
1119
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
server.registerTool("lighthouse_reports_get_dashboard", {
|
|
1123
|
+
title: "Get Dashboard",
|
|
1124
|
+
description: "Get a single dashboard by ID with all its widgets. " +
|
|
1125
|
+
"Returns: id, name, description, sharing, is_owner, owner {id, first_name, last_name, full_name}, " +
|
|
1126
|
+
"widget_count, shared_with_teams, widgets [{id, name, widget_type, config, created_at, updated_at}], created_at, updated_at.",
|
|
1127
|
+
inputSchema: {
|
|
1128
|
+
id: z.string().uuid().describe("Dashboard ID"),
|
|
1129
|
+
},
|
|
1130
|
+
}, async (params) => {
|
|
1131
|
+
try {
|
|
1132
|
+
const res = await api.get(`/dashboards/${params.id}`);
|
|
1133
|
+
return text(res);
|
|
1134
|
+
}
|
|
1135
|
+
catch (e) {
|
|
1136
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
server.registerTool("lighthouse_reports_update_dashboard", {
|
|
1140
|
+
title: "Update Dashboard",
|
|
1141
|
+
description: "Update a dashboard's name, description, or sharing. Only the dashboard owner can update. " +
|
|
1142
|
+
"Returns the full dashboard object (same format as get dashboard).",
|
|
1143
|
+
inputSchema: {
|
|
1144
|
+
id: z.string().uuid().describe("Dashboard ID"),
|
|
1145
|
+
name: z.string().optional().describe("New dashboard name"),
|
|
1146
|
+
description: z.string().optional().describe("New description"),
|
|
1147
|
+
sharing: z.enum(["private", "workspace"]).optional().describe("New visibility setting"),
|
|
1148
|
+
},
|
|
1149
|
+
}, async (params) => {
|
|
1150
|
+
try {
|
|
1151
|
+
const { id, ...body } = params;
|
|
1152
|
+
const res = await api.patch(`/dashboards/${id}`, body);
|
|
1153
|
+
return text(res);
|
|
1154
|
+
}
|
|
1155
|
+
catch (e) {
|
|
1156
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
server.registerTool("lighthouse_reports_delete_dashboard", {
|
|
1160
|
+
title: "Delete Dashboard",
|
|
1161
|
+
description: "Permanently delete a dashboard and all its widgets. Only the dashboard owner can delete.",
|
|
1162
|
+
inputSchema: {
|
|
1163
|
+
id: z.string().uuid().describe("Dashboard ID"),
|
|
1164
|
+
},
|
|
1165
|
+
}, async (params) => {
|
|
1166
|
+
try {
|
|
1167
|
+
const res = await api.delete(`/dashboards/${params.id}`);
|
|
1168
|
+
return text(res);
|
|
1169
|
+
}
|
|
1170
|
+
catch (e) {
|
|
1171
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
server.registerTool("lighthouse_reports_refresh_dashboard", {
|
|
1175
|
+
title: "Refresh Dashboard Data",
|
|
1176
|
+
description: "Refresh/fetch report data for all widgets in a dashboard. " +
|
|
1177
|
+
"Returns a map of { widget_id: report_data } for each widget.",
|
|
1178
|
+
inputSchema: {
|
|
1179
|
+
id: z.string().uuid().describe("Dashboard ID"),
|
|
1180
|
+
},
|
|
1181
|
+
}, async (params) => {
|
|
1182
|
+
try {
|
|
1183
|
+
const res = await api.post(`/dashboards/${params.id}/refresh`);
|
|
1184
|
+
return text(res);
|
|
1185
|
+
}
|
|
1186
|
+
catch (e) {
|
|
1187
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
// ─── Reports: Widgets ────────────────────────────────────
|
|
1191
|
+
server.registerTool("lighthouse_reports_list_widgets", {
|
|
1192
|
+
title: "List Dashboard Widgets",
|
|
1193
|
+
description: "Get all widgets for a dashboard. " +
|
|
1194
|
+
"Each widget includes: id, name, widget_type, config, created_at, updated_at.",
|
|
1195
|
+
inputSchema: {
|
|
1196
|
+
id: z.string().uuid().describe("Dashboard ID"),
|
|
1197
|
+
},
|
|
1198
|
+
}, async (params) => {
|
|
1199
|
+
try {
|
|
1200
|
+
const res = await api.get(`/dashboards/${params.id}/widgets`);
|
|
1201
|
+
return text(res);
|
|
1202
|
+
}
|
|
1203
|
+
catch (e) {
|
|
1204
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
server.registerTool("lighthouse_reports_create_widget", {
|
|
1208
|
+
title: "Create Report Widget",
|
|
1209
|
+
description: "Create a report widget (chart/table/KPI) on a dashboard. " +
|
|
1210
|
+
"WORKFLOW: 1) Call lighthouse_crm_get_attributes to discover field keys for the entity type, " +
|
|
1211
|
+
"2) Build a config object using those keys, 3) Create the widget. " +
|
|
1212
|
+
"Widget types: bar, line, pie, table, kpi, funnel, area. " +
|
|
1213
|
+
"Report types: standard (aggregate by field), funnel (pipeline conversion), stage_changes (historical transitions). " +
|
|
1214
|
+
"Returns: {id, name, widget_type, config, created_at, updated_at}.",
|
|
1215
|
+
inputSchema: {
|
|
1216
|
+
dashboard_id: z.string().uuid().describe("Dashboard ID to add widget to"),
|
|
1217
|
+
name: z.string().describe("Widget/report name"),
|
|
1218
|
+
widget_type: z
|
|
1219
|
+
.enum(["bar", "line", "pie", "table", "kpi", "funnel", "area"])
|
|
1220
|
+
.describe("Visualization type"),
|
|
1221
|
+
config: z.record(z.unknown()).describe(WIDGET_CONFIG_DESCRIPTION),
|
|
1222
|
+
position: z
|
|
1223
|
+
.record(z.unknown())
|
|
1224
|
+
.optional()
|
|
1225
|
+
.describe("Grid position: {x: number, y: number, w: number, h: number}"),
|
|
1226
|
+
},
|
|
1227
|
+
}, async (params) => {
|
|
1228
|
+
try {
|
|
1229
|
+
const { dashboard_id, ...body } = params;
|
|
1230
|
+
const res = await api.post(`/dashboards/${dashboard_id}/widgets`, body);
|
|
1231
|
+
return text(res);
|
|
1232
|
+
}
|
|
1233
|
+
catch (e) {
|
|
1234
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
server.registerTool("lighthouse_reports_update_widget", {
|
|
1238
|
+
title: "Update Report Widget",
|
|
1239
|
+
description: "Update a report widget's name, type, config, or position. " +
|
|
1240
|
+
"Only provide fields you want to change — others are preserved. " +
|
|
1241
|
+
"Returns: {id, name, widget_type, config, created_at, updated_at}.",
|
|
1242
|
+
inputSchema: {
|
|
1243
|
+
dashboard_id: z.string().uuid().describe("Dashboard ID"),
|
|
1244
|
+
widget_id: z.string().uuid().describe("Widget ID"),
|
|
1245
|
+
name: z.string().optional().describe("New widget name"),
|
|
1246
|
+
widget_type: z
|
|
1247
|
+
.enum(["bar", "line", "pie", "table", "kpi", "funnel", "area"])
|
|
1248
|
+
.optional()
|
|
1249
|
+
.describe("New visualization type"),
|
|
1250
|
+
config: z.record(z.unknown()).optional().describe(WIDGET_CONFIG_DESCRIPTION),
|
|
1251
|
+
position: z.record(z.unknown()).optional().describe("New grid position: {x, y, w, h}"),
|
|
1252
|
+
},
|
|
1253
|
+
}, async (params) => {
|
|
1254
|
+
try {
|
|
1255
|
+
const { dashboard_id, widget_id, ...body } = params;
|
|
1256
|
+
const res = await api.patch(`/dashboards/${dashboard_id}/widgets/${widget_id}`, body);
|
|
1257
|
+
return text(res);
|
|
1258
|
+
}
|
|
1259
|
+
catch (e) {
|
|
1260
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
server.registerTool("lighthouse_reports_delete_widget", {
|
|
1264
|
+
title: "Delete Report Widget",
|
|
1265
|
+
description: "Permanently delete a report widget from a dashboard.",
|
|
1266
|
+
inputSchema: {
|
|
1267
|
+
dashboard_id: z.string().uuid().describe("Dashboard ID"),
|
|
1268
|
+
widget_id: z.string().uuid().describe("Widget ID"),
|
|
1269
|
+
},
|
|
1270
|
+
}, async (params) => {
|
|
1271
|
+
try {
|
|
1272
|
+
const res = await api.delete(`/dashboards/${params.dashboard_id}/widgets/${params.widget_id}`);
|
|
1273
|
+
return text(res);
|
|
1274
|
+
}
|
|
1275
|
+
catch (e) {
|
|
1276
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
server.registerTool("lighthouse_reports_get_widget_data", {
|
|
1280
|
+
title: "Get Widget Report Data",
|
|
1281
|
+
description: "Fetch the aggregated report data for a specific widget. " +
|
|
1282
|
+
"Returns the computed data based on the widget's config (dimensions, measures, segments, filters). " +
|
|
1283
|
+
"For standard reports: array of {dimension, measure, segment?} rows. " +
|
|
1284
|
+
"For funnel reports: array of {stage, count} rows. " +
|
|
1285
|
+
"For historical reports: array of {period, from_stage, to_stage, transition_count} rows.",
|
|
1286
|
+
inputSchema: {
|
|
1287
|
+
dashboard_id: z.string().uuid().describe("Dashboard ID"),
|
|
1288
|
+
widget_id: z.string().uuid().describe("Widget ID"),
|
|
1289
|
+
},
|
|
1290
|
+
}, async (params) => {
|
|
1291
|
+
try {
|
|
1292
|
+
const res = await api.get(`/dashboards/${params.dashboard_id}/widgets/${params.widget_id}/data`);
|
|
1293
|
+
return text(res);
|
|
1294
|
+
}
|
|
1295
|
+
catch (e) {
|
|
1296
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
// ─── Sharing: Dashboard Teams ─────────────────────────────
|
|
1300
|
+
server.registerTool("lighthouse_reports_share_dashboard_with_teams", {
|
|
1301
|
+
title: "Share Dashboard with Teams",
|
|
1302
|
+
description: "Grant team-level access to a dashboard. Only the dashboard owner can share. " +
|
|
1303
|
+
"Use lighthouse_workspace_list_teams to find team IDs first. " +
|
|
1304
|
+
"Requires reports.edit permission.",
|
|
1305
|
+
inputSchema: {
|
|
1306
|
+
id: z.string().uuid().describe("Dashboard ID"),
|
|
1307
|
+
team_ids: z.array(z.string().uuid()).describe("Array of team UUIDs to grant access to"),
|
|
1308
|
+
},
|
|
1309
|
+
}, async (params) => {
|
|
1310
|
+
try {
|
|
1311
|
+
const { id, ...body } = params;
|
|
1312
|
+
const res = await api.post(`/dashboards/${id}/teams`, body);
|
|
1313
|
+
return text(res);
|
|
1314
|
+
}
|
|
1315
|
+
catch (e) {
|
|
1316
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
server.registerTool("lighthouse_reports_revoke_dashboard_from_teams", {
|
|
1320
|
+
title: "Revoke Dashboard Team Access",
|
|
1321
|
+
description: "Revoke team-level access from a dashboard. Only the dashboard owner can revoke. " +
|
|
1322
|
+
"Requires reports.edit permission.",
|
|
1323
|
+
inputSchema: {
|
|
1324
|
+
id: z.string().uuid().describe("Dashboard ID"),
|
|
1325
|
+
team_ids: z.array(z.string().uuid()).describe("Array of team UUIDs to revoke access from"),
|
|
1326
|
+
},
|
|
1327
|
+
}, async (params) => {
|
|
1328
|
+
try {
|
|
1329
|
+
const { id, ...body } = params;
|
|
1330
|
+
const res = await api.delete(`/dashboards/${id}/teams`, body);
|
|
1331
|
+
return text(res);
|
|
1332
|
+
}
|
|
1333
|
+
catch (e) {
|
|
1334
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
// ─── Sharing: View Teams ──────────────────────────────────
|
|
1338
|
+
server.registerTool("lighthouse_crm_share_view_with_teams", {
|
|
1339
|
+
title: "Share View with Teams",
|
|
1340
|
+
description: "Grant team-level access to a CRM view. Only the view owner can share. " +
|
|
1341
|
+
"Use lighthouse_workspace_list_teams to find team IDs first. " +
|
|
1342
|
+
"Requires views.share permission. " +
|
|
1343
|
+
"Returns formatted view: {id, name, entity_type, view_type, filters, sort, grouped_by, sharing, owner, shared_with_teams, created_at}.",
|
|
1344
|
+
inputSchema: {
|
|
1345
|
+
id: z.string().uuid().describe("View ID"),
|
|
1346
|
+
team_ids: z.array(z.string().uuid()).describe("Array of team UUIDs to grant access to"),
|
|
1347
|
+
},
|
|
1348
|
+
}, async (params) => {
|
|
1349
|
+
try {
|
|
1350
|
+
const { id, ...body } = params;
|
|
1351
|
+
const res = await api.post(`/views/${id}/teams`, body);
|
|
1352
|
+
return text(res);
|
|
1353
|
+
}
|
|
1354
|
+
catch (e) {
|
|
1355
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
server.registerTool("lighthouse_crm_revoke_view_from_teams", {
|
|
1359
|
+
title: "Revoke View Team Access",
|
|
1360
|
+
description: "Revoke team-level access from a CRM view. Only the view owner can revoke. " +
|
|
1361
|
+
"Requires views.share permission. " +
|
|
1362
|
+
"Returns formatted view: {id, name, entity_type, view_type, filters, sort, grouped_by, sharing, owner, shared_with_teams, created_at}.",
|
|
1363
|
+
inputSchema: {
|
|
1364
|
+
id: z.string().uuid().describe("View ID"),
|
|
1365
|
+
team_ids: z.array(z.string().uuid()).describe("Array of team UUIDs to revoke access from"),
|
|
1366
|
+
},
|
|
1367
|
+
}, async (params) => {
|
|
1368
|
+
try {
|
|
1369
|
+
const { id, ...body } = params;
|
|
1370
|
+
const res = await api.delete(`/views/${id}/teams`, body);
|
|
1371
|
+
return text(res);
|
|
1372
|
+
}
|
|
1373
|
+
catch (e) {
|
|
1374
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
server.registerTool("lighthouse_crm_update_view_sharing", {
|
|
1378
|
+
title: "Update View Sharing Status",
|
|
1379
|
+
description: "Change a CRM view's sharing visibility. Setting to 'private' also removes all team permissions. " +
|
|
1380
|
+
"Only the view owner can change sharing. Requires views.share permission. " +
|
|
1381
|
+
"Returns formatted view: {id, name, entity_type, view_type, filters, sort, grouped_by, sharing, owner, shared_with_teams, created_at}.",
|
|
1382
|
+
inputSchema: {
|
|
1383
|
+
id: z.string().uuid().describe("View ID"),
|
|
1384
|
+
sharing: z.enum(["private", "workspace"]).describe("New sharing visibility"),
|
|
1385
|
+
},
|
|
1386
|
+
}, async (params) => {
|
|
1387
|
+
try {
|
|
1388
|
+
const { id, ...body } = params;
|
|
1389
|
+
const res = await api.patch(`/views/${id}/sharing`, body);
|
|
1390
|
+
return text(res);
|
|
1391
|
+
}
|
|
1392
|
+
catch (e) {
|
|
1393
|
+
return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
return server;
|
|
1397
|
+
}
|