The CRM MCP connector for Claude & ChatGPT
Bring your CRM into the AI you already use. agentlyleads ships a Model Context Protocol connector, so Claude and ChatGPT can search your contacts, draft personalized outreach, log conversations from a pasted thread, and run a pipeline report — all scoped to your workspace, all with writes saved as drafts for your approval.
https://agentlyleads.com/api/mcpAuthorization happens in your browser on connect (OAuth 2.1). No API keys to copy around.
Connect Claude
- claude.ai: Settings → Connectors → Add custom connector → paste the URL → Connect.
- Claude Code:
claude mcp add --transport http agentlyleads https://agentlyleads.com/api/mcp.
Connect ChatGPT
- Settings → Apps & Connectors → Developer mode.
- Add a connector with the URL above and approve the OAuth sign-in.
57 tools, one connector
Everything the assistant can do, grouped by area. Read tools answer questions; write tools save drafts and records for a human to approve.
Search & read · 10
- Fetch record by typed id
fetchRetrieve the full detail of a single record given a typed id in the form `type:rawId` (e.g. `contact:abc123`, `lead:...`, `company:...`, `deal:...`), exactly as returned by the `search` tool. Returns the complete record including its activity timeline as markdown. An unknown or malformed id returns a readable error.
- Find a contact by exact email
find_contact_by_emailLook up a contact by exact email address (case-insensitive equality — NOT fuzzy search). Use this before log_communication or send_email when you have an email address and must be certain you attach activity to the right person. Returns the single matching contact's id and summary, or a clear 'no match'. If duplicates share the address, all are listed so you can pick.
- Find likely duplicate contacts
find_duplicate_contactsList likely duplicate contact PAIRS in the workspace — same normalized name or same phone number (emails can't collide; duplicates always differ there). Heuristic suspects only: review each pair with the human before merging. Use `merge_contacts` to merge a confirmed pair.
- Get one record's full detail
get_recordFetch the complete detail (including activity timeline and related records) for one record identified by its type and raw id. Equivalent to `fetch` but with type and id supplied separately rather than as a combined `type:rawId` string.
- List calendar
list_calendarThe CRM calendar for a date range, grouped by day: tasks and appointments, deal close dates, scheduled campaign sends, follow-ups due, contact birthdays, and the user's own connected Google/Outlook events as read-only busy time. Defaults to today through seven days out. Times are shown on the WORKSPACE's clock. Entries that are tasks carry a `task:<id>` handle — use it with `manage_task` to reschedule or complete one. Use this before `schedule_appointment` to find a free slot. Read-only.
- List tasks
list_tasksList workspace tasks as a markdown checklist, with due dates and any linked contact or deal. By default returns all tasks (open first, then by soonest due date). Pass `done: false` for only open tasks or `done: true` for only completed ones. Results are capped (default 25, max 100).
- Schedule appointment
schedule_appointmentBook something on the CRM calendar: a one-off meeting, an all-day entry, or a repeating series. Times are WALL CLOCK in the workspace's own timezone — write '14:00' for 2pm there, never a UTC instant or an offset. Give `repeat` to make it recur (daily/weekly/monthly, with intervals, and open-ended if you name neither `until` nor `count`); omit it for a one-off. Link it to a contact, lead or deal so it shows on their timeline. Calling this twice books it twice — check `list_calendar` first if you are unsure whether it is already there. Use `manage_task` instead for an all-day to-do with no clock time.
- Search everything
searchFree-text search across all CRM record types at once — contacts, leads, companies, and deals. Returns a flat markdown list; each result carries a typed id of the form `type:rawId` (e.g. `contact:abc123`), a title, and a one-line subtitle. Pass an id from these results to the `fetch` tool to retrieve the full record. Use this when you do not yet know which record type the user means.
- Search records of one type
search_recordsList records of a single chosen type (contact, lead, company, or deal) as a markdown table, optionally narrowed by free-text query and/or status. Status values are type-specific: contacts use NEW/ACTIVE/CUSTOMER/DORMANT; leads use NEW/WORKING/QUALIFIED/UNQUALIFIED/CONVERTED; deals use NEW/QUALIFIED/PROPOSAL/NEGOTIATION/WON/LOST; companies have no status. Contacts and deals can also be filtered by `owner` (a workspace user's email, or "unassigned"). Results are capped (default 25, max 100).
- Who am I
whoamiReturn the identity of the currently authenticated CRM user: their name, email, role, and the workspace they belong to. Takes no arguments. Use this to confirm the connection is working and to learn which workspace all other tools operate within.
Contacts & leads · 13
- Add a note
add_noteAdd a free-text note to the workspace, optionally attached to a contact. `body` is required. If `contactId` is given it is verified to belong to your workspace. Returns the new note's id.
- Convert a lead to a contact
convert_leadConvert a QUALIFIED lead into a contact. The lead must already be in status QUALIFIED or this fails. On success the lead is marked CONVERTED, a new active contact is created, and the lead's communications move onto the contact. Returns the new contact's typed id.
- Create a record
create_recordCreate a new contact, lead, company, or deal. The `name` field is always required. Other fields are type-specific and optional: contacts/leads accept title/company/email/phone/status (+ leads accept source/estimatedInterest/notes/interestedOfferingId); companies accept domain/industry/size/website/notes; deals accept value/stage/contactId/companyId/offeringId/expectedCloseDate/notes. Contacts and deals also accept `ownerEmail` to assign an owner. Relation ids (contactId, companyId, offeringId) are verified to belong to your workspace. Returns the new record's typed id and a summary.
- Delete a note
delete_notePermanently delete a single note. Prefer update_note to revise; delete only a note that is wrong or no longer applies. Confirm with the human before deleting a note you didn't create in this conversation.
- Delete a record
delete_recordPermanently delete a contact, lead, company, or deal — including its notes and activity timeline. This cannot be undone. Prefer a status update (contact DORMANT, lead UNQUALIFIED, deal LOST) when the history is worth keeping; delete only records that are wrong, duplicated, or dead ends (e.g. a hard-bounced address). Confirm with the human before deleting a record you didn't create in this conversation. The delete is scoped to your workspace; an id from another workspace returns 'not found'.
- Generate an AI brief for a contact
generate_briefGenerate an AI relationship brief for a contact, summarizing their history and recommending a next action plus a draft outreach message. Provide the contact id. This calls the configured AI provider and persists the brief; if no AI API key is configured in this environment, an error message is returned instead.
- Log a communication
log_communicationRecord a communication (call, email, note, or meeting) against a contact. `contactId` and `body` are required. `direction` (INBOUND/OUTBOUND) is optional and typically used for calls/emails. `occurredAt` defaults to now if omitted; supply an ISO datetime to backdate. The contact is verified to belong to your workspace.
- Log a pasted conversation (LinkedIn / meeting / call)
log_conversationLog a conversation that happened off-platform — a LinkedIn thread, a Zoom/other meeting, or a phone call — onto a person's CRM timeline. The human pastes the raw text (or gives their notes); YOU clean it up. Steps: (1) pick `channel`: "linkedin", "meeting", or "call". (2) Identify the other person — `personName`, and for LinkedIn their profile URL (`linkedinUrl`), `title`, `company`, `email` if visible. (3) Rewrite the messy copy into a clean chronological `transcript` — "Name: message" lines, dropping platform chrome (LinkedIn's "· 3rd", reactions, "sent", duplicated name lines; meeting speaker-timestamp noise). (4) Write a 1-2 sentence `summary` of where it stands / the next step. (5) If the human already told you which contact or lead this is, pass `contactId` or `leadId`; otherwise omit them and a lead is created if the person isn't found. Never invent content that wasn't in the paste. Re-logging the same transcript updates the existing entry (deduped), never duplicates.
- Create or modify a task
manage_taskCreate a task or change an existing one. `action` selects the operation: 'create' (requires `title`), 'complete' / 'reopen' (require `id`; toggle the done flag), 'update' (requires `id`; change title/dueDate/contactId/dealId/done), or 'end_series' (requires `id` of ANY occurrence of a repeating task; stops the repeat — upcoming occurrences are removed, completed and past ones are kept). `dueDate` is an ISO 'YYYY-MM-DD' string. To create a REPEATING task here, pass `repeatWeekdays` (day names, e.g. ['mon','wed','fri']) together with `repeatUntil` ('YYYY-MM-DD', the last day the repeat runs); one task is created per matching weekday from today until that date, and `dueDate` is ignored. A repeat in this CRM is not limited to that shape — in the app a repeat can be daily, weekly or monthly, can skip weeks or months (every 2 weeks, every 3 months), and can run open-ended with no last day — so an existing repeating task may be any of those, and 'update' handles it whatever shape it has. Pass `repeat` to create any of those shapes here: daily, weekly or monthly, with an `interval` for every-N, weekdays by name, a monthly day-of-month or nth-weekday position, ending on `until`, after `count` occurrences, or open-ended if you name neither. `repeat` and the older `repeatWeekdays`/`repeatUntil` pair are two ways to say the same thing, so pass one or the other, never both. Everything created here is an ALL-DAY to-do — if it has a clock time, a duration or is a meeting, use `schedule_appointment` instead, which takes the same `repeat`. Every occurrence of a repeat is its own task with its own id, so an 'update' must say WHICH ones it means: `scope` is 'this' (the default — just the one occurrence you named), 'following' (that occurrence and every later one; earlier ones keep the old values) or 'all' (the whole repeat, past occurrences included). `scope` only makes sense on a repeating task — 'following'/'all' on a one-off task is an error — and 'following'/'all' change the repeat itself, so `dueDate` and `done` (which belong to a single occurrence) cannot be combined with them. Any `contactId`/`dealId` is verified to belong to your workspace. Returns the resulting task summary.
- Merge two duplicate contacts
merge_contactsMerge a duplicate contact into a surviving one. ALL history moves to the survivor (communications, emails, deals, tasks, notes, briefs, attachments, tags); blanks on the survivor are backfilled from the duplicate; the duplicate is then DELETED. Irreversible — you MUST confirm the pair and the merge direction with the human first, then pass `confirm: true`. Pick the survivor as the contact with the better email/most history.
- Today's brief
today_briefWhat needs attention today, exactly as the CRM's own home screen computes it: a headline count, the pipeline snapshot, and the top-ranked items. Takes no arguments. Each item carries its TYPE and its ID, so act on one directly rather than searching for it by name: a `deal` or `lead` id goes to `get_record` or `draft_outreach`, a `reply` id to `get_email` then `classify_reply`, a `followup` id to `get_email` then `reply_to_email`. The ranking is already computed — read it in the order given. Read-only: this proposes nothing and writes nothing.
- Update a note
update_noteReplace the text of an existing note (and optionally re-attach it to a different contact). Use this to keep a status/ownership note current instead of adding a correction note next to a stale one. The note id comes from add_note's response or a record's detail view.
- Update a record
update_recordPartially update an existing contact, lead, company, or deal. Supply the record `type` and `id`, plus only the fields you want to change — omitted fields are left unchanged. Field set mirrors `create_record`. Relation ids are workspace-verified. The update is scoped to your workspace; an id from another workspace returns 'not found'. Returns a confirmation summary.
Deals & catalog · 6
- Create an offering
create_offeringAdd a new offering (catalog item). `name` is required; `type` defaults to PRODUCT (others: SERVICE, SUBSCRIPTION, CONTRACT). `category` is a name and is created automatically if new. `price`/`cost` are USD numbers. For SUBSCRIPTION set `billingPeriod`; for CONTRACT set `termMonths`. Returns the new offering's typed id.
- Get one offering's detail
get_offeringFetch the full detail of a single offering (catalog item) by its raw id: type, price, cost, currency, sku, unit, stock, billing, category, description, and more, as markdown. An unknown id or one from another workspace returns 'Offering not found.'.
- List offering categories
list_offering_categoriesList the workspace's offering categories with the number of offerings in each, as a markdown table. Use this to see how the catalog is organized before filtering `list_offerings` by category.
- List offerings (catalog)
list_offeringsList the workspace's offerings — its product/service catalog — as a markdown table. An offering's `type` is one of PRODUCT, SERVICE, SUBSCRIPTION, or CONTRACT (a 'product' is an offering of type PRODUCT). Optionally filter by free-text `query` (name/sku/description/category), `type`, `category` name, and `activeOnly`. Capped (default 25, max 100). Use this to answer 'what products/offerings do I have?'.
- Sales pipeline report
pipeline_reportSummarize the deal pipeline for the workspace: open deal count and value, won count and value, win rate, and a per-stage breakdown. Takes no arguments. Returns markdown.
- Update an offering
update_offeringPartially update an offering. Supply `id` plus only the fields to change (same fields as create_offering). `category` (a name) is resolved/created. Omitted fields are left unchanged. A foreign or unknown id returns 'Offering not found.'.
Email & outreach · 15
- Classify a received reply
classify_replyRecord your classification of a received (INBOUND) email. INTERESTED replies feed the campaign positive-reply rate — the metric that says which pitch works. Re-classifying overwrites.
- Delete a drafted email (drafts only)
delete_email_draftPermanently delete a single DRAFT email (and its attachments). Only DRAFTs can be deleted — sent and failed emails are immutable history. Use this to clean up a bad draft without deleting the whole campaign. Confirm with the human before deleting a draft you didn't create in this conversation.
- Generate personalized drafts for a match
draft_outreachStage 2 of context-aware outreach. For every INCLUDED candidate of a match, the AI writes a customized email grounded in that contact's timeline and the offering, and saves it as a DRAFT grouped under an 'Outreach: <offering>' campaign. Nothing is sent — drafts land in the draft-first review queue where a human edits/approves them. Idempotent: candidates already drafted are skipped. Provide the raw match id.
- Read one email (draft or sent) by id
get_emailFetch a single email by its raw id — subject, From/To, status, direction, campaign, contact, any send error, attachments, and the full HTML body. Works for DRAFTs too, so you can verify exactly what a draft contains before a human approves it (read-back QA). Ids come from send_email/reply_to_email responses, get_campaign's email list, or the app's Emails list.
- List emails (drafts and sent)
list_emailsList the workspace's emails as a markdown table — id, To, subject, From, status, campaign, created/sent time. Filter by `contactId`, `campaignId`, and/or `status` (DRAFT / QUEUED / SENT / FAILED). Use it to find pending drafts awaiting approval, recover a draft id, or review what a contact has been sent. Read one email's full body with get_email. Results are capped (default 25, max 100).
- List threads that are due a follow-up
list_followups_dueFind conversations that went quiet: threads whose LAST message is an outbound send with no reply for `quietDays` (default 3), no follow-up draft queued yet, and fewer than 3 unanswered touches (after 3 we stop nagging). Each row carries the email id to reply to, the contact (with their saved context), days quiet, nudges already sent, and a snippet of the last message. To nudge a thread: call `reply_to_email` with that email id and a SHORT personalised follow-up — use the contact's context, reference the original offer, and NEVER invent new offers, quantities, or prices. Drafts are approval-gated: the human reviews them (Emails list) and then you send with `send_draft`.
- Get contacts + timeline context for an offering (for you to match & draft)
list_offering_prospectsRead-only batch context for model-driven outreach. Given an offering id, returns the offering details plus every emailable, non-suppressed contact with a compact digest of their CRM timeline (recent notes, emails, meetings) — in ONE call. Use this when YOU (the assistant) want to judge which contacts fit the offering and write customized outreach yourself, rather than the server's AI: read each prospect's digest, decide fit, then call `send_email` per good-fit contact (it saves a DRAFT for human approval). This needs no server-side AI key, so it works from claude.ai and the desktop app. `limit` caps how many prospects are returned (default 50).
- List the workspace's From addresses
list_sending_identitiesList the addresses this workspace can send email from — its verified sending domains and connected mailboxes (Gmail, Microsoft, or SMTP/Amazon WorkMail). Pass one of these as `from` on `send_email`, or as `fromEmail` on `create_campaign` / `create_sequence`, to send from that mailbox instead of the default. Takes no arguments.
- List replies awaiting classification
list_unclassified_repliesReceived (INBOUND) emails that have not been classified yet, newest first, with a snippet of each. Read each one and call `classify_reply` with your judgement — the classifications feed the positive-reply rate on campaign analytics and follow-up prioritization. Classify in the same pass as checking replies.
- Find contacts suitable for an offering
match_offering_to_contactsStage 1 of context-aware outreach. Given an offering (product/deal) id, the AI reads each emailable contact's CRM timeline (notes, prior emails, meetings) and scores how well the offering fits what they actually buy. Returns a ranked list of candidates with a fit score, tier (STRONG/POSSIBLE/WEAK), a one-line reason, and a suggested personalization angle. Contacts scoring above the threshold are marked INCLUDED by default. Nothing is sent or drafted here — use `set_candidate_decision` to adjust who's included, then `draft_outreach` to generate the emails. Provide the raw offering id.
- Draft a reply to an existing email (requires human approval to send)
reply_to_emailReply to a specific email in the workspace. The human copies an email's id from the Emails list (the 'Ref' chip) and gives it to you. Writes a DRAFT reply to that email's contact, in the SAME conversation thread, with a 'Re:' subject — nothing is sent until a human approves it (draft-first). Use this to continue an existing thread; use send_email to start a new conversation. Pass the id of the email being replied to and the reply body.
- Send a drafted email (transmits real email)
send_draftSend a specific email DRAFT (created earlier by send_email) — this transmits a REAL email to the recipient. Draft-first is the default: only call this after the human has reviewed and explicitly approved sending this exact draft (e.g. they said "send it"). Pass the single draft id from send_email's response — never send in bulk, and never send a draft the human hasn't approved. If you're unsure, share the review link from send_email and ask first. Returns the send status (SENT / FAILED).
- Draft an email to a contact (requires human approval to send)
send_emailCompose an email to a contact. It is ALWAYS saved as a DRAFT — nothing is transmitted until a human reviews and approves it in the app (draft-first policy). The recipient address is the contact's own email on file — never a client-supplied address. Every email is grouped under a campaign for tracking: pass `campaign` with a short title you choose from the email's purpose (e.g. "Leaf Blower outreach") to group related emails together; omit it and the email lands in the workspace's standing "Direct Outreach" campaign. To attach files (e.g. a PDF sell-sheet or one-pager), pass `attachments` — each either as base64 bytes (`contentBase64`) or as a public https `url` the server downloads for you (preferred for anything over ~100 KB). The email also appears on the contact's timeline once sent. The HTML body is stored verbatim — write email-client-safe HTML: inline styles only (no <style> blocks or CSS classes), and for tabular data use a simple <table> with border/cellpadding attributes or aligned text lines.
- Include or exclude an outreach candidate
set_candidate_decisionSet whether a matched contact is INCLUDED or EXCLUDED before drafting. Provide the raw candidate id (from `match_offering_to_contacts`) and the decision. Only INCLUDED candidates get a draft in `draft_outreach`.
- Update a drafted email (subject / body / From / campaign)
update_email_draftEdit a DRAFT email in place — fix its subject, body, From address, or re-file it under a different campaign — instead of deleting and recreating it. Only DRAFTs are editable; sent emails are immutable history. Use get_email afterward to verify what was stored. The From must be one of list_sending_identities.
Campaigns & sequences · 11
- Attach a file to a campaign
add_campaign_attachmentAttach a file (up to 10 MB) to a DRAFT or SCHEDULED campaign — either as base64 bytes (`contentBase64`) or as a public https `url` the server downloads (preferred for anything over ~100 KB; base64 wastes the context window). The file is sent with every recipient email when the campaign sends. Provide the raw campaign id.
- Create a draft campaign
create_campaignCreate a DRAFT email campaign. `segmentType` is 'tag' (send to all contacts with a given tag) or 'static' (send to a fixed list of contact ids). For 'tag', supply `tagName` (case-insensitive exact match against workspace tags). For 'static', supply `contactIds`. Use `update_campaign` to revise a draft's copy or audience, and `launch_campaign` to actually send.
- Create an email/task sequence
create_sequenceCreate a new outreach sequence with one or more ordered steps. Each step is EMAIL (requires `subject` + `bodyHtml`) or TASK (requires `taskTitle`). `delayDays` is the number of days after the previous step (or enrollment for step 0) before this step runs. Returns the new sequence id and step count. Use `enroll_in_sequence` to start contacts on it.
- Delete a draft campaign
delete_campaignPermanently delete a DRAFT campaign that was never sent (e.g. an abandoned or duplicate draft). Only DRAFT campaigns can be deleted — to stop a scheduled or sent campaign, that carries history, cancel it instead. This cannot be undone.
- Enroll contacts in a sequence
enroll_in_sequenceEnroll one or more contacts into a sequence. Enrolled contacts begin receiving the sequence's scheduled steps. Contacts without an email, suppressed contacts, or already-enrolled contacts are automatically skipped. Returns enrolled and skipped counts.
- Get one campaign's detail and analytics
get_campaignFetch the full detail and delivery analytics for a single campaign by its raw id. Returns campaign metadata (name, subject, status, segment) plus recipient/delivery stats. An unknown or foreign id returns 'Campaign not found.'.
- Get one sequence's detail and stats
get_sequenceFetch the full detail of a single sequence (including its ordered steps) plus enrollment stats. An unknown or foreign id returns 'Sequence not found.'.
- Launch a campaign (materializes drafts for approval)
launch_campaignLaunch a DRAFT campaign now or schedule it (`scheduledAt`, ISO 8601). Launching does NOT transmit email: it materializes one DRAFT email per recipient, which a human reviews and approves in the app (Emails page or campaign view) before anything is sent. Returns the queued recipient count and new status.
- List campaigns
list_campaignsList email campaigns in the workspace as a markdown table, showing id, name, status, and scheduled/sent time. Results are capped (default 25, max 100). Statuses: DRAFT, SCHEDULED, SENDING, SENT, CANCELED.
- List sequences
list_sequencesList email/task sequences in the workspace as a markdown table: id, name, status, step count, and total enrollments. Results are capped (default 25, max 100).
- Edit a draft campaign
update_campaignRevise a DRAFT or SCHEDULED campaign in place — its name, subject, body, or audience — instead of creating a new one. Only works before the campaign has started sending; once SENDING/SENT it is locked (create a new campaign instead). For the audience, pass `segmentType` with `tagName` (for 'tag') or `contactIds` (for 'static'). Only the fields you pass are changed. Returns the updated campaign.
Admin & sync · 2
- HubSpot sync status, and optionally trigger a sync
hubspot_syncReport this workspace's HubSpot integration status (connected, enabled, last synced, last error, last summary). With trigger=true, start an incremental two-way sync now (workspace OWNER only).
- Zoho sync status, and optionally trigger a sync
zoho_syncReport this workspace's Zoho CRM integration status (connected, last synced, last error, last summary). With trigger=true, start an incremental two-way sync now (workspace OWNER only).
Questions
- What is a CRM MCP connector?
- It is a Model Context Protocol server that exposes your CRM as tools an AI assistant can call. Connect it once in Claude or ChatGPT and the assistant can search records, draft emails, log conversations, and report on your pipeline on your behalf.
- Does it work with both Claude and ChatGPT?
- Yes. The same connector is a Claude custom connector and a ChatGPT connector. It uses OAuth 2.1, so you sign in with your existing CRM account and approve access in your browser.
- Can the AI send emails without my approval?
- No. Every write is confirmation-gated and outbound emails are always saved as drafts. A human reviews and approves before anything is sent.
Already have an account? Connect it from Settings → AI connector.