Jake Shore ced6b4933b pipedrive: Add 20 React MCP Apps
- deal-dashboard: Overview stats, won/lost ratio, revenue forecast
- deal-detail: Full deal with products, activities, participants, timeline
- deal-grid: Sortable deal list with filters
- pipeline-kanban: Drag-drop pipeline board
- pipeline-analytics: Conversion rates, velocity, bottleneck analysis
- pipeline-funnel: Visual funnel with stage metrics
- person-detail: Contact card with deals, activities, files
- person-grid: Contact directory with search
- org-detail: Organization with people, deals, activities
- org-grid: Organization directory
- activity-dashboard: Activity calendar/list with completion tracking
- activity-calendar: Monthly calendar view
- lead-inbox: Lead list with labels and quick actions
- product-catalog: Product list with pricing
- goal-tracker: Goals with progress bars
- revenue-dashboard: Revenue analytics, forecasting
- email-inbox: Mail threads with preview
- deals-timeline: Timeline of deal progression
- search-results: Universal search
- won-deals: Closed-won deals celebration view

All apps use React with dark theme. Self-contained with inline shared components.
Each app has App.tsx, index.html, and vite.config.ts.
Ports 3000-3019 for dev servers.
2026-02-12 17:09:57 -05:00

680 lines
21 KiB
TypeScript

import { MailchimpConfig, MailchimpError, PaginatedResponse } from '../types/index.js';
export class MailchimpClient {
private apiKey: string;
private server: string;
private baseUrl: string;
private rateLimitRemaining: number = 10;
private rateLimitReset: number = Date.now();
constructor(config: MailchimpConfig) {
this.apiKey = config.apiKey;
// Extract data center from API key (format: xxxxxxxxxxxxxxxxxxxxx-us19)
if (config.server) {
this.server = config.server;
} else {
const parts = this.apiKey.split('-');
if (parts.length !== 2) {
throw new Error('Invalid API key format. Expected format: key-datacenter (e.g., xxx-us19)');
}
this.server = parts[1];
}
this.baseUrl = `https://${this.server}.api.mailchimp.com/3.0`;
}
/**
* Make an authenticated request to the Mailchimp API
*/
private async request<T>(
method: string,
endpoint: string,
data?: any,
params?: Record<string, any>
): Promise<T> {
// Rate limiting check
if (this.rateLimitRemaining <= 0 && Date.now() < this.rateLimitReset) {
const waitTime = this.rateLimitReset - Date.now();
await new Promise(resolve => setTimeout(resolve, waitTime));
}
// Build URL with query params
const url = new URL(`${this.baseUrl}${endpoint}`);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, String(value));
}
});
}
// Prepare headers - Mailchimp accepts Basic auth with "anystring" as username
const headers: HeadersInit = {
'Content-Type': 'application/json',
'Authorization': `Basic ${Buffer.from(`anystring:${this.apiKey}`).toString('base64')}`,
'User-Agent': 'Mailchimp-MCP-Server/1.0'
};
const options: RequestInit = {
method,
headers
};
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url.toString(), options);
// Update rate limit info from headers
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
if (remaining) this.rateLimitRemaining = parseInt(remaining);
if (reset) this.rateLimitReset = new Date(reset).getTime();
// Handle non-2xx responses
if (!response.ok) {
let errorData: MailchimpError;
try {
errorData = await response.json() as MailchimpError;
} catch {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
throw new Error(
`Mailchimp API Error (${errorData.status}): ${errorData.title} - ${errorData.detail}`
);
}
// Handle 204 No Content
if (response.status === 204) {
return {} as T;
}
const result = await response.json();
return result as T;
} catch (error: any) {
if (error.message?.includes('Mailchimp API Error')) {
throw error;
}
throw new Error(`Request failed: ${error.message}`);
}
}
// ===== HTTP METHOD HELPERS =====
async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
return this.request<T>('GET', endpoint, undefined, params);
}
async post<T>(endpoint: string, data?: any, params?: Record<string, any>): Promise<T> {
return this.request<T>('POST', endpoint, data, params);
}
async put<T>(endpoint: string, data?: any, params?: Record<string, any>): Promise<T> {
return this.request<T>('PUT', endpoint, data, params);
}
async patch<T>(endpoint: string, data?: any, params?: Record<string, any>): Promise<T> {
return this.request<T>('PATCH', endpoint, data, params);
}
async delete<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
return this.request<T>('DELETE', endpoint, undefined, params);
}
// ===== PAGINATION HELPER =====
/**
* Fetch all pages of a paginated endpoint
*/
async getAllPages<T>(
endpoint: string,
params: Record<string, any> = {},
itemsKey: string = 'items'
): Promise<T[]> {
const allItems: T[] = [];
let offset = 0;
const count = params.count || 100; // Default page size
while (true) {
const response = await this.get<PaginatedResponse<T>>(endpoint, {
...params,
offset,
count
});
const items = response[itemsKey] as T[];
if (!items || items.length === 0) break;
allItems.push(...items);
// Check if we've fetched all items
if (allItems.length >= response.total_items) break;
offset += count;
}
return allItems;
}
// ===== CAMPAIGNS =====
async getCampaigns(params?: {
count?: number;
offset?: number;
type?: string;
status?: string;
before_send_time?: string;
since_send_time?: string;
before_create_time?: string;
since_create_time?: string;
list_id?: string;
folder_id?: string;
sort_field?: string;
sort_dir?: 'ASC' | 'DESC';
}) {
return this.get('/campaigns', params);
}
async getCampaign(campaignId: string, params?: { fields?: string[]; exclude_fields?: string[] }) {
return this.get(`/campaigns/${campaignId}`, params);
}
async createCampaign(data: any) {
return this.post('/campaigns', data);
}
async updateCampaign(campaignId: string, data: any) {
return this.patch(`/campaigns/${campaignId}`, data);
}
async deleteCampaign(campaignId: string) {
return this.delete(`/campaigns/${campaignId}`);
}
async sendCampaign(campaignId: string) {
return this.post(`/campaigns/${campaignId}/actions/send`);
}
async scheduleCampaign(campaignId: string, schedule_time: string, timewarp?: boolean, batch_delivery?: any) {
return this.post(`/campaigns/${campaignId}/actions/schedule`, {
schedule_time,
timewarp,
batch_delivery
});
}
async unscheduleCampaign(campaignId: string) {
return this.post(`/campaigns/${campaignId}/actions/unschedule`);
}
async pauseCampaign(campaignId: string) {
return this.post(`/campaigns/${campaignId}/actions/pause`);
}
async resumeCampaign(campaignId: string) {
return this.post(`/campaigns/${campaignId}/actions/resume`);
}
async replicateCampaign(campaignId: string) {
return this.post(`/campaigns/${campaignId}/actions/replicate`);
}
async testCampaign(campaignId: string, test_emails: string[], send_type: 'html' | 'plaintext') {
return this.post(`/campaigns/${campaignId}/actions/test`, { test_emails, send_type });
}
async getCampaignContent(campaignId: string) {
return this.get(`/campaigns/${campaignId}/content`);
}
async setCampaignContent(campaignId: string, data: any) {
return this.put(`/campaigns/${campaignId}/content`, data);
}
async getSendChecklist(campaignId: string) {
return this.get(`/campaigns/${campaignId}/send-checklist`);
}
// ===== LISTS / AUDIENCES =====
async getLists(params?: {
count?: number;
offset?: number;
before_date_created?: string;
since_date_created?: string;
before_campaign_last_sent?: string;
since_campaign_last_sent?: string;
email?: string;
sort_field?: string;
sort_dir?: 'ASC' | 'DESC';
}) {
return this.get('/lists', params);
}
async getList(listId: string) {
return this.get(`/lists/${listId}`);
}
async createList(data: any) {
return this.post('/lists', data);
}
async updateList(listId: string, data: any) {
return this.patch(`/lists/${listId}`, data);
}
async deleteList(listId: string) {
return this.delete(`/lists/${listId}`);
}
async getListGrowthHistory(listId: string, params?: { count?: number; offset?: number; sort_field?: string; sort_dir?: 'ASC' | 'DESC' }) {
return this.get(`/lists/${listId}/growth-history`, params);
}
async batchSubscribe(listId: string, members: any[], update_existing?: boolean) {
return this.post(`/lists/${listId}`, {
members,
update_existing
});
}
// ===== MEMBERS =====
async getMembers(listId: string, params?: {
count?: number;
offset?: number;
email_type?: string;
status?: string;
since_timestamp_opt?: string;
before_timestamp_opt?: string;
since_last_changed?: string;
before_last_changed?: string;
unique_email_id?: string;
vip_only?: boolean;
interest_category_id?: string;
interest_ids?: string;
interest_match?: 'any' | 'all';
sort_field?: string;
sort_dir?: 'ASC' | 'DESC';
}) {
return this.get(`/lists/${listId}/members`, params);
}
async getMember(listId: string, subscriberHash: string) {
return this.get(`/lists/${listId}/members/${subscriberHash}`);
}
async addMember(listId: string, data: any) {
return this.post(`/lists/${listId}/members`, data);
}
async updateMember(listId: string, subscriberHash: string, data: any) {
return this.patch(`/lists/${listId}/members/${subscriberHash}`, data);
}
async addOrUpdateMember(listId: string, subscriberHash: string, data: any) {
return this.put(`/lists/${listId}/members/${subscriberHash}`, data);
}
async deleteMember(listId: string, subscriberHash: string) {
return this.delete(`/lists/${listId}/members/${subscriberHash}`);
}
async archiveMember(listId: string, subscriberHash: string) {
return this.delete(`/lists/${listId}/members/${subscriberHash}/actions/delete-permanent`);
}
async getMemberActivity(listId: string, subscriberHash: string, params?: { count?: number; offset?: number; fields?: string[]; exclude_fields?: string[] }) {
return this.get(`/lists/${listId}/members/${subscriberHash}/activity`, params);
}
async getMemberGoals(listId: string, subscriberHash: string) {
return this.get(`/lists/${listId}/members/${subscriberHash}/goals`);
}
async getMemberTags(listId: string, subscriberHash: string) {
return this.get(`/lists/${listId}/members/${subscriberHash}/tags`);
}
async addMemberTags(listId: string, subscriberHash: string, tags: Array<{ name: string; status: 'active' | 'inactive' }>) {
return this.post(`/lists/${listId}/members/${subscriberHash}/tags`, { tags });
}
// ===== SEGMENTS =====
async getSegments(listId: string, params?: { count?: number; offset?: number; type?: string; since_created_at?: string; before_created_at?: string; since_updated_at?: string; before_updated_at?: string }) {
return this.get(`/lists/${listId}/segments`, params);
}
async getSegment(listId: string, segmentId: string) {
return this.get(`/lists/${listId}/segments/${segmentId}`);
}
async createSegment(listId: string, data: any) {
return this.post(`/lists/${listId}/segments`, data);
}
async updateSegment(listId: string, segmentId: string, data: any) {
return this.patch(`/lists/${listId}/segments/${segmentId}`, data);
}
async deleteSegment(listId: string, segmentId: string) {
return this.delete(`/lists/${listId}/segments/${segmentId}`);
}
async batchAddSegmentMembers(listId: string, segmentId: string, members: string[]) {
return this.post(`/lists/${listId}/segments/${segmentId}/members`, {
members_to_add: members
});
}
async batchRemoveSegmentMembers(listId: string, segmentId: string, members: string[]) {
return this.post(`/lists/${listId}/segments/${segmentId}/members`, {
members_to_remove: members
});
}
// ===== INTEREST CATEGORIES & INTERESTS =====
async getInterestCategories(listId: string, params?: { count?: number; offset?: number; type?: string }) {
return this.get(`/lists/${listId}/interest-categories`, params);
}
async getInterests(listId: string, categoryId: string, params?: { count?: number; offset?: number }) {
return this.get(`/lists/${listId}/interest-categories/${categoryId}/interests`, params);
}
// ===== TEMPLATES =====
async getTemplates(params?: { count?: number; offset?: number; type?: string; category?: string; folder_id?: string; created_by?: string; since_created_at?: string; before_created_at?: string; sort_field?: string; sort_dir?: 'ASC' | 'DESC' }) {
return this.get('/templates', params);
}
async getTemplate(templateId: string) {
return this.get(`/templates/${templateId}`);
}
async createTemplate(data: any) {
return this.post('/templates', data);
}
async updateTemplate(templateId: string, data: any) {
return this.patch(`/templates/${templateId}`, data);
}
async deleteTemplate(templateId: string) {
return this.delete(`/templates/${templateId}`);
}
async getTemplateDefaultContent(templateId: string) {
return this.get(`/templates/${templateId}/default-content`);
}
// ===== AUTOMATIONS =====
async getAutomations(params?: { count?: number; offset?: number; before_create_time?: string; since_create_time?: string; before_start_time?: string; since_start_time?: string; status?: string }) {
return this.get('/automations', params);
}
async getAutomation(workflowId: string) {
return this.get(`/automations/${workflowId}`);
}
async pauseAutomation(workflowId: string) {
return this.post(`/automations/${workflowId}/actions/pause-all-emails`);
}
async startAutomation(workflowId: string) {
return this.post(`/automations/${workflowId}/actions/start-all-emails`);
}
async getAutomationEmails(workflowId: string) {
return this.get(`/automations/${workflowId}/emails`);
}
async getAutomationEmail(workflowId: string, emailId: string) {
return this.get(`/automations/${workflowId}/emails/${emailId}`);
}
async pauseAutomationEmail(workflowId: string, emailId: string) {
return this.post(`/automations/${workflowId}/emails/${emailId}/actions/pause`);
}
async startAutomationEmail(workflowId: string, emailId: string) {
return this.post(`/automations/${workflowId}/emails/${emailId}/actions/start`);
}
async getAutomationQueue(workflowId: string, emailId: string) {
return this.get(`/automations/${workflowId}/emails/${emailId}/queue`);
}
async addToAutomationQueue(workflowId: string, emailId: string, email_address: string) {
return this.post(`/automations/${workflowId}/emails/${emailId}/queue`, { email_address });
}
// ===== REPORTS =====
async getReports(params?: { count?: number; offset?: number; type?: string; before_send_time?: string; since_send_time?: string }) {
return this.get('/reports', params);
}
async getReport(campaignId: string) {
return this.get(`/reports/${campaignId}`);
}
async getClickReports(campaignId: string, params?: { count?: number; offset?: number }) {
return this.get(`/reports/${campaignId}/click-details`, params);
}
async getClickReport(campaignId: string, linkId: string) {
return this.get(`/reports/${campaignId}/click-details/${linkId}`);
}
async getOpenDetails(campaignId: string, params?: { count?: number; offset?: number; since?: string }) {
return this.get(`/reports/${campaignId}/open-details`, params);
}
async getDomainPerformance(campaignId: string) {
return this.get(`/reports/${campaignId}/domain-performance`);
}
async getEmailActivity(campaignId: string, subscriberHash: string) {
return this.get(`/reports/${campaignId}/email-activity/${subscriberHash}`);
}
async getSubReports(campaignId: string) {
return this.get(`/reports/${campaignId}/sub-reports`);
}
// ===== LANDING PAGES =====
async getLandingPages(params?: { count?: number; offset?: number; sort_field?: string; sort_dir?: 'ASC' | 'DESC' }) {
return this.get('/landing-pages', params);
}
async getLandingPage(pageId: string) {
return this.get(`/landing-pages/${pageId}`);
}
async createLandingPage(data: any) {
return this.post('/landing-pages', data);
}
async updateLandingPage(pageId: string, data: any) {
return this.patch(`/landing-pages/${pageId}`, data);
}
async deleteLandingPage(pageId: string) {
return this.delete(`/landing-pages/${pageId}`);
}
async publishLandingPage(pageId: string) {
return this.post(`/landing-pages/${pageId}/actions/publish`);
}
async unpublishLandingPage(pageId: string) {
return this.post(`/landing-pages/${pageId}/actions/unpublish`);
}
async getLandingPageContent(pageId: string) {
return this.get(`/landing-pages/${pageId}/content`);
}
// ===== ECOMMERCE =====
async getStores(params?: { count?: number; offset?: number }) {
return this.get('/ecommerce/stores', params);
}
async getStore(storeId: string) {
return this.get(`/ecommerce/stores/${storeId}`);
}
async addStore(data: any) {
return this.post('/ecommerce/stores', data);
}
async updateStore(storeId: string, data: any) {
return this.patch(`/ecommerce/stores/${storeId}`, data);
}
async deleteStore(storeId: string) {
return this.delete(`/ecommerce/stores/${storeId}`);
}
async getProducts(storeId: string, params?: { count?: number; offset?: number }) {
return this.get(`/ecommerce/stores/${storeId}/products`, params);
}
async getProduct(storeId: string, productId: string) {
return this.get(`/ecommerce/stores/${storeId}/products/${productId}`);
}
async addProduct(storeId: string, data: any) {
return this.post(`/ecommerce/stores/${storeId}/products`, data);
}
async updateProduct(storeId: string, productId: string, data: any) {
return this.patch(`/ecommerce/stores/${storeId}/products/${productId}`, data);
}
async deleteProduct(storeId: string, productId: string) {
return this.delete(`/ecommerce/stores/${storeId}/products/${productId}`);
}
async getOrders(storeId: string, params?: { count?: number; offset?: number; campaign_id?: string; customer_id?: string; has_outreach?: boolean }) {
return this.get(`/ecommerce/stores/${storeId}/orders`, params);
}
async getOrder(storeId: string, orderId: string) {
return this.get(`/ecommerce/stores/${storeId}/orders/${orderId}`);
}
async addOrder(storeId: string, data: any) {
return this.post(`/ecommerce/stores/${storeId}/orders`, data);
}
async updateOrder(storeId: string, orderId: string, data: any) {
return this.patch(`/ecommerce/stores/${storeId}/orders/${orderId}`, data);
}
async deleteOrder(storeId: string, orderId: string) {
return this.delete(`/ecommerce/stores/${storeId}/orders/${orderId}`);
}
async getCarts(storeId: string, params?: { count?: number; offset?: number }) {
return this.get(`/ecommerce/stores/${storeId}/carts`, params);
}
async getCart(storeId: string, cartId: string) {
return this.get(`/ecommerce/stores/${storeId}/carts/${cartId}`);
}
async addCart(storeId: string, data: any) {
return this.post(`/ecommerce/stores/${storeId}/carts`, data);
}
async updateCart(storeId: string, cartId: string, data: any) {
return this.patch(`/ecommerce/stores/${storeId}/carts/${cartId}`, data);
}
async deleteCart(storeId: string, cartId: string) {
return this.delete(`/ecommerce/stores/${storeId}/carts/${cartId}`);
}
async getCustomers(storeId: string, params?: { count?: number; offset?: number; email_address?: string }) {
return this.get(`/ecommerce/stores/${storeId}/customers`, params);
}
async getCustomer(storeId: string, customerId: string) {
return this.get(`/ecommerce/stores/${storeId}/customers/${customerId}`);
}
async addCustomer(storeId: string, data: any) {
return this.post(`/ecommerce/stores/${storeId}/customers`, data);
}
async updateCustomer(storeId: string, customerId: string, data: any) {
return this.patch(`/ecommerce/stores/${storeId}/customers/${customerId}`, data);
}
async deleteCustomer(storeId: string, customerId: string) {
return this.delete(`/ecommerce/stores/${storeId}/customers/${customerId}`);
}
async getPromoRules(storeId: string, params?: { count?: number; offset?: number }) {
return this.get(`/ecommerce/stores/${storeId}/promo-rules`, params);
}
async getPromoRule(storeId: string, ruleId: string) {
return this.get(`/ecommerce/stores/${storeId}/promo-rules/${ruleId}`);
}
async addPromoRule(storeId: string, data: any) {
return this.post(`/ecommerce/stores/${storeId}/promo-rules`, data);
}
async getPromoCodes(storeId: string, ruleId: string, params?: { count?: number; offset?: number }) {
return this.get(`/ecommerce/stores/${storeId}/promo-rules/${ruleId}/promo-codes`, params);
}
async getPromoCode(storeId: string, ruleId: string, codeId: string) {
return this.get(`/ecommerce/stores/${storeId}/promo-rules/${ruleId}/promo-codes/${codeId}`);
}
async addPromoCode(storeId: string, ruleId: string, data: any) {
return this.post(`/ecommerce/stores/${storeId}/promo-rules/${ruleId}/promo-codes`, data);
}
// ===== TAGS =====
async searchTags(listId: string, name: string) {
return this.get(`/lists/${listId}/tag-search`, { name });
}
// ===== SEARCH =====
async searchCampaigns(query: string, params?: { count?: number; offset?: number }) {
return this.get('/search-campaigns', { query, ...params });
}
async searchMembers(query: string, params?: { list_id?: string; count?: number; offset?: number }) {
return this.get('/search-members', { query, ...params });
}
// ===== PING =====
async ping() {
return this.get('/ping');
}
async getRoot() {
return this.get('/');
}
}