How to reach the server, how to authenticate against it, and the order things have to happen in.
You can connect to the Typeform MCP server at this address:
https://api.typeform.com/mcpOur MCP server only supports the newer streamable HTTP transport, and we don't offer an SSE endpoint. Clients configured for SSE will fail to connect.
Typeform accounts are hosted in more than one region, and each region has its own MCP endpoint. If you're building a connector that serves multiple customers, you must send each request to the endpoint for that account's region — this is a routing requirement, not an edge case.
| Account hosting | MCP endpoint | Authorization server |
|---|---|---|
| Default | https://api.typeform.com/mcp | https://api.typeform.com |
| EU data center 1 | https://api.eu.typeform.com/mcp | https://api.typeform.com |
| EU data center 2 | https://api.typeform.eu/mcp | https://api.typeform.eu |
The two EU endpoints are not interchangeable. api.eu.typeform.com is an EU resource served by the main authorization server. api.typeform.eu is a separate stack with its own issuer, authorization endpoint, token endpoint, and signing keys — an access token issued by one is not valid at the other.
EU data residency is an Enterprise plan feature. Customers in an Enterprise plan account can ask their Customer Success Manager for support.
accounts-list_accounts reports a region for each account it returns:
{
"items": [
{ "account_id": "01JXM…RPSQ", "name": "Acme", "region": null, "role": "owner" },
{ "account_id": "01KGE…K4R8", "name": "Acme US", "region": "us", "role": "member" },
{ "account_id": "01JXC…GHMA", "name": "Acme EU", "region": "eu", "role": "admin" }
]
}A region of null means the default hosting. Treat the field as open-ended rather than matching on a fixed set: route on the value you get, and fall back to the default endpoint for anything you don't recognise.
Resolve it once, when the user authorizes, store it alongside their token, and route their subsequent calls to the matching endpoint. Don't infer the region per request, and don't try one endpoint and fall back on failure — to your customer, that reads as an outage.
If an account the user expects isn't in the list, don't conclude it doesn't exist. Ask which region they're in and try that endpoint — a listing from one endpoint may not include accounts hosted in another.
The MCP server uses OAuth 2.0. When you connect, you should be prompted to grant OAuth access to your Typeform account with the required scopes.
Each tool's required scopes are listed in Supported tools. Currently, users can't customize the scopes they grant: they have to grant access to all the scopes to connect to the MCP server. The authoritative list of scopes the server accepts is the scopes_supported field of its metadata, described below; OAuth 2.0 scopes covers the REST APIs, and Applications covers registering an OAuth app by hand.
You don't need to hard-code endpoints, and you shouldn't — it's what makes a connector break when it meets an account in another region. An unauthenticated call to any MCP endpoint returns 401, and its WWW-Authenticate response header (auth scheme: Bearer) carries the pointer you need:
| Header parameter | Value |
|---|---|
resource_metadata | https://api.typeform.com/.well-known/oauth-protected-resource |
scope | Every scope that endpoint accepts, space-separated |
The protected-resource document names the resource, the authorization server that protects it, and the scopes it accepts. The authorization server in turn publishes its own metadata at /.well-known/oauth-authorization-server, including its authorization, token, and registration endpoints.
Walking that chain is how a connector resolves an account's region correctly rather than guessing. Most MCP client libraries do it for you; if you're implementing the OAuth layer yourself, follow it.
The authorization server supports dynamic client registration, so your client can register itself rather than waiting on a manually provisioned client_id:
POST https://api.typeform.com/oauth/registerIt also supports PKCE (S256) and public clients (token_endpoint_auth_method: none), so a native or desktop client doesn't need to ship a client secret.
If you'd rather register an OAuth application by hand, see Applications.
If your integration has to act after the initial access token expires, request the refresh-token scope — the authorization server advertises it as offline_access in its metadata — alongside the others. Without it you won't be issued a refresh token, and your connector will stop working partway through a user's session, usually well after you've stopped watching for it.
Almost every tool requires an account_id, and the access token alone doesn't imply one. Call accounts-list_accounts immediately after a user authorizes, and pass the result to subsequent calls. This is the most common cause of failed first calls.
Users may have access to more than one account. If your connector supports that, let the user choose rather than defaulting silently — operating on the wrong account is hard for them to detect afterwards.
Some tools exist to be called before others. They return the vocabulary the other tools accept, so skipping them means guessing.
| Before you… | Call | Why |
|---|---|---|
| do anything | accounts-list_accounts | Resolves the account_id almost every tool needs |
| build or edit a form | forms-public_get_capabilities | Returns supported field types, block operations, and logic operators |
| run any analytics query | insights-public_discover | Returns the queryable fields, measures, and dimensions for that form. Field IDs are per form |
Both forms-public_get_capabilities and insights-public_discover are stable within a session and safe to cache.
Tools are prefixed by domain, which is the fastest way to route a request. One rule that catches people: reacting to a form submission is an automation, not a form.
Form editing is patch-based, and validation is a required step in the chain — not an optional dry run:
get_capabilities → validate_patch → patch_form → publish_form
returns a requires that makes changes
validation_token validation_token liveforms-public_get_capabilities — once, cached. Build your ops using only the field types, operations, and operators it returns.forms-public_validate_patch — validates your ops against the form draft without saving. Returns a validation_token.forms-public_patch_form — applies the ops. Requires the validation_token from step 2. Saves to the draft.forms-public_publish_form — makes the draft live.patch_form does not publish. A connector that stops there leaves the user's changes invisible, which reads as a bug in your product. Only publish when the user explicitly asks for the form to go live.
forms-public_update_form_metadata changes title without a patch operation.
Response data is read through the insights-* tools:
| Tool | Use for |
|---|---|
insights-public_discover | Required first — resolves the schema |
insights-public_aggregate | Counts, sums, averages, NPS |
insights-public_toplist | Top-N and rankings, grouped by one or two dimensions |
insights-public_timeseries | Measures bucketed from hour to year, with up to two breakdown dimensions |
insights-public_list | Row-level data for a single field |
Insights queries support a filter grammar covering answers.<field_id>, hidden.<name>, tags, and response_type, with ranking and payment sub-paths and and/or filter groups.
For bulk extraction of full response rows, use the REST Responses API — see Not yet supported.