- READMEs added: asana, close, freshdesk, google-console, gusto, square - main.ts + server.ts (lazy loading): activecampaign, clickup, klaviyo, mailchimp, pipedrive, trello, touchbistro, closebot, close, google-console - All 13 compile with 0 TSC errors
242 lines
6.3 KiB
TypeScript
242 lines
6.3 KiB
TypeScript
/**
|
|
* ActiveCampaign MCP Server Class
|
|
* Implements lazy-loaded tool modules for optimal performance
|
|
*/
|
|
|
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
import {
|
|
CallToolRequestSchema,
|
|
ListToolsRequestSchema,
|
|
ListResourcesRequestSchema,
|
|
ReadResourceRequestSchema,
|
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
import { ActiveCampaignClient } from './client/index.js';
|
|
|
|
type ToolModule = Record<string, any>;
|
|
|
|
// Available app resources
|
|
const APPS = [
|
|
'contact-manager',
|
|
'deal-pipeline',
|
|
'list-builder',
|
|
'campaign-dashboard',
|
|
'automation-builder',
|
|
'form-manager',
|
|
'tag-organizer',
|
|
'task-center',
|
|
'notes-viewer',
|
|
'pipeline-settings',
|
|
'account-directory',
|
|
'webhook-manager',
|
|
'email-analytics',
|
|
'segment-viewer',
|
|
'site-tracking',
|
|
'score-dashboard',
|
|
];
|
|
|
|
export class ActiveCampaignMCPServer {
|
|
private server: Server;
|
|
private client: ActiveCampaignClient;
|
|
private toolModules: Map<string, () => Promise<ToolModule>>;
|
|
private loadedTools: ToolModule | null = null;
|
|
|
|
constructor(client: ActiveCampaignClient) {
|
|
this.client = client;
|
|
this.toolModules = new Map();
|
|
|
|
this.server = new Server(
|
|
{
|
|
name: 'activecampaign-server',
|
|
version: '1.0.0',
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {},
|
|
resources: {},
|
|
},
|
|
}
|
|
);
|
|
|
|
this.setupToolModules();
|
|
this.setupHandlers();
|
|
}
|
|
|
|
private setupToolModules() {
|
|
// Register lazy-loaded tool modules
|
|
this.toolModules.set('contacts', async () => {
|
|
const module = await import('./tools/contacts.js');
|
|
return module.createContactTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('deals', async () => {
|
|
const module = await import('./tools/deals.js');
|
|
return module.createDealTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('lists', async () => {
|
|
const module = await import('./tools/lists.js');
|
|
return module.createListTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('campaigns', async () => {
|
|
const module = await import('./tools/campaigns.js');
|
|
return module.createCampaignTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('automations', async () => {
|
|
const module = await import('./tools/automations.js');
|
|
return module.createAutomationTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('forms', async () => {
|
|
const module = await import('./tools/forms.js');
|
|
return module.createFormTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('tags', async () => {
|
|
const module = await import('./tools/tags.js');
|
|
return module.createTagTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('tasks', async () => {
|
|
const module = await import('./tools/tasks.js');
|
|
return module.createTaskTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('notes', async () => {
|
|
const module = await import('./tools/notes.js');
|
|
return module.createNoteTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('pipelines', async () => {
|
|
const module = await import('./tools/pipelines.js');
|
|
return module.createPipelineTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('accounts', async () => {
|
|
const module = await import('./tools/accounts.js');
|
|
return module.createAccountTools(this.client);
|
|
});
|
|
|
|
this.toolModules.set('webhooks', async () => {
|
|
const module = await import('./tools/webhooks.js');
|
|
return module.createWebhookTools(this.client);
|
|
});
|
|
}
|
|
|
|
private async loadAllTools(): Promise<ToolModule> {
|
|
if (this.loadedTools) {
|
|
return this.loadedTools;
|
|
}
|
|
|
|
const allTools: ToolModule = {};
|
|
|
|
for (const [name, loader] of this.toolModules.entries()) {
|
|
const tools = await loader();
|
|
Object.assign(allTools, tools);
|
|
}
|
|
|
|
this.loadedTools = allTools;
|
|
return allTools;
|
|
}
|
|
|
|
private setupHandlers() {
|
|
// List available tools
|
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
const allTools = await this.loadAllTools();
|
|
|
|
return {
|
|
tools: Object.entries(allTools).map(([name, tool]) => ({
|
|
name,
|
|
description: tool.description,
|
|
inputSchema: tool.inputSchema,
|
|
})),
|
|
};
|
|
});
|
|
|
|
// Execute tool
|
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
const allTools = await this.loadAllTools();
|
|
const toolName = request.params.name;
|
|
const tool = allTools[toolName];
|
|
|
|
if (!tool) {
|
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
}
|
|
|
|
try {
|
|
const result = await tool.handler(request.params.arguments || {});
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: JSON.stringify(result, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
},
|
|
],
|
|
isError: true,
|
|
};
|
|
}
|
|
});
|
|
|
|
// List app resources
|
|
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
return {
|
|
resources: APPS.map((app) => ({
|
|
uri: `activecampaign://app/${app}`,
|
|
mimeType: 'text/html',
|
|
name: app
|
|
.split('-')
|
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
.join(' '),
|
|
})),
|
|
};
|
|
});
|
|
|
|
// Read app resource
|
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
const uri = request.params.uri;
|
|
const match = uri.match(/^activecampaign:\/\/app\/(.+)$/);
|
|
|
|
if (!match) {
|
|
throw new Error(`Invalid resource URI: ${uri}`);
|
|
}
|
|
|
|
const appName = match[1];
|
|
if (!APPS.includes(appName)) {
|
|
throw new Error(`Unknown app: ${appName}`);
|
|
}
|
|
|
|
try {
|
|
// Dynamically import the app
|
|
const appModule = await import(`./apps/${appName}/index.js`);
|
|
const html = appModule.default();
|
|
|
|
return {
|
|
contents: [
|
|
{
|
|
uri,
|
|
mimeType: 'text/html',
|
|
text: html,
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
throw new Error(`Failed to load app ${appName}: ${error}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
async connect(transport: any) {
|
|
await this.server.connect(transport);
|
|
}
|
|
}
|