Announcing CIMD support for MCP Client registration
Learn more

Grain MCP vs Grain API for AI Agents (2026)

Shri Mithran
Director of Marketing

TL;DR

  • Grain MCP and Grain API cover different halves of Grain. The MCP's 41 tools include semantic transcript search, deal intelligence, coaching scorecards, and clips. The API's two dozen endpoints include webhooks, media upload and download, and sharing. Neither contains the other.
  • MCP auth is OAuth only. The API accepts Personal Access Tokens, Workspace Access Tokens, and OAuth 2.0 Authorization Code with PKCE. Free plans get no API access.
  • API v1 sunsets in September 2026. v2 requires Public-Api-Version: 2025-10-31 and allows 300 requests per minute per token.
  • Both paths hand you one credential per user and nothing else. Storage, refresh, and revocation are yours either way.
  • Scalekit ships one connector per path behind one connected account model, so the choice never changes your auth infrastructure.

Why the choice is not obvious

Your agent needs to read meetings out of Grain. Grain ships a hosted Model Context Protocol (MCP) server and a versioned REST API, and the two do not expose the same things. The MCP is where Grain's AI features live: semantic search across transcripts, deal health, coaching scorecards, clip creation. The API is where the plumbing lives: webhooks, uploads, downloads, sharing, workspace metadata. Pick wrong and you either lose the search quality your agent depends on or lose the event hooks your pipeline depends on. Here is how to choose, and how to avoid rebuilding auth when you change your mind.

What Grain MCP and Grain API actually are

Two objects are being compared. One is a hosted MCP endpoint that Grain operates and updates on its own schedule. The other is a date-versioned REST API that you call directly with a bearer token.

Grain MCP

Grain launched its official remote MCP server on June 18, 2025, and it is available to every Grain plan. The endpoint is hosted by Grain; there is nothing to install or run. Authentication is OAuth: the first connection opens a browser, the user signs in to Grain and approves access, and the server holds that grant for future sessions. The server supports Dynamic Client Registration (DCR), which is why no client ID or secret is required to connect.

What the MCP server exposes today

Grain's own quick-start page lists a smaller tool set than the server currently exposes. Release notes from May 2026 added clip creation and bulk tagging, and July 2026 added smart topics and workspace management tools. Scalekit's connector reflects the live server at 41 tools.

Deal and coaching tools are restricted to Business and Enterprise plans; Free and Starter users receive an error when calling them. The server also ships nine MCP Prompts for structured reports such as voice-of-customer, pipeline health, and SPICED or MEDDICC deal analysis. Grain has flagged mcp-remote as a soon-to-be-deprecated connection method.

Grain API

The Grain public API v2 lives under api.grain.com/_/public-api/v2. It exposes recordings (list, get, transcript in JSON, TXT, VTT, and SRT, download, upload, update title, tags, sharing to users and teams), hooks (create, list, delete across ten event types), and workspace metadata (users, teams, meeting types). Each request needs an Authorization: Bearer header and a Public-Api-Version header set to 2025-10-31, the only supported version.

Three credential types work: a Personal Access Token (PAT), a Workspace Access Token (WAT), or an OAuth 2.0 access token obtained via Authorization Code with PKCE. Rate limit is 300 requests per minute per token. The v1 beta API is sunset in September 2026.

Two connectors in Scalekit, one per path

Scalekit maintains both surfaces as separate connectors. The Grain connector wraps the REST API with 19 prebuilt tools using bearer token auth. The Grain MCP connector wraps the hosted MCP server with 41 tools using OAuth with DCR. Both sit behind the same connected account and execute_tool interface, which is the point of the rest of this article.

Comparing them where it matters for agents

Four dimensions matter: what the agent can do, how it authenticates, what you own in production, and which scenarios favor which path. Grain is unusual in this series because the MCP surface is not a subset of the API. It is a different product surface.

What your agent can actually do

The table below maps agent-relevant capabilities across both paths. Tool names in the MCP column are the Scalekit connector names, which prefix the server's native names with grainmcp_.

Capability
Grain MCP
Grain API
List and filter meetings
Yes (grainmcp_list_meetings)
Yes (POST /v2/recordings with filter)
Full transcript
Yes, Markdown (grainmcp_fetch_meeting_transcript)
Yes, JSON, TXT, VTT, SRT
Semantic search across transcripts
Yes, hybrid BM25 plus vector (grainmcp_search_in_transcripts)
No, title_search only
AI notes, summary, action items
Yes
Yes (include.ai_summary, include.ai_action_items)
Coaching scorecards
Yes, Business and Enterprise (grainmcp_list_coaching_feedback)
No
HubSpot deal status and risk
Yes, Business and Enterprise (grainmcp_list_open_deals, grainmcp_fetch_deal)
Limited, HubSpot IDs only via include.hubspot
Company intelligence dossier
Yes (grainmcp_get_dossier_for_company)
No
Create clips
Yes (grainmcp_create_clip)
No, read highlights only
Stories, collections, projects
Yes, create and manage
No
Smart topics
Yes, list and create (admin)
No
Tag meetings
Yes, bulk (grainmcp_tag_meetings)
Yes, per recording
Webhooks on recording, highlight, story, upload events
No
Yes (POST /v2/hooks/create)
Upload external recordings
No
Yes, Business and Enterprise
Download media file
No
Yes
Share recording with a user or team
No, collection and project share state only
Yes
Update recording title
No
Yes
List workspace users, teams, meeting types
Users only
Yes, all three
User recording settings
Yes (grainmcp_update_my_settings)
No

Search is the MCP's advantage

The gap that matters most for meeting-intelligence agents is search. The API filters recordings by title substring, date range, team, meeting type, and participant scope. It does not search transcript content. The MCP's grainmcp_search_in_transcripts runs hybrid keyword and semantic retrieval over segmented transcript chunks and returns matching segments grouped by meeting, so the agent never loads a full transcript to find one quote. A pricing-objection agent built on the API has to page through recordings and scan transcripts itself. The same agent on MCP makes one call.

Events and media are the API's advantage

The MCP has no event surface. If your agent should react when a recording finishes processing, a clip is created, or an upload completes, that requires the API's hooks. The MCP also cannot upload, download, retitle, or share individual recordings. Those are write operations on the recording object itself, and Grain has kept them on the API. An archival pipeline that pulls SRT transcripts and MP4 files into a data lake is an API workload, full stop.

The auth path each one puts you on

Auth is where the two paths diverge most sharply for anyone running agents without a user present.

MCP: OAuth only, one grant per user

The hosted MCP server requires a browser-based OAuth consent flow. There is no API key fallback. DCR means the MCP client registers itself, so you never obtain a client ID from Grain, but the user still has to be present to approve the grant. The resulting session is scoped to that user's Grain permissions: meetings they attended or have access to, deals linked to those meetings, and nothing from other workspaces. Grain's docs state this explicitly. What the user can't see, the agent can't see.

API: three token types, three trust models

The API accepts a PAT, a WAT, or an OAuth 2.0 access token, all sent as Authorization: Bearer. They differ in scope and lifecycle:

  • Personal Access Token: same permissions as the user who generated it. Available on Starter and above. Never expires on its own. Fine for one person's automation, wrong for a product.
  • Workspace Access Token: access to every recording in the workspace regardless of owner. Business and Enterprise only, and only admins can generate one. This is the service account pattern, with its full blast radius.
  • OAuth 2.0 Authorization Code with PKCE: per-user grant, scoped to that user's access. The documented expires_in is 3600 seconds and each refresh returns a new refresh token. This is the only API path suitable for a multi-user product.

Getting OAuth credentials from Grain

To use OAuth you must request a client_id and client_secret from Grain manually and register a redirect URI prefix. There is no self-serve app registration.

What neither path gives you

Both paths require per-user credential isolation in a multi-tenant B2B agent. MCP's OAuth flow gives you a token per user. The API's OAuth flow gives you an access token and refresh token per user. In neither case does the path itself solve storage, rotation, or revocation. Grain's API access tokens expire in an hour. Grain's MCP grants persist until the user revokes them in Grain. Your agent has to know which state it is in before every call. Those are infrastructure problems regardless of which path you choose.

What you own in production

The production question is simple: what breaks, who notices, and who fixes it?

On the MCP path

Grain owns the tool schemas, the endpoint normalization, the semantic index, and the plan gating logic. When Grain adds a tool, your agent sees it on the next tools/list without a redeploy. That is real leverage; the July 2026 smart topics tool appeared with zero work on the consumer side. What you still own: the OAuth token per user, detecting when a grant has been revoked, and keeping one user's session from being reused for another. MCP tool schemas are unversioned. A renamed parameter ships when Grain ships it.

On the API path

You own everything. Request construction, Public-Api-Version header discipline, 300 requests per minute per token with 429 backoff using Retry-After, cursor pagination, hook endpoint reachability (Grain probes the URL and requires a 2xx on creation), token refresh before the one-hour expiry, and the hook payload handling itself. In exchange you get a date-versioned contract: 2025-10-31 behaves the same next quarter. For deterministic pipelines that is the more predictable dependency.

Versioning and drift

The two surfaces drift independently. Grain shipped tags and action items to the API in March 2026, uploads in January 2026, and MCP clips in May 2026. Nothing forces parity. If your agent needs both semantic search and webhooks today, you are already on both paths, and you are already carrying two credential types for the same user.

When to use MCP, when to use the API

Grain agents tend to fall cleanly into one bucket or the other. The lists below are specific to Grain, not generic MCP advice.

Use Grain MCP when

  • The agent's core job is finding moments across many meetings: objections, competitor mentions, decisions, customer quotes. grainmcp_search_in_transcripts is the only way to do this without scanning transcripts yourself.
  • You are building a sales or customer success assistant that needs deal health, at-risk flags, or coaching scorecards. None of that exists on the API.
  • The agent runs interactively in Claude, Cursor, or your own chat surface where the user is present for the OAuth grant.
  • You want clip creation, story curation, or smart topic management driven by natural language, as in the competitive intelligence briefing agent pattern.
  • You are on a Free or Starter Grain plan. The API is unavailable on Free and workspace-wide access requires Business.

Use the Grain API when

  • The agent must react to events: a recording finished processing, a highlight was added, an upload completed. Hooks are API only.
  • You are ingesting external recordings into Grain or exporting media and SRT or VTT transcripts out of it.
  • The pipeline is headless and scheduled, and a Workspace Access Token on a Business plan is the right trust model for your organization.
  • You need recording-level administration: retitle, share with a team, revoke a user's access, manage tags per recording.
  • Schema stability matters more than automatic tool updates, and you want Public-Api-Version pinning.

The credential problem that exists on both paths

Whichever path you pick, the day a second user connects Grain is the day the auth problem becomes real. This section names it without dressing it up.

N users, N Grain credentials

A meeting-prep agent serving 40 account executives holds 40 Grain grants. On the MCP path those are 40 OAuth sessions that Grain can revoke individually and silently. On the API path those are 40 access tokens expiring hourly plus 40 refresh tokens that rotate on every use. If two threads refresh the same user's token concurrently, one of them ends up holding a dead refresh token and the agent fails on the next run. That race is not hypothetical; it is the standard failure mode described in how to handle token refresh for AI agents.

The Workspace Access Token shortcut

The shortcut is a single Workspace Access Token. It works in demos and does not survive production. Every action appears in Grain as the admin who minted the token, every user can reach every recording in the workspace, and revoking one user's access means rotating the credential for everyone. For a deeper treatment see access control for multi-tenant AI agents.

Where Scalekit fits

Scalekit's two Grain connectors handle the credential lifecycle for both paths. The MCP connector runs the OAuth and DCR flow, stores the grant per user, and injects it on every tool call. The API connector collects the bearer token per user through a hosted page, vaults it, and injects it the same way. Your agent code calls execute_tool with a user identifier and never sees a token on either path. The MCP vs API decision stops being an auth decision.

Building a Grain agent the Scalekit way

The pattern below is the same for both connectors: resolve the user, confirm their connected account is active, retrieve the tools they are authorized to call, then run the model loop. The examples use the Claude Messages API in Python for the MCP connector and TypeScript for the API connector, plus a LangChain adapter at the end.

Set up the connections

In the Scalekit dashboard, go to AgentKit, then Connections, then Create Connection. Create one connection for Grain MCP (no client credentials needed; DCR handles registration) and, if you need the API surface, one for Grain. Note the connection names. The string you pass as connection_name in code must match the dashboard connection name exactly; a mismatch returns an empty tool list rather than an error. The examples use grainmcp and grain.

Install the SDKs:

pip install anthropic scalekit-sdk-python python-dotenv npm install @scalekit-sdk/node @anthropic-ai/sdk dotenv

Set SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET, and ANTHROPIC_API_KEY in .env. The Scalekit values are under Developers, then API Credentials.

Retrieve the tools the user is authorized to call

Before any code, be clear about what list_scoped_tools returns. It is not the Grain MCP catalog. It is the set of tools the current user's connected account is authorized to call, filtered to the connection you name. For a user on a Starter plan that surface will not include deal or coaching tools that would fail anyway. Scoping the surface is the accuracy and cost lever: fewer tools in context means better tool selection and fewer tokens spent before the agent does any work.

""" Claude agent with Scalekit-authenticated Grain MCP tools. """ import os import anthropic from dotenv import load_dotenv from google.protobuf.json_format import MessageToDict from scalekit.client 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 claude = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"]) # Must match the Connection name in the Scalekit dashboard exactly. CONNECTION_NAME = "grainmcp" # Resolve from your authenticated session, never from client input. identifier = os.environ.get("USER_IDENTIFIER", "user_123") 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 Grain:", link.link) print("Re-run after completing the OAuth flow.") raise SystemExit(0) scoped_response, _ = actions.tools.list_scoped_tools( identifier=identifier, filter={"connection_names": [CONNECTION_NAME]}, ) llm_tools = [] for scoped in scoped_response.tools: definition = MessageToDict(scoped.tool).get("definition", {}) llm_tools.append( { "name": definition.get("name"), "description": definition.get("description", ""), "input_schema": definition.get("input_schema", {}), } ) print(f"{len(llm_tools)} Grain tools authorized for {identifier}")

Run the agent loop

The loop is the standard Claude tool-use pattern. Claude decides which Grain tool to call; your code executes it through execute_tool with the same identifier; the result goes back as a tool_result block. The loop ends when stop_reason is end_turn.

messages = [ { "role": "user", "content": ( "Search my external meetings from the last 30 days for pricing " "objections. For the three strongest examples, pull the meeting " "notes and summarize what the customer said and how we responded." ), } ] while True: response = claude.messages.create( model="claude-sonnet-4-6", max_tokens=2048, tools=llm_tools, messages=messages, ) if response.stop_reason == "end_turn": final_text = "".join( block.text for block in response.content if block.type == "text" ) print(final_text) break tool_results = [] for block in response.content: if block.type != "tool_use": continue result = actions.execute_tool( tool_name=block.name, identifier=identifier, tool_input=block.input, ) tool_results.append( { "type": "tool_result", "tool_use_id": block.id, "content": str(result.data), } ) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results})

Change USER_IDENTIFIER and the same script runs against a different rep's Grain workspace with that rep's permissions. No token handling changed.

Same pattern in TypeScript for the API connector

The API connector uses a bearer token instead of OAuth, but the code path is identical. When the connected account is not active, getAuthorizationLink returns a hosted page that collects the user's Personal Access Token instead of showing an OAuth consent screen. Scalekit picks the right flow from the connection type.

import 'dotenv/config'; import Anthropic from '@anthropic-ai/sdk'; import { ScalekitClient } from '@scalekit-sdk/node'; import { ConnectorStatus } from '@scalekit-sdk/node/lib/pkg/grpc/scalekit/v1/connected_accounts/connected_accounts_pb'; const scalekit = new ScalekitClient( process.env.SCALEKIT_ENVIRONMENT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET!, ); const claude = new Anthropic(); // Must match the Connection name in the Scalekit dashboard exactly. const CONNECTION_NAME = 'grain'; const identifier = process.env.USER_IDENTIFIER ?? 'user_123'; const account = await scalekit.actions.getOrCreateConnectedAccount({ connectionName: CONNECTION_NAME, identifier, }); if (account.connectedAccount?.status !== ConnectorStatus.ACTIVE) { const link = await scalekit.actions.getAuthorizationLink({ connectionName: CONNECTION_NAME, identifier, }); console.log('Connect Grain:', link.link); process.exit(0); } const { tools } = await scalekit.tools.listScopedTools(identifier, { filter: { connectionNames: [CONNECTION_NAME] }, pageSize: 50, }); const llmTools = tools.map((t) => ({ name: t.tool.definition.name, description: t.tool.definition.description, input_schema: t.tool.definition.input_schema, })); const messages: Anthropic.MessageParam[] = [ { role: 'user', content: 'List the webhooks registered on our Grain workspace. If there is no ' + 'recording_added hook pointing at https://ops.example.com/grain, create one.', }, ]; while (true) { const response = await claude.messages.create({ model: 'claude-sonnet-4-6', max_tokens: 1024, tools: llmTools, messages, }); if (response.stop_reason === 'end_turn') { for (const block of response.content) { if (block.type === 'text') console.log(block.text); } break; } const toolResults: Anthropic.ToolResultBlockParam[] = []; for (const block of response.content) { if (block.type !== 'tool_use') continue; const { data } = await scalekit.tools.executeTool({ toolName: block.name, identifier, params: block.input as Record, }); toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: JSON.stringify(data), }); } messages.push({ role: 'assistant', content: response.content }); messages.push({ role: 'user', content: toolResults }); }

The tools this agent sees are the 19 API tools such as grain_hooks_list, grain_hook_create, grain_recordings_list, and grain_recording_transcript_text_get. Full schemas are on the Grain connector docs page.

Drop the tools into LangChain

If you are on LangChain, skip the schema conversion. The Python SDK's adapter returns StructuredTool objects already bound to the user's connected account, and execution routes through Scalekit the same way.

from langchain_anthropic import ChatAnthropic from langgraph.prebuilt import create_react_agent tools = scalekit_client.actions.langchain.get_tools( identifier=identifier, connection_names=["grainmcp"], ) agent = create_react_agent( model=ChatAnthropic(model="claude-sonnet-4-6"), tools=tools, ) result = agent.invoke( {"messages": [("user", "Which open deals had no meeting in the last two weeks?")]} ) print(result["messages"][-1].content)

Google ADK, Vercel AI SDK, and Mastra examples follow the same shape and live in the code samples section of the docs.

Why route Grain through Scalekit

The comparison above is honest about the fact that Grain's two surfaces will keep drifting. The argument for Scalekit is that the drift stops being your problem at the auth and observability layer.

Auth logs for every downstream call

Every execute_tool call against either Grain connector is logged with the user who authorized it, the connection, the tool name, and the outcome. When a CISO asks which agent pulled a transcript from a board meeting and under whose grant, the answer is a query, not a three-week log archaeology project. The failure taxonomy that logging needs to support is laid out in audit trails for agent auth; the product surface is the Auth Logs dashboard.

Virtual MCP Servers for multi-tool, multi-tenant agents

A meeting-prep agent rarely stops at Grain. It reads the calendar, checks the CRM, and posts to Slack. Handing it the full Grain MCP server plus the full Slack server plus the full HubSpot server is over a hundred tools in context, most of which it should never be allowed to call. Virtual MCP Servers solve this: you define one server per agent role declaring exactly which connections and which tools it exposes, and you get a static mcp_server_url.

Session tokens for per-user isolation

Per-user isolation is handled by session tokens. Before each run you call create_session_token for that user, which mints a short-lived bearer scoped to that user's connected accounts with a default expiry of about one hour. One server definition serves every tenant; the identity is per run. A Grain summarizer might expose five tools out of 41. The agent cannot call grainmcp_update_my_settings because it was never given it. Setup is in set up and connect, and the design rationale is in when to use a Virtual MCP Server.

One credential model for both connectors

The rep who connects Grain MCP today for search and Grain API tomorrow for webhook-driven archival has two credentials in Grain's eyes and one connected account identity in yours. Scope is a function of identity, not connector configuration. Both connectors are included on the free tier, which covers 10,000 connected accounts. For adjacent build patterns see the sales call prep agent and the deal intelligence agent templates.

Which one to build against

If your agent's job is to find and reason about what was said across many meetings, or to surface deal risk and coaching signals, build on Grain MCP. The semantic search and the intelligence tools do not exist anywhere else, and the OAuth grant gives you correct per-user scoping by default.

API for moving data

If your agent's job is to move recordings, transcripts, and media in or out of Grain, or to react when Grain produces something new, build on the Grain API. Hooks, uploads, downloads, and recording-level administration are API only, and the date-versioned contract is the safer dependency for a pipeline that runs unattended.

Most agents need both

Most production Grain agents end up touching both: MCP for the interactive intelligence layer, API for the event-driven plumbing underneath it. That means two credential types per user on day one. The credential management problem is the same on both paths, and that is what needs production-grade infrastructure rather than another table in your database.

Browse the Grain connector docs, or see all connectors in the AgentKit documentation.

Talk to the people building Grain agents

If you are wiring Grain into a production agent and hit a question this article did not answer, two places will get you an engineer quickly. Join the Scalekit Slack community to compare notes with other teams building on Grain, Granola, Gong, and Fathom. Or, if you need immediate help with a multi-tenant design or a Virtual MCP Server layout, use the talk to us page and pick a time.

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.