MCP server for Keila API - comprehensive campaign and contact management
Project description
Keila MCP
MCP server for Keila — gives any MCP-compatible AI assistant full control over your Keila email campaigns, contacts, segments and forms.
[!NOTE] Migrating from an earlier clone? The source directory was renamed from
repo/tokeila-mcp/. Update any paths in your MCP client config accordingly.
Quickstart with uvx
If you have uv installed, no cloning or virtualenv needed:
{
"mcpServers": {
"keila": {
"command": "uvx",
"args": ["keila-mcp"],
"env": {
"KEILA_URL": "https://your-keila-instance.com",
"KEILA_API_KEY": "your-api-key"
}
}
}
}
Add this to your MCP client config and it works on first run. Skip to Client Configuration for client-specific formats.
Requirements
- A running Keila instance — self-host with Docker or use Keila Cloud
- A Keila API key — Settings → API Keys → Create
- Python 3.10+ on your machine
Installation
Choose where to install:
- Global — use Keila MCP in any project: install in
~/.mcp/ - Project — use it only in one project: install in your project folder, e.g.
~/Developer/my-project/.mcp/
macOS / Linux
# global
mkdir -p ~/.mcp && cd ~/.mcp
# or project-specific
mkdir -p ~/Developer/my-project/.mcp && cd ~/Developer/my-project/.mcp
git clone https://github.com/punkyard/keila-mcp.git keila-mcp
cd keila-mcp
python -m venv .venv
source .venv/bin/activate
pip install .
pwd
Copy the path printed by pwd — you will paste it into your client config below.
Windows
# global
mkdir %USERPROFILE%\.mcp && cd %USERPROFILE%\.mcp
# or project-specific
mkdir C:\Users\your-username\Developer\my-project\.mcp && cd C:\Users\your-username\Developer\my-project\.mcp
git clone https://github.com/punkyard/keila-mcp.git keila-mcp
cd keila-mcp
python -m venv .venv
.venv\Scripts\activate
pip install .
cd
Copy the path printed by cd — you will paste it into your client config below.
Client Setup
Every MCP client needs two things: the path to the Python interpreter and two environment variables.
| Variable | Value |
|---|---|
KEILA_URL |
Your Keila instance URL, e.g. https://keila.mydomain.com |
KEILA_API_KEY |
Create a Keila API key per project |
KEILA_MCP_HTTP_PORT |
(optional) HTTP port for the MCP server. Default: 3001 |
[!IMPORTANT] In all examples below, replace
/path/to/keila-mcp/repowith the path printed bypwd(orcdon Windows) at the end of the installation section.
Claude Desktop
File (macOS): ~/Library/Application Support/Claude/claude_desktop_config.json
File (Windows): %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"keila": {
"command": "/path/to/keila-mcp/keila-mcp/.venv/bin/python",
"args": ["/path/to/keila-mcp/keila-mcp/src/mcp_server.py"],
"env": {
"KEILA_URL": "https://your-keila-instance.com",
"KEILA_API_KEY": "your-api-key"
}
}
}
}
Fully quit and relaunch Claude Desktop after saving.
Claude Code
claude mcp add-json keila '{
"type": "stdio",
"command": "/path/to/keila-mcp/keila-mcp/.venv/bin/python",
"args": ["/path/to/keila-mcp/keila-mcp/src/mcp_server.py"],
"env": {
"KEILA_URL": "https://your-keila-instance.com",
"KEILA_API_KEY": "your-api-key"
}
}'
Add --scope global to make it available in all projects.
Cursor · Cline / Roo Code · Windsurf · OpenClaw
- Cursor:
.cursor/mcp.jsonin your project, or~/.cursor/mcp.jsonfor global - Cline / Roo Code: MCP Servers config via the sidebar
- Windsurf:
~/.codeium/windsurf/mcp_config.json - OpenClaw:
~/.openclaw/mcp.json
{
"mcpServers": {
"keila": {
"command": "/path/to/keila-mcp/keila-mcp/.venv/bin/python",
"args": ["/path/to/keila-mcp/keila-mcp/src/mcp_server.py"],
"env": {
"KEILA_URL": "https://your-keila-instance.com",
"KEILA_API_KEY": "your-api-key"
}
}
}
}
VS Code (GitHub Copilot Agent Mode)
Create .vscode/mcp.json in your project:
{
"servers": {
"keila": {
"command": "/path/to/keila-mcp/keila-mcp/.venv/bin/python",
"args": ["/path/to/keila-mcp/keila-mcp/src/mcp_server.py"],
"env": {
"KEILA_URL": "https://your-keila-instance.com",
"KEILA_API_KEY": "${input:keilaApiKey}"
}
}
},
"inputs": [
{
"id": "keilaApiKey",
"type": "promptString",
"description": "Keila API Key",
"password": true
}
]
}
Requires Copilot Chat in Agent mode. VS Code will prompt for the API key on first use.
Zed
In ~/.config/zed/settings.json (global) or .zed/settings.json (project):
{
"context_servers": {
"keila": {
"command": "/path/to/keila-mcp/keila-mcp/.venv/bin/python",
"args": ["/path/to/keila-mcp/keila-mcp/src/mcp_server.py"],
"env": {
"KEILA_URL": "https://your-keila-instance.com",
"KEILA_API_KEY": "your-api-key"
}
}
}
}
OpenCode
In ~/.config/opencode/opencode.json (global) or opencode.json (project):
{
"mcp": {
"keila": {
"type": "local",
"command": ["/path/to/keila-mcp/keila-mcp/.venv/bin/python", "/path/to/keila-mcp/keila-mcp/src/mcp_server.py"],
"env": {
"KEILA_URL": "https://your-keila-instance.com",
"KEILA_API_KEY": "your-api-key"
}
}
}
}
Pi Agent
In ~/.pi/agent/mcp.json:
{
"keila": {
"command": "/path/to/keila-mcp/keila-mcp/.venv/bin/python",
"args": ["/path/to/keila-mcp/keila-mcp/src/mcp_server.py"],
"env": {
"KEILA_URL": "https://your-keila-instance.com",
"KEILA_API_KEY": "your-api-key"
},
"lifecycle": "on-demand"
}
}
Hermes
In ~/.hermes/config.yaml:
mcp_servers:
keila:
command: /path/to/keila-mcp/keila-mcp/.venv/bin/python
args:
- /path/to/keila-mcp/keila-mcp/src/mcp_server.py
env:
KEILA_URL: https://your-keila-instance.com
KEILA_API_KEY: your-api-key
OpenAI Codex CLI
File: ~/.codex/config.toml (global) or .codex/config.toml (project):
[mcp_servers.keila]
command = "/path/to/keila-mcp/keila-mcp/.venv/bin/python"
args = ["/path/to/keila-mcp/keila-mcp/src/mcp_server.py"]
[mcp_servers.keila.env]
KEILA_URL = "https://your-keila-instance.com"
KEILA_API_KEY = "your-api-key"
Running manually (for testing)
# stdio mode (default)
python src/mcp_server.py
# HTTP mode
python src/mcp_server.py --http
# optional: export KEILA_MCP_HTTP_PORT=8325
Tools
| Tool | Description |
|---|---|
list_campaigns |
List all campaigns with optional status/search filter |
create_campaign |
Create a new campaign |
get_campaign |
Get a campaign by ID |
update_campaign |
Update an existing campaign |
delete_campaign |
Delete a campaign |
send_campaign |
Send a campaign immediately |
schedule_campaign |
Schedule a campaign for later delivery |
create_contact |
Create a new contact |
get_contact |
Get a contact by ID |
update_contact |
Update a contact |
delete_contact |
Delete a contact |
list_contacts |
List contacts with optional filtering |
update_contact_data |
Merge custom data fields on a contact |
replace_contact_data |
Replace all custom data fields on a contact |
list_senders |
List all senders |
create_segment |
Create a new segment |
list_segments |
List all segments |
get_segment |
Get a segment by ID |
update_segment |
Update a segment |
delete_segment |
Delete a segment |
list_forms |
List all forms |
get_form |
Get a form by ID |
create_form |
Create a new signup form |
update_form |
Update a form |
delete_form |
Delete a form |
submit_form |
Submit a signup form on behalf of a contact |
Campaigns
list_campaigns
List all email campaigns with optional filtering.
| Param | Type | Required | Description |
|---|---|---|---|
status |
string | No | Filter by: draft/scheduled/sent/archived/paused |
q |
string | No | Search by subject (case-insensitive substring) |
create_campaign
Create a new email campaign.
| Param | Type | Required | Description |
|---|---|---|---|
subject |
string | Yes | Campaign subject line |
body_type |
string | Yes | Body type: markdown/text/block/mjml |
text_body |
string | No | Plain text body |
preview_text |
string | No | Preview text for inbox |
sender_id |
string | No | Sender identity ID |
segment_id |
string | No | Target segment ID |
data |
object | No | Liquid template variables |
do_not_track |
boolean | No | Disable open/click tracking |
get_campaign
Get a single campaign by ID.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Campaign ID |
update_campaign
Update an existing campaign.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Campaign ID |
subject |
string | No | New subject line |
preview_text |
string | No | New preview text |
delete_campaign
Delete a campaign.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Campaign ID |
send_campaign
Send a campaign immediately.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Campaign ID |
sender_id |
string | No | Override sender identity |
schedule_campaign
Schedule a campaign for later delivery.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Campaign ID |
scheduled_for |
string | Yes | ISO 8601 datetime (e.g. 2026-06-01T09:00:00Z) |
Contacts
create_contact
Create a new contact.
| Param | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Email address |
first_name |
string | No | First name |
last_name |
string | No | Last name |
external_id |
string | No | External system ID |
status |
string | No | Status: active/inactive/bouncing/blocked/spam |
data |
object | No | Custom fields |
get_contact
Get a contact by ID, email, or external ID.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Contact identifier |
id_type |
string | No | Lookup type: id (default)/email/external_id |
update_contact
Update an existing contact.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Contact identifier |
email |
string | No | New email |
first_name |
string | No | New first name |
last_name |
string | No | New last name |
external_id |
string | No | New external ID |
data |
object | No | New custom fields |
id_type |
string | No | Lookup type: id (default)/email/external_id |
delete_contact
Delete a contact.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Contact identifier |
id_type |
string | No | Lookup type: id (default)/email/external_id |
list_contacts
List contacts with pagination and optional search.
| Param | Type | Required | Description |
|---|---|---|---|
page |
integer | No | Page number (default: 0) |
page_size |
integer | No | Results per page (default: 50) |
q |
string | No | Search query |
Senders
list_senders
List all sender identities.
No parameters.
Contact Data
Custom data is a free-form JSON object attached to each contact. Use it to store any extra fields (e.g. plan, score, tags). Fields can be used as merge tags in campaigns via {{ contact.data.my_field }} and as segment filters. See Keila — Segments and Custom Data and Contacts API docs.
update_contact_data
Merge new key/value pairs into a contact's custom data field. Keys not present in data are preserved.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Contact ID (or email/external_id when id_type set) |
data |
object | Yes | Key/value pairs to merge |
id_type |
string | No | id (default), email, or external_id |
replace_contact_data
Replace a contact's entire custom data field with the provided dict.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Contact ID (or email/external_id when id_type set) |
data |
object | Yes | New data dict (replaces existing) |
id_type |
string | No | id (default), email, or external_id |
Segments
create_segment
Create a contact segment with a filter.
| Param | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Segment name |
filter |
object | Yes | Keila filter expression |
list_segments
List all segments.
No parameters.
get_segment
Get a segment by ID.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Segment ID |
delete_segment
Delete a segment.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Segment ID |
update_segment
Update a segment's name and/or filter. At least one of name or filter must be provided.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Segment ID |
name |
string | No | New segment name |
filter |
object | No | New filter expression |
Forms
list_forms
List all subscription forms.
No parameters.
get_form
Get a form by ID.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Form ID |
create_form
Create a new subscription form.
| Param | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Form name |
sender_id |
string | No | Sender identity ID |
fields |
array | No | Form field definitions |
settings |
object | No | Form settings (double opt-in, redirect URLs, etc.) |
delete_form
Delete a form.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Form ID |
update_form
Update an existing signup form.
| Param | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Form ID |
name |
string | No | New form name |
sender_id |
string | No | New sender identity ID |
fields |
array | No | Replacement field definitions |
settings |
object | No | Updated form settings |
submit_form
Submit a signup form on behalf of a contact. Returns the created/updated contact on success, or {"data": {"double_opt_in_required": true}} if the form has double opt-in enabled.
| Param | Type | Required | Description |
|---|---|---|---|
form_id |
string | Yes | Form ID |
email |
string | Yes | Contact email address |
first_name |
string | No | Contact first name |
last_name |
string | No | Contact last name |
external_id |
string | No | External identifier |
status |
string | No | Contact status (e.g. active) |
data |
object | No | Custom data key/value pairs |
Development
pip install -e ".[dev]"
pytest tests/ -v
© 2026 — LICENSE AGPL-3.0
made with ⏳ by punkyard
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file keila_mcp-0.1.2.tar.gz.
File metadata
- Download URL: keila_mcp-0.1.2.tar.gz
- Upload date:
- Size: 583.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e05e1c36d7b47db0f480c09dff31178346711cbbf40b3ebe8c576ad0cb2619c0
|
|
| MD5 |
15db68349e33b5e05f0369e2070f8187
|
|
| BLAKE2b-256 |
74567f00ad93ef41ef4aad0d2266546d8fcac0c5c014e78cb115baa3bb55d2d9
|
Provenance
The following attestation bundles were made for keila_mcp-0.1.2.tar.gz:
Publisher:
publish.yml on punkyard/keila-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
keila_mcp-0.1.2.tar.gz -
Subject digest:
e05e1c36d7b47db0f480c09dff31178346711cbbf40b3ebe8c576ad0cb2619c0 - Sigstore transparency entry: 1704831423
- Sigstore integration time:
-
Permalink:
punkyard/keila-mcp@c7ec089d015f78f340ed408b1fafcc6035c49c4a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/punkyard
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c7ec089d015f78f340ed408b1fafcc6035c49c4a -
Trigger Event:
release
-
Statement type:
File details
Details for the file keila_mcp-0.1.2-py3-none-any.whl.
File metadata
- Download URL: keila_mcp-0.1.2-py3-none-any.whl
- Upload date:
- Size: 25.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
385b447c1a8ef7f715c25a1e59db00c7412cfbd94f3bac9be43ff9dda9ffd1a7
|
|
| MD5 |
d6fadee68c35515b97d5cca4cc0d1b3b
|
|
| BLAKE2b-256 |
776577898d2fd077a10c8b5facd52c578013734532edc215974803e88ed3a1e5
|
Provenance
The following attestation bundles were made for keila_mcp-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on punkyard/keila-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
keila_mcp-0.1.2-py3-none-any.whl -
Subject digest:
385b447c1a8ef7f715c25a1e59db00c7412cfbd94f3bac9be43ff9dda9ffd1a7 - Sigstore transparency entry: 1704831453
- Sigstore integration time:
-
Permalink:
punkyard/keila-mcp@c7ec089d015f78f340ed408b1fafcc6035c49c4a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/punkyard
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c7ec089d015f78f340ed408b1fafcc6035c49c4a -
Trigger Event:
release
-
Statement type: