Announcing CIMD support for MCP Client registration
Learn more

Felt MCP vs Felt API for AI Agents

Nityashree Yadunath
Product Marketing Manager

TL;DR

  • Felt's MCP server and REST API use mutually exclusive auth. MCP is OAuth-only with no API keys; the REST API accepts only personal access tokens (PATs).
  • Felt PATs are workspace-scoped, carry the creator's permissions, and never expire. A multi-tenant agent ends up holding N long-lived bearer tokens.
  • MCP adds agent affordances: SQL dialect guidance and generated styles. Comments, webhooks, dashboard components, layer exports, and embed tokens are REST-only.
  • The REST API throttles at 300 requests per minute per IP, so every tenant behind one egress IP shares that budget.
  • Scalekit's Felt connector vaults and refreshes each user's OAuth tokens, and a virtual MCP server narrows Felt's 44 cataloged tools per agent role.

The decision your Felt agent forces first

Your agent needs to put data on a map. A field-ops assistant plots open work orders. A site-selection agent joins candidate parcels against flood zones. A sales agent turns closed-won deals into a territory map. Felt ships two ways in: a hosted Model Context Protocol (MCP) server and a REST API. Felt MCP vs Felt API looks like a transport choice. It isn't. Each path commits you to a different credential type, a different capability surface, and a different set of production failure modes, and the wrong choice surfaces when your second customer connects. Here's the decision framework.

What Felt MCP and Felt API actually are

Two objects, two different owners of the auth model. Here is each one before the comparison starts.

Felt MCP

Felt launched its hosted MCP server on April 28, 2026, with about 30 tools. Felt builds and runs it, so there is nothing to install. Agents connect over Streamable HTTP with OAuth, and Felt's docs are explicit that there are no API keys: the user logs in through a browser and picks the workspace the agent works in. The authorization server supports Dynamic Client Registration (DCR), so MCP clients register themselves. MCP is available on Felt's Business and Enterprise plans; on other plans, only who_am_i, help_center, and developer_docs respond. Felt's tool reference now lists 49 tools across maps, layers, data sources, styling, and annotations.

Felt REST API

The REST API is versioned as v2 and publishes a machine-readable OpenAPI spec for the production version. It covers maps, layer uploads and exports, layer components for dashboards, elements (the API's name for annotations), comments, embed tokens, data sources, projects, and users. Every call authenticates with a personal access token (PAT), prefixed felt_pat_ and sent as a Bearer token. The REST API documents no OAuth flow. Webhooks exist, configured per map in workspace settings. The REST API and webhooks sit on the same Business and Enterprise plans as MCP, so plan tier doesn't decide this. Python teams can use Felt's official felt-python client.

What your agent can actually do

Both surfaces cover the core map-building loop. The differences sit at the edges, and for agents, the edges decide the architecture.

Where the two surfaces overlap

For creating maps, loading data, styling, and annotating, either path works.

Capability
Felt MCP
Felt REST API
Create maps and read map details
Yes: create_map, get_map, list_maps
Yes: Maps endpoints
Add layers from files and URLs
Yes: prepare_file_upload, import_layer_from_url
Yes: Layer Uploads endpoints
Layers backed by warehouse data
Yes: create_layer_from_data_source runs agent-written SQL
Yes: source-backed layers with scheduled refresh
Styling with Felt Style Language (FSL)
Generated by generate_fsl
You author the FSL JSON
Annotations: pins, polygons, notes
Yes: upsert_annotations
Yes: Elements endpoints, batched upserts
Map image export
export_map_image on Felt's server; not yet in Scalekit's catalog
Yes: Print Exports endpoints

Where only one path reaches

The rows below have no equivalent in Felt's MCP tool reference. Felt's docs also state that MCP can't read or create dashboard components, extensions, custom HTML popups, or print layouts.

Capability
Felt MCP
Felt REST API
Comments on maps
No
Yes: Comments endpoints
Change notifications
No
Webhooks: per map, map:update only
Dashboard components
No
Yes: Layer Components endpoints
Layer data export
No
Yes: Layer Exports endpoints
Data source connection management
No: lists and queries existing sources
Yes: Sources endpoints
Embed tokens for private maps in your product
No
Yes: Embed Tokens endpoints

What the MCP server makes easier

The MCP tools are written for a model, not a programmer. get_sql_guidance loads the correct SQL dialect before the agent writes a query against Snowflake, BigQuery, Databricks, Postgres, or Redshift. inspect_layer returns column schemas with sample values, plus the display and storage names that different tools expect. Tool descriptions carry operating rules too, such as passing location columns through untouched instead of inventing coordinates. Reproducing that on the REST API means writing and maintaining it yourself. Writing the schema is the hard part. Not the API call.

The auth path each one puts you on

This dimension decides most Felt architectures, because the two paths don't share a credential type.

Felt MCP: OAuth only, one login per user

MCP auth is OAuth with no API-key fallback. Each user completes a browser login once, and the connection is bound to the workspace chosen at login. Tokens act with that user's Felt permissions, so the agent sees only what the user sees and edits only what the user can edit. What the user can't do, the agent can't do. The browser is a one-time dependency, not a per-run one. Once a token vault holds and refreshes the user's tokens, scheduled and background runs execute without the user present.

Felt REST API: personal access tokens only

A PAT is created by the user in workspace settings and shown once; Felt stores only a hash. It is scoped to that single workspace, a request against another workspace returns 401, and it carries the creating account's permissions. It never expires. Rotation is manual: create a new token, switch the integration, revoke the old one. For internal automation in one workspace, that is simple. For a multi-tenant product, it means every customer user generates a token and pastes it into your app, and you hold a non-expiring credential that can do everything their account can. That is the largest blast radius on either path.

Both paths still need per-user isolation

In a multi-tenant B2B agent, MCP's OAuth flow gives you a token per user, and the REST API gives you a PAT per user per workspace. In neither case does the path itself solve storage, refresh, rotation, or revocation. Those are infrastructure problems regardless of which path you choose.

Recommended reading: OAuth vs API keys for AI agents

What you own in production

Neither path is zero-maintenance. They fail in different places.

On the MCP path

Felt maintains the server, the tool schemas, the SQL dialect guidance, and the styling engine. You own per-user token storage, refresh, and revocation, plus three Felt-specific behaviors. Layer creation is asynchronous: an agent that reports success before poll_layer_processing_status returns completed may be reporting an empty map. generate_fsl, help_center, and developer_docs consume Felt AI credits and stop when the workspace runs out, while the other tools keep working. And Felt flags delete_map, delete_layer, delete_annotation, and edit_features as irreversible, which matters more for an autonomous agent than for a chat user who can confirm.

On the REST API path

You own everything above plus the request layer, and three Felt behaviors need explicit handling. The rate limit is 300 requests per minute per IP address, keyed to the IP rather than the token, so an agent fleet behind one NAT gateway shares a single budget across every customer. Permission failures return 401, the same status as a revoked or wrong-workspace token. A handler that treats every 401 as "re-authorize" sends users through a reconnect when the real problem is a missing map role, so read the error detail first. List endpoints are unpaginated and return full collections, so large maps mean large payloads.

Schema drift and versioning

MCP tool surfaces move fast. Felt's launch post described about 30 tools, Felt's tool reference lists 49 today, and Scalekit's Felt connector catalogs 44 of them. Felt documents no version contract for MCP tools, so pin your agent to tool names you have verified in the connector's tool list and re-check when Felt ships new ones. The REST API is versioned and backed by an OpenAPI spec, which makes it the more predictable dependency for a deterministic pipeline. Webhooks are its weak spot: one event type, coalesced deliveries, at-least-once retries, and creation only through the workspace settings UI.

When to use MCP, when to use the API

Both lists are specific to Felt, because generic MCP advice doesn't survive Felt's auth model.

Use Felt MCP when

  • You are building a geospatial assistant in Claude, ChatGPT, Cursor, or your own chat product, where each user is present once to log in.
  • The agent authors analysis: writing spatial SQL against your warehouse, building layers from the results, and styling them without hand-written FSL.
  • The agent orchestrates across tools, such as pulling accounts from a CRM, building a territory map in Felt, and posting the link to Slack.
  • Each user's Felt permissions must bound the agent, and you don't want to collect PATs from customers.

Use the Felt REST API when

  • A deterministic pipeline pushes data into Felt on a schedule: file uploads, layer refreshes, and batched annotation upserts.
  • The agent needs a capability with no MCP tool: comments, dashboard components, layer data exports, embed tokens, or data source management.
  • A workflow must react to map changes through webhooks, and one event type with UI-only setup is acceptable.
  • The automation runs inside one workspace you control, under a dedicated user whose PAT you rotate on your own schedule.

The credential problem that exists on both paths

Choosing a path changes the token type. It doesn't change the infrastructure underneath.

N users, N workspace-bound credentials

Every user of a multi-tenant Felt agent holds their own credential, bound to one workspace. On the MCP path, that is an OAuth grant whose access tokens need refreshing, and the refresh has to happen without two concurrent agent runs racing each other. On the REST path, it is a PAT that never expires, which sounds easier until you notice nothing forces rotation: a leaked PAT stays valid until a person revokes it in Felt. Fifty customers with three users each is 150 credentials to encrypt at rest, isolate per tenant, keep out of logs and LLM context, and revoke on offboarding.

Where Scalekit fits

Scalekit's Felt connector handles the OAuth flow, token storage, and refresh for the MCP path, and a custom connector stores PATs in the same vault for the REST path. Credentials never touch the agent runtime, and the MCP vs API decision doesn't change your auth infrastructure.

Recommended reading: why a token vault matters for AI agent workflows

How to connect a Felt agent with Scalekit

Here is the MCP path end to end in Python with LangChain, following Scalekit's LangChain example: authorize a user once, retrieve the Felt tools that user's connected account can call, then run the loop. Python is the right default here, because minting virtual MCP session tokens, covered in the next section, requires the Python SDK today.

Prerequisites and the connection name

Create a Felt MCP connection in the Scalekit dashboard under AgentKit > Connections. The connection_name in your code must match that connection's name exactly; these examples use feltmcp. A mismatch here is the most common integration error. Copy SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, and SCALEKIT_CLIENT_SECRET from Developers > API Credentials into .env, and add OPENAI_API_KEY for the model.

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

Authorize each user once

The user signs in to Felt once through a Scalekit authorization link. After that, Scalekit holds and refreshes their tokens, and your code refers to the user only by identifier. See authorize a user for the production redirect flow.

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 = "feltmcp" # must match the connection name in AgentKit > Connections identifier = "user_123" # your app's stable ID for this user 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("Authorize Felt:", link.link) input("Press Enter after authorizing...") account = actions.get_or_create_connected_account( connection_name=CONNECTION_NAME, identifier=identifier ) if account.connected_account.status != "ACTIVE": raise RuntimeError(f"Felt is {account.connected_account.status}, not ACTIVE")

Retrieve the Felt tools this user can call

The agent doesn't load Felt's full catalog. Scalekit exposes Felt's tools with a feltmcp_ prefix, and actions.langchain.get_tools wraps list_scoped_tools to return LangChain StructuredTool objects bound to this user's connected account. The tool_names filter narrows that to five read-only tools, so delete tools never reach the model's context.

FELT_READ_TOOLS = [ "feltmcp_list_maps", "feltmcp_get_map_layers", "feltmcp_inspect_layer", "feltmcp_get_sql_guidance", "feltmcp_get_tabular_data_from_felt_layers", ] tools = actions.langchain.get_tools( identifier=identifier, connection_names=[CONNECTION_NAME], tool_names=FELT_READ_TOOLS, page_size=100, ) tool_map = {t.name: t for t in tools}

Run the agent loop

Every tool call below executes through Scalekit as user_123, with that user's Felt permissions. The loop is complete.

from langchain_core.messages import HumanMessage, ToolMessage from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [ HumanMessage( "On my Retail Expansion map, list the layers, then count how many " "store points fall inside each flood zone polygon." ) ] 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"]) result = tool.invoke(tc["args"]) if tool else f"Unknown tool: {tc['name']}" messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"]))

Deterministic calls with execute_tool

Pipelines that don't need a model call execute_tool directly. This one refreshes a warehouse-backed layer for a single user. Processing is asynchronous, so follow it with feltmcp_poll_layer_processing_status before reporting success.

result = actions.execute_tool( tool_input={"map_id": "YOUR_MAP_ID", "layer_id": "YOUR_LAYER_ID"}, tool_name="feltmcp_refresh_data_source_layer", connection_name=CONNECTION_NAME, identifier=identifier, ) print(result.execution_id, result.data)

Covering the REST API path

Scalekit doesn't ship a Felt REST API connector. If the agent needs comments, exports, dashboard components, or embed tokens, register Felt's API as a custom connector with BEARER auth. Each user's PAT becomes their connected account, and calls go through the Scalekit proxy with actions.request(). The PAT moves out of your database and agent runtime into the vault. Rotation stays a manual step for the user, because PATs are created in Felt's workspace settings.

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

A Scalekit virtual MCP server is one endpoint that declares which connections and tools an agent can see. One server definition serves every user, and a short-lived session token decides whose credentials each run uses. The endpoint is static; the identity is per-user.

Why Felt's tool surface needs scoping

At Scalekit's rule of thumb of about 200 tokens per tool, Felt's 44 cataloged tools cost roughly 8,800 tokens before the agent does any work. The weight is uneven: feltmcp_prepare_file_upload alone carries about 600 words of description and parameter docs. A territory-digest agent needs five Felt read tools and one Slack write. Scoping 44 tools down to six cuts that context and removes delete tools from the decision space entirely. Surface reduction is the lever. Model upgrades help. They are not the lever.

Create the server once per agent role

This runs once, not once per user; see set up a virtual MCP server for the full lifecycle. It exposes the five read-only Felt tools plus slack_send_message from the Slack connector, and any connector in the AgentKit catalog can join the same server. Both connection names must match the dashboard.

from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping vmcp = actions.mcp.create_config( name="felt-territory-digest", description="Read Felt maps and post coverage summaries to Slack", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="feltmcp", tools=FELT_READ_TOOLS, # the five read-only Felt tools defined above ), McpConfigConnectionToolMapping( connection_name="slack", tools=["slack_send_message"], ), ], ) print(vmcp.config.id, vmcp.config.mcp_server_url) # store both; reuse for every user

Mint a session token per user, per run

Before each run, confirm the user's Felt and Slack accounts are both active, then mint a token scoped to that user and this server. Tokens default to about an hour, and there is no refresh endpoint, so you mint a new one per run. Authorization links returned by this check are valid for one minute.

from datetime import timedelta def mint_session_token(config_id: str, user_id: str) -> str: state = actions.mcp.list_mcp_connected_accounts( config_id=config_id, identifier=user_id, include_auth_link=True, ) pending = { a.connection_name: a.authentication_link for a in state.connected_accounts if (a.connected_account_status or "").upper() != "ACTIVE" # compare case-insensitively } if pending: raise RuntimeError(f"Send {user_id} through re-authorization: {pending}") return actions.mcp.create_session_token( mcp_config_id=config_id, identifier=user_id, expiry=timedelta(minutes=30), ).token

Connect LangChain to the virtual MCP server

The agent sees six tools, whichever user it runs for. Credentials resolve per token, so user_123 and user_456 share one server definition and never share a credential. This block reuses actions and mint_session_token from above.

pip install "langchain-mcp-adapters>=0.3,<1"
import asyncio import os from langchain_core.messages import HumanMessage, ToolMessage from langchain_mcp_adapters.client import MultiServerMCPClient from langchain_openai import ChatOpenAI CONFIG_ID = os.environ["SCALEKIT_MCP_CONFIG_ID"] # saved from create_config MCP_SERVER_URL = os.environ["SCALEKIT_MCP_SERVER_URL"] # saved from create_config async def run_digest(user_id: str) -> str: token = mint_session_token(CONFIG_ID, user_id) client = MultiServerMCPClient( { "scalekit": { "transport": "streamable_http", "url": MCP_SERVER_URL, "headers": {"Authorization": f"Bearer {token}"}, } } ) tools = await client.get_tools() tool_map = {t.name: t for t in tools} llm = ChatOpenAI(model="gpt-4o").bind_tools(tools) messages = [ HumanMessage( "Summarize coverage gaps on my Field Ops map and post the summary " "to the #field-ops Slack channel." ) ] while True: response = await llm.ainvoke(messages) messages.append(response) if not response.tool_calls: return response.content for tc in response.tool_calls: tool = tool_map.get(tc["name"]) result = await tool.ainvoke(tc["args"]) if tool else f"Unknown tool: {tc['name']}" messages.append(ToolMessage(content=str(result), tool_call_id=tc["id"])) for user_id in ["user_123", "user_456"]: print(asyncio.run(run_digest(user_id)))

Observability: every Felt action tied to a user

When an agent edits a customer's map, someone eventually asks who authorized it. The answer has to come from logs, not memory.

What Scalekit records

Every execute_tool call returns an execution_id, and the LangChain adapter appends it to each tool result, so your agent trace carries a reference for every Felt action. Each call resolves a specific connected account, which ties the action to the user who authorized Felt instead of a shared service identity. Scalekit's Felt connector page describes per-call logs attributed to that user with 90 days of history, and the dashboard's Connected Accounts view lists every user's connection. For LangChain agents, the LangSmith tracing cookbook wires those tool calls into your traces.

Why shared credentials break attribution

The tempting shortcut on the REST path is one PAT owned by an automation user. Felt then sees that one user behind every action for every tenant, the audit trail collapses to a service account, and the agent can reach anything that user can. Shared credentials are a single-user solution. They do not survive a second user.

Recommended reading: audit trails for agent auth

Which one to build against

If your Felt agent is interactive or user-delegated, and its job is authoring maps and spatial analysis from a prompt, build on MCP. Felt maintains the tools, the SQL guidance, and the styling, and OAuth keeps every action inside the user's own permissions. If your agent is a deterministic pipeline, or it needs comments, webhooks, dashboard components, exports, or embed tokens, build on the REST API and treat every PAT as the long-lived, user-equivalent secret it is. Many production Felt agents will run both. Either way, the credential problem is the same, and that is what needs production-grade infrastructure.

Build your Felt agent with Scalekit

Browse the Scalekit Felt MCP connector and the Felt connector docs for all 44 tools, or start from a per-user template such as the CRM AI agent. Plan the rollout against Scalekit pricing. Building a Felt agent and want help with the auth design or a virtual MCP setup? Talk to us for immediate help.

FAQs

Does Felt MCP support API key authentication?

No. Felt's hosted MCP server is OAuth-only. There is no API key fallback. Each user must complete a browser-based OAuth login once, and the MCP connection is bound to the workspace chosen at that login.

Can Felt PATs be scoped to specific workspaces?

Felt PATs are scoped to the workspace of the user who created them. A token issued in Workspace A returns 401 if used against Workspace B. However, within that workspace, the PAT carries the full permissions of the creating account and never expires.

What Felt capabilities are only available via the REST API and not MCP?

Comments, webhooks, dashboard (layer) components, layer data exports, data source connection management, and embed tokens for private maps are all REST-only. Felt's MCP docs explicitly state MCP cannot read or create dashboard components, extensions, custom HTML popups, or print layouts.

What is the Felt REST API rate limit?

The Felt REST API throttles at 300 requests per minute per IP address. Because the limit is IP-keyed rather than token-keyed, all tenants behind a shared egress IP or NAT gateway share a single request budget.

How does Scalekit handle Felt OAuth token refresh?

Scalekit's Felt MCP connector vaults each user's OAuth tokens and handles refresh automatically. The agent runtime never holds credentials directly, and refresh races between concurrent runs are avoided by the vault's centralized management.

When should I use Felt MCP vs the Felt REST API for AI agents?

Use Felt MCP when building interactive or user-delegated agents that author maps, write spatial SQL, or orchestrate across tools, and where each user's Felt permissions should bound the agent. Use the REST API for deterministic pipelines, schedule-driven data pushes, or when you need comments, webhooks, dashboard components, exports, or embed tokens.

How do I prevent Felt delete tools from reaching my agent's model context?

Use the tool_names filter in Scalekit's actions.langchain.get_tools (or the virtual MCP server's connection_tool_mappings) to allowlist only the tools your agent role needs. This removes destructive tools like feltmcp_delete_map from the model's decision space entirely.

Can a single Scalekit virtual MCP server serve multiple users?

Yes. A virtual MCP server is defined once per agent role. Per-user identity is resolved at runtime via a short-lived session token minted with actions.mcp.create_session_token and scoped to that user's connected accounts. Multiple users share one server definition without sharing credentials.

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.