sendflo-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Virtual environment
|
|
2
|
+
.venv/
|
|
3
|
+
|
|
4
|
+
# Environment variables — never commit this
|
|
5
|
+
.env*
|
|
6
|
+
|
|
7
|
+
# Python cache
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.pyc
|
|
10
|
+
*.pyo
|
|
11
|
+
*.pyd
|
|
12
|
+
|
|
13
|
+
# Database — don't commit local DB
|
|
14
|
+
*.db
|
|
15
|
+
*.sqlite
|
|
16
|
+
|
|
17
|
+
# VS Code settings
|
|
18
|
+
.vscode/
|
|
19
|
+
|
|
20
|
+
# Anaconda
|
|
21
|
+
.conda/
|
|
22
|
+
|
|
23
|
+
# OS files
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
|
|
27
|
+
# Local documents — not tracked in git
|
|
28
|
+
docs/
|
|
29
|
+
|
|
30
|
+
# Local test scripts
|
|
31
|
+
backend/test_*.py
|
|
32
|
+
|
|
33
|
+
#claude
|
|
34
|
+
.claude
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sendflo-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Use Sendflo from Claude Desktop — manage campaigns, contacts, and segments with natural language
|
|
5
|
+
Project-URL: Homepage, https://sendflo.io
|
|
6
|
+
Project-URL: Repository, https://github.com/rahulxj100/whatsapp
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx>=0.27.0
|
|
10
|
+
Requires-Dist: mcp[cli]>=1.9.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# sendflo-mcp
|
|
14
|
+
|
|
15
|
+
Use [Sendflo](https://sendflo.io) from Claude Desktop. Manage campaigns, contacts, and segments with natural language.
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
**1. Get your Sendflo API key**
|
|
20
|
+
|
|
21
|
+
Sendflo dashboard → Settings → API Keys → Create
|
|
22
|
+
|
|
23
|
+
**2. Add to Claude Desktop config**
|
|
24
|
+
|
|
25
|
+
Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
26
|
+
Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"sendflo": {
|
|
32
|
+
"command": "uvx",
|
|
33
|
+
"args": ["sendflo-mcp"],
|
|
34
|
+
"env": {
|
|
35
|
+
"SENDFLO_API_KEY": "sk_sf_your_key_here"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**3. Restart Claude Desktop**
|
|
43
|
+
|
|
44
|
+
The Sendflo tools appear under the 🔨 icon in chat.
|
|
45
|
+
|
|
46
|
+
## Example prompts
|
|
47
|
+
|
|
48
|
+
- "List my VIP contacts"
|
|
49
|
+
- "Create an email campaign for the Diwali sale"
|
|
50
|
+
- "Generate a friendly email about 50% off"
|
|
51
|
+
- "What are my analytics?"
|
|
52
|
+
- "Send campaign 42"
|
|
53
|
+
|
|
54
|
+
## Requirements
|
|
55
|
+
|
|
56
|
+
- Claude Desktop (claude.ai/download)
|
|
57
|
+
- `uvx` — install via `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
|
58
|
+
- Sendflo account with API key
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# sendflo-mcp
|
|
2
|
+
|
|
3
|
+
Use [Sendflo](https://sendflo.io) from Claude Desktop. Manage campaigns, contacts, and segments with natural language.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
**1. Get your Sendflo API key**
|
|
8
|
+
|
|
9
|
+
Sendflo dashboard → Settings → API Keys → Create
|
|
10
|
+
|
|
11
|
+
**2. Add to Claude Desktop config**
|
|
12
|
+
|
|
13
|
+
Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
14
|
+
Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"sendflo": {
|
|
20
|
+
"command": "uvx",
|
|
21
|
+
"args": ["sendflo-mcp"],
|
|
22
|
+
"env": {
|
|
23
|
+
"SENDFLO_API_KEY": "sk_sf_your_key_here"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**3. Restart Claude Desktop**
|
|
31
|
+
|
|
32
|
+
The Sendflo tools appear under the 🔨 icon in chat.
|
|
33
|
+
|
|
34
|
+
## Example prompts
|
|
35
|
+
|
|
36
|
+
- "List my VIP contacts"
|
|
37
|
+
- "Create an email campaign for the Diwali sale"
|
|
38
|
+
- "Generate a friendly email about 50% off"
|
|
39
|
+
- "What are my analytics?"
|
|
40
|
+
- "Send campaign 42"
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
- Claude Desktop (claude.ai/download)
|
|
45
|
+
- `uvx` — install via `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
|
46
|
+
- Sendflo account with API key
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sendflo-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Use Sendflo from Claude Desktop — manage campaigns, contacts, and segments with natural language"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"mcp[cli]>=1.9.0",
|
|
14
|
+
"httpx>=0.27.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
sendflo-mcp = "sendflo_mcp.server:main"
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://sendflo.io"
|
|
22
|
+
Repository = "https://github.com/rahulxj100/whatsapp"
|
|
File without changes
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
API_KEY = os.environ["SENDFLO_API_KEY"]
|
|
9
|
+
BASE_URL = os.environ.get("SENDFLO_BASE_URL", "https://api.sendflo.io")
|
|
10
|
+
|
|
11
|
+
mcp = FastMCP("sendflo")
|
|
12
|
+
|
|
13
|
+
client = httpx.AsyncClient(
|
|
14
|
+
base_url=BASE_URL,
|
|
15
|
+
headers={"Authorization": f"Bearer {API_KEY}"},
|
|
16
|
+
timeout=30.0,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def _get(path: str, params: dict | None = None) -> dict | list:
|
|
21
|
+
try:
|
|
22
|
+
r = await client.get(path, params=params)
|
|
23
|
+
r.raise_for_status()
|
|
24
|
+
return r.json()
|
|
25
|
+
except httpx.HTTPStatusError as e:
|
|
26
|
+
return {"error": f"{e.response.status_code}: {e.response.text}"}
|
|
27
|
+
except httpx.RequestError as e:
|
|
28
|
+
return {"error": f"Network error: {e}"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def _post(path: str, body: dict | None = None) -> dict | list:
|
|
32
|
+
try:
|
|
33
|
+
r = await client.post(path, json=body)
|
|
34
|
+
r.raise_for_status()
|
|
35
|
+
return r.json()
|
|
36
|
+
except httpx.HTTPStatusError as e:
|
|
37
|
+
return {"error": f"{e.response.status_code}: {e.response.text}"}
|
|
38
|
+
except httpx.RequestError as e:
|
|
39
|
+
return {"error": f"Network error: {e}"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@mcp.tool()
|
|
43
|
+
async def list_contacts(tag: str | None = None, limit: int = 50) -> list:
|
|
44
|
+
"""List contacts. Optionally filter by tag."""
|
|
45
|
+
params: dict = {"limit": limit}
|
|
46
|
+
if tag:
|
|
47
|
+
params["tag"] = tag
|
|
48
|
+
return await _get("/contacts", params=params)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@mcp.tool()
|
|
52
|
+
async def create_contact(
|
|
53
|
+
name: str,
|
|
54
|
+
phone: str,
|
|
55
|
+
email: str | None = None,
|
|
56
|
+
tags: list[str] | None = None,
|
|
57
|
+
) -> dict:
|
|
58
|
+
"""Create a new contact."""
|
|
59
|
+
return await _post("/contacts", {
|
|
60
|
+
"name": name,
|
|
61
|
+
"phone": phone,
|
|
62
|
+
"email": email,
|
|
63
|
+
"tags": tags or [],
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@mcp.tool()
|
|
68
|
+
async def list_segments() -> list:
|
|
69
|
+
"""List all contact segments."""
|
|
70
|
+
return await _get("/segments")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@mcp.tool()
|
|
74
|
+
async def list_campaigns(status: str | None = None) -> list:
|
|
75
|
+
"""List campaigns. status filter: draft | scheduled | sent"""
|
|
76
|
+
params: dict = {}
|
|
77
|
+
if status:
|
|
78
|
+
params["status"] = status
|
|
79
|
+
return await _get("/campaigns", params=params)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@mcp.tool()
|
|
83
|
+
async def get_campaign(campaign_id: int) -> dict:
|
|
84
|
+
"""Get a campaign by ID including delivery stats (sent, delivered, read counts)."""
|
|
85
|
+
return await _get(f"/campaigns/{campaign_id}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@mcp.tool()
|
|
89
|
+
async def create_email_campaign(
|
|
90
|
+
name: str,
|
|
91
|
+
subject: str,
|
|
92
|
+
html: str,
|
|
93
|
+
segment_id: int | None = None,
|
|
94
|
+
) -> dict:
|
|
95
|
+
"""Create a draft email campaign. html is the full HTML body. Returns campaign with its ID."""
|
|
96
|
+
return await _post("/campaigns", {
|
|
97
|
+
"name": name,
|
|
98
|
+
"channel": "email",
|
|
99
|
+
"subject": subject,
|
|
100
|
+
"message": html,
|
|
101
|
+
"segment_id": segment_id,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@mcp.tool()
|
|
106
|
+
async def send_campaign(campaign_id: int) -> dict:
|
|
107
|
+
"""Send a campaign to all contacts in its segment. Irreversible."""
|
|
108
|
+
return await _post(f"/campaigns/{campaign_id}/send")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@mcp.tool()
|
|
112
|
+
async def get_analytics() -> dict:
|
|
113
|
+
"""Account analytics: credits remaining, plan, total campaigns, total sent, avg open rate."""
|
|
114
|
+
me = await _get("/auth/me")
|
|
115
|
+
if "error" in me:
|
|
116
|
+
return me
|
|
117
|
+
campaigns = await _get("/campaigns")
|
|
118
|
+
if isinstance(campaigns, dict) and "error" in campaigns:
|
|
119
|
+
return campaigns
|
|
120
|
+
sent = [c for c in campaigns if c.get("status") == "sent"]
|
|
121
|
+
total_sent = sum(c.get("sent_count", 0) for c in sent)
|
|
122
|
+
avg_open_rate = (
|
|
123
|
+
sum(c.get("read_count", 0) / max(c.get("sent_count", 1), 1) for c in sent) / len(sent)
|
|
124
|
+
if sent else 0.0
|
|
125
|
+
)
|
|
126
|
+
return {
|
|
127
|
+
"credits_remaining": me.get("credits", 0),
|
|
128
|
+
"plan": me.get("plan", "unknown"),
|
|
129
|
+
"total_campaigns": len(campaigns),
|
|
130
|
+
"total_sent": total_sent,
|
|
131
|
+
"avg_open_rate": round(avg_open_rate, 4),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@mcp.tool()
|
|
136
|
+
async def generate_email(prompt: str, tone: str = "friendly") -> dict:
|
|
137
|
+
"""Generate email subject + HTML from a prompt. tone: friendly | professional | urgent | playful"""
|
|
138
|
+
return await _post("/ai/generate-email", {"prompt": prompt, "tone": tone})
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@mcp.tool()
|
|
142
|
+
async def score_contacts() -> dict:
|
|
143
|
+
"""Score all contacts hot/warm/cold by campaign engagement. Requires Pro plan."""
|
|
144
|
+
return await _post("/ai/score-contacts")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@mcp.tool()
|
|
148
|
+
async def list_workspaces() -> list:
|
|
149
|
+
"""List agency sub-workspaces. To operate in a workspace, use its own API key in a separate MCP config entry."""
|
|
150
|
+
return await _get("/workspaces")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main() -> None:
|
|
154
|
+
mcp.run()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
main()
|