diff --git a/servers/brevo/README.md b/servers/brevo/README.md
index 76ba7bc..0cc05c0 100644
--- a/servers/brevo/README.md
+++ b/servers/brevo/README.md
@@ -1,235 +1,328 @@
-> **๐ Don't want to self-host?** [Join the waitlist for our fully managed solution โ](https://mcpengage.com/brevo)
->
-> Zero setup. Zero maintenance. Just connect and automate.
+# Brevo MCP Server
----
+Complete Model Context Protocol (MCP) server for Brevo (formerly SendinBlue) with 55+ tools and 14 interactive apps.
-# ๐ Brevo MCP Server โ 2026 Complete Version
+## Features
-## ๐ก What This Unlocks
+### ๐ ๏ธ 55+ Tools Across 10 Categories
-**This MCP server gives AI direct access to your entire Brevo email and SMS marketing workspace.** Instead of clicking through interfaces, you just *tell* it what you need.
+- **Contacts** (12 tools): List, get, create, update, delete, search, import, export, manage attributes, folders, and lists
+- **Email Campaigns** (9 tools): List, get, create, update, delete, send, schedule, reports, and link tracking
+- **Transactional** (4 tools): Send transactional emails/SMS, event tracking, aggregated reports
+- **Lists** (7 tools): Manage contact lists, add/remove contacts
+- **Senders** (6 tools): List, create, update, delete, validate sender domains
+- **Templates** (6 tools): List, create, update, delete templates, send test emails
+- **Automations** (5 tools): List workflows, activate/deactivate, get stats
+- **SMS Campaigns** (6 tools): List, create, update, send SMS campaigns, reports
+- **CRM Deals** (7 tools): Manage deals, pipelines, and stages
+- **Webhooks** (5 tools): List, create, update, delete webhooks for real-time events
-Brevo (formerly Sendinblue) is a complete email and SMS marketing platform used by 500,000+ businesses worldwide. This MCP server brings all its power into your AI workflow.
+### ๐ฑ 14 Interactive MCP Apps
-### ๐ฏ Email/SMS Marketing Power Moves
+1. **contact-dashboard** - Overview with key metrics and recent activity
+2. **contact-detail** - Full profile view with attributes and timeline
+3. **contact-grid** - Searchable, filterable grid view
+4. **campaign-dashboard** - Campaign performance metrics
+5. **campaign-builder** - Visual campaign creation wizard
+6. **automation-dashboard** - Marketing automation workflows
+7. **deal-pipeline** - Visual CRM pipeline with drag-and-drop
+8. **transactional-monitor** - Real-time transactional event tracking
+9. **email-template-gallery** - Browse and manage templates
+10. **sms-dashboard** - SMS campaign management
+11. **list-manager** - Contact lists and folders organization
+12. **report-dashboard** - Comprehensive analytics and insights
+13. **webhook-manager** - Configure webhooks for integrations
+14. **import-wizard** - Step-by-step contact import
-Stop context-switching between Claude and Brevo. The AI can directly control your campaigns:
-
-1. **Emergency campaign deployment** โ "Send an urgent email about the service outage to all active customers, skip the test list"
-2. **Smart segmentation** โ "Export all contacts who opened our last 3 campaigns but didn't convert, then create a re-engagement campaign"
-3. **Multi-channel orchestration** โ "Check email deliverability for campaign #12345, if bounce rate is over 5%, send an SMS follow-up to non-openers"
-4. **Template-driven automation** โ "List all active email templates, use template #8 to send welcome emails to the 50 contacts added this week"
-5. **Real-time list hygiene** โ "Find all contacts with invalid emails from yesterday's imports, add them to the cleanup list, and notify me with stats"
-
-### ๐ The Real Power: Combining Tools
-
-AI can chain multiple Brevo operations together:
-
-- Query campaign metrics โ Segment by engagement โ Create targeted follow-up โ Schedule SMS backup
-- Import contacts โ Validate emails โ Auto-assign to lists โ Trigger welcome sequence
-- Analyze template performance โ Clone best performers โ Customize for new segments โ Deploy and track
-
-## ๐ฆ What's Inside
-
-**8 powerful API tools** covering Brevo's email and SMS marketing platform:
-
-1. **send_email** โ Send transactional emails with templates, attachments, and tracking
-2. **list_contacts** โ Query and filter your contact database with pagination
-3. **add_contact** โ Create contacts with custom attributes and list assignments
-4. **update_contact** โ Modify contact data, list memberships, and preferences
-5. **list_campaigns** โ Browse email campaigns by type, status, and date
-6. **create_campaign** โ Build and schedule email campaigns programmatically
-7. **send_sms** โ Send transactional SMS with delivery tracking
-8. **list_templates** โ Access your email template library
-
-All with proper error handling, automatic authentication, and TypeScript types.
-
-**API Foundation:** [Brevo API v3](https://developers.brevo.com/reference/getting-started-1) (REST)
-
-## ๐ Quick Start
-
-### Option 1: Claude Desktop (Local)
-
-1. **Clone and build:**
- ```bash
- git clone https://github.com/BusyBee3333/Brevo-MCP-2026-Complete.git
- cd brevo-mcp-2026-complete
- npm install
- npm run build
- ```
-
-2. **Get your Brevo API key:**
- - Log into [Brevo](https://app.brevo.com/)
- - Go to **Settings โ SMTP & API โ API Keys**
- - Create a new API key (v3) with email and SMS permissions
- - Copy the key (you'll only see it once)
-
-3. **Configure Claude Desktop:**
-
- On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
-
- On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
-
- ```json
- {
- "mcpServers": {
- "brevo": {
- "command": "node",
- "args": ["/ABSOLUTE/PATH/TO/brevo-mcp-2026-complete/dist/index.js"],
- "env": {
- "BREVO_API_KEY": "xkeysib-abc123..."
- }
- }
- }
- }
- ```
-
-4. **Restart Claude Desktop**
-
-### Option 2: Deploy to Railway
-
-[](https://railway.app/template/brevo-mcp)
-
-1. Click the button above
-2. Set `BREVO_API_KEY` in Railway dashboard
-3. Use the Railway URL as your MCP server endpoint
-
-### Option 3: Docker
+## Installation
```bash
-docker build -t brevo-mcp .
-docker run -p 3000:3000 \
- -e BREVO_API_KEY=xkeysib-abc123... \
- brevo-mcp
+npm install
+npm run build
```
-## ๐ Authentication
+## Configuration
-**Brevo uses API key authentication** (v3 API):
-
-- **Header:** `api-key: YOUR_KEY`
-- **Format:** `xkeysib-...` (starts with xkeysib-)
-- **Permissions:** Email campaigns, Contacts, SMS (depending on your plan)
-- **Rate limits:** 300 calls/minute on free plans, higher on paid
-
-Get your API key at: https://app.brevo.com/settings/keys/api
-
-The MCP server handles authentication automaticallyโjust set `BREVO_API_KEY`.
-
-## ๐ฏ Example Prompts for Email Marketers
-
-Once connected to Claude, use natural language. Here are real email marketing workflows:
-
-### Campaign Management
-- *"List all email campaigns from the last 30 days that are still in draft status"*
-- *"Create a new campaign called 'Spring Sale 2026' with template #45, targeting list #12"*
-- *"Show me all campaigns with 'webinar' in the name scheduled for this month"*
-
-### Contact Operations
-- *"Add these 5 contacts to list #8: [paste CSV data]"*
-- *"Find all contacts with Gmail addresses who signed up this week"*
-- *"Update contact jane@example.com: set FIRSTNAME to Jane, add to VIP list"*
-
-### Multi-Channel Workflows
-- *"Send a welcome email to everyone added to list #15 today using template #9"*
-- *"If bounce rate on campaign #789 is over 3%, send SMS backup to all recipients"*
-- *"List all templates with 'newsletter' in the name, show me #3's stats"*
-
-### Bulk Operations
-- *"Export all contacts modified in the last 7 days as JSON"*
-- *"Send 'Account Verified' email to all contacts with VERIFIED=true attribute"*
-- *"Check how many contacts are in lists #10, #11, and #12 combined"*
-
-## ๐ ๏ธ Development
-
-### Prerequisites
-- Node.js 18+
-- npm or yarn
-- Brevo account (free or paid)
-
-### Setup
+Set your Brevo API key as an environment variable:
+
+```bash
+export BREVO_API_KEY=your-api-key-here
+```
+
+Get your API key from: https://app.brevo.com/settings/keys/api
+
+## Usage
+
+### Running the Server
```bash
-git clone https://github.com/BusyBee3333/Brevo-MCP-2026-Complete.git
-cd brevo-mcp-2026-complete
-npm install
-cp .env.example .env
-# Edit .env with your Brevo API key
-npm run build
npm start
```
-### Testing
+Or with inline API key:
```bash
-npm test # Run all tests
-npm run test:watch # Watch mode
-npm run test:coverage # Coverage report
+BREVO_API_KEY=your-api-key-here npm start
```
-### Project Structure
+### MCP Client Configuration
+
+Add to your MCP client configuration (e.g., Claude Desktop):
+
+```json
+{
+ "mcpServers": {
+ "brevo": {
+ "command": "node",
+ "args": ["/path/to/brevo/dist/main.js"],
+ "env": {
+ "BREVO_API_KEY": "your-api-key-here"
+ }
+ }
+ }
+}
+```
+
+## Tool Examples
+
+### Create a Contact
+
+```typescript
+brevo_create_contact({
+ email: "user@example.com",
+ attributes: {
+ FIRSTNAME: "John",
+ LASTNAME: "Doe",
+ COMPANY: "Acme Inc"
+ },
+ listIds: [123]
+})
+```
+
+### Send Transactional Email
+
+```typescript
+brevo_send_transactional_email({
+ to: [{ email: "user@example.com", name: "John Doe" }],
+ sender: { email: "hello@company.com", name: "Company" },
+ subject: "Welcome!",
+ htmlContent: "
Welcome to our service! ",
+ tags: ["welcome", "onboarding"]
+})
+```
+
+### Create Email Campaign
+
+```typescript
+brevo_create_email_campaign({
+ name: "Monthly Newsletter",
+ subject: "Your Monthly Update",
+ sender: { name: "Company", email: "newsletter@company.com" },
+ htmlContent: "...",
+ recipients: {
+ listIds: [123],
+ exclusionListIds: [456]
+ },
+ scheduledAt: "2024-02-01T10:00:00Z"
+})
+```
+
+### List Contacts
+
+```typescript
+brevo_list_contacts({
+ limit: 50,
+ offset: 0,
+ listIds: [123],
+ sort: "desc"
+})
+```
+
+## API Reference
+
+### Contacts Tools
+
+- `brevo_list_contacts` - List all contacts with filters
+- `brevo_get_contact` - Get contact by email/ID
+- `brevo_create_contact` - Create new contact
+- `brevo_update_contact` - Update contact
+- `brevo_delete_contact` - Delete contact
+- `brevo_search_contacts` - Advanced contact search
+- `brevo_import_contacts` - Bulk import from CSV
+- `brevo_export_contacts` - Export contacts
+- `brevo_list_contact_attributes` - List all attributes
+- `brevo_create_contact_attribute` - Create custom attribute
+- `brevo_list_contact_folders` - List folders
+- `brevo_list_contact_lists` - List all lists
+
+### Campaign Tools
+
+- `brevo_list_email_campaigns` - List campaigns
+- `brevo_get_email_campaign` - Get campaign details
+- `brevo_create_email_campaign` - Create campaign
+- `brevo_update_email_campaign` - Update campaign
+- `brevo_delete_email_campaign` - Delete campaign
+- `brevo_send_email_campaign` - Send immediately
+- `brevo_schedule_email_campaign` - Schedule send
+- `brevo_get_campaign_report` - Get performance report
+- `brevo_list_campaign_links` - Get campaign links
+
+### Transactional Tools
+
+- `brevo_send_transactional_email` - Send transactional email
+- `brevo_send_transactional_sms` - Send SMS
+- `brevo_list_transactional_events` - List events
+- `brevo_get_aggregated_report` - Get statistics
+
+### Lists Tools
+
+- `brevo_list_lists` - List all contact lists
+- `brevo_get_list` - Get list details
+- `brevo_create_list` - Create new list
+- `brevo_update_list` - Update list
+- `brevo_delete_list` - Delete list
+- `brevo_add_contacts_to_list` - Add contacts
+- `brevo_remove_contacts_from_list` - Remove contacts
+
+### Templates Tools
+
+- `brevo_list_templates` - List templates
+- `brevo_get_template` - Get template
+- `brevo_create_template` - Create template
+- `brevo_update_template` - Update template
+- `brevo_delete_template` - Delete template
+- `brevo_send_test_template` - Send test email
+
+### Automation Tools
+
+- `brevo_list_workflows` - List workflows
+- `brevo_get_workflow` - Get workflow
+- `brevo_activate_workflow` - Activate workflow
+- `brevo_deactivate_workflow` - Deactivate workflow
+- `brevo_get_workflow_stats` - Get statistics
+
+### SMS Tools
+
+- `brevo_list_sms_campaigns` - List SMS campaigns
+- `brevo_get_sms_campaign` - Get campaign
+- `brevo_create_sms_campaign` - Create campaign
+- `brevo_update_sms_campaign` - Update campaign
+- `brevo_send_sms_campaign` - Send SMS campaign
+- `brevo_get_sms_campaign_report` - Get report
+
+### CRM Deals Tools
+
+- `brevo_list_deals` - List deals
+- `brevo_get_deal` - Get deal
+- `brevo_create_deal` - Create deal
+- `brevo_update_deal` - Update deal
+- `brevo_delete_deal` - Delete deal
+- `brevo_list_pipelines` - List pipelines
+- `brevo_list_deal_stages` - List stages
+
+### Webhook Tools
+
+- `brevo_list_webhooks` - List webhooks
+- `brevo_get_webhook` - Get webhook
+- `brevo_create_webhook` - Create webhook
+- `brevo_update_webhook` - Update webhook
+- `brevo_delete_webhook` - Delete webhook
+
+## MCP Apps
+
+All apps are accessible via MCP resources at `brevo://app/{app-name}`
+
+### Contact Apps
+- `contact-dashboard` - Metrics and overview
+- `contact-detail` - Full contact profile
+- `contact-grid` - Searchable contact table
+
+### Campaign Apps
+- `campaign-dashboard` - Campaign analytics
+- `campaign-builder` - Visual campaign creator
+
+### Other Apps
+- `automation-dashboard` - Workflow management
+- `transactional-monitor` - Real-time event tracking
+- `email-template-gallery` - Template browser
+- `sms-dashboard` - SMS management
+- `deal-pipeline` - CRM pipeline view
+- `list-manager` - List organization
+- `report-dashboard` - Analytics hub
+- `webhook-manager` - Webhook configuration
+- `import-wizard` - Contact import tool
+
+## Architecture
```
-brevo-mcp-2026-complete/
+brevo/
โโโ src/
-โ โโโ index.ts # Main server implementation
-โโโ dist/ # Compiled JavaScript
+โ โโโ types/
+โ โ โโโ index.ts # TypeScript interfaces
+โ โ โโโ api-client.ts # Brevo API client
+โ โโโ tools/
+โ โ โโโ contacts-tools.ts # 12 contact tools
+โ โ โโโ campaigns-tools.ts # 9 campaign tools
+โ โ โโโ transactional-tools.ts # 4 transactional tools
+โ โ โโโ lists-tools.ts # 7 list tools
+โ โ โโโ senders-tools.ts # 6 sender tools
+โ โ โโโ templates-tools.ts # 6 template tools
+โ โ โโโ automations-tools.ts # 5 automation tools
+โ โ โโโ sms-tools.ts # 6 SMS tools
+โ โ โโโ deals-tools.ts # 7 CRM tools
+โ โ โโโ webhooks-tools.ts # 5 webhook tools
+โ โโโ apps/
+โ โ โโโ contact-dashboard.ts
+โ โ โโโ contact-detail.ts
+โ โ โโโ contact-grid.ts
+โ โ โโโ campaign-dashboard.ts
+โ โ โโโ campaign-builder.ts
+โ โ โโโ automation-dashboard.ts
+โ โ โโโ deal-pipeline.ts
+โ โ โโโ transactional-monitor.ts
+โ โ โโโ template-gallery.ts
+โ โ โโโ sms-dashboard.ts
+โ โ โโโ list-manager.ts
+โ โ โโโ report-dashboard.ts
+โ โ โโโ webhook-manager.ts
+โ โ โโโ import-wizard.ts
+โ โโโ server.ts # MCP server implementation
+โ โโโ main.ts # Entry point
โโโ package.json
โโโ tsconfig.json
-โโโ .env.example
+โโโ README.md
```
-## ๐ Troubleshooting
+## Development
-### "Authentication failed"
-- Verify your API key starts with `xkeysib-`
-- Check key permissions at https://app.brevo.com/settings/keys/api
-- Ensure your account is active (not suspended)
+```bash
+# Install dependencies
+npm install
-### "Rate limit exceeded"
-- Free plans: 300 calls/minute
-- Wait 60 seconds or upgrade to paid plan
-- Use pagination (`limit` parameter) to reduce calls
+# Build
+npm run build
-### "Tools not appearing in Claude"
-- Restart Claude Desktop after updating config
-- Check that the path in `claude_desktop_config.json` is absolute (not relative)
-- Verify the build completed: `ls dist/index.js`
-- Check Claude Desktop logs: `tail -f ~/Library/Logs/Claude/mcp*.log`
+# Development mode (watch)
+npm run dev
-### "Invalid list ID" or "Template not found"
-- List IDs are numeric (e.g., 12, not "12")
-- Get valid IDs: *"List all my contact lists"* or *"Show me all templates"*
+# Clean build artifacts
+npm run clean
+```
-## ๐ Resources
+## API Documentation
-- **[Brevo API v3 Docs](https://developers.brevo.com/reference/getting-started-1)** โ Official API reference
-- **[Brevo Help Center](https://help.brevo.com/)** โ Tutorials and guides
-- **[MCP Protocol Spec](https://modelcontextprotocol.io/)** โ How MCP servers work
-- **[Claude Desktop Docs](https://claude.ai/desktop)** โ Installing and configuring Claude
-- **[MCPEngage Platform](https://mcpengine.pages.dev)** โ Browse 30+ business MCP servers
+Brevo API v3 Documentation: https://developers.brevo.com/reference
-## ๐ค Contributing
+## License
-Contributions are welcome! Please:
+MIT
-1. Fork the repo
-2. Create a feature branch (`git checkout -b feature/sms-analytics`)
-3. Commit your changes (`git commit -m 'Add SMS campaign stats tool'`)
-4. Push to the branch (`git push origin feature/sms-analytics`)
-5. Open a Pull Request
+## Support
-## ๐ License
+For issues and feature requests, please file an issue in the repository.
-MIT License - see [LICENSE](LICENSE) for details
+## Related Projects
-## ๐ Credits
-
-Built by [MCPEngage](https://mcpengage.com) โ AI infrastructure for business software.
-
-Want more MCP servers? Check out our [full catalog](https://mcpengage.com) covering 30+ business platforms including Constant Contact, Mailchimp, ActiveCampaign, and more.
-
----
-
-**Questions?** Open an issue or join our [Discord community](https://discord.gg/mcpengage).
+- [Brevo API Documentation](https://developers.brevo.com/)
+- [Model Context Protocol](https://modelcontextprotocol.io/)
+- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
diff --git a/servers/brevo/package.json b/servers/brevo/package.json
index 180ce99..744c7e7 100644
--- a/servers/brevo/package.json
+++ b/servers/brevo/package.json
@@ -1,27 +1,32 @@
{
- "name": "mcp-server-brevo",
+ "name": "@mcpengine/brevo-server",
"version": "1.0.0",
+ "description": "Complete Brevo MCP Server with 50+ tools and 15+ apps",
"type": "module",
- "description": "MCP server for Brevo (formerly Sendinblue) email and SMS marketing API",
- "main": "dist/index.js",
+ "main": "dist/main.js",
"bin": {
- "mcp-server-brevo": "./dist/index.js"
+ "brevo-mcp": "./dist/main.js"
},
"scripts": {
"build": "tsc",
- "start": "node dist/index.js",
- "dev": "tsx src/index.ts",
- "prepublishOnly": "npm run build"
+ "dev": "tsc --watch",
+ "start": "node dist/main.js",
+ "lint": "eslint src --ext .ts",
+ "clean": "rm -rf dist"
},
- "keywords": ["mcp", "brevo", "sendinblue", "email", "sms", "marketing"],
+ "keywords": ["mcp", "brevo", "sendinblue", "email", "marketing", "automation", "crm"],
+ "author": "MCPEngine",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^0.5.0",
+ "axios": "^1.6.2",
"zod": "^3.22.4"
},
"devDependencies": {
- "@types/node": "^20.10.0",
- "tsx": "^4.7.0",
- "typescript": "^5.3.0"
+ "@types/node": "^20.10.5",
+ "typescript": "^5.3.3"
+ },
+ "engines": {
+ "node": ">=18.0.0"
}
}
diff --git a/servers/brevo/src/apps/automation-dashboard.ts b/servers/brevo/src/apps/automation-dashboard.ts
new file mode 100644
index 0000000..51ac9da
--- /dev/null
+++ b/servers/brevo/src/apps/automation-dashboard.ts
@@ -0,0 +1,97 @@
+export function automationDashboardApp() {
+ return {
+ name: 'automation-dashboard',
+ description: 'Marketing automation workflows dashboard with performance tracking',
+ ui: {
+ title: 'Automation Dashboard',
+ content: `
+# Marketing Automation
+
+
+
+
Active Workflows
+
{{activeWorkflows}}
+
+
+
Total Sent
+
{{totalSent}}
+
+
+
Conversion Rate
+
{{conversionRate}}%
+
+
+
Revenue
+
\${{totalRevenue}}
+
+
+
+## Workflows
+{{#each workflows}}
+
+
+
+
+
+
Sent
+
{{this.stats.sent}}
+
+
+
Opened
+
{{this.stats.opened}}
+
+
+
Clicked
+
{{this.stats.clicked}}
+
+
+
Goals
+
{{this.stats.goal}}
+
+
+
+
+ View Details
+ {{#if this.isActive}}
+ Pause
+ {{else}}
+ Activate
+ {{/if}}
+
+
+{{/each}}
+
+## Quick Actions
+- [Create Workflow](#new-workflow)
+- [View Reports](#workflow-reports)
+- [Manage Triggers](#triggers)
+`,
+ style: `
+ .automation-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
+ .auto-stat { padding: 20px; background: #f8f9fa; border-radius: 8px; text-align: center; }
+ .auto-number { font-size: 32px; font-weight: bold; color: #007bff; margin-top: 10px; }
+ .workflow-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 20px; }
+ .workflow-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
+ .workflow-status { display: flex; align-items: center; gap: 8px; }
+ .status-indicator { width: 10px; height: 10px; border-radius: 50%; }
+ .status-active { background: #28a745; }
+ .status-inactive { background: #dc3545; }
+ .status-draft { background: #ffc107; }
+ .workflow-stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 15px; }
+ .workflow-stat { text-align: center; padding: 10px; background: #f8f9fa; border-radius: 4px; }
+ .stat-label { font-size: 12px; color: #666; }
+ .stat-value { font-size: 20px; font-weight: bold; margin-top: 5px; }
+ .workflow-actions { display: flex; gap: 10px; }
+ .btn-small { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; background: #007bff; color: white; }
+ .btn-warning { background: #ffc107; color: black; }
+ .btn-success { background: #28a745; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/campaign-builder.ts b/servers/brevo/src/apps/campaign-builder.ts
new file mode 100644
index 0000000..67fcf9b
--- /dev/null
+++ b/servers/brevo/src/apps/campaign-builder.ts
@@ -0,0 +1,98 @@
+export function campaignBuilderApp() {
+ return {
+ name: 'campaign-builder',
+ description: 'Visual campaign builder for creating and scheduling email campaigns',
+ ui: {
+ title: 'Campaign Builder',
+ content: `
+# Create Email Campaign
+
+
+
+
+
+
Campaign Settings
+
+
+ Campaign Name
+
+
+
+
+ Subject Line
+
+
+
+
+ Sender
+
+ {{#each senders}}
+ {{this.name}} <{{this.email}}>
+ {{/each}}
+
+
+
+
+
+
+ Content Editor
+
+
+
+
+ Save Draft
+ Continue to Recipients โ
+
+
+
+`,
+ style: `
+ .builder-container { display: flex; gap: 30px; }
+ .builder-sidebar { width: 200px; }
+ .step { padding: 15px; margin-bottom: 10px; border-radius: 8px; background: #f8f9fa; display: flex; align-items: center; gap: 10px; }
+ .step.active { background: #007bff; color: white; }
+ .step-number { width: 30px; height: 30px; background: white; color: #007bff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; }
+ .step.active .step-number { background: white; color: #007bff; }
+ .builder-main { flex: 1; }
+ .form-group { margin-bottom: 25px; }
+ .form-group label { display: block; font-weight: bold; margin-bottom: 8px; }
+ .form-input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
+ .form-textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: monospace; }
+ .template-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
+ .template-card { padding: 15px; border: 2px solid #e0e0e0; border-radius: 8px; text-align: center; cursor: pointer; }
+ .template-card:hover { border-color: #007bff; }
+ .template-preview { font-size: 48px; margin-bottom: 10px; }
+ .builder-actions { display: flex; gap: 10px; margin-top: 30px; }
+ .btn-secondary { padding: 10px 20px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .btn-primary { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/campaign-dashboard.ts b/servers/brevo/src/apps/campaign-dashboard.ts
new file mode 100644
index 0000000..d2e433f
--- /dev/null
+++ b/servers/brevo/src/apps/campaign-dashboard.ts
@@ -0,0 +1,77 @@
+export function campaignDashboardApp() {
+ return {
+ name: 'campaign-dashboard',
+ description: 'Email campaign dashboard with performance metrics and recent campaigns',
+ ui: {
+ title: 'Campaign Dashboard',
+ content: `
+# Campaign Dashboard
+
+
+
+
Total Campaigns
+
{{totalCampaigns}}
+
{{activeCampaigns}} active
+
+
+
+
Emails Sent
+
{{totalEmailsSent}}
+
This month
+
+
+
+
Avg Open Rate
+
{{avgOpenRate}}%
+
+{{openRateChange}}%
+
+
+
+
Avg Click Rate
+
{{avgClickRate}}%
+
+{{clickRateChange}}%
+
+
+
+## Recent Campaigns
+{{#each recentCampaigns}}
+
+
+
{{this.subject}}
+
+ ๐ง {{this.stats.sent}} sent
+ ๐ {{this.stats.openRate}}% opened
+ ๐ {{this.stats.clickRate}}% clicked
+
+
{{this.createdAt}}
+
+{{/each}}
+
+## Quick Actions
+- [Create New Campaign](#new-campaign)
+- [View All Campaigns](#campaigns)
+- [Campaign Reports](#reports)
+- [A/B Test Campaign](#ab-test)
+`,
+ style: `
+ .stats-overview { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin: 20px 0; }
+ .stat-card { padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; }
+ .stat-number { font-size: 36px; font-weight: bold; margin: 10px 0; }
+ .stat-detail { opacity: 0.9; }
+ .stat-trend { color: #4ade80; font-weight: bold; }
+ .campaign-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 15px; }
+ .campaign-header { display: flex; justify-content: space-between; align-items: center; }
+ .status-badge { padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
+ .status-draft { background: #ffc107; color: black; }
+ .status-sent { background: #28a745; color: white; }
+ .status-queued { background: #17a2b8; color: white; }
+ .campaign-subject { color: #666; margin: 10px 0; }
+ .campaign-stats { display: flex; gap: 20px; margin: 10px 0; }
+ .campaign-date { color: #999; font-size: 0.9em; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/contact-dashboard.ts b/servers/brevo/src/apps/contact-dashboard.ts
new file mode 100644
index 0000000..bd334a4
--- /dev/null
+++ b/servers/brevo/src/apps/contact-dashboard.ts
@@ -0,0 +1,63 @@
+export function contactDashboardApp() {
+ return {
+ name: 'contact-dashboard',
+ description: 'Overview dashboard of all contacts with key metrics and recent activity',
+ ui: {
+ title: 'Contact Dashboard',
+ content: `
+# Contact Dashboard
+
+
+
+
Total Contacts
+
{{totalContacts}}
+
+{{newContactsThisMonth}} this month
+
+
+
+
Active Contacts
+
{{activeContacts}}
+
{{engagementRate}}% engagement
+
+
+
+
Blacklisted
+
{{blacklistedContacts}}
+
Email & SMS
+
+
+
+
Lists
+
{{totalLists}}
+
{{totalFolders}} folders
+
+
+
+## Recent Contacts
+{{#each recentContacts}}
+
+ {{this.email}}
+ Added {{this.createdAt}}
+ {{this.listCount}} lists
+
+{{/each}}
+
+## Quick Actions
+- [View All Contacts](#contacts)
+- [Import Contacts](#import)
+- [Create New List](#new-list)
+- [Export Contacts](#export)
+`,
+ style: `
+ .dashboard-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin: 20px 0; }
+ .metric-card { padding: 20px; background: #f8f9fa; border-radius: 8px; }
+ .metric-value { font-size: 32px; font-weight: bold; margin: 10px 0; }
+ .metric-change { color: #28a745; }
+ .metric-trend { color: #007bff; }
+ .contact-row { padding: 12px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; }
+ .contact-date { color: #666; }
+ .contact-lists { color: #007bff; font-size: 0.9em; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/contact-detail.ts b/servers/brevo/src/apps/contact-detail.ts
new file mode 100644
index 0000000..366e25f
--- /dev/null
+++ b/servers/brevo/src/apps/contact-detail.ts
@@ -0,0 +1,84 @@
+export function contactDetailApp() {
+ return {
+ name: 'contact-detail',
+ description: 'Detailed view of a single contact with full profile, attributes, and activity history',
+ ui: {
+ title: 'Contact Detail',
+ content: `
+# Contact Profile: {{email}}
+
+
+
+## Contact Attributes
+
+ {{#each attributes}}
+
+ {{@key}}
+ {{this}}
+
+ {{/each}}
+
+
+## Lists Membership
+
+ {{#each listIds}}
+ List {{this}}
+ {{/each}}
+
+
+## Activity Timeline
+
+
+
{{modifiedAt}}
+
Contact Modified
+
+
+
{{createdAt}}
+
Contact Created
+
+
+
+## Actions
+- [Edit Contact](#edit/{{id}})
+- [Add to List](#add-list/{{id}})
+- [Export Contact](#export/{{id}})
+- [Delete Contact](#delete/{{id}})
+`,
+ style: `
+ .profile-header { display: flex; justify-content: space-between; margin-bottom: 30px; }
+ .profile-info h2 { margin: 0 0 10px 0; }
+ .contact-email { font-size: 18px; color: #007bff; }
+ .contact-id { color: #666; font-size: 14px; }
+ .badge { padding: 5px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; }
+ .badge-success { background: #28a745; color: white; }
+ .badge-danger { background: #dc3545; color: white; }
+ .badge-warning { background: #ffc107; color: black; }
+ .attributes-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
+ .attributes-table td { padding: 10px; border-bottom: 1px solid #e0e0e0; }
+ .attr-name { font-weight: bold; width: 200px; }
+ .list-badge { display: inline-block; padding: 5px 12px; background: #e7f3ff; color: #007bff; border-radius: 4px; margin: 5px; }
+ .timeline { margin: 20px 0; }
+ .timeline-item { padding: 15px; border-left: 3px solid #007bff; margin-left: 20px; }
+ .timeline-date { font-weight: bold; color: #007bff; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/contact-grid.ts b/servers/brevo/src/apps/contact-grid.ts
new file mode 100644
index 0000000..5329b3a
--- /dev/null
+++ b/servers/brevo/src/apps/contact-grid.ts
@@ -0,0 +1,78 @@
+export function contactGridApp() {
+ return {
+ name: 'contact-grid',
+ description: 'Searchable and filterable grid view of all contacts',
+ ui: {
+ title: 'Contact Grid',
+ content: `
+# All Contacts
+
+
+
+
+ All Lists
+ {{#each lists}}
+ {{this.name}}
+ {{/each}}
+
+ + New Contact
+
+
+
+
+
+`,
+ style: `
+ .grid-controls { display: flex; gap: 10px; margin-bottom: 20px; }
+ .search-input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
+ .filter-select { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
+ .btn-primary { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .contacts-grid { width: 100%; border-collapse: collapse; }
+ .contacts-grid th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: bold; }
+ .contacts-grid td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
+ .contacts-grid tr:hover { background: #f8f9fa; }
+ .badge-count { background: #007bff; color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; }
+ .status-active { color: #28a745; font-weight: bold; }
+ .status-inactive { color: #dc3545; font-weight: bold; }
+ .btn-icon { background: none; border: none; font-size: 18px; cursor: pointer; }
+ .pagination { display: flex; justify-content: center; gap: 20px; margin-top: 20px; align-items: center; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/deal-pipeline.ts b/servers/brevo/src/apps/deal-pipeline.ts
new file mode 100644
index 0000000..5ad0cdd
--- /dev/null
+++ b/servers/brevo/src/apps/deal-pipeline.ts
@@ -0,0 +1,66 @@
+export function dealPipelineApp() {
+ return {
+ name: 'deal-pipeline',
+ description: 'Visual CRM deal pipeline with drag-and-drop stages and deal tracking',
+ ui: {
+ title: 'Deal Pipeline',
+ content: `
+# CRM Pipeline
+
+
+
+
+ {{#each stages}}
+
+
+
\${{this.totalValue}}
+
+
+ {{#each this.deals}}
+
+
{{this.name}}
+
\${{this.attributes.amount}}
+
{{this.contactName}}
+
{{this.modifiedAt}}
+
+ {{/each}}
+
+
+ {{/each}}
+
+
+## Quick Actions
+- [Add New Deal](#new-deal)
+- [Change Pipeline](#pipelines)
+- [Export Pipeline](#export)
+- [Pipeline Reports](#reports)
+`,
+ style: `
+ .pipeline-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
+ .pipeline-stats { display: flex; gap: 30px; color: #666; }
+ .pipeline-board { display: flex; gap: 20px; overflow-x: auto; padding-bottom: 20px; }
+ .pipeline-stage { min-width: 280px; background: #f8f9fa; border-radius: 8px; padding: 15px; }
+ .stage-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
+ .stage-count { background: #007bff; color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; }
+ .stage-value { font-size: 20px; font-weight: bold; color: #28a745; margin-bottom: 15px; }
+ .deals-container { max-height: 600px; overflow-y: auto; }
+ .deal-card { background: white; padding: 15px; border-radius: 6px; margin-bottom: 10px; border-left: 4px solid #007bff; cursor: move; }
+ .deal-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
+ .deal-name { font-weight: bold; margin-bottom: 5px; }
+ .deal-value { font-size: 18px; color: #28a745; margin-bottom: 5px; }
+ .deal-contact { color: #666; font-size: 14px; }
+ .deal-date { color: #999; font-size: 12px; margin-top: 5px; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/import-wizard.ts b/servers/brevo/src/apps/import-wizard.ts
new file mode 100644
index 0000000..47ceab0
--- /dev/null
+++ b/servers/brevo/src/apps/import-wizard.ts
@@ -0,0 +1,145 @@
+export function importWizardApp() {
+ return {
+ name: 'import-wizard',
+ description: 'Step-by-step wizard for importing contacts with mapping and validation',
+ ui: {
+ title: 'Import Contacts Wizard',
+ content: `
+# Import Contacts
+
+
+
+
+
Step 1: Upload Your File
+
+
+
๐
+
Drag and drop your CSV file here
+
or
+
Browse Files
+
+ Supported format: CSV (comma-separated values)
+ Maximum file size: 10 MB
+ Maximum contacts: 100,000 per import
+
+
+
+
+
Need a template?
+
Download our CSV template with all the standard fields
+
๐ฅ Download Template
+
+
+
+
File Requirements
+
+
+ โ
+ First row must contain column headers
+
+
+ โ
+ Email column is required
+
+
+ โ
+ UTF-8 encoding recommended
+
+
+ โ
+ Use comma (,) as field separator
+
+
+
+
+
+
+ Cancel
+ Next: Map Fields โ
+
+
+
+
Recent Imports
+
+
+
+ Date
+ File Name
+ Total
+ Success
+ Failed
+ Status
+
+
+
+ {{#each recentImports}}
+
+ {{this.date}}
+ {{this.fileName}}
+ {{this.total}}
+ {{this.success}}
+ {{this.failed}}
+ {{this.status}}
+
+ {{/each}}
+
+
+
+`,
+ style: `
+ .wizard-progress { display: flex; align-items: center; justify-content: center; margin-bottom: 40px; padding: 30px; background: white; border-radius: 8px; }
+ .progress-step { display: flex; flex-direction: column; align-items: center; }
+ .step-circle { width: 40px; height: 40px; border-radius: 50%; background: #e0e0e0; color: #666; display: flex; align-items: center; justify-content: center; font-weight: bold; }
+ .progress-step.active .step-circle { background: #007bff; color: white; }
+ .step-label { margin-top: 10px; font-size: 14px; color: #666; }
+ .progress-step.active .step-label { color: #007bff; font-weight: bold; }
+ .progress-line { width: 100px; height: 2px; background: #e0e0e0; margin: 0 20px; }
+ .wizard-content { background: white; padding: 30px; border-radius: 8px; margin-bottom: 20px; }
+ .upload-area { border: 2px dashed #007bff; border-radius: 8px; padding: 60px; text-align: center; background: #f8f9ff; margin: 20px 0; }
+ .upload-icon { font-size: 64px; margin-bottom: 20px; }
+ .btn-upload { padding: 12px 30px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
+ .upload-info { margin-top: 20px; color: #666; font-size: 14px; line-height: 1.6; }
+ .template-section { padding: 20px; background: #f8f9fa; border-radius: 8px; margin: 30px 0; text-align: center; }
+ .btn-secondary { padding: 10px 20px; background: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .requirements-section { margin-top: 30px; }
+ .requirements-list { list-style: none; padding: 0; }
+ .req-item { display: flex; align-items: center; gap: 10px; padding: 10px; background: #f8f9fa; margin-bottom: 8px; border-radius: 4px; }
+ .req-icon { width: 24px; height: 24px; background: #28a745; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; }
+ .wizard-actions { display: flex; justify-content: space-between; margin-bottom: 30px; }
+ .btn-cancel { padding: 10px 20px; background: white; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
+ .btn-next { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .btn-next:disabled { background: #ccc; cursor: not-allowed; }
+ .recent-imports { background: white; padding: 30px; border-radius: 8px; }
+ .imports-table { width: 100%; border-collapse: collapse; margin-top: 15px; }
+ .imports-table th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: bold; }
+ .imports-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
+ .success-count { color: #28a745; font-weight: bold; }
+ .failed-count { color: #dc3545; font-weight: bold; }
+ .status-badge { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; }
+ .status-completed { background: #d4edda; color: #155724; }
+ .status-processing { background: #fff3cd; color: #856404; }
+ .status-failed { background: #f8d7da; color: #721c24; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/list-manager.ts b/servers/brevo/src/apps/list-manager.ts
new file mode 100644
index 0000000..51e6440
--- /dev/null
+++ b/servers/brevo/src/apps/list-manager.ts
@@ -0,0 +1,132 @@
+export function listManagerApp() {
+ return {
+ name: 'list-manager',
+ description: 'Manage contact lists and folders with organization and segmentation tools',
+ ui: {
+ title: 'List Manager',
+ content: `
+# Contact Lists & Folders
+
+
+
+
+
+
+
+
+
+
+ Sort by Name
+ Sort by Size
+ Sort by Date
+
+
+
+
+ {{#each lists}}
+
+
+
+
+
+
๐ฅ
+
+
{{this.totalSubscribers}}
+
Subscribers
+
+
+
+
+
๐ซ
+
+
{{this.totalBlacklisted}}
+
Blacklisted
+
+
+
+
+
+ {{#if this.folderId}}
+ ๐ {{this.folderName}}
+ {{else}}
+ No folder
+ {{/if}}
+
+
+
+ View Contacts
+ Add Contacts
+ Edit
+
+
+ {{/each}}
+
+
+
+`,
+ style: `
+ .manager-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; }
+ .manager-stats { display: flex; gap: 20px; }
+ .stat-box { padding: 15px 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; }
+ .stat-number { font-size: 28px; font-weight: bold; }
+ .stat-label { font-size: 12px; opacity: 0.9; }
+ .btn-create { padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .manager-layout { display: flex; gap: 20px; }
+ .folders-sidebar { width: 250px; padding: 20px; background: #f8f9fa; border-radius: 8px; }
+ .folders-sidebar h3 { margin-top: 0; }
+ .folder-list { margin: 15px 0; }
+ .folder-item { display: flex; align-items: center; gap: 10px; padding: 10px; border-radius: 4px; cursor: pointer; margin-bottom: 5px; }
+ .folder-item:hover { background: white; }
+ .folder-count { margin-left: auto; color: #666; font-size: 12px; }
+ .btn-secondary { width: 100%; padding: 8px; background: white; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
+ .lists-main { flex: 1; }
+ .lists-toolbar { display: flex; gap: 10px; margin-bottom: 20px; }
+ .search-input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
+ .sort-select { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
+ .lists-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
+ .list-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; }
+ .list-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
+ .list-menu { cursor: pointer; font-size: 20px; }
+ .list-stats { display: flex; gap: 20px; margin-bottom: 15px; }
+ .list-stat { display: flex; gap: 10px; align-items: center; }
+ .stat-icon { font-size: 24px; }
+ .stat-num { font-size: 20px; font-weight: bold; }
+ .stat-text { font-size: 12px; color: #666; }
+ .list-folder { padding: 8px; background: #f8f9fa; border-radius: 4px; margin-bottom: 15px; font-size: 14px; }
+ .list-actions { display: flex; gap: 5px; flex-wrap: wrap; }
+ .action-btn { padding: 6px 12px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/report-dashboard.ts b/servers/brevo/src/apps/report-dashboard.ts
new file mode 100644
index 0000000..184939f
--- /dev/null
+++ b/servers/brevo/src/apps/report-dashboard.ts
@@ -0,0 +1,144 @@
+export function reportDashboardApp() {
+ return {
+ name: 'report-dashboard',
+ description: 'Comprehensive analytics and reporting dashboard with charts and insights',
+ ui: {
+ title: 'Reports & Analytics',
+ content: `
+# Reports Dashboard
+
+
+
+ Last 7 Days
+ Last 30 Days
+ Last 90 Days
+ Custom Range
+
+ ๐ฅ Export Report
+
+
+
+
+
+
{{totalSends}}
+
๐
+
+
+
+
+
{{openRate}}%
+
๐
+
+
+
+
+
{{clickRate}}%
+
๐
+
+
+
+
+
{{conversionRate}}%
+
๐ฐ
+
+
+
+## Performance Over Time
+
+
+ ๐ Email Performance Chart
+
+ Opens
+ Clicks
+ Bounces
+
+
+
+
+## Top Performing Campaigns
+
+
+
+ Campaign
+ Sent
+ Opens
+ Clicks
+ Conv. Rate
+ Revenue
+
+
+
+ {{#each topCampaigns}}
+
+ {{this.name}}
+ {{this.sent}}
+ {{this.opens}} ({{this.openRate}}%)
+ {{this.clicks}} ({{this.clickRate}}%)
+ {{this.conversionRate}}%
+ \${{this.revenue}}
+
+ {{/each}}
+
+
+
+## Engagement by Device
+
+
+
๐ป
+
Desktop
+
{{desktopPercent}}%
+
+
+
๐ฑ
+
Mobile
+
{{mobilePercent}}%
+
+
+
๐ง
+
Webmail
+
{{webmailPercent}}%
+
+
+`,
+ style: `
+ .report-period { display: flex; justify-content: space-between; margin-bottom: 30px; }
+ .period-select { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
+ .btn-export { padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
+ .kpi-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; }
+ .kpi-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
+ .kpi-trend { font-size: 12px; font-weight: bold; }
+ .kpi-trend.up { color: #28a745; }
+ .kpi-trend.down { color: #dc3545; }
+ .kpi-value { font-size: 36px; font-weight: bold; color: #007bff; margin-bottom: 10px; }
+ .kpi-chart { font-size: 30px; }
+ .chart-container { padding: 30px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 30px; }
+ .chart-placeholder { height: 300px; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #f8f9fa; border-radius: 4px; font-size: 24px; color: #666; }
+ .chart-legend { display: flex; gap: 20px; margin-top: 20px; font-size: 14px; }
+ .legend-item { display: flex; align-items: center; gap: 5px; }
+ .legend-color { width: 15px; height: 15px; border-radius: 3px; }
+ .report-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
+ .report-table th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: bold; }
+ .report-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
+ .rate-badge { padding: 4px 8px; background: #d4edda; color: #155724; border-radius: 4px; font-weight: bold; }
+ .device-stats { display: flex; gap: 30px; justify-content: center; }
+ .device-stat { text-align: center; padding: 20px; background: #f8f9fa; border-radius: 8px; min-width: 150px; }
+ .device-icon { font-size: 48px; margin-bottom: 10px; }
+ .device-name { color: #666; margin-bottom: 5px; }
+ .device-percent { font-size: 24px; font-weight: bold; color: #007bff; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/sms-dashboard.ts b/servers/brevo/src/apps/sms-dashboard.ts
new file mode 100644
index 0000000..c8ba742
--- /dev/null
+++ b/servers/brevo/src/apps/sms-dashboard.ts
@@ -0,0 +1,119 @@
+export function smsDashboardApp() {
+ return {
+ name: 'sms-dashboard',
+ description: 'SMS campaign management dashboard with delivery tracking and analytics',
+ ui: {
+ title: 'SMS Dashboard',
+ content: `
+# SMS Campaigns
+
+
+
+
๐ฑ
+
+
{{totalSms}}
+
Total SMS Sent
+
+
+
+
+
โ
+
+
{{deliveryRate}}%
+
Delivery Rate
+
+
+
+
+
๐
+
+
{{clickRate}}%
+
Click Rate
+
+
+
+
+
๐ฐ
+
+
\${{costThisMonth}}
+
Cost This Month
+
+
+
+
+## SMS Campaigns
+{{#each campaigns}}
+
+
+
+
{{this.content}}
+
+
+
+ Recipients:
+ {{this.stats.recipients}}
+
+
+ Delivered:
+ {{this.stats.delivered}}
+
+
+ Clicked:
+ {{this.stats.clicked}}
+
+
+ Failed:
+ {{this.stats.failed}}
+
+
+
+
+
+{{/each}}
+
+## Quick Actions
+- [Create SMS Campaign](#new-sms)
+- [View All Reports](#sms-reports)
+- [Manage Senders](#sms-senders)
+`,
+ style: `
+ .sms-overview { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
+ .sms-metric { display: flex; gap: 15px; padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; }
+ .metric-icon { font-size: 48px; }
+ .metric-value { font-size: 28px; font-weight: bold; color: #007bff; }
+ .metric-label { color: #666; font-size: 13px; }
+ .sms-campaign-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 15px; }
+ .campaign-header-row { display: flex; justify-content: space-between; align-items: start; margin-bottom: 15px; }
+ .campaign-sender { color: #666; font-size: 14px; }
+ .status-badge { padding: 5px 12px; border-radius: 4px; font-size: 12px; font-weight: bold; }
+ .status-sent { background: #28a745; color: white; }
+ .status-draft { background: #ffc107; color: black; }
+ .status-queued { background: #17a2b8; color: white; }
+ .campaign-content { padding: 15px; background: #f8f9fa; border-radius: 4px; margin-bottom: 15px; font-family: monospace; }
+ .campaign-stats-row { display: flex; gap: 30px; margin-bottom: 15px; padding: 15px; background: #f8f9fa; border-radius: 4px; }
+ .stat-item { display: flex; gap: 5px; }
+ .stat-label { color: #666; }
+ .stat-value { font-weight: bold; }
+ .campaign-footer { display: flex; justify-content: space-between; align-items: center; }
+ .campaign-date { color: #999; font-size: 14px; }
+ .campaign-actions { display: flex; gap: 10px; }
+ .btn-sm { padding: 6px 12px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; }
+ .btn-primary { background: #007bff; color: white; border-color: #007bff; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/template-gallery.ts b/servers/brevo/src/apps/template-gallery.ts
new file mode 100644
index 0000000..00c8f36
--- /dev/null
+++ b/servers/brevo/src/apps/template-gallery.ts
@@ -0,0 +1,94 @@
+export function templateGalleryApp() {
+ return {
+ name: 'email-template-gallery',
+ description: 'Browse, preview, and manage email templates with visual gallery',
+ ui: {
+ title: 'Email Template Gallery',
+ content: `
+# Email Templates
+
+
+
+
+ {{#each templates}}
+
+
+
+
๐ง
+
{{this.subject}}
+
+
+
+
+
{{this.name}}
+
+ {{this.sender.name}}
+ {{this.modifiedAt}}
+
+
+
+ {{#if this.tag}}
+ {{this.tag}}
+ {{/if}}
+ {{#if this.isActive}}
+ Active
+ {{else}}
+ Inactive
+ {{/if}}
+
+
+
+ ๐๏ธ
+ โ๏ธ
+ ๐
+ ๐ค
+ ๐๏ธ
+
+
+
+ {{/each}}
+
+
+
+`,
+ style: `
+ .gallery-header { display: flex; gap: 15px; align-items: center; margin-bottom: 30px; }
+ .search-bar { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
+ .gallery-filters { display: flex; gap: 5px; }
+ .filter-btn { padding: 8px 16px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; }
+ .filter-btn.active { background: #007bff; color: white; border-color: #007bff; }
+ .btn-create { padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .templates-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 25px; }
+ .template-card { background: white; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; transition: box-shadow 0.3s; }
+ .template-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
+ .template-preview-box { height: 200px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; }
+ .preview-placeholder { text-align: center; color: white; }
+ .preview-icon { font-size: 60px; display: block; margin-bottom: 10px; }
+ .preview-subject { font-size: 14px; opacity: 0.9; }
+ .template-info { padding: 20px; }
+ .template-info h4 { margin: 0 0 10px 0; }
+ .template-meta { display: flex; justify-content: space-between; color: #666; font-size: 12px; margin-bottom: 10px; }
+ .template-tags { display: flex; gap: 5px; margin-bottom: 15px; }
+ .tag { padding: 4px 8px; background: #e0e0e0; border-radius: 4px; font-size: 11px; }
+ .tag-active { background: #d4edda; color: #155724; }
+ .tag-inactive { background: #f8d7da; color: #721c24; }
+ .template-actions { display: flex; gap: 5px; justify-content: space-around; }
+ .action-btn { background: none; border: none; font-size: 20px; cursor: pointer; padding: 5px; }
+ .pagination { display: flex; justify-content: center; gap: 20px; margin-top: 30px; align-items: center; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/transactional-monitor.ts b/servers/brevo/src/apps/transactional-monitor.ts
new file mode 100644
index 0000000..1c5d58f
--- /dev/null
+++ b/servers/brevo/src/apps/transactional-monitor.ts
@@ -0,0 +1,118 @@
+export function transactionalMonitorApp() {
+ return {
+ name: 'transactional-monitor',
+ description: 'Real-time monitoring of transactional emails and SMS with event tracking',
+ ui: {
+ title: 'Transactional Monitor',
+ content: `
+# Transactional Email & SMS Monitor
+
+
+ Email Events
+ SMS Events
+ Statistics
+
+
+
+
+
๐ง
+
+
{{emailsSentToday}}
+
Emails Today
+
+
+
+
+
โ
+
+
{{deliveryRate}}%
+
Delivery Rate
+
+
+
+
+
๐ฑ
+
+
{{smsSentToday}}
+
SMS Today
+
+
+
+
+
โ ๏ธ
+
+
{{bounceRate}}%
+
Bounce Rate
+
+
+
+
+## Recent Events
+
+
+ All Events
+ Delivered
+ Opened
+ Clicked
+ Bounced
+ Spam
+
+
+
+
+
+
+
+ Time
+ Event
+ Recipient
+ Subject/Content
+ Template
+ Status
+
+
+
+ {{#each events}}
+
+ {{this.date}}
+ {{this.event}}
+ {{this.email}}
+ {{this.subject}}
+ {{this.templateId}}
+ {{this.status}}
+
+ {{/each}}
+
+
+
+
+`,
+ style: `
+ .monitor-tabs { display: flex; gap: 10px; margin-bottom: 20px; }
+ .tab { padding: 10px 20px; border: none; background: #f8f9fa; border-radius: 4px; cursor: pointer; }
+ .tab.active { background: #007bff; color: white; }
+ .monitor-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 30px; }
+ .monitor-card { display: flex; gap: 15px; padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; }
+ .card-icon { font-size: 40px; }
+ .card-value { font-size: 28px; font-weight: bold; }
+ .card-label { color: #666; font-size: 14px; }
+ .events-filter { display: flex; gap: 10px; margin-bottom: 20px; }
+ .filter-input { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
+ .events-table { width: 100%; border-collapse: collapse; }
+ .events-table th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: bold; }
+ .events-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
+ .event-badge { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; }
+ .event-delivered { background: #d4edda; color: #155724; }
+ .event-opened { background: #d1ecf1; color: #0c5460; }
+ .event-clicked { background: #cce5ff; color: #004085; }
+ .event-bounced { background: #f8d7da; color: #721c24; }
+ .subject-col { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+ .pagination { display: flex; justify-content: center; gap: 20px; margin-top: 20px; align-items: center; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/apps/webhook-manager.ts b/servers/brevo/src/apps/webhook-manager.ts
new file mode 100644
index 0000000..cb87944
--- /dev/null
+++ b/servers/brevo/src/apps/webhook-manager.ts
@@ -0,0 +1,149 @@
+export function webhookManagerApp() {
+ return {
+ name: 'webhook-manager',
+ description: 'Manage webhooks for real-time event notifications and integrations',
+ ui: {
+ title: 'Webhook Manager',
+ content: `
+# Webhook Manager
+
+
+
+
+ All Webhooks
+ Marketing
+ Transactional
+
+
+## Configured Webhooks
+{{#each webhooks}}
+
+
+
+
{{this.type}}
+
+
+
Events:
+
+ {{#each this.events}}
+ {{this}}
+ {{/each}}
+
+
+
+
+
+
+ Test Webhook
+ View Logs
+ Edit
+ Delete
+
+
+{{/each}}
+
+## Available Events
+
+
+
Email Events
+
+ delivered - Email successfully delivered
+ opened - Email opened by recipient
+ clicked - Link clicked in email
+ soft_bounce - Temporary delivery failure
+ hard_bounce - Permanent delivery failure
+ spam - Marked as spam
+ unsubscribed - Recipient unsubscribed
+
+
+
+
+
SMS Events
+
+ sms_delivered - SMS successfully delivered
+ sms_failed - SMS delivery failed
+ sms_replied - Recipient replied to SMS
+
+
+
+
+
Contact Events
+
+ contact_created - New contact added
+ contact_updated - Contact information updated
+ contact_deleted - Contact removed
+ list_addition - Contact added to list
+
+
+
+`,
+ style: `
+ .webhook-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
+ .webhook-info p { color: #666; margin: 5px 0 0 0; }
+ .btn-primary { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .webhook-types { display: flex; gap: 10px; margin-bottom: 30px; }
+ .type-btn { padding: 8px 16px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; }
+ .type-btn.active { background: #007bff; color: white; border-color: #007bff; }
+ .webhook-card { padding: 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 20px; }
+ .webhook-card-header { display: flex; justify-content: space-between; margin-bottom: 15px; }
+ .webhook-url { color: #007bff; font-family: monospace; font-size: 14px; margin-top: 5px; word-break: break-all; }
+ .webhook-status { display: flex; align-items: center; gap: 10px; }
+ .status-indicator { width: 12px; height: 12px; border-radius: 50%; background: #28a745; }
+ .status-indicator.inactive { background: #dc3545; }
+ .btn-menu { background: none; border: none; font-size: 20px; cursor: pointer; }
+ .webhook-type-badge { display: inline-block; padding: 4px 10px; background: #e7f3ff; color: #007bff; border-radius: 4px; font-size: 12px; font-weight: bold; margin-bottom: 15px; }
+ .webhook-events { margin-bottom: 15px; }
+ .event-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
+ .event-tag { padding: 4px 8px; background: #f8f9fa; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; font-family: monospace; }
+ .webhook-meta { display: flex; flex-wrap: wrap; gap: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; margin-bottom: 15px; }
+ .meta-item { display: flex; gap: 5px; font-size: 14px; }
+ .meta-label { color: #666; }
+ .badge-info { padding: 4px 8px; background: #d1ecf1; color: #0c5460; border-radius: 4px; font-size: 12px; }
+ .badge-secure { padding: 4px 8px; background: #d4edda; color: #155724; border-radius: 4px; font-size: 12px; }
+ .webhook-actions { display: flex; gap: 10px; }
+ .action-btn { padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
+ .action-btn.danger { background: #dc3545; }
+ .events-reference { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 30px; }
+ .event-category { padding: 20px; background: #f8f9fa; border-radius: 8px; }
+ .event-category h4 { margin-top: 0; }
+ .event-category ul { list-style: none; padding: 0; }
+ .event-category li { padding: 8px 0; border-bottom: 1px solid #e0e0e0; }
+ .event-category code { background: white; padding: 2px 6px; border-radius: 3px; color: #007bff; }
+ `,
+ },
+ };
+}
diff --git a/servers/brevo/src/index.ts b/servers/brevo/src/index.ts
index e779557..087390b 100644
--- a/servers/brevo/src/index.ts
+++ b/servers/brevo/src/index.ts
@@ -1,393 +1,3 @@
-#!/usr/bin/env node
-import { Server } from "@modelcontextprotocol/sdk/server/index.js";
-import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
-import {
- CallToolRequestSchema,
- ListToolsRequestSchema,
-} from "@modelcontextprotocol/sdk/types.js";
-
-// ============================================
-// CONFIGURATION
-// ============================================
-const MCP_NAME = "brevo";
-const MCP_VERSION = "1.0.0";
-const API_BASE_URL = "https://api.brevo.com/v3";
-
-// ============================================
-// API CLIENT - Brevo uses api-key header
-// ============================================
-class BrevoClient {
- private apiKey: string;
- private baseUrl: string;
-
- constructor(apiKey: string) {
- this.apiKey = apiKey;
- this.baseUrl = API_BASE_URL;
- }
-
- async request(endpoint: string, options: RequestInit = {}) {
- const url = `${this.baseUrl}${endpoint}`;
- const response = await fetch(url, {
- ...options,
- headers: {
- "api-key": this.apiKey,
- "Content-Type": "application/json",
- "Accept": "application/json",
- ...options.headers,
- },
- });
-
- if (!response.ok) {
- const text = await response.text();
- throw new Error(`Brevo API error: ${response.status} ${response.statusText} - ${text}`);
- }
-
- // Some endpoints return 204 No Content
- if (response.status === 204) {
- return { success: true };
- }
-
- return response.json();
- }
-
- async get(endpoint: string) {
- return this.request(endpoint, { method: "GET" });
- }
-
- async post(endpoint: string, data: any) {
- return this.request(endpoint, {
- method: "POST",
- body: JSON.stringify(data),
- });
- }
-
- async put(endpoint: string, data: any) {
- return this.request(endpoint, {
- method: "PUT",
- body: JSON.stringify(data),
- });
- }
-
- async delete(endpoint: string) {
- return this.request(endpoint, { method: "DELETE" });
- }
-}
-
-// ============================================
-// TOOL DEFINITIONS
-// ============================================
-const tools = [
- {
- name: "send_email",
- description: "Send a transactional email",
- inputSchema: {
- type: "object" as const,
- properties: {
- to: {
- type: "array",
- description: "Array of recipient objects with email and optional name",
- items: {
- type: "object",
- properties: {
- email: { type: "string" },
- name: { type: "string" }
- },
- required: ["email"]
- }
- },
- sender: {
- type: "object",
- description: "Sender object with email and optional name",
- properties: {
- email: { type: "string" },
- name: { type: "string" }
- },
- required: ["email"]
- },
- subject: { type: "string", description: "Email subject" },
- htmlContent: { type: "string", description: "HTML content of the email" },
- textContent: { type: "string", description: "Plain text content" },
- templateId: { type: "number", description: "Template ID to use instead of content" },
- params: { type: "object", description: "Template parameters" },
- replyTo: { type: "object", description: "Reply-to address" },
- attachment: { type: "array", description: "Array of attachment objects" },
- tags: { type: "array", items: { type: "string" }, description: "Tags for the email" },
- },
- required: ["to", "sender"],
- },
- },
- {
- name: "list_contacts",
- description: "List contacts with optional filters",
- inputSchema: {
- type: "object" as const,
- properties: {
- limit: { type: "number", description: "Number of contacts to return (default 50, max 1000)" },
- offset: { type: "number", description: "Pagination offset" },
- modifiedSince: { type: "string", description: "Filter by modification date (YYYY-MM-DD)" },
- sort: { type: "string", description: "Sort order (asc or desc)" },
- },
- },
- },
- {
- name: "add_contact",
- description: "Create a new contact",
- inputSchema: {
- type: "object" as const,
- properties: {
- email: { type: "string", description: "Contact email address" },
- attributes: { type: "object", description: "Contact attributes (FIRSTNAME, LASTNAME, SMS, etc.)" },
- listIds: { type: "array", items: { type: "number" }, description: "List IDs to add contact to" },
- updateEnabled: { type: "boolean", description: "Update contact if already exists" },
- smtpBlacklistSender: { type: "array", items: { type: "string" }, description: "Blacklisted senders" },
- },
- required: ["email"],
- },
- },
- {
- name: "update_contact",
- description: "Update an existing contact",
- inputSchema: {
- type: "object" as const,
- properties: {
- identifier: { type: "string", description: "Email or contact ID" },
- attributes: { type: "object", description: "Contact attributes to update" },
- listIds: { type: "array", items: { type: "number" }, description: "List IDs to add contact to" },
- unlinkListIds: { type: "array", items: { type: "number" }, description: "List IDs to remove contact from" },
- emailBlacklisted: { type: "boolean", description: "Blacklist the contact email" },
- smsBlacklisted: { type: "boolean", description: "Blacklist the contact SMS" },
- },
- required: ["identifier"],
- },
- },
- {
- name: "list_campaigns",
- description: "List email campaigns",
- inputSchema: {
- type: "object" as const,
- properties: {
- type: { type: "string", description: "Campaign type (classic, trigger)" },
- status: { type: "string", description: "Campaign status (suspended, archive, sent, queued, draft, inProcess)" },
- limit: { type: "number", description: "Number of results (default 50, max 1000)" },
- offset: { type: "number", description: "Pagination offset" },
- sort: { type: "string", description: "Sort order (asc or desc)" },
- },
- },
- },
- {
- name: "create_campaign",
- description: "Create a new email campaign",
- inputSchema: {
- type: "object" as const,
- properties: {
- name: { type: "string", description: "Campaign name" },
- subject: { type: "string", description: "Email subject" },
- sender: {
- type: "object",
- description: "Sender object with email and name",
- properties: {
- email: { type: "string" },
- name: { type: "string" }
- },
- required: ["email", "name"]
- },
- htmlContent: { type: "string", description: "HTML content" },
- templateId: { type: "number", description: "Template ID to use" },
- recipients: {
- type: "object",
- description: "Recipients configuration",
- properties: {
- listIds: { type: "array", items: { type: "number" } },
- exclusionListIds: { type: "array", items: { type: "number" } }
- }
- },
- scheduledAt: { type: "string", description: "Schedule time (ISO 8601)" },
- replyTo: { type: "string", description: "Reply-to email address" },
- toField: { type: "string", description: "Personalization field for To header" },
- tag: { type: "string", description: "Campaign tag" },
- },
- required: ["name", "subject", "sender"],
- },
- },
- {
- name: "send_sms",
- description: "Send a transactional SMS",
- inputSchema: {
- type: "object" as const,
- properties: {
- sender: { type: "string", description: "Sender name (max 11 chars) or phone number" },
- recipient: { type: "string", description: "Recipient phone number with country code" },
- content: { type: "string", description: "SMS message content (max 160 chars for single SMS)" },
- type: { type: "string", description: "SMS type: transactional or marketing" },
- tag: { type: "string", description: "Tag for the SMS" },
- webUrl: { type: "string", description: "Webhook URL for delivery report" },
- },
- required: ["sender", "recipient", "content"],
- },
- },
- {
- name: "list_templates",
- description: "List email templates",
- inputSchema: {
- type: "object" as const,
- properties: {
- templateStatus: { type: "boolean", description: "Filter by active status" },
- limit: { type: "number", description: "Number of results (default 50, max 1000)" },
- offset: { type: "number", description: "Pagination offset" },
- sort: { type: "string", description: "Sort order (asc or desc)" },
- },
- },
- },
-];
-
-// ============================================
-// TOOL HANDLERS
-// ============================================
-async function handleTool(client: BrevoClient, name: string, args: any) {
- switch (name) {
- case "send_email": {
- const payload: any = {
- to: args.to,
- sender: args.sender,
- };
- if (args.subject) payload.subject = args.subject;
- if (args.htmlContent) payload.htmlContent = args.htmlContent;
- if (args.textContent) payload.textContent = args.textContent;
- if (args.templateId) payload.templateId = args.templateId;
- if (args.params) payload.params = args.params;
- if (args.replyTo) payload.replyTo = args.replyTo;
- if (args.attachment) payload.attachment = args.attachment;
- if (args.tags) payload.tags = args.tags;
- return await client.post("/smtp/email", payload);
- }
-
- case "list_contacts": {
- const params = new URLSearchParams();
- if (args.limit) params.append("limit", String(args.limit));
- if (args.offset) params.append("offset", String(args.offset));
- if (args.modifiedSince) params.append("modifiedSince", args.modifiedSince);
- if (args.sort) params.append("sort", args.sort);
- const query = params.toString();
- return await client.get(`/contacts${query ? `?${query}` : ""}`);
- }
-
- case "add_contact": {
- const payload: any = {
- email: args.email,
- };
- if (args.attributes) payload.attributes = args.attributes;
- if (args.listIds) payload.listIds = args.listIds;
- if (args.updateEnabled !== undefined) payload.updateEnabled = args.updateEnabled;
- if (args.smtpBlacklistSender) payload.smtpBlacklistSender = args.smtpBlacklistSender;
- return await client.post("/contacts", payload);
- }
-
- case "update_contact": {
- const payload: any = {};
- if (args.attributes) payload.attributes = args.attributes;
- if (args.listIds) payload.listIds = args.listIds;
- if (args.unlinkListIds) payload.unlinkListIds = args.unlinkListIds;
- if (args.emailBlacklisted !== undefined) payload.emailBlacklisted = args.emailBlacklisted;
- if (args.smsBlacklisted !== undefined) payload.smsBlacklisted = args.smsBlacklisted;
- return await client.put(`/contacts/${encodeURIComponent(args.identifier)}`, payload);
- }
-
- case "list_campaigns": {
- const params = new URLSearchParams();
- if (args.type) params.append("type", args.type);
- if (args.status) params.append("status", args.status);
- if (args.limit) params.append("limit", String(args.limit));
- if (args.offset) params.append("offset", String(args.offset));
- if (args.sort) params.append("sort", args.sort);
- const query = params.toString();
- return await client.get(`/emailCampaigns${query ? `?${query}` : ""}`);
- }
-
- case "create_campaign": {
- const payload: any = {
- name: args.name,
- subject: args.subject,
- sender: args.sender,
- };
- if (args.htmlContent) payload.htmlContent = args.htmlContent;
- if (args.templateId) payload.templateId = args.templateId;
- if (args.recipients) payload.recipients = args.recipients;
- if (args.scheduledAt) payload.scheduledAt = args.scheduledAt;
- if (args.replyTo) payload.replyTo = args.replyTo;
- if (args.toField) payload.toField = args.toField;
- if (args.tag) payload.tag = args.tag;
- return await client.post("/emailCampaigns", payload);
- }
-
- case "send_sms": {
- const payload: any = {
- sender: args.sender,
- recipient: args.recipient,
- content: args.content,
- };
- if (args.type) payload.type = args.type;
- if (args.tag) payload.tag = args.tag;
- if (args.webUrl) payload.webUrl = args.webUrl;
- return await client.post("/transactionalSMS/sms", payload);
- }
-
- case "list_templates": {
- const params = new URLSearchParams();
- if (args.templateStatus !== undefined) params.append("templateStatus", String(args.templateStatus));
- if (args.limit) params.append("limit", String(args.limit));
- if (args.offset) params.append("offset", String(args.offset));
- if (args.sort) params.append("sort", args.sort);
- const query = params.toString();
- return await client.get(`/smtp/templates${query ? `?${query}` : ""}`);
- }
-
- default:
- throw new Error(`Unknown tool: ${name}`);
- }
-}
-
-// ============================================
-// SERVER SETUP
-// ============================================
-async function main() {
- const apiKey = process.env.BREVO_API_KEY;
-
- if (!apiKey) {
- console.error("Error: BREVO_API_KEY environment variable required");
- process.exit(1);
- }
-
- const client = new BrevoClient(apiKey);
-
- const server = new Server(
- { name: `${MCP_NAME}-mcp`, version: MCP_VERSION },
- { capabilities: { tools: {} } }
- );
-
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
- tools,
- }));
-
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
- const { name, arguments: args } = request.params;
-
- try {
- const result = await handleTool(client, name, args || {});
- return {
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
- };
- } catch (error) {
- const message = error instanceof Error ? error.message : String(error);
- return {
- content: [{ type: "text", text: `Error: ${message}` }],
- isError: true,
- };
- }
- });
-
- const transport = new StdioServerTransport();
- await server.connect(transport);
- console.error(`${MCP_NAME} MCP server running on stdio`);
-}
-
-main().catch(console.error);
+export * from './types/index.js';
+export * from './types/api-client.js';
+export * from './server.js';
diff --git a/servers/brevo/src/main.ts b/servers/brevo/src/main.ts
new file mode 100644
index 0000000..53650f4
--- /dev/null
+++ b/servers/brevo/src/main.ts
@@ -0,0 +1,23 @@
+#!/usr/bin/env node
+
+import { BrevoServer } from './server.js';
+
+const apiKey = process.env.BREVO_API_KEY;
+
+if (!apiKey) {
+ console.error('Error: BREVO_API_KEY environment variable is required');
+ console.error('');
+ console.error('Usage:');
+ console.error(' export BREVO_API_KEY=your-api-key-here');
+ console.error(' brevo-mcp');
+ console.error('');
+ console.error('Or provide it inline:');
+ console.error(' BREVO_API_KEY=your-api-key-here brevo-mcp');
+ process.exit(1);
+}
+
+const server = new BrevoServer(apiKey);
+server.run().catch((error) => {
+ console.error('Fatal error running server:', error);
+ process.exit(1);
+});
diff --git a/servers/brevo/src/server.ts b/servers/brevo/src/server.ts
new file mode 100644
index 0000000..64f3d94
--- /dev/null
+++ b/servers/brevo/src/server.ts
@@ -0,0 +1,205 @@
+import { Server } from '@modelcontextprotocol/sdk/server/index.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import {
+ CallToolRequestSchema,
+ ListToolsRequestSchema,
+ ListResourcesRequestSchema,
+ ReadResourceRequestSchema,
+} from '@modelcontextprotocol/sdk/types.js';
+import { BrevoApiClient } from './types/api-client.js';
+import { createContactsTools } from './tools/contacts-tools.js';
+import { createCampaignsTools } from './tools/campaigns-tools.js';
+import { createTransactionalTools } from './tools/transactional-tools.js';
+import { createListsTools } from './tools/lists-tools.js';
+import { createSendersTools } from './tools/senders-tools.js';
+import { createTemplatesTools } from './tools/templates-tools.js';
+import { createAutomationsTools } from './tools/automations-tools.js';
+import { createSmsTools } from './tools/sms-tools.js';
+import { createDealsTools } from './tools/deals-tools.js';
+import { createWebhooksTools } from './tools/webhooks-tools.js';
+import { contactDashboardApp } from './apps/contact-dashboard.js';
+import { contactDetailApp } from './apps/contact-detail.js';
+import { contactGridApp } from './apps/contact-grid.js';
+import { campaignDashboardApp } from './apps/campaign-dashboard.js';
+import { campaignBuilderApp } from './apps/campaign-builder.js';
+import { automationDashboardApp } from './apps/automation-dashboard.js';
+import { dealPipelineApp } from './apps/deal-pipeline.js';
+import { transactionalMonitorApp } from './apps/transactional-monitor.js';
+import { templateGalleryApp } from './apps/template-gallery.js';
+import { smsDashboardApp } from './apps/sms-dashboard.js';
+import { listManagerApp } from './apps/list-manager.js';
+import { reportDashboardApp } from './apps/report-dashboard.js';
+import { webhookManagerApp } from './apps/webhook-manager.js';
+import { importWizardApp } from './apps/import-wizard.js';
+
+export class BrevoServer {
+ private server: Server;
+ private client: BrevoApiClient;
+ private tools: any[];
+ private apps: any[];
+
+ constructor(apiKey: string) {
+ this.server = new Server(
+ {
+ name: 'brevo-server',
+ version: '1.0.0',
+ },
+ {
+ capabilities: {
+ tools: {},
+ resources: {},
+ },
+ }
+ );
+
+ this.client = new BrevoApiClient({ apiKey });
+
+ // Initialize all tools
+ this.tools = [
+ ...createContactsTools(this.client),
+ ...createCampaignsTools(this.client),
+ ...createTransactionalTools(this.client),
+ ...createListsTools(this.client),
+ ...createSendersTools(this.client),
+ ...createTemplatesTools(this.client),
+ ...createAutomationsTools(this.client),
+ ...createSmsTools(this.client),
+ ...createDealsTools(this.client),
+ ...createWebhooksTools(this.client),
+ ];
+
+ // Initialize all apps
+ this.apps = [
+ contactDashboardApp(),
+ contactDetailApp(),
+ contactGridApp(),
+ campaignDashboardApp(),
+ campaignBuilderApp(),
+ automationDashboardApp(),
+ dealPipelineApp(),
+ transactionalMonitorApp(),
+ templateGalleryApp(),
+ smsDashboardApp(),
+ listManagerApp(),
+ reportDashboardApp(),
+ webhookManagerApp(),
+ importWizardApp(),
+ ];
+
+ this.setupHandlers();
+ }
+
+ private setupHandlers() {
+ // List available tools
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
+ return {
+ tools: this.tools.map(tool => ({
+ name: tool.name,
+ description: tool.description,
+ inputSchema: {
+ type: 'object',
+ properties: tool.inputSchema.shape,
+ required: Object.keys(tool.inputSchema.shape).filter(
+ key => !tool.inputSchema.shape[key].isOptional()
+ ),
+ },
+ })),
+ };
+ });
+
+ // Execute tool
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const tool = this.tools.find(t => t.name === request.params.name);
+ if (!tool) {
+ throw new Error(`Tool not found: ${request.params.name}`);
+ }
+
+ try {
+ const result = await tool.execute(request.params.arguments || {});
+ return {
+ content: [
+ {
+ type: 'text',
+ text: JSON.stringify(result, null, 2),
+ },
+ ],
+ };
+ } catch (error: any) {
+ return {
+ content: [
+ {
+ type: 'text',
+ text: `Error: ${error.message}`,
+ },
+ ],
+ isError: true,
+ };
+ }
+ });
+
+ // List resources (MCP apps)
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
+ return {
+ resources: this.apps.map(app => ({
+ uri: `brevo://app/${app.name}`,
+ name: app.name,
+ description: app.description,
+ mimeType: 'text/html',
+ })),
+ };
+ });
+
+ // Read resource (render app UI)
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
+ const appName = request.params.uri.replace('brevo://app/', '');
+ const app = this.apps.find(a => a.name === appName);
+
+ if (!app) {
+ throw new Error(`App not found: ${appName}`);
+ }
+
+ const html = `
+
+
+
+
+
+ ${app.ui.title}
+
+
+
+ ${app.ui.content}
+
+
+ `;
+
+ return {
+ contents: [
+ {
+ uri: request.params.uri,
+ mimeType: 'text/html',
+ text: html,
+ },
+ ],
+ };
+ });
+ }
+
+ async run() {
+ const transport = new StdioServerTransport();
+ await this.server.connect(transport);
+ console.error('Brevo MCP Server running on stdio');
+ }
+}
diff --git a/servers/brevo/src/tools/automations-tools.ts b/servers/brevo/src/tools/automations-tools.ts
new file mode 100644
index 0000000..e5431eb
--- /dev/null
+++ b/servers/brevo/src/tools/automations-tools.ts
@@ -0,0 +1,77 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { Workflow, WorkflowStats } from '../types/index.js';
+
+export function createAutomationsTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_workflows',
+ description: 'List all marketing automation workflows',
+ inputSchema: z.object({
+ limit: z.number().optional().describe('Number of workflows (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
+ status: z.enum(['draft', 'active', 'inactive']).optional().describe('Filter by status'),
+ }),
+ execute: async (args: any) => {
+ const params: any = {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ };
+ if (args.sort) params.sort = args.sort;
+ if (args.status) params.status = args.status;
+
+ const result = await client.getPaginated('/automation/workflows', params);
+ return {
+ workflows: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_workflow',
+ description: 'Get details of a specific workflow',
+ inputSchema: z.object({
+ workflowId: z.number().describe('Workflow ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/automation/workflows/${args.workflowId}`);
+ },
+ },
+ {
+ name: 'brevo_activate_workflow',
+ description: 'Activate a workflow',
+ inputSchema: z.object({
+ workflowId: z.number().describe('Workflow ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.patch(`/automation/workflows/${args.workflowId}`, {
+ status: 'active',
+ });
+ },
+ },
+ {
+ name: 'brevo_deactivate_workflow',
+ description: 'Deactivate a workflow',
+ inputSchema: z.object({
+ workflowId: z.number().describe('Workflow ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.patch(`/automation/workflows/${args.workflowId}`, {
+ status: 'inactive',
+ });
+ },
+ },
+ {
+ name: 'brevo_get_workflow_stats',
+ description: 'Get statistics for a workflow',
+ inputSchema: z.object({
+ workflowId: z.number().describe('Workflow ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/automation/workflows/${args.workflowId}/stats`);
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/campaigns-tools.ts b/servers/brevo/src/tools/campaigns-tools.ts
new file mode 100644
index 0000000..4c62d26
--- /dev/null
+++ b/servers/brevo/src/tools/campaigns-tools.ts
@@ -0,0 +1,188 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { EmailCampaign, CampaignReport } from '../types/index.js';
+
+export function createCampaignsTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_email_campaigns',
+ description: 'List all email campaigns with filters',
+ inputSchema: z.object({
+ type: z.enum(['classic', 'trigger', 'ab']).optional().describe('Campaign type'),
+ status: z.enum(['draft', 'sent', 'queued', 'suspended', 'archive']).optional().describe('Campaign status'),
+ limit: z.number().optional().describe('Number of campaigns (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
+ }),
+ execute: async (args: any) => {
+ const params: any = { limit: args.limit || 50, offset: args.offset || 0 };
+ if (args.type) params.type = args.type;
+ if (args.status) params.status = args.status;
+ if (args.sort) params.sort = args.sort;
+
+ const result = await client.getPaginated('/emailCampaigns', params);
+ return {
+ campaigns: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_email_campaign',
+ description: 'Get details of a specific email campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/emailCampaigns/${args.campaignId}`);
+ },
+ },
+ {
+ name: 'brevo_create_email_campaign',
+ description: 'Create a new email campaign',
+ inputSchema: z.object({
+ name: z.string().describe('Campaign name'),
+ subject: z.string().describe('Email subject line'),
+ sender: z.object({
+ name: z.string().optional(),
+ email: z.string().email(),
+ }).describe('Sender information'),
+ type: z.enum(['classic', 'trigger', 'ab']).optional().describe('Campaign type (default: classic)'),
+ htmlContent: z.string().optional().describe('HTML content of the email'),
+ templateId: z.number().optional().describe('Template ID to use'),
+ scheduledAt: z.string().optional().describe('Schedule date-time (ISO 8601)'),
+ recipients: z.object({
+ listIds: z.array(z.number()).optional(),
+ exclusionListIds: z.array(z.number()).optional(),
+ segmentIds: z.array(z.number()).optional(),
+ }).optional().describe('Recipients configuration'),
+ replyTo: z.string().email().optional().describe('Reply-to email'),
+ toField: z.string().optional().describe('To field placeholder'),
+ inlineImageActivation: z.boolean().optional().describe('Enable inline images'),
+ mirrorActive: z.boolean().optional().describe('Enable mirror link'),
+ recurring: z.boolean().optional().describe('Is recurring campaign'),
+ abTesting: z.boolean().optional().describe('Enable A/B testing'),
+ subjectA: z.string().optional().describe('Subject line A for A/B test'),
+ subjectB: z.string().optional().describe('Subject line B for A/B test'),
+ splitRule: z.number().optional().describe('A/B test split percentage'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {
+ name: args.name,
+ subject: args.subject,
+ sender: args.sender,
+ };
+ if (args.type) data.type = args.type;
+ if (args.htmlContent) data.htmlContent = args.htmlContent;
+ if (args.templateId) data.templateId = args.templateId;
+ if (args.scheduledAt) data.scheduledAt = args.scheduledAt;
+ if (args.recipients) data.recipients = args.recipients;
+ if (args.replyTo) data.replyTo = args.replyTo;
+ if (args.toField) data.toField = args.toField;
+ if (args.inlineImageActivation !== undefined) data.inlineImageActivation = args.inlineImageActivation;
+ if (args.mirrorActive !== undefined) data.mirrorActive = args.mirrorActive;
+ if (args.recurring !== undefined) data.recurring = args.recurring;
+ if (args.abTesting !== undefined) data.abTesting = args.abTesting;
+ if (args.subjectA) data.subjectA = args.subjectA;
+ if (args.subjectB) data.subjectB = args.subjectB;
+ if (args.splitRule) data.splitRule = args.splitRule;
+
+ return await client.post('/emailCampaigns', data);
+ },
+ },
+ {
+ name: 'brevo_update_email_campaign',
+ description: 'Update an email campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ name: z.string().optional().describe('Campaign name'),
+ subject: z.string().optional().describe('Email subject line'),
+ sender: z.object({
+ name: z.string().optional(),
+ email: z.string().email(),
+ }).optional().describe('Sender information'),
+ htmlContent: z.string().optional().describe('HTML content'),
+ scheduledAt: z.string().optional().describe('Schedule date-time'),
+ recipients: z.object({
+ listIds: z.array(z.number()).optional(),
+ exclusionListIds: z.array(z.number()).optional(),
+ }).optional().describe('Recipients'),
+ replyTo: z.string().email().optional().describe('Reply-to email'),
+ inlineImageActivation: z.boolean().optional().describe('Enable inline images'),
+ mirrorActive: z.boolean().optional().describe('Enable mirror link'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.name) data.name = args.name;
+ if (args.subject) data.subject = args.subject;
+ if (args.sender) data.sender = args.sender;
+ if (args.htmlContent) data.htmlContent = args.htmlContent;
+ if (args.scheduledAt) data.scheduledAt = args.scheduledAt;
+ if (args.recipients) data.recipients = args.recipients;
+ if (args.replyTo) data.replyTo = args.replyTo;
+ if (args.inlineImageActivation !== undefined) data.inlineImageActivation = args.inlineImageActivation;
+ if (args.mirrorActive !== undefined) data.mirrorActive = args.mirrorActive;
+
+ return await client.put(`/emailCampaigns/${args.campaignId}`, data);
+ },
+ },
+ {
+ name: 'brevo_delete_email_campaign',
+ description: 'Delete an email campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.delete(`/emailCampaigns/${args.campaignId}`);
+ },
+ },
+ {
+ name: 'brevo_send_email_campaign',
+ description: 'Send an email campaign immediately',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.post(`/emailCampaigns/${args.campaignId}/sendNow`);
+ },
+ },
+ {
+ name: 'brevo_schedule_email_campaign',
+ description: 'Schedule an email campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ scheduledAt: z.string().describe('Schedule date-time (ISO 8601)'),
+ }),
+ execute: async (args: any) => {
+ return await client.put(`/emailCampaigns/${args.campaignId}`, {
+ scheduledAt: args.scheduledAt,
+ });
+ },
+ },
+ {
+ name: 'brevo_get_campaign_report',
+ description: 'Get detailed report for an email campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/emailCampaigns/${args.campaignId}/report`);
+ },
+ },
+ {
+ name: 'brevo_list_campaign_links',
+ description: 'Get all links from an email campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ }),
+ execute: async (args: any) => {
+ const report = await client.get(`/emailCampaigns/${args.campaignId}/report`);
+ return {
+ links: Object.keys(report.linksStats || {}),
+ linkStats: report.linksStats,
+ };
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/contacts-tools.ts b/servers/brevo/src/tools/contacts-tools.ts
new file mode 100644
index 0000000..e6f79ca
--- /dev/null
+++ b/servers/brevo/src/tools/contacts-tools.ts
@@ -0,0 +1,239 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { Contact, ContactAttribute, ContactList, Folder } from '../types/index.js';
+
+export function createContactsTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_contacts',
+ description: 'List all contacts with optional filters and pagination',
+ inputSchema: z.object({
+ limit: z.number().optional().describe('Number of contacts to return (default: 50)'),
+ offset: z.number().optional().describe('Index of the first contact (default: 0)'),
+ modifiedSince: z.string().optional().describe('Filter contacts modified since date (ISO 8601)'),
+ listIds: z.array(z.number()).optional().describe('Filter by list IDs'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
+ }),
+ execute: async (args: any) => {
+ const params: any = {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ };
+ if (args.modifiedSince) params.modifiedSince = args.modifiedSince;
+ if (args.listIds) params.listIds = args.listIds;
+ if (args.sort) params.sort = args.sort;
+
+ const result = await client.getPaginated('/contacts', params);
+ return {
+ contacts: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_contact',
+ description: 'Get details of a specific contact by email or ID',
+ inputSchema: z.object({
+ identifier: z.string().describe('Contact email address or ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/contacts/${encodeURIComponent(args.identifier)}`);
+ },
+ },
+ {
+ name: 'brevo_create_contact',
+ description: 'Create a new contact',
+ inputSchema: z.object({
+ email: z.string().email().describe('Contact email address'),
+ attributes: z.record(z.any()).optional().describe('Contact attributes (e.g., FIRSTNAME, LASTNAME)'),
+ emailBlacklisted: z.boolean().optional().describe('Whether contact is blacklisted for emails'),
+ smsBlacklisted: z.boolean().optional().describe('Whether contact is blacklisted for SMS'),
+ listIds: z.array(z.number()).optional().describe('List IDs to add the contact to'),
+ updateEnabled: z.boolean().optional().describe('Update contact if already exists'),
+ smtpBlacklistSender: z.array(z.string()).optional().describe('SMTP blacklist sender emails'),
+ }),
+ execute: async (args: any) => {
+ const data: any = { email: args.email };
+ if (args.attributes) data.attributes = args.attributes;
+ if (args.emailBlacklisted !== undefined) data.emailBlacklisted = args.emailBlacklisted;
+ if (args.smsBlacklisted !== undefined) data.smsBlacklisted = args.smsBlacklisted;
+ if (args.listIds) data.listIds = args.listIds;
+ if (args.updateEnabled !== undefined) data.updateEnabled = args.updateEnabled;
+ if (args.smtpBlacklistSender) data.smtpBlacklistSender = args.smtpBlacklistSender;
+
+ return await client.post('/contacts', data);
+ },
+ },
+ {
+ name: 'brevo_update_contact',
+ description: 'Update an existing contact',
+ inputSchema: z.object({
+ identifier: z.string().describe('Contact email address or ID'),
+ attributes: z.record(z.any()).optional().describe('Contact attributes to update'),
+ emailBlacklisted: z.boolean().optional().describe('Whether contact is blacklisted for emails'),
+ smsBlacklisted: z.boolean().optional().describe('Whether contact is blacklisted for SMS'),
+ listIds: z.array(z.number()).optional().describe('List IDs to add the contact to'),
+ unlinkListIds: z.array(z.number()).optional().describe('List IDs to remove the contact from'),
+ smtpBlacklistSender: z.array(z.string()).optional().describe('SMTP blacklist sender emails'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.attributes) data.attributes = args.attributes;
+ if (args.emailBlacklisted !== undefined) data.emailBlacklisted = args.emailBlacklisted;
+ if (args.smsBlacklisted !== undefined) data.smsBlacklisted = args.smsBlacklisted;
+ if (args.listIds) data.listIds = args.listIds;
+ if (args.unlinkListIds) data.unlinkListIds = args.unlinkListIds;
+ if (args.smtpBlacklistSender) data.smtpBlacklistSender = args.smtpBlacklistSender;
+
+ return await client.put(`/contacts/${encodeURIComponent(args.identifier)}`, data);
+ },
+ },
+ {
+ name: 'brevo_delete_contact',
+ description: 'Delete a contact',
+ inputSchema: z.object({
+ identifier: z.string().describe('Contact email address or ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.delete(`/contacts/${encodeURIComponent(args.identifier)}`);
+ },
+ },
+ {
+ name: 'brevo_search_contacts',
+ description: 'Search contacts using advanced filters',
+ inputSchema: z.object({
+ email: z.string().optional().describe('Email to search for'),
+ modifiedSince: z.string().optional().describe('Filter contacts modified since date'),
+ listIds: z.array(z.number()).optional().describe('Filter by list IDs'),
+ limit: z.number().optional().describe('Number of results (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ }),
+ execute: async (args: any) => {
+ const params: any = { limit: args.limit || 50, offset: args.offset || 0 };
+ if (args.email) params.email = args.email;
+ if (args.modifiedSince) params.modifiedSince = args.modifiedSince;
+ if (args.listIds) params.listIds = args.listIds;
+
+ const result = await client.getPaginated('/contacts', params);
+ return {
+ contacts: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_import_contacts',
+ description: 'Import contacts from a file URL',
+ inputSchema: z.object({
+ fileUrl: z.string().url().describe('URL of the file to import (CSV)'),
+ listIds: z.array(z.number()).optional().describe('List IDs to add contacts to'),
+ emailBlacklist: z.boolean().optional().describe('Blacklist emails'),
+ smsBlacklist: z.boolean().optional().describe('Blacklist SMS'),
+ updateExistingContacts: z.boolean().optional().describe('Update existing contacts'),
+ emptyContactsAttributes: z.boolean().optional().describe('Empty contact attributes'),
+ }),
+ execute: async (args: any) => {
+ const data: any = { fileUrl: args.fileUrl };
+ if (args.listIds) data.listIds = args.listIds;
+ if (args.emailBlacklist !== undefined) data.emailBlacklist = args.emailBlacklist;
+ if (args.smsBlacklist !== undefined) data.smsBlacklist = args.smsBlacklist;
+ if (args.updateExistingContacts !== undefined) data.updateExistingContacts = args.updateExistingContacts;
+ if (args.emptyContactsAttributes !== undefined) data.emptyContactsAttributes = args.emptyContactsAttributes;
+
+ return await client.post('/contacts/import', data);
+ },
+ },
+ {
+ name: 'brevo_export_contacts',
+ description: 'Export contacts to a file',
+ inputSchema: z.object({
+ exportAttributes: z.array(z.string()).optional().describe('Attributes to export'),
+ filter: z.object({
+ listIds: z.array(z.number()).optional(),
+ excludedListIds: z.array(z.number()).optional(),
+ modifiedSince: z.string().optional(),
+ }).optional().describe('Filter criteria for export'),
+ notifyUrl: z.string().url().optional().describe('Webhook URL to notify when export is ready'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.exportAttributes) data.exportAttributes = args.exportAttributes;
+ if (args.filter) data.filter = args.filter;
+ if (args.notifyUrl) data.notifyUrl = args.notifyUrl;
+
+ return await client.post('/contacts/export', data);
+ },
+ },
+ {
+ name: 'brevo_list_contact_attributes',
+ description: 'List all contact attributes',
+ inputSchema: z.object({}),
+ execute: async () => {
+ return await client.get<{ attributes: ContactAttribute[] }>('/contacts/attributes');
+ },
+ },
+ {
+ name: 'brevo_create_contact_attribute',
+ description: 'Create a new contact attribute',
+ inputSchema: z.object({
+ name: z.string().describe('Attribute name (e.g., CUSTOM_FIELD)'),
+ category: z.enum(['normal', 'transactional', 'category', 'calculated', 'global']).describe('Attribute category'),
+ type: z.enum(['text', 'date', 'float', 'id', 'boolean']).optional().describe('Attribute type'),
+ enumeration: z.array(z.object({
+ value: z.number(),
+ label: z.string(),
+ })).optional().describe('Enumeration values for category type'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {
+ name: args.name,
+ category: args.category,
+ };
+ if (args.type) data.type = args.type;
+ if (args.enumeration) data.enumeration = args.enumeration;
+
+ return await client.post('/contacts/attributes/normal/' + args.name, data);
+ },
+ },
+ {
+ name: 'brevo_list_contact_folders',
+ description: 'List all contact folders',
+ inputSchema: z.object({
+ limit: z.number().optional().describe('Number of folders (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ }),
+ execute: async (args: any) => {
+ const result = await client.getPaginated('/contacts/folders', {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ });
+ return {
+ folders: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_list_contact_lists',
+ description: 'List all contact lists',
+ inputSchema: z.object({
+ limit: z.number().optional().describe('Number of lists (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ }),
+ execute: async (args: any) => {
+ const result = await client.getPaginated('/contacts/lists', {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ });
+ return {
+ lists: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/deals-tools.ts b/servers/brevo/src/tools/deals-tools.ts
new file mode 100644
index 0000000..082a6e3
--- /dev/null
+++ b/servers/brevo/src/tools/deals-tools.ts
@@ -0,0 +1,122 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { Deal, Pipeline, Stage } from '../types/index.js';
+
+export function createDealsTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_deals',
+ description: 'List all CRM deals',
+ inputSchema: z.object({
+ limit: z.number().optional().describe('Number of deals (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
+ filters: z.object({
+ 'attributes.pipeline': z.string().optional(),
+ 'attributes.deal_stage': z.string().optional(),
+ }).optional().describe('Filter by pipeline or stage'),
+ }),
+ execute: async (args: any) => {
+ const params: any = {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ };
+ if (args.sort) params.sort = args.sort;
+ if (args.filters) {
+ params.filters = JSON.stringify(args.filters);
+ }
+
+ const result = await client.getPaginated('/crm/deals', params);
+ return {
+ deals: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_deal',
+ description: 'Get details of a specific deal',
+ inputSchema: z.object({
+ dealId: z.string().describe('Deal ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/crm/deals/${args.dealId}`);
+ },
+ },
+ {
+ name: 'brevo_create_deal',
+ description: 'Create a new deal',
+ inputSchema: z.object({
+ name: z.string().describe('Deal name'),
+ attributes: z.record(z.any()).optional().describe('Deal attributes (pipeline, stage, amount, etc.)'),
+ linkedContactsIds: z.array(z.number()).optional().describe('Contact IDs to link'),
+ linkedCompaniesIds: z.array(z.string()).optional().describe('Company IDs to link'),
+ }),
+ execute: async (args: any) => {
+ const data: any = { name: args.name };
+ if (args.attributes) data.attributes = args.attributes;
+ if (args.linkedContactsIds) data.linkedContactsIds = args.linkedContactsIds;
+ if (args.linkedCompaniesIds) data.linkedCompaniesIds = args.linkedCompaniesIds;
+
+ return await client.post('/crm/deals', data);
+ },
+ },
+ {
+ name: 'brevo_update_deal',
+ description: 'Update a deal',
+ inputSchema: z.object({
+ dealId: z.string().describe('Deal ID'),
+ name: z.string().optional().describe('Deal name'),
+ attributes: z.record(z.any()).optional().describe('Deal attributes'),
+ linkedContactsIds: z.array(z.number()).optional().describe('Contact IDs'),
+ linkedCompaniesIds: z.array(z.string()).optional().describe('Company IDs'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.name) data.name = args.name;
+ if (args.attributes) data.attributes = args.attributes;
+ if (args.linkedContactsIds) data.linkedContactsIds = args.linkedContactsIds;
+ if (args.linkedCompaniesIds) data.linkedCompaniesIds = args.linkedCompaniesIds;
+
+ return await client.patch(`/crm/deals/${args.dealId}`, data);
+ },
+ },
+ {
+ name: 'brevo_delete_deal',
+ description: 'Delete a deal',
+ inputSchema: z.object({
+ dealId: z.string().describe('Deal ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.delete(`/crm/deals/${args.dealId}`);
+ },
+ },
+ {
+ name: 'brevo_list_pipelines',
+ description: 'List all CRM pipelines',
+ inputSchema: z.object({}),
+ execute: async () => {
+ const result = await client.get<{ pipelines: Pipeline[] }>('/crm/pipeline/details/all');
+ return {
+ pipelines: result.pipelines || [],
+ count: result.pipelines?.length || 0,
+ };
+ },
+ },
+ {
+ name: 'brevo_list_deal_stages',
+ description: 'List all stages for a specific pipeline',
+ inputSchema: z.object({
+ pipelineId: z.string().describe('Pipeline ID'),
+ }),
+ execute: async (args: any) => {
+ const result = await client.get(`/crm/pipeline/details/${args.pipelineId}`);
+ return {
+ pipelineId: args.pipelineId,
+ stages: result || [],
+ };
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/lists-tools.ts b/servers/brevo/src/tools/lists-tools.ts
new file mode 100644
index 0000000..c27502c
--- /dev/null
+++ b/servers/brevo/src/tools/lists-tools.ts
@@ -0,0 +1,105 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { ContactList } from '../types/index.js';
+
+export function createListsTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_lists',
+ description: 'List all contact lists',
+ inputSchema: z.object({
+ limit: z.number().optional().describe('Number of lists (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
+ }),
+ execute: async (args: any) => {
+ const result = await client.getPaginated('/contacts/lists', {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ sort: args.sort,
+ });
+ return {
+ lists: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_list',
+ description: 'Get details of a specific contact list',
+ inputSchema: z.object({
+ listId: z.number().describe('List ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/contacts/lists/${args.listId}`);
+ },
+ },
+ {
+ name: 'brevo_create_list',
+ description: 'Create a new contact list',
+ inputSchema: z.object({
+ name: z.string().describe('List name'),
+ folderId: z.number().optional().describe('Folder ID to place the list in'),
+ }),
+ execute: async (args: any) => {
+ const data: any = { name: args.name };
+ if (args.folderId) data.folderId = args.folderId;
+
+ return await client.post('/contacts/lists', data);
+ },
+ },
+ {
+ name: 'brevo_update_list',
+ description: 'Update a contact list',
+ inputSchema: z.object({
+ listId: z.number().describe('List ID'),
+ name: z.string().optional().describe('New list name'),
+ folderId: z.number().optional().describe('New folder ID'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.name) data.name = args.name;
+ if (args.folderId) data.folderId = args.folderId;
+
+ return await client.put(`/contacts/lists/${args.listId}`, data);
+ },
+ },
+ {
+ name: 'brevo_delete_list',
+ description: 'Delete a contact list',
+ inputSchema: z.object({
+ listId: z.number().describe('List ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.delete(`/contacts/lists/${args.listId}`);
+ },
+ },
+ {
+ name: 'brevo_add_contacts_to_list',
+ description: 'Add contacts to a list',
+ inputSchema: z.object({
+ listId: z.number().describe('List ID'),
+ emails: z.array(z.string().email()).describe('Array of contact emails to add'),
+ }),
+ execute: async (args: any) => {
+ return await client.post(`/contacts/lists/${args.listId}/contacts/add`, {
+ emails: args.emails,
+ });
+ },
+ },
+ {
+ name: 'brevo_remove_contacts_from_list',
+ description: 'Remove contacts from a list',
+ inputSchema: z.object({
+ listId: z.number().describe('List ID'),
+ emails: z.array(z.string().email()).describe('Array of contact emails to remove'),
+ }),
+ execute: async (args: any) => {
+ return await client.post(`/contacts/lists/${args.listId}/contacts/remove`, {
+ emails: args.emails,
+ });
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/senders-tools.ts b/servers/brevo/src/tools/senders-tools.ts
new file mode 100644
index 0000000..24f0c84
--- /dev/null
+++ b/servers/brevo/src/tools/senders-tools.ts
@@ -0,0 +1,109 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { Sender } from '../types/index.js';
+
+export function createSendersTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_senders',
+ description: 'List all senders',
+ inputSchema: z.object({
+ ip: z.string().optional().describe('Filter by dedicated IP'),
+ domain: z.string().optional().describe('Filter by domain'),
+ }),
+ execute: async (args: any) => {
+ const params: any = {};
+ if (args.ip) params.ip = args.ip;
+ if (args.domain) params.domain = args.domain;
+
+ const result = await client.get<{ senders: Sender[] }>('/senders', params);
+ return {
+ senders: result.senders || [],
+ count: result.senders?.length || 0,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_sender',
+ description: 'Get details of a specific sender',
+ inputSchema: z.object({
+ senderId: z.number().describe('Sender ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/senders/${args.senderId}`);
+ },
+ },
+ {
+ name: 'brevo_create_sender',
+ description: 'Create a new sender',
+ inputSchema: z.object({
+ name: z.string().describe('Sender name'),
+ email: z.string().email().describe('Sender email address'),
+ ips: z.array(z.object({
+ ip: z.string(),
+ domain: z.string(),
+ weight: z.number().optional(),
+ })).optional().describe('IPs configuration'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {
+ name: args.name,
+ email: args.email,
+ };
+ if (args.ips) data.ips = args.ips;
+
+ return await client.post('/senders', data);
+ },
+ },
+ {
+ name: 'brevo_update_sender',
+ description: 'Update a sender',
+ inputSchema: z.object({
+ senderId: z.number().describe('Sender ID'),
+ name: z.string().optional().describe('Sender name'),
+ email: z.string().email().optional().describe('Sender email'),
+ ips: z.array(z.object({
+ ip: z.string(),
+ domain: z.string(),
+ weight: z.number().optional(),
+ })).optional().describe('IPs configuration'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.name) data.name = args.name;
+ if (args.email) data.email = args.email;
+ if (args.ips) data.ips = args.ips;
+
+ return await client.put(`/senders/${args.senderId}`, data);
+ },
+ },
+ {
+ name: 'brevo_delete_sender',
+ description: 'Delete a sender',
+ inputSchema: z.object({
+ senderId: z.number().describe('Sender ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.delete(`/senders/${args.senderId}`);
+ },
+ },
+ {
+ name: 'brevo_validate_sender',
+ description: 'Validate sender domain and authentication (SPF/DKIM)',
+ inputSchema: z.object({
+ senderId: z.number().describe('Sender ID'),
+ }),
+ execute: async (args: any) => {
+ const sender = await client.get(`/senders/${args.senderId}`);
+ return {
+ senderId: args.senderId,
+ email: sender.email,
+ spf: sender.spf,
+ dkim: sender.dkim,
+ active: sender.active,
+ validated: sender.spf && sender.dkim,
+ };
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/sms-tools.ts b/servers/brevo/src/tools/sms-tools.ts
new file mode 100644
index 0000000..d556f8f
--- /dev/null
+++ b/servers/brevo/src/tools/sms-tools.ts
@@ -0,0 +1,113 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { SMSCampaign } from '../types/index.js';
+
+export function createSmsTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_sms_campaigns',
+ description: 'List all SMS campaigns',
+ inputSchema: z.object({
+ status: z.enum(['draft', 'sent', 'queued', 'suspended', 'inProcess']).optional().describe('Filter by status'),
+ limit: z.number().optional().describe('Number of campaigns (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
+ }),
+ execute: async (args: any) => {
+ const params: any = {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ };
+ if (args.status) params.status = args.status;
+ if (args.sort) params.sort = args.sort;
+
+ const result = await client.getPaginated('/smsCampaigns', params);
+ return {
+ campaigns: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_sms_campaign',
+ description: 'Get details of a specific SMS campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('SMS campaign ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/smsCampaigns/${args.campaignId}`);
+ },
+ },
+ {
+ name: 'brevo_create_sms_campaign',
+ description: 'Create a new SMS campaign',
+ inputSchema: z.object({
+ name: z.string().describe('Campaign name'),
+ sender: z.string().describe('Sender name or phone number'),
+ content: z.string().describe('SMS content'),
+ recipients: z.object({
+ listIds: z.array(z.number()).optional(),
+ exclusionListIds: z.array(z.number()).optional(),
+ }).optional().describe('Recipients configuration'),
+ scheduledAt: z.string().optional().describe('Schedule date-time (ISO 8601)'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {
+ name: args.name,
+ sender: args.sender,
+ content: args.content,
+ };
+ if (args.recipients) data.recipients = args.recipients;
+ if (args.scheduledAt) data.scheduledAt = args.scheduledAt;
+
+ return await client.post('/smsCampaigns', data);
+ },
+ },
+ {
+ name: 'brevo_update_sms_campaign',
+ description: 'Update an SMS campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ name: z.string().optional().describe('Campaign name'),
+ sender: z.string().optional().describe('Sender'),
+ content: z.string().optional().describe('SMS content'),
+ recipients: z.object({
+ listIds: z.array(z.number()).optional(),
+ exclusionListIds: z.array(z.number()).optional(),
+ }).optional().describe('Recipients'),
+ scheduledAt: z.string().optional().describe('Schedule date-time'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.name) data.name = args.name;
+ if (args.sender) data.sender = args.sender;
+ if (args.content) data.content = args.content;
+ if (args.recipients) data.recipients = args.recipients;
+ if (args.scheduledAt) data.scheduledAt = args.scheduledAt;
+
+ return await client.put(`/smsCampaigns/${args.campaignId}`, data);
+ },
+ },
+ {
+ name: 'brevo_send_sms_campaign',
+ description: 'Send an SMS campaign immediately',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.post(`/smsCampaigns/${args.campaignId}/sendNow`);
+ },
+ },
+ {
+ name: 'brevo_get_sms_campaign_report',
+ description: 'Get report for an SMS campaign',
+ inputSchema: z.object({
+ campaignId: z.number().describe('Campaign ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/smsCampaigns/${args.campaignId}/report`);
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/templates-tools.ts b/servers/brevo/src/tools/templates-tools.ts
new file mode 100644
index 0000000..8708a04
--- /dev/null
+++ b/servers/brevo/src/tools/templates-tools.ts
@@ -0,0 +1,124 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { EmailTemplate } from '../types/index.js';
+
+export function createTemplatesTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_templates',
+ description: 'List all email templates',
+ inputSchema: z.object({
+ limit: z.number().optional().describe('Number of templates (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
+ }),
+ execute: async (args: any) => {
+ const result = await client.getPaginated('/smtp/templates', {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ sort: args.sort,
+ });
+ return {
+ templates: result.items,
+ total: result.total,
+ count: result.items.length,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_template',
+ description: 'Get details of a specific email template',
+ inputSchema: z.object({
+ templateId: z.number().describe('Template ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/smtp/templates/${args.templateId}`);
+ },
+ },
+ {
+ name: 'brevo_create_template',
+ description: 'Create a new email template',
+ inputSchema: z.object({
+ name: z.string().describe('Template name'),
+ subject: z.string().describe('Email subject line'),
+ sender: z.object({
+ name: z.string().optional(),
+ email: z.string().email(),
+ }).describe('Sender information'),
+ htmlContent: z.string().describe('HTML content with template variables'),
+ isActive: z.boolean().optional().describe('Is template active (default: true)'),
+ replyTo: z.string().email().optional().describe('Reply-to email'),
+ toField: z.string().optional().describe('To field placeholder'),
+ tag: z.string().optional().describe('Tag for categorization'),
+ attachmentUrl: z.string().url().optional().describe('URL of attachment'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {
+ name: args.name,
+ subject: args.subject,
+ sender: args.sender,
+ htmlContent: args.htmlContent,
+ };
+ if (args.isActive !== undefined) data.isActive = args.isActive;
+ if (args.replyTo) data.replyTo = args.replyTo;
+ if (args.toField) data.toField = args.toField;
+ if (args.tag) data.tag = args.tag;
+ if (args.attachmentUrl) data.attachmentUrl = args.attachmentUrl;
+
+ return await client.post('/smtp/templates', data);
+ },
+ },
+ {
+ name: 'brevo_update_template',
+ description: 'Update an email template',
+ inputSchema: z.object({
+ templateId: z.number().describe('Template ID'),
+ name: z.string().optional().describe('Template name'),
+ subject: z.string().optional().describe('Email subject'),
+ sender: z.object({
+ name: z.string().optional(),
+ email: z.string().email(),
+ }).optional().describe('Sender information'),
+ htmlContent: z.string().optional().describe('HTML content'),
+ isActive: z.boolean().optional().describe('Is template active'),
+ replyTo: z.string().email().optional().describe('Reply-to email'),
+ tag: z.string().optional().describe('Tag'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.name) data.name = args.name;
+ if (args.subject) data.subject = args.subject;
+ if (args.sender) data.sender = args.sender;
+ if (args.htmlContent) data.htmlContent = args.htmlContent;
+ if (args.isActive !== undefined) data.isActive = args.isActive;
+ if (args.replyTo) data.replyTo = args.replyTo;
+ if (args.tag) data.tag = args.tag;
+
+ return await client.put(`/smtp/templates/${args.templateId}`, data);
+ },
+ },
+ {
+ name: 'brevo_delete_template',
+ description: 'Delete an email template',
+ inputSchema: z.object({
+ templateId: z.number().describe('Template ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.delete(`/smtp/templates/${args.templateId}`);
+ },
+ },
+ {
+ name: 'brevo_send_test_template',
+ description: 'Send a test email using a template',
+ inputSchema: z.object({
+ templateId: z.number().describe('Template ID'),
+ emailTo: z.array(z.string().email()).describe('Test recipient emails'),
+ }),
+ execute: async (args: any) => {
+ return await client.post(`/smtp/templates/${args.templateId}/sendTest`, {
+ emailTo: args.emailTo,
+ });
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/transactional-tools.ts b/servers/brevo/src/tools/transactional-tools.ts
new file mode 100644
index 0000000..ab6b1c2
--- /dev/null
+++ b/servers/brevo/src/tools/transactional-tools.ts
@@ -0,0 +1,139 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { TransactionalEmail, TransactionalSMS } from '../types/index.js';
+
+export function createTransactionalTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_send_transactional_email',
+ description: 'Send a transactional email',
+ inputSchema: z.object({
+ to: z.array(z.object({
+ email: z.string().email(),
+ name: z.string().optional(),
+ })).describe('Recipients'),
+ sender: z.object({
+ email: z.string().email(),
+ name: z.string().optional(),
+ }).describe('Sender information'),
+ subject: z.string().optional().describe('Email subject'),
+ htmlContent: z.string().optional().describe('HTML content'),
+ textContent: z.string().optional().describe('Text content'),
+ templateId: z.number().optional().describe('Template ID'),
+ params: z.record(z.any()).optional().describe('Template parameters'),
+ cc: z.array(z.object({
+ email: z.string().email(),
+ name: z.string().optional(),
+ })).optional().describe('CC recipients'),
+ bcc: z.array(z.object({
+ email: z.string().email(),
+ name: z.string().optional(),
+ })).optional().describe('BCC recipients'),
+ replyTo: z.object({
+ email: z.string().email(),
+ name: z.string().optional(),
+ }).optional().describe('Reply-to'),
+ attachment: z.array(z.object({
+ url: z.string().url().optional(),
+ content: z.string().optional(),
+ name: z.string(),
+ })).optional().describe('Attachments'),
+ headers: z.record(z.string()).optional().describe('Custom headers'),
+ tags: z.array(z.string()).optional().describe('Tags for categorization'),
+ }),
+ execute: async (args: any) => {
+ const data: TransactionalEmail = {
+ to: args.to,
+ sender: args.sender,
+ };
+ if (args.subject) data.subject = args.subject;
+ if (args.htmlContent) data.htmlContent = args.htmlContent;
+ if (args.textContent) data.textContent = args.textContent;
+ if (args.templateId) data.templateId = args.templateId;
+ if (args.params) data.params = args.params;
+ if (args.cc) data.cc = args.cc;
+ if (args.bcc) data.bcc = args.bcc;
+ if (args.replyTo) data.replyTo = args.replyTo;
+ if (args.attachment) data.attachment = args.attachment;
+ if (args.headers) data.headers = args.headers;
+ if (args.tags) data.tags = args.tags;
+
+ return await client.post('/smtp/email', data);
+ },
+ },
+ {
+ name: 'brevo_send_transactional_sms',
+ description: 'Send a transactional SMS',
+ inputSchema: z.object({
+ sender: z.string().describe('Sender name (max 11 characters) or phone number'),
+ recipient: z.string().describe('Recipient phone number (E.164 format)'),
+ content: z.string().describe('SMS content (max 160 characters for single SMS)'),
+ type: z.enum(['transactional', 'marketing']).optional().describe('SMS type (default: transactional)'),
+ tag: z.string().optional().describe('Tag for categorization'),
+ webUrl: z.string().url().optional().describe('URL for web version'),
+ }),
+ execute: async (args: any) => {
+ const data: TransactionalSMS = {
+ sender: args.sender,
+ recipient: args.recipient,
+ content: args.content,
+ };
+ if (args.type) data.type = args.type;
+ if (args.tag) data.tag = args.tag;
+ if (args.webUrl) data.webUrl = args.webUrl;
+
+ return await client.post('/transactionalSMS/sms', data);
+ },
+ },
+ {
+ name: 'brevo_list_transactional_events',
+ description: 'Get list of transactional email/SMS events',
+ inputSchema: z.object({
+ email: z.string().email().optional().describe('Filter by email'),
+ templateId: z.number().optional().describe('Filter by template ID'),
+ messageId: z.string().optional().describe('Filter by message ID'),
+ event: z.enum(['bounces', 'hardBounces', 'softBounces', 'delivered', 'spam', 'requests', 'opened', 'clicks', 'invalid', 'deferred', 'blocked', 'unsubscribed']).optional().describe('Event type'),
+ days: z.number().optional().describe('Number of days to retrieve (max 30)'),
+ limit: z.number().optional().describe('Number of events (default: 50)'),
+ offset: z.number().optional().describe('Pagination offset'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by date'),
+ }),
+ execute: async (args: any) => {
+ const params: any = {
+ limit: args.limit || 50,
+ offset: args.offset || 0,
+ };
+ if (args.email) params.email = args.email;
+ if (args.templateId) params.templateId = args.templateId;
+ if (args.messageId) params.messageId = args.messageId;
+ if (args.event) params.event = args.event;
+ if (args.days) params.days = args.days;
+ if (args.sort) params.sort = args.sort;
+
+ return await client.get('/smtp/statistics/events', params);
+ },
+ },
+ {
+ name: 'brevo_get_aggregated_report',
+ description: 'Get aggregated transactional email/SMS statistics',
+ inputSchema: z.object({
+ startDate: z.string().describe('Start date (YYYY-MM-DD)'),
+ endDate: z.string().describe('End date (YYYY-MM-DD)'),
+ days: z.number().optional().describe('Number of days (alternative to date range)'),
+ tag: z.string().optional().describe('Filter by tag'),
+ }),
+ execute: async (args: any) => {
+ const params: any = {};
+ if (args.days) {
+ params.days = args.days;
+ } else {
+ params.startDate = args.startDate;
+ params.endDate = args.endDate;
+ }
+ if (args.tag) params.tag = args.tag;
+
+ return await client.get('/smtp/statistics/aggregatedReport', params);
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/tools/webhooks-tools.ts b/servers/brevo/src/tools/webhooks-tools.ts
new file mode 100644
index 0000000..a9881e8
--- /dev/null
+++ b/servers/brevo/src/tools/webhooks-tools.ts
@@ -0,0 +1,103 @@
+import { z } from 'zod';
+import { BrevoApiClient } from '../types/api-client.js';
+import { Webhook } from '../types/index.js';
+
+export function createWebhooksTools(client: BrevoApiClient) {
+ return [
+ {
+ name: 'brevo_list_webhooks',
+ description: 'List all webhooks',
+ inputSchema: z.object({
+ type: z.enum(['marketing', 'transactional']).optional().describe('Filter by webhook type'),
+ sort: z.enum(['asc', 'desc']).optional().describe('Sort by creation date'),
+ }),
+ execute: async (args: any) => {
+ const params: any = {};
+ if (args.type) params.type = args.type;
+ if (args.sort) params.sort = args.sort;
+
+ const result = await client.get<{ webhooks: Webhook[] }>('/webhooks', params);
+ return {
+ webhooks: result.webhooks || [],
+ count: result.webhooks?.length || 0,
+ };
+ },
+ },
+ {
+ name: 'brevo_get_webhook',
+ description: 'Get details of a specific webhook',
+ inputSchema: z.object({
+ webhookId: z.number().describe('Webhook ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.get(`/webhooks/${args.webhookId}`);
+ },
+ },
+ {
+ name: 'brevo_create_webhook',
+ description: 'Create a new webhook',
+ inputSchema: z.object({
+ url: z.string().url().describe('Webhook URL'),
+ description: z.string().optional().describe('Webhook description'),
+ events: z.array(z.string()).describe('Events to subscribe to (e.g., delivered, opened, clicked)'),
+ type: z.enum(['marketing', 'transactional']).describe('Webhook type'),
+ batched: z.boolean().optional().describe('Enable batched events'),
+ auth: z.object({
+ type: z.enum(['bearer', 'basic']),
+ token: z.string().optional(),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ }).optional().describe('Authentication configuration'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {
+ url: args.url,
+ events: args.events,
+ type: args.type,
+ };
+ if (args.description) data.description = args.description;
+ if (args.batched !== undefined) data.batched = args.batched;
+ if (args.auth) data.auth = args.auth;
+
+ return await client.post('/webhooks', data);
+ },
+ },
+ {
+ name: 'brevo_update_webhook',
+ description: 'Update a webhook',
+ inputSchema: z.object({
+ webhookId: z.number().describe('Webhook ID'),
+ url: z.string().url().optional().describe('Webhook URL'),
+ description: z.string().optional().describe('Description'),
+ events: z.array(z.string()).optional().describe('Events'),
+ batched: z.boolean().optional().describe('Batched events'),
+ auth: z.object({
+ type: z.enum(['bearer', 'basic']),
+ token: z.string().optional(),
+ username: z.string().optional(),
+ password: z.string().optional(),
+ }).optional().describe('Authentication'),
+ }),
+ execute: async (args: any) => {
+ const data: any = {};
+ if (args.url) data.url = args.url;
+ if (args.description) data.description = args.description;
+ if (args.events) data.events = args.events;
+ if (args.batched !== undefined) data.batched = args.batched;
+ if (args.auth) data.auth = args.auth;
+
+ return await client.put(`/webhooks/${args.webhookId}`, data);
+ },
+ },
+ {
+ name: 'brevo_delete_webhook',
+ description: 'Delete a webhook',
+ inputSchema: z.object({
+ webhookId: z.number().describe('Webhook ID'),
+ }),
+ execute: async (args: any) => {
+ return await client.delete(`/webhooks/${args.webhookId}`);
+ },
+ },
+ ];
+}
diff --git a/servers/brevo/src/types/api-client.ts b/servers/brevo/src/types/api-client.ts
new file mode 100644
index 0000000..2afc280
--- /dev/null
+++ b/servers/brevo/src/types/api-client.ts
@@ -0,0 +1,92 @@
+import axios, { AxiosInstance, AxiosError } from 'axios';
+import { BrevoConfig, ApiError } from './index.js';
+
+export class BrevoApiClient {
+ private client: AxiosInstance;
+
+ constructor(config: BrevoConfig) {
+ this.client = axios.create({
+ baseURL: config.baseUrl || 'https://api.brevo.com/v3',
+ headers: {
+ 'api-key': config.apiKey,
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ });
+
+ // Response interceptor for error handling
+ this.client.interceptors.response.use(
+ (response) => response,
+ (error: AxiosError) => {
+ if (error.response) {
+ const apiError = error.response.data;
+ throw new Error(
+ `Brevo API Error (${error.response.status}): ${apiError?.message || error.message}`
+ );
+ }
+ throw error;
+ }
+ );
+ }
+
+ // Generic GET request with pagination support
+ async get(endpoint: string, params?: Record): Promise {
+ const response = await this.client.get(endpoint, { params });
+ return response.data;
+ }
+
+ // Generic POST request
+ async post(endpoint: string, data?: any): Promise {
+ const response = await this.client.post(endpoint, data);
+ return response.data;
+ }
+
+ // Generic PUT request
+ async put(endpoint: string, data?: any): Promise {
+ const response = await this.client.put(endpoint, data);
+ return response.data;
+ }
+
+ // Generic PATCH request
+ async patch(endpoint: string, data?: any): Promise {
+ const response = await this.client.patch(endpoint, data);
+ return response.data;
+ }
+
+ // Generic DELETE request
+ async delete(endpoint: string): Promise {
+ const response = await this.client.delete(endpoint);
+ return response.data;
+ }
+
+ // Paginated list helper
+ async getPaginated(
+ endpoint: string,
+ params: Record = {}
+ ): Promise<{ items: T[]; total?: number }> {
+ const limit = params.limit || 50;
+ const offset = params.offset || 0;
+
+ const response = await this.get(endpoint, { ...params, limit, offset });
+
+ // Handle different response structures
+ const items =
+ response.contacts ||
+ response.campaigns ||
+ response.lists ||
+ response.folders ||
+ response.templates ||
+ response.senders ||
+ response.workflows ||
+ response.webhooks ||
+ response.deals ||
+ response.pipelines ||
+ response.stages ||
+ [];
+
+ return {
+ items,
+ total: response.count,
+ };
+ }
+}
diff --git a/servers/brevo/src/types/index.ts b/servers/brevo/src/types/index.ts
new file mode 100644
index 0000000..cf5ef9d
--- /dev/null
+++ b/servers/brevo/src/types/index.ts
@@ -0,0 +1,272 @@
+// Brevo API Types
+
+export interface BrevoConfig {
+ apiKey: string;
+ baseUrl?: string;
+}
+
+// Contacts
+export interface Contact {
+ id: number;
+ email: string;
+ emailBlacklisted?: boolean;
+ smsBlacklisted?: boolean;
+ createdAt?: string;
+ modifiedAt?: string;
+ attributes?: Record;
+ listIds?: number[];
+ listUnsubscribed?: number[];
+}
+
+export interface ContactAttribute {
+ name: string;
+ category: 'normal' | 'transactional' | 'category' | 'calculated' | 'global';
+ type?: 'text' | 'date' | 'float' | 'id' | 'boolean';
+ enumeration?: { value: number; label: string }[];
+}
+
+export interface ContactList {
+ id: number;
+ name: string;
+ totalBlacklisted?: number;
+ totalSubscribers?: number;
+ uniqueSubscribers?: number;
+ folderId?: number;
+ createdAt?: string;
+}
+
+export interface Folder {
+ id: number;
+ name: string;
+ totalBlacklisted?: number;
+ totalSubscribers?: number;
+ uniqueSubscribers?: number;
+}
+
+// Campaigns
+export interface EmailCampaign {
+ id: number;
+ name: string;
+ subject?: string;
+ type: 'classic' | 'trigger' | 'ab';
+ status: 'draft' | 'sent' | 'queued' | 'suspended' | 'archive';
+ scheduledAt?: string;
+ abTesting?: boolean;
+ subjectA?: string;
+ subjectB?: string;
+ splitRule?: number;
+ winnerCriteria?: string;
+ winnerDelay?: number;
+ sendAtBestTime?: boolean;
+ htmlContent?: string;
+ sender?: { name?: string; email?: string; id?: number };
+ replyTo?: string;
+ toField?: string;
+ recipients?: { listIds?: number[]; exclusionListIds?: number[]; segmentIds?: number[] };
+ attachmentUrl?: string;
+ inlineImageActivation?: boolean;
+ mirrorActive?: boolean;
+ recurring?: boolean;
+ createdAt?: string;
+ modifiedAt?: string;
+}
+
+export interface CampaignReport {
+ globalStats: {
+ uniqueClicks: number;
+ clickers: number;
+ complaints: number;
+ delivered: number;
+ sent: number;
+ softBounces: number;
+ hardBounces: number;
+ uniqueViews: number;
+ trackableViews: number;
+ unsubscriptions: number;
+ viewed: number;
+ deferred: number;
+ returnBounce: number;
+ };
+ campaignStats: Array<{
+ listId: number;
+ uniqueClicks: number;
+ clickers: number;
+ complaints: number;
+ delivered: number;
+ sent: number;
+ softBounces: number;
+ hardBounces: number;
+ uniqueViews: number;
+ unsubscriptions: number;
+ viewed: number;
+ }>;
+ mirrorClick: number;
+ remaining: number;
+ linksStats: Record;
+ statsByDomain: {
+ [domain: string]: {
+ delivered: number;
+ softBounces: number;
+ hardBounces: number;
+ complaints: number;
+ opened: number;
+ clicked: number;
+ unsubscribed: number;
+ };
+ };
+ statsByDevice: {
+ [device: string]: {
+ viewed: number;
+ clicked: number;
+ };
+ };
+ statsByBrowser: Record;
+}
+
+// Transactional
+export interface TransactionalEmail {
+ sender: { name?: string; email: string; id?: number };
+ to: Array<{ email: string; name?: string }>;
+ bcc?: Array<{ email: string; name?: string }>;
+ cc?: Array<{ email: string; name?: string }>;
+ htmlContent?: string;
+ textContent?: string;
+ subject?: string;
+ replyTo?: { email: string; name?: string };
+ attachment?: Array<{ url?: string; content?: string; name?: string }>;
+ headers?: Record;
+ templateId?: number;
+ params?: Record;
+ tags?: string[];
+}
+
+export interface TransactionalSMS {
+ sender: string;
+ recipient: string;
+ content: string;
+ type?: 'transactional' | 'marketing';
+ tag?: string;
+ webUrl?: string;
+}
+
+// Templates
+export interface EmailTemplate {
+ id: number;
+ name: string;
+ subject?: string;
+ isActive?: boolean;
+ testSent?: boolean;
+ sender?: { name?: string; email?: string; id?: number };
+ replyTo?: string;
+ toField?: string;
+ htmlContent?: string;
+ createdAt?: string;
+ modifiedAt?: string;
+ tag?: string;
+ attachmentUrl?: string;
+}
+
+// Senders
+export interface Sender {
+ id: number;
+ name: string;
+ email: string;
+ active?: boolean;
+ spf?: boolean;
+ dkim?: boolean;
+}
+
+// Automations/Workflows
+export interface Workflow {
+ id: number;
+ name: string;
+ status: 'draft' | 'active' | 'inactive';
+ createdAt?: string;
+ modifiedAt?: string;
+}
+
+export interface WorkflowStats {
+ sent: number;
+ opened: number;
+ clicked: number;
+ unsubscribed: number;
+ hardBounced: number;
+ softBounced: number;
+ goal: number;
+ revenue: number;
+}
+
+// SMS Campaigns
+export interface SMSCampaign {
+ id: number;
+ name: string;
+ status: 'draft' | 'sent' | 'queued' | 'suspended' | 'inProcess';
+ content: string;
+ scheduledAt?: string;
+ sender: string;
+ recipients?: {
+ listIds?: number[];
+ exclusionListIds?: number[];
+ };
+ createdAt?: string;
+ modifiedAt?: string;
+}
+
+// CRM Deals
+export interface Deal {
+ id: string;
+ name: string;
+ attributes?: Record;
+ linkedCompaniesIds?: string[];
+ linkedContactsIds?: number[];
+}
+
+export interface Pipeline {
+ id: string;
+ name: string;
+}
+
+export interface Stage {
+ id: string;
+ name: string;
+}
+
+// Webhooks
+export interface Webhook {
+ id: number;
+ url: string;
+ description?: string;
+ events: string[];
+ type: 'marketing' | 'transactional';
+ createdAt?: string;
+ modifiedAt?: string;
+ batched?: boolean;
+ auth?: {
+ type: 'bearer' | 'basic';
+ token?: string;
+ username?: string;
+ password?: string;
+ };
+}
+
+// API Response Types
+export interface PaginatedResponse {
+ count?: number;
+ contacts?: T[];
+ campaigns?: T[];
+ lists?: T[];
+ folders?: T[];
+ templates?: T[];
+ senders?: T[];
+ workflows?: T[];
+ webhooks?: T[];
+ deals?: T[];
+ pipelines?: T[];
+ limit?: number;
+ offset?: number;
+}
+
+export interface ApiError {
+ code: string;
+ message: string;
+}
diff --git a/servers/brevo/tsconfig.json b/servers/brevo/tsconfig.json
index de6431e..156b6d5 100644
--- a/servers/brevo/tsconfig.json
+++ b/servers/brevo/tsconfig.json
@@ -1,14 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
- "module": "NodeNext",
- "moduleResolution": "NodeNext",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
- "declaration": true
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]