Multi-language client ecosystem for the Front customer-communication platform. Front is a shared-inbox product that unifies email, SMS, chat, and social channels into one place for support and sales teams. Their Core API exposes conversations, messages, contacts, teammates, tags, inboxes, and more.
This monorepo ships:
- A production-grade Python client with transport-layer retries, rate-limit awareness, and pagination handling for Front's cursor-token paging.
- A TypeScript client generated from the same OpenAPI spec via hey-api.
- An MCP server exposing Frontapp operations as tools to AI assistants like Claude Desktop and Cursor.
The OpenAPI spec is vendored from
frontapp/front-api-specs and sanitized by
scripts/vendor_spec.py (strips binary attachment-download paths and normalizes a few
upstream quirks that trip the generators).
| Package | Language | Version | Description |
|---|---|---|---|
| frontapp-openapi-client | Python | 0.1.0 | API client with transport-layer resilience + domain helpers |
| frontapp-mcp-server | Python | 0.1.0 | Model Context Protocol server for AI assistants |
| frontapp-client | TypeScript | 0.1.0 | Generated TypeScript/JavaScript client |
| Feature | Python | TypeScript | MCP Server |
|---|---|---|---|
| Automatic retries (network + 5xx) | Yes | Yes | Yes (via Python client) |
| Rate-limit retry with exponential backoff | Yes | Yes | Yes |
| Cursor-token pagination helpers | Yes | Yes | Yes |
| Two-step confirm on mutations (ctx.elicit) | — | — | Yes |
| Full type safety (attrs + Pydantic / TS types) | Yes | Yes | Yes |
| AI tool surface | — | — | Claude Desktop, Cursor |
pip install frontapp-openapi-clientimport asyncio
from frontapp_public_api_client import FrontappClient
async def main():
async with FrontappClient() as client:
# List recent open conversations tagged "urgent"
conversations = await client.conversations.list(
q="status:open tag:urgent", limit=25
)
for conv in conversations:
assignee = conv.assignee.username if conv.assignee else "unassigned"
print(f"{conv.id}: {conv.subject!r} — {conv.status} ({assignee})")
asyncio.run(main())npm install frontapp-clientimport { FrontappClient } from "frontapp-client";
const client = await FrontappClient.create();
const { data } = await client.listConversations({ query: { q: "status:open" } });
console.log(`${data._results.length} open conversations`);pip install frontapp-mcp-serverAdd to Claude Desktop config
(~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"frontapp": {
"command": "uvx",
"args": ["frontapp-mcp-server"],
"env": {
"FRONTAPP_API_KEY": "your-api-key-here"
}
}
}
}Get an API key at Front → Settings → Developers → API tokens.
All packages authenticate with a Front API token via bearer auth:
- Environment variable:
FRONTAPP_API_KEY .envfile:FRONTAPP_API_KEY=your-key- Direct parameter:
FrontappClient(api_key="...") ~/.netrc:machine api2.frontapp.com+password your-key
# .env
FRONTAPP_API_KEY=your-api-key-here
FRONTAPP_BASE_URL=https://api2.frontapp.com # optional overrideThe generated clients cover Front's full Core API — 139 paths, 233 operations
spanning conversations, messages, contacts, teammates, accounts, tags, inboxes,
channels, rules, custom fields, drafts, message templates, analytics, and more. Browse
frontapp_public_api_client/api/ (Python) or
packages/frontapp-client/src/generated/sdk.gen.ts (TS) for the full surface.
Hand-written ergonomic helpers (Python client.<resource>.…) and MCP tools wrap the
highest-value subset. Current status:
| Resource | Python helper (client.X.…) |
MCP tools |
|---|---|---|
| Conversations | ✅ .conversations |
list / get / search / list_messages / list_comments / update / add_comment |
| Drafts | ✅ .drafts |
list_conversation_drafts / create_draft_on_channel / create_draft_reply / edit_draft / delete_draft |
| Contacts | ✅ .contacts |
list / get / lookup_by_email / list_team / list_teammate / list_conversations / list_notes / create (+ team/teammate variants) / update / merge / delete / add_note / add_handle / delete_handle |
| Contact Lists | ✅ .contact_lists |
list (+ team/teammate scopes) / list_members / create (+ team/teammate variants) / delete / add_contacts / remove_contacts |
| Contact Groups (deprecated) | ✅ .contact_groups |
Same shape as contact_lists; Front has deprecated this surface — prefer contact_lists for new code |
| Messages | ✅ .messages |
get / seen_status / mark_seen |
| Tags | ✅ .tags |
list (+ company/team/teammate scopes) / get / list_children / list_tagged_conversations / add_tag_to_conversation / remove_tag_from_conversation / create / create_child / update / delete |
| Inboxes | ✅ .inboxes |
list (+ team/teammate scopes) / get / list_conversations / list_channels / list_access / create (+ team) / grant_access / revoke_access |
| Teammates | ✅ .teammates |
list / get / list_inboxes / list_assigned_conversations / update |
| Attachments | ✅ .attachments |
download_attachment + attachment_paths parameter on every draft / reply / comment tool |
| Analytics | ⏳ planned | ⏳ planned (create→poll recipe) |
See the issue tracker for
the roadmap. The full generated surface is usable today via direct imports from
frontapp_public_api_client.api.<tag> — helpers and MCP tools just add ergonomic polish
on top.
Conversation list/search endpoints accept Front's q= search syntax:
| Query | Description |
|---|---|
status:open |
Open conversations |
status:archived |
Archived |
tag:urgent |
Tagged urgent |
assignee:me |
Assigned to the token owner |
is:unassigned |
Unassigned |
inbox:support |
In a named inbox |
after:2024-01-01 |
Updated after a date |
before:2024-12-31 |
Updated before a date |
Combine with AND / OR: status:open AND tag:urgent.
Mutations use a two-step confirm pattern — call with confirm=false for a preview, then
confirm=true to apply (which also elicits explicit user approval via ctx.elicit).
Customer-facing replies always go through the drafts vertical: agents draft, humans
send.
| Tool | Mutation? | Description |
|---|---|---|
list_conversations |
no | Filter + cursor-paginate, reverse chronological |
get_conversation |
no | Full detail for one conversation |
search_conversations |
no | Full Front search syntax as the primary filter |
list_conversation_messages |
no | Messages inside a conversation (most recent first) |
list_conversation_comments |
no | Internal teammate comments on a conversation |
update_conversation |
yes | Archive/reopen, reassign, retag, move inbox |
add_conversation_comment |
yes | Add a teammate-only comment (never reaches the customer) |
| Tool | Mutation? | Description |
|---|---|---|
list_conversation_drafts |
no | Existing drafts on a conversation |
create_draft_on_channel |
yes | Brand-new outbound draft on a channel |
create_draft_reply |
yes | Draft a reply on an existing conversation |
edit_draft |
yes | Full-replacement edit (body + channel_id required) |
delete_draft |
yes | Discard a draft |
Front exposes no programmatic send_draft — drafts are reviewed and sent by a human in
Front's UI. There is intentionally no reply_to_conversation tool.
Reads are cached for 30s via ResponseCachingMiddleware.
Every endpoint inherits these transport-layer behaviors automatically — no decorators or wrappers needed:
- Retries:
httpx-retriesretries idempotent methods on 502/503/504 and any method on 429 (safe because 429 means "not processed"). Configurable viamax_retries. - Rate-limit awareness:
Retry-Afterheaders are honored when present; otherwise exponential backoff. - Sensitive-data redaction:
Authorizationheaders and common secret field names (api_key,password,token, etc.) are scrubbed from log output.
Auto-pagination for Front's cursor-token paging — every paginated list method has an
iter_* async-iterator counterpart:
| Helper | iter_all |
Variant iterators |
|---|---|---|
client.conversations |
yes | iter_search, iter_messages |
client.contacts |
yes | iter_for_team, iter_for_teammate, iter_conversations |
client.tags |
yes | iter_company, iter_for_team, iter_for_teammate, iter_conversations |
client.inboxes |
(no top-level pagination) | iter_conversations |
async for conv in client.conversations.iter_all(q="status:open", max_items=500):
print(conv.id, conv.subject)The iterator walks _pagination.next until exhausted or a safety limit (max_items,
max_pages) trips. Pass page_token manually to the underlying list() if you need
cursor control instead.
frontapp-openapi-client/ # Monorepo root
├── pyproject.toml # uv workspace configuration
├── pnpm-workspace.yaml # pnpm TS workspace
├── scripts/vendor_spec.py # download + sanitize Front's OpenAPI spec
├── docs/
│ ├── frontapp-openapi.yaml # OpenAPI 3.0.0 spec (vendored + sanitized)
│ ├── FRONTAPP_API_ENDPOINTS.md # Prioritization reference
│ └── *.md # Shared documentation
├── frontapp_public_api_client/ # Python client
│ ├── frontapp_client.py # Resilient client + transport layer
│ ├── domain/ # Pydantic domain models (Conversation, …)
│ ├── helpers/ # Ergonomic facades (client.conversations)
│ ├── utils.py # unwrap/is_success + error types
│ ├── api/, models/ # Generated from the OpenAPI spec
│ └── docs/ # Package documentation
├── frontapp_mcp_server/ # MCP server (FastMCP)
│ └── src/frontapp_mcp/
│ ├── server.py # Lifespan, auth, caching middleware
│ ├── tools/ # MCP tool modules (conversations, …)
│ └── resources/ # help + workspace-orientation resources
└── packages/
└── frontapp-client/ # TypeScript client (hey-api)
├── src/
│ ├── client.ts # createClient + transport stack
│ ├── transport/ # resilient.ts, pagination.ts
│ └── generated/ # Generated from the same spec
└── openapi-ts.config.ts
git clone https://github.com/dougborg/frontapp-openapi-client.git
cd frontapp-openapi-client
uv sync --extra dev # install Python deps including dev tools
uv run pre-commit install # install git hooks
pnpm install # install TS deps
cp .env.example .env # add FRONTAPP_API_KEYuv run python scripts/vendor_spec.py # refresh docs/frontapp-openapi.yaml from upstream
uv run poe regenerate-client # regenerate the Python client from the spec
pnpm --filter frontapp-client generate # regenerate the TypeScript client
uv run poe test # pytest -n 4
uv run poe quick-check # format + lint
uv run poe check # format + lint + typecheck + testConventional commits drive per-package semantic-release versioning:
git commit -m "feat(client): add contacts helper"
git commit -m "fix(client): handle empty paginated response"
git commit -m "feat(mcp): add tools for tag management"
git commit -m "feat(ts): export retry middleware"
git commit -m "docs: update quick-start"Use ! for breaking changes: feat(client)!: drop Python 3.11 support.
See MONOREPO_SEMANTIC_RELEASE.md for details.
- Scaffolding pattern lifted from dougborg/statuspro-openapi-client and dougborg/katana-openapi-client, which established the shape of this ecosystem (Python + TS client + FastMCP server, transport-layer resilience, two-step confirm mutations, etc.).
- OpenAPI spec from frontapp/front-api-specs.
MIT License — see LICENSE.
Contributions welcome. See CONTRIBUTING.md for guidelines.