headlight-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,62 @@
1
+ # headlight-mcp-server
2
+
3
+ MCP server for head·light CRM - connect Claude to your contacts, touchpoints, and reminders.
4
+
5
+ ## Setup
6
+
7
+ 1. Generate an API key in head·light Settings → Integrations → AI Integration
8
+ 2. Add to your Claude Desktop config (`claude_desktop_config.json`):
9
+
10
+ ```json
11
+ {
12
+ "mcpServers": {
13
+ "headlight": {
14
+ "command": "npx",
15
+ "args": ["-y", "headlight-mcp-server"],
16
+ "env": {
17
+ "HEADLIGHT_API_KEY": "your-key-here"
18
+ }
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ 3. Restart Claude Desktop
25
+
26
+ ## Available Tools
27
+
28
+ | Tool | Description |
29
+ |------|-------------|
30
+ | `get_today_summary` | Quick overview of today's due items |
31
+ | `search_people` | Find contacts by name, journey, or status |
32
+ | `get_person` | Full contact details |
33
+ | `add_note` | Add a note to a contact |
34
+ | `list_touchpoints` | View due/overdue tasks |
35
+ | `complete_touchpoint` | Mark a touchpoint done |
36
+ | `list_reminders` | View personal reminders |
37
+ | `create_reminder` | Create a reminder |
38
+ | `complete_reminder` | Mark a reminder done |
39
+ | `list_journeys` | See available journeys |
40
+ | `get_journey` | Journey details and steps |
41
+ | `assign_journey` | Put a contact on a journey |
42
+
43
+ ## Example Prompts
44
+
45
+ - "What's on my head·light agenda today?"
46
+ - "Find all my contacts with overdue touchpoints"
47
+ - "Add a note to John Smith: Had a great call, interested in the Oak Street listing"
48
+ - "Complete the touchpoint for Sarah about the welcome email"
49
+ - "Remind me to follow up on the Johnson contract tomorrow"
50
+
51
+ ## Development
52
+
53
+ ```bash
54
+ # Install dependencies
55
+ pnpm install
56
+
57
+ # Build
58
+ pnpm build
59
+
60
+ # Test locally
61
+ HEADLIGHT_API_KEY=your-key HEADLIGHT_API_URL=http://localhost:3000 node dist/index.js
62
+ ```
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ const API_KEY = process.env.HEADLIGHT_API_KEY;
6
+ const API_URL = process.env.HEADLIGHT_API_URL || 'https://headlight.homes';
7
+ if (!API_KEY) {
8
+ console.error('HEADLIGHT_API_KEY environment variable is required');
9
+ process.exit(1);
10
+ }
11
+ async function apiCall(method, path, body) {
12
+ const url = `${API_URL}/api/mcp${path}`;
13
+ const response = await fetch(url, {
14
+ method,
15
+ headers: {
16
+ 'Authorization': `Bearer ${API_KEY}`,
17
+ 'Content-Type': 'application/json',
18
+ },
19
+ body: body ? JSON.stringify(body) : undefined,
20
+ });
21
+ if (!response.ok) {
22
+ const error = await response.json().catch(() => ({ error: response.statusText }));
23
+ throw new Error(error.error || `API error: ${response.status}`);
24
+ }
25
+ return response.json();
26
+ }
27
+ const server = new Server({
28
+ name: 'headlight',
29
+ version: '0.1.0',
30
+ }, {
31
+ capabilities: {
32
+ tools: {},
33
+ },
34
+ });
35
+ // Define all available tools
36
+ const tools = [
37
+ {
38
+ name: 'get_today_summary',
39
+ description: 'Get a quick overview of today\'s touchpoints and reminders due, plus overdue items',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {},
43
+ required: [],
44
+ },
45
+ },
46
+ {
47
+ name: 'search_people',
48
+ description: 'Search for contacts by name, workflow/journey, status, or find those with overdue touchpoints',
49
+ inputSchema: {
50
+ type: 'object',
51
+ properties: {
52
+ query: { type: 'string', description: 'Search by name' },
53
+ workflow: { type: 'string', description: 'Filter by journey/workflow name' },
54
+ status: { type: 'string', enum: ['active', 'inactive', 'archived'], description: 'Filter by status' },
55
+ hasOverdue: { type: 'boolean', description: 'Only show people with overdue touchpoints' },
56
+ limit: { type: 'number', description: 'Max results (default 10)' },
57
+ },
58
+ required: [],
59
+ },
60
+ },
61
+ {
62
+ name: 'get_person',
63
+ description: 'Get full details about a person including phones, emails, notes, upcoming touchpoints, relationships, and important dates',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: {
67
+ personId: { type: 'string', description: 'The person\'s ID' },
68
+ },
69
+ required: ['personId'],
70
+ },
71
+ },
72
+ {
73
+ name: 'add_note',
74
+ description: 'Add a note to a person\'s profile',
75
+ inputSchema: {
76
+ type: 'object',
77
+ properties: {
78
+ personId: { type: 'string', description: 'The person\'s ID' },
79
+ note: { type: 'string', description: 'The note content' },
80
+ },
81
+ required: ['personId', 'note'],
82
+ },
83
+ },
84
+ {
85
+ name: 'list_touchpoints',
86
+ description: 'List touchpoints (tasks) with optional filters. Use to see what\'s due, overdue, or upcoming.',
87
+ inputSchema: {
88
+ type: 'object',
89
+ properties: {
90
+ personId: { type: 'string', description: 'Filter by person' },
91
+ status: { type: 'string', enum: ['upcoming', 'completed', 'overdue'], description: 'Filter by status (default: upcoming)' },
92
+ channel: { type: 'string', enum: ['email', 'call', 'text', 'meeting', 'content', 'other'], description: 'Filter by channel' },
93
+ dateFrom: { type: 'string', description: 'Start date (ISO format)' },
94
+ dateTo: { type: 'string', description: 'End date (ISO format)' },
95
+ limit: { type: 'number', description: 'Max results (default 10)' },
96
+ },
97
+ required: [],
98
+ },
99
+ },
100
+ {
101
+ name: 'complete_touchpoint',
102
+ description: 'Mark a touchpoint as done. Returns info about the next touchpoint in the journey.',
103
+ inputSchema: {
104
+ type: 'object',
105
+ properties: {
106
+ touchpointId: { type: 'string', description: 'The touchpoint ID to complete' },
107
+ },
108
+ required: ['touchpointId'],
109
+ },
110
+ },
111
+ {
112
+ name: 'list_reminders',
113
+ description: 'List personal reminders (not tied to contacts)',
114
+ inputSchema: {
115
+ type: 'object',
116
+ properties: {
117
+ status: { type: 'string', enum: ['active', 'completed', 'all'], description: 'Filter by status (default: active)' },
118
+ dateFrom: { type: 'string', description: 'Start date (ISO format)' },
119
+ dateTo: { type: 'string', description: 'End date (ISO format)' },
120
+ limit: { type: 'number', description: 'Max results (default 10)' },
121
+ },
122
+ required: [],
123
+ },
124
+ },
125
+ {
126
+ name: 'create_reminder',
127
+ description: 'Create a personal reminder. Defaults to tomorrow at 9am if no date specified.',
128
+ inputSchema: {
129
+ type: 'object',
130
+ properties: {
131
+ name: { type: 'string', description: 'Reminder title' },
132
+ text: { type: 'string', description: 'Additional details (optional)' },
133
+ dueAt: { type: 'string', description: 'Due date/time (ISO format, optional)' },
134
+ isAllDay: { type: 'boolean', description: 'All-day reminder (default false)' },
135
+ },
136
+ required: ['name'],
137
+ },
138
+ },
139
+ {
140
+ name: 'complete_reminder',
141
+ description: 'Mark a reminder as done',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: {
145
+ reminderId: { type: 'string', description: 'The reminder ID to complete' },
146
+ },
147
+ required: ['reminderId'],
148
+ },
149
+ },
150
+ {
151
+ name: 'list_journeys',
152
+ description: 'List available journeys (workflows) with task and contact counts',
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: {
156
+ includeInactive: { type: 'boolean', description: 'Include inactive journeys (default false)' },
157
+ },
158
+ required: [],
159
+ },
160
+ },
161
+ {
162
+ name: 'get_journey',
163
+ description: 'Get journey details including all task templates',
164
+ inputSchema: {
165
+ type: 'object',
166
+ properties: {
167
+ journeyId: { type: 'string', description: 'The journey ID' },
168
+ },
169
+ required: ['journeyId'],
170
+ },
171
+ },
172
+ {
173
+ name: 'assign_journey',
174
+ description: 'Assign a person to a journey. Creates touchpoints based on the journey\'s task templates.',
175
+ inputSchema: {
176
+ type: 'object',
177
+ properties: {
178
+ personId: { type: 'string', description: 'The person\'s ID (or use personName)' },
179
+ personName: { type: 'string', description: 'Search for person by name (or use personId)' },
180
+ journeyId: { type: 'string', description: 'The journey ID (or use journeyName)' },
181
+ journeyName: { type: 'string', description: 'Search for journey by name (or use journeyId)' },
182
+ },
183
+ required: [],
184
+ },
185
+ },
186
+ ];
187
+ // Handle list tools request
188
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
189
+ return { tools };
190
+ });
191
+ // Handle tool calls
192
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
193
+ const { name, arguments: args } = request.params;
194
+ try {
195
+ let result;
196
+ switch (name) {
197
+ case 'get_today_summary':
198
+ result = await apiCall('GET', '/summary');
199
+ break;
200
+ case 'search_people':
201
+ result = await apiCall('POST', '/people/search', args);
202
+ break;
203
+ case 'get_person':
204
+ result = await apiCall('GET', `/people/${args.personId}`);
205
+ break;
206
+ case 'add_note':
207
+ result = await apiCall('POST', `/people/${args.personId}/notes`, {
208
+ note: args.note,
209
+ });
210
+ break;
211
+ case 'list_touchpoints': {
212
+ const params = new URLSearchParams();
213
+ const typedArgs = args;
214
+ if (typedArgs.personId)
215
+ params.set('personId', String(typedArgs.personId));
216
+ if (typedArgs.status)
217
+ params.set('status', String(typedArgs.status));
218
+ if (typedArgs.channel)
219
+ params.set('channel', String(typedArgs.channel));
220
+ if (typedArgs.dateFrom)
221
+ params.set('dateFrom', String(typedArgs.dateFrom));
222
+ if (typedArgs.dateTo)
223
+ params.set('dateTo', String(typedArgs.dateTo));
224
+ if (typedArgs.limit)
225
+ params.set('limit', String(typedArgs.limit));
226
+ const query = params.toString();
227
+ result = await apiCall('GET', `/touchpoints${query ? `?${query}` : ''}`);
228
+ break;
229
+ }
230
+ case 'complete_touchpoint':
231
+ result = await apiCall('POST', `/touchpoints/${args.touchpointId}/complete`);
232
+ break;
233
+ case 'list_reminders': {
234
+ const params = new URLSearchParams();
235
+ const typedArgs = args;
236
+ if (typedArgs.status)
237
+ params.set('status', String(typedArgs.status));
238
+ if (typedArgs.dateFrom)
239
+ params.set('dateFrom', String(typedArgs.dateFrom));
240
+ if (typedArgs.dateTo)
241
+ params.set('dateTo', String(typedArgs.dateTo));
242
+ if (typedArgs.limit)
243
+ params.set('limit', String(typedArgs.limit));
244
+ const query = params.toString();
245
+ result = await apiCall('GET', `/reminders${query ? `?${query}` : ''}`);
246
+ break;
247
+ }
248
+ case 'create_reminder':
249
+ result = await apiCall('POST', '/reminders', args);
250
+ break;
251
+ case 'complete_reminder':
252
+ result = await apiCall('POST', `/reminders/${args.reminderId}/complete`);
253
+ break;
254
+ case 'list_journeys': {
255
+ const params = new URLSearchParams();
256
+ if (args.includeInactive) {
257
+ params.set('includeInactive', 'true');
258
+ }
259
+ const query = params.toString();
260
+ result = await apiCall('GET', `/journeys${query ? `?${query}` : ''}`);
261
+ break;
262
+ }
263
+ case 'get_journey':
264
+ result = await apiCall('GET', `/journeys/${args.journeyId}`);
265
+ break;
266
+ case 'assign_journey':
267
+ result = await apiCall('POST', '/journeys/assign', args);
268
+ break;
269
+ default:
270
+ throw new Error(`Unknown tool: ${name}`);
271
+ }
272
+ return {
273
+ content: [
274
+ {
275
+ type: 'text',
276
+ text: JSON.stringify(result, null, 2),
277
+ },
278
+ ],
279
+ };
280
+ }
281
+ catch (error) {
282
+ return {
283
+ content: [
284
+ {
285
+ type: 'text',
286
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
287
+ },
288
+ ],
289
+ isError: true,
290
+ };
291
+ }
292
+ });
293
+ // Start the server
294
+ async function main() {
295
+ const transport = new StdioServerTransport();
296
+ await server.connect(transport);
297
+ console.error('head·light MCP server running');
298
+ }
299
+ main().catch((error) => {
300
+ console.error('Fatal error:', error);
301
+ process.exit(1);
302
+ });
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "headlight-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for head·light CRM - connect Claude to your contacts, touchpoints, and reminders",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "headlight-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "claude",
18
+ "headlight",
19
+ "crm"
20
+ ],
21
+ "author": "Hoffman Tools",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.0.0",
28
+ "typescript": "^5.0.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "publishConfig": {}
37
+ }