Developers · MCP
Build on the registry your team already uses.
External agents operate Limena over the Model Context Protocol: the same 58 capabilities Limena’s own assistant uses, with scoped API keys, rate zones, and every mutation written to the audit log. Point an MCP client at the endpoint below and your agent can read and write your CRM, within the scopes you grant it.
- url
- https://api.limena.io/mcp/sseHTTP + Server-Sent Events. A local checkout can also run it over STDIO.
- auth
- Authorization: Bearer lmn_…
- requires
- an Ultra workspace + a key with mcp:accessKeys are created on Pro and up; the connection itself is an Ultra capability. The catalogue below is the same on every plan.
Connect
Limena speaks MCP over two transports. Most clients use the hosted SSE endpoint; a local checkout of the server can run over STDIO. This is the same configuration the app shows under Settings → Connect an AI client.
Connecting needs an Ultra workspace and an API key with the mcp:access scope.
Create the key under Settings → API keys (available on Pro and up). Browsing this reference needs no account.
{
"mcpServers": {
"limena": {
"url": "https://api.limena.io/mcp/sse",
"headers": {
"Authorization": "Bearer <YOUR_API_KEY>"
}
}
}
}Self-hosting / local development (STDIO transport)
STDIO runs the MCP server as a subprocess on your machine; you only need it with a local checkout of the Limena repo. Replace <REPO> with the checkout path and <YOUR_API_KEY> with your lmn_ key.
{
"mcpServers": {
"limena": {
"command": "<REPO>/.venv/bin/python",
"args": ["-m", "app.ai.mcp.server"],
"cwd": "<REPO>",
"env": {
"LIMENA_API_KEY": "<YOUR_API_KEY>"
}
}
}
}Authentication & keys
- format
- lmn_ + base58, shown once at creationThe secret is hashed at rest and never displayed again.
- header
- Authorization: Bearer lmn_…
- create
- Settings → API keysAvailable on Pro and up. Admin-scoped keys are restricted to the owner.
- revoke
- any key, any time; the next call with it is refused
Two-tier scopes
mcp:accessis the connection scope. A key without it can open no MCP session at all.- Granular
<family>:<read|write>scopes are checked per call.create_taskneedstasks:write;search_contactsneedscontacts:read. Holdingmcp:accessalone grants nothing beyond the handshake. - Grant the minimum set. A key carries exactly the scopes you select when you create it, so an agent can do only what its key allows.
Scopes
The scopes the published catalogue uses, grouped by resource family. Each is a read or write grant on one family, plus the mcp:access connection scope.
- mcp:access
- open an MCP connectionRequired to connect. Grants no data access on its own.
- Contacts
- contacts:read · contacts:write
- Companies
- companies:read · companies:write
- Deals
- deals:read · deals:write
- Tasks
- tasks:read · tasks:write
- Notes
- notes:read · notes:write
- Meetings
- meetings:read · meetings:write
- Calls
- calls:read
- Outreach
- outreach:read · outreach:write
- Campaigns
- campaigns:read · campaigns:write
- Attachments
- attachments:read
- Search
- search:read
- Analytics
- analytics:read
- Users
- users:read
Capability reference
Every capability an MCP client can call, grouped by resource family. Mutating calls are flagged and need a write scope.
58 capabilities · 25 mutating
Contacts
contacts:read · contacts:writebulk_create_contactsmutatesCreate up to 100 contacts atomically. Each entry may carry optional `tags` and `custom_fields` for per-contact enrichment (e.g. prospect_intel shapes). Any failure mid-batch rolls back the whole transaction — zero contacts are written. Returns {contacts: [<detail>, ...], created_count: N}.
create_contactmutatesCreate a new contact in the tenant's CRM.
find_or_create_contactmutatesEmail-keyed find-or-create. Looks up the tenant's contacts by lowercased email; returns the existing record if matched, otherwise creates with the supplied fields. `first_name` and `last_name` are required only on the create path. `tags` and `custom_fields` apply only on the create path; the find branch returns the existing record unchanged. Returns {contact, created: bool}.
find_similar_contactsFind contacts semantically similar to a seed contact (by contact_id) or to a free-text description (by query) — exactly one must be given. Returns contacts ranked nearest-first as {contact, distance} (cosine distance, 0 = identical). An unindexed seed returns an empty list.
get_contactGet full details for a specific contact by id.
search_contactsSearch CRM contacts by name, email, or company.
update_contactmutatesPartially update a contact's editable fields.
Companies
companies:read · companies:writecreate_companymutatesCreate a new company in the tenant's CRM. Mirrors the REST POST /companies endpoint.
find_or_create_companymutatesDomain-keyed find-or-create. Looks up the tenant's companies by normalised domain; returns the existing record if matched, otherwise creates with the supplied fields. `name` is required only on the create path. Returns {company, created: bool}.
find_similar_companiesFind companies semantically similar to a seed company (by company_id) or to a free-text description (by query) — exactly one must be given. Returns companies ranked nearest-first as {company, distance} (cosine distance, 0 = identical). An unindexed seed returns an empty list.
get_companyGet full details for a company by id, including contacts.
list_company_contactsList contacts that belong to a specific company.
search_companiesSearch companies by name or domain. Returns paginated list with contact counts.
update_companymutatesUpdate a company's fields. Pass only the fields to change. Domain input is canonicalised (scheme/path stripped).
Deals
deals:read · deals:writecreate_dealmutatesCreate a new deal in the pipeline. Requires a contact; company is optional. Value is in cents. Stage defaults to the tenant's first open pipeline stage if omitted.
get_dealGet full details for a specific deal by id.
get_pipeline_summaryCurrent deal pipeline grouped by stage with totals.
search_dealsSearch and filter deals. Supports filtering by stage, owner, contact, or company. Optional text query matches deal title.
update_dealmutatesUpdate a deal's fields. Pass only the fields to change. Stage changes are audited with direction (forward/backward).
Tasks
tasks:read · tasks:writecomplete_taskmutatesMark a task as completed. Idempotent if already completed.
create_taskmutatesCreate a follow-up task. Optionally attach to a contact, deal, or company. Assignee defaults to the API key creator.
get_my_tasksGet open tasks assigned to the API key's creator, sorted by due date (overdue first, then upcoming, then no-date).
list_tasksList tasks, optionally filtered by entity or assignee. Returns open tasks by default; set include_completed=true for all.
Notes
notes:read · notes:writecreate_notemutatesAdd a note to a contact, deal, or company, or a comment to a task. To @-mention a teammate so they get notified, write @[Their Name](user:<user_id>) in the body — user ids come from list_users.
list_entity_notesList notes attached to a contact, deal, company, or task (a task's notes are its comments). Returns newest first.
Meetings
meetings:read · meetings:writelist_meetingsList meetings logged against a contact, company, or deal. Returns newest first.
log_meetingmutatesLog a meeting against a contact, company, or deal — date, duration, attendees, location, and notes. Records a past or scheduled meeting; surfaces on the entity's timeline.
Calls
calls:readlist_call_recordingsList captured calls (uploaded recordings with an AI transcript and summary), newest first — logged against a contact, company, or deal, or the team's internal calls (standups, deal reviews) via entity_type 'internal' with no entity_id. Returns each call's title, date, transcript/summary status, AI summary, sentiment, action items, and suggested next step. Use to recall what was discussed on recent calls with a record or in team meetings.
Outreach
outreach:read · outreach:writedraft_emailGenerate a {subject, body} email draft from a brief, using the tenant's configured LLM. Read-only — no DB write. The caller passes the returned draft to send_one_off_email (or discards it). Optional reply_to_outreach_id enriches the prompt with the inbound thread for reply drafting.
get_campaign_summaryAggregate outreach stats for a date range or campaign by name. Returns total_sent, total_replied, reply_rate, open_rate, bounce_rate, by_channel.
get_outreach_historyGet the most recent outreach records for a contact.
get_recent_outreachMost recent outreach records, optionally filtered to one contact. Useful for context before drafting a follow-up.
log_outreachmutatesRecord an outreach event against a contact.
send_one_off_emailmutatesStage a single-recipient email send through the human-approval queue (M33d). Creates a pending SendApprovalRequest backed by an implicit one-off campaign; a human must approve in the Limena UI before the email leaves. Idempotent within 5 minutes per tenant+contact. When reply_to_outreach_id is supplied (M33f), the send is threaded as a reply to that inbound message — In-Reply-To and References headers are attached to the outgoing email and, for Gmail, the message lands in the same conversation.
upload_outreach_feedbackmutatesBulk-record outreach status updates from an external mailmerge tool. Unmatched emails create new contacts; the response lists them.
Campaigns
campaigns:read · campaigns:writeadd_contacts_to_campaignmutatesAdd individual contacts to a campaign as manual-source recipients. Up to 500 contacts per call. Idempotent — contacts already in the campaign via this source are silently skipped. Returns suppression breakdown (unsubscribed, email_invalid).
add_list_to_campaignmutatesAdd all contacts from a prospect list to a campaign. Auto-promotes eligible list items to contacts. Returns suppression breakdown and promotion count.
add_segment_to_campaignmutatesAdd contacts matching a saved segment to a campaign. Snapshot at add-time — re-add the segment to pick up new matches. Returns suppression breakdown.
cancel_campaign_sendmutatesCancel a campaign send that is sending or paused. Already-sent messages are not retracted. This action cannot be undone.
check_send_approvalCheck the status of a send approval request. Returns pending, approved, or rejected with decision details.
create_campaignmutatesCreate a new campaign. Idempotent — returns the existing campaign if one with the same name already exists.
get_campaignGet campaign detail including member count, outreach count, send status, and last sent date.
list_campaign_membersList contacts that are members of a campaign.
pause_campaign_sendmutatesPause a campaign that is currently sending. Already-queued messages will self-skip. Resume must be done by a human via the UI.
remove_contact_from_campaignmutatesRemove a contact from a campaign across all sources. Idempotent — returns removed=false if the contact was not a member.
request_campaign_sendmutatesRequest approval to send a campaign. Creates a pending approval that a human must approve or reject in the Limena UI before any emails are sent. Returns the approval ID for polling via check_send_approval. Idempotent — a second call while one is pending returns the existing approval.
update_campaignmutatesUpdate campaign metadata. Only send the fields you want to change. Supports: name, description, compose_mode (template or ai_generated), tracking_enabled, system_prompt_override.
Attachments
attachments:readlist_entity_attachmentsList file attachments on a contact, deal, company, task, or note. Returns newest first. Read-only — uploads + deletes go through the REST API.
Search
search:readsearch_crmHybrid keyword + semantic search across the CRM. Exact name / email / title matches rank first; contacts and companies also surface semantically related records a keyword miss would skip. Returns up to per_type results in each bucket: contacts, companies, campaigns, deals, outreach.
Analytics
analytics:readcampaign_comparisonCompare reply rates or open rates across named campaigns.
count_recordsTotal record count for a tenant-scoped entity.
list_campaignsList all campaigns for the tenant with member and outreach counts. Use to discover campaign names before filtering by them.
outreach_by_channelBreakdown of outreach volume and reply rates by channel.
outreach_summaryAggregate outreach stats for a date range / campaign.
query_analytics10/hourNatural language analytics question. Delegates to the AnalyticsAgent; consumes LLM tokens; rate-limited 10/hour.
recent_activityMost recent outreach and deal stage changes.
top_contactsList contacts ranked by engagement metric.
Users
users:readlist_usersList tenant team members. Useful for discovering user IDs when assigning tasks or deal ownership.
Governance
Governance is a property of the surface, not the agent’s goodwill. Every MCP call passes the same checks, in code.
- authentication
- scoped Bearer API keysmcp:access to connect; a granular scope checked on every call.
- rate_limits
- 120 requests / minute per key
query_analyticsis limited to 10 / hour, since it runs an LLM. - audit
- every mutation is written to the audit logRecorded with the acting key. Mutations write two rows (the service event and an mcp_client provenance row); reads are not logged.
- isolation
- PostgreSQL row-level security, per tenantThe app role has no BYPASSRLS; a query without a tenant context returns zero rows.
MCP calls execute immediately. There is no confirm step on this surface.
A mutating call runs the moment your agent invokes it, governed by the key’s scopes, the rate zone, and the audit log. The proposal-and-confirm gate, where a person approves every write before it runs, belongs to Limena’s in-app assistant, a separate surface. Over MCP, a key acts on its own authority, so grant scopes deliberately.
REST API
Beyond MCP, the core resources are a REST API documented with OpenAPI. Same lmn_ Bearer keys and the same granular scopes.
- Swagger UI is the interactive reference. ReDoc and the raw OpenAPI document are published too.
- The MCP endpoints are a streaming mount and are not part of the OpenAPI document; use the capability reference above for those.
Build something on your CRM.
Want it wired into your stack? We’ll walk the surface with you on your own data.
{
"method": "tools/call",
"params": {
"name": "create_task",
"arguments": {
"subject": "Follow up with Helix Logistics",
"due_at": "2026-06-19",
"entity_type": "deal"
}
}
}