Skip to main content

MCP — connect an AI assistant

Status: Public preview. Tools, resources, and prompts are stable; new entries land additively and are announced in this documentation.
The Scripe MCP (Model Context Protocol) server lets MCP-compatible hosts — Claude, ChatGPT, Cursor, agent frameworks — read and write Scripe workspace data on behalf of an authenticated user. It exposes the same capabilities as the REST API plus the irreversible verbs REST deliberately lacks (publish to LinkedIn now, permanent deletion, settings writes), each gated behind a two-phase confirmation that hands your host an exact preview of what will happen before anything executes. This page gets a host connected and explains the concepts every integration needs (auth, workspaces, projects, limits). The full tool catalogue and the contracts behind it live on MCP tools & contracts.

TL;DR

In Claude, ChatGPT, or Cursor, add a remote MCP server with the https://mcp.scripe.io/mcp URL and approve the consent screen. That’s the whole setup.

1. Endpoints and discovery

Both transports serve the same server instance — same tools, same resources, same prompts. New integrations should prefer Streamable HTTP; the bare host root (https://mcp.scripe.io/) also answers Streamable HTTP for clients that probe the resource URL directly. Discovery is RFC 9728 protected-resource metadata:
It names the resource (https://mcp.scripe.io), the authorization server, and the supported scope vocabulary (the same closed scope list as the rest of the API). Hosts that follow the spec discover the authorization server automatically; for older hosts, paste the URLs manually.

2. Authentication

OAuth 2.1 only. The MCP transport accepts OAuth access tokens (scripe_oat_*) exclusively — scripe_sk_* API keys are a REST-only credential and fail the auth challenge here. You don’t need to build anything for the common case: every major MCP host registers itself via Dynamic Client Registration, sends the user through the Scripe consent screen, and stores the resulting tokens. A 401 with WWW-Authenticate: Bearer … resource_metadata=… is the signal that makes hosts (re)run the flow. Two things happen on the consent screen that matter later:
  • The user picks the workspace the grant is pinned to (its default workspace).
  • The user can pick a default project for the grant (auto-selected when the workspace has exactly one). See §2.2 for which tools fall back to it.
Both are editable later from Settings → Developer → Connected apps. The token’s scopes gate every tool call. A tool whose scope wasn’t granted stays listed but returns a scope_missing teaching error when called — re-consent with the wider scope set to unlock it. The sensitive scopes (posts:publish, settings:write, *:destroy) must be requested by name; the consent screen then lists them under Full access — the user’s one choice is Read-only vs Full access, there are no per-scope toggles. See Scopes. For headless automation that genuinely cannot complete an interactive consent, use the REST API with an API key instead.

2.1 Session-resumable but token-bound

The server stores session state in Redis keyed off the sha256 of the access token. If the token rotates (refresh), the old session is automatically retired and a new one is created on the next request. Hosts do not need to do anything special: just keep presenting the freshest access token they have.

2.2 The default project, and which tools fall back to it

With a default project pinned at consent, most tools let the model omit projectId and the server fills it in — which is what lets a prompt like “draft me a LinkedIn post about X” work without a discovery round trip.
  • projectId is optional on 32 tools: the seven idea tools (attach_media_to_idea included) and generate_post_ideas, the content writes (create_note, generate_post, create_post_draft, create_source_text, create_source_file, add_to_knowledge_base, create_media_asset), and the project-scoped reads (list_posts, list_notes, list_sources, list_knowledge, get_knowledge_doc, get_project, get_settings, get_engagement_policy, get_usage, list_post_statuses, search_media, get_personal_dna, list_profile_lists, and the single-workspace analytics tools). Pass it explicitly to override the default.
  • Two tools take no projectId at all: get_cross_workspace_analytics_overview and get_cross_workspace_analytics_report aggregate every project the caller can read in each workspace, so a project id would be a workspace-scoped id at a workspace-crossing call — they reject it.
  • Eleven tools still require it: the calendar family (list_calendar, get_next_free_slot, get_posting_times, update_posting_schedule), the irreversible or admin-gated writes (publish_post, update_tone_of_voice, update_engagement_policy, update_personal_dna, delete_knowledge_doc), and the spend-capped generators (generate_image, generate_carousel). Omitting it there is a schema rejection before the call runs, never a write to the default project.
  • If the default is unset and the argument is omitted on a tool that allows it, the tool returns invalid_request with a hint to either pass projectId or set a default at consent time.
  • The active default is surfaced on get_workspace / scripe://workspace/me as defaultProject: { id, name } | null, and the server instructs the model to echo the project name when it acts on the default (“Using the Marketing project — pass projectId to override.”).
  • Only the idea family, get_personal_dna and list_profile_lists echo the resolution: every such response — including delete_idea’s preview — carries project: { id, name, fromDefault }. When fromDefault is true the model is told to name the project back to the user, because the idea board is more visible than a note and landing on the wrong one should be obvious and correctable. name is the display name the dashboard shows; it is null only when none resolves.
  • The REST endpoints are unchanged: projectId stays required there, since an HTTP caller has no connection-pinned default.

2.3 When the connection stops working

Every auth failure answers with the cause and the repair, because they need different repairs and only one of them is “re-authorize”. The last two are the ones worth handling explicitly: neither is fixed by re-authorizing, and both used to answer 401 — which makes a host throw away a working token and send the user back through consent for a problem consent cannot solve. The body is the OAuth shape (RFC 6749 §5.2) with Scripe’s code beside it, and the WWW-Authenticate challenge always keeps its resource_metadata pointer:

3. Workspaces and projects

Every tool call runs against a single active workspace.
  • Default. The active workspace starts as the one pinned at consent.
  • The model can switch. switch_workspace({ workspaceId }) moves the active workspace to any workspace the consenting user can reach, mid-conversation — list_workspaces returns the reachable set with ids, an isDefault marker and a reach marker. Switching to a workspace they cannot reach fails with workspace_unavailable. The semantics of the switch are below.
  • Hosts can pin. A Scripe-Workspace-Id header on the MCP connection selects a workspace for the whole connection — useful in hosts like Cursor where an agency runs one MCP entry per client workspace off the same login. A switch_workspace call takes precedence over the header.
  • Two grants, checked live. Reach is the user’s current Clerk workspace membership (60-second cache) or the billing-based agency-owner grant: the client workspaces an agency they own pays for, which is what the Scripe dashboard has always honoured. Both are re-checked on every request, so a new client workspace becomes reachable immediately with no re-consent, and a revoked membership stops access within ~60 seconds. list_workspaces marks each entry reach: "member" | "agency_owner", and inside a workspace reached by the agency grant the owner is treated as an admin — the same rights the dashboard gives them. See Workspaces § Two ways a workspace is reachable.
  • Plan follows the default workspace. Gating inherits the pinned workspace’s plan, so client workspaces on cheaper plans still work under an agency’s API-enabled grant. The pinned workspace’s plan is the plan of whoever pays for it, so a client workspace an agency manages carries the agency’s plan even when it is the pinned one — how it is resolved, and which workspace a plan_not_eligible names, is on plan_not_eligible.
The switch is connection state, not conversation state. Three consequences worth designing around:
  • It outlives the conversation that made it — up to 12 hours, or until something switches back. A later conversation on the same connection inherits it. The switch_workspace response carries expiresInSeconds, defaultWorkspaceId (to undo), and defaultProjectCleared.
  • While the connection is off its default workspace, every tool result carries a second text block naming the active workspace and how to switch back. It rides on failures too — a not_found for an id the user just named is what an inherited switch looks like from the model’s side. The notice is a content block only; structuredContent is unchanged, so it never breaks a typed client.
  • The pinned default project is cleared while switched (it belongs to the workspace you left), so pass projectId explicitly. Omitting it fails with an invalid_request that names the active workspace and tells you to call list_projects.
Switching back to defaultWorkspaceId drops the override immediately and restores the default project. Analytics is the exception to one-workspace-per-call. get_cross_workspace_analytics_overview (and its report twin) fan out over every reachable workspace in a single call, attributing each row to the workspace it came from — so “how are all my clients doing” does not need a switch at all. See MCP tools § 1.17. Everything else stays single-workspace by design. The server’s instructions tell the model to announce the workspace and project it acts on, so users can correct a wrong guess before more than one thing lands in the wrong place.
REST API keys stay single-workspace. On the REST API, a Scripe-Workspace-Id header that disagrees with the key’s workspace is rejected with workspace_unavailable. Multi-workspace access requires an OAuth token — which is all the MCP transport accepts anyway.

4. What the model can and cannot do

The short version of the tool contracts, across the 76 tools:
  • Reads are free. 39 tools advertise readOnlyHint: true, across workspaces, projects, notes, posts, ideas, calendar, sources, knowledge, media, analytics, settings, positioning, community profile lists, usage, and jobs — including draft_positioning_from_website, which crawls a site and drafts text but persists nothing.
  • Writes are scoped and metered. 37 write tools (including the data-neutral switch_workspace) mirror the REST writes plus the MCP-only verbs. Job-creating tools stream progress.
  • Engaging with another person’s post is not on this API, by decision. add_profile_to_list puts somebody on a watchlist so Scripe collects their posts; liking, commenting and reposting stay an explicit human click in the Scripe dashboard, and the data model admits nothing else. See §2.16.
  • Per-post auto-engagement mirrors the post editor. get_post_engagements reads a post’s queued likes/comments/reposts plus the actors it could engage from; update_post_engagements composes that timeline under the editor’s own limits (Advanced+ plan, same-workspace LinkedIn-connected actors with a non-OFF policy for the action, the editor’s content and timing bounds). Composing is not consent: each acting brand’s engagement policy resolves fail-closed when the post is scheduled, and granting a REQUEST approval is not available on this API — approvals happen in the Scripe dashboard or the mobile app. The write is single-phase, and removing a row is irreversible: it also deletes the tracked links bound to that row and their click history. Read the timeline with get_post_engagements before changing it.
  • Irreversible actions are two-phase. publish_post, schedule_post, delete_post, delete_idea, delete_knowledge_doc, delete_source, delete_media_asset, permanent delete_note, update_tone_of_voice, update_engagement_policy, and update_posting_schedule never execute on the first call: they return a proposal and a confirmation token (~5-minute TTL), and only a second call replaying that token executes. The positioning writes (update_positioning, update_global_tone_of_voice, update_personal_dna) are conditionally two-phase: filling an empty field lands directly, replacing text a person already wrote confirms. What the server enforces is the binding: the token is tied to the action, the acting user, the subject, and the proposed change — confirming with different arguments than you proposed fails. publish_post alone also binds a hash of the post’s content, media, and engagement flags, so only there does editing the subject between the two calls invalidate the token.
  • Two-phase is not a human gate on MCP. Both calls are model-initiated — the host receives the token from phase one and can replay it immediately, and the server cannot tell an approved replay from an auto-approved one. What two-phase buys here is argument-drift protection (plus content-drift for publish_post), the TTL, and an audit trail; it is not evidence that a person saw the action. Surfacing the proposal and getting an explicit yes is your host’s obligation (see mcp-tools.md §2.2). The real consent boundary on MCP is the OAuth scope: posts:publish, settings:write and the *:destroy scopes must each be granted by name, are implied by no alias, and cannot be held by an API key.
  • Some things stay in the dashboard. Turning autonomous engagement fully ON, reshare publishing, review decisions (approving or rejecting a post), reconnecting LinkedIn, and project creation are deliberately not available from any agent surface.

5. Limits and quotas

  • Rate limits — the same per-credential buckets as REST, keyed by the OAuth access token: reads 120/min, writes 30/min, and the job bucket 10/min — the job-creating tools (generate_post, generate_image, generate_carousel, create_source_file, add_to_knowledge_base) plus two that create no job but run a model synchronously: draft_positioning_from_website (which also crawls a site) and generate_post_ideas. The two cross-workspace analytics tools (get_cross_workspace_analytics_overview, get_cross_workspace_analytics_report) draw from their own tighter analytics_fanout bucket, also 10/min — each call fans out per reachable workspace and costs roughly 20x (overview) or 8x (report) a plain read’s database work. Exceeding a bucket returns rate_limited.
  • Agent publishes — 5 per user per rolling 24 hours, counted across every project and both agent surfaces (MCP and the in-app chat). Dashboard, editor, and scheduled publishes do not count. See publish_post.
  • AI budget — job-creating tools draw from the same weekly usage limit and daily spend cap as the REST job endpoints (Jobs § AI budget); exhaustion returns usage_limit_exceeded / spend_cap_exceeded. get_usage reports both meters and a canGenerate verdict before either fires — check it once before a batch.
  • Sessions — 100 active per workspace, oldest evicted first; idle sessions retire after 30 minutes (resuming is invisible to the host — see §2.1).
  • Progress notifications — capped at 60 per request, at most one per ~750 ms.
  • Streamed body cap — 8 MB per response; larger results return payload_too_large. Use the paginated list_* tools rather than dumping everything at once.
  • Plan — the API (REST and MCP alike) is gated on the plan’s apiAccess entitlement: Advanced and Business carry it, as do Enterprise, the grandfathered Agency tier, and admin-assigned Trial workspaces. Everything else is rejected with 403 plan_not_eligible. Some tools carry their own plan gate on top and return plan_not_eligible with an upgrade pointer rather than being hidden.

6. Host setup walkthrough

Claude (Desktop or claude.ai): Settings → Connectors → Add custom connector, URL https://mcp.scripe.io/mcp, then approve the consent screen in the browser tab that opens. The connector turns green and the Scripe tools, resources, and prompts appear. ChatGPT: add the same URL in the connector settings (Streamable HTTP). Cursor: add a remote MCP server with the same URL; older Cursor builds fall back to the SSE endpoint automatically. For the one-connection-per-workspace agency pattern, add multiple entries with a Scripe-Workspace-Id header each. Anything else that speaks MCP: point it at the Streamable HTTP URL; the OAuth flow is standard DCR + PKCE, so no Scripe-specific host code is needed. Try it with a prompt like “What’s on my Scripe calendar next week?” — the model will call list_calendar and answer from the result. The slash-command prompts (weekly_planning, repurpose, inbox_zero) are listed in MCP tools § Prompts.

7. Operational notes

  • Tool payloads are versionless — REST’s Scripe-Api-Version pin and its no-breaking-changes promise do not extend here; MCP takes no version header, so tool result shapes may change between releases. Read them dynamically rather than compiling against them. See Conventions § Versioning.
  • Audit log — every tool call appears in the workspace audit log, on success and on failure, recorded as method MCP with path mcp/<tool_name>, the OAuth client, the acting user, and the resulting status code.
  • Email notifications — the user receives an email when a new MCP consent is granted.
  • Revocation — users disconnect from Settings → Developer → Connected apps; revoking the consent kills the tokens and with them every session.
  • Status pagestatus.scripe.io surfaces the MCP endpoint and the OAuth server as separate components.

Need help?