Announcing CIMD support for MCP Client registration
Learn more

Square MCP vs Square API for AI Agents (2026)

Vinayak Ravi
Head of Marketing

Square gives an AI agent two ways into a seller's account: an official MCP server in beta that reaches 38 Square API services through three generic tools, and the REST API with OAuth permissions, webhooks and a sandbox. They overlap on nearly every resource. They diverge on how tools are shaped, which environment you can test in, how events reach you, and who holds the credential. This guide compares what each path lets an agent do, the credential each one puts you on, and how to serve thousands of small sellers in production.

TL;DR

  • Square's official MCP server is in beta. It runs hosted at https://mcp.squareup.com/mcp with OAuth sign-in, or locally through npx square-mcp-server start with an ACCESS_TOKEN. It exposes three generic tools, get_service_info, get_type_info, and make_api_request, that reach 38 Square API services. The hosted server only touches production data, and Square only accepts MCP clients on its allowlist.
  • The Square API authenticates with OAuth 2.0 for apps that serve other sellers, or a personal access token that gives unrestricted access to your own account. OAuth access tokens expire after 30 days, Square tells you to renew them every 7 days or less, and refresh tokens from the code flow don't expire. Webhook subscriptions and the Events API accept only a personal access token, never an OAuth token.
  • Square's rate limits apply to your whole application, not to each seller. A multi-tenant agent serving 2,000 small businesses shares one budget across all of them, so one seller's catalog backfill can slow down every other seller.
  • MCP is the faster path for a shop owner who asks Claude why Saturday's sales dropped or looks up a customer's last order. The API is the right path for headless agents that react to dispute.created webhooks, reconcile payouts nightly, or issue refunds under your own approval rules.
  • Scalekit's square connector exposes 118 prebuilt tools over the Square API and runs the OAuth flow, token storage, and refresh through your own Square app, so the MCP vs API decision doesn't change your auth infrastructure.

The decision your Square agent forces first

Your agent needs to work inside your customers' Square accounts: summarize yesterday's sales for each location, find the refunds that went out without a note, answer a chargeback before its deadline, or check which items are about to run out. Those customers are small businesses. A coffee shop, a salon, a food truck, a boutique with two locations: each one is a separate Square seller with its own OAuth grant. Square ships an official Model Context Protocol (MCP) server that reaches almost every Square API, and it ships the REST API itself with webhooks and per-endpoint permissions. They overlap on nearly every resource. They diverge on how tools are shaped, which environment you can test in, how events reach you, and who holds the credential. Here's how to pick.

What Square MCP and Square API actually are

Square MCP

The Square MCP server is a bridge from AI clients to the Square REST API, and Square's docs state that it "is currently in Beta." It runs two ways. The hosted server at https://mcp.squareup.com/mcp, run by Block, signs users in with OAuth, so there are no access tokens to manage by hand. The local server runs with npx square-mcp-server start and reads a Square access token from ACCESS_TOKEN, with SANDBOX=true or PRODUCTION=true to pick an environment, DISALLOW_WRITES=true to restrict it to read-only operations, and SQUARE_VERSION to pin an API version. The GitHub README lists the hosted endpoint as https://mcp.squareup.com/sse for SSE clients.

The tool design is unusual. Instead of one tool per endpoint, the server exposes three: get_service_info to list a service's methods, get_type_info to read a method's parameters, and make_api_request to execute the call. The services behind them cover 38 areas, including payments, refunds, orders, catalog, inventory, customers, loyalty, gift cards, bookings, invoices, subscriptions, disputes, payouts, labor, team, terminal, devices, cash drawers, bank accounts, vendors, and webhook subscriptions. The code is generated from Square's OpenAPI specification. Square lists Claude.ai, Claude Desktop, Goose, Cursor, and Windsurf as supported clients and keeps an allowlist of MCP clients to guard against malicious registrations; anything else must request access on the developer forum.

Square API

The Square API is a set of REST APIs under https://connect.squareup.com/v2, with a separate sandbox at https://connect.squareupsandbox.com. It covers the same services the MCP server routes to: payments, refunds, orders, catalog, inventory, customers, loyalty, bookings, invoices, subscriptions, disputes, payouts, team and labor, terminal, and more. Every request carries a bearer token. Square offers two kinds: a personal access token, which "provides unrestricted Square API access to resources in a Square account" and only reaches the account that created it, and an OAuth access token, which a seller grants your app with specific permissions. Multi-tenant products use OAuth. The API is versioned by date through the Square-Version header, and Square releases new versions "typically on a monthly basis."

Comparing them where it matters for agents

What your agent can actually do

Capability
Square MCP (mcp.squareup.com)
Square API (connect.squareup.com/v2)
List and inspect payments
Yes, make_api_request against the payments service
Yes, Scalekit square_payments_list with time, location, card, and amount filters; square_payment_get
Refunds
Yes, Refunds service through make_api_request
Yes, Scalekit square_refund_create with a required idempotency_key; square_refunds_list
Orders
Yes, Orders service
Yes, Scalekit square_orders_search across up to 10 locations, square_order_get, square_order_create, square_order_pay
Catalog and inventory
Yes, Catalog and inventory services
Limited: Scalekit square_catalog_items_search, square_catalog_object_upsert, square_inventory_changes_batch_create; the connector has no delete tools
Customers and loyalty
Yes, Customers, groups, segments, loyalty services
Yes, Scalekit square_customers_search, square_customer_update, square_loyalty_points_accumulate, square_loyalty_reward_redeem
Disputes (chargebacks)
Yes, Disputes service
Limited: Scalekit square_dispute_evidence_text_create, square_dispute_evidence_submit, square_dispute_accept; text evidence only, no file evidence tool
Bookings and staff
Yes, Bookings, team, and labor services
Yes, Scalekit square_bookings_availability_search, square_booking_create, square_team_members_search, square_timecards_search
Invoices, payment links, subscriptions
Yes, Invoices, checkout, subscriptions services
Yes, Scalekit square_invoice_create, square_invoice_publish, square_payment_link_create, square_subscription_pause
Terminal, devices, cash drawers
Yes, Listed services
Limited: available in the API, but no connector tool; call through the Scalekit API proxy
Webhook subscriptions and Events API
Listed services, but Square requires a personal access token for these endpoints, so an OAuth session can't call them
Yes, Configured once per app in the Developer Console or with your personal access token; not a connector tool
Sandbox testing
Limited: hosted server is production only; local server supports SANDBOX=true
Yes, Separate sandbox base URL and sandbox credentials per app
Event-driven triggers
No triggers
Yes, Webhooks such as dispute.created, signed with x-square-hmacsha256-signature

On raw coverage, Square MCP is close to the full API. It routes to almost every service Square offers, including several the Scalekit connector doesn't wrap as tools. The differences are in shape and in the edges: the hosted server runs only against live seller data, it can't receive events, and the webhook and Events endpoints sit behind a credential type it doesn't use.

What the MCP path makes easier

Three tools is the smallest catalog in this series. A model holding get_service_info, get_type_info, and make_api_request carries a few hundred tokens of tool definitions, not tens of thousands, and it can still reach refunds, catalog, and bookings when a question needs them. For an exploratory question such as "which items sold out at the Mission Street location last weekend," the model can look up the inventory service, read the method's parameters, and make the call without anyone writing a schema. Writing the schema is the hard part. Not the API call. Square generated that layer from its OpenAPI specification, so the MCP server tracks the API as Square ships it.

The tradeoff is that the model does the schema work at run time. Every new operation is a three-step chain: discover the method, read its type, then build the request body by hand. That costs round trips, and the request body is assembled from a type description rather than a curated example, so a wrong field name or an amount in dollars instead of cents surfaces as a Square error, not a schema validation failure. The tool surface is also flat in a way that matters for safety. make_api_request is one tool whether it reads a payment or refunds one. You can't hand an agent "payments, read only" at the tool layer on the hosted server; you get whatever the OAuth grant allows. Locally, DISALLOW_WRITES=true is the only switch, and it is all or nothing.

The auth path each one puts you on

On the MCP path, the hosted server signs the user in with their Square account through OAuth in the browser. Square's docs say remote OAuth lets users authorize only the scopes an application needs, and the session then acts inside that Square account. The local server skips OAuth entirely: it reads one token from ACCESS_TOKEN, which in practice is either a personal access token with unrestricted access to one account, or an OAuth token you obtained elsewhere. The client allowlist is the Square-specific catch. Off-the-shelf clients such as Claude and Cursor work immediately. A custom agent backend that wants to call the hosted server for your customers has to get on Square's allowlist first.

On the direct API path, a seller grants your app permissions through the OAuth code flow at https://connect.squareup.com/oauth2/authorize, and your server exchanges the code at POST /oauth2/token. The token lifecycle is long by industry standards: "Square OAuth access tokens expire after 30 days," and "refresh tokens obtained using the code flow don't expire." The PKCE flow, meant for public clients, issues single-use refresh tokens that expire after 90 days. Square's best practices say your app "should automatically renew OAuth access tokens every 7 days or less" and should raise an alert when a stored token is more than 8 days old.

Permissions are named per endpoint in the Permissions Reference: PAYMENTS_READ for ListPayments, PAYMENTS_WRITE for RefundPayment, ORDERS_READ for SearchOrders, DISPUTES_WRITE for submitting chargeback evidence, MERCHANT_PROFILE_READ for ListLocations, and so on. Square tells you to record the permissions you request, because you can't look them up later from an authorization code or token; RetrieveTokenStatus is the way to check a live token. You can narrow an existing grant by refreshing with a scopes array that is a subset of the original, and a 403 with INSUFFICIENT_SCOPES means the grant lacks a permission.

The credential types also split the work. OAuth tokens can't call the Webhook Subscriptions API or the Events API; those "require the developer's personal access token." Your webhook plumbing therefore runs on your own developer credential, configured once for your Square application, while every seller-facing call runs on that seller's OAuth grant. Each event carries a merchant_id, and mapping it back to the right customer is your job.

Both paths still need per-user isolation

In a multi-tenant B2B agent, MCP gives you a credential per user and the direct API gives you a credential per user. In neither case does the path itself solve storage, rotation, or revocation; those are infrastructure problems regardless of which path you choose.

Square sharpens this because of who its sellers are. A Square grant covers a seller account, and most of your customers are one-owner businesses with a handful of staff. That makes the seller the natural unit: one connected account per business. It also means the long-lived refresh token from the code flow is effectively a standing key to that small business's payments, refunds, and customer list until someone revokes it. The agent acting for the coffee shop must never be able to reach the salon. What the user can't do, the agent can't do, and the only thing enforcing that on the API path is how you bind each grant to one customer.

Recommended reading: access control for multi-tenant AI agents

What you own in production

On the MCP path, Square hosts the server, generates the tool layer from its spec, and enforces the grant. You own each user's OAuth session inside a client you may not control, and the reconnect when it goes stale. You also own the gaps. The hosted server can't touch the sandbox, so every prompt you test against it runs on a live seller's data; Square itself recommends testing prompts against a sandbox account first, which means the local server. MCP tools only run when someone starts a conversation, so nothing happens when a chargeback arrives at 2 a.m. And because the server is in beta, its behavior may change without a release on your side.

On the API path, you own schemas, pagination, retries, versioning, and the token lifecycle. Square returns 429 Too Many Requests with a RATE_LIMITED code, publishes no numeric limits, notes that "Square API endpoints might enforce different rate limits," and recommends exponential backoff with jitter. In a July 22, 2026 developer forum answer, Square staff confirmed that "rate limits are at the application level and may vary by endpoint." For a product serving thousands of small sellers, that is the constraint that shapes your scheduler: nightly syncs for every seller compete for one budget, so you stagger them or a few large catalogs starve everyone else.

Versioning is another thing you own. Every app has a default API version pinned in the Developer Console, which applies to any request without a Square-Version header. Upgrading the default changes behavior for every seller at once, so pin the header in code and move it deliberately.

Webhooks carry their own rules. Your endpoint has 10 seconds to respond with a 2xx. Square retries failed deliveries for up to 24 hours with exponential backoff, then drops them. "Webhooks can be sent more than once," and there is "no guarantee of the delivery order of event notices," so you deduplicate on event_id. Each notification is signed with HMAC-SHA256 in the x-square-hmacsha256-signature header, computed over the notification URL and the raw request body with your subscription's signature key. Because the URL is part of the signature, a proxy or load balancer that rewrites the URL breaks validation. Missed events are recovered through the Events API, which again needs your personal access token.

Revocation arrives from the seller's side. A seller can disconnect your app from the Square Dashboard, which revokes every token for that seller, and the oauth.authorization.revoked webhook's revoker_type tells you whether the seller, your app, or Square did it. When a customer churns, POST /oauth2/revoke, authenticated with an Authorization: Client header carrying your application secret, revokes "the entire authorization" by default.

When to use MCP, when to use the API

Use Square MCP when:

  • A shop owner in Claude asks "how did Saturday compare to the last four Saturdays at both locations," and the model can find the payments and orders services, read the parameters, and answer without anyone building a dashboard.
  • Your own team is exploring an unfamiliar part of Square, such as loyalty programs, labor timecards, or terminal checkouts, and wants to see real responses before committing to a schema.
  • You run the local server against a sandbox seller with SANDBOX=true and DISALLOW_WRITES=true to prototype prompts for a future Square agent with no risk to live money.
  • One person is connecting one Square account in an allowlisted client, with a human reading every answer before anything is refunded.

Use the Square API when:

  • The agent runs headless: a dispute.created webhook triggers it to pull the payment and order with square_payment_get and square_order_get and draft evidence before the due date.
  • The workflow is a deterministic pipeline, such as a nightly close that lists payments, refunds, and payouts per location for every connected seller and posts a reconciliation report.
  • You serve hundreds or thousands of sellers and need connected accounts per seller, webhooks across all of them, and a scheduler that respects one application-level rate budget.
  • You need tool-level boundaries the MCP server can't express, such as an agent that may read payments and add dispute evidence but must never call square_refund_create.

The credential problem that exists on both paths

Both paths hand you a Square credential per seller. Neither hands you a vault. A vertical SaaS product serving 2,000 salons holds 2,000 OAuth grants on the API path. Each has an access token that must be renewed at least every 7 days, and a code-flow refresh token that never expires, which makes it the most valuable thing in your database. Behind them sits your Square application secret, which signs refreshes and revocations for every seller at once. Every token must be encrypted at rest, kept out of logs and LLM context, refreshed on schedule rather than on the first ACCESS_TOKEN_EXPIRED, and cleaned up when the seller disconnects from their Square Dashboard.

The MCP path changes where the sessions live, not the problem. Each owner who connects through Claude or Cursor holds their own session, which matches Square's model, but you can't refresh, inspect, or revoke those sessions from your backend, and a custom backend can't use the hosted server at all until Square allowlists it. The tempting shortcut is the local server with a personal access token per customer. That is unrestricted, unscoped access to each customer's Square account, issued by them, pasted into your systems. Fewer moving parts, larger blast radius per secret.

Scalekit's square connector handles the Square OAuth flow, token storage, and refresh for every connected account, so the MCP vs API decision doesn't change your auth infrastructure. Your agent passes an identifier; credentials never touch the agent runtime or the LLM context.

Recommended reading: how to handle token refresh for AI agents

How to connect a Square agent with Scalekit

Scalekit's square connector is an API-path connector built by Scalekit with 118 tools: 69 read, 49 write, and 0 destructive. It covers payments, refunds, orders, catalog, inventory, customers, customer groups and segments, loyalty, gift cards, cards on file, bookings and booking profiles, invoices, payment links, subscriptions, disputes, payouts, locations, merchants, team members, wages, timecards, break types, and vendors. Write tools take an idempotency_key, so retries return the original result instead of acting twice. It authenticates with OAuth 2.0: each seller signs in to Square once, and Scalekit stores and refreshes the tokens.

Prerequisites and the connection name

In the Scalekit dashboard, go to AgentKit > Connections, create a Square connection, and copy its redirect URI. The name you give the connection is the connection_name your code passes, and the string must match the dashboard exactly. This is the most common integration error.

Square connections require your own OAuth app. In the Square Developer Console, create an application, add the Scalekit redirect URI on its production OAuth page, and paste the application's ID and secret into the Square connection. Then select the permissions your tools need on the connection; they apply to every connected account that uses it. For the examples below that means MERCHANT_PROFILE_READ, PAYMENTS_READ, ORDERS_READ, DISPUTES_READ, and DISPUTES_WRITE. Leave out PAYMENTS_WRITE unless an agent role genuinely issues refunds. Copy SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET from Developers > API Credentials into .env.

pip install scalekit-sdk-python python-dotenv langchain-openai squareup

Authorize each seller once

get_or_create_connected_account returns the connected account for an identifier. If it is not ACTIVE, get_authorization_link returns a link to Square's OAuth consent screen for your app. The seller approves once; Scalekit exchanges the code and stores the tokens, and the agent never sees them. Here the identifier is your customer's ID, because the grant covers the whole Square seller account. The last step records the seller's Square merchant_id, which you need later to route webhooks.

import os from dotenv import load_dotenv from scalekit import ScalekitClient load_dotenv() scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], ) actions = scalekit_client.actions CONNECTION_NAME = "square" # must match the connection name in AgentKit > Connections identifier = "customer_salon_0412" # your app's stable ID for this Square seller account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTION_NAME, identifier=identifier, ) print("The Square seller should open:", link.link) input("Press Enter after approving access in Square...") account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier, ) if account.connected_account.status != "ACTIVE": raise RuntimeError(f"Square is {account.connected_account.status}, not ACTIVE") merchant = actions.execute_tool( tool_name="square_merchant_get", tool_input={"merchant_id": "me"}, connection_name=CONNECTION_NAME, identifier=identifier, ) merchant_id = (merchant.data.get("merchant") or {}).get("id") print(f"Store this mapping: {merchant_id} -> {identifier}")

Retrieve the Square tools this account can call

This is not a catalog load. The agent asks for the tools this connected account is authorized to call, then narrows them to the six a daily-close agent needs. Loading all 118 Square tools would put square_refund_create, square_dispute_accept, and square_payment_create in front of a model whose job is to summarize a day of sales. None of the six below changes anything in Square.

tools = actions.langchain.get_tools( identifier=identifier, connection_names=[CONNECTION_NAME], tool_names=[ "square_merchant_get", "square_locations_list", "square_payments_list", "square_refunds_list", "square_orders_search", "square_disputes_list", ], page_size=100, ) tool_map = {t.name: t for t in tools} print(sorted(tool_map))

Run the agent loop

The loop runs until the model stops requesting tools. Every call executes with this seller's Square grant. The system prompt carries the two Square details models get wrong most often: amounts are in the smallest currency unit, and square_payments_list defaults to the main location unless you pass a location_id.

from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [ SystemMessage( "You prepare a daily close for one Square seller. Call square_locations_list " "first and pass each location_id explicitly to square_payments_list and " "square_refunds_list, because they default to the main location. Pass " "location_ids to square_orders_search. Square amounts are in the smallest " "currency unit, so 1250 USD means $12.50. Times are RFC 3339. Never guess IDs." ), HumanMessage( "Give me the close for 2026-10-27 from 00:00 to 23:59 America/Los_Angeles: " "gross card sales, tips, refunds with their reasons, and any disputes that " "still need evidence, broken down by location." ), ] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for tc in response.tool_calls: tool = tool_map.get(tc["name"]) if tool: result = tool.invoke(tc["args"]) else: result = f"Unknown tool: {tc['name']}" messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

Deterministic calls with execute_tool

For pipelines, skip the model. execute_tool runs one tool with a fixed input and returns data plus an execution_id you can log. This handler runs when your webhook endpoint receives a dispute.created event. It verifies the signature with Square's SDK helper, drops duplicate deliveries, maps the event's merchant_id to your customer, then pulls the disputed payment and its order and attaches a text summary as evidence. It stops before square_dispute_evidence_submit, because evidence can't be added after submission and a person should read it first.

import json import uuid from square.utils.webhooks_helper import verify_signature SQUARE_WEBHOOK_SIGNATURE_KEY = os.environ["SQUARE_WEBHOOK_SIGNATURE_KEY"] SQUARE_WEBHOOK_URL = os.environ["SQUARE_WEBHOOK_URL"] # exactly as registered in the Developer Console # merchant_id -> identifier, filled after each seller connects (see square_merchant_get above) MERCHANT_TO_IDENTIFIER = {"MLR7Z3K9QX2B1": "customer_salon_0412"} processed_event_ids: set[str] = set() # use a durable store in production def handle_dispute_created(raw_body: str, signature: str) -> None: if not verify_signature( request_body=raw_body, signature_header=signature, signature_key=SQUARE_WEBHOOK_SIGNATURE_KEY, notification_url=SQUARE_WEBHOOK_URL, ): raise PermissionError("Invalid x-square-hmacsha256-signature") event = json.loads(raw_body) if event.get("type") != "dispute.created": return if event["event_id"] in processed_event_ids: return # Square may deliver the same event more than once seller = MERCHANT_TO_IDENTIFIER.get(event["merchant_id"]) if seller is None: raise LookupError(f"No connected account for merchant {event['merchant_id']}") dispute_id = event["data"]["id"] dispute = actions.execute_tool( tool_name="square_dispute_get", tool_input={"dispute_id": dispute_id}, connection_name=CONNECTION_NAME, identifier=seller, ).data.get("dispute", {}) payment_id = (dispute.get("disputed_payment") or {}).get("payment_id") payment = actions.execute_tool( tool_name="square_payment_get", tool_input={"payment_id": payment_id}, connection_name=CONNECTION_NAME, identifier=seller, ).data.get("payment", {}) line_items = [] if payment.get("order_id"): order = actions.execute_tool( tool_name="square_order_get", tool_input={"order_id": payment["order_id"]}, connection_name=CONNECTION_NAME, identifier=seller, ).data.get("order", {}) line_items = [ f"{item.get('quantity', '1')} x {item.get('name', 'item')}" for item in order.get("line_items", []) ] amount = payment.get("amount_money") or {} evidence_text = ( f"Payment {payment_id} on {payment.get('created_at')} for " f"{amount.get('amount', 0) / 100:.2f} {amount.get('currency', 'USD')}. " f"Status {payment.get('status')}. Items: {', '.join(line_items) or 'not itemized'}. " f"Receipt: {payment.get('receipt_url', 'not available')}." ) evidence = actions.execute_tool( tool_name="square_dispute_evidence_text_create", tool_input={ "dispute_id": dispute_id, "evidence_text": evidence_text, "evidence_type": "GENERIC_EVIDENCE", # Same event -> same key, so a retried handler can't attach evidence twice "idempotency_key": str(uuid.uuid5(uuid.NAMESPACE_URL, event["event_id"])), }, connection_name=CONNECTION_NAME, identifier=seller, ) processed_event_ids.add(event["event_id"]) print(evidence.execution_id, dispute_id, dispute.get("reason"), dispute.get("due_at"))

The webhook subscription itself is not a connector tool, and on Square it can't be one: subscriptions belong to your Square application and are created in the Developer Console or with your personal access token, not with a seller's OAuth grant. Subscribe once to dispute.created, which requires DISPUTES_READ on each seller's grant, and route every event by merchant_id. The Square MCP server has no equivalent for either step.

Two other writes show where the API path earns its place. square_refund_create refunds a completed payment and requires an idempotency_key, and square_dispute_accept concedes a chargeback. Neither belongs in a model's default tool set; both fit a pipeline with a human approval step in front of them.

Multi-tool, multi-tenant Square agents on a virtual MCP server

The square connector has 118 tools. At roughly 200 tokens each, that is about 23,600 tokens of tool definitions before the agent does any work. Square's own MCP server avoids that cost with three generic tools, but it moves the problem rather than solving it: the model can still reach every service the grant allows, and the decision of what it may do lives in the prompt. A dispute agent that can reach square_refund_create or square_dispute_accept can refund or concede on a bad inference. The fix is not better prompting. It is surface reduction. A Scalekit virtual MCP server declares exactly which tools an agent role can see, and session tokens keep each run bound to one seller's connected accounts. Docs: set up a virtual MCP server.

Create the server once per agent role

from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping vmcp = actions.mcp.create_config( name="square-dispute-desk", description="Reviews new Square disputes, drafts text evidence, and alerts the owner in Slack", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="square", tools=[ "square_disputes_list", "square_dispute_get", "square_payment_get", "square_order_get", "square_dispute_evidence_text_create", ], ), McpConfigConnectionToolMapping( connection_name="slack", tools=["slack_send_message"], ), ], ) print(vmcp.config.id, vmcp.config.mcp_server_url)

Mint a session token per seller, per run

One server definition serves every seller. Before each run, check that the seller's accounts are active and mint a short-lived session token scoped to them. Any MCP client that speaks Streamable HTTP can connect; this example uses langchain-mcp-adapters (pip install langchain-mcp-adapters) and passes the token as a Bearer header.

import asyncio from datetime import timedelta from langchain_mcp_adapters.client import MultiServerMCPClient config_id = vmcp.config.id mcp_server_url = vmcp.config.mcp_server_url customer_id = "customer_salon_0412" accounts = actions.mcp.list_mcp_connected_accounts( config_id=config_id, identifier=customer_id, include_auth_link=True, ) not_ready = [ a for a in accounts.connected_accounts if str(a.connected_account_status).upper() != "ACTIVE" ] for a in not_ready: print(f"{a.connection_name} is {a.connected_account_status}: {a.authentication_link}") if not_ready: raise RuntimeError("Connect the accounts above before starting this run") session_token = actions.mcp.create_session_token( mcp_config_id=config_id, identifier=customer_id, expiry=timedelta(minutes=30), ).token async def load_tools(): client = MultiServerMCPClient( { "scalekit": { "transport": "streamable_http", "url": mcp_server_url, "headers": {"Authorization": f"Bearer {session_token}"}, } } ) return await client.get_tools() mcp_tools = asyncio.run(load_tools()) print([t.name for t in mcp_tools])

The agent sees six tools instead of 118, and none of them can move money. The endpoint is static; the identity is per seller. Unlike Square's own MCP server, this endpoint needs no client allowlisting, works from your own backend, and lets you give a refund role square_refund_create while every other role goes without it.

Observability: every Square action tied to a seller

Every execute_tool call returns an execution_id, and every execution is attributed to a connected account, which maps to one of your customers. When a salon owner asks which agent run attached evidence to a chargeback, or why a refund went out on Sunday, the answer is a lookup, not an investigation. Store the execution_id next to the Square object it touched, such as the dispute ID or the refund ID, and you can trace any Square record back to the run and the prompt that produced it.

Attribution is also where the paths differ. On the MCP path, actions run inside the owner's own chat session and the record lives in their client. On the API path, every call runs under the seller-level grant your app holds, and Square has no field that says which of your users asked the agent to act; your execution log is the only place that record exists. A shared credential across sellers breaks even that: one token reaching several Square accounts means every action looks the same, and a leak exposes all of them. Shared credentials work for one user. They do not survive a second user.

Recommended reading: audit trails for agent auth

Which one to build against

If your agent is a seller's assistant in Claude, Cursor, or another allowlisted client, answering questions about sales, customers, and stock with the owner watching, Square's official MCP server is a good fit: it is hosted, it reaches nearly every Square service through three tools, and Square keeps it in step with the API. Run it locally against a sandbox seller while you test. If your agent runs unattended across many sellers, reacts to dispute.created and payment webhooks, reconciles payouts, or needs to be barred from refunds by construction, build against the Square API, where you choose the permissions, the tools, and the schedule. Either way, you hold an OAuth grant per Square seller, with access tokens to renew every week and refresh tokens that never expire, and that's what needs production-grade infrastructure.

FAQs

Does Square have an official MCP server?

Yes, in beta. It runs hosted at https://mcp.squareup.com/mcp with OAuth sign-in, or locally with npx square-mcp-server start and an ACCESS_TOKEN. Instead of one tool per endpoint it exposes three, get_service_info, get_type_info, and make_api_request, which reach 38 Square API services generated from Square's OpenAPI specification. The hosted server only touches production data, and Square keeps an allowlist of MCP clients.

What can an agent not do through Square MCP?

It cannot receive events, since there are no triggers, and it cannot call the Webhook Subscriptions API or the Events API, because Square requires a personal access token for those endpoints and an OAuth session cannot use them. The hosted server cannot touch the sandbox, so every test prompt runs on live seller data, and the only write restriction is the local server's all-or-nothing DISALLOW_WRITES flag.

How long do Square OAuth tokens last?

Square OAuth access tokens expire after 30 days, and Square's best practices say to renew them every 7 days or less and to alert when a stored token is more than 8 days old. Refresh tokens obtained with the code flow do not expire. The PKCE flow for public clients issues single-use refresh tokens that expire after 90 days.

How do Square rate limits work for a multi-tenant agent?

Square applies rate limits at the application level, not per seller, and publishes no numeric limits. A product serving thousands of sellers shares one budget across all of them, so nightly syncs compete and one seller's catalog backfill can slow down every other seller. Square returns 429 with a RATE_LIMITED code and recommends exponential backoff with jitter.

When should an agent use the Square API instead of MCP?

When it runs headless from a dispute.created or payment webhook, when it runs a deterministic nightly close across every connected seller, when it serves hundreds or thousands of sellers with per-seller connected accounts, or when it needs tool-level boundaries the MCP server cannot express, such as reading payments and adding dispute evidence without ever being able to call square_refund_create.

How does Scalekit connect an AI agent to Square?

The square connector exposes 118 tools over the Square API and runs the OAuth flow, token storage, and refresh through your own Square app. Each seller approves once, Scalekit stores and renews the tokens, write tools take an idempotency_key, a virtual MCP server narrows the tools per agent role so a dispute desk never sees refunds, and every execution returns an execution_id attributed to that seller.

Build your Square agent with Scalekit

Browse the Scalekit Square connector and the Square connector docs with the full tool list. See Scalekit pricing, or talk to us.

No items found.
On this page
Share this article
Scalekit
Role-scoped tools

Acquire enterprise customers with
‍zero upfront cost.

Every feature unlocked. No hidden fees.