keap: Complete MCP server with 111 tools and 20 React apps
This commit is contained in:
parent
e78b92a140
commit
91b2d348be
2
servers/keap/.env.example
Normal file
2
servers/keap/.env.example
Normal 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
471
servers/keap/README.md
Normal 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.*
|
||||
192
servers/keap/generate-apps.js
Normal file
192
servers/keap/generate-apps.js
Normal 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!`);
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
187
servers/keap/src/tools/affiliates-tools.ts
Normal file
187
servers/keap/src/tools/affiliates-tools.ts
Normal 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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
302
servers/keap/src/tools/ecommerce-tools.ts
Normal file
302
servers/keap/src/tools/ecommerce-tools.ts
Normal 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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
100
servers/keap/src/tools/files-tools.ts
Normal file
100
servers/keap/src/tools/files-tools.ts
Normal 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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
135
servers/keap/src/tools/notes-tools.ts
Normal file
135
servers/keap/src/tools/notes-tools.ts
Normal 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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
206
servers/keap/src/tools/opportunities-tools.ts
Normal file
206
servers/keap/src/tools/opportunities-tools.ts
Normal 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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -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 || [] };
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
105
servers/keap/src/tools/settings-tools.ts
Normal file
105
servers/keap/src/tools/settings-tools.ts
Normal 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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}` }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
434
servers/keap/src/types/index.ts
Normal file
434
servers/keap/src/types/index.ts
Normal 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;
|
||||
}
|
||||
118
servers/keap/src/ui/react-app/automation-dashboard/index.tsx
Normal file
118
servers/keap/src/ui/react-app/automation-dashboard/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
19
servers/keap/src/ui/react-app/build-all.js
vendored
Normal file
19
servers/keap/src/ui/react-app/build-all.js
vendored
Normal 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!');
|
||||
91
servers/keap/src/ui/react-app/contact-timeline/index.tsx
Normal file
91
servers/keap/src/ui/react-app/contact-timeline/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
74
servers/keap/src/ui/react-app/order-dashboard/index.tsx
Normal file
74
servers/keap/src/ui/react-app/order-dashboard/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
99
servers/keap/src/ui/react-app/order-detail/index.tsx
Normal file
99
servers/keap/src/ui/react-app/order-detail/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
22
servers/keap/src/ui/react-app/package.json
Normal file
22
servers/keap/src/ui/react-app/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
39
servers/keap/src/ui/react-app/src/apps/contact-grid/App.tsx
Normal file
39
servers/keap/src/ui/react-app/src/apps/contact-grid/App.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
39
servers/keap/src/ui/react-app/src/apps/deal-detail/App.tsx
Normal file
39
servers/keap/src/ui/react-app/src/apps/deal-detail/App.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
39
servers/keap/src/ui/react-app/src/apps/file-browser/App.tsx
Normal file
39
servers/keap/src/ui/react-app/src/apps/file-browser/App.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
39
servers/keap/src/ui/react-app/src/apps/order-detail/App.tsx
Normal file
39
servers/keap/src/ui/react-app/src/apps/order-detail/App.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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]'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
);
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user