salesflare-mcp-server 1.0.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.
Files changed (73) hide show
  1. package/API.md +691 -0
  2. package/CHANGELOG.md +49 -0
  3. package/CLAUDE.md +117 -0
  4. package/CONTRIBUTING.md +399 -0
  5. package/FIX_PLAN.md +70 -0
  6. package/INSPECTOR.md +191 -0
  7. package/LICENSE +21 -0
  8. package/PUBLISH.md +73 -0
  9. package/README.md +383 -0
  10. package/dist/auth/api-key-auth.d.ts +75 -0
  11. package/dist/auth/api-key-auth.d.ts.map +1 -0
  12. package/dist/auth/api-key-auth.js +103 -0
  13. package/dist/auth/oauth-auth.d.ts +81 -0
  14. package/dist/auth/oauth-auth.d.ts.map +1 -0
  15. package/dist/auth/oauth-auth.js +123 -0
  16. package/dist/auth/token-manager.d.ts +105 -0
  17. package/dist/auth/token-manager.d.ts.map +1 -0
  18. package/dist/auth/token-manager.js +87 -0
  19. package/dist/client/salesflare-client.d.ts +219 -0
  20. package/dist/client/salesflare-client.d.ts.map +1 -0
  21. package/dist/client/salesflare-client.js +484 -0
  22. package/dist/index.d.ts +15 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +82 -0
  25. package/dist/server.d.ts +39 -0
  26. package/dist/server.d.ts.map +1 -0
  27. package/dist/server.js +140 -0
  28. package/dist/tools/companies.d.ts +45 -0
  29. package/dist/tools/companies.d.ts.map +1 -0
  30. package/dist/tools/companies.js +392 -0
  31. package/dist/tools/contacts.d.ts +45 -0
  32. package/dist/tools/contacts.d.ts.map +1 -0
  33. package/dist/tools/contacts.js +290 -0
  34. package/dist/tools/deals.d.ts +46 -0
  35. package/dist/tools/deals.d.ts.map +1 -0
  36. package/dist/tools/deals.js +442 -0
  37. package/dist/tools/pipeline.d.ts +43 -0
  38. package/dist/tools/pipeline.d.ts.map +1 -0
  39. package/dist/tools/pipeline.js +328 -0
  40. package/dist/tools/tasks.d.ts +44 -0
  41. package/dist/tools/tasks.d.ts.map +1 -0
  42. package/dist/tools/tasks.js +406 -0
  43. package/dist/transport/http-transport.d.ts +36 -0
  44. package/dist/transport/http-transport.d.ts.map +1 -0
  45. package/dist/transport/http-transport.js +173 -0
  46. package/dist/transport/stdio-transport.d.ts +37 -0
  47. package/dist/transport/stdio-transport.d.ts.map +1 -0
  48. package/dist/transport/stdio-transport.js +129 -0
  49. package/dist/types/company.d.ts +223 -0
  50. package/dist/types/company.d.ts.map +1 -0
  51. package/dist/types/company.js +8 -0
  52. package/dist/types/contact.d.ts +166 -0
  53. package/dist/types/contact.d.ts.map +1 -0
  54. package/dist/types/contact.js +8 -0
  55. package/dist/types/deal.d.ts +203 -0
  56. package/dist/types/deal.d.ts.map +1 -0
  57. package/dist/types/deal.js +8 -0
  58. package/dist/types/pipeline.d.ts +116 -0
  59. package/dist/types/pipeline.d.ts.map +1 -0
  60. package/dist/types/pipeline.js +8 -0
  61. package/dist/types/task.d.ts +154 -0
  62. package/dist/types/task.d.ts.map +1 -0
  63. package/dist/types/task.js +8 -0
  64. package/dist/utils/errors.d.ts +128 -0
  65. package/dist/utils/errors.d.ts.map +1 -0
  66. package/dist/utils/errors.js +205 -0
  67. package/dist/utils/validation.d.ts +354 -0
  68. package/dist/utils/validation.d.ts.map +1 -0
  69. package/dist/utils/validation.js +716 -0
  70. package/package.json +49 -0
  71. package/test-tasks-debug.js +21 -0
  72. package/test-tasks-params.js +52 -0
  73. package/test-tools.js +171 -0
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Company tools for Salesflare MCP Server
3
+ *
4
+ * Implements CRUD operations for companies:
5
+ * - salesflare_companies_list: List companies with filtering and pagination
6
+ * - salesflare_companies_create: Create new companies
7
+ * - salesflare_companies_update: Update existing companies
8
+ * - salesflare_companies_delete: Delete companies
9
+ *
10
+ * @module tools/companies
11
+ */
12
+ import { SalesflareError, ErrorCode } from '../utils/errors.js';
13
+ /**
14
+ * Company tool definitions with JSON schemas
15
+ */
16
+ const companyTools = [
17
+ {
18
+ name: 'salesflare_companies_list',
19
+ description: 'List companies from Salesflare CRM with optional filtering and pagination. ' +
20
+ 'Supports filtering by name (partial match), industry (exact match), tags (AND logic), owner, and search. ' +
21
+ 'Returns paginated results with total count and has_more indicator.',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ name: { type: 'string', description: 'Filter by name (partial match, case-insensitive)' },
26
+ industry: { type: 'string', description: 'Filter by industry (exact match)' },
27
+ tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags (companies must have all specified tags)' },
28
+ owner: { type: 'string', description: 'Filter by owner ID or name' },
29
+ search: { type: 'string', description: 'Free-text search across company fields' },
30
+ page: { type: 'number', description: 'Page number (default: 1)' },
31
+ limit: { type: 'number', description: 'Items per page (default: 20, max: 100)' },
32
+ },
33
+ },
34
+ },
35
+ {
36
+ name: 'salesflare_companies_create',
37
+ description: 'Create a new company in Salesflare CRM. ' +
38
+ 'Name is required. Optional fields include website, industry, phone, description, address, social_media, tags, and owner_id.',
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ name: { type: 'string', description: 'Company name (required)' },
43
+ website: { type: 'string', description: 'Company website URL' },
44
+ industry: { type: 'string', description: 'Industry category' },
45
+ phone: { type: 'string', description: 'Phone number' },
46
+ description: { type: 'string', description: 'Company description' },
47
+ address: {
48
+ type: 'object',
49
+ description: 'Address information',
50
+ properties: {
51
+ street: { type: 'string', description: 'Street address' },
52
+ city: { type: 'string', description: 'City' },
53
+ state: { type: 'string', description: 'State or province' },
54
+ zip: { type: 'string', description: 'ZIP or postal code' },
55
+ country: { type: 'string', description: 'Country' },
56
+ },
57
+ },
58
+ social_media: {
59
+ type: 'object',
60
+ description: 'Social media profiles',
61
+ properties: {
62
+ linkedin: { type: 'string', description: 'LinkedIn profile URL' },
63
+ twitter: { type: 'string', description: 'Twitter/X handle or URL' },
64
+ facebook: { type: 'string', description: 'Facebook page URL' },
65
+ },
66
+ },
67
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags to assign' },
68
+ owner_id: { type: 'string', description: 'Owner/user ID to assign (UUID)' },
69
+ },
70
+ required: ['name'],
71
+ },
72
+ },
73
+ {
74
+ name: 'salesflare_companies_update',
75
+ description: 'Update an existing company in Salesflare CRM. ' +
76
+ 'Requires company_id. All other fields are optional - only provided fields will be updated.',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: {
80
+ company_id: { type: 'string', description: 'Company UUID to update' },
81
+ name: { type: 'string', description: 'Company name' },
82
+ website: { type: 'string', description: 'Company website URL' },
83
+ industry: { type: 'string', description: 'Industry category' },
84
+ phone: { type: 'string', description: 'Phone number' },
85
+ description: { type: 'string', description: 'Company description' },
86
+ address: {
87
+ type: 'object',
88
+ description: 'Address information',
89
+ properties: {
90
+ street: { type: 'string', description: 'Street address' },
91
+ city: { type: 'string', description: 'City' },
92
+ state: { type: 'string', description: 'State or province' },
93
+ zip: { type: 'string', description: 'ZIP or postal code' },
94
+ country: { type: 'string', description: 'Country' },
95
+ },
96
+ },
97
+ social_media: {
98
+ type: 'object',
99
+ description: 'Social media profiles',
100
+ properties: {
101
+ linkedin: { type: 'string', description: 'LinkedIn profile URL' },
102
+ twitter: { type: 'string', description: 'Twitter/X handle or URL' },
103
+ facebook: { type: 'string', description: 'Facebook page URL' },
104
+ },
105
+ },
106
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags to assign' },
107
+ owner_id: { type: 'string', description: 'Owner/user ID to assign (UUID)' },
108
+ },
109
+ required: ['company_id'],
110
+ },
111
+ },
112
+ {
113
+ name: 'salesflare_companies_delete',
114
+ description: 'Delete a company from Salesflare CRM. ' +
115
+ 'Requires company_id. Associated contacts will be unlinked before deletion. ' +
116
+ 'This action cannot be undone.',
117
+ inputSchema: {
118
+ type: 'object',
119
+ properties: {
120
+ company_id: { type: 'string', description: 'Company UUID to delete' },
121
+ },
122
+ required: ['company_id'],
123
+ },
124
+ },
125
+ ];
126
+ /**
127
+ * Exported company tools array for unified registration
128
+ */
129
+ export { companyTools };
130
+ /**
131
+ * Handle company tool calls
132
+ *
133
+ * @param client - Salesflare API client
134
+ * @param name - Tool name
135
+ * @param args - Tool arguments
136
+ * @returns Tool response
137
+ */
138
+ export async function handleCompaniesTool(client, name, args) {
139
+ switch (name) {
140
+ case 'salesflare_companies_list':
141
+ return await handleListCompanies(client, args);
142
+ case 'salesflare_companies_create':
143
+ return await handleCreateCompany(client, args);
144
+ case 'salesflare_companies_update':
145
+ return await handleUpdateCompany(client, args);
146
+ case 'salesflare_companies_delete':
147
+ return await handleDeleteCompany(client, args);
148
+ default:
149
+ throw new SalesflareError({
150
+ code: ErrorCode.INVALID_INPUT,
151
+ message: `Unknown company tool: ${name}`,
152
+ retryable: false,
153
+ });
154
+ }
155
+ }
156
+ /**
157
+ * Register company-related tools with the MCP server
158
+ *
159
+ * @deprecated Use handleCompaniesTool instead for unified registration
160
+ * @param server - MCP Server instance
161
+ * @param client - Salesflare API client
162
+ */
163
+ export function registerCompaniesTools(server, client) {
164
+ // This function is deprecated - use unified tool registration in server.ts
165
+ // Kept for backward compatibility
166
+ }
167
+ /**
168
+ * Handle salesflare_companies_list tool call
169
+ * Implements filters per D-12 to D-16
170
+ */
171
+ async function handleListCompanies(client, params) {
172
+ // Build query parameters from filters
173
+ const queryParams = {};
174
+ if (params.name)
175
+ queryParams.name = params.name;
176
+ if (params.industry)
177
+ queryParams.industry = params.industry;
178
+ if (params.tags && params.tags.length > 0)
179
+ queryParams.tags = params.tags.join(',');
180
+ if (params.owner)
181
+ queryParams.owner = params.owner;
182
+ if (params.search)
183
+ queryParams.search = params.search;
184
+ // Pagination
185
+ queryParams.page = Number(params.page) || 1;
186
+ queryParams.limit = Number(params.limit) || 20;
187
+ // Call Salesflare API (companies endpoint is /accounts)
188
+ const response = await client.get('/accounts', { params: queryParams });
189
+ // Handle different response formats
190
+ const items = response.items || (Array.isArray(response) ? response : []);
191
+ const total = response.total ?? items.length;
192
+ const page = (response.page ?? Number(params.page)) || 1;
193
+ const limit = (response.limit ?? Number(params.limit)) || 20;
194
+ // Transform to curated response format (D-08)
195
+ const companies = items.map((item) => ({
196
+ id: item.id,
197
+ name: item.name,
198
+ website: item.website,
199
+ industry: item.industry,
200
+ phone: item.phone,
201
+ description: item.description,
202
+ address: item.address ? {
203
+ street: item.address.street,
204
+ city: item.address.city,
205
+ state: item.address.state,
206
+ zip: item.address.zip,
207
+ country: item.address.country,
208
+ } : undefined,
209
+ social_media: item.social_media ? {
210
+ linkedin: item.social_media.linkedin,
211
+ twitter: item.social_media.twitter,
212
+ facebook: item.social_media.facebook,
213
+ } : undefined,
214
+ tags: item.tags,
215
+ owner: item.owner ? {
216
+ id: item.owner.id,
217
+ name: item.owner.name,
218
+ } : undefined,
219
+ created_at: item.created_at,
220
+ updated_at: item.updated_at,
221
+ }));
222
+ const listResponse = {
223
+ companies,
224
+ total,
225
+ page,
226
+ limit,
227
+ has_more: page * limit < total,
228
+ };
229
+ // Generate human-readable summary (D-09)
230
+ const summaryText = listResponse.companies.length === 0
231
+ ? 'No companies found matching the criteria.'
232
+ : `Found ${listResponse.total} company(ies). Showing page ${listResponse.page} with ${listResponse.companies.length} result(s).` +
233
+ (listResponse.has_more ? ` More pages available.` : '');
234
+ return {
235
+ content: [
236
+ { type: 'text', text: summaryText },
237
+ { type: 'text', text: JSON.stringify(listResponse, null, 2) },
238
+ ],
239
+ };
240
+ }
241
+ /**
242
+ * Handle salesflare_companies_create tool call
243
+ * Name is required per D-01, other fields optional per D-02
244
+ */
245
+ async function handleCreateCompany(client, params) {
246
+ // Call Salesflare API to create company (endpoint is /accounts)
247
+ const response = await client.post('/accounts', {
248
+ name: params.name,
249
+ website: params.website,
250
+ industry: params.industry,
251
+ phone: params.phone,
252
+ description: params.description,
253
+ address: params.address,
254
+ social_media: params.social_media,
255
+ tags: params.tags,
256
+ owner_id: params.owner_id,
257
+ });
258
+ // Transform to curated response format (D-08)
259
+ const company = {
260
+ id: response.id,
261
+ name: response.name,
262
+ website: response.website,
263
+ industry: response.industry,
264
+ phone: response.phone,
265
+ description: response.description,
266
+ address: response.address ? {
267
+ street: response.address.street,
268
+ city: response.address.city,
269
+ state: response.address.state,
270
+ zip: response.address.zip,
271
+ country: response.address.country,
272
+ } : undefined,
273
+ social_media: response.social_media ? {
274
+ linkedin: response.social_media.linkedin,
275
+ twitter: response.social_media.twitter,
276
+ facebook: response.social_media.facebook,
277
+ } : undefined,
278
+ tags: response.tags,
279
+ owner: response.owner ? {
280
+ id: response.owner.id,
281
+ name: response.owner.name,
282
+ } : undefined,
283
+ created_at: response.created_at,
284
+ updated_at: response.updated_at,
285
+ };
286
+ return {
287
+ content: [
288
+ { type: 'text', text: `Company created: ${company.name}` },
289
+ { type: 'text', text: JSON.stringify(company, null, 2) },
290
+ ],
291
+ };
292
+ }
293
+ /**
294
+ * Handle salesflare_companies_update tool call
295
+ */
296
+ async function handleUpdateCompany(client, params) {
297
+ const { company_id, ...updateData } = params;
298
+ // Check if at least one field is provided for update
299
+ const hasUpdateFields = Object.keys(updateData).length > 0;
300
+ if (!hasUpdateFields) {
301
+ return {
302
+ content: [
303
+ { type: 'text', text: 'Error [INVALID_INPUT]: At least one field must be provided for update.' },
304
+ { type: 'text', text: '{}' },
305
+ ],
306
+ };
307
+ }
308
+ // Call Salesflare API to update company (endpoint is /accounts)
309
+ const response = await client.patch(`/accounts/${company_id}`, updateData);
310
+ // Transform to curated response format (D-08)
311
+ const company = {
312
+ id: response.id,
313
+ name: response.name,
314
+ website: response.website,
315
+ industry: response.industry,
316
+ phone: response.phone,
317
+ description: response.description,
318
+ address: response.address ? {
319
+ street: response.address.street,
320
+ city: response.address.city,
321
+ state: response.address.state,
322
+ zip: response.address.zip,
323
+ country: response.address.country,
324
+ } : undefined,
325
+ social_media: response.social_media ? {
326
+ linkedin: response.social_media.linkedin,
327
+ twitter: response.social_media.twitter,
328
+ facebook: response.social_media.facebook,
329
+ } : undefined,
330
+ tags: response.tags,
331
+ owner: response.owner ? {
332
+ id: response.owner.id,
333
+ name: response.owner.name,
334
+ } : undefined,
335
+ created_at: response.created_at,
336
+ updated_at: response.updated_at,
337
+ };
338
+ return {
339
+ content: [
340
+ { type: 'text', text: `Company updated: ${company.name}` },
341
+ { type: 'text', text: JSON.stringify(company, null, 2) },
342
+ ],
343
+ };
344
+ }
345
+ /**
346
+ * Handle salesflare_companies_delete tool call
347
+ * Implements D-07: Unlink all associated contacts before deletion
348
+ */
349
+ async function handleDeleteCompany(client, params) {
350
+ let unlinkedCount = 0;
351
+ try {
352
+ // Step 1: Find associated contacts with this company_id
353
+ const contactsResponse = await client.get('/contacts', {
354
+ params: { company_id: params.company_id, limit: 100 },
355
+ });
356
+ const associatedContacts = contactsResponse.items || [];
357
+ // Step 2: Unlink all associated contacts (best effort)
358
+ // Set company_id to null to preserve contacts while allowing company deletion
359
+ for (const contact of associatedContacts) {
360
+ try {
361
+ await client.patch(`/contacts/${contact.id}`, { company_id: null });
362
+ unlinkedCount++;
363
+ }
364
+ catch (unlinkError) {
365
+ // Log but don't fail - continue with other contacts and company deletion
366
+ console.warn(`Failed to unlink contact ${contact.id} from company ${params.company_id}:`, unlinkError);
367
+ }
368
+ }
369
+ // Step 3: Delete the company
370
+ await client.delete(`/accounts/${params.company_id}`);
371
+ // Step 4: Return appropriate success message (D-10)
372
+ const message = unlinkedCount > 0
373
+ ? `Company deleted. ${unlinkedCount} associated contact(s) unlinked.`
374
+ : 'Company deleted successfully.';
375
+ return {
376
+ content: [{ type: 'text', text: message }],
377
+ };
378
+ }
379
+ catch (error) {
380
+ // Handle specific error cases
381
+ if (error instanceof SalesflareError) {
382
+ // If company not found, we still report success if we tried to unlink contacts
383
+ if (error.code === ErrorCode.NOT_FOUND && unlinkedCount > 0) {
384
+ return {
385
+ content: [{ type: 'text', text: `Company not found (may have been already deleted). ${unlinkedCount} associated contact(s) were unlinked.` }],
386
+ };
387
+ }
388
+ throw error;
389
+ }
390
+ throw error;
391
+ }
392
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Contact tools for Salesflare MCP Server
3
+ *
4
+ * Implements CRUD operations for contacts:
5
+ * - salesflare_contacts_list: List contacts with filtering and pagination
6
+ * - salesflare_contacts_create: Create new contacts
7
+ * - salesflare_contacts_update: Update existing contacts
8
+ * - salesflare_contacts_delete: Delete contacts
9
+ *
10
+ * @module tools/contacts
11
+ */
12
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
13
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
14
+ import { SalesflareClient } from '../client/salesflare-client.js';
15
+ /**
16
+ * Contact tool definitions with Zod schemas
17
+ */
18
+ declare const contactTools: Tool[];
19
+ /**
20
+ * Exported contact tools array for unified registration
21
+ */
22
+ export { contactTools };
23
+ /**
24
+ * Handle contact tool calls
25
+ *
26
+ * @param client - Salesflare API client
27
+ * @param name - Tool name
28
+ * @param args - Tool arguments
29
+ * @returns Tool response
30
+ */
31
+ export declare function handleContactsTool(client: SalesflareClient, name: string, args: unknown): Promise<{
32
+ content: Array<{
33
+ type: string;
34
+ text: string;
35
+ }>;
36
+ }>;
37
+ /**
38
+ * Register contact-related tools with the MCP server
39
+ *
40
+ * @deprecated Use handleContactsTool instead for unified registration
41
+ * @param server - MCP Server instance
42
+ * @param client - Salesflare API client
43
+ */
44
+ export declare function registerContactsTools(server: Server, client: SalesflareClient): void;
45
+ //# sourceMappingURL=contacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../src/tools/contacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAGL,IAAI,EACL,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAkBlE;;GAEG;AACH,QAAA,MAAM,YAAY,EAAE,IAAI,EAsEvB,CAAC;AAEF;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CAqB7D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAGpF"}