Announcing CIMD support for MCP Client registration
Learn more

Should you use Close MCP or Close API for building AI Agents?

Varun Krishnan
Senior Content Marketer

TL;DR

  • Close is one of the few CRMs where the MCP server has finer permission granularity than the API. Close MCP splits tools across mcp.read, mcp.write_safe, and mcp.write_destructive. The REST API's OAuth grant returns a single coarse scope, all.full_access.
  • Close MCP is not OAuth-only. It accepts a static API key through the Close-API-Key and Close-Scope headers, so headless execution is possible. That reintroduces the shared static secret problem, which is why per-user OAuth is still correct for multi-tenant agents.
  • The MCP server cannot send. closemcp_create_draft_email saves an unsent draft for a human to review; there is no send tool, and no SMS send tool. Outbound communication that an agent actually dispatches lives in the REST API.
  • Webhooks, bulk actions, exports, lead merge, and external call logging are REST-only. Natural-language search, aggregation, voice agent dispatch, and Notetaker transcripts are where MCP is stronger.
  • Close access tokens expire in 3600 seconds and refresh tokens rotate on every use, with the old one revoked immediately. Two agent threads refreshing concurrently will strand a user. Scalekit's Close connectors centralize that token lifecycle for both paths.

Two paths into the same Close organization

Your agent needs to work in Close. It needs to pull the pipeline before a call, log what happened after, and nudge stalled opportunities overnight. Close ships a hosted MCP server and a REST API that has been production-grade for years, and Scalekit exposes both as separate connectors. They are not interchangeable. The gap is not capability breadth; it is write semantics and event handling. Here is how to pick.

What Close MCP and the Close API actually are

Both paths sit on the same Close organization and the same OAuth authorization server. What differs is the surface each one presents to an agent and the credential each one will accept.

Close MCP

Close runs a hosted Model Context Protocol (MCP) server at mcp.close.com/mcp, maintained by Close. Transport is Streamable HTTP; Server-Sent Events is not supported. Authentication is OAuth 2.0 with Dynamic Client Registration (DCR), or a static API key passed through the Close-API-Key and Close-Scope request headers.

Tools are tiered by the Close-Scope value. At the time of writing, Close's tool reference lists 67 read-only tools under mcp.read, 16 more under mcp.write_safe, and 34 more under mcp.write_destructive. Each higher scope includes everything below it.

Official docs: Close MCP Server and Close MCP Tools.

The Close REST API

The Close REST API is a conventional JSON interface at api.close.com/api/v1. It covers leads, contacts, opportunities, activities, webhooks, the 30-day event log, bulk actions, exports, reporting, sequences, and org configuration.

Authentication accepts two credential types: an API key over HTTP Basic Auth, with the key as the username and an empty password, or an OAuth 2.0 bearer token. Rate limits are enforced per endpoint group, with a per-key limit and a wider organization limit.

Official docs: Close API Overview.

What your agent can actually do

The overlap on core CRM objects is nearly total. Both paths create leads, update opportunities, manage tasks, and read activity history. The divergence starts the moment your agent tries to send something, react to something, or operate on thousands of records at once.

Capability coverage at a glance

Capability
Close MCP (closemcp_*)
Close API (close_*)
Natural-language lead and contact search
Yes (closemcp_search)
No; structured filters via close_leads_list and Advanced Filtering
Cross-object aggregation and counts
Yes (closemcp_aggregation)
Partial; via the Reporting endpoints
Create and update leads, contacts, opportunities, tasks
Yes
Yes
Send an email from the agent
No; unsent drafts only (closemcp_create_draft_email)
Yes (close_email_create with status set to sent)
Send or log an SMS
No; templates only
Yes (close_sms_create)
Log an externally placed call
No
Yes (close_call_create)
Dispatch Close's voice agent to call a contact
Yes (closemcp_schedule_voice_agent_call)
Not in the REST reference
Webhook subscription management
No
Yes (close_webhook_create and related)
Bulk edit, bulk delete, bulk sequence enrollment
No
Yes, via Bulk Actions
Lead and opportunity export jobs
No
Yes, via Exports
Merge two leads
No
Yes (close_lead_merge)
Meeting Notetaker transcripts
Yes (closemcp_fetch_meeting_transcript)
Yes, via the _fields=transcripts parameter

Where the MCP tool surface stops

The single most consequential gap is send semantics. closemcp_create_draft_email explicitly saves an unsent draft for the user to review and send from Close. There is no tool that dispatches it. There is no SMS creation tool at all, only SMS template management.

For a human-in-the-loop assistant, that is a feature. A drafting agent cannot accidentally email a prospect. For an autonomous outbound sequencer, it is a hard blocker, and no configuration flag changes it.

The second gap is reactivity. The MCP server has no webhook tools, so an agent on the MCP path cannot subscribe to opportunity status changes; it can only poll. Bulk actions, export jobs, lead merge, and logging calls placed by an external dialer are likewise REST-only.

What MCP exposes that the REST API does not

The gap runs both directions, which is unusual in this series. closemcp_search takes a query like "leads with an active opportunity over $500" and resolves it server-side, while the REST equivalent requires you to construct an Advanced Filtering query object yourself.

closemcp_aggregation answers counting questions across leads, contacts, opportunities, and activities in one call, after a required closemcp_get_fields lookup. Voice agent tooling is MCP-only: your agent can list configured voice agents, dispatch one to call a contact, and pull performance reports. Billing reads such as closemcp_get_billing_summary and closemcp_get_ai_credit_usage have no REST counterpart either.

There is also closemcp_close_product_knowledge_search, which queries Close's own documentation. A support or onboarding agent can answer "how do I set up automated lead assignment" without you building a retrieval pipeline over Close's help center.

The auth path each one puts you on

This is where Close breaks the usual pattern. On most tools in this series, MCP means OAuth and the API means credential choice. Close inverts part of that, and the inversion has real consequences for how you scope an agent.

Close MCP: OAuth with DCR, or a static API key header

The recommended path is OAuth 2.0 with Dynamic Client Registration, which is what Claude, Cursor, ChatGPT, and other MCP clients use. For custom setups, Close documents an alternative: send your API key in Close-API-Key and a scope tier in Close-Scope.

That second option is why "MCP cannot run headless" is false for Close. A nightly job can hold an API key and a mcp.read header and never touch a browser. Whether it should is a separate question, addressed below.

Close API: API key or OAuth, with one coarse scope

The REST API accepts an API key over HTTP Basic Auth or an OAuth bearer token. The OAuth token response returns "scope": "all.full_access offline_access", and Close's documented authorization request carries no scope parameter at all.

Scalekit's Close connector docs confirm this: Close OAuth apps automatically receive all.full_access and offline_access, with no additional scope configuration. There is no read-only OAuth grant for the REST API.

The permission granularity inversion

Read those two sections together and the practical result is clear. On the MCP path you can hand an agent a read-only credential. On the REST path you cannot; every OAuth token is a full-access token, and every API key inherits its owner's permissions.

That does not make the REST API unusable for least privilege. It moves the enforcement point. If the credential cannot be narrowed, the tool surface has to be, which is exactly what a virtual MCP server does: it declares which connections and which specific tools an agent role can see, independent of what the underlying token permits.

What you own in production

Close manages the MCP server and the API. Everything downstream of the credential is yours, and three specific behaviors will bite an agent that was only tested with one user.

Refresh token rotation is the failure mode to design for

Close access tokens carry expires_in: 3600. Refresh requires the offline_access scope, and Close's documentation is explicit that the authorization server issues a new refresh token on every refresh and revokes the old one immediately.

For a single-threaded script this is unremarkable. For an agent runtime it is a race condition waiting to happen: two workers detect an expired token at the same moment, both call /oauth2/token/, one wins, and the loser writes a refresh token that Close has already revoked. That user is now disconnected and must re-authorize, with no error surfaced until the next tool call. This is the same class of problem covered in handling token refresh for AI agents.

Rate limits are an organization-level budget

Close enforces limits per endpoint group, not globally. There is a per-API-key limit and a wider organization limit, documented as three times the per-key limit, shared across all users' keys in that organization.

Your agent is therefore competing with the customer's Zapier automations, their data warehouse sync, and their other integrations for the same budget. Handle 429 by reading the RateLimit header and sleeping for the reset value, and treat rate-limit headroom as a property of the tenant rather than of your service.

One token, one Close organization

The OAuth token response includes an organization_id and a user_id. The credential is bound to the organization the user selected on the consent screen, not to the user's full account.

Close's own MCP documentation makes the consequence explicit: to work with more than one Close organization you add a separate connection per organization, each with its own name. For a B2B agent whose customers run multiple Close orgs, the unit of credential isolation is the user and organization pair, not the user.

When to use Close MCP, when to use the Close API

Most production Close agents end up using both, because the two surfaces answer different questions. The split below is about the job the agent does, not about how quickly you want to ship.

Use Close MCP when

  • The agent is a reading and reasoning assistant: a rep asks about pipeline in natural language and closemcp_search plus closemcp_aggregation answer it without you writing filter objects
  • You want a genuinely read-only agent, which the mcp.read scope tier gives you and the REST OAuth grant does not
  • The agent drafts rather than sends, and a human reviews every outbound message in Close before it leaves
  • The workflow involves Close's voice agents, Notetaker transcripts, or questions about Close itself

Use the Close API when

  • The agent sends: outbound email, SMS, or bulk sequence enrollment that no human will click through
  • The agent reacts to changes and you need webhook subscriptions instead of a polling loop against the 30-day event log
  • The workload is volume: bulk edits, bulk deletes, and export jobs over thousands of leads
  • The agent logs activity from outside Close, such as calls placed by an external dialer, or merges duplicate leads
  • You need an endpoint the tool catalog does not wrap; actions.request() proxies any Close API path using the same connected account

Building Close agents with Scalekit

Scalekit ships both paths as separate connectors, and this is the practical reason to run Close through it. The Close connector wraps the REST API with 103 tools prefixed close_. The Close MCP connector proxies Close's own server with tools prefixed closemcp_. Same SDK, same connected account model, same execute_tool call.

Connect a user to Close

Install the SDK and the framework you are building against. This walkthrough uses LangChain in Python.

pip install scalekit-sdk-python langchain-openai

A connected account is the per-user credential record. Create or fetch it first, and send the user through consent only if it is not already ACTIVE. The connection_name values below must match the connection names configured in your Scalekit dashboard exactly; this is the most common integration error.

import os from scalekit.client import ScalekitClient scalekit = ScalekitClient( env_url=os.getenv("SCALEKIT_ENV_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) actions = scalekit.actions CLOSE_API_CONNECTION = "close" # must match the connection name in the Scalekit dashboard CLOSE_MCP_CONNECTION = "closemcp" # must match the connection name in the Scalekit dashboard identifier = "user_123" account = actions.get_or_create_connected_account( connection_name=CLOSE_API_CONNECTION, identifier=identifier, ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CLOSE_API_CONNECTION, identifier=identifier, ) print("Authorize Close:", link.link)

Scalekit stores the resulting access and refresh tokens and refreshes them against the one-hour expiry using the offline_access grant. The rotation race described earlier is handled in one place rather than in every worker.

Retrieve the tools this user is authorized to call

Before the agent loop, fetch the tool surface. list_scoped_tools does not return a flat catalog of everything Close can do; it returns the tools the current user's connected account is authorized to call. That distinction is what separates a per-user agent from a shared-credential agent.

from google.protobuf.json_format import MessageToDict scoped, _ = actions.tools.list_scoped_tools( identifier=identifier, filter={"connection_names": [CLOSE_API_CONNECTION]}, page_size=100, ) for scoped_tool in scoped.tools: definition = MessageToDict(scoped_tool.tool).get("definition", {}) print(definition.get("name"), definition.get("input_schema"))

Run the agent against the REST connector

For LangChain, Scalekit returns native tool objects, so no schema reshaping is needed. This agent works the REST surface, where writes actually dispatch. Understanding how LangChain tool calling works helps clarify why the integration is seamless here.

from langchain_openai import ChatOpenAI from langchain.agents import create_agent tools = actions.langchain.get_tools( identifier=identifier, connection_names=[CLOSE_API_CONNECTION], page_size=100, ) agent = create_agent( model=ChatOpenAI(model="gpt-4o"), tools=tools, system_prompt=( "You maintain pipeline hygiene in Close CRM. " "Never delete records. Log a task rather than emailing a contact directly." ), ) result = agent.invoke({ "messages": [{ "role": "user", "content": ( "Find active opportunities with no activity in 14 days " "and create a follow-up task on each." ), }] }) print(result["messages"][-1].content)

Subscribing to change events is a REST-only capability, and it is one call through the same connected account. Note that events is passed as a JSON-encoded string.

webhook = actions.execute_tool( tool_name="close_webhook_create", connection_name=CLOSE_API_CONNECTION, identifier=identifier, tool_input={ "url": "https://agents.example.com/hooks/close", "events": '[{"object_type": "opportunity", "action": "updated"}]', }, ) print(webhook.data)

Call the MCP connector from the same agent

Nothing about the calling convention changes when you switch paths. The same execute_tool method, the same identifier, a different connection_name. Here the agent uses MCP for the natural-language query it is better at, then drafts an email for human review.

search = actions.execute_tool( tool_name="closemcp_search", connection_name=CLOSE_MCP_CONNECTION, identifier=identifier, tool_input={"query": "leads with an active opportunity over $500 not contacted in the past week"}, ) draft = actions.execute_tool( tool_name="closemcp_create_draft_email", connection_name=CLOSE_MCP_CONNECTION, identifier=identifier, tool_input={ "lead_id": "lead_abc123", "subject": "Following up on our call", "body_html": "

Hi Jane,

Circling back on the pricing question.

", }, ) print(search.data, draft.data)

Compose both paths behind one virtual MCP server

Handing an agent every tool from both connectors is the wrong default. A virtual MCP server declares which connections and which specific tools a given agent role can see, then mints a short-lived session token bound to one user before each run. The default token expiry is about an hour, and create_session_token is the remint call.

Generate the per-user URL on your backend. Never share one URL across users, since each is pre-authenticated for a single identity.

instance = actions.mcp.ensure_instance( config_name="close-pipeline-agent", user_identifier="user_123", ) mcp_url = instance.instance.url

Consume the scoped server from Mastra

Any MCP-capable framework consumes that URL directly. Mastra discovers the tool list and schemas automatically.

npm install @scalekit-sdk/node @mastra/core @mastra/mcp @ai-sdk/openai
import { Agent } from '@mastra/core/agent'; import { MCPClient } from '@mastra/mcp'; import { openai } from '@ai-sdk/openai'; // Resolved per authenticated user from your backend, never a process-wide value const mcpUrl = await getMcpUrlForUser(currentUserId); const mcp = new MCPClient({ servers: { close: { url: new URL(mcpUrl) }, }, }); const tools = await mcp.getTools(); const agent = new Agent({ name: 'close_pipeline_agent', instructions: 'You maintain pipeline hygiene in Close. Draft emails for review; never send directly.', model: openai('gpt-4o'), tools, }); const result = await agent.generate( 'Summarize opportunities closing this month and draft a follow-up email for each owner.', ); console.log(result.text); await mcp.disconnect();

This is where the permission inversion gets resolved. The REST connector's token is all.full_access and cannot be narrowed at Close; the virtual MCP server narrows what the agent can reach with it, and does so per agent role rather than per credential. It also cuts context cost, since a server scoped to eight tools does not spend thousands of tokens describing 103.

Seeing what the agent actually did

Every tool call through Scalekit is recorded with full attribution: who authorized the connection, which agent ran the call, which tool, and what came back. Those agent auth logs are queryable and exportable to your SIEM.

For a Close agent this matters more than usual. When a rep asks why an opportunity changed stage at 2am, "the automation did it" is not an answer a sales leader accepts. More on the reasoning in agent tool observability.

The credential problem that exists on both paths

Both paths hand you a credential per user. Neither hands you a vault, a rotation strategy, or a revocation flow.

What neither path gives you

In a multi-tenant B2B agent, every customer user has their own Close credential. Fifty reps across eight customer organizations is fifty tokens to encrypt at rest, isolate per tenant, refresh before the 3600-second expiry, and invalidate when someone leaves.

Close's refresh token rotation makes the storage requirement stricter than most: the stored refresh token is single-use, so a write that loses a race silently disconnects a user. The API key alternative avoids refresh entirely, which is precisely why it is tempting and precisely why it is wrong for multi-tenant use. A Close API key is a long-lived static secret with no expiry, no per-agent attribution, and no clean revocation story when an employee offboards. The broader implications of secure token management for AI agents at scale apply directly here.

Where Scalekit fits

Scalekit's Close connectors handle the OAuth flow, encrypted per-user token storage, and automatic refresh for both the REST path and the MCP path. The MCP versus API decision changes which tools your agent can call. It does not change your auth infrastructure.

Related reading: access control for multi-tenant AI agents and single vs multi-tenant tool calling agent auth.

Which one to build against

If your agent reads, reasons, and drafts for a human to approve, build against Close MCP. The mcp.read tier gives you a genuinely read-only credential, natural-language search removes a layer of query construction, and the draft-only write semantics are a safety property rather than a limitation.

If your agent sends, reacts, or operates at volume, build against the Close API. Outbound email and SMS, webhook subscriptions, bulk actions, exports, and external call logging have no MCP equivalent, and will not get one by waiting.

Most teams ship both: MCP behind the in-app assistant, REST behind the overnight pipeline. The decision worth making deliberately is not which path, but where least privilege gets enforced, because on Close the REST credential cannot enforce it for you.

Build Close agents with Scalekit

Browse the Scalekit Close connector, or start from a working pattern with the CRM AI agent, outbound prospecting agent, and deal intelligence agent templates. The full catalog of GTM and RevOps agent templates covers adjacent workflows, and every connector in the catalog is listed in the AgentKit connector docs.

Building on Close and hitting something this post did not cover? Join the Scalekit Slack community and ask, or talk to us for help wiring it up. Usage and limits are on the pricing page.

No items found.
Agent
Auth Quickstart
On this page
Share this article
Agent
Auth Quickstart

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.