Jake Shore 04f254c40b constant-contact: Complete production MCP server rebuild
- Deleted single-file stub, rebuilt from scratch
- API client with OAuth2, pagination, rate limiting, error handling
- 50+ tools across 9 domains:
  * 12 contact tools (list, get, create, update, delete, search, tags, import/export, activity)
  * 11 campaign tools (list, get, create, update, delete, schedule, send, stats, activities, clone, test)
  * 9 list tools (list, get, create, update, delete, add/remove contacts, membership, stats)
  * 6 segment tools (list, get, create, update, delete, get contacts)
  * 2 template tools (list, get)
  * 11 reporting tools (campaign stats, contact stats, bounces, clicks, opens, forwards, optouts, sends, links)
  * 7 landing page tools (list, get, create, update, delete, publish, stats)
  * 6 social tools (list, get, create, update, delete, publish)
  * 6 tag tools (list, get, create, update, delete, usage)
- 17 React apps (dark theme, standalone Vite, client-side state):
  * contact-dashboard, contact-detail, contact-grid
  * campaign-dashboard, campaign-detail, campaign-builder
  * list-manager, segment-builder, template-gallery
  * report-dashboard, report-detail, bounce-report, engagement-chart
  * landing-page-grid, social-manager, tag-manager, import-wizard
- Full TypeScript types for Constant Contact API v3
- Production-ready: server.ts, main.ts (stdio), comprehensive README
- .env.example, .gitignore, package.json with MCP SDK 1.0.4
2026-02-12 17:22:25 -05:00

314 lines
9.2 KiB
TypeScript

import type { ConstantContactClient } from '../clients/constant-contact.js';
import type { ContactList, Contact } from '../types/index.js';
export function registerListsTools(client: ConstantContactClient) {
return {
// List all contact lists
lists_list: {
description: 'Get all contact lists',
parameters: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of lists to return'
},
include_count: {
type: 'boolean',
description: 'Include membership counts'
},
include_membership_count: {
type: 'string',
enum: ['active', 'all'],
description: 'Type of membership count to include'
}
}
},
handler: async (args: any) => {
const params: any = {};
if (args.limit) params.limit = args.limit;
if (args.include_count) params.include_count = args.include_count;
if (args.include_membership_count) params.include_membership_count = args.include_membership_count;
return await client.getPaginated<ContactList>('/contact_lists', params, args.limit);
}
},
// Get list by ID
lists_get: {
description: 'Get a specific contact list by ID',
parameters: {
type: 'object',
properties: {
list_id: {
type: 'string',
description: 'List ID',
required: true
},
include_membership_count: {
type: 'string',
enum: ['active', 'all'],
description: 'Include membership count'
}
},
required: ['list_id']
},
handler: async (args: any) => {
const params = args.include_membership_count ?
{ include_membership_count: args.include_membership_count } : undefined;
return await client.get<ContactList>(`/contact_lists/${args.list_id}`, params);
}
},
// Create list
lists_create: {
description: 'Create a new contact list',
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'List name',
required: true
},
description: {
type: 'string',
description: 'List description'
},
favorite: {
type: 'boolean',
description: 'Mark as favorite list'
}
},
required: ['name']
},
handler: async (args: any) => {
const listData: Partial<ContactList> = {
name: args.name
};
if (args.description) listData.description = args.description;
if (args.favorite !== undefined) listData.favorite = args.favorite;
return await client.post<ContactList>('/contact_lists', listData);
}
},
// Update list
lists_update: {
description: 'Update an existing contact list',
parameters: {
type: 'object',
properties: {
list_id: {
type: 'string',
description: 'List ID',
required: true
},
name: {
type: 'string',
description: 'New list name'
},
description: {
type: 'string',
description: 'New list description'
},
favorite: {
type: 'boolean',
description: 'Mark as favorite'
}
},
required: ['list_id']
},
handler: async (args: any) => {
const { list_id, ...updates } = args;
return await client.put<ContactList>(`/contact_lists/${list_id}`, updates);
}
},
// Delete list
lists_delete: {
description: 'Delete a contact list',
parameters: {
type: 'object',
properties: {
list_id: {
type: 'string',
description: 'List ID to delete',
required: true
}
},
required: ['list_id']
},
handler: async (args: any) => {
await client.delete(`/contact_lists/${args.list_id}`);
return { success: true, message: `List ${args.list_id} deleted` };
}
},
// Add contacts to list
lists_add_contacts: {
description: 'Add one or more contacts to a list',
parameters: {
type: 'object',
properties: {
list_id: {
type: 'string',
description: 'List ID',
required: true
},
contact_ids: {
type: 'array',
items: { type: 'string' },
description: 'Array of contact IDs to add',
required: true
}
},
required: ['list_id', 'contact_ids']
},
handler: async (args: any) => {
const results = [];
for (const contactId of args.contact_ids) {
try {
// Get contact, add list to memberships, update
const contact = await client.get<Contact>(`/contacts/${contactId}`);
const memberships = contact.list_memberships || [];
if (!memberships.includes(args.list_id)) {
memberships.push(args.list_id);
await client.put(`/contacts/${contactId}`, {
list_memberships: memberships
});
results.push({ contact_id: contactId, success: true });
} else {
results.push({ contact_id: contactId, success: true, message: 'Already member' });
}
} catch (error: any) {
results.push({ contact_id: contactId, success: false, error: error.message });
}
}
return { results };
}
},
// Remove contacts from list
lists_remove_contacts: {
description: 'Remove contacts from a list',
parameters: {
type: 'object',
properties: {
list_id: {
type: 'string',
description: 'List ID',
required: true
},
contact_ids: {
type: 'array',
items: { type: 'string' },
description: 'Array of contact IDs to remove',
required: true
}
},
required: ['list_id', 'contact_ids']
},
handler: async (args: any) => {
const results = [];
for (const contactId of args.contact_ids) {
try {
const contact = await client.get<Contact>(`/contacts/${contactId}`);
const memberships = (contact.list_memberships || []).filter(
(id) => id !== args.list_id
);
await client.put(`/contacts/${contactId}`, {
list_memberships: memberships
});
results.push({ contact_id: contactId, success: true });
} catch (error: any) {
results.push({ contact_id: contactId, success: false, error: error.message });
}
}
return { results };
}
},
// Get list membership
lists_get_membership: {
description: 'Get all contacts that are members of a list',
parameters: {
type: 'object',
properties: {
list_id: {
type: 'string',
description: 'List ID',
required: true
},
limit: {
type: 'number',
description: 'Maximum number of contacts to return'
},
status: {
type: 'string',
enum: ['all', 'active', 'unsubscribed'],
description: 'Filter by contact status'
}
},
required: ['list_id']
},
handler: async (args: any) => {
const params: any = {
list_ids: args.list_id
};
if (args.status) params.status = args.status;
if (args.limit) params.limit = args.limit;
const contacts = await client.getPaginated<Contact>('/contacts', params, args.limit);
return { contacts, count: contacts.length };
}
},
// Get list statistics
lists_get_stats: {
description: 'Get statistics about a list',
parameters: {
type: 'object',
properties: {
list_id: {
type: 'string',
description: 'List ID',
required: true
}
},
required: ['list_id']
},
handler: async (args: any) => {
const list = await client.get<ContactList>(
`/contact_lists/${args.list_id}?include_membership_count=all`
);
// Get contacts to calculate additional stats
const contacts = await client.getPaginated<Contact>(
'/contacts',
{ list_ids: args.list_id, status: 'all' }
);
const activeCount = contacts.filter(c => c.permission_to_send === 'implicit').length;
const unsubscribedCount = contacts.filter(c => c.permission_to_send === 'unsubscribed').length;
return {
list_id: args.list_id,
name: list.name,
total_members: list.membership_count || 0,
active_members: activeCount,
unsubscribed_members: unsubscribedCount
};
}
}
};
}