Core concepts

How to reach the server, how to authenticate against it, and the order things have to happen in.

Connecting to the Typeform MCP server

You can connect to the Typeform MCP server at this address:

https://api.typeform.com/mcp

Our 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.

Data residency

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 hostingMCP endpointAuthorization server
Defaulthttps://api.typeform.com/mcphttps://api.typeform.com
EU data center 1https://api.eu.typeform.com/mcphttps://api.typeform.com
EU data center 2https://api.typeform.eu/mcphttps://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.

Find out which region an account is in

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.

Authentication

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.

Discover the configuration, don't hard-code it

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 parameterValue
resource_metadatahttps://api.typeform.com/.well-known/oauth-protected-resource
scopeEvery 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.

Register your client

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/register

It 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.

Ask for a refresh token if you need one

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.

Resolve the account first

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.

Call order

Some tools exist to be called before others. They return the vocabulary the other tools accept, so skipping them means guessing.

Before you…CallWhy
do anythingaccounts-list_accountsResolves the account_id almost every tool needs
build or edit a formforms-public_get_capabilitiesReturns supported field types, block operations, and logic operators
run any analytics queryinsights-public_discoverReturns 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.

Editing forms

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    live
  1. forms-public_get_capabilities — once, cached. Build your ops using only the field types, operations, and operators it returns.
  2. forms-public_validate_patch — validates your ops against the form draft without saving. Returns a validation_token.
  3. forms-public_patch_form — applies the ops. Requires the validation_token from step 2. Saves to the draft.
  4. 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.

Reading responses

Response data is read through the insights-* tools:

ToolUse for
insights-public_discoverRequired first — resolves the schema
insights-public_aggregateCounts, sums, averages, NPS
insights-public_toplistTop-N and rankings, grouped by one or two dimensions
insights-public_timeseriesMeasures bucketed from hour to year, with up to two breakdown dimensions
insights-public_listRow-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.