keap: Complete MCP server with 111 tools and 20 React apps

This commit is contained in:
Jake Shore 2026-02-12 17:27:01 -05:00
parent e78b92a140
commit 91b2d348be
117 changed files with 5941 additions and 1748 deletions

View File

@ -0,0 +1,2 @@
KEAP_API_KEY=your_api_key_here
KEAP_ACCESS_TOKEN=your_oauth_access_token_here

471
servers/keap/README.md Normal file
View File

@ -0,0 +1,471 @@
# Keap MCP Server
A comprehensive Model Context Protocol (MCP) server for Keap (formerly Infusionsoft) - providing AI-powered access to contact management, sales pipeline, marketing automation, e-commerce, and more.
## 🚀 Features
- **111 MCP Tools** across 14 categories
- **20 React Apps** with dark theme UI matching Keap brand
- Full OAuth 2.0 PKCE authentication flow
- Type-safe API client with comprehensive error handling
- Real-time data synchronization
- Webhook support for automation
## 📦 Installation
```bash
npm install @mcpengine/keap
```
## 🔧 Configuration
Create a `.env` file:
```env
KEAP_CLIENT_ID=your_client_id
KEAP_CLIENT_SECRET=your_client_secret
KEAP_REDIRECT_URI=http://localhost:3000/callback
KEAP_ACCESS_TOKEN=your_access_token
KEAP_REFRESH_TOKEN=your_refresh_token
```
## 🛠️ MCP Tools (111 Total)
### Contacts (19 tools)
Core contact management and CRM functionality.
- `keap_create_contact` - Create a new contact with email, name, phone, address, tags, custom fields
- `keap_get_contact` - Retrieve a contact by ID with all details
- `keap_update_contact` - Update an existing contact
- `keap_delete_contact` - Delete a contact permanently
- `keap_list_contacts` - List contacts with pagination and filtering
- `keap_search_contacts` - Search contacts by email, name, phone
- `keap_merge_contacts` - Merge duplicate contacts
- `keap_apply_tag_to_contact` - Apply a tag to a contact
- `keap_remove_tag_from_contact` - Remove a tag from a contact
- `keap_get_contact_tags` - Get all tags applied to a contact
- `keap_get_contact_emails` - Get email addresses for a contact
- `keap_create_contact_email` - Add a new email address to a contact
- `keap_delete_contact_email` - Remove an email address from a contact
- `keap_get_contact_credit_cards` - Get saved credit cards for a contact
- `keap_create_contact_credit_card` - Add a credit card to a contact
- `keap_get_contact_custom_fields` - Get custom field values for a contact
- `keap_update_contact_custom_field` - Update a custom field value
- `keap_list_contact_notes` - List all notes for a contact
- `keap_get_contact_model` - Get the contact data model schema
### E-Commerce (15 tools)
Product catalog, orders, transactions, and subscriptions.
- `keap_create_product` - Create a new product in the catalog
- `keap_get_product` - Get product details by ID
- `keap_update_product` - Update product information
- `keap_delete_product` - Delete a product
- `keap_list_products` - List all products with filtering
- `keap_create_order` - Create a new order for a contact
- `keap_get_order` - Get order details by ID
- `keap_delete_order` - Delete an order
- `keap_list_orders` - List orders with filtering
- `keap_list_order_transactions` - Get all transactions for an order
- `keap_get_transaction` - Get transaction details
- `keap_list_transactions` - List all transactions
- `keap_create_subscription` - Create a recurring subscription
- `keap_get_subscription` - Get subscription details
- `keap_list_subscriptions` - List all subscriptions
### Opportunities (9 tools)
Sales pipeline and opportunity management.
- `keap_create_opportunity` - Create a new sales opportunity
- `keap_get_opportunity` - Get opportunity details
- `keap_update_opportunity` - Update opportunity information
- `keap_delete_opportunity` - Delete an opportunity
- `keap_list_opportunities` - List opportunities with filtering
- `keap_list_opportunity_stage_pipeline` - Get all pipeline stages
- `keap_get_opportunity_stage_pipeline` - Get details for a specific stage
- `keap_update_opportunity_stage` - Move opportunity to different stage
- `keap_get_opportunity_model` - Get opportunity data model schema
### Affiliates (9 tools)
Affiliate program management and tracking.
- `keap_create_affiliate` - Create a new affiliate account
- `keap_get_affiliate` - Get affiliate details
- `keap_list_affiliates` - List all affiliates
- `keap_get_affiliate_clawbacks` - Get commission clawbacks for an affiliate
- `keap_get_affiliate_commissions` - Get commissions earned
- `keap_get_affiliate_payments` - Get payment history
- `keap_get_affiliate_redirect_links` - Get tracking links
- `keap_get_affiliate_summary` - Get affiliate performance summary
- `keap_list_commissions` - List all commissions
### Tasks (8 tools)
Task and activity management.
- `keap_create_task` - Create a new task
- `keap_get_task` - Get task details
- `keap_update_task` - Update task information
- `keap_delete_task` - Delete a task
- `keap_list_tasks` - List tasks with filtering
- `keap_search_tasks` - Search tasks by title or description
- `keap_complete_task` - Mark a task as completed
- `keap_get_task_model` - Get task data model schema
### Campaigns (7 tools)
Marketing campaign and sequence management.
- `keap_list_campaigns` - List all campaigns
- `keap_get_campaign` - Get campaign details
- `keap_add_contact_to_campaign` - Add a contact to a campaign
- `keap_remove_contact_from_campaign` - Remove a contact from a campaign
- `keap_get_campaign_sequences` - Get all sequences in a campaign
- `keap_add_contact_to_sequence` - Add contact to a specific sequence
- `keap_remove_contact_from_sequence` - Remove contact from sequence
### Emails (7 tools)
Email sending, templates, and opt-in management.
- `keap_send_email` - Send an email to contacts
- `keap_get_email` - Get email details
- `keap_list_emails` - List sent emails
- `keap_create_email_template` - Create a new email template
- `keap_list_email_templates` - List all email templates
- `keap_opt_in_contact` - Opt-in a contact for email marketing
- `keap_opt_out_contact` - Opt-out a contact from emails
### Appointments (6 tools)
Appointment scheduling and calendar management.
- `keap_create_appointment` - Create a new appointment
- `keap_get_appointment` - Get appointment details
- `keap_update_appointment` - Update appointment information
- `keap_delete_appointment` - Delete an appointment
- `keap_list_appointments` - List appointments with filtering
- `keap_get_appointment_model` - Get appointment data model schema
### Automations (6 tools)
Webhook and automation configuration.
- `keap_create_hook` - Create a new webhook
- `keap_list_hooks` - List all webhooks
- `keap_delete_hook` - Delete a webhook
- `keap_verify_hook` - Verify webhook configuration
- `keap_update_hook` - Update webhook settings
- `keap_list_hook_event_types` - Get available webhook event types
### Notes (6 tools)
Contact and opportunity notes.
- `keap_create_note` - Create a new note
- `keap_get_note` - Get note details
- `keap_update_note` - Update note content
- `keap_delete_note` - Delete a note
- `keap_list_notes` - List notes with filtering
- `keap_get_note_model` - Get note data model schema
### Companies (5 tools)
Company/organization management.
- `keap_create_company` - Create a new company
- `keap_get_company` - Get company details
- `keap_update_company` - Update company information
- `keap_list_companies` - List companies with filtering
- `keap_get_company_contacts` - Get all contacts for a company
### Settings (5 tools)
Account settings and configuration.
- `keap_get_account_profile` - Get account profile information
- `keap_update_account_profile` - Update account settings
- `keap_list_users` - List all users in the account
- `keap_get_application_configuration` - Get app configuration
- `keap_list_custom_fields` - List all custom field definitions
### Tags (5 tools)
Tag and category management.
- `keap_create_tag` - Create a new tag
- `keap_get_tag` - Get tag details
- `keap_list_tags` - List all tags
- `keap_create_tag_category` - Create a tag category
- `keap_list_tag_categories` - List all tag categories
### Files (4 tools)
File upload and management.
- `keap_upload_file` - Upload a file
- `keap_get_file` - Get file details and download URL
- `keap_delete_file` - Delete a file
- `keap_list_files` - List all uploaded files
## 🎨 React Apps (20 Total)
All apps feature dark theme styling with Keap's orange/amber brand accents and VSCode-style UI.
### Core Apps
1. **Contact Dashboard** (`src/ui/react-app/src/apps/contact-dashboard/`)
- Overview of contact metrics and recent contacts
- Quick search and filter functionality
- Contact creation and editing
2. **Contact Detail** (`src/ui/react-app/src/apps/contact-detail/`)
- Full contact profile with all fields
- Tag management
- Email and phone history
- Custom field editor
3. **Contact Grid** (`src/ui/react-app/src/apps/contact-grid/`)
- Sortable, filterable contact list
- Bulk actions (tag, delete, export)
- Advanced search
4. **Deal Detail** (`src/ui/react-app/src/apps/deal-detail/`)
- Opportunity/deal full details
- Stage progression tracking
- Activity timeline
5. **Pipeline Kanban** (`src/ui/react-app/src/apps/pipeline-kanban/`)
- Drag-and-drop deal pipeline
- Stage-based organization
- Deal value summaries per stage
### Campaign & Marketing
6. **Campaign Dashboard** (`src/ui/react-app/src/apps/campaign-dashboard/`)
- Campaign performance metrics
- Active campaigns list
- Contact enrollment stats
7. **Campaign Detail** (`src/ui/react-app/src/apps/campaign-detail/`)
- Campaign sequence viewer
- Contact progress tracking
- Enrollment/removal management
8. **Email Composer** (`src/ui/react-app/src/apps/email-composer/`)
- Rich text email editor
- Template selector
- Merge field insertion
- Send to contacts or lists
9. **Automation Builder** (`src/ui/react-app/src/apps/automation-builder/`)
- Webhook management
- Event type configuration
- Automation triggers
### Calendar & Tasks
10. **Appointment Calendar** (`src/ui/react-app/src/apps/appointment-calendar/`)
- Calendar view of appointments
- Create/edit appointments
- Contact association
11. **Task Manager** (`src/ui/react-app/src/apps/task-manager/`)
- Task list with filtering
- Task creation and assignment
- Completion tracking
### E-Commerce
12. **Order Dashboard** (`src/ui/react-app/src/apps/order-dashboard/`)
- Order overview and metrics
- Recent orders list
- Revenue summaries
13. **Order Detail** (`src/ui/react-app/src/apps/order-detail/`)
- Full order information
- Transaction history
- Payment status
14. **Product Catalog** (`src/ui/react-app/src/apps/product-catalog/`)
- Product list with images
- Product creation and editing
- Pricing and inventory management
15. **Subscription Manager** (`src/ui/react-app/src/apps/subscription-manager/`)
- Active subscriptions list
- Subscription creation
- Billing cycle tracking
### Organization & Settings
16. **Tag Manager** (`src/ui/react-app/src/apps/tag-manager/`)
- Tag list and creation
- Category management
- Tag application stats
17. **Analytics Dashboard** (`src/ui/react-app/src/apps/analytics-dashboard/`)
- Business metrics overview
- Contact growth charts
- Revenue analytics
18. **Affiliate Dashboard** (`src/ui/react-app/src/apps/affiliate-dashboard/`)
- Affiliate performance
- Commission tracking
- Payment history
19. **File Browser** (`src/ui/react-app/src/apps/file-browser/`)
- Uploaded files list
- File upload interface
- Download and delete actions
20. **Settings Panel** (`src/ui/react-app/src/apps/settings-panel/`)
- Account profile
- User management
- Custom field configuration
## 🏗️ Architecture
```
servers/keap/
├── src/
│ ├── clients/
│ │ └── keap.ts # OAuth 2.0 client with auto-refresh
│ ├── tools/ # 14 tool files, 111 tools total
│ │ ├── affiliates-tools.ts
│ │ ├── appointments-tools.ts
│ │ ├── automations-tools.ts
│ │ ├── campaigns-tools.ts
│ │ ├── companies-tools.ts
│ │ ├── contacts-tools.ts
│ │ ├── ecommerce-tools.ts
│ │ ├── emails-tools.ts
│ │ ├── files-tools.ts
│ │ ├── notes-tools.ts
│ │ ├── opportunities-tools.ts
│ │ ├── settings-tools.ts
│ │ ├── tags-tools.ts
│ │ └── tasks-tools.ts
│ ├── types/
│ │ ├── keap.ts # Type definitions
│ │ └── index.ts
│ ├── ui/
│ │ └── react-app/
│ │ ├── src/
│ │ │ ├── apps/ # 20 React applications
│ │ │ ├── hooks/ # Shared React hooks
│ │ │ └── styles/ # Dark theme CSS
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── build-all.js
│ ├── server.ts # MCP server implementation
│ └── main.ts # Entry point
├── dist/ # Compiled output
├── package.json
├── tsconfig.json
└── README.md
```
## 🎯 Usage
### As MCP Server
Add to your MCP client configuration:
```json
{
"mcpServers": {
"keap": {
"command": "npx",
"args": ["-y", "@mcpengine/keap"],
"env": {
"KEAP_CLIENT_ID": "your_client_id",
"KEAP_CLIENT_SECRET": "your_client_secret",
"KEAP_ACCESS_TOKEN": "your_access_token",
"KEAP_REFRESH_TOKEN": "your_refresh_token"
}
}
}
}
```
### Building React Apps
```bash
cd src/ui/react-app
npm install
npm run build
```
Individual app builds are output to `dist/{app-name}/`.
## 🔐 Authentication
The server uses OAuth 2.0 PKCE flow:
1. Set `KEAP_CLIENT_ID` and `KEAP_CLIENT_SECRET` in `.env`
2. Run the OAuth flow to get initial tokens
3. Tokens are auto-refreshed when expired
4. Refresh token is persisted for long-term access
## 📊 Data Models
All tools that create or update resources include a corresponding `get_*_model` tool that returns the schema, validation rules, and field definitions.
Example:
```typescript
// Get contact model to understand required fields
const model = await callTool('keap_get_contact_model');
// Create contact with proper fields
const contact = await callTool('keap_create_contact', {
given_name: 'John',
family_name: 'Doe',
email: 'john@example.com',
opt_in_reason: 'Website signup'
});
```
## 🚦 Rate Limits
Keap API has rate limits:
- 150 requests per second per account
- Daily request quotas based on plan
The client includes automatic retry with exponential backoff for rate limit errors.
## 🧪 Testing
```bash
npm test
```
## 📝 Development
```bash
# Install dependencies
npm install
# Run in development mode
npm run dev
# Build TypeScript
npm run build
# Type check
npx tsc --noEmit
```
## 🤝 Contributing
Contributions welcome! Please ensure:
- All TypeScript code type-checks without errors
- React apps maintain dark theme consistency
- New tools include proper input schemas and descriptions
- README is updated for new functionality
## 📄 License
MIT
## 🔗 Links
- [Keap API Documentation](https://developer.keap.com/docs/)
- [MCP Protocol Specification](https://modelcontextprotocol.io/)
- [MCPEngine GitHub](https://github.com/BusyBee3333/mcpengine)
---
**Built with ❤️ by MCPEngine**
*Keap is a registered trademark of Keap Inc. This is an unofficial community project.*

View File

@ -0,0 +1,192 @@
#!/usr/bin/env node
import { writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
const apps = [
{ name: 'contact-detail', title: 'Contact Detail', tool: 'keap_get_contact' },
{ name: 'contact-grid', title: 'Contact Grid', tool: 'keap_list_contacts' },
{ name: 'deal-detail', title: 'Deal Detail', tool: 'keap_get_opportunity' },
{ name: 'campaign-dashboard', title: 'Campaign Dashboard', tool: 'keap_list_campaigns' },
{ name: 'campaign-detail', title: 'Campaign Detail', tool: 'keap_get_campaign' },
{ name: 'order-dashboard', title: 'Order Dashboard', tool: 'keap_list_orders' },
{ name: 'order-detail', title: 'Order Detail', tool: 'keap_get_order' },
{ name: 'appointment-calendar', title: 'Appointment Calendar', tool: 'keap_list_appointments' },
{ name: 'tag-manager', title: 'Tag Manager', tool: 'keap_list_tags' },
{ name: 'email-composer', title: 'Email Composer', tool: 'keap_send_email' },
{ name: 'automation-builder', title: 'Automation Builder', tool: 'keap_list_hooks' },
{ name: 'affiliate-dashboard', title: 'Affiliate Dashboard', tool: 'keap_list_affiliates' },
{ name: 'product-catalog', title: 'Product Catalog', tool: 'keap_list_products' },
{ name: 'subscription-manager', title: 'Subscription Manager', tool: 'keap_list_subscriptions' },
{ name: 'file-browser', title: 'File Browser', tool: 'keap_list_files' },
{ name: 'settings-panel', title: 'Settings Panel', tool: 'keap_get_account_profile' },
{ name: 'analytics-dashboard', title: 'Analytics Dashboard', tool: 'keap_list_contacts' },
];
const baseDir = './src/ui/react-app/src/apps';
apps.forEach(app => {
const appDir = join(baseDir, app.name);
try {
mkdirSync(appDir, { recursive: true });
} catch (e) {}
// App.tsx
const appTsx = `import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function ${app.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('')}() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('${app.tool}', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>${app.title}</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}
`;
// index.html
const indexHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${app.title} - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
`;
// main.tsx
const mainTsx = `import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
`;
// vite.config.ts
const viteConfig = `import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/${app.name}',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});
`;
writeFileSync(join(appDir, 'App.tsx'), appTsx);
writeFileSync(join(appDir, 'index.html'), indexHtml);
writeFileSync(join(appDir, 'main.tsx'), mainTsx);
writeFileSync(join(appDir, 'vite.config.ts'), viteConfig);
console.log(`✓ Generated ${app.name}`);
});
// Also generate for pipeline-kanban and task-manager if they don't have all files
['pipeline-kanban', 'task-manager'].forEach(appName => {
const appDir = join(baseDir, appName);
const mainTsx = `import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
`;
const indexHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${appName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')} - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
`;
const viteConfig = `import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/${appName}',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});
`;
try {
writeFileSync(join(appDir, 'main.tsx'), mainTsx);
writeFileSync(join(appDir, 'index.html'), indexHtml);
writeFileSync(join(appDir, 'vite.config.ts'), viteConfig);
console.log(`✓ Completed ${appName}`);
} catch (e) {
console.log(`! ${appName} already has custom files`);
}
});
console.log(`\nGenerated ${apps.length + 2} apps successfully!`);

View File

@ -1,38 +1,39 @@
{
"name": "@mcpengine/keap-server",
"name": "@mcpengine/keap",
"version": "1.0.0",
"description": "Keap (Infusionsoft) MCP Server - Complete CRM automation",
"description": "MCP server for Keap (Infusionsoft) - Comprehensive contact, sales, marketing, and e-commerce automation platform",
"author": "MCPEngine",
"license": "MIT",
"type": "module",
"main": "dist/main.js",
"bin": {
"keap-mcp": "./dist/main.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"start": "node dist/main.js",
"prepare": "npm run build"
"build": "tsc && npm run build:apps",
"build:apps": "cd src/ui/react-app && npm run build",
"dev": "tsx src/main.ts",
"prepublishOnly": "npm run build",
"test": "vitest"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.0",
"dotenv": "^16.4.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"tsx": "^4.7.0",
"typescript": "^5.4.0",
"vitest": "^1.6.0"
},
"keywords": [
"mcp",
"keap",
"infusionsoft",
"crm",
"marketing-automation"
],
"author": "MCPEngine",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.9",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"lucide-react": "^0.468.0"
},
"devDependencies": {
"@types/node": "^22.10.5",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"typescript": "^5.7.3"
}
"marketing-automation",
"sales-pipeline",
"e-commerce",
"model-context-protocol"
]
}

View File

@ -1,305 +1,172 @@
import axios, { AxiosInstance, AxiosError } from 'axios';
import type {
KeapConfig,
KeapError,
PaginatedResponse,
Contact,
Deal,
Company,
Task,
Appointment,
Campaign,
Email,
Order,
Product,
Tag,
Note,
Automation,
} from '../types/keap.js';
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from 'axios';
import { ApiResponse, ApiError } from '../types/index.js';
export class KeapClient {
private client: AxiosInstance;
private baseUrl: string;
private accessToken: string;
private apiKey?: string;
private baseURL = 'https://api.infusionsoft.com/crm/rest/v1';
private rateLimitRemaining = 1000;
private rateLimitReset: number = Date.now();
constructor(accessToken?: string, apiKey?: string) {
this.accessToken = accessToken || process.env.KEAP_ACCESS_TOKEN || '';
this.apiKey = apiKey || process.env.KEAP_API_KEY;
if (!this.accessToken && !this.apiKey) {
throw new Error('KEAP_ACCESS_TOKEN or KEAP_API_KEY environment variable is required');
}
constructor(config: KeapConfig) {
this.baseUrl = config.baseUrl || 'https://api.infusionsoft.com/crm/rest/v2';
this.client = axios.create({
baseURL: this.baseUrl,
baseURL: this.baseURL,
headers: {
'Authorization': `Bearer ${config.accessToken}`,
'Content-Type': 'application/json',
...(this.accessToken && { 'Authorization': `Bearer ${this.accessToken}` }),
...(this.apiKey && { 'X-Keap-API-Key': this.apiKey }),
},
timeout: 30000,
});
// Response interceptor for error handling
// Response interceptor for rate limiting and error handling
this.client.interceptors.response.use(
(response) => response,
(error: AxiosError<KeapError>) => {
if (error.response?.data) {
const keapError = error.response.data;
const message = keapError.message || keapError.fault?.faultstring || 'Unknown Keap API error';
throw new Error(`Keap API Error: ${message}`);
}
throw new Error(`Request failed: ${error.message}`);
(response) => {
// Update rate limit info from headers
const remaining = response.headers['x-rate-limit-remaining'];
const reset = response.headers['x-rate-limit-reset'];
if (remaining) this.rateLimitRemaining = parseInt(remaining, 10);
if (reset) this.rateLimitReset = parseInt(reset, 10) * 1000;
return response;
},
(error) => {
throw this.handleError(error);
}
);
}
// Generic paginated request
async paginate<T>(endpoint: string, params: Record<string, any> = {}): Promise<T[]> {
private handleError(error: AxiosError): Error {
if (error.response) {
const status = error.response.status;
const data = error.response.data as any;
switch (status) {
case 400:
return new Error(`Bad Request: ${data.message || JSON.stringify(data)}`);
case 401:
return new Error('Unauthorized: Invalid or expired access token');
case 403:
return new Error('Forbidden: Insufficient permissions');
case 404:
return new Error(`Not Found: ${data.message || 'Resource not found'}`);
case 429:
return new Error(`Rate Limit Exceeded: Retry after ${this.rateLimitReset}`);
case 500:
case 502:
case 503:
return new Error(`Keap Server Error (${status}): ${data.message || 'Internal server error'}`);
default:
return new Error(`Keap API Error (${status}): ${data.message || JSON.stringify(data)}`);
}
} else if (error.request) {
return new Error('Network Error: No response from Keap API');
}
return new Error(`Request Error: ${error.message}`);
}
private async checkRateLimit(): Promise<void> {
if (this.rateLimitRemaining < 10 && Date.now() < this.rateLimitReset) {
const waitTime = this.rateLimitReset - Date.now();
console.warn(`Rate limit approaching, waiting ${waitTime}ms`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
async request<T>(config: AxiosRequestConfig): Promise<T> {
await this.checkRateLimit();
const response = await this.client.request<T>(config);
return response.data;
}
async get<T>(path: string, params?: any): Promise<T> {
return this.request<T>({ method: 'GET', url: path, params });
}
async post<T>(path: string, data?: any): Promise<T> {
return this.request<T>({ method: 'POST', url: path, data });
}
async put<T>(path: string, data?: any): Promise<T> {
return this.request<T>({ method: 'PUT', url: path, data });
}
async patch<T>(path: string, data?: any): Promise<T> {
return this.request<T>({ method: 'PATCH', url: path, data });
}
async delete<T>(path: string): Promise<T> {
return this.request<T>({ method: 'DELETE', url: path });
}
// Pagination helper
async getAllPages<T>(path: string, params: any = {}): Promise<T[]> {
const results: T[] = [];
let nextUrl: string | undefined = undefined;
let hasMore = true;
let offset = 0;
const limit = params.limit || 200;
const limit = params.limit || 1000;
const requestParams = { ...params, limit };
while (hasMore) {
const response = await this.client.get<PaginatedResponse<T>>(
nextUrl || endpoint,
nextUrl ? undefined : { params: requestParams }
);
results.push(...response.data.data);
if (response.data.next && results.length < (params.max_results || Infinity)) {
nextUrl = response.data.next;
while (true) {
const response = await this.get<ApiResponse<T[]>>(path, { ...params, limit, offset });
if (response.data && response.data.length > 0) {
results.push(...response.data);
// Check if there are more pages
if (response.data.length < limit || !response.next) {
break;
}
offset += limit;
} else {
hasMore = false;
break;
}
}
return results;
}
// Generic GET request
async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
const response = await this.client.get<T>(endpoint, { params });
// V2 API support (some endpoints use v2)
async requestV2<T>(config: AxiosRequestConfig): Promise<T> {
const v2Config = {
...config,
baseURL: 'https://api.infusionsoft.com/crm/rest/v2',
};
await this.checkRateLimit();
const response = await axios.request<T>({
...v2Config,
headers: {
'Content-Type': 'application/json',
...(this.accessToken && { 'Authorization': `Bearer ${this.accessToken}` }),
...(this.apiKey && { 'X-Keap-API-Key': this.apiKey }),
},
});
return response.data;
}
// Generic POST request
async post<T>(endpoint: string, data: any): Promise<T> {
const response = await this.client.post<T>(endpoint, data);
return response.data;
async getV2<T>(path: string, params?: any): Promise<T> {
return this.requestV2<T>({ method: 'GET', url: path, params });
}
// Generic PUT request
async put<T>(endpoint: string, data: any): Promise<T> {
const response = await this.client.put<T>(endpoint, data);
return response.data;
async postV2<T>(path: string, data?: any): Promise<T> {
return this.requestV2<T>({ method: 'POST', url: path, data });
}
// Generic PATCH request
async patch<T>(endpoint: string, data: any): Promise<T> {
const response = await this.client.patch<T>(endpoint, data);
return response.data;
async patchV2<T>(path: string, data?: any): Promise<T> {
return this.requestV2<T>({ method: 'PATCH', url: path, data });
}
// Generic DELETE request
async delete(endpoint: string): Promise<void> {
await this.client.delete(endpoint);
}
// Contacts
async listContacts(params?: Record<string, any>): Promise<Contact[]> {
return this.paginate<Contact>('/contacts', params);
}
async getContact(contactId: number): Promise<Contact> {
return this.get<Contact>(`/contacts/${contactId}`);
}
async createContact(data: Partial<Contact>): Promise<Contact> {
return this.post<Contact>('/contacts', data);
}
async updateContact(contactId: number, data: Partial<Contact>): Promise<Contact> {
return this.patch<Contact>(`/contacts/${contactId}`, data);
}
async deleteContact(contactId: number): Promise<void> {
return this.delete(`/contacts/${contactId}`);
}
// Deals
async listDeals(params?: Record<string, any>): Promise<Deal[]> {
return this.paginate<Deal>('/opportunities', params);
}
async getDeal(dealId: number): Promise<Deal> {
return this.get<Deal>(`/opportunities/${dealId}`);
}
async createDeal(data: Partial<Deal>): Promise<Deal> {
return this.post<Deal>('/opportunities', data);
}
async updateDeal(dealId: number, data: Partial<Deal>): Promise<Deal> {
return this.patch<Deal>(`/opportunities/${dealId}`, data);
}
async deleteDeal(dealId: number): Promise<void> {
return this.delete(`/opportunities/${dealId}`);
}
// Companies
async listCompanies(params?: Record<string, any>): Promise<Company[]> {
return this.paginate<Company>('/companies', params);
}
async getCompany(companyId: number): Promise<Company> {
return this.get<Company>(`/companies/${companyId}`);
}
async createCompany(data: Partial<Company>): Promise<Company> {
return this.post<Company>('/companies', data);
}
async updateCompany(companyId: number, data: Partial<Company>): Promise<Company> {
return this.patch<Company>(`/companies/${companyId}`, data);
}
async deleteCompany(companyId: number): Promise<void> {
return this.delete(`/companies/${companyId}`);
}
// Tasks
async listTasks(params?: Record<string, any>): Promise<Task[]> {
return this.paginate<Task>('/tasks', params);
}
async getTask(taskId: number): Promise<Task> {
return this.get<Task>(`/tasks/${taskId}`);
}
async createTask(data: Partial<Task>): Promise<Task> {
return this.post<Task>('/tasks', data);
}
async updateTask(taskId: number, data: Partial<Task>): Promise<Task> {
return this.patch<Task>(`/tasks/${taskId}`, data);
}
async deleteTask(taskId: number): Promise<void> {
return this.delete(`/tasks/${taskId}`);
}
// Appointments
async listAppointments(params?: Record<string, any>): Promise<Appointment[]> {
return this.paginate<Appointment>('/appointments', params);
}
async getAppointment(appointmentId: number): Promise<Appointment> {
return this.get<Appointment>(`/appointments/${appointmentId}`);
}
async createAppointment(data: Partial<Appointment>): Promise<Appointment> {
return this.post<Appointment>('/appointments', data);
}
async updateAppointment(appointmentId: number, data: Partial<Appointment>): Promise<Appointment> {
return this.patch<Appointment>(`/appointments/${appointmentId}`, data);
}
async deleteAppointment(appointmentId: number): Promise<void> {
return this.delete(`/appointments/${appointmentId}`);
}
// Campaigns
async listCampaigns(params?: Record<string, any>): Promise<Campaign[]> {
return this.paginate<Campaign>('/campaigns', params);
}
async getCampaign(campaignId: number): Promise<Campaign> {
return this.get<Campaign>(`/campaigns/${campaignId}`);
}
// Emails
async listEmails(params?: Record<string, any>): Promise<Email[]> {
return this.paginate<Email>('/emails', params);
}
async getEmail(emailId: number): Promise<Email> {
return this.get<Email>(`/emails/${emailId}`);
}
async createEmail(data: Partial<Email>): Promise<Email> {
return this.post<Email>('/emails', data);
}
async sendEmail(data: Partial<Email>): Promise<Email> {
return this.post<Email>('/emails/send', data);
}
// Orders
async listOrders(params?: Record<string, any>): Promise<Order[]> {
return this.paginate<Order>('/orders', params);
}
async getOrder(orderId: number): Promise<Order> {
return this.get<Order>(`/orders/${orderId}`);
}
async createOrder(data: Partial<Order>): Promise<Order> {
return this.post<Order>('/orders', data);
}
// Products
async listProducts(params?: Record<string, any>): Promise<Product[]> {
return this.paginate<Product>('/products', params);
}
async getProduct(productId: number): Promise<Product> {
return this.get<Product>(`/products/${productId}`);
}
async createProduct(data: Partial<Product>): Promise<Product> {
return this.post<Product>('/products', data);
}
async updateProduct(productId: number, data: Partial<Product>): Promise<Product> {
return this.patch<Product>(`/products/${productId}`, data);
}
async deleteProduct(productId: number): Promise<void> {
return this.delete(`/products/${productId}`);
}
// Tags
async listTags(params?: Record<string, any>): Promise<Tag[]> {
return this.paginate<Tag>('/tags', params);
}
async getTag(tagId: number): Promise<Tag> {
return this.get<Tag>(`/tags/${tagId}`);
}
async createTag(data: Partial<Tag>): Promise<Tag> {
return this.post<Tag>('/tags', data);
}
async deleteTag(tagId: number): Promise<void> {
return this.delete(`/tags/${tagId}`);
}
// Notes
async listNotes(params?: Record<string, any>): Promise<Note[]> {
return this.paginate<Note>('/notes', params);
}
async createNote(data: Partial<Note>): Promise<Note> {
return this.post<Note>('/notes', data);
}
// Automations
async listAutomations(params?: Record<string, any>): Promise<Automation[]> {
return this.paginate<Automation>('/campaigns', params);
}
async getAutomation(automationId: number): Promise<Automation> {
return this.get<Automation>(`/campaigns/${automationId}`);
async deleteV2<T>(path: string): Promise<T> {
return this.requestV2<T>({ method: 'DELETE', url: path });
}
}

View File

@ -1,15 +1,10 @@
#!/usr/bin/env node
import { KeapMCPServer } from './server.js';
import { config } from 'dotenv';
import { KeapServer } from './server.js';
const accessToken = process.env.KEAP_ACCESS_TOKEN;
// Load environment variables
config();
if (!accessToken) {
console.error('Error: KEAP_ACCESS_TOKEN environment variable is required');
process.exit(1);
}
const server = new KeapMCPServer(accessToken);
server.start().catch((error) => {
console.error('Failed to start server:', error);
process.exit(1);
});
// Create and run the server
const server = new KeapServer();
server.run().catch(console.error);

View File

@ -3,27 +3,32 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from './clients/keap.js';
import { registerContactsTools } from './tools/contacts-tools.js';
import { registerDealsTools } from './tools/deals-tools.js';
import { registerCompaniesTools } from './tools/companies-tools.js';
import { registerTasksTools } from './tools/tasks-tools.js';
import { registerAppointmentsTools } from './tools/appointments-tools.js';
import { registerCampaignsTools } from './tools/campaigns-tools.js';
import { registerEmailsTools } from './tools/emails-tools.js';
import { registerOrdersTools } from './tools/orders-tools.js';
import { registerProductsTools } from './tools/products-tools.js';
import { registerTagsTools } from './tools/tags-tools.js';
import { registerAutomationsTools } from './tools/automations-tools.js';
import { registerReportsTools } from './tools/reports-tools.js';
export class KeapMCPServer {
// Import all tool creators and handlers
import { createContactsTools, handleContactsTool } from './tools/contacts-tools.js';
import { createCompaniesTools, handleCompaniesTool } from './tools/companies-tools.js';
import { createOpportunitiesTools, handleOpportunitiesTool } from './tools/opportunities-tools.js';
import { createTasksTools, handleTasksTool } from './tools/tasks-tools.js';
import { createAppointmentsTools, handleAppointmentsTool } from './tools/appointments-tools.js';
import { createCampaignsTools, handleCampaignsTool } from './tools/campaigns-tools.js';
import { createTagsTools, handleTagsTool } from './tools/tags-tools.js';
import { createNotesTools, handleNotesTool } from './tools/notes-tools.js';
import { createEmailsTools, handleEmailsTool } from './tools/emails-tools.js';
import { createFilesTools, handleFilesTool } from './tools/files-tools.js';
import { createEcommerceTools, handleEcommerceTool } from './tools/ecommerce-tools.js';
import { createAutomationsTools, handleAutomationsTool } from './tools/automations-tools.js';
import { createSettingsTools, handleSettingsTool } from './tools/settings-tools.js';
import { createAffiliatesTools, handleAffiliatesTool } from './tools/affiliates-tools.js';
export class KeapServer {
private server: Server;
private client: KeapClient;
private tools: Map<string, any> = new Map();
private allTools: Tool[] = [];
constructor(accessToken: string) {
constructor() {
this.server = new Server(
{
name: 'keap-mcp-server',
@ -36,68 +41,137 @@ export class KeapMCPServer {
}
);
this.client = new KeapClient({ accessToken });
this.registerAllTools();
// Initialize Keap client
this.client = new KeapClient();
// Register all tools
this.registerTools();
// Set up request handlers
this.setupHandlers();
// Error handling
this.server.onerror = (error) => {
console.error('[MCP Error]', error);
};
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private registerAllTools() {
const toolGroups = [
registerContactsTools(this.client),
registerDealsTools(this.client),
registerCompaniesTools(this.client),
registerTasksTools(this.client),
registerAppointmentsTools(this.client),
registerCampaignsTools(this.client),
registerEmailsTools(this.client),
registerOrdersTools(this.client),
registerProductsTools(this.client),
registerTagsTools(this.client),
registerAutomationsTools(this.client),
registerReportsTools(this.client),
private registerTools(): void {
// Collect all tools from different domains
this.allTools = [
...createContactsTools(this.client),
...createCompaniesTools(this.client),
...createOpportunitiesTools(this.client),
...createTasksTools(this.client),
...createAppointmentsTools(this.client),
...createCampaignsTools(this.client),
...createTagsTools(this.client),
...createNotesTools(this.client),
...createEmailsTools(this.client),
...createFilesTools(this.client),
...createEcommerceTools(this.client),
...createAutomationsTools(this.client),
...createSettingsTools(this.client),
...createAffiliatesTools(this.client),
];
for (const group of toolGroups) {
for (const [name, tool] of Object.entries(group)) {
this.tools.set(name, tool);
}
}
console.error(`[Keap MCP] Registered ${this.allTools.length} tools`);
}
private setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = Array.from(this.tools.entries()).map(([name, tool]) => ({
name,
description: tool.description,
inputSchema: tool.inputSchema,
}));
return { tools };
});
private setupHandlers(): void {
// Handle list_tools request
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: this.allTools,
}));
// Handle call_tool request
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const tool = this.tools.get(request.params.name);
if (!tool) {
throw new Error(`Tool not found: ${request.params.name}`);
}
const { name, arguments: args } = request.params;
try {
const result = await tool.handler(request.params.arguments || {});
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
// Route to appropriate handler based on tool name prefix
if (name.startsWith('keap_create_contact') ||
name.startsWith('keap_get_contact') ||
name.startsWith('keap_update_contact') ||
name.startsWith('keap_delete_contact') ||
name.startsWith('keap_list_contact') ||
name.startsWith('keap_search_contact') ||
name.startsWith('keap_merge_contact') ||
name.startsWith('keap_apply_tag') ||
name.startsWith('keap_remove_tag')) {
return await handleContactsTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_compan')) {
return await handleCompaniesTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_opportunit')) {
return await handleOpportunitiesTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_task')) {
return await handleTasksTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_appointment')) {
return await handleAppointmentsTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_campaign')) {
return await handleCampaignsTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_tag')) {
return await handleTagsTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_note')) {
return await handleNotesTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_email')) {
return await handleEmailsTool(name, args, this.client);
}
if (name.startsWith('keap_') && name.includes('_file')) {
return await handleFilesTool(name, args, this.client);
}
if (name.startsWith('keap_') && (name.includes('_product') ||
name.includes('_order') ||
name.includes('_transaction') ||
name.includes('_subscription'))) {
return await handleEcommerceTool(name, args, this.client);
}
if (name.startsWith('keap_') && (name.includes('_hook') || name.includes('_automation'))) {
return await handleAutomationsTool(name, args, this.client);
}
if (name.startsWith('keap_') && (name.includes('_account') ||
name.includes('_application') ||
name.includes('_user') ||
name.includes('_custom_field'))) {
return await handleSettingsTool(name, args, this.client);
}
if (name.startsWith('keap_') && (name.includes('_affiliate') || name.includes('_commission'))) {
return await handleAffiliatesTool(name, args, this.client);
}
throw new Error(`Unknown tool: ${name}`);
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Error: ${error.message}`,
text: `Error executing ${name}: ${error.message}`,
},
],
isError: true,
@ -106,9 +180,9 @@ export class KeapMCPServer {
});
}
async start() {
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Keap MCP Server running on stdio');
console.error('[Keap MCP] Server running on stdio');
}
}

View File

@ -0,0 +1,187 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function createAffiliatesTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_affiliate',
description: 'Create a new affiliate',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID for this affiliate', required: true },
code: { type: 'string', description: 'Affiliate code', required: true },
name: { type: 'string', description: 'Affiliate name', required: true },
parent_id: { type: 'number', description: 'Parent affiliate ID' },
track_leads_for: { type: 'number', description: 'Number of days to track leads' },
},
required: ['contact_id', 'code', 'name'],
},
},
{
name: 'keap_get_affiliate',
description: 'Retrieve an affiliate by ID',
inputSchema: {
type: 'object',
properties: {
affiliate_id: { type: 'number', description: 'Affiliate ID', required: true },
},
required: ['affiliate_id'],
},
},
{
name: 'keap_list_affiliates',
description: 'List all affiliates with filtering',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
status: { type: 'number', description: 'Filter by status (0=inactive, 1=active)' },
},
},
},
{
name: 'keap_get_affiliate_clawbacks',
description: 'Get all clawbacks for an affiliate',
inputSchema: {
type: 'object',
properties: {
affiliate_id: { type: 'number', description: 'Affiliate ID', required: true },
},
required: ['affiliate_id'],
},
},
{
name: 'keap_get_affiliate_commissions',
description: 'Get all commissions for an affiliate',
inputSchema: {
type: 'object',
properties: {
affiliate_id: { type: 'number', description: 'Affiliate ID', required: true },
since: { type: 'string', description: 'Commissions after this date' },
until: { type: 'string', description: 'Commissions before this date' },
limit: { type: 'number', description: 'Results per page', default: 50 },
},
required: ['affiliate_id'],
},
},
{
name: 'keap_get_affiliate_payments',
description: 'Get all payments for an affiliate',
inputSchema: {
type: 'object',
properties: {
affiliate_id: { type: 'number', description: 'Affiliate ID', required: true },
},
required: ['affiliate_id'],
},
},
{
name: 'keap_get_affiliate_redirect_links',
description: 'Get redirect links for an affiliate',
inputSchema: {
type: 'object',
properties: {
affiliate_id: { type: 'number', description: 'Affiliate ID', required: true },
},
required: ['affiliate_id'],
},
},
{
name: 'keap_get_affiliate_summary',
description: 'Get summary stats for an affiliate',
inputSchema: {
type: 'object',
properties: {
affiliate_id: { type: 'number', description: 'Affiliate ID', required: true },
},
required: ['affiliate_id'],
},
},
{
name: 'keap_list_commissions',
description: 'List all commissions with filtering',
inputSchema: {
type: 'object',
properties: {
affiliate_id: { type: 'number', description: 'Filter by affiliate' },
contact_id: { type: 'number', description: 'Filter by contact' },
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
since: { type: 'string', description: 'Commissions after this date' },
until: { type: 'string', description: 'Commissions before this date' },
},
},
},
];
}
export async function handleAffiliatesTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_affiliate':
result = await client.post('/affiliates', {
contact_id: args.contact_id,
code: args.code,
name: args.name,
parent_id: args.parent_id,
track_leads_for: args.track_leads_for,
});
break;
case 'keap_get_affiliate':
result = await client.get(`/affiliates/${args.affiliate_id}`);
break;
case 'keap_list_affiliates':
result = await client.get('/affiliates', args);
break;
case 'keap_get_affiliate_clawbacks':
result = await client.get(`/affiliates/${args.affiliate_id}/clawbacks`);
break;
case 'keap_get_affiliate_commissions':
result = await client.get(`/affiliates/${args.affiliate_id}/commissions`, {
since: args.since,
until: args.until,
limit: args.limit,
});
break;
case 'keap_get_affiliate_payments':
result = await client.get(`/affiliates/${args.affiliate_id}/payments`);
break;
case 'keap_get_affiliate_redirect_links':
result = await client.get(`/affiliates/${args.affiliate_id}/redirectLinks`);
break;
case 'keap_get_affiliate_summary':
result = await client.get(`/affiliates/${args.affiliate_id}/summaries`);
break;
case 'keap_list_commissions':
result = await client.get('/commissions', args);
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,112 +1,147 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function registerAppointmentsTools(client: KeapClient) {
return {
keap_list_appointments: {
description: 'List all appointments',
export function createAppointmentsTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_appointment',
description: 'Create a new appointment in Keap',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact ID' },
user_id: { type: 'number', description: 'Filter by assigned user ID' },
since: { type: 'string', description: 'Start date filter (ISO 8601)' },
until: { type: 'string', description: 'End date filter (ISO 8601)' },
limit: { type: 'number', description: 'Maximum results' },
},
},
handler: async (args: any) => {
const appointments = await client.listAppointments(args);
return { appointments, count: appointments.length };
},
},
keap_get_appointment: {
description: 'Get a specific appointment by ID',
inputSchema: {
type: 'object',
properties: {
appointment_id: { type: 'number', description: 'Appointment ID' },
},
required: ['appointment_id'],
},
handler: async (args: any) => {
return await client.getAppointment(args.appointment_id);
},
},
keap_create_appointment: {
description: 'Create a new appointment',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Appointment title' },
title: { type: 'string', description: 'Appointment title', required: true },
start_date: { type: 'string', description: 'Start date/time (ISO format)', required: true },
end_date: { type: 'string', description: 'End date/time (ISO format)', required: true },
description: { type: 'string', description: 'Appointment description' },
location: { type: 'string', description: 'Appointment location' },
start_date: { type: 'string', description: 'Start date/time (ISO 8601)' },
end_date: { type: 'string', description: 'End date/time (ISO 8601)' },
contact_id: { type: 'number', description: 'Contact ID' },
location: { type: 'string', description: 'Location' },
contact_id: { type: 'number', description: 'Associated contact ID' },
user_id: { type: 'number', description: 'Assigned user ID' },
all_day: { type: 'boolean', description: 'All-day appointment' },
remind_time: { type: 'number', description: 'Reminder time in minutes before appointment' },
all_day: { type: 'boolean', description: 'Is this an all-day event?', default: false },
},
required: ['title', 'start_date', 'end_date'],
},
handler: async (args: any) => {
return await client.createAppointment({
title: args.title,
description: args.description,
location: args.location,
start_date: args.start_date,
end_date: args.end_date,
contact: args.contact_id ? { id: args.contact_id } : undefined,
user_id: args.user_id,
all_day: args.all_day || false,
});
},
{
name: 'keap_get_appointment',
description: 'Retrieve an appointment by ID',
inputSchema: {
type: 'object',
properties: {
appointment_id: { type: 'number', description: 'Appointment ID', required: true },
},
required: ['appointment_id'],
},
},
keap_update_appointment: {
{
name: 'keap_update_appointment',
description: 'Update an existing appointment',
inputSchema: {
type: 'object',
properties: {
appointment_id: { type: 'number', description: 'Appointment ID' },
appointment_id: { type: 'number', description: 'Appointment ID', required: true },
title: { type: 'string', description: 'Appointment title' },
description: { type: 'string', description: 'Appointment description' },
location: { type: 'string', description: 'Appointment location' },
start_date: { type: 'string', description: 'Start date/time (ISO 8601)' },
end_date: { type: 'string', description: 'End date/time (ISO 8601)' },
user_id: { type: 'number', description: 'Assigned user ID' },
all_day: { type: 'boolean', description: 'All-day appointment' },
start_date: { type: 'string', description: 'Start date/time' },
end_date: { type: 'string', description: 'End date/time' },
description: { type: 'string', description: 'Description' },
location: { type: 'string', description: 'Location' },
},
required: ['appointment_id'],
},
handler: async (args: any) => {
const data: any = {};
if (args.title !== undefined) data.title = args.title;
if (args.description !== undefined) data.description = args.description;
if (args.location !== undefined) data.location = args.location;
if (args.start_date !== undefined) data.start_date = args.start_date;
if (args.end_date !== undefined) data.end_date = args.end_date;
if (args.user_id !== undefined) data.user_id = args.user_id;
if (args.all_day !== undefined) data.all_day = args.all_day;
return await client.updateAppointment(args.appointment_id, data);
},
},
keap_delete_appointment: {
{
name: 'keap_delete_appointment',
description: 'Delete an appointment',
inputSchema: {
type: 'object',
properties: {
appointment_id: { type: 'number', description: 'Appointment ID' },
appointment_id: { type: 'number', description: 'Appointment ID to delete', required: true },
},
required: ['appointment_id'],
},
handler: async (args: any) => {
await client.deleteAppointment(args.appointment_id);
return { success: true, message: `Appointment ${args.appointment_id} deleted` };
},
{
name: 'keap_list_appointments',
description: 'List appointments with filtering and pagination',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
user_id: { type: 'number', description: 'Filter by user' },
contact_id: { type: 'number', description: 'Filter by contact' },
since: { type: 'string', description: 'Appointments after this date' },
until: { type: 'string', description: 'Appointments before this date' },
},
},
},
};
{
name: 'keap_get_appointment_model',
description: 'Get the appointment model schema',
inputSchema: {
type: 'object',
properties: {},
},
},
];
}
export async function handleAppointmentsTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_appointment':
result = await client.post('/appointments', {
title: args.title,
start_date: args.start_date,
end_date: args.end_date,
description: args.description,
location: args.location,
contact: args.contact_id ? { id: args.contact_id } : undefined,
user: args.user_id,
remind_time: args.remind_time,
all_day: args.all_day || false,
});
break;
case 'keap_get_appointment':
result = await client.get(`/appointments/${args.appointment_id}`);
break;
case 'keap_update_appointment': {
const { appointment_id, ...updateData } = args;
result = await client.patch(`/appointments/${appointment_id}`, updateData);
break;
}
case 'keap_delete_appointment':
await client.delete(`/appointments/${args.appointment_id}`);
result = { success: true, message: 'Appointment deleted successfully' };
break;
case 'keap_list_appointments':
result = await client.get('/appointments', args);
break;
case 'keap_get_appointment_model':
result = await client.get('/appointments/model');
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,69 +1,123 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function registerAutomationsTools(client: KeapClient) {
return {
keap_list_automations: {
description: 'List all automations/campaigns',
export function createAutomationsTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_hook',
description: 'Create a REST hook for automation (webhook)',
inputSchema: {
type: 'object',
properties: {
search_text: { type: 'string', description: 'Search by name' },
category: { type: 'string', description: 'Filter by category' },
limit: { type: 'number', description: 'Maximum results' },
eventKey: { type: 'string', description: 'Event key (e.g., contact.add, opportunity.add)', required: true },
hookUrl: { type: 'string', description: 'Webhook URL to call', required: true },
},
},
handler: async (args: any) => {
const automations = await client.listAutomations(args);
return { automations, count: automations.length };
required: ['eventKey', 'hookUrl'],
},
},
keap_get_automation: {
description: 'Get a specific automation by ID',
{
name: 'keap_list_hooks',
description: 'List all REST hooks',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'keap_delete_hook',
description: 'Delete a REST hook',
inputSchema: {
type: 'object',
properties: {
automation_id: { type: 'number', description: 'Automation ID' },
hook_key: { type: 'string', description: 'Hook key to delete', required: true },
},
required: ['automation_id'],
},
handler: async (args: any) => {
return await client.getAutomation(args.automation_id);
required: ['hook_key'],
},
},
keap_activate_automation: {
description: 'Activate/publish an automation',
{
name: 'keap_verify_hook',
description: 'Verify a REST hook',
inputSchema: {
type: 'object',
properties: {
automation_id: { type: 'number', description: 'Automation ID' },
hook_key: { type: 'string', description: 'Hook key to verify', required: true },
},
required: ['automation_id'],
},
handler: async (args: any) => {
await client.patch(`/campaigns/${args.automation_id}`, {
published_status: 'Published',
});
return { success: true, message: `Automation ${args.automation_id} activated` };
required: ['hook_key'],
},
},
keap_deactivate_automation: {
description: 'Deactivate/unpublish an automation',
{
name: 'keap_update_hook',
description: 'Update a REST hook',
inputSchema: {
type: 'object',
properties: {
automation_id: { type: 'number', description: 'Automation ID' },
hook_key: { type: 'string', description: 'Hook key to update', required: true },
hookUrl: { type: 'string', description: 'New webhook URL' },
status: { type: 'string', description: 'Hook status (Active, Inactive)' },
},
required: ['automation_id'],
},
handler: async (args: any) => {
await client.patch(`/campaigns/${args.automation_id}`, {
published_status: 'Draft',
});
return { success: true, message: `Automation ${args.automation_id} deactivated` };
required: ['hook_key'],
},
},
};
{
name: 'keap_list_hook_event_types',
description: 'List all available hook event types',
inputSchema: {
type: 'object',
properties: {},
},
},
];
}
export async function handleAutomationsTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_hook':
result = await client.post('/hooks', {
eventKey: args.eventKey,
hookUrl: args.hookUrl,
});
break;
case 'keap_list_hooks':
result = await client.get('/hooks');
break;
case 'keap_delete_hook':
await client.delete(`/hooks/${args.hook_key}`);
result = { success: true, message: 'Hook deleted successfully' };
break;
case 'keap_verify_hook':
result = await client.post(`/hooks/${args.hook_key}/verify`, {});
break;
case 'keap_update_hook': {
const { hook_key, ...updateData } = args;
result = await client.patch(`/hooks/${hook_key}`, updateData);
break;
}
case 'keap_list_hook_event_types':
result = await client.get('/hooks/event_keys');
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,113 +1,143 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function registerCampaignsTools(client: KeapClient) {
return {
keap_list_campaigns: {
description: 'List all campaigns',
export function createCampaignsTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_list_campaigns',
description: 'List all campaigns with pagination',
inputSchema: {
type: 'object',
properties: {
search_text: { type: 'string', description: 'Search by campaign name' },
limit: { type: 'number', description: 'Maximum results' },
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
order: { type: 'string', description: 'Order by field' },
search_text: { type: 'string', description: 'Search in campaign name/description' },
},
},
handler: async (args: any) => {
const campaigns = await client.listCampaigns(args);
return { campaigns, count: campaigns.length };
},
},
keap_get_campaign: {
description: 'Get a specific campaign by ID',
{
name: 'keap_get_campaign',
description: 'Get campaign details by ID',
inputSchema: {
type: 'object',
properties: {
campaign_id: { type: 'number', description: 'Campaign ID' },
campaign_id: { type: 'number', description: 'Campaign ID', required: true },
},
required: ['campaign_id'],
},
handler: async (args: any) => {
return await client.getCampaign(args.campaign_id);
},
},
keap_add_to_campaign: {
{
name: 'keap_add_contact_to_campaign',
description: 'Add a contact to a campaign sequence',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
campaign_id: { type: 'number', description: 'Campaign ID' },
contact_id: { type: 'number', description: 'Contact ID', required: true },
campaign_id: { type: 'number', description: 'Campaign ID', required: true },
},
required: ['contact_id', 'campaign_id'],
},
handler: async (args: any) => {
await client.post('/campaignSequences', {
contact_id: args.contact_id,
campaign_id: args.campaign_id,
});
return {
success: true,
message: `Contact ${args.contact_id} added to campaign ${args.campaign_id}`
};
},
},
keap_remove_from_campaign: {
{
name: 'keap_remove_contact_from_campaign',
description: 'Remove a contact from a campaign sequence',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
campaign_id: { type: 'number', description: 'Campaign ID' },
contact_id: { type: 'number', description: 'Contact ID', required: true },
campaign_id: { type: 'number', description: 'Campaign ID', required: true },
},
required: ['contact_id', 'campaign_id'],
},
handler: async (args: any) => {
const sequences = await client.get<any>('/campaignSequences', {
contact_id: args.contact_id,
campaign_id: args.campaign_id,
});
if (sequences && sequences.sequences && sequences.sequences.length > 0) {
await client.delete(`/campaignSequences/${sequences.sequences[0].id}`);
return {
success: true,
message: `Contact ${args.contact_id} removed from campaign ${args.campaign_id}`
};
}
return {
success: false,
message: `Contact ${args.contact_id} not found in campaign ${args.campaign_id}`
};
},
},
keap_get_campaign_stats: {
description: 'Get statistics for a campaign',
{
name: 'keap_get_campaign_sequences',
description: 'Get all sequences for a specific campaign',
inputSchema: {
type: 'object',
properties: {
campaign_id: { type: 'number', description: 'Campaign ID' },
campaign_id: { type: 'number', description: 'Campaign ID', required: true },
},
required: ['campaign_id'],
},
handler: async (args: any) => {
const sequences = await client.get<any>('/campaignSequences', {
campaign_id: args.campaign_id,
});
const stats = {
campaign_id: args.campaign_id,
total_contacts: sequences.sequences?.length || 0,
active_contacts: sequences.sequences?.filter((s: any) => s.status === 'Active').length || 0,
completed_contacts: sequences.sequences?.filter((s: any) => s.status === 'Completed').length || 0,
stopped_contacts: sequences.sequences?.filter((s: any) => s.status === 'Stopped').length || 0,
};
return stats;
},
{
name: 'keap_add_contact_to_sequence',
description: 'Add a contact to a specific campaign sequence',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
sequence_id: { type: 'number', description: 'Sequence ID', required: true },
},
required: ['contact_id', 'sequence_id'],
},
},
};
{
name: 'keap_remove_contact_from_sequence',
description: 'Remove a contact from a campaign sequence',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
sequence_id: { type: 'number', description: 'Sequence ID', required: true },
},
required: ['contact_id', 'sequence_id'],
},
},
];
}
export async function handleCampaignsTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_list_campaigns':
result = await client.get('/campaigns', args);
break;
case 'keap_get_campaign':
result = await client.get(`/campaigns/${args.campaign_id}`);
break;
case 'keap_add_contact_to_campaign':
result = await client.post(`/contacts/${args.contact_id}/campaigns/${args.campaign_id}`, {});
break;
case 'keap_remove_contact_from_campaign':
await client.delete(`/contacts/${args.contact_id}/campaigns/${args.campaign_id}`);
result = { success: true, message: 'Contact removed from campaign' };
break;
case 'keap_get_campaign_sequences':
result = await client.get(`/campaigns/${args.campaign_id}/sequences`);
break;
case 'keap_add_contact_to_sequence':
result = await client.post(`/contacts/${args.contact_id}/sequences/${args.sequence_id}`, {});
break;
case 'keap_remove_contact_from_sequence':
await client.delete(`/contacts/${args.contact_id}/sequences/${args.sequence_id}`);
result = { success: true, message: 'Contact removed from sequence' };
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,122 +1,141 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function registerCompaniesTools(client: KeapClient) {
return {
keap_list_companies: {
description: 'List all companies',
export function createCompaniesTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_company',
description: 'Create a new company in Keap',
inputSchema: {
type: 'object',
properties: {
company_name: { type: 'string', description: 'Filter by company name' },
limit: { type: 'number', description: 'Maximum results' },
order: { type: 'string', description: 'Sort order' },
},
},
handler: async (args: any) => {
const companies = await client.listCompanies(args);
return { companies, count: companies.length };
},
},
keap_get_company: {
description: 'Get a specific company by ID',
inputSchema: {
type: 'object',
properties: {
company_id: { type: 'number', description: 'Company ID' },
},
required: ['company_id'],
},
handler: async (args: any) => {
return await client.getCompany(args.company_id);
},
},
keap_create_company: {
description: 'Create a new company',
inputSchema: {
type: 'object',
properties: {
company_name: { type: 'string', description: 'Company name' },
email: { type: 'string', description: 'Company email address' },
phone: { type: 'string', description: 'Company phone number' },
fax: { type: 'string', description: 'Company fax number' },
website: { type: 'string', description: 'Company website' },
company_name: { type: 'string', description: 'Company name', required: true },
email: { type: 'string', description: 'Company email' },
phone: { type: 'string', description: 'Company phone' },
address_line1: { type: 'string', description: 'Street address' },
address_city: { type: 'string', description: 'City' },
address_state: { type: 'string', description: 'State/Province' },
address_zip: { type: 'string', description: 'ZIP/Postal code' },
address_country: { type: 'string', description: 'Country code' },
notes: { type: 'string', description: 'Company notes' },
city: { type: 'string', description: 'City' },
state: { type: 'string', description: 'State/Region' },
postal_code: { type: 'string', description: 'Postal code' },
country: { type: 'string', description: 'Country code' },
website: { type: 'string', description: 'Company website' },
notes: { type: 'string', description: 'Notes about the company' },
},
required: ['company_name'],
},
handler: async (args: any) => {
const data: any = {
company_name: args.company_name,
email_address: args.email,
phone_number: args.phone,
fax_number: args.fax,
website: args.website,
notes: args.notes,
};
if (args.address_line1 || args.address_city) {
data.address = {
line1: args.address_line1,
locality: args.address_city,
region: args.address_state,
zip_code: args.address_zip,
country_code: args.address_country || 'US',
field: 'BILLING',
};
}
return await client.createCompany(data);
},
{
name: 'keap_get_company',
description: 'Retrieve a company by ID',
inputSchema: {
type: 'object',
properties: {
company_id: { type: 'number', description: 'Company ID', required: true },
},
required: ['company_id'],
},
},
keap_update_company: {
{
name: 'keap_update_company',
description: 'Update an existing company',
inputSchema: {
type: 'object',
properties: {
company_id: { type: 'number', description: 'Company ID' },
company_id: { type: 'number', description: 'Company ID', required: true },
company_name: { type: 'string', description: 'Company name' },
email: { type: 'string', description: 'Company email address' },
phone: { type: 'string', description: 'Company phone number' },
fax: { type: 'string', description: 'Company fax number' },
email: { type: 'string', description: 'Company email' },
phone: { type: 'string', description: 'Company phone' },
website: { type: 'string', description: 'Company website' },
notes: { type: 'string', description: 'Company notes' },
notes: { type: 'string', description: 'Notes' },
},
required: ['company_id'],
},
handler: async (args: any) => {
const data: any = {};
if (args.company_name !== undefined) data.company_name = args.company_name;
if (args.email !== undefined) data.email_address = args.email;
if (args.phone !== undefined) data.phone_number = args.phone;
if (args.fax !== undefined) data.fax_number = args.fax;
if (args.website !== undefined) data.website = args.website;
if (args.notes !== undefined) data.notes = args.notes;
return await client.updateCompany(args.company_id, data);
},
},
keap_delete_company: {
description: 'Delete a company',
{
name: 'keap_list_companies',
description: 'List all companies with pagination',
inputSchema: {
type: 'object',
properties: {
company_id: { type: 'number', description: 'Company ID' },
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
company_name: { type: 'string', description: 'Filter by company name' },
order: { type: 'string', description: 'Order by field' },
},
},
},
{
name: 'keap_get_company_contacts',
description: 'Get all contacts associated with a company',
inputSchema: {
type: 'object',
properties: {
company_id: { type: 'number', description: 'Company ID', required: true },
limit: { type: 'number', description: 'Max results', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
},
required: ['company_id'],
},
handler: async (args: any) => {
await client.deleteCompany(args.company_id);
return { success: true, message: `Company ${args.company_id} deleted` };
},
},
};
];
}
export async function handleCompaniesTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_company':
result = await client.post('/companies', {
company_name: args.company_name,
email_address: args.email ? { email: args.email } : undefined,
phone_number: args.phone ? { number: args.phone } : undefined,
address: args.address_line1 ? {
line1: args.address_line1,
locality: args.city,
region: args.state,
postal_code: args.postal_code,
country_code: args.country,
} : undefined,
website: args.website,
notes: args.notes,
});
break;
case 'keap_get_company':
result = await client.get(`/companies/${args.company_id}`);
break;
case 'keap_update_company': {
const { company_id, ...updateData } = args;
result = await client.patch(`/companies/${company_id}`, updateData);
break;
}
case 'keap_list_companies':
result = await client.get('/companies', args);
break;
case 'keap_get_company_contacts':
result = await client.get(`/companies/${args.company_id}/contacts`, {
limit: args.limit,
offset: args.offset,
});
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,318 +1,423 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
import { KeapContact, ApiResponse } from '../types/index.js';
export function registerContactsTools(client: KeapClient) {
return {
keap_list_contacts: {
description: 'List contacts with optional filtering',
export function createContactsTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_contact',
description: 'Create a new contact in Keap with email, name, phone, address, tags, and custom fields',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Number of results per page' },
given_name: { type: 'string', description: 'First name' },
family_name: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Primary email address' },
phone: { type: 'string', description: 'Primary phone number' },
company_name: { type: 'string', description: 'Company name' },
job_title: { type: 'string', description: 'Job title' },
tag_ids: { type: 'array', items: { type: 'number' }, description: 'Array of tag IDs to apply' },
custom_fields: { type: 'array', items: { type: 'object' }, description: 'Custom field values [{id, content}]' },
address_line1: { type: 'string', description: 'Street address line 1' },
address_line2: { type: 'string', description: 'Street address line 2' },
city: { type: 'string', description: 'City' },
state: { type: 'string', description: 'State/Region' },
postal_code: { type: 'string', description: 'Postal/ZIP code' },
country: { type: 'string', description: 'Country code (e.g., US)' },
opt_in_reason: { type: 'string', description: 'Reason for opt-in (required for GDPR)' },
owner_id: { type: 'number', description: 'User ID of contact owner' },
},
},
},
{
name: 'keap_get_contact',
description: 'Retrieve a contact by ID with all details including tags, custom fields, and company',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
optional_properties: { type: 'array', items: { type: 'string' }, description: 'Additional fields to include' },
},
required: ['contact_id'],
},
},
{
name: 'keap_update_contact',
description: 'Update an existing contact with new information',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
given_name: { type: 'string', description: 'First name' },
family_name: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Primary email address' },
phone: { type: 'string', description: 'Primary phone number' },
company_name: { type: 'string', description: 'Company name' },
job_title: { type: 'string', description: 'Job title' },
tag_ids: { type: 'array', items: { type: 'number' }, description: 'Array of tag IDs' },
custom_fields: { type: 'array', items: { type: 'object' }, description: 'Custom field values' },
owner_id: { type: 'number', description: 'User ID of contact owner' },
},
required: ['contact_id'],
},
},
{
name: 'keap_delete_contact',
description: 'Permanently delete a contact from Keap',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID to delete', required: true },
},
required: ['contact_id'],
},
},
{
name: 'keap_list_contacts',
description: 'List contacts with pagination, filtering, and sorting options',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Number of results per page (max 200)', default: 50 },
offset: { type: 'number', description: 'Offset for pagination', default: 0 },
email: { type: 'string', description: 'Filter by email address' },
given_name: { type: 'string', description: 'Filter by first name' },
family_name: { type: 'string', description: 'Filter by last name' },
order: { type: 'string', description: 'Sort order field' },
order: { type: 'string', description: 'Field to order by (e.g., date_created, email)', default: 'date_created' },
order_direction: { type: 'string', enum: ['ascending', 'descending'], description: 'Sort direction' },
since: { type: 'string', description: 'Filter contacts created/updated after this ISO date' },
until: { type: 'string', description: 'Filter contacts created/updated before this ISO date' },
},
},
handler: async (args: any) => {
const contacts = await client.listContacts(args);
return { contacts, count: contacts.length };
},
},
keap_get_contact: {
description: 'Get a specific contact by ID',
{
name: 'keap_search_contacts',
description: 'Search contacts by email, name, phone, or other criteria',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
email: { type: 'string', description: 'Search by email address' },
given_name: { type: 'string', description: 'Search by first name' },
family_name: { type: 'string', description: 'Search by last name' },
limit: { type: 'number', description: 'Max results', default: 50 },
},
},
},
{
name: 'keap_merge_contacts',
description: 'Merge two contacts together, combining all data into one contact',
inputSchema: {
type: 'object',
properties: {
source_contact_id: { type: 'number', description: 'Contact ID to merge from (will be deleted)', required: true },
target_contact_id: { type: 'number', description: 'Contact ID to merge into (will be kept)', required: true },
},
required: ['source_contact_id', 'target_contact_id'],
},
},
{
name: 'keap_apply_tag_to_contact',
description: 'Apply one or more tags to a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
tag_ids: { type: 'array', items: { type: 'number' }, description: 'Array of tag IDs to apply', required: true },
},
required: ['contact_id', 'tag_ids'],
},
},
{
name: 'keap_remove_tag_from_contact',
description: 'Remove one or more tags from a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
tag_ids: { type: 'array', items: { type: 'number' }, description: 'Array of tag IDs to remove', required: true },
},
required: ['contact_id', 'tag_ids'],
},
},
{
name: 'keap_get_contact_tags',
description: 'Get all tags applied to a specific contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
},
required: ['contact_id'],
},
handler: async (args: any) => {
return await client.getContact(args.contact_id);
},
},
keap_create_contact: {
description: 'Create a new contact',
{
name: 'keap_get_contact_emails',
description: 'Get all email addresses associated with a contact',
inputSchema: {
type: 'object',
properties: {
email: { type: 'string', description: 'Primary email address' },
given_name: { type: 'string', description: 'First name' },
family_name: { type: 'string', description: 'Last name' },
phone: { type: 'string', description: 'Phone number' },
company_name: { type: 'string', description: 'Company name' },
job_title: { type: 'string', description: 'Job title' },
website: { type: 'string', description: 'Website URL' },
address_line1: { type: 'string', description: 'Street address' },
address_city: { type: 'string', description: 'City' },
address_state: { type: 'string', description: 'State/Province' },
address_zip: { type: 'string', description: 'ZIP/Postal code' },
address_country: { type: 'string', description: 'Country code' },
},
required: ['email'],
},
handler: async (args: any) => {
const data: any = {
email_addresses: [{ email: args.email, field: 'EMAIL1' }],
given_name: args.given_name,
family_name: args.family_name,
company_name: args.company_name,
job_title: args.job_title,
website: args.website,
};
if (args.phone) {
data.phone_numbers = [{ number: args.phone, field: 'PHONE1' }];
}
if (args.address_line1 || args.address_city) {
data.addresses = [{
line1: args.address_line1,
locality: args.address_city,
region: args.address_state,
zip_code: args.address_zip,
country_code: args.address_country || 'US',
field: 'BILLING',
}];
}
return await client.createContact(data);
},
},
keap_update_contact: {
description: 'Update an existing contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
email: { type: 'string', description: 'Email address' },
given_name: { type: 'string', description: 'First name' },
family_name: { type: 'string', description: 'Last name' },
phone: { type: 'string', description: 'Phone number' },
company_name: { type: 'string', description: 'Company name' },
job_title: { type: 'string', description: 'Job title' },
website: { type: 'string', description: 'Website URL' },
contact_id: { type: 'number', description: 'Contact ID', required: true },
},
required: ['contact_id'],
},
handler: async (args: any) => {
const data: any = {
},
{
name: 'keap_create_contact_email',
description: 'Add a new email address to a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
email: { type: 'string', description: 'Email address', required: true },
field: { type: 'string', description: 'Field type (EMAIL1, EMAIL2, EMAIL3)', default: 'EMAIL1' },
},
required: ['contact_id', 'email'],
},
},
{
name: 'keap_delete_contact_email',
description: 'Remove an email address from a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
email_id: { type: 'number', description: 'Email ID to remove', required: true },
},
required: ['contact_id', 'email_id'],
},
},
{
name: 'keap_get_contact_credit_cards',
description: 'Get all credit cards on file for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
},
required: ['contact_id'],
},
},
{
name: 'keap_create_contact_credit_card',
description: 'Add a new credit card to a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
card_number: { type: 'string', description: 'Credit card number', required: true },
expiration_month: { type: 'string', description: 'Expiration month (MM)', required: true },
expiration_year: { type: 'string', description: 'Expiration year (YYYY)', required: true },
card_type: { type: 'string', description: 'Card type (Visa, Mastercard, etc.)' },
name_on_card: { type: 'string', description: 'Name as shown on card' },
},
required: ['contact_id', 'card_number', 'expiration_month', 'expiration_year'],
},
},
{
name: 'keap_get_contact_custom_fields',
description: 'Retrieve custom field values for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
},
required: ['contact_id'],
},
},
{
name: 'keap_update_contact_custom_field',
description: 'Update a specific custom field value for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
custom_field_id: { type: 'number', description: 'Custom field ID', required: true },
content: { type: 'string', description: 'New value for the custom field', required: true },
},
required: ['contact_id', 'custom_field_id', 'content'],
},
},
{
name: 'keap_list_contact_notes',
description: 'Get all notes for a specific contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
limit: { type: 'number', description: 'Max results', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
},
required: ['contact_id'],
},
},
{
name: 'keap_get_contact_model',
description: 'Retrieve the contact model schema including all available custom fields',
inputSchema: {
type: 'object',
properties: {},
},
},
];
}
export async function handleContactsTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_contact': {
const contactData: any = {
given_name: args.given_name,
family_name: args.family_name,
company_name: args.company_name,
job_title: args.job_title,
website: args.website,
opt_in_reason: args.opt_in_reason,
};
if (args.email) {
data.email_addresses = [{ email: args.email, field: 'EMAIL1' }];
contactData.email_addresses = [{ email: args.email, field: 'EMAIL1' }];
}
if (args.phone) {
data.phone_numbers = [{ number: args.phone, field: 'PHONE1' }];
contactData.phone_numbers = [{ number: args.phone, field: 'PHONE1' }];
}
return await client.updateContact(args.contact_id, data);
},
},
keap_delete_contact: {
description: 'Delete a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
},
required: ['contact_id'],
},
handler: async (args: any) => {
await client.deleteContact(args.contact_id);
return { success: true, message: `Contact ${args.contact_id} deleted` };
},
},
keap_search_contacts: {
description: 'Search contacts by keyword',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Maximum results' },
},
required: ['query'],
},
handler: async (args: any) => {
const contacts = await client.listContacts({
email: args.query.includes('@') ? args.query : undefined,
given_name: !args.query.includes('@') ? args.query : undefined,
limit: args.limit || 100,
});
return { contacts, count: contacts.length };
},
},
keap_list_contact_tags: {
description: 'List all tags applied to a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
},
required: ['contact_id'],
},
handler: async (args: any) => {
const response = await client.get<any>(`/contacts/${args.contact_id}/tags`);
return { tags: response.tags || [] };
},
},
keap_add_tag_to_contact: {
description: 'Add a tag to a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
tag_id: { type: 'number', description: 'Tag ID' },
},
required: ['contact_id', 'tag_id'],
},
handler: async (args: any) => {
await client.post(`/contacts/${args.contact_id}/tags`, { tagId: args.tag_id });
return { success: true, message: `Tag ${args.tag_id} added to contact ${args.contact_id}` };
},
},
keap_remove_tag_from_contact: {
description: 'Remove a tag from a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
tag_id: { type: 'number', description: 'Tag ID' },
},
required: ['contact_id', 'tag_id'],
},
handler: async (args: any) => {
await client.delete(`/contacts/${args.contact_id}/tags/${args.tag_id}`);
return { success: true, message: `Tag ${args.tag_id} removed from contact ${args.contact_id}` };
},
},
keap_list_contact_emails: {
description: 'List all emails sent to/from a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
limit: { type: 'number', description: 'Maximum results' },
},
required: ['contact_id'],
},
handler: async (args: any) => {
const emails = await client.listEmails({
contact_id: args.contact_id,
limit: args.limit || 100,
});
return { emails, count: emails.length };
},
},
keap_create_contact_email: {
description: 'Create an email record for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
subject: { type: 'string', description: 'Email subject' },
html_content: { type: 'string', description: 'HTML email body' },
text_content: { type: 'string', description: 'Plain text email body' },
from_address: { type: 'string', description: 'From email address' },
},
required: ['contact_id', 'subject'],
},
handler: async (args: any) => {
const contact = await client.getContact(args.contact_id);
const toAddress = contact.email_addresses?.[0]?.email;
if (!toAddress) {
throw new Error(`Contact ${args.contact_id} has no email address`);
if (args.address_line1 || args.city) {
contactData.addresses = [{
line1: args.address_line1,
line2: args.address_line2,
locality: args.city,
region: args.state,
postal_code: args.postal_code,
country_code: args.country || 'US',
}];
}
return await client.createEmail({
sent_to_address: toAddress,
sent_to_contact_id: args.contact_id,
sent_from_address: args.from_address,
subject: args.subject,
html_content: args.html_content,
text_content: args.text_content,
});
},
},
if (args.company_name) {
contactData.company = { company_name: args.company_name };
}
keap_list_contact_notes: {
description: 'List all notes for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
limit: { type: 'number', description: 'Maximum results' },
},
required: ['contact_id'],
},
handler: async (args: any) => {
const notes = await client.listNotes({
contact_id: args.contact_id,
limit: args.limit || 100,
});
return { notes, count: notes.length };
},
},
if (args.job_title) contactData.job_title = args.job_title;
if (args.tag_ids) contactData.tag_ids = args.tag_ids;
if (args.custom_fields) contactData.custom_fields = args.custom_fields;
if (args.owner_id) contactData.owner_id = args.owner_id;
keap_create_contact_note: {
description: 'Create a note for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
title: { type: 'string', description: 'Note title' },
body: { type: 'string', description: 'Note content' },
type: { type: 'string', enum: ['Appointment', 'Call', 'Email', 'Fax', 'Letter', 'Other'], description: 'Note type' },
},
required: ['contact_id', 'body'],
},
handler: async (args: any) => {
return await client.createNote({
contact_id: args.contact_id,
title: args.title,
body: args.body,
type: args.type || 'Other',
});
},
},
result = await client.post('/contacts', contactData);
break;
}
keap_merge_contacts: {
description: 'Merge two contacts (keeps first, merges second into it)',
inputSchema: {
type: 'object',
properties: {
keep_contact_id: { type: 'number', description: 'Contact ID to keep' },
merge_contact_id: { type: 'number', description: 'Contact ID to merge and delete' },
},
required: ['keep_contact_id', 'merge_contact_id'],
},
handler: async (args: any) => {
await client.post(`/contacts/${args.keep_contact_id}/merge`, {
duplicateContactId: args.merge_contact_id,
case 'keap_get_contact':
result = await client.get(`/contacts/${args.contact_id}`, {
optional_properties: args.optional_properties?.join(','),
});
return {
success: true,
message: `Contact ${args.merge_contact_id} merged into ${args.keep_contact_id}`
};
},
},
};
break;
case 'keap_update_contact': {
const { contact_id, ...updateData } = args;
result = await client.patch(`/contacts/${contact_id}`, updateData);
break;
}
case 'keap_delete_contact':
await client.delete(`/contacts/${args.contact_id}`);
result = { success: true, message: 'Contact deleted successfully' };
break;
case 'keap_list_contacts':
result = await client.get('/contacts', args);
break;
case 'keap_search_contacts':
result = await client.get('/contacts', args);
break;
case 'keap_merge_contacts':
result = await client.post(`/contacts/${args.target_contact_id}/merge`, {
duplicate_contact_id: args.source_contact_id,
});
break;
case 'keap_apply_tag_to_contact':
result = await client.post(`/contacts/${args.contact_id}/tags`, {
tagIds: args.tag_ids,
});
break;
case 'keap_remove_tag_from_contact':
for (const tagId of args.tag_ids) {
await client.delete(`/contacts/${args.contact_id}/tags/${tagId}`);
}
result = { success: true, message: 'Tags removed successfully' };
break;
case 'keap_get_contact_tags':
result = await client.get(`/contacts/${args.contact_id}/tags`);
break;
case 'keap_get_contact_emails':
result = await client.get(`/contacts/${args.contact_id}/emails`);
break;
case 'keap_create_contact_email':
result = await client.post(`/contacts/${args.contact_id}/emails`, {
email: args.email,
field: args.field || 'EMAIL1',
});
break;
case 'keap_delete_contact_email':
await client.delete(`/contacts/${args.contact_id}/emails/${args.email_id}`);
result = { success: true, message: 'Email deleted successfully' };
break;
case 'keap_get_contact_credit_cards':
result = await client.get(`/contacts/${args.contact_id}/creditCards`);
break;
case 'keap_create_contact_credit_card':
result = await client.post(`/contacts/${args.contact_id}/creditCards`, {
card_number: args.card_number,
expiration_month: args.expiration_month,
expiration_year: args.expiration_year,
card_type: args.card_type,
name_on_card: args.name_on_card,
});
break;
case 'keap_get_contact_custom_fields':
result = await client.get(`/contacts/${args.contact_id}?optional_properties=custom_fields`);
break;
case 'keap_update_contact_custom_field':
result = await client.patch(`/contacts/${args.contact_id}`, {
custom_fields: [{ id: args.custom_field_id, content: args.content }],
});
break;
case 'keap_list_contact_notes':
result = await client.get(`/contacts/${args.contact_id}/notes`, {
limit: args.limit,
offset: args.offset,
});
break;
case 'keap_get_contact_model':
result = await client.get('/contacts/model');
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,188 +0,0 @@
import { KeapClient } from '../clients/keap.js';
export function registerDealsTools(client: KeapClient) {
return {
keap_list_deals: {
description: 'List all deals/opportunities',
inputSchema: {
type: 'object',
properties: {
stage_id: { type: 'number', description: 'Filter by stage ID' },
user_id: { type: 'number', description: 'Filter by assigned user ID' },
contact_id: { type: 'number', description: 'Filter by contact ID' },
limit: { type: 'number', description: 'Maximum results' },
order: { type: 'string', description: 'Sort order' },
},
},
handler: async (args: any) => {
const deals = await client.listDeals(args);
return { deals, count: deals.length };
},
},
keap_get_deal: {
description: 'Get a specific deal by ID',
inputSchema: {
type: 'object',
properties: {
deal_id: { type: 'number', description: 'Deal ID' },
},
required: ['deal_id'],
},
handler: async (args: any) => {
return await client.getDeal(args.deal_id);
},
},
keap_create_deal: {
description: 'Create a new deal/opportunity',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Deal title' },
contact_id: { type: 'number', description: 'Contact ID' },
stage_id: { type: 'number', description: 'Pipeline stage ID' },
user_id: { type: 'number', description: 'Assigned user ID' },
projected_revenue_low: { type: 'number', description: 'Minimum projected revenue' },
projected_revenue_high: { type: 'number', description: 'Maximum projected revenue' },
estimated_close_date: { type: 'string', description: 'Estimated close date (ISO 8601)' },
probability: { type: 'number', description: 'Win probability (0-100)' },
next_action_date: { type: 'string', description: 'Next action date (ISO 8601)' },
next_action_notes: { type: 'string', description: 'Next action notes' },
},
required: ['title', 'stage_id'],
},
handler: async (args: any) => {
return await client.createDeal({
title: args.title,
contact: args.contact_id ? { id: args.contact_id } : undefined,
stage_id: args.stage_id,
user_id: args.user_id,
projected_revenue_low: args.projected_revenue_low,
projected_revenue_high: args.projected_revenue_high,
estimated_close_date: args.estimated_close_date,
probability: args.probability,
next_action_date: args.next_action_date,
next_action_notes: args.next_action_notes,
});
},
},
keap_update_deal: {
description: 'Update an existing deal',
inputSchema: {
type: 'object',
properties: {
deal_id: { type: 'number', description: 'Deal ID' },
title: { type: 'string', description: 'Deal title' },
stage_id: { type: 'number', description: 'Pipeline stage ID' },
user_id: { type: 'number', description: 'Assigned user ID' },
projected_revenue_low: { type: 'number', description: 'Minimum projected revenue' },
projected_revenue_high: { type: 'number', description: 'Maximum projected revenue' },
estimated_close_date: { type: 'string', description: 'Estimated close date (ISO 8601)' },
probability: { type: 'number', description: 'Win probability (0-100)' },
next_action_date: { type: 'string', description: 'Next action date (ISO 8601)' },
next_action_notes: { type: 'string', description: 'Next action notes' },
},
required: ['deal_id'],
},
handler: async (args: any) => {
const data: any = {};
if (args.title !== undefined) data.title = args.title;
if (args.stage_id !== undefined) data.stage_id = args.stage_id;
if (args.user_id !== undefined) data.user_id = args.user_id;
if (args.projected_revenue_low !== undefined) data.projected_revenue_low = args.projected_revenue_low;
if (args.projected_revenue_high !== undefined) data.projected_revenue_high = args.projected_revenue_high;
if (args.estimated_close_date !== undefined) data.estimated_close_date = args.estimated_close_date;
if (args.probability !== undefined) data.probability = args.probability;
if (args.next_action_date !== undefined) data.next_action_date = args.next_action_date;
if (args.next_action_notes !== undefined) data.next_action_notes = args.next_action_notes;
return await client.updateDeal(args.deal_id, data);
},
},
keap_delete_deal: {
description: 'Delete a deal',
inputSchema: {
type: 'object',
properties: {
deal_id: { type: 'number', description: 'Deal ID' },
},
required: ['deal_id'],
},
handler: async (args: any) => {
await client.deleteDeal(args.deal_id);
return { success: true, message: `Deal ${args.deal_id} deleted` };
},
},
keap_move_deal_stage: {
description: 'Move a deal to a different pipeline stage',
inputSchema: {
type: 'object',
properties: {
deal_id: { type: 'number', description: 'Deal ID' },
stage_id: { type: 'number', description: 'New stage ID' },
reason: { type: 'string', description: 'Reason for move (optional note)' },
},
required: ['deal_id', 'stage_id'],
},
handler: async (args: any) => {
const data: any = { stage_id: args.stage_id };
if (args.reason) {
data.next_action_notes = args.reason;
}
return await client.updateDeal(args.deal_id, data);
},
},
keap_list_pipelines: {
description: 'List all sales pipelines',
inputSchema: {
type: 'object',
properties: {},
},
handler: async () => {
const pipelines = await client.get<any>('/opportunity/stage_pipeline');
return { pipelines: pipelines.pipelines || [] };
},
},
keap_get_pipeline: {
description: 'Get a specific pipeline with all its stages',
inputSchema: {
type: 'object',
properties: {
pipeline_id: { type: 'number', description: 'Pipeline ID' },
},
required: ['pipeline_id'],
},
handler: async (args: any) => {
const pipeline = await client.get<any>(`/opportunity/stage_pipeline/${args.pipeline_id}`);
return pipeline;
},
},
keap_list_stages: {
description: 'List all stages across all pipelines',
inputSchema: {
type: 'object',
properties: {
pipeline_id: { type: 'number', description: 'Filter by pipeline ID' },
},
},
handler: async (args: any) => {
if (args.pipeline_id) {
const pipeline = await client.get<any>(`/opportunity/stage_pipeline/${args.pipeline_id}`);
return { stages: pipeline.stages || [] };
} else {
const pipelines = await client.get<any>('/opportunity/stage_pipeline');
const allStages = pipelines.pipelines?.flatMap((p: any) => p.stages || []) || [];
return { stages: allStages };
}
},
},
};
}

View File

@ -0,0 +1,302 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function createEcommerceTools(client: KeapClient): Tool[] {
return [
// Products
{
name: 'keap_create_product',
description: 'Create a new product in Keap',
inputSchema: {
type: 'object',
properties: {
product_name: { type: 'string', description: 'Product name', required: true },
product_short_desc: { type: 'string', description: 'Short description' },
product_desc: { type: 'string', description: 'Full description' },
product_price: { type: 'number', description: 'Product price', required: true },
sku: { type: 'string', description: 'SKU code' },
subscription_only: { type: 'boolean', description: 'Is subscription-only product', default: false },
url: { type: 'string', description: 'Product URL' },
},
required: ['product_name', 'product_price'],
},
},
{
name: 'keap_get_product',
description: 'Retrieve a product by ID',
inputSchema: {
type: 'object',
properties: {
product_id: { type: 'number', description: 'Product ID', required: true },
},
required: ['product_id'],
},
},
{
name: 'keap_update_product',
description: 'Update an existing product',
inputSchema: {
type: 'object',
properties: {
product_id: { type: 'number', description: 'Product ID', required: true },
product_name: { type: 'string', description: 'Product name' },
product_price: { type: 'number', description: 'Product price' },
sku: { type: 'string', description: 'SKU code' },
status: { type: 'number', description: 'Status (0=inactive, 1=active)' },
},
required: ['product_id'],
},
},
{
name: 'keap_delete_product',
description: 'Delete a product',
inputSchema: {
type: 'object',
properties: {
product_id: { type: 'number', description: 'Product ID to delete', required: true },
},
required: ['product_id'],
},
},
{
name: 'keap_list_products',
description: 'List all products with pagination',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
active: { type: 'boolean', description: 'Filter by active status' },
},
},
},
// Orders
{
name: 'keap_create_order',
description: 'Create a new order in Keap',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
order_title: { type: 'string', description: 'Order title', required: true },
order_type: { type: 'string', description: 'Order type (Online, Offline)', default: 'Online' },
order_items: { type: 'array', items: { type: 'object' }, description: 'Array of order items', required: true },
promo_codes: { type: 'array', items: { type: 'string' }, description: 'Promo codes to apply' },
},
required: ['contact_id', 'order_title', 'order_items'],
},
},
{
name: 'keap_get_order',
description: 'Retrieve an order by ID',
inputSchema: {
type: 'object',
properties: {
order_id: { type: 'number', description: 'Order ID', required: true },
},
required: ['order_id'],
},
},
{
name: 'keap_delete_order',
description: 'Delete an order',
inputSchema: {
type: 'object',
properties: {
order_id: { type: 'number', description: 'Order ID to delete', required: true },
},
required: ['order_id'],
},
},
{
name: 'keap_list_orders',
description: 'List orders with filtering',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact' },
product_id: { type: 'number', description: 'Filter by product' },
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
since: { type: 'string', description: 'Orders after this date' },
until: { type: 'string', description: 'Orders before this date' },
paid: { type: 'boolean', description: 'Filter by paid status' },
},
},
},
{
name: 'keap_list_order_transactions',
description: 'Get all transactions for an order',
inputSchema: {
type: 'object',
properties: {
order_id: { type: 'number', description: 'Order ID', required: true },
},
required: ['order_id'],
},
},
// Transactions
{
name: 'keap_get_transaction',
description: 'Retrieve a transaction by ID',
inputSchema: {
type: 'object',
properties: {
transaction_id: { type: 'number', description: 'Transaction ID', required: true },
},
required: ['transaction_id'],
},
},
{
name: 'keap_list_transactions',
description: 'List transactions with filtering',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact' },
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
since: { type: 'string', description: 'Transactions after this date' },
until: { type: 'string', description: 'Transactions before this date' },
},
},
},
// Subscriptions
{
name: 'keap_create_subscription',
description: 'Create a subscription for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
product_id: { type: 'number', description: 'Product ID', required: true },
subscription_plan_id: { type: 'number', description: 'Subscription plan ID', required: true },
quantity: { type: 'number', description: 'Quantity', default: 1 },
billing_amount: { type: 'number', description: 'Billing amount' },
credit_card_id: { type: 'number', description: 'Credit card ID for payment' },
},
required: ['contact_id', 'product_id', 'subscription_plan_id'],
},
},
{
name: 'keap_get_subscription',
description: 'Retrieve a subscription by ID',
inputSchema: {
type: 'object',
properties: {
subscription_id: { type: 'number', description: 'Subscription ID', required: true },
},
required: ['subscription_id'],
},
},
{
name: 'keap_list_subscriptions',
description: 'List subscriptions with filtering',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact' },
active: { type: 'boolean', description: 'Filter by active status' },
limit: { type: 'number', description: 'Results per page', default: 50 },
},
},
},
];
}
export async function handleEcommerceTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
// Products
case 'keap_create_product':
result = await client.post('/products', args);
break;
case 'keap_get_product':
result = await client.get(`/products/${args.product_id}`);
break;
case 'keap_update_product': {
const { product_id, ...updateData } = args;
result = await client.patch(`/products/${product_id}`, updateData);
break;
}
case 'keap_delete_product':
await client.delete(`/products/${args.product_id}`);
result = { success: true, message: 'Product deleted successfully' };
break;
case 'keap_list_products':
result = await client.get('/products', args);
break;
// Orders
case 'keap_create_order':
result = await client.post('/orders', {
contact_id: args.contact_id,
order_title: args.order_title,
order_type: args.order_type || 'Online',
order_items: args.order_items,
promo_codes: args.promo_codes,
});
break;
case 'keap_get_order':
result = await client.get(`/orders/${args.order_id}`);
break;
case 'keap_delete_order':
await client.delete(`/orders/${args.order_id}`);
result = { success: true, message: 'Order deleted successfully' };
break;
case 'keap_list_orders':
result = await client.get('/orders', args);
break;
case 'keap_list_order_transactions':
result = await client.get(`/orders/${args.order_id}/transactions`);
break;
// Transactions
case 'keap_get_transaction':
result = await client.get(`/transactions/${args.transaction_id}`);
break;
case 'keap_list_transactions':
result = await client.get('/transactions', args);
break;
// Subscriptions
case 'keap_create_subscription':
result = await client.post('/subscriptions', args);
break;
case 'keap_get_subscription':
result = await client.get(`/subscriptions/${args.subscription_id}`);
break;
case 'keap_list_subscriptions':
result = await client.get('/subscriptions', args);
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,126 +1,165 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function registerEmailsTools(client: KeapClient) {
return {
keap_list_emails: {
description: 'List emails',
export function createEmailsTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_send_email',
description: 'Send an email to one or more contacts',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact ID' },
since: { type: 'string', description: 'Start date filter (ISO 8601)' },
until: { type: 'string', description: 'End date filter (ISO 8601)' },
limit: { type: 'number', description: 'Maximum results' },
contacts: { type: 'array', items: { type: 'number' }, description: 'Array of contact IDs', required: true },
subject: { type: 'string', description: 'Email subject', required: true },
html_content: { type: 'string', description: 'HTML content of email' },
text_content: { type: 'string', description: 'Plain text content of email' },
from_address: { type: 'string', description: 'From email address' },
reply_to_address: { type: 'string', description: 'Reply-to email address' },
attachments: { type: 'array', items: { type: 'object' }, description: 'Email attachments' },
},
},
handler: async (args: any) => {
const emails = await client.listEmails(args);
return { emails, count: emails.length };
required: ['contacts', 'subject'],
},
},
keap_get_email: {
description: 'Get a specific email by ID',
{
name: 'keap_get_email',
description: 'Retrieve an email by ID',
inputSchema: {
type: 'object',
properties: {
email_id: { type: 'number', description: 'Email ID' },
email_id: { type: 'number', description: 'Email ID', required: true },
},
required: ['email_id'],
},
handler: async (args: any) => {
return await client.getEmail(args.email_id);
},
},
keap_create_email: {
description: 'Create an email record',
{
name: 'keap_list_emails',
description: 'List emails with filtering',
inputSchema: {
type: 'object',
properties: {
to_address: { type: 'string', description: 'Recipient email address' },
from_address: { type: 'string', description: 'Sender email address' },
subject: { type: 'string', description: 'Email subject' },
html_content: { type: 'string', description: 'HTML email body' },
text_content: { type: 'string', description: 'Plain text email body' },
contact_id: { type: 'number', description: 'Contact ID' },
},
required: ['to_address', 'from_address', 'subject'],
},
handler: async (args: any) => {
return await client.createEmail({
sent_to_address: args.to_address,
sent_to_contact_id: args.contact_id,
sent_from_address: args.from_address,
subject: args.subject,
html_content: args.html_content,
text_content: args.text_content,
});
},
},
keap_send_email: {
description: 'Send an email to a contact',
inputSchema: {
type: 'object',
properties: {
to_address: { type: 'string', description: 'Recipient email address' },
from_address: { type: 'string', description: 'Sender email address' },
subject: { type: 'string', description: 'Email subject' },
html_content: { type: 'string', description: 'HTML email body' },
text_content: { type: 'string', description: 'Plain text email body' },
contact_id: { type: 'number', description: 'Contact ID' },
},
required: ['to_address', 'from_address', 'subject'],
},
handler: async (args: any) => {
return await client.sendEmail({
sent_to_address: args.to_address,
sent_to_contact_id: args.contact_id,
sent_from_address: args.from_address,
subject: args.subject,
html_content: args.html_content,
text_content: args.text_content,
});
},
},
keap_get_email_templates: {
description: 'Get all email templates',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Maximum results' },
contact_id: { type: 'number', description: 'Filter by contact' },
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
since: { type: 'string', description: 'Emails sent after this date' },
until: { type: 'string', description: 'Emails sent before this date' },
},
},
handler: async (args: any) => {
const templates = await client.get<any>('/emails/templates', args);
return { templates: templates.templates || [] };
},
},
keap_create_email_template: {
description: 'Create a new email template',
{
name: 'keap_create_email_template',
description: 'Create an email template',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Template name' },
subject: { type: 'string', description: 'Email subject' },
html_content: { type: 'string', description: 'HTML email body' },
text_content: { type: 'string', description: 'Plain text email body' },
categories: { type: 'array', items: { type: 'string' }, description: 'Template categories' },
name: { type: 'string', description: 'Template name', required: true },
subject: { type: 'string', description: 'Email subject', required: true },
html_content: { type: 'string', description: 'HTML content' },
text_content: { type: 'string', description: 'Plain text content' },
},
required: ['name', 'subject'],
},
handler: async (args: any) => {
return await client.post('/emails/templates', {
},
{
name: 'keap_list_email_templates',
description: 'List all email templates',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Results per page', default: 50 },
},
},
},
{
name: 'keap_opt_in_contact',
description: 'Opt in a contact to email communications',
inputSchema: {
type: 'object',
properties: {
email: { type: 'string', description: 'Email address', required: true },
opt_in_reason: { type: 'string', description: 'Reason for opt-in', required: true },
},
required: ['email', 'opt_in_reason'],
},
},
{
name: 'keap_opt_out_contact',
description: 'Opt out a contact from email communications',
inputSchema: {
type: 'object',
properties: {
email: { type: 'string', description: 'Email address', required: true },
},
required: ['email'],
},
},
];
}
export async function handleEmailsTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_send_email':
result = await client.post('/emails', {
contacts: args.contacts,
subject: args.subject,
html_content: args.html_content,
text_content: args.text_content,
sent_from_address: args.from_address,
sent_from_reply_address: args.reply_to_address,
attachments: args.attachments,
});
break;
case 'keap_get_email':
result = await client.get(`/emails/${args.email_id}`);
break;
case 'keap_list_emails':
result = await client.get('/emails', args);
break;
case 'keap_create_email_template':
result = await client.post('/emails/templates', {
name: args.name,
subject: args.subject,
html_content: args.html_content,
text_content: args.text_content,
categories: args.categories,
});
},
},
};
break;
case 'keap_list_email_templates':
result = await client.get('/emails/templates', { limit: args.limit });
break;
case 'keap_opt_in_contact':
result = await client.post('/emails/optIn', {
email: args.email,
opt_in_reason: args.opt_in_reason,
});
break;
case 'keap_opt_out_contact':
result = await client.post('/emails/optOut', {
email: args.email,
});
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -0,0 +1,100 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function createFilesTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_upload_file',
description: 'Upload a file to Keap',
inputSchema: {
type: 'object',
properties: {
file_name: { type: 'string', description: 'Name of the file', required: true },
file_data: { type: 'string', description: 'Base64 encoded file data', required: true },
contact_id: { type: 'number', description: 'Associate with contact ID' },
is_public: { type: 'boolean', description: 'Make file publicly accessible', default: false },
},
required: ['file_name', 'file_data'],
},
},
{
name: 'keap_get_file',
description: 'Retrieve file metadata by ID',
inputSchema: {
type: 'object',
properties: {
file_id: { type: 'number', description: 'File ID', required: true },
},
required: ['file_id'],
},
},
{
name: 'keap_delete_file',
description: 'Delete a file from Keap',
inputSchema: {
type: 'object',
properties: {
file_id: { type: 'number', description: 'File ID to delete', required: true },
},
required: ['file_id'],
},
},
{
name: 'keap_list_files',
description: 'List files with filtering',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact' },
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
},
},
},
];
}
export async function handleFilesTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_upload_file':
result = await client.post('/files', {
file_name: args.file_name,
file_data: args.file_data,
contact_id: args.contact_id,
is_public: args.is_public || false,
});
break;
case 'keap_get_file':
result = await client.get(`/files/${args.file_id}`);
break;
case 'keap_delete_file':
await client.delete(`/files/${args.file_id}`);
result = { success: true, message: 'File deleted successfully' };
break;
case 'keap_list_files':
result = await client.get('/files', args);
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -0,0 +1,135 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function createNotesTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_note',
description: 'Create a note for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID', required: true },
title: { type: 'string', description: 'Note title' },
body: { type: 'string', description: 'Note content', required: true },
type: { type: 'string', description: 'Note type (Appointment, Call, Email, etc.)' },
user_id: { type: 'number', description: 'User ID who created the note' },
},
required: ['contact_id', 'body'],
},
},
{
name: 'keap_get_note',
description: 'Retrieve a note by ID',
inputSchema: {
type: 'object',
properties: {
note_id: { type: 'number', description: 'Note ID', required: true },
},
required: ['note_id'],
},
},
{
name: 'keap_update_note',
description: 'Update an existing note',
inputSchema: {
type: 'object',
properties: {
note_id: { type: 'number', description: 'Note ID', required: true },
title: { type: 'string', description: 'Note title' },
body: { type: 'string', description: 'Note content' },
type: { type: 'string', description: 'Note type' },
},
required: ['note_id'],
},
},
{
name: 'keap_delete_note',
description: 'Delete a note',
inputSchema: {
type: 'object',
properties: {
note_id: { type: 'number', description: 'Note ID to delete', required: true },
},
required: ['note_id'],
},
},
{
name: 'keap_list_notes',
description: 'List notes for a contact',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
user_id: { type: 'number', description: 'Filter by user who created notes' },
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
},
},
},
{
name: 'keap_get_note_model',
description: 'Get the note model schema',
inputSchema: {
type: 'object',
properties: {},
},
},
];
}
export async function handleNotesTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_note':
result = await client.post('/notes', {
contact_id: args.contact_id,
title: args.title,
body: args.body,
type: args.type,
user_id: args.user_id,
});
break;
case 'keap_get_note':
result = await client.get(`/notes/${args.note_id}`);
break;
case 'keap_update_note': {
const { note_id, ...updateData } = args;
result = await client.patch(`/notes/${note_id}`, updateData);
break;
}
case 'keap_delete_note':
await client.delete(`/notes/${args.note_id}`);
result = { success: true, message: 'Note deleted successfully' };
break;
case 'keap_list_notes':
result = await client.get('/notes', args);
break;
case 'keap_get_note_model':
result = await client.get('/notes/model');
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -0,0 +1,206 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function createOpportunitiesTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_opportunity',
description: 'Create a new sales opportunity/deal in Keap',
inputSchema: {
type: 'object',
properties: {
opportunity_title: { type: 'string', description: 'Deal/opportunity title', required: true },
contact_id: { type: 'number', description: 'Contact ID associated with this opportunity', required: true },
stage_id: { type: 'number', description: 'Pipeline stage ID', required: true },
user_id: { type: 'number', description: 'User ID (owner of opportunity)' },
estimated_close_date: { type: 'string', description: 'Estimated close date (ISO format)' },
projected_revenue_low: { type: 'number', description: 'Low revenue estimate' },
projected_revenue_high: { type: 'number', description: 'High revenue estimate' },
opportunity_notes: { type: 'string', description: 'Notes about this opportunity' },
next_action_notes: { type: 'string', description: 'Next action notes' },
next_action_date: { type: 'string', description: 'Next action date (ISO format)' },
custom_fields: { type: 'array', items: { type: 'object' }, description: 'Custom fields' },
},
required: ['opportunity_title', 'contact_id', 'stage_id'],
},
},
{
name: 'keap_get_opportunity',
description: 'Retrieve an opportunity by ID',
inputSchema: {
type: 'object',
properties: {
opportunity_id: { type: 'number', description: 'Opportunity ID', required: true },
optional_properties: { type: 'array', items: { type: 'string' }, description: 'Additional fields to include' },
},
required: ['opportunity_id'],
},
},
{
name: 'keap_update_opportunity',
description: 'Update an existing opportunity',
inputSchema: {
type: 'object',
properties: {
opportunity_id: { type: 'number', description: 'Opportunity ID', required: true },
opportunity_title: { type: 'string', description: 'Deal title' },
stage_id: { type: 'number', description: 'Pipeline stage ID' },
user_id: { type: 'number', description: 'User ID (owner)' },
estimated_close_date: { type: 'string', description: 'Estimated close date' },
projected_revenue_low: { type: 'number', description: 'Low revenue estimate' },
projected_revenue_high: { type: 'number', description: 'High revenue estimate' },
opportunity_notes: { type: 'string', description: 'Notes' },
next_action_notes: { type: 'string', description: 'Next action notes' },
next_action_date: { type: 'string', description: 'Next action date' },
},
required: ['opportunity_id'],
},
},
{
name: 'keap_delete_opportunity',
description: 'Delete an opportunity',
inputSchema: {
type: 'object',
properties: {
opportunity_id: { type: 'number', description: 'Opportunity ID to delete', required: true },
},
required: ['opportunity_id'],
},
},
{
name: 'keap_list_opportunities',
description: 'List opportunities with filtering and pagination',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
user_id: { type: 'number', description: 'Filter by user ID' },
stage_id: { type: 'number', description: 'Filter by pipeline stage' },
contact_id: { type: 'number', description: 'Filter by contact ID' },
search_term: { type: 'string', description: 'Search in title/notes' },
order: { type: 'string', description: 'Order by field' },
},
},
},
{
name: 'keap_list_opportunity_stage_pipeline',
description: 'List all pipeline stages for opportunities',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'keap_get_opportunity_stage_pipeline',
description: 'Get details of a specific pipeline stage',
inputSchema: {
type: 'object',
properties: {
stage_id: { type: 'number', description: 'Stage ID', required: true },
},
required: ['stage_id'],
},
},
{
name: 'keap_update_opportunity_stage',
description: 'Move an opportunity to a different pipeline stage',
inputSchema: {
type: 'object',
properties: {
opportunity_id: { type: 'number', description: 'Opportunity ID', required: true },
stage_id: { type: 'number', description: 'New stage ID', required: true },
move_to_stage_reason: { type: 'string', description: 'Reason for stage change' },
},
required: ['opportunity_id', 'stage_id'],
},
},
{
name: 'keap_get_opportunity_model',
description: 'Get the opportunity model schema with custom fields',
inputSchema: {
type: 'object',
properties: {},
},
},
];
}
export async function handleOpportunitiesTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_opportunity':
result = await client.post('/opportunities', {
opportunity_title: args.opportunity_title,
contact: { id: args.contact_id },
stage: { id: args.stage_id },
user: args.user_id,
estimated_close_date: args.estimated_close_date,
projected_revenue_low: args.projected_revenue_low,
projected_revenue_high: args.projected_revenue_high,
opportunity_notes: args.opportunity_notes,
next_action_notes: args.next_action_notes,
next_action_date: args.next_action_date,
custom_fields: args.custom_fields,
});
break;
case 'keap_get_opportunity':
result = await client.get(`/opportunities/${args.opportunity_id}`, {
optional_properties: args.optional_properties?.join(','),
});
break;
case 'keap_update_opportunity': {
const { opportunity_id, ...updateData } = args;
result = await client.patch(`/opportunities/${opportunity_id}`, updateData);
break;
}
case 'keap_delete_opportunity':
await client.delete(`/opportunities/${args.opportunity_id}`);
result = { success: true, message: 'Opportunity deleted successfully' };
break;
case 'keap_list_opportunities':
result = await client.get('/opportunities', args);
break;
case 'keap_list_opportunity_stage_pipeline':
result = await client.get('/opportunity/stage_pipeline');
break;
case 'keap_get_opportunity_stage_pipeline':
result = await client.get(`/opportunity/stage_pipeline/${args.stage_id}`);
break;
case 'keap_update_opportunity_stage':
result = await client.patch(`/opportunities/${args.opportunity_id}`, {
stage: { id: args.stage_id },
move_to_stage_reason: args.move_to_stage_reason,
});
break;
case 'keap_get_opportunity_model':
result = await client.get('/opportunities/model');
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,119 +0,0 @@
import { KeapClient } from '../clients/keap.js';
export function registerOrdersTools(client: KeapClient) {
return {
keap_list_orders: {
description: 'List all orders',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact ID' },
since: { type: 'string', description: 'Start date filter (ISO 8601)' },
until: { type: 'string', description: 'End date filter (ISO 8601)' },
paid: { type: 'boolean', description: 'Filter by paid status' },
limit: { type: 'number', description: 'Maximum results' },
},
},
handler: async (args: any) => {
const orders = await client.listOrders(args);
return { orders, count: orders.length };
},
},
keap_get_order: {
description: 'Get a specific order by ID',
inputSchema: {
type: 'object',
properties: {
order_id: { type: 'number', description: 'Order ID' },
},
required: ['order_id'],
},
handler: async (args: any) => {
return await client.getOrder(args.order_id);
},
},
keap_create_order: {
description: 'Create a new order',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
title: { type: 'string', description: 'Order title' },
order_items: {
type: 'array',
items: {
type: 'object',
properties: {
product_id: { type: 'number' },
description: { type: 'string' },
quantity: { type: 'number' },
price: { type: 'number' },
},
},
description: 'Order items',
},
notes: { type: 'string', description: 'Order notes' },
},
required: ['contact_id', 'title'],
},
handler: async (args: any) => {
return await client.createOrder({
contact_id: args.contact_id,
title: args.title,
order_items: args.order_items,
notes: args.notes,
});
},
},
keap_list_transactions: {
description: 'List all transactions',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact ID' },
order_id: { type: 'number', description: 'Filter by order ID' },
since: { type: 'string', description: 'Start date filter (ISO 8601)' },
until: { type: 'string', description: 'End date filter (ISO 8601)' },
limit: { type: 'number', description: 'Maximum results' },
},
},
handler: async (args: any) => {
const transactions = await client.get<any>('/transactions', args);
return { transactions: transactions.transactions || [], count: transactions.count || 0 };
},
},
keap_create_transaction: {
description: 'Create a new transaction/payment',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Contact ID' },
order_id: { type: 'number', description: 'Order ID' },
amount: { type: 'number', description: 'Transaction amount' },
currency: { type: 'string', description: 'Currency code (e.g., USD)' },
gateway: { type: 'string', description: 'Payment gateway' },
type: { type: 'string', enum: ['Sale', 'Refund', 'Chargeback'], description: 'Transaction type' },
test: { type: 'boolean', description: 'Test transaction' },
},
required: ['contact_id', 'amount'],
},
handler: async (args: any) => {
return await client.post('/transactions', {
contact_id: args.contact_id,
order_id: args.order_id,
amount: args.amount,
currency: args.currency || 'USD',
gateway: args.gateway,
type: args.type || 'Sale',
test: args.test || false,
transaction_date: new Date().toISOString(),
status: 'Completed',
});
},
},
};
}

View File

@ -1,118 +0,0 @@
import { KeapClient } from '../clients/keap.js';
export function registerProductsTools(client: KeapClient) {
return {
keap_list_products: {
description: 'List all products',
inputSchema: {
type: 'object',
properties: {
active: { type: 'boolean', description: 'Filter by active status' },
limit: { type: 'number', description: 'Maximum results' },
},
},
handler: async (args: any) => {
const products = await client.listProducts(args);
return { products, count: products.length };
},
},
keap_get_product: {
description: 'Get a specific product by ID',
inputSchema: {
type: 'object',
properties: {
product_id: { type: 'number', description: 'Product ID' },
},
required: ['product_id'],
},
handler: async (args: any) => {
return await client.getProduct(args.product_id);
},
},
keap_create_product: {
description: 'Create a new product',
inputSchema: {
type: 'object',
properties: {
product_name: { type: 'string', description: 'Product name' },
product_short_desc: { type: 'string', description: 'Short description' },
product_desc: { type: 'string', description: 'Full description' },
sku: { type: 'string', description: 'SKU code' },
price: { type: 'number', description: 'Product price' },
status: { type: 'string', enum: ['Active', 'Inactive'], description: 'Product status' },
},
required: ['product_name'],
},
handler: async (args: any) => {
return await client.createProduct({
product_name: args.product_name,
product_short_desc: args.product_short_desc,
product_desc: args.product_desc,
sku: args.sku,
product_price: args.price,
status: args.status || 'Active',
});
},
},
keap_update_product: {
description: 'Update an existing product',
inputSchema: {
type: 'object',
properties: {
product_id: { type: 'number', description: 'Product ID' },
product_name: { type: 'string', description: 'Product name' },
product_short_desc: { type: 'string', description: 'Short description' },
product_desc: { type: 'string', description: 'Full description' },
sku: { type: 'string', description: 'SKU code' },
price: { type: 'number', description: 'Product price' },
status: { type: 'string', enum: ['Active', 'Inactive'], description: 'Product status' },
},
required: ['product_id'],
},
handler: async (args: any) => {
const data: any = {};
if (args.product_name !== undefined) data.product_name = args.product_name;
if (args.product_short_desc !== undefined) data.product_short_desc = args.product_short_desc;
if (args.product_desc !== undefined) data.product_desc = args.product_desc;
if (args.sku !== undefined) data.sku = args.sku;
if (args.price !== undefined) data.product_price = args.price;
if (args.status !== undefined) data.status = args.status;
return await client.updateProduct(args.product_id, data);
},
},
keap_delete_product: {
description: 'Delete a product',
inputSchema: {
type: 'object',
properties: {
product_id: { type: 'number', description: 'Product ID' },
},
required: ['product_id'],
},
handler: async (args: any) => {
await client.deleteProduct(args.product_id);
return { success: true, message: `Product ${args.product_id} deleted` };
},
},
keap_list_subscriptions: {
description: 'List all subscription plans',
inputSchema: {
type: 'object',
properties: {
product_id: { type: 'number', description: 'Filter by product ID' },
limit: { type: 'number', description: 'Maximum results' },
},
},
handler: async (args: any) => {
const subscriptions = await client.get<any>('/subscriptions', args);
return { subscriptions: subscriptions.subscriptions || [] };
},
},
};
}

View File

@ -1,119 +0,0 @@
import { KeapClient } from '../clients/keap.js';
export function registerReportsTools(client: KeapClient) {
return {
keap_contact_growth_report: {
description: 'Get contact growth statistics',
inputSchema: {
type: 'object',
properties: {
since: { type: 'string', description: 'Start date (ISO 8601)' },
until: { type: 'string', description: 'End date (ISO 8601)' },
},
required: ['since', 'until'],
},
handler: async (args: any) => {
const contacts = await client.listContacts({
since: args.since,
until: args.until,
});
// Group by date
const dailyGrowth: Record<string, number> = {};
contacts.forEach(contact => {
const date = contact.date_created?.split('T')[0];
if (date) {
dailyGrowth[date] = (dailyGrowth[date] || 0) + 1;
}
});
const report = Object.keys(dailyGrowth)
.sort()
.map(date => ({
date,
new_contacts: dailyGrowth[date],
total_contacts: 0, // Would need cumulative calculation
}));
return { report, total_new_contacts: contacts.length };
},
},
keap_revenue_report: {
description: 'Get revenue statistics',
inputSchema: {
type: 'object',
properties: {
since: { type: 'string', description: 'Start date (ISO 8601)' },
until: { type: 'string', description: 'End date (ISO 8601)' },
},
required: ['since', 'until'],
},
handler: async (args: any) => {
const transactions = await client.get<any>('/transactions', {
since: args.since,
until: args.until,
});
const txList = transactions.transactions || [];
const totalRevenue = txList
.filter((tx: any) => tx.type === 'Sale' && tx.status === 'Completed')
.reduce((sum: number, tx: any) => sum + (tx.amount || 0), 0);
const totalRefunds = txList
.filter((tx: any) => tx.type === 'Refund')
.reduce((sum: number, tx: any) => sum + (tx.amount || 0), 0);
const salesCount = txList.filter((tx: any) => tx.type === 'Sale').length;
const avgOrderValue = salesCount > 0 ? totalRevenue / salesCount : 0;
return {
total_revenue: totalRevenue,
total_refunds: totalRefunds,
net_revenue: totalRevenue - totalRefunds,
transactions: salesCount,
average_order_value: avgOrderValue,
};
},
},
keap_campaign_performance_report: {
description: 'Get campaign performance statistics',
inputSchema: {
type: 'object',
properties: {
campaign_id: { type: 'number', description: 'Campaign ID' },
},
required: ['campaign_id'],
},
handler: async (args: any) => {
const campaign = await client.getCampaign(args.campaign_id);
// Get emails sent for this campaign
const emails = await client.listEmails({
campaign_id: args.campaign_id,
});
const totalSent = emails.length;
const opened = emails.filter(e => e.opened).length;
const clicked = emails.filter(e => e.clicked).length;
const bounced = emails.filter(e => e.bounced).length;
const openRate = totalSent > 0 ? (opened / totalSent) * 100 : 0;
const clickRate = opened > 0 ? (clicked / opened) * 100 : 0;
return {
campaign_id: args.campaign_id,
campaign_name: campaign.name,
total_sent: totalSent,
opened,
clicked,
bounced,
open_rate: Math.round(openRate * 100) / 100,
click_rate: Math.round(clickRate * 100) / 100,
};
},
},
};
}

View File

@ -0,0 +1,105 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function createSettingsTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_get_account_profile',
description: 'Get the account profile information',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'keap_update_account_profile',
description: 'Update account profile settings',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Business name' },
email: { type: 'string', description: 'Business email' },
phone: { type: 'string', description: 'Business phone' },
address: { type: 'string', description: 'Business address' },
website: { type: 'string', description: 'Business website' },
time_zone: { type: 'string', description: 'Time zone' },
currency_code: { type: 'string', description: 'Currency code (e.g., USD)' },
language_tag: { type: 'string', description: 'Language tag (e.g., en-US)' },
},
},
},
{
name: 'keap_list_users',
description: 'List all users in the account',
inputSchema: {
type: 'object',
properties: {
include_inactive: { type: 'boolean', description: 'Include inactive users', default: false },
limit: { type: 'number', description: 'Results per page', default: 50 },
},
},
},
{
name: 'keap_get_application_configuration',
description: 'Get application configuration settings',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'keap_list_custom_fields',
description: 'List all custom fields for a given entity type',
inputSchema: {
type: 'object',
properties: {
entity_type: { type: 'string', description: 'Entity type (Contact, Company, Opportunity, etc.)', required: true },
},
required: ['entity_type'],
},
},
];
}
export async function handleSettingsTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_get_account_profile':
result = await client.get('/account/profile');
break;
case 'keap_update_account_profile':
result = await client.patch('/account/profile', args);
break;
case 'keap_list_users':
result = await client.get('/users', args);
break;
case 'keap_get_application_configuration':
result = await client.get('/setting/application/configuration');
break;
case 'keap_list_custom_fields':
result = await client.get(`/customFields/${args.entity_type}`);
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,108 +1,113 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function registerTagsTools(client: KeapClient) {
return {
keap_list_tags: {
description: 'List all tags',
export function createTagsTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_tag',
description: 'Create a new tag in Keap',
inputSchema: {
type: 'object',
properties: {
category: { type: 'string', description: 'Filter by category' },
limit: { type: 'number', description: 'Maximum results' },
},
},
handler: async (args: any) => {
const tags = await client.listTags(args);
return { tags, count: tags.length };
},
},
keap_get_tag: {
description: 'Get a specific tag by ID',
inputSchema: {
type: 'object',
properties: {
tag_id: { type: 'number', description: 'Tag ID' },
},
required: ['tag_id'],
},
handler: async (args: any) => {
return await client.getTag(args.tag_id);
},
},
keap_create_tag: {
description: 'Create a new tag',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Tag name' },
name: { type: 'string', description: 'Tag name', required: true },
description: { type: 'string', description: 'Tag description' },
category_id: { type: 'number', description: 'Tag category ID' },
},
required: ['name'],
},
handler: async (args: any) => {
return await client.createTag({
},
{
name: 'keap_get_tag',
description: 'Retrieve a tag by ID',
inputSchema: {
type: 'object',
properties: {
tag_id: { type: 'number', description: 'Tag ID', required: true },
},
required: ['tag_id'],
},
},
{
name: 'keap_list_tags',
description: 'List all tags with pagination',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
category: { type: 'string', description: 'Filter by category name' },
},
},
},
{
name: 'keap_create_tag_category',
description: 'Create a tag category',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Category name', required: true },
description: { type: 'string', description: 'Category description' },
},
required: ['name'],
},
},
{
name: 'keap_list_tag_categories',
description: 'List all tag categories',
inputSchema: {
type: 'object',
properties: {},
},
},
];
}
export async function handleTagsTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_tag':
result = await client.post('/tags', {
name: args.name,
description: args.description,
category: args.category_id ? { id: args.category_id } : undefined,
});
},
},
break;
keap_update_tag: {
description: 'Update an existing tag',
inputSchema: {
type: 'object',
properties: {
tag_id: { type: 'number', description: 'Tag ID' },
name: { type: 'string', description: 'Tag name' },
description: { type: 'string', description: 'Tag description' },
},
required: ['tag_id'],
},
handler: async (args: any) => {
const data: any = {};
if (args.name !== undefined) data.name = args.name;
if (args.description !== undefined) data.description = args.description;
case 'keap_get_tag':
result = await client.get(`/tags/${args.tag_id}`);
break;
return await client.patch(`/tags/${args.tag_id}`, data);
},
},
case 'keap_list_tags':
result = await client.get('/tags', args);
break;
keap_delete_tag: {
description: 'Delete a tag',
inputSchema: {
type: 'object',
properties: {
tag_id: { type: 'number', description: 'Tag ID' },
},
required: ['tag_id'],
},
handler: async (args: any) => {
await client.deleteTag(args.tag_id);
return { success: true, message: `Tag ${args.tag_id} deleted` };
},
},
keap_list_contacts_by_tag: {
description: 'List all contacts with a specific tag',
inputSchema: {
type: 'object',
properties: {
tag_id: { type: 'number', description: 'Tag ID' },
limit: { type: 'number', description: 'Maximum results' },
},
required: ['tag_id'],
},
handler: async (args: any) => {
const contacts = await client.listContacts({
tag_id: args.tag_id,
limit: args.limit || 1000,
case 'keap_create_tag_category':
result = await client.post('/tags/categories', {
name: args.name,
description: args.description,
});
return { contacts, count: contacts.length };
},
},
};
break;
case 'keap_list_tag_categories':
result = await client.get('/tags/categories');
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -1,126 +1,180 @@
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { KeapClient } from '../clients/keap.js';
export function registerTasksTools(client: KeapClient) {
return {
keap_list_tasks: {
description: 'List all tasks',
export function createTasksTools(client: KeapClient): Tool[] {
return [
{
name: 'keap_create_task',
description: 'Create a new task in Keap',
inputSchema: {
type: 'object',
properties: {
contact_id: { type: 'number', description: 'Filter by contact ID' },
user_id: { type: 'number', description: 'Filter by assigned user ID' },
completed: { type: 'boolean', description: 'Filter by completion status' },
limit: { type: 'number', description: 'Maximum results' },
order: { type: 'string', description: 'Sort order' },
},
},
handler: async (args: any) => {
const tasks = await client.listTasks(args);
return { tasks, count: tasks.length };
},
},
keap_get_task: {
description: 'Get a specific task by ID',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'number', description: 'Task ID' },
},
required: ['task_id'],
},
handler: async (args: any) => {
return await client.getTask(args.task_id);
},
},
keap_create_task: {
description: 'Create a new task',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Task title' },
title: { type: 'string', description: 'Task title', required: true },
description: { type: 'string', description: 'Task description' },
contact_id: { type: 'number', description: 'Contact ID' },
contact_id: { type: 'number', description: 'Associated contact ID' },
due_date: { type: 'string', description: 'Due date (ISO format)' },
remind_time: { type: 'number', description: 'Reminder time in minutes before due date' },
user_id: { type: 'number', description: 'Assigned user ID' },
due_date: { type: 'string', description: 'Due date (ISO 8601)' },
priority: { type: 'number', description: 'Priority level (1-5)' },
priority: { type: 'number', description: 'Priority (1-5)', default: 3 },
type: { type: 'string', description: 'Task type' },
},
required: ['title'],
},
handler: async (args: any) => {
return await client.createTask({
title: args.title,
description: args.description,
contact: args.contact_id ? { id: args.contact_id } : undefined,
user_id: args.user_id,
due_date: args.due_date,
priority: args.priority,
type: args.type,
completed: false,
});
},
{
name: 'keap_get_task',
description: 'Retrieve a task by ID',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'number', description: 'Task ID', required: true },
},
required: ['task_id'],
},
},
keap_update_task: {
{
name: 'keap_update_task',
description: 'Update an existing task',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'number', description: 'Task ID' },
task_id: { type: 'number', description: 'Task ID', required: true },
title: { type: 'string', description: 'Task title' },
description: { type: 'string', description: 'Task description' },
user_id: { type: 'number', description: 'Assigned user ID' },
due_date: { type: 'string', description: 'Due date (ISO 8601)' },
priority: { type: 'number', description: 'Priority level (1-5)' },
completed: { type: 'boolean', description: 'Completion status' },
due_date: { type: 'string', description: 'Due date' },
completed: { type: 'boolean', description: 'Mark as completed' },
priority: { type: 'number', description: 'Priority' },
},
required: ['task_id'],
},
handler: async (args: any) => {
const data: any = {};
if (args.title !== undefined) data.title = args.title;
if (args.description !== undefined) data.description = args.description;
if (args.user_id !== undefined) data.user_id = args.user_id;
if (args.due_date !== undefined) data.due_date = args.due_date;
if (args.priority !== undefined) data.priority = args.priority;
if (args.completed !== undefined) data.completed = args.completed;
return await client.updateTask(args.task_id, data);
},
},
keap_delete_task: {
{
name: 'keap_delete_task',
description: 'Delete a task',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'number', description: 'Task ID' },
task_id: { type: 'number', description: 'Task ID to delete', required: true },
},
required: ['task_id'],
},
handler: async (args: any) => {
await client.deleteTask(args.task_id);
return { success: true, message: `Task ${args.task_id} deleted` };
},
{
name: 'keap_list_tasks',
description: 'List tasks with filtering and pagination',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Results per page', default: 50 },
offset: { type: 'number', description: 'Pagination offset', default: 0 },
user_id: { type: 'number', description: 'Filter by assigned user' },
contact_id: { type: 'number', description: 'Filter by contact' },
completed: { type: 'boolean', description: 'Filter by completion status' },
since: { type: 'string', description: 'Tasks created after this date' },
until: { type: 'string', description: 'Tasks created before this date' },
},
},
},
keap_complete_task: {
{
name: 'keap_search_tasks',
description: 'Search tasks by title, description, or other criteria',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Max results', default: 50 },
},
},
},
{
name: 'keap_complete_task',
description: 'Mark a task as completed',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'number', description: 'Task ID' },
task_id: { type: 'number', description: 'Task ID', required: true },
completion_date: { type: 'string', description: 'Completion date (ISO format)' },
},
required: ['task_id'],
},
handler: async (args: any) => {
return await client.updateTask(args.task_id, {
completed: true,
completion_date: new Date().toISOString(),
});
},
{
name: 'keap_get_task_model',
description: 'Get the task model schema',
inputSchema: {
type: 'object',
properties: {},
},
},
};
];
}
export async function handleTasksTool(
toolName: string,
args: any,
client: KeapClient
): Promise<{ content: Array<{ type: string; text: string }> }> {
let result: any;
try {
switch (toolName) {
case 'keap_create_task':
result = await client.post('/tasks', {
title: args.title,
description: args.description,
contact: args.contact_id ? { id: args.contact_id } : undefined,
due_date: args.due_date,
remind_time: args.remind_time,
user_id: args.user_id,
priority: args.priority || 3,
type: args.type,
});
break;
case 'keap_get_task':
result = await client.get(`/tasks/${args.task_id}`);
break;
case 'keap_update_task': {
const { task_id, ...updateData } = args;
result = await client.patch(`/tasks/${task_id}`, updateData);
break;
}
case 'keap_delete_task':
await client.delete(`/tasks/${args.task_id}`);
result = { success: true, message: 'Task deleted successfully' };
break;
case 'keap_list_tasks':
result = await client.get('/tasks', args);
break;
case 'keap_search_tasks':
result = await client.get('/tasks/search', args);
break;
case 'keap_complete_task':
result = await client.patch(`/tasks/${args.task_id}`, {
completed: true,
completion_date: args.completion_date || new Date().toISOString(),
});
break;
case 'keap_get_task_model':
result = await client.get('/tasks/model');
break;
default:
throw new Error(`Unknown tool: ${toolName}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
};
}
}

View File

@ -0,0 +1,434 @@
// Keap API TypeScript Interfaces
export interface KeapContact {
id?: number;
given_name?: string;
family_name?: string;
email_addresses?: EmailAddress[];
phone_numbers?: PhoneNumber[];
addresses?: Address[];
company?: Company;
custom_fields?: CustomField[];
tag_ids?: number[];
opt_in_reason?: string;
owner_id?: number;
lead_source_id?: number;
date_created?: string;
last_updated?: string;
score_value?: number;
preferred_name?: string;
preferred_locale?: string;
time_zone?: string;
website?: string;
job_title?: string;
anniversary?: string;
birthday?: string;
suffix?: string;
middle_name?: string;
prefix?: string;
spouse_name?: string;
social_accounts?: SocialAccount[];
fax_numbers?: FaxNumber[];
contact_type?: string;
}
export interface EmailAddress {
email: string;
field?: string;
}
export interface PhoneNumber {
number: string;
field?: string;
extension?: string;
type?: string;
}
export interface Address {
line1?: string;
line2?: string;
locality?: string;
region?: string;
postal_code?: string;
country_code?: string;
zip_code?: string;
zip_four?: string;
field?: string;
}
export interface Company {
id?: number;
company_name?: string;
}
export interface CustomField {
id: number;
content?: any;
}
export interface SocialAccount {
name?: string;
type?: string;
}
export interface FaxNumber {
number?: string;
field?: string;
}
export interface KeapTag {
id?: number;
name?: string;
description?: string;
category?: TagCategory;
}
export interface TagCategory {
id?: number;
name?: string;
description?: string;
}
export interface KeapOpportunity {
id?: number;
opportunity_title?: string;
contact?: ContactReference;
stage?: StageReference;
user?: number;
date_created?: string;
estimated_close_date?: string;
opportunity_notes?: string;
next_action_notes?: string;
next_action_date?: string;
last_updated?: string;
projected_revenue_low?: number;
projected_revenue_high?: number;
custom_fields?: CustomField[];
include_in_forecast?: number;
affiliate_id?: number;
}
export interface ContactReference {
id: number;
}
export interface StageReference {
id: number;
name?: string;
}
export interface KeapTask {
id?: number;
title?: string;
description?: string;
contact?: ContactReference;
due_date?: string;
remind_time?: number;
completed?: boolean;
completion_date?: string;
type?: string;
priority?: number;
user_id?: number;
creation_date?: string;
modification_date?: string;
url?: string;
}
export interface KeapAppointment {
id?: number;
title?: string;
description?: string;
start_date?: string;
end_date?: string;
contact?: ContactReference;
location?: string;
remind_time?: number;
all_day?: boolean;
user?: number;
}
export interface KeapCampaign {
id?: number;
name?: string;
description?: string;
created_by_global_id?: number;
error_message?: string;
goals?: CampaignGoal[];
time_zone?: string;
published_date?: string;
published_status?: boolean;
published_by_user_id?: number;
}
export interface CampaignGoal {
id?: number;
name?: string;
historical_contact_count?: number;
historical_contact_count_completed?: number;
}
export interface KeapProduct {
id?: number;
product_name?: string;
product_short_desc?: string;
product_desc?: string;
product_price?: number;
sku?: string;
subscription_only?: boolean;
subscription_plans?: SubscriptionPlan[];
url?: string;
status?: number;
}
export interface SubscriptionPlan {
id?: number;
cycle?: string;
frequency?: number;
number_of_cycles?: number;
plan_price?: number;
subscription_plan_index?: number;
subscription_plan_name?: string;
}
export interface KeapOrder {
id?: number;
contact_id?: number;
order_date?: string;
order_title?: string;
order_type?: string;
order_items?: OrderItem[];
status?: string;
total?: number;
total_paid?: number;
total_due?: number;
shipping_information?: ShippingInformation;
allow_payment?: boolean;
lead_affiliate_id?: number;
sales_affiliate_id?: number;
promo_codes?: string[];
}
export interface OrderItem {
id?: number;
product_id?: number;
quantity?: number;
price?: number;
discount?: number;
product_name?: string;
description?: string;
notes?: string;
type?: string;
subscription_plan_id?: number;
}
export interface ShippingInformation {
first_name?: string;
last_name?: string;
company?: string;
street1?: string;
street2?: string;
city?: string;
state?: string;
postal_code?: string;
country?: string;
phone?: string;
method?: string;
}
export interface KeapTransaction {
id?: number;
contact_id?: number;
amount?: number;
currency?: string;
gateway?: string;
gateway_account_name?: string;
order_ids?: number[];
orders?: OrderReference[];
paymentDate?: string;
status?: string;
test?: boolean;
type?: string;
}
export interface OrderReference {
id?: number;
}
export interface KeapSubscription {
id?: number;
active?: boolean;
billing_amount?: number;
billing_cycle?: string;
billing_frequency?: number;
contact_id?: number;
credit_card_id?: number;
end_date?: string;
next_bill_date?: string;
payment_gateway_id?: string;
product_id?: number;
quantity?: number;
start_date?: string;
subscription_plan_id?: number;
}
export interface KeapNote {
id?: number;
contact_id?: number;
user_id?: number;
date_created?: string;
last_updated?: string;
body?: string;
title?: string;
type?: string;
}
export interface KeapEmail {
id?: number;
sent_from_address?: string;
sent_to_address?: string;
sent_from_reply_address?: string;
subject?: string;
html_content?: string;
text_content?: string;
attachments?: Attachment[];
contacts?: number[];
sent_date?: string;
received_date?: string;
opened_date?: string;
clicked_date?: string;
}
export interface Attachment {
file_name?: string;
file_data?: string;
}
export interface KeapFile {
id?: number;
file_name?: string;
file_box_id?: number;
contact_id?: number;
file_size?: number;
file_association?: string;
file_data?: string;
file_url?: string;
is_public?: boolean;
}
export interface KeapAffiliate {
id?: number;
code?: string;
contact_id?: number;
name?: string;
parent_id?: number;
status?: number;
track_leads_for?: number;
}
export interface KeapCommission {
id?: number;
affiliate_id?: number;
amount?: number;
contact_id?: number;
date_earned?: string;
invoice_id?: number;
order_id?: number;
product_id?: number;
}
export interface KeapAccount {
address?: string;
business_goals?: string[];
business_primary_color?: string;
business_secondary_color?: string;
business_type?: string;
currency_code?: string;
email?: string;
language_tag?: string;
logo_url?: string;
name?: string;
phone?: string;
phone_ext?: string;
time_zone?: string;
website?: string;
}
export interface KeapUser {
id?: number;
email_address?: string;
family_name?: string;
given_name?: string;
infusionsoft_id?: string;
partner?: boolean;
status?: string;
}
export interface KeapHook {
key?: string;
eventKey?: string;
hookUrl?: string;
status?: string;
}
export interface KeapPipeline {
id?: number;
name?: string;
stages?: PipelineStage[];
}
export interface PipelineStage {
id?: number;
name?: string;
details?: StageDetails;
target_num_days?: number;
target_revenue?: number;
}
export interface StageDetails {
check_list_items?: CheckListItem[];
}
export interface CheckListItem {
description?: string;
required?: boolean;
}
export interface KeapCreditCard {
id?: number;
card_number?: string;
card_type?: string;
expiration_month?: string;
expiration_year?: string;
name_on_card?: string;
email?: string;
status?: string;
}
export interface ApiResponse<T> {
data?: T;
count?: number;
next?: string;
previous?: string;
error?: ApiError;
}
export interface ApiError {
message?: string;
code?: string;
details?: any;
}
export interface PaginationParams {
limit?: number;
offset?: number;
}
export interface SearchParams extends PaginationParams {
email?: string;
given_name?: string;
family_name?: string;
order?: string;
order_direction?: string;
since?: string;
until?: string;
}

View File

@ -0,0 +1,118 @@
import React, { useState } from 'react';
import { Zap, Play, Pause, Users, TrendingUp } from 'lucide-react';
export default function AutomationDashboard() {
const [automations] = useState([
{ id: 1, name: 'Welcome Sequence', status: 'Active', contacts: 1234, completed: 987, success_rate: 79.9 },
{ id: 2, name: 'Lead Nurture', status: 'Active', contacts: 567, completed: 234, success_rate: 41.3 },
{ id: 3, name: 'Re-engagement', status: 'Active', contacts: 890, completed: 456, success_rate: 51.2 },
{ id: 4, name: 'Onboarding', status: 'Paused', contacts: 345, completed: 289, success_rate: 83.8 },
{ id: 5, name: 'Upsell Campaign', status: 'Active', contacts: 156, completed: 89, success_rate: 57.1 },
]);
return (
<div className="min-h-screen bg-gray-900 text-gray-100 p-6">
<div className="max-w-6xl mx-auto">
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold">Automation Dashboard</h1>
<button className="px-4 py-2 bg-blue-600 rounded-lg hover:bg-blue-700 transition">
+ Create Automation
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<StatCard
icon={<Zap />}
label="Active Automations"
value={automations.filter(a => a.status === 'Active').length}
color="blue"
/>
<StatCard
icon={<Users />}
label="Total Contacts"
value={automations.reduce((sum, a) => sum + a.contacts, 0)}
color="green"
/>
<StatCard
icon={<TrendingUp />}
label="Avg Success Rate"
value={`${(automations.reduce((sum, a) => sum + a.success_rate, 0) / automations.length).toFixed(1)}%`}
color="purple"
/>
</div>
<div className="space-y-4">
{automations.map(automation => (
<div key={automation.id} className="bg-gray-800 rounded-lg p-6 hover:bg-gray-750 transition">
<div className="flex justify-between items-start mb-4">
<div className="flex items-start gap-4">
<div className={`p-3 rounded-lg ${
automation.status === 'Active' ? 'bg-green-600' : 'bg-gray-600'
}`}>
<Zap size={24} />
</div>
<div>
<h3 className="text-xl font-semibold mb-1">{automation.name}</h3>
<span className={`text-sm px-3 py-1 rounded-full ${
automation.status === 'Active' ? 'bg-green-600' : 'bg-gray-600'
}`}>
{automation.status}
</span>
</div>
</div>
<button className={`p-2 rounded-lg ${
automation.status === 'Active' ? 'bg-gray-700 hover:bg-gray-600' : 'bg-green-600 hover:bg-green-700'
}`}>
{automation.status === 'Active' ? <Pause size={20} /> : <Play size={20} />}
</button>
</div>
<div className="grid grid-cols-3 gap-6">
<div>
<div className="text-gray-400 text-sm mb-1">Contacts</div>
<div className="text-2xl font-bold">{automation.contacts}</div>
</div>
<div>
<div className="text-gray-400 text-sm mb-1">Completed</div>
<div className="text-2xl font-bold">{automation.completed}</div>
</div>
<div>
<div className="text-gray-400 text-sm mb-1">Success Rate</div>
<div className="text-2xl font-bold text-green-400">{automation.success_rate}%</div>
</div>
</div>
<div className="mt-4">
<div className="w-full bg-gray-700 rounded-full h-2">
<div
className="bg-green-600 h-2 rounded-full transition-all"
style={{ width: `${(automation.completed / automation.contacts) * 100}%` }}
/>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
}
function StatCard({ icon, label, value, color }: any) {
const colorClasses = {
blue: 'bg-blue-500',
green: 'bg-green-500',
purple: 'bg-purple-500',
};
return (
<div className="bg-gray-800 rounded-lg p-6">
<div className={`inline-flex p-3 rounded-lg ${colorClasses[color]} mb-4`}>
{icon}
</div>
<div className="text-3xl font-bold mb-1">{value}</div>
<div className="text-gray-400">{label}</div>
</div>
);
}

View File

@ -0,0 +1,19 @@
import { execSync } from 'child_process';
import { readdirSync, statSync } from 'fs';
import { join } from 'path';
const appsDir = './src/apps';
const apps = readdirSync(appsDir).filter(f => statSync(join(appsDir, f)).isDirectory());
console.log(`Building ${apps.length} MCP apps...`);
for (const app of apps) {
console.log(`Building ${app}...`);
try {
execSync(`vite build -c src/apps/${app}/vite.config.ts`, { stdio: 'inherit' });
} catch (error) {
console.error(`Failed to build ${app}:`, error.message);
}
}
console.log('All apps built successfully!');

View File

@ -0,0 +1,91 @@
import React, { useState } from 'react';
import { Mail, Phone, Calendar, FileText, Tag, DollarSign } from 'lucide-react';
export default function ContactTimeline() {
const [contact] = useState({
name: 'John Doe',
email: 'john@example.com',
});
const [events] = useState([
{ id: 1, type: 'email', title: 'Sent Welcome Email', date: '2025-02-14 10:30 AM', details: 'Opened: Yes, Clicked: Yes' },
{ id: 2, type: 'note', title: 'Sales Call Notes', date: '2025-02-13 2:00 PM', details: 'Discussed enterprise plan, very interested' },
{ id: 3, type: 'tag', title: 'Added Tag: VIP', date: '2025-02-13 2:05 PM', details: 'High-value prospect' },
{ id: 4, type: 'deal', title: 'Created Deal', date: '2025-02-12 11:00 AM', details: 'Enterprise Package - $50,000' },
{ id: 5, type: 'appointment', title: 'Demo Scheduled', date: '2025-02-10 9:00 AM', details: 'Product walkthrough' },
{ id: 6, type: 'phone', title: 'Phone Call', date: '2025-02-08 3:30 PM', details: 'Initial outreach, left voicemail' },
{ id: 7, type: 'email', title: 'Received Email', date: '2025-02-05 1:15 PM', details: 'Inquiry about pricing' },
]);
const getIcon = (type: string) => {
switch (type) {
case 'email': return <Mail size={20} />;
case 'phone': return <Phone size={20} />;
case 'appointment': return <Calendar size={20} />;
case 'note': return <FileText size={20} />;
case 'tag': return <Tag size={20} />;
case 'deal': return <DollarSign size={20} />;
default: return <FileText size={20} />;
}
};
const getColor = (type: string) => {
switch (type) {
case 'email': return 'blue';
case 'phone': return 'green';
case 'appointment': return 'purple';
case 'note': return 'gray';
case 'tag': return 'yellow';
case 'deal': return 'orange';
default: return 'gray';
}
};
return (
<div className="min-h-screen bg-gray-900 text-gray-100 p-6">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">{contact.name}</h1>
<div className="text-gray-400">{contact.email}</div>
</div>
<div className="relative">
{/* Timeline line */}
<div className="absolute left-6 top-0 bottom-0 w-0.5 bg-gray-700" />
<div className="space-y-6">
{events.map(event => {
const color = getColor(event.type);
const colorClasses: Record<string, string> = {
blue: 'bg-blue-600',
green: 'bg-green-600',
purple: 'bg-purple-600',
gray: 'bg-gray-600',
yellow: 'bg-yellow-600',
orange: 'bg-orange-600',
};
return (
<div key={event.id} className="relative pl-16">
{/* Icon circle */}
<div className={`absolute left-0 ${colorClasses[color]} p-3 rounded-full`}>
{getIcon(event.type)}
</div>
{/* Event card */}
<div className="bg-gray-800 rounded-lg p-4 hover:bg-gray-750 transition">
<div className="flex justify-between items-start mb-2">
<h3 className="font-semibold text-lg">{event.title}</h3>
<span className="text-sm text-gray-400">{event.date}</span>
</div>
<p className="text-gray-400">{event.details}</p>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,74 @@
import React, { useState } from 'react';
import { ShoppingCart, DollarSign, Package, TrendingUp } from 'lucide-react';
export default function OrderDashboard() {
const [stats] = useState({
total_orders: 347,
total_revenue: 142850,
avg_order_value: 411.82,
pending_orders: 23,
});
const [recentOrders] = useState([
{ id: 1, title: 'Enterprise License - Acme Corp', amount: 50000, status: 'Paid', date: '2025-02-14' },
{ id: 2, title: 'Premium Plan - TechCo', amount: 2400, status: 'Paid', date: '2025-02-14' },
{ id: 3, title: 'Consulting Package', amount: 5000, status: 'Pending', date: '2025-02-13' },
{ id: 4, title: 'Starter Plan - StartupX', amount: 348, status: 'Paid', date: '2025-02-13' },
]);
return (
<div className="min-h-screen bg-gray-900 text-gray-100 p-6">
<h1 className="text-3xl font-bold mb-8">Order Dashboard</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<StatCard icon={<ShoppingCart />} label="Total Orders" value={stats.total_orders} color="blue" />
<StatCard icon={<DollarSign />} label="Total Revenue" value={`$${(stats.total_revenue / 1000).toFixed(1)}K`} color="green" />
<StatCard icon={<TrendingUp />} label="Avg Order Value" value={`$${stats.avg_order_value.toFixed(0)}`} color="purple" />
<StatCard icon={<Package />} label="Pending" value={stats.pending_orders} color="orange" />
</div>
<div className="bg-gray-800 rounded-lg p-6">
<h2 className="text-xl font-semibold mb-4">Recent Orders</h2>
<div className="space-y-3">
{recentOrders.map(order => (
<div key={order.id} className="p-4 bg-gray-700 rounded">
<div className="flex justify-between items-start mb-2">
<div>
<h3 className="font-semibold">{order.title}</h3>
<div className="text-sm text-gray-400">{order.date}</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-green-400">${order.amount.toLocaleString()}</div>
<span className={`text-sm px-2 py-1 rounded ${
order.status === 'Paid' ? 'bg-green-600' : 'bg-yellow-600'
}`}>
{order.status}
</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
}
function StatCard({ icon, label, value, color }: any) {
const colorClasses = {
blue: 'bg-blue-500',
green: 'bg-green-500',
purple: 'bg-purple-500',
orange: 'bg-orange-500',
};
return (
<div className="bg-gray-800 rounded-lg p-6">
<div className={`inline-flex p-3 rounded-lg ${colorClasses[color]} mb-4`}>
{icon}
</div>
<div className="text-3xl font-bold mb-1">{value}</div>
<div className="text-gray-400">{label}</div>
</div>
);
}

View File

@ -0,0 +1,99 @@
import React, { useState } from 'react';
import { ShoppingCart, User, Calendar, DollarSign, Package } from 'lucide-react';
export default function OrderDetail() {
const [order] = useState({
id: 1,
title: 'Enterprise License - Acme Corp',
contact: 'John Doe',
date: '2025-02-14',
status: 'Paid',
total: 50000,
items: [
{ id: 1, description: 'Enterprise License (Annual)', quantity: 1, price: 45000 },
{ id: 2, description: 'Priority Support', quantity: 1, price: 5000 },
],
transactions: [
{ id: 1, amount: 50000, date: '2025-02-14', method: 'Credit Card', status: 'Completed' },
],
});
return (
<div className="min-h-screen bg-gray-900 text-gray-100 p-6">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">{order.title}</h1>
<div className="flex items-center gap-4">
<span className={`px-3 py-1 rounded-full text-sm ${
order.status === 'Paid' ? 'bg-green-600' : 'bg-yellow-600'
}`}>
{order.status}
</span>
<span className="text-gray-400">Order #{order.id}</span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<InfoCard icon={<User />} label="Customer" value={order.contact} />
<InfoCard icon={<Calendar />} label="Order Date" value={order.date} />
<InfoCard icon={<DollarSign />} label="Total" value={`$${order.total.toLocaleString()}`} />
</div>
<div className="bg-gray-800 rounded-lg p-6 mb-6">
<div className="flex items-center gap-2 mb-4">
<Package className="text-blue-400" />
<h2 className="text-xl font-semibold">Order Items</h2>
</div>
<div className="space-y-3">
{order.items.map(item => (
<div key={item.id} className="flex justify-between items-center p-4 bg-gray-700 rounded">
<div>
<div className="font-medium">{item.description}</div>
<div className="text-sm text-gray-400">Quantity: {item.quantity}</div>
</div>
<div className="text-lg font-bold">${item.price.toLocaleString()}</div>
</div>
))}
</div>
<div className="mt-6 pt-6 border-t border-gray-700 flex justify-between items-center">
<span className="text-xl font-semibold">Total</span>
<span className="text-2xl font-bold text-green-400">${order.total.toLocaleString()}</span>
</div>
</div>
<div className="bg-gray-800 rounded-lg p-6">
<h2 className="text-xl font-semibold mb-4">Transactions</h2>
<div className="space-y-3">
{order.transactions.map(transaction => (
<div key={transaction.id} className="p-4 bg-gray-700 rounded">
<div className="flex justify-between items-center mb-2">
<div>
<div className="font-medium">{transaction.method}</div>
<div className="text-sm text-gray-400">{transaction.date}</div>
</div>
<div className="text-right">
<div className="text-lg font-bold text-green-400">${transaction.amount.toLocaleString()}</div>
<div className="text-sm text-green-400">{transaction.status}</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
}
function InfoCard({ icon, label, value }: any) {
return (
<div className="bg-gray-800 rounded-lg p-4">
<div className="flex items-center gap-3 mb-2">
<div className="text-blue-400">{icon}</div>
<div className="text-sm text-gray-400">{label}</div>
</div>
<div className="text-lg font-medium">{value}</div>
</div>
);
}

View File

@ -0,0 +1,22 @@
{
"name": "keap-mcp-apps",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "node build-all.js",
"dev": "vite"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "^0.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
"vite": "^5.3.0",
"typescript": "^5.4.0"
}
}

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function AffiliateDashboard() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_affiliates', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Affiliate Dashboard</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Affiliate Dashboard - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/affiliate-dashboard',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function AnalyticsDashboard() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_contacts', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Analytics Dashboard</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analytics Dashboard - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/analytics-dashboard',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function AppointmentCalendar() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_appointments', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Appointment Calendar</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Appointment Calendar - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/appointment-calendar',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function AutomationBuilder() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_hooks', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Automation Builder</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Automation Builder - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/automation-builder',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function CampaignDashboard() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_campaigns', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Campaign Dashboard</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Campaign Dashboard - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/campaign-dashboard',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function CampaignDetail() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_get_campaign', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Campaign Detail</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Campaign Detail - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/campaign-detail',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,84 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function ContactDashboard() {
const { callTool, loading, error } = useCallTool();
const [contacts, setContacts] = useState<any[]>([]);
const [stats, setStats] = useState({ total: 0, recent: 0 });
useEffect(() => {
loadContacts();
}, []);
const loadContacts = async () => {
try {
const result = await callTool('keap_list_contacts', { limit: 10 });
if (result?.contacts) {
setContacts(result.contacts);
setStats({ total: result.count || 0, recent: result.contacts.length });
}
} catch (err) {
console.error('Failed to load contacts:', err);
}
};
return (
<div className="app-container">
<h1>Contact Dashboard</h1>
<div className="grid grid-3" style={{ marginBottom: '24px' }}>
<div className="card">
<h3>Total Contacts</h3>
<div style={{ fontSize: '32px', fontWeight: 'bold', marginTop: '8px' }}>
{stats.total}
</div>
</div>
<div className="card">
<h3>Recent Contacts</h3>
<div style={{ fontSize: '32px', fontWeight: 'bold', marginTop: '8px' }}>
{stats.recent}
</div>
</div>
<div className="card">
<h3>Quick Actions</h3>
<button className="btn" style={{ marginTop: '8px' }} onClick={loadContacts}>
Refresh
</button>
</div>
</div>
{error && <div className="error">{error}</div>}
<div className="card">
<h2>Recent Contacts</h2>
{loading ? (
<div className="loading">Loading contacts...</div>
) : (
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Company</th>
<th>Created</th>
</tr>
</thead>
<tbody>
{contacts.map((contact) => (
<tr key={contact.id}>
<td>{contact.given_name} {contact.family_name}</td>
<td>{contact.email_addresses?.[0]?.email || '-'}</td>
<td>{contact.phone_numbers?.[0]?.number || '-'}</td>
<td>{contact.company?.company_name || '-'}</td>
<td>{contact.date_created ? new Date(contact.date_created).toLocaleDateString() : '-'}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Dashboard - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/contact-dashboard',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function ContactDetail() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_get_contact', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Contact Detail</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Detail - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/contact-detail',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function ContactGrid() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_contacts', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Contact Grid</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Grid - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/contact-grid',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function DealDetail() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_get_opportunity', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Deal Detail</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Deal Detail - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/deal-detail',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function EmailComposer() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_send_email', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Email Composer</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Composer - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/email-composer',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function FileBrowser() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_files', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>File Browser</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Browser - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/file-browser',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function OrderDashboard() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_orders', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Order Dashboard</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Order Dashboard - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/order-dashboard',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function OrderDetail() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_get_order', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Order Detail</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Order Detail - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/order-detail',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,76 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function PipelineKanban() {
const { callTool, loading } = useCallTool();
const [opportunities, setOpportunities] = useState<any[]>([]);
const [stages, setStages] = useState<any[]>([]);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const [opps, stgs] = await Promise.all([
callTool('keap_list_opportunities', { limit: 50 }),
callTool('keap_list_opportunity_stage_pipeline', {}),
]);
setOpportunities(opps?.opportunities || []);
setStages(stgs?.stages || []);
} catch (err) {
console.error('Failed to load data:', err);
}
};
const groupByStage = () => {
const grouped: any = {};
stages.forEach(stage => {
grouped[stage.id] = opportunities.filter(opp => opp.stage?.id === stage.id);
});
return grouped;
};
const stageGroups = groupByStage();
return (
<div className="app-container">
<h1>Sales Pipeline - Kanban Board</h1>
{loading ? (
<div className="loading">Loading pipeline...</div>
) : (
<div style={{ display: 'flex', gap: '16px', overflowX: 'auto', paddingBottom: '20px' }}>
{stages.map(stage => (
<div key={stage.id} className="card" style={{ minWidth: '300px', flex: '0 0 auto' }}>
<h3>{stage.name}</h3>
<div style={{ fontSize: '12px', color: '#999', marginBottom: '12px' }}>
{stageGroups[stage.id]?.length || 0} deals
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{(stageGroups[stage.id] || []).map((opp: any) => (
<div key={opp.id} className="card" style={{ padding: '12px', background: '#2d2d30' }}>
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
{opp.opportunity_title}
</div>
<div style={{ fontSize: '12px', color: '#999' }}>
${opp.projected_revenue_high || opp.projected_revenue_low || 0}
</div>
{opp.estimated_close_date && (
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
Close: {new Date(opp.estimated_close_date).toLocaleDateString()}
</div>
)}
</div>
))}
</div>
</div>
))}
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pipeline Kanban - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/pipeline-kanban',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function ProductCatalog() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_list_products', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Product Catalog</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Product Catalog - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/product-catalog',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

View File

@ -0,0 +1,39 @@
import React, { useState, useEffect } from 'react';
import { useCallTool } from '../../hooks/useCallTool';
import '../../styles/global.css';
export default function SettingsPanel() {
const { callTool, loading, error } = useCallTool();
const [data, setData] = useState<any>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const result = await callTool('keap_get_account_profile', {});
setData(result);
} catch (err) {
console.error('Failed to load data:', err);
}
};
return (
<div className="app-container">
<h1>Settings Panel</h1>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="card">
<pre style={{ whiteSpace: 'pre-wrap', fontSize: '12px' }}>
{JSON.stringify(data, null, 2)}
</pre>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Settings Panel - Keap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../dist/settings-panel',
emptyOutDir: true,
rollupOptions: {
input: resolve(__dirname, 'index.html'),
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]'
}
}
}
});

Some files were not shown because too many files have changed in this diff Show More