@trylighthouse/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # Lighthouse MCP Server
2
+
3
+ An [MCP](https://modelcontextprotocol.io/) server that connects AI assistants (Claude, Cursor, etc.) to the Lighthouse CRM API.
4
+
5
+ ## Setup
6
+
7
+ ### 1. Get your API key
8
+
9
+ Go to **Settings → API Keys** in Lighthouse and generate a key (starts with `lgt_live_`).
10
+
11
+ ### 2. Build
12
+
13
+ ```bash
14
+ cd mcp-server
15
+ npm install
16
+ npm run build
17
+ ```
18
+
19
+ ### 3. Configure your AI client
20
+
21
+ #### Claude Desktop
22
+
23
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "lighthouse": {
29
+ "command": "node",
30
+ "args": ["/absolute/path/to/lighthouse/mcp-server/build/index.js"],
31
+ "env": {
32
+ "LIGHTHOUSE_API_KEY": "lgt_live_..."
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ #### Claude Code
40
+
41
+ ```bash
42
+ claude mcp add lighthouse -- node /absolute/path/to/lighthouse/mcp-server/build/index.js
43
+ ```
44
+
45
+ Set the env var: `export LIGHTHOUSE_API_KEY=lgt_live_...`
46
+
47
+ #### Cursor
48
+
49
+ Add to `.cursor/mcp.json`:
50
+
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "lighthouse": {
55
+ "command": "node",
56
+ "args": ["/absolute/path/to/lighthouse/mcp-server/build/index.js"],
57
+ "env": {
58
+ "LIGHTHOUSE_API_KEY": "lgt_live_..."
59
+ }
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ### 4. After publishing to npm
66
+
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "lighthouse": {
71
+ "command": "npx",
72
+ "args": ["-y", "@trylighthouse/mcp-server@latest"],
73
+ "env": {
74
+ "LIGHTHOUSE_API_KEY": "lgt_live_..."
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Available Tools
82
+
83
+ | Tool | Description |
84
+ |------|-------------|
85
+ | `lighthouse_search_records` | Search/filter companies, people, or deals |
86
+ | `lighthouse_get_record` | Get a single record by ID |
87
+ | `lighthouse_create_record` | Create a new record |
88
+ | `lighthouse_update_record` | Update a record's fields |
89
+ | `lighthouse_delete_record` | Delete a record |
90
+ | `lighthouse_list_lists` | Get all CRM lists |
91
+ | `lighthouse_get_list_records` | Get records in a list |
92
+ | `lighthouse_add_to_list` | Add a record to a list |
93
+ | `lighthouse_remove_from_list` | Remove a record from a list |
94
+ | `lighthouse_list_notes` | Get notes for a record |
95
+ | `lighthouse_create_note` | Create a note on a record |
96
+ | `lighthouse_list_tasks` | Get CRM tasks |
97
+ | `lighthouse_create_task` | Create a task |
98
+ | `lighthouse_update_task` | Update a task |
99
+ | `lighthouse_list_views` | Get saved views |
100
+ | `lighthouse_list_attributes` | List custom field definitions |
101
+
102
+ ## Environment Variables
103
+
104
+ | Variable | Required | Description |
105
+ |----------|----------|-------------|
106
+ | `LIGHTHOUSE_API_KEY` | Yes | Your Lighthouse API key (`lgt_live_...`) |
107
+ | `LIGHTHOUSE_API_URL` | No | Override API base URL (default: `https://api.trylighthouse.vc`) |
108
+
109
+ ## Publishing
110
+
111
+ ```bash
112
+ npm login
113
+ npm publish --access public
114
+ ```
package/build/api.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Lightweight HTTP client for the Lighthouse API.
3
+ * Reads LIGHTHOUSE_API_KEY from environment.
4
+ */
5
+ export declare class LighthouseAPI {
6
+ private baseUrl;
7
+ private apiKey;
8
+ constructor();
9
+ private request;
10
+ get(path: string, params?: Record<string, unknown>): Promise<{
11
+ status: number;
12
+ data: unknown;
13
+ error: unknown;
14
+ meta?: unknown;
15
+ }>;
16
+ post(path: string, body?: Record<string, unknown>): Promise<{
17
+ status: number;
18
+ data: unknown;
19
+ error: unknown;
20
+ meta?: unknown;
21
+ }>;
22
+ patch(path: string, body?: Record<string, unknown>): Promise<{
23
+ status: number;
24
+ data: unknown;
25
+ error: unknown;
26
+ meta?: unknown;
27
+ }>;
28
+ delete(path: string, body?: Record<string, unknown>): Promise<{
29
+ status: number;
30
+ data: unknown;
31
+ error: unknown;
32
+ meta?: unknown;
33
+ }>;
34
+ }
package/build/api.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Lightweight HTTP client for the Lighthouse API.
3
+ * Reads LIGHTHOUSE_API_KEY from environment.
4
+ */
5
+ const DEFAULT_BASE_URL = "https://api.trylighthouse.vc";
6
+ export class LighthouseAPI {
7
+ baseUrl;
8
+ apiKey;
9
+ constructor() {
10
+ const key = process.env.LIGHTHOUSE_API_KEY;
11
+ if (!key) {
12
+ throw new Error("LIGHTHOUSE_API_KEY environment variable is required. " +
13
+ "Generate one at Settings → API Keys in Lighthouse.");
14
+ }
15
+ this.apiKey = key;
16
+ this.baseUrl = (process.env.LIGHTHOUSE_API_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
17
+ }
18
+ async request(method, path, body) {
19
+ const url = `${this.baseUrl}/v1${path}`;
20
+ const headers = {
21
+ Authorization: `Bearer ${this.apiKey}`,
22
+ "Content-Type": "application/json",
23
+ };
24
+ const res = await fetch(url, {
25
+ method,
26
+ headers,
27
+ body: body ? JSON.stringify(body) : undefined,
28
+ });
29
+ const json = await res.json();
30
+ return json;
31
+ }
32
+ async get(path, params) {
33
+ const qs = params
34
+ ? "?" +
35
+ Object.entries(params)
36
+ .filter(([, v]) => v !== undefined && v !== null)
37
+ .map(([k, v]) => `${k}=${encodeURIComponent(typeof v === "object" ? JSON.stringify(v) : String(v))}`)
38
+ .join("&")
39
+ : "";
40
+ return this.request("GET", path + qs);
41
+ }
42
+ async post(path, body) {
43
+ return this.request("POST", path, body);
44
+ }
45
+ async patch(path, body) {
46
+ return this.request("PATCH", path, body);
47
+ }
48
+ async delete(path, body) {
49
+ return this.request("DELETE", path, body);
50
+ }
51
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/build/index.js ADDED
@@ -0,0 +1,957 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { LighthouseAPI } from "./api.js";
6
+ const api = new LighthouseAPI();
7
+ function text(data) {
8
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
9
+ }
10
+ function err(message) {
11
+ return { content: [{ type: "text", text: message }], isError: true };
12
+ }
13
+ const server = new McpServer({
14
+ name: "lighthouse",
15
+ version: "0.1.0",
16
+ });
17
+ // ─────────────────────────────────────────────────────────
18
+ // FILTER FORMAT REFERENCE (used in tool descriptions)
19
+ // ─────────────────────────────────────────────────────────
20
+ //
21
+ // Shared filter structure (CRM & Discovery):
22
+ // { "operator":"AND"|"OR", "conditions":[...], "groups":[...] }
23
+ // conditions: [{ "field":"<key>", "operator":"<op>", "value":<val> }]
24
+ // groups: nested filter blocks with their own operator/conditions/groups
25
+ //
26
+ // Value formats:
27
+ // - text operators (contains, eq, etc.): string value
28
+ // - number operators (gt, lt, eq, etc.): numeric value
29
+ // - CRM between (number): { "from": <num>, "to": <num> } OR [min, max]
30
+ // - CRM between (date): { "from": "YYYY-MM-DD", "to": "YYYY-MM-DD" }
31
+ // - Discovery between: { "min": <num>, "max": <num> }
32
+ // - contains_any / not_contains_any / contains_all: array of values e.g. ["US","GB"]
33
+ // - empty / not_empty: no value needed (omit value key)
34
+ // - boolean eq: true or false
35
+ // - date (before, after, on): ISO date string "2024-01-15"
36
+ //
37
+ // CRM operators (from FilterBuilder.jsx):
38
+ // text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty
39
+ // number/currency/rating: gt, gte, lt, lte, eq, neq, empty, not_empty
40
+ // date/datetimez: before, after, on
41
+ // select/status/user: contains_any, not_contains_any, empty, not_empty
42
+ // multi_select/category/multi_user/multi_record: contains_any, not_contains_any, contains_all, empty, not_empty
43
+ // record: contains_any, not_contains_any, empty, not_empty
44
+ // domain/email/phone: contains_any, not_contains_any, contains_all, empty, not_empty
45
+ // list_entries: contains_any, not_contains_any, contains_all
46
+ //
47
+ // Discovery operators (from resourceFilterConfig.js):
48
+ // short_text: contains, is
49
+ // long_text: contains, not_contains, empty, not_empty
50
+ // text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty
51
+ // number/currency: between, gt, gte, lt, lte, eq, neq, empty, not_empty
52
+ // range: between, gt, lt, empty, not_empty
53
+ // date: after, before
54
+ // select: contains_any, not_contains_any, empty, not_empty
55
+ // 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
56
+ // funding_type_multi_select: contains_any, not_contains_any, empty, not_empty
57
+ // multi_user: contains_any, not_contains_any, contains_all, empty, not_empty
58
+ // boolean/checkbox: eq
59
+ // json: contains, not_contains, empty, not_empty
60
+ // ─────────────────────────────────────────────────────────
61
+ const CRM_FILTER_DESCRIPTION = "CRM filter object. Format: " +
62
+ '{"operator":"AND","conditions":[{"field":"<key>","operator":"<op>","value":<val>}],"groups":[]}. ' +
63
+ "IMPORTANT: First call lighthouse_crm_get_attributes to discover available field keys and types. " +
64
+ "Operators by field type — " +
65
+ "text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty; " +
66
+ "number/currency/rating: gt, gte, lt, lte, eq, neq, empty, not_empty; " +
67
+ "date: before, after, on; " +
68
+ "select/status/user: contains_any, not_contains_any, empty, not_empty; " +
69
+ "multi_select/category/multi_user/multi_record: contains_any, not_contains_any, contains_all, empty, not_empty; " +
70
+ "record: contains_any, not_contains_any, empty, not_empty; " +
71
+ "domain/email/phone: contains_any, not_contains_any, contains_all, empty, not_empty; " +
72
+ "list_entries: contains_any, not_contains_any, contains_all. " +
73
+ "Value formats: text ops → string; number ops → number; between (number) → {\"from\": min, \"to\": max}; " +
74
+ "between (date) → {\"from\": \"YYYY-MM-DD\", \"to\": \"YYYY-MM-DD\"}; " +
75
+ "contains_any/not_contains_any/contains_all → array of values; " +
76
+ "empty/not_empty → omit value; date ops → ISO date string. " +
77
+ "Groups allow nested AND/OR filter blocks with their own operator/conditions/groups. " +
78
+ "Do NOT use discovery field keys here — CRM fields come from lighthouse_crm_get_attributes.";
79
+ const CRM_SORT_DESCRIPTION = 'CRM sort array. Format: [{"field":"<key>","direction":"asc"|"desc"}]. ' +
80
+ "Field keys must match a sortable CRM attribute (text, number, date, select, status, currency, rating).";
81
+ const DISCOVERY_FILTER_DESCRIPTION = "Discovery filter object. Format: " +
82
+ '{"operator":"AND","conditions":[{"field":"<filter_id>","operator":"<op>","value":<val>}],"groups":[]}. ' +
83
+ "IMPORTANT: First call lighthouse_discovery_get_attributes to discover available filter_id values and types. " +
84
+ "Fields use prefixed keys (company_name, company_headcount, person_country, etc.). " +
85
+ "Operators by field type — " +
86
+ "short_text: contains, is; " +
87
+ "long_text: contains, not_contains, empty, not_empty; " +
88
+ "text/url: contains, not_contains, starts_with, ends_with, eq, neq, empty, not_empty; " +
89
+ "number/currency: between, gt, gte, lt, lte, eq, neq, empty, not_empty; " +
90
+ "range: between, gt, lt, empty, not_empty; " +
91
+ "date: after, before; " +
92
+ "select: contains_any, not_contains_any, empty, not_empty; " +
93
+ "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; " +
94
+ "funding_type_multi_select: contains_any, not_contains_any, empty, not_empty; " +
95
+ "multi_user: contains_any, not_contains_any, contains_all, empty, not_empty; " +
96
+ "boolean/checkbox: eq; " +
97
+ "json: contains, not_contains, empty, not_empty. " +
98
+ "Value formats: text ops → string; number ops → number; between → {\"min\": <num>, \"max\": <num>}; " +
99
+ "contains_any/not_contains_any/contains_all → array of values; empty/not_empty → omit value; boolean eq → true/false. " +
100
+ "Do NOT use CRM attribute keys here — discovery fields come from lighthouse_discovery_get_attributes.";
101
+ const DISCOVERY_SORT_DESCRIPTION = 'Discovery sort object. Format: {"by":"<field_name>","dir":"asc"|"desc"}. ' +
102
+ "Common sort fields: founded_on, total_funding_amount_usd, score. Default: score descending.";
103
+ // ─── CRM: Attributes ────────────────────────────────────
104
+ server.registerTool("lighthouse_crm_get_attributes", {
105
+ title: "Get CRM Attributes",
106
+ description: "Get custom field definitions for a CRM record type. " +
107
+ "ALWAYS call this before building filters for search_records, list_records, or creating/updating views. " +
108
+ "Returns field keys, types, and options needed to construct valid filter conditions. " +
109
+ "This queries YOUR WORKSPACE's custom fields (CRM data you manage).",
110
+ inputSchema: {
111
+ entity_type: z
112
+ .enum(["company", "person", "deal"])
113
+ .describe("Record type to get attributes for"),
114
+ },
115
+ }, async (params) => {
116
+ try {
117
+ const res = await api.get("/attributes", params);
118
+ return text(res);
119
+ }
120
+ catch (e) {
121
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
122
+ }
123
+ });
124
+ server.registerTool("lighthouse_crm_get_attribute", {
125
+ title: "Get CRM Attribute",
126
+ description: "Get a single custom field definition by its field_permalink.",
127
+ inputSchema: {
128
+ field_permalink: z.string().describe("Field permalink (unique key) of the attribute"),
129
+ entity_type: z.enum(["company", "person", "deal"]).optional().describe("Record type context"),
130
+ },
131
+ }, async (params) => {
132
+ try {
133
+ const res = await api.get(`/attributes/${params.field_permalink}`, {
134
+ entity_type: params.entity_type,
135
+ });
136
+ return text(res);
137
+ }
138
+ catch (e) {
139
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
140
+ }
141
+ });
142
+ server.registerTool("lighthouse_crm_create_attribute", {
143
+ title: "Create CRM Attribute",
144
+ description: "Create a new custom field in your CRM workspace. " +
145
+ "Common types: short_text, long_text, number, currency, date, select, multi_select, boolean, url, email, phone, rating.",
146
+ inputSchema: {
147
+ name: z.string().describe("Display name for the field"),
148
+ entity_type: z.enum(["company", "person", "deal"]).describe("Record type this field belongs to"),
149
+ data_type: z.string().describe("Field type (short_text, long_text, number, currency, date, select, multi_select, boolean, url, email, phone, rating)"),
150
+ options: z
151
+ .array(z.record(z.unknown()))
152
+ .optional()
153
+ .describe("For select/multi_select fields: array of {value, label} option objects"),
154
+ },
155
+ }, async (params) => {
156
+ try {
157
+ const res = await api.post("/attributes", params);
158
+ return text(res);
159
+ }
160
+ catch (e) {
161
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
162
+ }
163
+ });
164
+ server.registerTool("lighthouse_crm_update_attribute", {
165
+ title: "Update CRM Attribute",
166
+ description: "Update a custom field's name or configuration.",
167
+ inputSchema: {
168
+ field_permalink: z.string().describe("Field permalink of the attribute to update"),
169
+ entity_type: z.enum(["company", "person", "deal"]).optional().describe("Record type context"),
170
+ name: z.string().optional().describe("New display name"),
171
+ },
172
+ }, async (params) => {
173
+ try {
174
+ const { field_permalink, ...body } = params;
175
+ const res = await api.patch(`/attributes/${field_permalink}`, body);
176
+ return text(res);
177
+ }
178
+ catch (e) {
179
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
180
+ }
181
+ });
182
+ // ─── CRM: Options (select field values) ─────────────────
183
+ server.registerTool("lighthouse_crm_list_options", {
184
+ title: "List CRM Field Options",
185
+ description: "Get select/multi_select field options. " +
186
+ "Use this to discover available values for select fields before setting them on records.",
187
+ inputSchema: {
188
+ field_permalink: z.string().optional().describe("Filter by field permalink"),
189
+ entity_type: z.enum(["company", "person", "deal"]).optional().describe("Filter by record type"),
190
+ },
191
+ }, async (params) => {
192
+ try {
193
+ const res = await api.get("/options", params);
194
+ return text(res);
195
+ }
196
+ catch (e) {
197
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
198
+ }
199
+ });
200
+ server.registerTool("lighthouse_crm_create_option", {
201
+ title: "Create CRM Field Option",
202
+ description: "Add a new option value to a select/multi_select field.",
203
+ inputSchema: {
204
+ field_permalink: z.string().describe("Field permalink of the select/multi_select field"),
205
+ entity_type: z.enum(["company", "person", "deal"]).describe("Record type"),
206
+ value: z.string().describe("Option value (stored value)"),
207
+ label: z.string().describe("Option label (display text)"),
208
+ color: z.string().optional().describe("Option color (hex code)"),
209
+ },
210
+ }, async (params) => {
211
+ try {
212
+ const res = await api.post("/options", params);
213
+ return text(res);
214
+ }
215
+ catch (e) {
216
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
217
+ }
218
+ });
219
+ // ─── CRM: Records ────────────────────────────────────────
220
+ server.registerTool("lighthouse_crm_search_records", {
221
+ title: "Search CRM Records",
222
+ description: "Search and filter records in YOUR CRM workspace (companies, people, or deals you've added). " +
223
+ "Supports filters, sorting, pagination, and saved views. " +
224
+ "NEVER guess field keys — ALWAYS call lighthouse_crm_get_attributes first to get the exact field keys " +
225
+ "for the entity_type, then use those keys in your filter conditions. " +
226
+ "NOT for discovering new companies/people — use lighthouse_discovery_* tools for that.",
227
+ inputSchema: {
228
+ type: z.enum(["company", "person", "deal"]).describe("Record type"),
229
+ filters: z
230
+ .record(z.unknown())
231
+ .optional()
232
+ .describe(CRM_FILTER_DESCRIPTION),
233
+ sort: z
234
+ .array(z.record(z.unknown()))
235
+ .optional()
236
+ .describe(CRM_SORT_DESCRIPTION),
237
+ limit: z.number().min(1).max(100).default(25).describe("Records per page (max 100)"),
238
+ offset: z.number().min(0).default(0).describe("Number of records to skip"),
239
+ view_id: z
240
+ .string()
241
+ .uuid()
242
+ .optional()
243
+ .describe("Load saved filters/sort from a view. User-provided filters override view filters."),
244
+ },
245
+ }, async (params) => {
246
+ try {
247
+ const res = await api.post(`/records/${params.type}/search`, {
248
+ filters: params.filters,
249
+ sort: params.sort,
250
+ limit: params.limit,
251
+ offset: params.offset,
252
+ view_id: params.view_id,
253
+ });
254
+ return text(res);
255
+ }
256
+ catch (e) {
257
+ return err(`Search failed: ${e instanceof Error ? e.message : String(e)}`);
258
+ }
259
+ });
260
+ server.registerTool("lighthouse_crm_get_record", {
261
+ title: "Get CRM Record",
262
+ description: "Get a single CRM record by ID with all fields and relationships.",
263
+ inputSchema: {
264
+ type: z.enum(["company", "person", "deal"]).describe("Record type"),
265
+ id: z.string().uuid().describe("Record ID"),
266
+ },
267
+ }, async (params) => {
268
+ try {
269
+ const res = await api.get(`/records/${params.type}/${params.id}`);
270
+ return text(res);
271
+ }
272
+ catch (e) {
273
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
274
+ }
275
+ });
276
+ server.registerTool("lighthouse_crm_create_record", {
277
+ title: "Create CRM Record",
278
+ description: "Create a new CRM record. Required fields: " +
279
+ "company (name, domain), person (first_name, last_name), deal (name).",
280
+ inputSchema: {
281
+ type: z.enum(["company", "person", "deal"]).describe("Record type"),
282
+ data: z.record(z.unknown()).describe("Record fields (name, domain, first_name, last_name, etc.)"),
283
+ attributes: z
284
+ .record(z.unknown())
285
+ .optional()
286
+ .describe("Custom field values keyed by field_permalink (from lighthouse_crm_get_attributes)"),
287
+ },
288
+ }, async (params) => {
289
+ try {
290
+ const res = await api.post(`/records/${params.type}`, {
291
+ data: params.data,
292
+ attributes: params.attributes,
293
+ });
294
+ return text(res);
295
+ }
296
+ catch (e) {
297
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
298
+ }
299
+ });
300
+ server.registerTool("lighthouse_crm_update_record", {
301
+ title: "Update CRM Record",
302
+ description: "Update an existing CRM record's fields.",
303
+ inputSchema: {
304
+ type: z.enum(["company", "person", "deal"]).describe("Record type"),
305
+ id: z.string().uuid().describe("Record ID"),
306
+ data: z.record(z.unknown()).describe("Fields to update"),
307
+ attributes: z.record(z.unknown()).optional().describe("Custom field values to update (keyed by field_permalink)"),
308
+ },
309
+ }, async (params) => {
310
+ try {
311
+ const res = await api.patch(`/records/${params.type}/${params.id}`, {
312
+ data: params.data,
313
+ attributes: params.attributes,
314
+ });
315
+ return text(res);
316
+ }
317
+ catch (e) {
318
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
319
+ }
320
+ });
321
+ server.registerTool("lighthouse_crm_delete_record", {
322
+ title: "Delete CRM Record",
323
+ description: "Permanently delete a CRM record.",
324
+ inputSchema: {
325
+ type: z.enum(["company", "person", "deal"]).describe("Record type"),
326
+ id: z.string().uuid().describe("Record ID"),
327
+ },
328
+ }, async (params) => {
329
+ try {
330
+ const res = await api.delete(`/records/${params.type}/${params.id}`);
331
+ return text(res);
332
+ }
333
+ catch (e) {
334
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
335
+ }
336
+ });
337
+ // ─── CRM: Lists ──────────────────────────────────────────
338
+ server.registerTool("lighthouse_crm_create_list", {
339
+ title: "Create CRM List",
340
+ description: "Create a new CRM list to organize records.",
341
+ inputSchema: {
342
+ name: z.string().describe("List name"),
343
+ entity_type: z.enum(["company", "person", "deal"]).describe("Record type for this list"),
344
+ },
345
+ }, async (params) => {
346
+ try {
347
+ const res = await api.post("/lists", params);
348
+ return text(res);
349
+ }
350
+ catch (e) {
351
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
352
+ }
353
+ });
354
+ server.registerTool("lighthouse_crm_list_lists", {
355
+ title: "List CRM Lists",
356
+ description: "Get all CRM lists in your workspace.",
357
+ inputSchema: {},
358
+ }, async (params) => {
359
+ try {
360
+ const res = await api.get("/lists", params);
361
+ return text(res);
362
+ }
363
+ catch (e) {
364
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
365
+ }
366
+ });
367
+ server.registerTool("lighthouse_crm_get_list", {
368
+ title: "Get CRM List",
369
+ description: "Get a single CRM list by ID with its configuration.",
370
+ inputSchema: {
371
+ id: z.string().uuid().describe("List ID"),
372
+ },
373
+ }, async (params) => {
374
+ try {
375
+ const res = await api.get(`/lists/${params.id}`);
376
+ return text(res);
377
+ }
378
+ catch (e) {
379
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
380
+ }
381
+ });
382
+ server.registerTool("lighthouse_crm_update_list", {
383
+ title: "Update CRM List",
384
+ description: "Update a CRM list's name or configuration.",
385
+ inputSchema: {
386
+ id: z.string().uuid().describe("List ID"),
387
+ name: z.string().optional().describe("New list name"),
388
+ },
389
+ }, async (params) => {
390
+ try {
391
+ const { id, ...body } = params;
392
+ const res = await api.patch(`/lists/${id}`, body);
393
+ return text(res);
394
+ }
395
+ catch (e) {
396
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
397
+ }
398
+ });
399
+ server.registerTool("lighthouse_crm_delete_list", {
400
+ title: "Delete CRM List",
401
+ description: "Permanently delete a CRM list.",
402
+ inputSchema: {
403
+ id: z.string().uuid().describe("List ID"),
404
+ },
405
+ }, async (params) => {
406
+ try {
407
+ const res = await api.delete(`/lists/${params.id}`);
408
+ return text(res);
409
+ }
410
+ catch (e) {
411
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
412
+ }
413
+ });
414
+ server.registerTool("lighthouse_crm_get_list_records", {
415
+ title: "Get List Records",
416
+ description: "Get records in a CRM list with optional filtering, sorting, and view support. " +
417
+ "Same filter format as search_records.",
418
+ inputSchema: {
419
+ id: z.string().uuid().describe("List ID"),
420
+ filters: z.record(z.unknown()).optional().describe(CRM_FILTER_DESCRIPTION),
421
+ sort: z.array(z.record(z.unknown())).optional().describe(CRM_SORT_DESCRIPTION),
422
+ limit: z.number().min(1).max(100).default(25),
423
+ offset: z.number().min(0).default(0),
424
+ view_id: z.string().uuid().optional().describe("Load saved filters/sort from a view. User-provided filters override view filters."),
425
+ },
426
+ }, async (params) => {
427
+ try {
428
+ const { id, ...query } = params;
429
+ const res = await api.get(`/lists/${id}/records`, query);
430
+ return text(res);
431
+ }
432
+ catch (e) {
433
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
434
+ }
435
+ });
436
+ server.registerTool("lighthouse_crm_add_to_list", {
437
+ title: "Add Record to List",
438
+ description: "Add a CRM record to a list.",
439
+ inputSchema: {
440
+ list_id: z.string().uuid().describe("List ID"),
441
+ record_id: z.string().uuid().describe("Record ID to add"),
442
+ type: z.enum(["company", "person", "deal"]).describe("Record type"),
443
+ },
444
+ }, async (params) => {
445
+ try {
446
+ const res = await api.post(`/lists/${params.list_id}/records`, {
447
+ record_id: params.record_id,
448
+ type: params.type,
449
+ });
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_remove_from_list", {
457
+ title: "Remove Record from List",
458
+ description: "Remove a record from a CRM list.",
459
+ inputSchema: {
460
+ list_id: z.string().uuid().describe("List ID"),
461
+ record_id: z.string().uuid().describe("Record ID to remove"),
462
+ },
463
+ }, async (params) => {
464
+ try {
465
+ const res = await api.delete(`/lists/${params.list_id}/records/${params.record_id}`);
466
+ return text(res);
467
+ }
468
+ catch (e) {
469
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
470
+ }
471
+ });
472
+ // ─── CRM: Notes ──────────────────────────────────────────
473
+ server.registerTool("lighthouse_crm_list_notes", {
474
+ title: "List Notes",
475
+ description: "Get notes for a CRM record.",
476
+ inputSchema: {
477
+ record_id: z.string().uuid().describe("Record ID to get notes for"),
478
+ record_type: z.enum(["company", "person", "deal"]).describe("Record type"),
479
+ limit: z.number().min(1).max(100).default(25),
480
+ offset: z.number().min(0).default(0),
481
+ },
482
+ }, async (params) => {
483
+ try {
484
+ const res = await api.get("/notes", params);
485
+ return text(res);
486
+ }
487
+ catch (e) {
488
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
489
+ }
490
+ });
491
+ server.registerTool("lighthouse_crm_create_note", {
492
+ title: "Create Note",
493
+ description: "Create a note on a CRM record. " +
494
+ "Content is HTML. For plain text, just pass it as-is. " +
495
+ "To @mention/tag workspace users in the note, you MUST: " +
496
+ "1) Call lighthouse_workspace_list_users first to find the user's ID, " +
497
+ "2) Include their user IDs in the tagged_users array, " +
498
+ "3) Include a mention span in the HTML content: " +
499
+ '<span data-type="mention" data-id="USER_UUID" data-label="First Last">@First Last</span>. ' +
500
+ "Tagged users receive an in-app notification.",
501
+ inputSchema: {
502
+ record_id: z.string().uuid().describe("Record ID to attach the note to"),
503
+ record_type: z.enum(["company", "person", "deal"]).describe("Record type"),
504
+ title: z.string().describe("Note title"),
505
+ content: z.string().describe("Note content (plain text or HTML). Use <p> tags for paragraphs. For @mentions, include mention spans (see description)."),
506
+ tagged_users: z
507
+ .array(z.string().uuid())
508
+ .optional()
509
+ .describe("Array of workspace user UUIDs to tag/mention in the note. Must match mention spans in content."),
510
+ },
511
+ }, async (params) => {
512
+ try {
513
+ const res = await api.post("/notes", params);
514
+ return text(res);
515
+ }
516
+ catch (e) {
517
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
518
+ }
519
+ });
520
+ server.registerTool("lighthouse_crm_get_note", {
521
+ title: "Get Note",
522
+ description: "Get a single note by ID.",
523
+ inputSchema: {
524
+ id: z.string().uuid().describe("Note ID"),
525
+ },
526
+ }, async (params) => {
527
+ try {
528
+ const res = await api.get(`/notes/${params.id}`);
529
+ return text(res);
530
+ }
531
+ catch (e) {
532
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
533
+ }
534
+ });
535
+ server.registerTool("lighthouse_crm_delete_note", {
536
+ title: "Delete Note",
537
+ description: "Permanently delete a note.",
538
+ inputSchema: {
539
+ id: z.string().uuid().describe("Note ID"),
540
+ },
541
+ }, async (params) => {
542
+ try {
543
+ const res = await api.delete(`/notes/${params.id}`);
544
+ return text(res);
545
+ }
546
+ catch (e) {
547
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
548
+ }
549
+ });
550
+ // ─── CRM: Tasks ──────────────────────────────────────────
551
+ server.registerTool("lighthouse_crm_list_tasks", {
552
+ title: "List Tasks",
553
+ description: "List CRM tasks with optional filtering and sorting. " +
554
+ "The filters object supports: " +
555
+ 'filter (CRM-style): {"operator":"AND","conditions":[{"field":"status","operator":"contains_any","value":["pending"]}]}; ' +
556
+ 'sort: [{"field":"due_date","direction":"asc"}]; ' +
557
+ "Filterable fields: title, status, priority, due_date, description, assigned_to, records (person/company/deal), plus custom fields.",
558
+ inputSchema: {
559
+ filters: z
560
+ .object({
561
+ filter: z.record(z.any()).optional().describe("CRM filter object with operator/conditions/groups"),
562
+ sort: z.array(z.record(z.any())).optional().describe('Sort array: [{"field":"due_date","direction":"asc"}]'),
563
+ })
564
+ .optional()
565
+ .describe("Filter and sort options"),
566
+ limit: z.number().min(1).max(100).default(25),
567
+ offset: z.number().min(0).default(0),
568
+ },
569
+ }, async (params) => {
570
+ try {
571
+ const res = await api.get("/tasks", params);
572
+ return text(res);
573
+ }
574
+ catch (e) {
575
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
576
+ }
577
+ });
578
+ server.registerTool("lighthouse_crm_get_task", {
579
+ title: "Get Task",
580
+ description: "Get a single CRM task by ID.",
581
+ inputSchema: {
582
+ id: z.string().uuid().describe("Task ID"),
583
+ },
584
+ }, async (params) => {
585
+ try {
586
+ const res = await api.get(`/tasks/${params.id}`);
587
+ return text(res);
588
+ }
589
+ catch (e) {
590
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
591
+ }
592
+ });
593
+ server.registerTool("lighthouse_crm_create_task", {
594
+ title: "Create Task",
595
+ description: "Create a CRM task, optionally linked to records and assigned to users. " +
596
+ "The SQL expects assigned_to as [{id: uuid}] and records as {company: [{id}], person: [{id}], deal: [{id}]}.",
597
+ inputSchema: {
598
+ title: z.string().describe("Task title"),
599
+ description: z.string().optional().describe("Task description"),
600
+ status: z.string().optional().describe("Task status (e.g. 'pending', 'in_progress', 'completed')"),
601
+ priority: z.number().int().optional().describe("Priority as integer (e.g. 1=low, 2=medium, 3=high)"),
602
+ due_date: z.string().optional().describe("Due date (ISO 8601)"),
603
+ assigned_to: z
604
+ .array(z.string().uuid())
605
+ .optional()
606
+ .describe("Array of user UUIDs to assign the task to"),
607
+ records: z
608
+ .object({
609
+ company: z.array(z.string().uuid()).optional(),
610
+ person: z.array(z.string().uuid()).optional(),
611
+ deal: z.array(z.string().uuid()).optional(),
612
+ })
613
+ .optional()
614
+ .describe("Records to link: {company: [ids], person: [ids], deal: [ids]}"),
615
+ attributes: z.record(z.any()).optional().describe("Custom task field values as {field_permalink: value}"),
616
+ },
617
+ }, async (params) => {
618
+ try {
619
+ const { attributes, assigned_to, records, ...rest } = params;
620
+ const data = { ...rest };
621
+ if (assigned_to)
622
+ data.assigned_to = assigned_to.map((id) => ({ id }));
623
+ if (records) {
624
+ data.records = {
625
+ company: records.company?.map((id) => ({ id })) || [],
626
+ person: records.person?.map((id) => ({ id })) || [],
627
+ deal: records.deal?.map((id) => ({ id })) || [],
628
+ };
629
+ }
630
+ const res = await api.post("/tasks", { data, attributes });
631
+ return text(res);
632
+ }
633
+ catch (e) {
634
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
635
+ }
636
+ });
637
+ server.registerTool("lighthouse_crm_update_task", {
638
+ title: "Update Task",
639
+ description: "Update a CRM task. Provide only fields you want to change. " +
640
+ "assigned_to replaces all current assignees. records replaces all linked records.",
641
+ inputSchema: {
642
+ id: z.string().uuid().describe("Task ID"),
643
+ title: z.string().optional(),
644
+ description: z.string().optional(),
645
+ status: z.string().optional().describe("Task status (e.g. 'pending', 'in_progress', 'completed')"),
646
+ priority: z.number().int().optional().describe("Priority as integer (e.g. 1=low, 2=medium, 3=high)"),
647
+ due_date: z.string().optional(),
648
+ assigned_to: z
649
+ .array(z.string().uuid())
650
+ .optional()
651
+ .describe("Array of user UUIDs to assign (replaces current assignees)"),
652
+ records: z
653
+ .object({
654
+ company: z.array(z.string().uuid()).optional(),
655
+ person: z.array(z.string().uuid()).optional(),
656
+ deal: z.array(z.string().uuid()).optional(),
657
+ })
658
+ .optional()
659
+ .describe("Records to link (replaces current): {company: [ids], person: [ids], deal: [ids]}"),
660
+ attributes: z.record(z.any()).optional().describe("Custom task field values as {field_permalink: value}"),
661
+ },
662
+ }, async (params) => {
663
+ try {
664
+ const { id, attributes, assigned_to, records, ...rest } = params;
665
+ const data = { ...rest };
666
+ if (assigned_to)
667
+ data.assigned_to = assigned_to.map((id) => ({ id }));
668
+ if (records) {
669
+ data.records = {
670
+ company: records.company?.map((id) => ({ id })) || [],
671
+ person: records.person?.map((id) => ({ id })) || [],
672
+ deal: records.deal?.map((id) => ({ id })) || [],
673
+ };
674
+ }
675
+ const res = await api.patch(`/tasks/${id}`, { data, attributes });
676
+ return text(res);
677
+ }
678
+ catch (e) {
679
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
680
+ }
681
+ });
682
+ server.registerTool("lighthouse_crm_delete_task", {
683
+ title: "Delete Task",
684
+ description: "Permanently delete a CRM task.",
685
+ inputSchema: {
686
+ id: z.string().uuid().describe("Task ID"),
687
+ },
688
+ }, async (params) => {
689
+ try {
690
+ const res = await api.delete(`/tasks/${params.id}`);
691
+ return text(res);
692
+ }
693
+ catch (e) {
694
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
695
+ }
696
+ });
697
+ // ─── CRM: Views ──────────────────────────────────────────
698
+ server.registerTool("lighthouse_crm_list_views", {
699
+ title: "List CRM Views",
700
+ description: "Get all saved CRM views. Views store filter/sort configurations that can be loaded in search_records.",
701
+ inputSchema: {
702
+ entity_type: z.enum(["company", "person", "deal"]).optional().describe("Filter by record type"),
703
+ },
704
+ }, async (params) => {
705
+ try {
706
+ const res = await api.get("/views", params);
707
+ return text(res);
708
+ }
709
+ catch (e) {
710
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
711
+ }
712
+ });
713
+ server.registerTool("lighthouse_crm_create_view", {
714
+ title: "Create CRM View",
715
+ description: "Create a saved CRM view with filters and sort configuration. " +
716
+ "Views are for YOUR CRM data only (not discovery). " +
717
+ "WORKFLOW: 1) Call lighthouse_crm_get_attributes to get field keys, " +
718
+ "2) Build a filter object using those keys, " +
719
+ "3) Create the view with the filter. " +
720
+ "The saved filters will auto-apply when search_records is called with this view_id.",
721
+ inputSchema: {
722
+ name: z.string().describe("View name"),
723
+ entity_type: z.enum(["company", "person", "deal"]).describe("Record type"),
724
+ filters: z
725
+ .record(z.unknown())
726
+ .optional()
727
+ .describe(CRM_FILTER_DESCRIPTION),
728
+ sort: z.array(z.record(z.unknown())).optional().describe(CRM_SORT_DESCRIPTION),
729
+ view_type: z.enum(["table", "kanban"]).default("table").describe("View layout type"),
730
+ grouped_by: z.string().optional().describe("Field key to group by (required for kanban views)"),
731
+ sharing: z.enum(["private", "workspace"]).default("private").describe("Visibility"),
732
+ },
733
+ }, async (params) => {
734
+ try {
735
+ const res = await api.post("/views", params);
736
+ return text(res);
737
+ }
738
+ catch (e) {
739
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
740
+ }
741
+ });
742
+ server.registerTool("lighthouse_crm_update_view", {
743
+ title: "Update CRM View",
744
+ description: "Update a saved CRM view's name, filters, sort, or sharing settings. " +
745
+ "Uses CRM filter format — call lighthouse_crm_get_attributes first if modifying filters.",
746
+ inputSchema: {
747
+ id: z.string().uuid().describe("View ID"),
748
+ name: z.string().optional(),
749
+ filters: z.record(z.unknown()).optional().describe(CRM_FILTER_DESCRIPTION),
750
+ sort: z.array(z.record(z.unknown())).optional().describe(CRM_SORT_DESCRIPTION),
751
+ view_type: z.enum(["table", "kanban"]).optional(),
752
+ grouped_by: z.string().optional().describe("Field key to group by (required for kanban views)"),
753
+ sharing: z.enum(["private", "workspace"]).optional(),
754
+ },
755
+ }, async (params) => {
756
+ try {
757
+ const { id, ...body } = params;
758
+ const res = await api.patch(`/views/${id}`, body);
759
+ return text(res);
760
+ }
761
+ catch (e) {
762
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
763
+ }
764
+ });
765
+ server.registerTool("lighthouse_crm_get_view", {
766
+ title: "Get CRM View",
767
+ description: "Get a single saved CRM view by ID.",
768
+ inputSchema: {
769
+ id: z.string().uuid().describe("View ID"),
770
+ },
771
+ }, async (params) => {
772
+ try {
773
+ const res = await api.get(`/views/${params.id}`);
774
+ return text(res);
775
+ }
776
+ catch (e) {
777
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
778
+ }
779
+ });
780
+ server.registerTool("lighthouse_crm_delete_view", {
781
+ title: "Delete CRM View",
782
+ description: "Permanently delete a saved CRM view.",
783
+ inputSchema: {
784
+ id: z.string().uuid().describe("View ID"),
785
+ },
786
+ }, async (params) => {
787
+ try {
788
+ const res = await api.delete(`/views/${params.id}`);
789
+ return text(res);
790
+ }
791
+ catch (e) {
792
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
793
+ }
794
+ });
795
+ // ─── Workspace: Users ───────────────────────────────────
796
+ server.registerTool("lighthouse_workspace_me", {
797
+ title: "Get Current User",
798
+ description: "Get the current authenticated user's profile (name, email, role, etc.).",
799
+ inputSchema: {},
800
+ }, async () => {
801
+ try {
802
+ const res = await api.get("/users/me");
803
+ return text(res);
804
+ }
805
+ catch (e) {
806
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
807
+ }
808
+ });
809
+ server.registerTool("lighthouse_workspace_list_users", {
810
+ title: "List Workspace Users",
811
+ description: "List users in the workspace. Use this to find user IDs for task assignment, note tagging, or filtering by user. " +
812
+ "Supports search by name/email.",
813
+ inputSchema: {
814
+ search: z.string().optional().describe("Search by name or email"),
815
+ team_id: z.string().uuid().optional().describe("Filter by team ID"),
816
+ role_id: z.string().uuid().optional().describe("Filter by role ID"),
817
+ limit: z.number().min(1).max(100).default(25),
818
+ offset: z.number().min(0).default(0),
819
+ },
820
+ }, async (params) => {
821
+ try {
822
+ const res = await api.get("/users", 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_workspace_get_user", {
830
+ title: "Get Workspace User",
831
+ description: "Get a specific workspace user's profile by ID.",
832
+ inputSchema: {
833
+ id: z.string().uuid().describe("User ID"),
834
+ },
835
+ }, async (params) => {
836
+ try {
837
+ const res = await api.get(`/users/${params.id}`);
838
+ return text(res);
839
+ }
840
+ catch (e) {
841
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
842
+ }
843
+ });
844
+ // ─── Workspace: Teams ───────────────────────────────────
845
+ server.registerTool("lighthouse_workspace_list_teams", {
846
+ title: "List Workspace Teams",
847
+ description: "List all teams in the workspace.",
848
+ inputSchema: {
849
+ limit: z.number().min(1).max(100).default(25),
850
+ offset: z.number().min(0).default(0),
851
+ },
852
+ }, async (params) => {
853
+ try {
854
+ const res = await api.get("/teams", params);
855
+ return text(res);
856
+ }
857
+ catch (e) {
858
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
859
+ }
860
+ });
861
+ server.registerTool("lighthouse_workspace_get_team", {
862
+ title: "Get Workspace Team",
863
+ description: "Get a specific team by ID with its members.",
864
+ inputSchema: {
865
+ id: z.string().uuid().describe("Team ID"),
866
+ },
867
+ }, async (params) => {
868
+ try {
869
+ const res = await api.get(`/teams/${params.id}`);
870
+ return text(res);
871
+ }
872
+ catch (e) {
873
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
874
+ }
875
+ });
876
+ // ─── Discovery: Attributes ───────────────────────────────
877
+ server.registerTool("lighthouse_discovery_get_attributes", {
878
+ title: "Get Discovery Attributes",
879
+ description: "Get available filter fields for Lighthouse's company/people database (discovery). " +
880
+ "ALWAYS call this before building filters for discovery_search_companies or discovery_search_people. " +
881
+ "Returns field definitions with filter_id, type, and options. " +
882
+ "These are NOT your CRM fields — these are Lighthouse's global database fields.",
883
+ inputSchema: {
884
+ type: z
885
+ .enum(["company", "person"])
886
+ .optional()
887
+ .describe("Entity type: 'company' or 'person'. If omitted, returns both as { company: [...], person: [...] }."),
888
+ },
889
+ }, async (params) => {
890
+ try {
891
+ const res = await api.get("/discovery/attributes", params);
892
+ return text(res);
893
+ }
894
+ catch (e) {
895
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
896
+ }
897
+ });
898
+ // ─── Discovery: Search ───────────────────────────────────
899
+ server.registerTool("lighthouse_discovery_search_companies", {
900
+ title: "Search Discovery Companies",
901
+ description: "Search Lighthouse's global company database to DISCOVER new companies. " +
902
+ "This searches Lighthouse's enriched database (millions of companies), NOT your CRM. " +
903
+ "NEVER guess field keys — ALWAYS call lighthouse_discovery_get_attributes with type=company first " +
904
+ "to get the exact filter_id values, then use those in your filter conditions. " +
905
+ "Common fields: company_name, company_headcount, company_hq_country, company_total_funding, company_categories.",
906
+ inputSchema: {
907
+ filters: z
908
+ .record(z.unknown())
909
+ .optional()
910
+ .describe(DISCOVERY_FILTER_DESCRIPTION),
911
+ sort: z.record(z.unknown()).optional().describe(DISCOVERY_SORT_DESCRIPTION),
912
+ skip: z.number().min(0).default(0).describe("Number of results to skip"),
913
+ limit: z.number().min(1).max(100).default(25).describe("Results per page"),
914
+ },
915
+ }, async (params) => {
916
+ try {
917
+ const res = await api.post("/database/get-companies", params);
918
+ return text(res);
919
+ }
920
+ catch (e) {
921
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
922
+ }
923
+ });
924
+ server.registerTool("lighthouse_discovery_search_people", {
925
+ title: "Search Discovery People",
926
+ description: "Search Lighthouse's global people database to DISCOVER new people. " +
927
+ "This searches Lighthouse's enriched database, NOT your CRM. " +
928
+ "NEVER guess field keys — ALWAYS call lighthouse_discovery_get_attributes with type=person first " +
929
+ "to get the exact filter_id values, then use those in your filter conditions. " +
930
+ "Common fields: person_name, person_country, person_highlights, person_current_categories.",
931
+ inputSchema: {
932
+ filters: z
933
+ .record(z.unknown())
934
+ .optional()
935
+ .describe(DISCOVERY_FILTER_DESCRIPTION),
936
+ skip: z.number().min(0).default(0).describe("Number of results to skip"),
937
+ limit: z.number().min(1).max(100).default(25).describe("Results per page"),
938
+ },
939
+ }, async (params) => {
940
+ try {
941
+ const res = await api.post("/database/get-people", params);
942
+ return text(res);
943
+ }
944
+ catch (e) {
945
+ return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
946
+ }
947
+ });
948
+ // ─── Start ───────────────────────────────────────────────
949
+ async function main() {
950
+ const transport = new StdioServerTransport();
951
+ await server.connect(transport);
952
+ console.error("Lighthouse MCP server running on stdio");
953
+ }
954
+ main().catch((error) => {
955
+ console.error("Fatal error:", error);
956
+ process.exit(1);
957
+ });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@trylighthouse/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for the Lighthouse CRM API",
5
+ "type": "module",
6
+ "bin": {
7
+ "lighthouse-mcp": "./build/index.js"
8
+ },
9
+ "files": [
10
+ "build"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc && chmod 755 build/index.js",
14
+ "dev": "tsc --watch",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.27.1",
19
+ "zod": "^3.24.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.0.0",
23
+ "typescript": "^5.7.0"
24
+ },
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "license": "MIT"
29
+ }