import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { client } from '../client.js';
const ContactInputSchema = z.object({
firstName: z.string().min(1).optional().describe('First name'),
lastName: z.string().min(1).optional().describe('Last name'),
fullName: z.string().optional().describe('Full name (alternative to first/last)'),
address1: z.string().min(1).describe('Street address'),
address2: z.string().optional().describe('Apartment, suite, etc.'),
city: z.string().min(1).describe('City'),
state: z.string().length(2).describe('2-letter state code'),
zip: z.string().min(5).describe('ZIP code (5 or 9 digit)'),
email: z.string().email().optional().describe('Email address'),
phone: z.string().optional().describe('Phone number'),
});
export function registerContactsTools(server: McpServer): void {
server.tool(
'create_contact',
'Create a new contact that can be used as a recipient for mailings. Address is automatically validated.',
ContactInputSchema.shape,
async (params) => {
try {
const contact = await client.post('/contacts', params);
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(contact, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
);
server.tool(
'create_contacts_batch',
'Create multiple contacts at once (max 1000). Each contact address is validated.',
{
contacts: z
.array(ContactInputSchema)
.min(1)
.max(1000)
.describe('Array of contacts to create'),
},
async (params) => {
try {
const result = await client.post('/contacts/batch', {
contacts: params.contacts,
});
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
);
server.tool(
'match_contact',
'Find and enrich a contact by email, phone, or property address. Returns enriched property data if a match is found.',
{
email: z.string().email().optional().describe('Email to match'),
phone: z.string().optional().describe('Phone number to match'),
propertyAddress: z
.string()
.optional()
.describe('Property address to match (e.g., "123 Main St, Austin, TX 78701")'),
},
async (params) => {
try {
const result = await client.post('/contacts/match', params);
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
);
server.tool(
'list_contacts',
'Get a paginated list of contacts with optional search and status filters.',
{
limit: z.number().int().min(1).max(100).default(20).describe('Number of results per page'),
offset: z.number().int().min(0).default(0).describe('Pagination offset'),
search: z.string().optional().describe('Search by name, email, or address'),
status: z
.enum(['active', 'inactive', 'bounced', 'do_not_mail'])
.optional()
.describe('Filter by status'),
},
async (params) => {
try {
const result = await client.get('/contacts', {
limit: params.limit,
offset: params.offset,
search: params.search,
status: params.status,
});
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
);
server.tool(
'get_contact',
'Get detailed information about a specific contact.',
{
contactId: z.string().describe('Contact ID (e.g., contact_12345)'),
},
async (params) => {
try {
const contact = await client.get(`/contacts/${params.contactId}`);
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(contact, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
);
server.tool(
'update_contact',
'Update contact information. Only provided fields will be updated.',
{
contactId: z.string().describe('Contact ID to update'),
firstName: z.string().min(1).optional().describe('First name'),
lastName: z.string().min(1).optional().describe('Last name'),
fullName: z.string().optional().describe('Full name'),
address1: z.string().min(1).optional().describe('Street address'),
address2: z.string().optional().describe('Apartment, suite, etc.'),
city: z.string().min(1).optional().describe('City'),
state: z.string().length(2).optional().describe('2-letter state code'),
zip: z.string().min(5).optional().describe('ZIP code'),
email: z.string().email().optional().describe('Email address'),
phone: z.string().optional().describe('Phone number'),
status: z
.enum(['active', 'inactive', 'do_not_mail'])
.optional()
.describe('Contact status'),
},
async (params) => {
try {
const { contactId, ...updateData } = params;
const contact = await client.patch(`/contacts/${contactId}`, updateData);
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(contact, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
);
server.tool(
'delete_contact',
'Soft-delete a contact by marking them as inactive. They will no longer receive mailings.',
{
contactId: z.string().describe('Contact ID to delete'),
},
async (params) => {
try {
const result = await client.delete(`/contacts/${params.contactId}`);
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
);
server.tool(
'get_contact_history',
'Get the mailing history for a contact, including all campaigns they were enrolled in.',
{
contactId: z.string().describe('Contact ID'),
limit: z.number().int().min(1).max(100).default(50).describe('Number of results per page'),
offset: z.number().int().min(0).default(0).describe('Pagination offset'),
},
async (params) => {
try {
const result = await client.get(`/contacts/${params.contactId}/history`, {
limit: params.limit,
offset: params.offset,
});
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text' as const,
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
}
);
}