Announcing CIMD support for MCP Client registration
Learn more

Expo MCP vs Expo API for AI Agents (2026)

Saif Ali Shaik
Founding Developer Advocate

TL;DR

  • Expo inverts the usual comparison. The hosted Expo MCP Server is the broader documented programmatic surface: builds, submissions, workflows, TestFlight crashes, App Store and Play Store reviews. The generally available EAS REST API is two endpoints, both for workflows.
  • Expo's tool list splits into server and local capabilities. Local tools such as simulator screenshots need a dev server on your machine, which rules them out for any hosted or headless agent.
  • MCP auth is a browser OAuth sign-in tied to an Expo account. The HTTP surface uses a bearer EXPO_TOKEN that is either a personal access token, which carries everything the human can reach, or a robot user token, which can be role-scoped and revoked independently.
  • Expo MCP exposes public write actions: submitting builds to the stores and posting developer replies on App Store and Play Store reviews. A shared token makes those actions untraceable to the person who triggered them.
  • Neither path gives you a vault, rotation or revocation. Scalekit's Expo MCP connector resolves a per-user credential on every tool call and writes an attributable audit record, so the MCP decision does not change your auth architecture.

Your agent needs to drive an Expo project. Check whether the production iOS build passed, pull the failing job's logs, trigger a rebuild, then triage the TestFlight crashes that followed. Expo ships both a hosted MCP server and an HTTP surface, and here the usual assumption breaks down: the REST API is not the superset. Most of what an Expo release agent wants to do has an MCP tool and no documented endpoint anywhere else. This is where each path ends, and what that means for how you authenticate.

What Expo MCP and the Expo API actually are

These two paths are not two views of the same platform. They were built for different consumers, and the capability gap between them runs in the opposite direction to most tools in this series.

The Expo MCP Server

Expo MCP Server is a remote server hosted by Expo at https://mcp.expo.dev/mcp, running Streamable HTTP with OAuth authentication. It launched in preview in October 2025 for paid plans and opened to Free accounts in May 2026 with a monthly usage allowance.

Its capabilities split into two classes. Server capabilities work with the remote connection alone. Local capabilities require the expo-mcp package in your project and a dev server started with EXPO_UNSTABLE_MCP_SERVER=1, and they are only available on SDK 54 and later.

Expo's own MCP documentation lists the tools and carries an explicit caveat that the list may change with expo-mcp package updates or server changes. Treat it as a moving target, not a contract.

The Expo and EAS HTTP surface

There is no single Expo REST API in the way Slack or GitHub ship one. What exists is a small set of separately documented HTTP surfaces.

The EAS Workflows REST API documents two endpoints under https://api.expo.dev: POST /v2/workflows/dispatch to trigger a run and GET /v2/workflows/runs/:workflowRunId to read its status and jobs. Both take a bearer EXPO_TOKEN.

Alongside that sit the Expo Push Service API at exp.host, EAS webhooks for BUILD and SUBMIT completion, and the EAS Simulator REST API, which is a limited-access preview available only to selected partners. The api.expo.dev/graphql endpoint that EAS CLI talks to is not documented as a public integration surface, and Expo publishes no schema reference for it.

Comparing them where it matters for agents

Four dimensions decide this for a production agent: what it can do, how it authenticates, what breaks in production, and which workloads fit each path.

What your agent can actually do

At the time of writing, Expo's docs list 25 server tools and 6 local tools. The documented HTTP surface covers workflow dispatch, workflow run status, push notifications and two webhook event types. Everything else in the table below has an MCP tool and no published endpoint.

Capability
Expo MCP
Documented Expo HTTP surface
List and inspect EAS builds
Yes, build_list and build_info
No
Trigger an EAS build
Yes, build_run, requires a connected GitHub repo
No
Fetch build logs
Yes, build_logs
No
Cancel a queued or running build
Yes, build_cancel
No
Submit a build to App Store or Google Play
Yes, build_submit
No
Trigger an EAS workflow run
Yes, workflow_run
Yes, POST /v2/workflows/dispatch
Read workflow run status and job results
Yes, workflow_info and workflow_list
Yes, GET /v2/workflows/runs/:id
Fetch workflow job logs
Yes, workflow_logs
No, the run response carries status and outputs only
Create and validate workflow YAML
Yes, workflow_create and workflow_validate
No
TestFlight crashes and screenshot feedback
Yes, testflight_crashes and testflight_feedback
No
Store reviews and public developer replies
Yes, App Store and Play Store tool families
No
Play Store crash and ANR data
Yes, playstore_crashes
No
Build and submit completion events
No
Yes, EAS webhooks with BUILD and SUBMIT types
Send push notifications
No
Yes, Expo Push Service API
Simulator screenshots, taps and view inspection
Local capabilities only
No

Where the gap bites

The gap is not the usual one. If your agent needs build history, build logs, store crash data or review replies, MCP is the only documented path. Going around it means either shelling out to EAS CLI or calling an undocumented GraphQL endpoint whose shape can change without notice.

Where the API still wins

Two things the MCP server does not do. It has no event subscription model, so a build-completion trigger has to come from an EAS webhook. And it has no push notification tool, so anything that ends in a user-facing notification goes through the Push Service API.

The auth path each one puts you on

The credential model differs more than the transport does, and the difference decides what your audit trail is worth.

MCP: browser OAuth, one identity per developer

The current docs describe a single flow: sign in with your Expo account in the browser when prompted, and the server generates an access token. The agent then sees what that Expo account can see across its projects and organizations.

For an interactive coding agent this is the right default. Each developer authorizes once, and every build triggered through the agent is attributable to that developer's Expo identity rather than to shared infrastructure.

API: two bearer tokens that are not equivalent

The HTTP surface takes Authorization: Bearer <EXPO_TOKEN>, and the token type matters. A personal access token can perform actions on your behalf across your personal account and every organization you have been granted access to. That is a wide blast radius for an automated caller.

A robot user token is the alternative Expo recommends for production integrations. Robot users can be assigned a role, cannot sign in to Expo products, cannot own projects and authenticate only through their token, so revoking one does not disturb any human account.

The structural point that applies to both

Both paths hand you a credential per identity and nothing else. MCP's OAuth flow produces a token per user. The HTTP path produces a token per robot or per person. Neither path gives you storage, rotation, revocation or tenant isolation. Those remain infrastructure problems no matter which one you pick, and the difference between single and multi-tenant tool calling auth shows up the moment a second team or a second customer org enters the picture.

What you own in production

Choosing a path decides which class of failure lands in your on-call rotation.

On the MCP path

Expo owns the server, the tool schemas and the underlying calls into EAS. You own the OAuth token per user, plus one thing that catches teams out: MCP usage is metered. Free accounts get a monthly allowance counted at the billing account level and shared across organization members, and requests fail with an error once it is exhausted.

You also own the consequences of a changing tool list. Expo states plainly that capabilities may change with package or server updates. An agent with hardcoded tool names needs a check on startup, not a stack trace at 3am. That is the same class of silent breakage covered in tool call failures in production.

The local capability trap

Six of the tools need a local dev server. They also come with hard limits: one dev server connection at a time, iOS support restricted to simulators, and iOS local capabilities only on macOS hosts. Restarting the dev server requires reconnecting the MCP client.

None of that survives a hosted or scheduled agent. Treat local capabilities as developer-workstation features, not production ones, and design your agent so it never plans around a tool that will not exist on your server.

On the API path

You own polling. The workflow run endpoint returns a status you read in a loop until it reaches success, failure or canceled. You own webhook verification too: EAS sends an expo-signature header containing a hex-encoded HMAC-SHA1 digest of the body, keyed on a secret at least 16 characters long, with exponential backoff retries on any non-2xx or non-3xx response.

You also own one ceiling that has no workaround. Anything outside workflows, push and webhooks means driving EAS CLI in a subprocess, with all the process management, non-interactive flags and project-linking prerequisites that implies.

When to use MCP, when to use the API

The two lists below are Expo-specific. They are not general MCP advice.

Use Expo MCP when

  • Your agent reads release health: build status, build logs, workflow job logs, TestFlight crashes, Play Store ANRs
  • Your agent handles store feedback, including fetching reviews and posting or editing public developer replies
  • Your agent triggers builds and submissions on behalf of a named engineer, and you want each action attributable to that person
  • You are building a coding or DevOps assistant where the developer is present and a browser consent step is natural

Use the Expo HTTP surface when

  • Your pipeline only needs to dispatch a workflow and poll it, which is exactly what the two documented endpoints do
  • Your agent must react to build or submit completion, which requires an EAS webhook rather than polling a tool
  • Your workflow ends in a push notification to app users
  • You are running a fully headless service account where a role-scoped robot user is the correct identity and no human should be in the loop

Connecting an Expo agent through Scalekit

Scalekit ships an Expo MCP connector that fronts the hosted Expo MCP Server, runs the authorization handshake, stores the resulting credential per user and resolves it at call time. The connector page lists 24 tools, covering the server-capability surface except search_documentation, which Expo gates behind an EAS paid plan.

There is no separate Expo API connector in the catalog today, which is consistent with how small the documented HTTP surface is. If you need POST /v2/workflows/dispatch alongside the MCP tools, add it through bring your own connector and it inherits the same vault and audit chain.

Authorize the user once, in Python

Install the SDK and set three environment variables from your Scalekit dashboard, then create the connected account.

pip install scalekit-sdk-python langchain-openai
import os from dotenv import load_dotenv from scalekit.client import ScalekitClient load_dotenv() 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 CONNECTOR = "expomcp" IDENTIFIER = "user_123" account = actions.get_or_create_connected_account( connection_name=CONNECTOR, identifier=IDENTIFIER, ) if account.connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name=CONNECTOR, identifier=IDENTIFIER, ) print("Authorize Expo MCP:", link.link) input("Press Enter after authorizing...")

Make the first call and confirm the wiring

Before handing anything to a model, call one read-only tool directly. If this returns builds, your connector, credential and project linkage are all correct.

result = actions.execute_tool( tool_name="expomcp_build_list", connection_name=CONNECTOR, identifier=IDENTIFIER, tool_input={ "appFullName": "@acme/mobile", "platform": "IOS", "limit": 5, }, ) print(result.data)

Note the expomcp_ prefix. Scalekit namespaces tool names per connector, so build_list on Expo's server is expomcp_build_list here. Use the exact names from the connector's tool list rather than Expo's unprefixed table.

Bind the tools to a LangChain agent

actions.langchain.get_tools() returns native StructuredTool objects, so there is no schema reshaping. Set page_size above the default so a connector with two dozen tools is not silently truncated.

from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, ToolMessage tools = actions.langchain.get_tools( identifier=IDENTIFIER, connection_names=[CONNECTOR], page_size=100, ) tool_map = {t.name: t for t in tools} llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [HumanMessage( "Find the most recent failed iOS build for @acme/mobile, fetch its logs, " "and summarize the root cause. Do not trigger a new build." )] while True: response = llm.invoke(messages) messages.append(response) if not response.tool_calls: print(response.content) break for tc in response.tool_calls: result = tool_map[tc["name"]].invoke(tc["args"]) messages.append( ToolMessage(content=str(result), tool_call_id=tc["id"]) )

The instruction not to trigger a build is a prompt, and prompts are not a control. The next section replaces it with something enforced.

Scope the agent with a Virtual MCP server

A triage agent does not need build_run, build_submit or the review-reply tools. A Virtual MCP server declares which tools exist for that agent role, so the excluded ones are never offered to the model and calls to them are blocked. As covered in agent tool observability, knowing exactly which tools an agent can reach is the first step to meaningful monitoring.

from datetime import timedelta from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping # Run once per agent role, not once per user config = scalekit.actions.mcp.create_config( name="expo-release-triage", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="expomcp", tools=[ "expomcp_build_list", "expomcp_build_info", "expomcp_build_logs", "expomcp_workflow_info", "expomcp_testflight_crashes", ], ), ], ) mcp_server_url = config.config.mcp_server_url config_id = config.config.id

The token cost matters here too. Every tool definition sits in every context window, so a five-tool triage server costs roughly a fifth of what the full connector costs per run. At CI volume that is a real line item.

Mint a per-user session token

The server URL is static and safe to share. The session token carries the identity, so mint one per user before each run and never put it in client-side code.

session = scalekit.actions.mcp.create_session_token( mcp_config_id=config_id, identifier=IDENTIFIER, expiry=timedelta(minutes=30), ) # Return mcp_server_url and session.token to this user's agent process only

Set expiry longer than the expected run. There is no refresh endpoint; create_session_token is the remint call, and a host holding an expired bearer fails at its next tool call rather than at startup.

The TypeScript path with Mastra

Mastra has native MCP support, so it discovers the scoped tool list and schemas straight from the Virtual MCP server. Pass the URL and the user's session token as bearer auth.

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'; // Fetched from your backend for the authenticated user const { mcpServerUrl, mcpToken } = await getExpoMcpSession(currentUserId); const mcp = new MCPClient({ servers: { expo: { url: new URL(mcpServerUrl), requestInit: { headers: { Authorization: `Bearer ${mcpToken}` }, }, }, }, }); const tools = await mcp.getTools(); const agent = new Agent({ name: 'expo_release_triage', instructions: 'You triage Expo release health. Always read build status and logs before drawing conclusions.', model: openai('gpt-4o'), tools, }); const result = await agent.generate( 'Did the latest production iOS build for @acme/mobile pass, and are there new TestFlight crashes since it shipped?', ); console.log(result.text); await mcp.disconnect();

If you prefer direct tool calling over MCP in Node, scalekit.tools.listScopedTools() returns Anthropic-shaped schemas with input_schema, and scalekit.actions.executeTool() runs them.

The credential problem that exists on both paths

Both paths hand you a token and stop. No vault, no rotation logic, no revocation flow, no tenant boundary. That gap is identical whether you chose MCP or the workflow endpoints.

The shared robot token failure mode

A single robot user token looks correct in a demo. Every workflow dispatches, every build triggers, everything works. In production it collapses attribution: the EAS activity log shows one robot user for every action, with no link back to the engineer, the Slack thread or the incident that caused it.

That is tolerable for a nightly build. It is not tolerable for build_submit, which pushes a binary to the App Store or Google Play. This is exactly the kind of credential ownership pattern that breaks down at scale when multiple agents share a single identity.

Who authorized the App Store reply

Expo MCP's review tools sharpen this. appstore_reply_review and playstore_reply_review post text that is publicly visible on the store listing, and each review holds exactly one developer reply, so replying again overwrites the previous one.

An agent posting under a shared credential means a public statement from your company with no recorded author. When someone asks who approved the wording, the answer needs to be a name, not a service account. The case for an audit trail in agent auth is rarely this concrete.

The N-credential problem at team scale

Multiply by headcount. Fifty engineers using a release assistant means fifty Expo credentials to store, refresh and revoke. Offboarding is where this breaks: the SSO account is disabled on Friday, and a personal access token minted eight months ago still works on Monday. The agent does not decide to keep using it. It simply does.

Where Scalekit fits

Scalekit's Expo MCP connector resolves the right per-user credential on every tool call from an AES-256 encrypted, per-tenant vault. The token never enters your prompt, your model context or your logs, and revoking one user's connected account stops their agent without touching anyone else's.

The same infrastructure serves both paths. If you later add the workflow dispatch endpoint as a custom connector, it uses the same vault and the same audit chain, so the MCP decision never becomes an auth migration. Secure token management for AI agents covers the storage and rotation mechanics in full.

What Scalekit adds for Expo agent builders

Three things matter specifically for agents that touch mobile release infrastructure.

Downstream tool call observability

Every call through the connector is logged with full attribution: who authorized it, which agent ran it, which tool, what scope and what came back. Logs are queryable, exportable to Datadog, Splunk or any SIEM, and retained 90 days by default.

For Expo this is not a compliance checkbox. When a build gets cancelled or a store reply gets overwritten, the log tells you which agent run did it and which engineer's credential it used. Failures are separated by source, so an Expo MCP quota error reads differently from an expired credential.

Virtual MCP servers for multi-tool release agents

Real release agents are not Expo-only. A useful one reads the failing EAS build, finds the commit, opens the GitHub PR, files the Linear issue and posts to Slack. That is four connectors and potentially a hundred tools in one context window.

A Virtual MCP server declares the five or six tools that agent role actually needs across all of those connectors, behind one endpoint and one session token per user. One definition serves every user, and each run resolves to that user's own connected accounts.

Multi-tenant isolation without per-tenant code

If you ship agents to other companies, each customer has their own Expo organization, their own builds and their own store listings. One connection definition serves all of them; each tenant's tokens sit in a separate vault namespace and resolve at request time.

The blast radius of a misbehaving run is one user's scoped session token, not a platform credential. Access control for multi-tenant AI agents covers the model in full.

Which one to build against

For Expo the answer is less balanced than usual. If your agent reads or acts on build health, workflow logs, store crashes or store reviews, build against Expo MCP. The documented HTTP surface covers none of it, and EAS CLI in a subprocess is not an integration strategy.

Reach for the HTTP surface for what it was built for: dispatching and polling a workflow, receiving build and submit completion events, sending push notifications. Most production Expo agents use both, with MCP reading and reasoning while webhooks provide the trigger.

The credential layer does not change either way. Per-user isolation, rotation, revocation and an attributable record for every public write action are infrastructure requirements, not path-dependent ones.

Talk to other Expo agent builders

Browse the Scalekit Expo MCP connector or read the connector setup docs to get a first authenticated build call running.

If you are building release automation, crash triage or store feedback agents on Expo, join the Scalekit Slack community and compare notes with other agent builders. For architecture questions that need a faster answer, talk to our team directly.

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.