How connectors work
A connector turns a service you already use into first-party tools your AI agents can call. You authorize the service once — OAuth for most, an API key for some — and BusinessMCP handles the rest: it stores the credential securely server-side, keeps OAuth tokens refreshed, and exposes the service's capabilities as named, typed tools. The credential never touches the browser or the model; the agent only ever sees clean tools.
Crucially, this is the same plumbing behind both ways you consume BusinessMCP. A tool you connect is available to agents in the cloud app *and* to any external agent hitting your MCP endpoint. Connect once, use everywhere.
Required, recommended, supplementary
Connections are organized by how much they matter, so you are never staring at a wall of logos:
- Required — the tracking script. This is the one thing you must do; it is the analytics spine everything else builds on.
- Recommended — the high-leverage set: Stripe, GitHub, Google Search Console, Resend, and the social and ads OAuth providers. Your onboarding goals map to the right ones automatically.
- Supplementary — everything else, tucked into a collapsible section for when you need it.
The catalog
Open Connections to browse the catalog by category. Highlights:
- Code & ops — GitHub (read + PR writes), Vercel, Sentry, Linear, Notion, Airtable.
- Revenue — Stripe (via its hosted MCP server), PayPal.
- E-commerce & support — Shopify, Zendesk, Intercom, Crisp.
- CRM & sales — HubSpot, Pipedrive, Calendly.
- Marketing & analytics — Search Console, GA4 (historical import), Mailchimp, Klaviyo, Plausible.
- Messaging — Slack (read + post), Resend (email).
- Calendars — Google Calendar, Microsoft 365, Apple iCloud — these power booking links.
- Your own database — read-only PostgreSQL, MySQL, BigQuery or Snowflake.
- Social & ads — X, LinkedIn, TikTok, YouTube, Instagram, Facebook, Meta Ads (Google Ads once our developer token is approved) — one-click white-label OAuth.
- Enrichment — built-in IP→company identification (in-house graph, no vendor needed) + firmographics from our own 34M-company database.
Four connections have their own step-by-step pages: Connect Stripe, Connect Google Search Console, Connect Google Ads and Meta Ads, and — for the goals every rate is built on — Set up conversion goals.
The catalog is the list in the product, not a roadmap: if a provider is not on a card in Connections, it is not connectable today. Anything with an HTTP MCP server of its own can still be added through a custom MCP server.
Support conversations: Intercom and Crisp
The Intercom and Crisp cards work the other way round from the rest of the catalog: they receive webhooks, they call no API and expose no agent tools. Every conversation lands on the customer's contact timeline and in the CRM Inbox — the customer's messages as inbound bubbles, your team's replies as outbound ones, a note when the conversation is closed — so the whole history of a person sits in one place. Replies stay in Intercom or Crisp: we never draft an answer for a support message and never move a contact's pipeline stage because they asked a question.
Setup is the same for both:
1. Open Connections → Support, expand the card and paste the signing secret — Intercom's app Client secret (Developer Hub → your app → Basic information), or the secret Crisp shows when you add a Web Hook (Settings → Websites → your site → Web Hooks). Connect.
2. Copy the webhook URL that now appears on the card (https://businessmcp.com/api/webhooks/intercom/… or …/crisp/…). It is unique to your workspace.
3. Register that URL in the tool. Intercom: subscribe to conversation.user.created, conversation.user.replied, conversation.admin.replied and conversation.admin.closed. Crisp: enable message:send, message:received and session:set_email.
Every delivery is verified against the secret (unsigned or wrongly signed requests are rejected) and replays are ignored. A conversation attaches to a contact only when the tool tells us the person's email — Intercom sends it with the opening message, Crisp sends it as a separate session:set_email event once the visitor types it in. Anonymous chats are counted on the card ("without an email") and not stored; a workspace teammate chatting with your own widget is skipped too. Optionally, Settings → Knowledge → Sources → "Support conversations" (or the switch on the Intercom/Crisp card) also files the transcripts into the company brain as untrusted, contact-PII knowledge so the analyst and Inbox drafts know what customers actually ask. It is off by default.
## {#tools} From connection to tools
The instant a connection is live, its tools become available to your agents — namespaced so they never collide (github_create_issue, gsc_search_analytics, slack_post_message). An agent discovers them exactly like the built-in platform tools, and your MCP endpoint exposes them to external clients the same way. There is no separate "publish" step; connecting *is* publishing.
This is what makes the platform compound. Each connector you add widens what every agent — and every model — can do, without any protocol code on your side.
Inbound webhooks: forms, scheduling, commerce, email, billing
Some tools are more useful sending data to BusinessMCP than answering agent queries. Nine connectors accept the vendor's own webhooks and land the result on the contact timeline, the analytics spine and the CRM lifecycle — no Zapier, no code.
| Connector | Card | Events | What lands |
| --- | --- | --- | --- |
| Typeform | Marketing → Typeform | form_response | Contact + answers, form_submit goal, Inbox message when the form has a free-text field |
| Tally | Marketing → Tally | FORM_RESPONSE | Same as Typeform |
| Calendly | Sales → Calendly | invitee.created, invitee.canceled | meeting_booked / meeting_cancelled, guests as contacts, Qualified stage |
| Cal.com | Sales → Cal.com | BOOKING_CREATED, BOOKING_RESCHEDULED, BOOKING_CANCELLED | Same as Calendly |
| Shopify | Sales → Shopify | orders/create, orders/paid, refunds/create | Server-verified revenue, Customer stage + LTV, refunds netted out |
| WooCommerce | Sales → WooCommerce | order.created, order.updated | Same as Shopify (refunds ride order.updated) |
| Mailchimp | Marketing → Mailchimp | open, click, unsubscribe, cleaned | Engagement on the contact timeline; bounces + complaints suppress the address |
| Klaviyo | Marketing → Klaviyo | Opened / Clicked / Bounced Email, Unsubscribed, Marked as Spam | Same as Mailchimp |
| Brevo | Marketing → Brevo | delivered, opened, click, hard/soft bounce, spam, unsubscribed | Same as Mailchimp |
Setup is the same for every one of them. Open the card in Connections and click Connect once — that mints your private webhook URL, shown on the card with a copy button. Paste the URL into the vendor, set a signing secret on their side, paste the same secret into the card and save again. Every delivery is verified against that secret before anything is read (a missing secret answers 503, a bad signature 401), replays are deduplicated on the vendor's event id, and the vendor's IP is never treated as the visitor's.
Two email platforms are the exception, and it is worth knowing which. Mailchimp does not sign its webhooks at all: its "secret" is a value you append to the URL you register (?s=YOUR_SECRET), which we compare in constant time. Brevo offers neither a signature nor a secret, so for Brevo the webhook URL *is* the credential — treat it like a password, and if it leaks, disconnect and reconnect the card to mint a new one. In both cases the tenant token in the path is a separate 32-hex value, so a leaked Mailchimp ?s= on its own opens nothing.
| Paddle | Sales → Paddle | transaction.completed, subscription.*, adjustment.updated | Revenue, subscription status + MRR on the contact, Customer stage, refunds netted out |
| Lemon Squeezy | Sales → Lemon Squeezy | order_created, order_refunded, subscription_*, subscription_payment_success/failed | Same as Paddle (MRR is not sent by this vendor) |
| Chargebee | Sales → Chargebee | payment_succeeded, payment_failed, subscription_*, refund_initiated | Same as Paddle |
Paste the URL into the vendor, set a signing secret on their side, paste the same secret into the card and save again. Every delivery is verified against that secret before anything is read (a missing secret answers 503, a bad signature 401), replays are deduplicated on the vendor's event id, and the vendor's IP is never treated as the visitor's.
Where to paste the URL
- Typeform: open the form → Connect → Webhooks → *Add a webhook*. Turn on *Secret* and choose one.
- Tally: open the form → Integrations → Webhooks → *Connect*. Set the *Signing secret*.
- Calendly: create a webhook subscription (API or an admin) for
invitee.createdandinvitee.canceledwith a *signing key*; the same key goes in the card. - Cal.com: Settings → Developer → Webhooks → *New*: pick the three booking triggers and set a *Secret*.
- Shopify: Settings → Notifications → Webhooks: add
orders/create,orders/paidandrefunds/createin JSON format. The signing secret is printed at the bottom of that page. - WooCommerce: WooCommerce → Settings → Advanced → Webhooks: add *Order created* and *Order updated* (API v3) with a *Secret*.
- Mailchimp: Audience → Settings → Webhooks. Paste the URL with `?s=YOUR_SECRET` appended, tick unsubscribes and cleaned addresses, and paste the same secret into the card. Mailchimp GETs the URL to validate it before saving — that is expected and answers 200.
- Klaviyo: add a *Webhook* action to the flows you want tracked, paste the URL, set a signing secret, and paste the same secret into the card.
- Brevo: Settings → Webhooks → *Add a new webhook*: paste the URL and tick delivered, opened, click, hard bounce, soft bounce, spam and unsubscribed. Nothing to paste back.
- Paddle: Developer tools → Notifications → *New destination*. The *secret key* (
pdl_ntfset_…) shown after saving goes in the card. - Lemon Squeezy: Settings → Webhooks → *+*. Set a *signing secret* and tick the order and subscription events.
- Chargebee: Settings → Configure Chargebee → Webhooks → *Add webhook*. Chargebee signs nothing — switch on *Basic Authentication*, choose a username and password, and paste them into the card as
username:password.
Stitching to the visitor journey. Forms and orders can carry the visitor id the tracker minted, so the submission or purchase attaches to the session that produced it (and inherits its channel and UTM for attribution):
- Typeform / Tally: add a hidden field named
mcph_vidand fill it from the page that embeds the form withwindow.mcph.getVisitorId()(Typeform:?mcph_vid=…on the embed URL; Tally: the hidden-field parameter of the same name). A hiddenpage_urlfield is picked up too. - Shopify: append
?mcph_vid=<id>to the storefront link you send visitors to — Shopify records it on the order aslanding_site— or set a cart attribute namedmcph_vid(it arrives as a note attribute). - WooCommerce: store the id as order meta
mcph_vid(a one-line snippet onwoocommerce_checkout_create_orderreading themcph_vidcookie or query parameter).
Without the id, everything still works — the contact is identified by email and the revenue is recorded as Direct.
Email engagement never creates a contact. An open or a click from your newsletter is recorded only for someone already in your CRM; an address we do not hold is skipped. A marketing list is not a lead-capture surface, and minting a contact per address on a 50,000-person send would fill your pipeline with people who never visited — and then feed them to outreach. Sends and deliveries are stored as the denominator your rates need but stay off the contact timeline, so one campaign cannot bury a real conversation.
Bounces and opt-outs carry across. A hard bounce or a spam complaint reported by your email platform suppresses that address for BusinessMCP outreach too, and an unsubscribe from your marketing list marks the contact unsubscribed here. It is the same suppression list our own sending already honours — you do not have to keep two.
What does not happen. A form submitted by one of your own teammates is ignored; disposable or placeholder addresses are dropped with a reason; an unpaid Shopify or WooCommerce order identifies the buyer but records no revenue until it is paid; a re-delivered payload never writes twice.
Payments beyond Stripe
Stripe is polled, not webhooked — a per-tenant signing secret is unmanageable at scale, so the revenue cron walks your Stripe account every six hours. Merchants of record cannot be polled that way, and most SaaS outside the US sells through one: Paddle, Lemon Squeezy or Chargebee is the seller of record, handles VAT and pays you out. Connect one of those three and the same facts land, by webhook instead:
| What lands | Where you see it |
| --- | --- |
| Each payment as server-verified revenue in its own currency | Overview revenue, attribution, ROAS |
| Subscription status, plan, MRR, seats, trial end | The Subscription card on the contact, get_lead_scores, churn risk |
| Refunds and chargebacks as negative revenue | The revenue metric is net, never gross |
| Customer / Churned stage | The CRM pipeline, through the one lifecycle state machine |
A pending cancellation is not churn. All three vendors have a state that reads as "cancelled" while the customer still has paid-for access to the end of the term — Lemon Squeezy's cancelled with a future end date, Paddle's scheduled change, Chargebee's non_renewing. We record the state and leave the pipeline alone; the stage moves when the term actually ends. A failed payment is recorded and flagged on the contact, but never moves anyone to Churned on its own.
Two vendor quirks worth knowing before you connect:
- Paddle does not send the buyer's email address. Its subscription and transaction notifications carry an opaque
customer_idand nothing else, so a purchase cannot be matched to a person on its own. PasscustomData: { email }(andmcph_vidfor journey stitching) when you open Paddle Checkout. Once we have seen that address once, later email-less events for the same customer resolve to the same contact. - Chargebee has no request signature. It authenticates its webhook with HTTP Basic credentials on the URL, so the secret URL and those credentials together are the credential — treat both as secrets, and rotate the webhook if either leaks. Every delivery without matching credentials is refused.
Lemon Squeezy does not report MRR. Its subscription payload carries neither an amount nor a billing interval, so we leave the MRR figure empty rather than guessing one; the actual payments still land as revenue, which is the number that matters. Paddle and Chargebee both send an amount and a cadence, and annual, quarterly and weekly plans are normalised to a monthly figure.
## {#database} Your own database: read-only SQL
Connections → Operations → Your database connects your own PostgreSQL, MySQL, BigQuery or Snowflake. Once it is connected the analyst gets three tools — list_database_tables, describe_database_table and query_database — and so does your MCP endpoint and the CLI. That is what lets it answer questions about subscriptions, orders and product usage from your real data rather than inferring them from web analytics.
Create a read-only role for it. For Postgres that is roughly:
CREATE ROLE mcph_reader LOGIN PASSWORD '…';
GRANT CONNECT ON DATABASE yourdb TO mcph_reader;
GRANT USAGE ON SCHEMA public TO mcph_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcph_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcph_reader;Three layers keep queries read-only, and it is worth being clear about which one is load-bearing:
1. The role you create. This is the real control. Everything below is defence in depth.
2. A read-only session. Postgres runs inside BEGIN READ ONLY; MySQL sets a read-only transaction; BigQuery and Snowflake rely on the grants.
3. A statement classifier. One statement at a time, and it must start with SELECT, WITH, SHOW, EXPLAIN or DESCRIBE. It also refuses writes smuggled into a CTE, SELECT … INTO, and file-access functions.
What none of the three can catch: a function with side effects. select my_audit_fn(1) is a plain SELECT that does whatever the role may do — which is exactly why the role matters more than the classifier.
Other things worth knowing. Results are capped (100 rows by default, 500 maximum) using each engine's own mechanism — a cursor, a stream, maxResults — so a select * on a huge table never gets pulled across the wire. Queries run against your live server and use its resources. Connections are encrypted; the default encrypts without verifying the certificate chain, because managed providers usually present a chain that is not in the default trust store, and you can switch to full verification and paste your CA. You can list the schemas the analyst may read to keep it out of, say, an auth schema. Only an owner or admin can connect, change or remove the card.
We also read the shape of the database — table and column names and types — into your company brain every six hours, so the analyst knows your data model without guessing table names. That is schema only: no sample rows are ever stored.
One honest limitation about access policies: query results come back as positional rows with your column names, so the field-level scrubbing that hides revenue and PII elsewhere can only mask a column whose *name* matches (email yes, cust_mail no). The Connected database class in Settings → API & access is the real switch, and scoping the database role is better than scoping the policy.
Google Analytics 4: importing your history
Installing the tracking script starts you at zero, while your GA4 property already holds months or years of traffic. Connecting Marketing → Google Analytics 4 imports that history read-only, so the trend on your Overview does not begin at the day you signed up.
Click Connect and sign in with a Google account that can read the property. If that account can read exactly one GA4 property it is selected automatically; otherwise pick it from the card. Nothing is imported until a property is selected. The connection requests analytics.readonly and nothing else — we never write to your property, and it shares the same Google app as Search Console rather than asking you to register anything.
Each day we pull sessions, users, new users, conversions and revenue for the last few complete days, broken down by channel, source, medium and landing page. Ask us to run a backfill and we will pull up to 400 days in one pass; that is deliberately not automatic, because re-reading years of unchanged history every night costs everyone time and buys nothing.
The result appears as a "Before you had us" card on the Overview, covering the period that ends the day before our own tracking starts, and the AI business analyst can read it on request.
Read it as what it is. These are Google's numbers on Google's definitions, at day resolution. They are not comparable row-for-row with our first-party sessions: there are no journeys, no session replays and no identified people behind them, and GA4's "users" figure is counted per day, so a person who visited on three days appears three times. We show the per-day sum and label it, rather than inventing a total Google never computed. Treat the two as separate chapters of the same story, not one series.
Two-way HubSpot sync
HubSpot is the one connector that runs in both directions. Connecting it pulls your contacts, deals, owners and pipelines into the BusinessMCP CRM every two hours — a closed-won deal moves the person to Customer, and the deal's amount, close date and owner land on the contact card. That half is on as soon as you connect.
The other half is opt-in. Switch on Push to HubSpot on the HubSpot card in Connections, and what BusinessMCP works out about a person is written back into your own portal:
| What we write | Properties | | --- | --- | | Scores | businessmcp_lead_score, businessmcp_score_band, businessmcp_fit_score, businessmcp_intent_score, businessmcp_churn_risk, businessmcp_is_pql, businessmcp_ltv_estimate | | First-touch attribution | businessmcp_first_channel, businessmcp_first_utm_source, businessmcp_first_utm_campaign, businessmcp_hdyhau | | The company behind the visitor | businessmcp_company_domain, businessmcp_company_industry, businessmcp_company_employee_range — plus the same industry and size on the matching HubSpot company, matched by domain | | Activity | businessmcp_last_seen_at, businessmcp_last_page, businessmcp_lifecycle_stage, businessmcp_url (a deep link back to the contact) |
Every property lives in a `businessmcp` property group and every name starts with `businessmcp_`. That is the whole safety model. Your portal is yours: a bare lead_score would collide with whatever your team already built, so ours sits in its own namespace, created automatically the first time the push runs — and re-created if one is deleted.
Our lifecycle never overwrites yours. businessmcp_lifecycle_stage is a mirror; HubSpot's own lifecyclestage and dealstage are never written, and neither are name, email, phone, company or job title. Nothing is deleted either — a value we do not know yet is left out of the payload rather than sent as an empty string, which would clear the field on your side.
Contacts are matched by email, so a person we have no address for is never pushed: there would be nothing to match them to. Records go up in batches as they change — a nightly re-score that moved nothing costs no API call — and the two-hourly pass sweeps up anything the live path missed.
If you connected HubSpot before the write-back existed, your token carries read scopes only. The card says so; reconnect, or add crm.objects.contacts.write, crm.objects.companies.write and the two schema write scopes to your private app.
Custom & external MCP servers
Already run an MCP server, or want to connect one of the thousands that exist? BusinessMCP acts as a gateway: attach any external MCP server by URL and it becomes reachable through your one endpoint. See Connect a custom MCP server.
Tokens & scoping
Credentials live in an encrypted, server-only vault with deny-all access policies; only the platform's service layer resolves them at call time. OAuth tokens are refreshed automatically, so connections do not silently expire. On the exposure side, each connection carries an allowlist of enabled tools and the platform caps the total number of connector tools surfaced at once — both for safety and because a tighter, sharper tool set makes models more accurate. Enable what you need, scope your MCP keys per agent, and review your tool call log to see exactly what each connection has done.
Frequently asked questions
Do I have to register a developer app to connect a tool?
No. BusinessMCP runs the OAuth apps for supported networks, so you just click Connect and authorize. For API-key services you paste the key once; it is stored server-side in an encrypted vault, never in the browser.
Where do connected tools show up?
Everywhere your agents run — in the cloud app, and through your hosted MCP endpoint. Each connector adds namespaced tools (github_*, gsc_*, slack_*) that any connected agent can call.
Is the database connection really read-only?
Three things make it read-only, and only the last is ours: the role you create when you connect (the real control), the read-only session we open, and a classifier that refuses anything that is not a single SELECT/WITH/SHOW/EXPLAIN/DESCRIBE. Create a dedicated read-only role — a classifier cannot make an owner credential safe, and a SELECT can still call a function that writes.
Can I limit which tools a connection exposes?
Yes. Each connection carries an allowlist of enabled tools, and there is a cap on the total number of connector tools to keep model accuracy high. Enable only what you need.
Keep going
Turn your company into one AI-ready data platform on a single hosted MCP endpoint.