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()