redtail-mcp
MCP server that exposes Redtail CRM's TWAPI as tools, enabling CRM operations like contacts, activities, and onboarding via natural language.
README
redtail-mcp
An agent-agnostic FastMCP stdio server that exposes the Redtail CRM public API (TWAPI) as Model Context Protocol tools.
Any MCP client can drive it — Claude Desktop, Claude Code, or a custom agent built on the Claude Agent SDK or another MCP-aware framework. The server speaks JSON-RPC over stdio; stdout is the protocol stream, all logs go to stderr.
Two things distinguish it from a thin wrapper:
- Firm-identity externalization. No advisor names, user ids, rep codes,
or activity-code labels live in the tool modules.
config.pyloads them from JSON env vars (with placeholder defaults) so the same binary serves a different firm by pointing at a different.env. - Multi-user attribution. Optionally set
REDTAIL_USER_KEYSto a map of{name: durable_user_key}. When a write tool is called with anacting_userargument, the server authenticates as that user via Redtail'sUserKeyAuth, so the new record'sadded_byis stamped with the real person — on one shared API subscription.
Install
Requires Python 3.11 and uv.
git clone https://github.com/your-org/redtail-mcp.git
cd redtail-mcp
uv sync
cp .env.example .env # then fill in the three required secrets
Configuration
Everything the server reads at boot is documented inline in
.env.example. At minimum you must set:
| Variable | Purpose |
|---|---|
TWAPI_API_KEY |
TWAPI subscription key from the Redtail developer portal. |
TWAPI_USERNAME |
Shared-account login (fallback attribution for writes). |
TWAPI_PASSWORD |
Shared-account password. |
Optional — override placeholder defaults to make the server your firm's:
| Variable | Purpose |
|---|---|
REDTAIL_USER_KEYS |
JSON {name: user_key} for per-user write attribution. Secret; env-only. |
REDTAIL_DEFAULT_USER_ID |
User id used as the attendee floor / default owner. |
REDTAIL_TEAM_MAP |
JSON {name: user_id} — resolves friendly names in tool args. |
REDTAIL_REP_ADVISOR_MAP |
JSON {rep_code: {routing}} — drives onboarding + call defaults. |
REDTAIL_ACTIVITY_CODE_LEGEND |
JSON {id: name} — friendly names for activity codes. |
REDTAIL_ALLOWED_TOOLS |
CSV allowlist — trim the exposed catalog without removing capability. |
TWAPI_BASE_URL, TWAPI_MAX_RETRIES, TWAPI_CONCURRENCY, LOG_LEVEL |
Transport tuning. |
The server fails loud (sys.exit 2) at boot on a missing credential or a
malformed JSON map — a misconfigured deployment dies instead of silently
mis-routing writes.
Register with Claude Desktop
Add an entry under mcpServers in claude_desktop_config.json:
{
"mcpServers": {
"redtail": {
"command": "uv",
"args": [
"--directory", "/path/to/redtail-mcp",
"run", "python", "server.py"
]
}
}
}
Restart Claude Desktop; the redtail server should appear in the MCP panel
with its tools listed.
Register with Claude Code
claude mcp add redtail -- uv --directory /path/to/redtail-mcp run python server.py
Or add it to a project's .mcp.json:
{
"mcpServers": {
"redtail": {
"command": "uv",
"args": ["--directory", "/path/to/redtail-mcp", "run", "python", "server.py"]
}
}
}
Tool reference
43 tools are implemented across four modules. The default catalog exposes
them all; narrow it with REDTAIL_ALLOWED_TOOLS.
Activities (tools_activities.py)
| Tool | Purpose | Key args |
|---|---|---|
get_agenda |
One person's or the team's activities for today / a range. | user_ids?, date_range? |
get_overdue_activities |
Every open activity past its due date. | user_ids? |
get_activities_in_range |
Activities in an explicit ISO date range. | start, end, user_ids? |
get_activity |
Fetch a single activity by id. | activity_id |
get_contact_activity_history |
Activity history for one contact. | contact_id, kind? |
create_task |
Create a task activity (owner defaults resolved from name). | subject, contact_id?, due_date?, user_ids?, acting_user? |
log_call |
Log a completed phone call against a contact. | contact_id, subject, note?, acting_user? |
add_note |
Add a durable note as a logged activity. | contact_id, note, acting_user? |
complete_activity |
Mark an activity done with an optional result. | activity_id, result?, acting_user? |
reschedule_activity |
Move an activity's start / end. | activity_id, new_start, new_end?, acting_user? |
assign_activity |
Change an activity's owner / attendees. | activity_id, user_ids, acting_user? |
create_activity |
General activity create with full field control. | subject, activity_code_id, start, end?, ... |
update_activity |
Patch fields on an existing activity. | activity_id, patch, acting_user? |
batch_update_activities |
Apply the same patch across many activities. | activity_ids, patch, acting_user? |
delete_activity |
Delete an activity. | activity_id, acting_user? |
Contacts (tools_contacts.py)
| Tool | Purpose | Key args |
|---|---|---|
search_contacts |
Full-text contact search. | q, limit? |
find_contact_by_email |
Exact-email lookup. | email |
get_contact |
Full contact record. | contact_id |
get_contact_notes |
Notes attached to a contact. | contact_id |
get_important_info |
Memberships / important-info sheet. | contact_id |
get_professional_contacts |
Attorneys / CPAs / other advisors linked to a contact. | contact_id |
get_family |
Family + spouse links. | contact_id |
get_contact_photo |
Profile photo URL. | contact_id |
get_household |
Household record. | household_id |
resolve_contact |
Fuzzy identity resolution — "which contact do you mean?". | q |
create_contact |
Create a new contact. | first_name, last_name, ... |
update_contact |
Patch fields on an existing contact. | contact_id, patch, acting_user? |
add_phone |
Add a phone number. | contact_id, number, type?, acting_user? |
add_email |
Add an email address. | contact_id, email, type?, acting_user? |
add_address |
Add a mailing address. | contact_id, street, city, state, zip, type?, acting_user? |
link_family |
Link a family relationship. | contact_id, related_id, relation, acting_user? |
add_tag_member |
Put a contact on a tag (cohort). | tag, contact_id, acting_user? |
add_contact_keyword |
Add a keyword to a contact. | contact_id, keyword, acting_user? |
update_important_info |
Patch important-info fields. | contact_id, patch, acting_user? |
remove_contact_subresource |
Remove a phone / email / address / etc. | contact_id, resource, resource_id, acting_user? |
Onboarding (tools_onboarding.py)
| Tool | Purpose | Key args |
|---|---|---|
create_onboarding_tasks |
Example workflow: create a bundle of onboarding tasks for a new client, routed off the rep code. Override the plan by passing tasks=[...]. |
contact_id, rep_code, effective_date?, tasks?, acting_user? |
Reference (tools_reference.py)
| Tool | Purpose | Key args |
|---|---|---|
list_database_users |
Enumerate every user in the Redtail database. | — |
list_activity_codes |
Enumerate activity codes. | — |
list_contact_categories |
Enumerate contact categories. | — |
list_tags |
Enumerate tags. | — |
list_tag_members |
List contacts on a tag (cohort). | tag |
list_contacts |
Filtered contact list (category / status / updated-since). | category_id?, status_id?, updated_since? |
remove_tag_member |
Take a contact off a tag. | tag, contact_id |
Usage examples
Once the server is registered, natural-language prompts flow through the
tools. All examples below use the fictional client Jane Sample.
Read. "Who's on my calendar this week?" — the client calls get_agenda
with user_ids resolved from the caller's name via REDTAIL_TEAM_MAP.
Write with default attribution. "Log a call with Jane Sample: reviewed
her rollover paperwork, will send the DocuSign tomorrow." — the client
calls log_call(contact_id=..., subject="Rollover paperwork review", note="..."). The record's added_by is the shared TWAPI user.
Write with per-user attribution. With REDTAIL_USER_KEYS populated and
an agent that tags the inbound user (e.g. [alice|100001]), pass
acting_user="alice" on any write tool. The server switches to
UserKeyAuth for that call and Redtail stamps the record as coming from
Alice — even though the API subscription is shared.
Onboarding. "Start onboarding for Jane Sample, rep code REP01." —
create_onboarding_tasks(contact_id=..., rep_code="REP01") creates the
example task bundle, routed to the owner mapped in
REDTAIL_REP_ADVISOR_MAP.
Development
uv sync --extra dev
uv run pytest
uv run python tests/smoke_stdio.py
DESIGN.md covers the architecture — request path, auth model, per-user
attribution decision, truncation contract, and the firm-identity
externalization pattern.
License
MIT. See LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.