mailerbot-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,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .pytest_cache/
@@ -0,0 +1,24 @@
1
+ FROM python:3.13-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install uv for fast dependency resolution
6
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
7
+
8
+ # Copy SDK and MCP server source
9
+ COPY sdk/python /app/sdk/python
10
+ COPY mcp /app/mcp
11
+
12
+ # Install the MCP server (which pulls in the local SDK)
13
+ RUN uv pip install --system /app/sdk/python /app/mcp
14
+
15
+ EXPOSE 8080
16
+
17
+ # Default to streamable-http (current MCP standard) for remote deployment
18
+ ENV MAILERBOT_MCP_TRANSPORT=streamable-http
19
+ ENV MAILERBOT_MCP_HOST=0.0.0.0
20
+ ENV MAILERBOT_MCP_PORT=8080
21
+ # In Docker Compose, MAILERBOT_API_URL and MAILERBOT_OAUTH_ISSUER are set
22
+ # via environment or env_file to point at the backend service.
23
+
24
+ CMD ["mailerbot-mcp"]
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.5
2
+ Name: mailerbot-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for the MailerBot direct mail API
5
+ Project-URL: Homepage, https://mailerbot.com
6
+ Project-URL: Documentation, https://mailerbot.com/docs/mcp
7
+ Project-URL: Repository, https://github.com/mailerbot-hq/MailerBot/tree/dev/mcp
8
+ Project-URL: Bug Tracker, https://github.com/mailerbot-hq/MailerBot/issues
9
+ Author-email: MailerBot <dev@mailerbot.com>
10
+ License: MIT
11
+ Keywords: ai,direct-mail,mailerbot,mcp,model-context-protocol
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: mailerbot>=0.1.0
23
+ Requires-Dist: mcp[cli]>=1.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ # MailerBot MCP Server
27
+
28
+ MCP (Model Context Protocol) server for the MailerBot direct mail API. Enables AI tools like Claude, Cursor, and others to manage contacts, mailings, campaigns, and more.
29
+
30
+ ## Remote server (recommended)
31
+
32
+ URL: `https://mcp.mailerbot.com/mcp`. OAuth is handled automatically — no API key to manage.
33
+
34
+ In Claude: **Settings > Connectors > Add custom connector**, then paste the URL above.
35
+
36
+ ## Quick Start (Local / STDIO)
37
+
38
+ ```bash
39
+ # Set your API key
40
+ export MAILERBOT_API_KEY="mb_live_..."
41
+
42
+ # Run via uvx
43
+ uvx mailerbot-mcp
44
+
45
+ # Or run from source
46
+ cd mcp
47
+ uv run mailerbot-mcp
48
+ ```
49
+
50
+ ### Claude Desktop Configuration
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "mailerbot": {
56
+ "command": "uvx",
57
+ "args": ["mailerbot-mcp"],
58
+ "env": {
59
+ "MAILERBOT_API_KEY": "mb_live_..."
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Self-Hosted (SSE)
67
+
68
+ ```bash
69
+ docker build -t mailerbot-mcp -f mcp/Dockerfile .
70
+ docker run -p 8080:8080 \
71
+ -e MAILERBOT_MCP_TRANSPORT=sse \
72
+ -e MAILERBOT_BEARER_TOKEN="..." \
73
+ mailerbot-mcp
74
+ ```
75
+
76
+ ## Environment Variables
77
+
78
+ | Variable | Description | Default |
79
+ |---|---|---|
80
+ | `MAILERBOT_API_KEY` | API key for authentication | — |
81
+ | `MAILERBOT_BEARER_TOKEN` | OAuth bearer token (alternative to API key) | — |
82
+ | `MAILERBOT_API_URL` | API base URL | `https://api.mailerbot.com/api/v1` |
83
+ | `MAILERBOT_MCP_TRANSPORT` | Transport mode: `stdio` or `sse` | `stdio` |
84
+ | `MAILERBOT_MCP_HOST` | Host to bind (SSE mode) | `0.0.0.0` |
85
+ | `MAILERBOT_MCP_PORT` | Port to bind (SSE mode) | `8080` |
86
+
87
+ ## Available Tools
88
+
89
+ **Contacts** — list, get, create, update, delete, validate addresses
90
+ **Contact Lists** — list, get, create, update, delete, add/remove contacts
91
+ **Documents** — list, get, create, update, delete
92
+ **Postcards** — list templates, list, get, create, delete
93
+ **Mailings** — list, get, create, update, delete, validate addresses, estimate cost, calculate cost, send
94
+ **Campaigns** — list, get, create, update, delete, add/remove mailings
95
+ **Dashboard** — stats, reporting
96
+ **Pricing** — catalog, countries, postage zones, postage rates
97
+ **Coupons** — list, get, create, update, delete, list/import codes, check availability
98
+ **QR Tracking** — list, create, delete links, analytics
99
+ **Merge Tags** — list available tags
@@ -0,0 +1,74 @@
1
+ # MailerBot MCP Server
2
+
3
+ MCP (Model Context Protocol) server for the MailerBot direct mail API. Enables AI tools like Claude, Cursor, and others to manage contacts, mailings, campaigns, and more.
4
+
5
+ ## Remote server (recommended)
6
+
7
+ URL: `https://mcp.mailerbot.com/mcp`. OAuth is handled automatically — no API key to manage.
8
+
9
+ In Claude: **Settings > Connectors > Add custom connector**, then paste the URL above.
10
+
11
+ ## Quick Start (Local / STDIO)
12
+
13
+ ```bash
14
+ # Set your API key
15
+ export MAILERBOT_API_KEY="mb_live_..."
16
+
17
+ # Run via uvx
18
+ uvx mailerbot-mcp
19
+
20
+ # Or run from source
21
+ cd mcp
22
+ uv run mailerbot-mcp
23
+ ```
24
+
25
+ ### Claude Desktop Configuration
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "mailerbot": {
31
+ "command": "uvx",
32
+ "args": ["mailerbot-mcp"],
33
+ "env": {
34
+ "MAILERBOT_API_KEY": "mb_live_..."
35
+ }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Self-Hosted (SSE)
42
+
43
+ ```bash
44
+ docker build -t mailerbot-mcp -f mcp/Dockerfile .
45
+ docker run -p 8080:8080 \
46
+ -e MAILERBOT_MCP_TRANSPORT=sse \
47
+ -e MAILERBOT_BEARER_TOKEN="..." \
48
+ mailerbot-mcp
49
+ ```
50
+
51
+ ## Environment Variables
52
+
53
+ | Variable | Description | Default |
54
+ |---|---|---|
55
+ | `MAILERBOT_API_KEY` | API key for authentication | — |
56
+ | `MAILERBOT_BEARER_TOKEN` | OAuth bearer token (alternative to API key) | — |
57
+ | `MAILERBOT_API_URL` | API base URL | `https://api.mailerbot.com/api/v1` |
58
+ | `MAILERBOT_MCP_TRANSPORT` | Transport mode: `stdio` or `sse` | `stdio` |
59
+ | `MAILERBOT_MCP_HOST` | Host to bind (SSE mode) | `0.0.0.0` |
60
+ | `MAILERBOT_MCP_PORT` | Port to bind (SSE mode) | `8080` |
61
+
62
+ ## Available Tools
63
+
64
+ **Contacts** — list, get, create, update, delete, validate addresses
65
+ **Contact Lists** — list, get, create, update, delete, add/remove contacts
66
+ **Documents** — list, get, create, update, delete
67
+ **Postcards** — list templates, list, get, create, delete
68
+ **Mailings** — list, get, create, update, delete, validate addresses, estimate cost, calculate cost, send
69
+ **Campaigns** — list, get, create, update, delete, add/remove mailings
70
+ **Dashboard** — stats, reporting
71
+ **Pricing** — catalog, countries, postage zones, postage rates
72
+ **Coupons** — list, get, create, update, delete, list/import codes, check availability
73
+ **QR Tracking** — list, create, delete links, analytics
74
+ **Merge Tags** — list available tags
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "mailerbot-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server for the MailerBot direct mail API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "MailerBot", email = "dev@mailerbot.com" }]
13
+ keywords = ["mailerbot", "mcp", "model-context-protocol", "direct-mail", "ai"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "mcp[cli]>=1.0",
27
+ "mailerbot>=0.1.0",
28
+ ]
29
+
30
+ [project.scripts]
31
+ mailerbot-mcp = "mailerbot_mcp:main"
32
+
33
+ [project.urls]
34
+ Homepage = "https://mailerbot.com"
35
+ Documentation = "https://mailerbot.com/docs/mcp"
36
+ Repository = "https://github.com/mailerbot-hq/MailerBot/tree/dev/mcp"
37
+ "Bug Tracker" = "https://github.com/mailerbot-hq/MailerBot/issues"
38
+
39
+ [dependency-groups]
40
+ dev = ["pytest"]
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/mailerbot_mcp"]
47
+
48
+ # For local development, use the SDK from the monorepo
49
+ [tool.uv.sources]
50
+ mailerbot = { path = "../sdk/python", editable = true }
@@ -0,0 +1,83 @@
1
+ import os
2
+
3
+ from .server import mcp
4
+
5
+
6
+ def _configure_auth() -> None:
7
+ """Configure OAuth authentication for remote (SSE/streamable-http) mode.
8
+
9
+ Sets up the MCP server as an OAuth Resource Server that:
10
+ - Points MCP clients to the MailerBot backend's OAuth endpoints
11
+ - Validates bearer tokens by calling the backend's /auth/me endpoint
12
+ - Passes valid tokens through to the SDK for API calls
13
+ """
14
+ from pydantic import AnyHttpUrl
15
+ from mcp.server.auth.settings import AuthSettings
16
+ from .auth import MailerBotTokenVerifier
17
+
18
+ api_url = os.environ.get(
19
+ "MAILERBOT_API_URL", "https://api.mailerbot.com/api/v1"
20
+ )
21
+ # The OAuth issuer is the backend root (without /api/v1)
22
+ issuer_url = os.environ.get(
23
+ "MAILERBOT_OAUTH_ISSUER",
24
+ api_url.replace("/api/v1", ""),
25
+ )
26
+ # The MCP server's own public URL (for resource server metadata)
27
+ mcp_url = os.environ.get("MAILERBOT_MCP_URL", "http://localhost:8080")
28
+
29
+ mcp.settings.auth = AuthSettings(
30
+ issuer_url=AnyHttpUrl(issuer_url),
31
+ resource_server_url=AnyHttpUrl(mcp_url),
32
+ )
33
+ mcp._token_verifier = MailerBotTokenVerifier(api_base_url=api_url)
34
+
35
+
36
+ def main():
37
+ transport = os.environ.get("MAILERBOT_MCP_TRANSPORT", "stdio")
38
+
39
+ if transport in ("sse", "streamable-http"):
40
+ import uvicorn
41
+ from urllib.parse import urlparse
42
+ from starlette.middleware.cors import CORSMiddleware
43
+ from mcp.server.transport_security import TransportSecuritySettings
44
+
45
+ _configure_auth()
46
+ host = os.environ.get("MAILERBOT_MCP_HOST", "0.0.0.0")
47
+ port = int(os.environ.get("MAILERBOT_MCP_PORT", "8080"))
48
+
49
+ # Configure transport security to allow the public-facing hostname.
50
+ # Behind an ALB the Host header is the public domain, not localhost.
51
+ mcp_url = os.environ.get("MAILERBOT_MCP_URL", "http://localhost:8080")
52
+ parsed = urlparse(mcp_url)
53
+ mcp.settings.transport_security = TransportSecuritySettings(
54
+ enable_dns_rebinding_protection=True,
55
+ allowed_hosts=[
56
+ parsed.netloc, # e.g. "mcp.mailerbot.com" or "localhost:8080"
57
+ "127.0.0.1:*",
58
+ "localhost:*",
59
+ ],
60
+ allowed_origins=["*"],
61
+ )
62
+
63
+ # Get the ASGI app from FastMCP and wrap with CORS middleware.
64
+ # This ensures OPTIONS preflight requests are handled before the
65
+ # auth middleware rejects them with 401.
66
+ if transport == "streamable-http":
67
+ app = mcp.streamable_http_app()
68
+ else:
69
+ app = mcp.sse_app()
70
+
71
+ app.add_middleware(
72
+ CORSMiddleware,
73
+ allow_origins=["*"],
74
+ allow_methods=["*"],
75
+ allow_headers=["*"],
76
+ )
77
+
78
+ uvicorn.run(app, host=host, port=port)
79
+ else:
80
+ mcp.run(transport="stdio")
81
+
82
+
83
+ __all__ = ["main", "mcp"]
@@ -0,0 +1,5 @@
1
+ """Allow running as `python -m mailerbot_mcp` or `uv run mailerbot-mcp`."""
2
+
3
+ from mailerbot_mcp import main
4
+
5
+ main()
@@ -0,0 +1,123 @@
1
+ """Token verification for the MailerBot MCP server.
2
+
3
+ In SSE/streamable-http mode, MCP clients authenticate via OAuth against the
4
+ MailerBot backend. The MCP server acts as a Resource Server (RS) — it
5
+ validates incoming bearer tokens by calling the backend's ``/api/v1/auth/me``
6
+ endpoint. If the backend returns the user profile, the token is valid.
7
+
8
+ Verified tokens are cached in memory (keyed by token hash) to avoid a
9
+ backend round-trip on every MCP tool invocation.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ import hashlib
16
+ import json
17
+ import logging
18
+ import os
19
+ import time
20
+
21
+ import httpx
22
+
23
+ from mcp.server.auth.provider import AccessToken, TokenVerifier
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # Cache verified tokens for up to 5 minutes to reduce backend round-trips.
28
+ _TOKEN_CACHE_TTL = 300
29
+ _token_cache: dict[str, tuple[AccessToken, float]] = {}
30
+
31
+
32
+ class MailerBotTokenVerifier(TokenVerifier):
33
+ """Verify bearer tokens by calling the MailerBot backend."""
34
+
35
+ def __init__(self, api_base_url: str | None = None):
36
+ self.api_base_url = (api_base_url or os.environ.get(
37
+ "MAILERBOT_API_URL", "https://api.mailerbot.com/api/v1"
38
+ )).rstrip("/")
39
+
40
+ async def verify_token(self, token: str) -> AccessToken | None:
41
+ """Verify a bearer token against the MailerBot backend.
42
+
43
+ Checks the local cache first; if not cached, calls GET
44
+ /api/v1/auth/me with the token. Valid tokens are cached to avoid
45
+ repeated round-trips for the same session.
46
+ """
47
+ # Check cache first
48
+ cache_key = hashlib.sha256(token.encode()).hexdigest()[:16]
49
+ cached = _token_cache.get(cache_key)
50
+ if cached is not None:
51
+ access_token, cached_at = cached
52
+ if time.monotonic() - cached_at < _TOKEN_CACHE_TTL:
53
+ return access_token
54
+ else:
55
+ del _token_cache[cache_key]
56
+
57
+ try:
58
+ async with httpx.AsyncClient(timeout=10.0) as client:
59
+ resp = await client.get(
60
+ f"{self.api_base_url}/auth/me",
61
+ headers={"Authorization": f"Bearer {token}"},
62
+ )
63
+
64
+ if resp.status_code != 200:
65
+ logger.debug("Token verification failed: %s", resp.status_code)
66
+ return None
67
+
68
+ user_data = resp.json()
69
+
70
+ scopes = _extract_scopes_from_jwt(token)
71
+ expires_at = _extract_exp_from_jwt(token)
72
+
73
+ access_token = AccessToken(
74
+ token=token,
75
+ client_id=user_data.get("id", "unknown"),
76
+ scopes=scopes,
77
+ expires_at=expires_at,
78
+ )
79
+
80
+ # Cache the verified token
81
+ _token_cache[cache_key] = (access_token, time.monotonic())
82
+
83
+ return access_token
84
+
85
+ except httpx.HTTPError as exc:
86
+ logger.error("Token verification HTTP error: %s", exc)
87
+ return None
88
+ except Exception as exc:
89
+ logger.error("Token verification error: %s", exc)
90
+ return None
91
+
92
+
93
+ def _decode_jwt_payload(token: str) -> dict | None:
94
+ """Decode JWT payload without signature verification.
95
+
96
+ The backend already validated the token — we just need the claims.
97
+ """
98
+ try:
99
+ payload_b64 = token.split(".")[1]
100
+ # Add padding
101
+ padding = 4 - len(payload_b64) % 4
102
+ if padding != 4:
103
+ payload_b64 += "=" * padding
104
+ return json.loads(base64.urlsafe_b64decode(payload_b64))
105
+ except Exception:
106
+ return None
107
+
108
+
109
+ def _extract_scopes_from_jwt(token: str) -> list[str]:
110
+ """Extract scopes from a JWT."""
111
+ payload = _decode_jwt_payload(token)
112
+ return payload.get("scopes", []) if payload else []
113
+
114
+
115
+ def _extract_exp_from_jwt(token: str) -> int | None:
116
+ """Extract expiration from a JWT."""
117
+ payload = _decode_jwt_payload(token)
118
+ if not payload:
119
+ return None
120
+ exp = payload.get("exp")
121
+ if exp is None:
122
+ logger.warning("JWT has no exp claim — token refresh may not work correctly")
123
+ return exp