Scripe relaunch is live.See what's new

API v1 · MCP — agents

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

text
Streamable HTTP endpoint:  https://mcp.scripe.io/mcp
Legacy SSE endpoint:       https://mcp.scripe.io/sse  (companion POST: /message)
Discovery:                 https://mcp.scripe.io/.well-known/oauth-protected-resource
Auth:                      OAuth 2.1 only — the host runs the flow for you

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.

The server lists the planning tools by name and groups every other operation into compact domain tools (posts_read, ideas_write, …) whose action names the operation, plus describe_actions for an operation's full documentation — see §1.1.


1. Endpoints and discovery

https://mcp.scripe.io/mcp        # Streamable HTTP (preferred)
https://mcp.scripe.io/sse        # Legacy SSE GET — older hosts
https://mcp.scripe.io/message    # Legacy SSE POST companion (don't call directly)

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:

http
GET https://mcp.scripe.io/.well-known/oauth-protected-resource

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.

1.1 The tool catalogue

A host that loads every tool definition up front pays for them on every step of a conversation, so the server does not list one tool per operation. It lists:

ToolCarries
the planning tools, by name (get_planning_overview, preview_plan, commit_plan, …)one planning operation each, with its full schema — the planning loop is the path most requests start on
<domain>_reada domain's read-only operations (readOnlyHint: true)
<domain>_writeits operations that create or change data
<domain>_deleteits operations that delete or replace data wholesale (destructiveHint: true)
describe_actionsthe full description and JSON input schema of any operation

The domains are workspace, posts, ideas, calendar, knowledge, media, analytics, brand, organize, community and links; the generated MCP tool catalogue lists every tool and the operations it carries. A domain tool takes the operation as action and its arguments in arguments:

json
{ "name": "posts_write",
  "arguments": { "action": "create_post_draft",
                 "arguments": { "content": "Shipping our connector taught me…" } } }

Each domain tool's description lists its operations as one-line signatures. An operation called with arguments its schema refuses comes back as an error that carries the schema, and an operation called on the wrong tool names the tool that carries it — nothing runs in either case.

An operation behaves exactly like the tool it used to be. The action values are the tool names this server has always used, and an operation runs the same handler under its own name: the same scopes, rate-limit bucket, audit trail, plan and role checks and two-phase confirmation. MCP tools & contracts documents each one under that name.

Which catalogue a connection gets. The grouped catalogue is for hosts that load every tool definition into the conversation up front — Claude.ai, ChatGPT and most clients — where the flat list of 132 tools was re-sent on every step. Claude Code instead defers tool definitions and lets the model pick tools by NAME, which a flat name like get_posting_times serves better than calendar_read; so a request from Claude Code (its User-Agent starts with claude-code/) is served the flat catalogue, one tool per operation, exactly as before. Either catalogue can be chosen explicitly with a catalog parameter on the server URL:

text
https://mcp.scripe.io/mcp?catalog=classic   # one tool per operation
https://mcp.scripe.io/mcp?catalog=lean      # planning tools + domain tools

Every former tool name stays callable. On the grouped catalogue, tools/call with list_posts, create_idea or any other former tool name still runs that operation, though it is no longer listed — a script or automation that hard-codes a tool name keeps working. Nothing is deprecated: both catalogues serve the same operations under the same names, and the flat one stays the default for tool-search hosts.


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 51 tools: the seven idea tools (attach_media_to_idea included), generate_post_ideas and the eight content-question tools, list_posts_with_label (where it only narrows to one profile), list_calendar_slots and fill_calendar_slot, list_for_you_ideas and get_goals (where OMITTING it reads every profile of the workspace you can access), save_for_you_idea (where omitting it saves to the profile that generated the idea), the content writes (create_note, generate_post, create_post_draft, create_source_text, create_source_file, add_to_knowledge_base, create_media_asset), get_icp (where a projectId only adds that profile's view beside the workspace's Ideal customer, so it has no default to fall back to), 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), create_tracked_link (where it only records which profile a link is for, so nothing falls back to the default; with a postId the post's own profile is used), and three planning reads — get_profile_planning_context, search_knowledge and read_knowledge_document (a folder- or document-narrowed knowledge read still reaches every profile you can reach). Pass it explicitly to override the default.
  • The workspace-wide planning tools take profiles, not a project: get_planning_overview and get_planning_signals read every profile you can reach unless you name profileIds, and preview_plan and check_plan_ideas take a REQUIRED projectId on each plan item rather than at the top level, so one plan can span every profile without any default (MCP tools §2.19).
  • 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 (its content questions included), list_calendar_slots, fill_calendar_slot, get_personal_dna and list_profile_lists echo the resolution (get_profile_planning_context names the profile it read in its own shape): 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".

What happenedHTTPcodeWhat to do
No Authorization header401unauthenticatedRun the OAuth flow; the authorization server is behind the resource_metadata URL in the challenge
Not a scripe_oat_ token (e.g. an API key)401invalid_tokenPresent an OAuth access token; API keys are REST-only
Access token expired401invalid_tokenRefresh it
Access token revoked (disconnected, or a refresh token was presented twice and the family was revoked)401invalid_tokenRe-run the authorization flow — refreshing will fail too
The user revoked the app in Settings → Connected apps401consent_requiredAsk the user to authorize again
The OAuth client is suspended401client_suspendedRe-authorizing will not help — contact support
Scripe-Workspace-Id names a workspace the user is not in400workspace_unavailableDrop the header, or pick an id from list_workspaces
Scripe could not verify the token (dependency failure)500internal_errorRetry. The token was not rejected — do not discard it

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:

jsonc
{
  "error": "invalid_token",              // RFC 6750 code
  "error_description": "This access token expired at 2026-08-14T01:02:03.000Z. Get a fresh access token: refresh with your refresh token, or re-run the OAuth authorization flow if the refresh also fails.",
  "error_uri": "https://scripe.io/developers/api/v1/errors#invalid_token",
  "code": "invalid_token",               // Scripe error code — same vocabulary as REST
  "request_id": "req_1Tl0TqNidPIiZZlhE"  // quote this in support requests
}

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. The few plan-gated tools judge the pinned workspace's plan, which 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 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 132 operations (§1.1 — each was a tool of its own before the domain catalogue, and keeps its name as an action):

  • Reads are free. 66 tools advertise readOnlyHint: true, across workspaces, projects, notes, posts, labels, campaigns, ideas and their content questions, "For you" suggested ideas, goals, calendar and its planned slots, the planning overview, per-profile planning context and planning signals, a draft plan's quality check, sources, knowledge (searched by meaning and read in full), media, analytics, settings, positioning, community profile lists, the anonymised comments under your own posts, what waits for your review, draft runs, the Ideal customer, tracked links and their clicks, usage, and jobs — including draft_positioning_from_website, which crawls a site and drafts text but persists nothing, and preview_plan, which validates a plan and stores it to be committed but writes nothing to the calendar.
  • Writes are scoped and metered. 66 write tools (including the data-neutral switch_workspace) mirror the REST writes plus the MCP-only verbs. Job-creating tools stream progress.
  • The planning autopilot plans; Scripe writes the posts. get_planning_overview → get_profile_planning_context + get_planning_signals → preview_plan → commit_plan → draft_planned_posts → get_draft_run plan a whole workspace's period in a handful of calls. The agent writes the ideas on its own tokens; the posts are written by Scripe's own pipeline by default (hooks in the post's lane, the profile's voice, the voice pass), in the background on the workspace's AI budget, with mode: "agent_text" for text the user asked the agent to write. teamEngagement: true, or set_up_team_engagement on any posts, adds the post editor's standard team engagement. Before the review request the agent finishes each draft: add_post_media puts the visual the data picks on it, check_post_readiness says what still fails and tag_post_mentions turns names into LinkedIn tags only where Scripe is sure who they are. Questions only the person can answer go out in ONE send_content_questions batch, and request_review sends the finished posts in one notification per reviewer. Those three are two-phase: the user sees the proposal and nothing reaches a person or changes a post's media before their yes, autopilot or not (captain, 2026-10-10). The planning writes (preview_plan, add_post_media and tag_post_mentions included) are open to every customer on every plan that has MCP (2026-10-16); only request_review still needs a plan with content approvals. See MCP tools §2.19.
  • The Ideal customer is a pilot. The two tools of §2.20 stay listed everywhere but answer only in a workspace inside Scripe's Signals pilot; elsewhere a call returns a teaching error that says so. The people who engage with a profile's posts are never on this API.
  • Comments on your own posts arrive anonymised. list_post_comments returns the comments and replies under one of the user's own posts — the text, the date, the threading and whether it was written from the user's workspace — so the model can analyse what the audience said. It never says who commented: no name, headline, profile link or id, and a person tagged in a comment is redacted, by the spans of a live LinkedIn read on every call (a comment that read did not return is counted, not sent). See §2.22.
  • Engaging with another person's post is not on this API, by decision. add_profile_to_list and add_profiles_to_list put people 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 team 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), and set_up_team_engagement adds the editor's "Generate engagements" set in one call, each teammate's comment written in their own voice. 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.
  • Scheduling is one call. schedule_post schedules on the first call and the post goes live on LinkedIn by itself; its notice tells the user so, and update_post with scheduledFor: null undoes it.
  • Irreversible actions are two-phase. publish_post, delete_post, delete_idea, delete_knowledge_doc, delete_source, delete_media_asset, delete_label, delete_campaign, delete_content_question, sending content questions (whatever when says), request_review, add_post_media, 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 also binds a hash of the post's content, media, and engagement flags, the content-question sends a hash of each question's wording and the people it reaches, and add_post_media the visuals it decided, so changing those between the two calls invalidates 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 content-question sends and add_post_media), 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 — per-credential buckets keyed by the OAuth access token, counted apart from REST's: reads 120/min, writes 120/min (REST: 30), the profile-list additions 30/min (REST: 5; an add_profiles_to_list call is one unit however many profiles it carries), 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. The planning writes commit_plan, draft_planned_posts and request_review cost ONE write unit per call however many items they carry. Exceeding a bucket returns rate_limited with retry_after, the seconds to wait before calling again.
  • 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 (the planning autopilot's tools draw from neither) (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 available on every plan, Free and every grandfathered tier included. A few tools whose product feature is plan-gated 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.

Hermes Agent: add the server to ~/.hermes/config.yaml with url: "https://mcp.scripe.io/mcp" and auth: oauth, then run hermes mcp login scripe.

Claude Code: /plugin marketplace add Scripe-GmbH/scripe-agent, then /plugin install scripe@scripe-agent, installs the Scripe skill and this server together.

Gemini CLI: gemini extensions install https://github.com/Scripe-GmbH/scripe-agent.

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.

The Scripe agent skill. An Agent Skill that teaches an agent how to plan, write, schedule and measure with these tools is published at github.com/Scripe-GmbH/scripe-agent: npx skills add Scripe-GmbH/scripe-agent for any skills-compatible agent, or hermes skills install well-known:https://scripe.io/.well-known/skills/scripe for Hermes. It does not replace the server; connect both.

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 (plan_content, fill_calendar, 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 page — status.scripe.io surfaces the MCP endpoint and the OAuth server as separate components.

Need help?