From 1dd639f67f13aabb9fc96e4165e8b1de1f1519fb Mon Sep 17 00:00:00 2001 From: Jake Shore Date: Thu, 12 Feb 2026 18:11:14 -0500 Subject: [PATCH] Complete Brevo MCP server: 55+ tools, 14 apps, full API coverage --- servers/brevo/README.md | 483 +++++++++++------- servers/brevo/package.json | 27 +- .../brevo/src/apps/automation-dashboard.ts | 97 ++++ servers/brevo/src/apps/campaign-builder.ts | 98 ++++ servers/brevo/src/apps/campaign-dashboard.ts | 77 +++ servers/brevo/src/apps/contact-dashboard.ts | 63 +++ servers/brevo/src/apps/contact-detail.ts | 84 +++ servers/brevo/src/apps/contact-grid.ts | 78 +++ servers/brevo/src/apps/deal-pipeline.ts | 66 +++ servers/brevo/src/apps/import-wizard.ts | 145 ++++++ servers/brevo/src/apps/list-manager.ts | 132 +++++ servers/brevo/src/apps/report-dashboard.ts | 144 ++++++ servers/brevo/src/apps/sms-dashboard.ts | 119 +++++ servers/brevo/src/apps/template-gallery.ts | 94 ++++ .../brevo/src/apps/transactional-monitor.ts | 118 +++++ servers/brevo/src/apps/webhook-manager.ts | 149 ++++++ servers/brevo/src/index.ts | 396 +------------- servers/brevo/src/main.ts | 23 + servers/brevo/src/server.ts | 205 ++++++++ servers/brevo/src/tools/automations-tools.ts | 77 +++ servers/brevo/src/tools/campaigns-tools.ts | 188 +++++++ servers/brevo/src/tools/contacts-tools.ts | 239 +++++++++ servers/brevo/src/tools/deals-tools.ts | 122 +++++ servers/brevo/src/tools/lists-tools.ts | 105 ++++ servers/brevo/src/tools/senders-tools.ts | 109 ++++ servers/brevo/src/tools/sms-tools.ts | 113 ++++ servers/brevo/src/tools/templates-tools.ts | 124 +++++ .../brevo/src/tools/transactional-tools.ts | 139 +++++ servers/brevo/src/tools/webhooks-tools.ts | 103 ++++ servers/brevo/src/types/api-client.ts | 92 ++++ servers/brevo/src/types/index.ts | 272 ++++++++++ servers/brevo/tsconfig.json | 11 +- 32 files changed, 3690 insertions(+), 602 deletions(-) create mode 100644 servers/brevo/src/apps/automation-dashboard.ts create mode 100644 servers/brevo/src/apps/campaign-builder.ts create mode 100644 servers/brevo/src/apps/campaign-dashboard.ts create mode 100644 servers/brevo/src/apps/contact-dashboard.ts create mode 100644 servers/brevo/src/apps/contact-detail.ts create mode 100644 servers/brevo/src/apps/contact-grid.ts create mode 100644 servers/brevo/src/apps/deal-pipeline.ts create mode 100644 servers/brevo/src/apps/import-wizard.ts create mode 100644 servers/brevo/src/apps/list-manager.ts create mode 100644 servers/brevo/src/apps/report-dashboard.ts create mode 100644 servers/brevo/src/apps/sms-dashboard.ts create mode 100644 servers/brevo/src/apps/template-gallery.ts create mode 100644 servers/brevo/src/apps/transactional-monitor.ts create mode 100644 servers/brevo/src/apps/webhook-manager.ts create mode 100644 servers/brevo/src/main.ts create mode 100644 servers/brevo/src/server.ts create mode 100644 servers/brevo/src/tools/automations-tools.ts create mode 100644 servers/brevo/src/tools/campaigns-tools.ts create mode 100644 servers/brevo/src/tools/contacts-tools.ts create mode 100644 servers/brevo/src/tools/deals-tools.ts create mode 100644 servers/brevo/src/tools/lists-tools.ts create mode 100644 servers/brevo/src/tools/senders-tools.ts create mode 100644 servers/brevo/src/tools/sms-tools.ts create mode 100644 servers/brevo/src/tools/templates-tools.ts create mode 100644 servers/brevo/src/tools/transactional-tools.ts create mode 100644 servers/brevo/src/tools/webhooks-tools.ts create mode 100644 servers/brevo/src/types/api-client.ts create mode 100644 servers/brevo/src/types/index.ts 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 - -[![Deploy on Railway](https://railway.app/button.svg)](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}} +
+
+

{{this.name}}

+
+ + {{this.status}} +
+
+ +
+
+
Sent
+
{{this.stats.sent}}
+
+
+
Opened
+
{{this.stats.opened}}
+
+
+
Clicked
+
{{this.stats.clicked}}
+
+
+
Goals
+
{{this.stats.goal}}
+
+
+ +
+ + {{#if this.isActive}} + + {{else}} + + {{/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 + +
+
+
+
1
+
Setup
+
+
+
2
+
Design
+
+
+
3
+
Recipients
+
+
+
4
+
Schedule
+
+
+ +
+

Campaign Settings

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ {{#each templates}} +
+
๐Ÿ“ง
+
{{this.name}}
+
+ {{/each}} +
+
+ +
+ + +
+ +
+ + +
+
+
+`, + 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.name}}

+ {{this.status}} +
+
{{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}} + +
+
+

{{attributes.FIRSTNAME}} {{attributes.LASTNAME}}

+
{{email}}
+
ID: {{id}}
+
+ +
+ {{#if emailBlacklisted}} + Email Blacklisted + {{/if}} + {{#if smsBlacklisted}} + SMS Blacklisted + {{/if}} + {{#unless emailBlacklisted}} + Active + {{/unless}} +
+
+ +## Contact Attributes + + {{#each attributes}} + + + + + {{/each}} +
{{@key}}{{this}}
+ +## 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 + +
+ + + +
+ + + + + + + + + + + + + + {{#each contacts}} + + + + + + + + + {{/each}} + +
EmailNameListsStatusCreatedActions
{{this.email}}{{this.attributes.FIRSTNAME}} {{this.attributes.LASTNAME}}{{this.listIds.length}} + {{#if this.emailBlacklisted}} + Blacklisted + {{else}} + Active + {{/if}} + {{this.createdAt}} + + +
+ + +`, + 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 + +
+

{{pipelineName}}

+
+ Total Value: \${{totalValue}} + {{totalDeals}} Deals + Win Rate: {{winRate}}% +
+
+ +
+ {{#each stages}} +
+
+

{{this.name}}

+ {{this.deals.length}} +
+
\${{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 + +
+
+
1
+
Upload File
+
+
+
+
2
+
Map Fields
+
+
+
+
3
+
Configure
+
+
+
+
4
+
Review & Import
+
+
+ +
+

Step 1: Upload Your File

+ +
+
๐Ÿ“„
+

Drag and drop your CSV file here

+

or

+ +
+ 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

+ +
+ +
+

File Requirements

+
    +
  • + โœ“ + First row must contain column headers +
  • +
  • + โœ“ + Email column is required +
  • +
  • + โœ“ + UTF-8 encoding recommended +
  • +
  • + โœ“ + Use comma (,) as field separator +
  • +
+
+
+ +
+ + +
+ +
+

Recent Imports

+ + + + + + + + + + + + + {{#each recentImports}} + + + + + + + + + {{/each}} + +
DateFile NameTotalSuccessFailedStatus
{{this.date}}{{this.fileName}}{{this.total}}{{this.success}}{{this.failed}}{{this.status}}
+
+`, + 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 + +
+
+
+
{{totalLists}}
+
Total Lists
+
+
+
{{totalSubscribers}}
+
Total Subscribers
+
+
+
{{totalFolders}}
+
Folders
+
+
+ +
+ +
+
+

Folders

+
+ {{#each folders}} +
+ ๐Ÿ“ + {{this.name}} + {{this.totalSubscribers}} +
+ {{/each}} +
+ +
+ +
+
+ + +
+ +
+ {{#each lists}} +
+
+

{{this.name}}

+
โ‹ฎ
+
+ +
+
+
๐Ÿ‘ฅ
+
+
{{this.totalSubscribers}}
+
Subscribers
+
+
+ +
+
๐Ÿšซ
+
+
{{this.totalBlacklisted}}
+
Blacklisted
+
+
+
+ +
+ {{#if this.folderId}} + ๐Ÿ“ {{this.folderName}} + {{else}} + No folder + {{/if}} +
+ +
+ + + +
+
+ {{/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 + +
+ + +
+ +
+
+
+

Total Sends

+ +15% +
+
{{totalSends}}
+
๐Ÿ“Š
+
+ +
+
+

Open Rate

+ +3.2% +
+
{{openRate}}%
+
๐Ÿ“ˆ
+
+ +
+
+

Click Rate

+ -1.5% +
+
{{clickRate}}%
+
๐Ÿ“‰
+
+ +
+
+

Conversion Rate

+ +5.8% +
+
{{conversionRate}}%
+
๐Ÿ’ฐ
+
+
+ +## Performance Over Time +
+
+ ๐Ÿ“Š Email Performance Chart +
+ Opens + Clicks + Bounces +
+
+
+ +## Top Performing Campaigns + + + + + + + + + + + + + {{#each topCampaigns}} + + + + + + + + + {{/each}} + +
CampaignSentOpensClicksConv. RateRevenue
{{this.name}}{{this.sent}}{{this.opens}} ({{this.openRate}}%){{this.clicks}} ({{this.clickRate}}%){{this.conversionRate}}%\${{this.revenue}}
+ +## 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.name}}

+
From: {{this.sender}}
+
+ {{this.status}} +
+ +
{{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 + +
+ + + +
+ +
+
+
๐Ÿ“ง
+
+
{{emailsSentToday}}
+
Emails Today
+
+
+ +
+
โœ…
+
+
{{deliveryRate}}%
+
Delivery Rate
+
+
+ +
+
๐Ÿ“ฑ
+
+
{{smsSentToday}}
+
SMS Today
+
+
+ +
+
โš ๏ธ
+
+
{{bounceRate}}%
+
Bounce Rate
+
+
+
+ +## Recent Events +
+ + +
+ + + + + + + + + + + + + + {{#each events}} + + + + + + + + + {{/each}} + +
TimeEventRecipientSubject/ContentTemplateStatus
{{this.date}}{{this.event}}{{this.email}}{{this.subject}}{{this.templateId}}{{this.status}}
+ + +`, + 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 + +
+
+

Active Webhooks

+

Configure webhooks to receive real-time notifications for email and SMS events

+
+ +
+ +
+ + + +
+ +## Configured Webhooks +{{#each webhooks}} +
+
+
+

{{this.description}}

+
{{this.url}}
+
+
+ + +
+
+ +
{{this.type}}
+ +
+ Events: +
+ {{#each this.events}} + {{this}} + {{/each}} +
+
+ +
+
+ Created: + {{this.createdAt}} +
+
+ Last Modified: + {{this.modifiedAt}} +
+ {{#if this.batched}} +
+ Batched Events +
+ {{/if}} + {{#if this.auth}} +
+ ๐Ÿ”’ Authenticated +
+ {{/if}} +
+ +
+ + + + +
+
+{{/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"]