MCP

MCP server

ShipMyForm is an AI-native form backend: everything the dashboard can do is a tool on our MCP server, and a few things the dashboard cannot. Point Claude Code, Cursor, VS Code, Codex, Gemini CLI, or any MCP-capable agent at the endpoint and it can create the account, verify it, wire forms and connectors, pick a plan, and read, classify and export submissions, without opening a browser once.

No browser needed

An agent with no key can call sign_upwith the user's email. The address receives a 6-digit code, and verify_account with that code returns the API key. From there the agent can create forms, paste snippets into the site, add Slack or webhook delivery, publish a hosted page, and hand the user a checkout link from start_upgradewhen they want a paid plan. If the agent can read the user's inbox (a Gmail connector, say), the whole thing runs without a single click.

The code is the only thing that ever produces a key, so an address you do not control cannot be signed up on your behalf. It works for an existing account too: the email says an agent is asking for API access, and the code approves it, exactly like creating a key in Settings. Nothing is emailed to a new address before the code is used.

Connect

The server is remote (Streamable HTTP), so there is nothing to install. Get an API key either by letting your agent call sign_up, or in Settings → API keys (every plan, including free). Every client needs the same three facts:

URLhttps://shipmyform.com/api/mcp
TransportStreamable HTTP (not SSE, not stdio)
Auth headerAuthorization: Bearer smf_YOUR_KEY
Keep the key out of your repo: Several clients read a config file inside the project. Where the client supports it, the examples below pull the key from an environment variable (SMF_API_KEY) instead of pasting it into a file you might commit. Keys can be revoked anytime in Settings.

Claude Code

One command, stored in your user config:

code
claude mcp add --transport http shipmyform https://shipmyform.com/api/mcp \
  --header "Authorization: Bearer smf_YOUR_KEY"

Or share it with your team through a project .mcp.json, which expands environment variables:

.mcp.json
{
  "mcpServers": {
    "shipmyform": {
      "type": "http",
      "url": "https://shipmyform.com/api/mcp",
      "headers": { "Authorization": "Bearer ${SMF_API_KEY}" }
    }
  }
}

Claude Desktop and claude.ai

The “Add custom connector” dialog is built for OAuth and has no field for an API-key header, so on Claude Desktop use the mcp-remote bridge (needs Node.js). Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\) and restart the app:

claude_desktop_config.json
{
  "mcpServers": {
    "shipmyform": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://shipmyform.com/api/mcp",
        "--header",
        "Authorization:${AUTH_HEADER}"
      ],
      "env": { "AUTH_HEADER": "Bearer smf_YOUR_KEY" }
    }
  }
}

The header is written without a space after the colon on purpose: some clients mangle spaces inside args, so the value with the space lives in env. On Team and Enterprise plans, an organization admin can instead add ShipMyForm as a custom connector with a request header (authorization), which Anthropic currently offers in beta.

Cursor

~/.cursor/mcp.json for every project, or .cursor/mcp.json in one:

mcp.json
{
  "mcpServers": {
    "shipmyform": {
      "url": "https://shipmyform.com/api/mcp",
      "headers": { "Authorization": "Bearer ${env:SMF_API_KEY}" }
    }
  }
}

VS Code (GitHub Copilot)

.vscode/mcp.json in the workspace, or run MCP: Open User Configuration for a global entry. Note the top-level key is servers, and the input variable makes VS Code prompt for the key once and store it securely:

.vscode/mcp.json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "shipmyform-key",
      "description": "ShipMyForm API key (smf_…)",
      "password": true
    }
  ],
  "servers": {
    "shipmyform": {
      "type": "http",
      "url": "https://shipmyform.com/api/mcp",
      "headers": { "Authorization": "Bearer ${input:shipmyform-key}" }
    }
  }
}

Windsurf

~/.codeium/windsurf/mcp_config.json. Windsurf spells the URL key serverUrl:

mcp_config.json
{
  "mcpServers": {
    "shipmyform": {
      "serverUrl": "https://shipmyform.com/api/mcp",
      "headers": { "Authorization": "Bearer ${env:SMF_API_KEY}" }
    }
  }
}

OpenAI Codex CLI

~/.codex/config.toml (or a project .codex/config.toml). Codex reads the bearer token from an environment variable you name:

config.toml
[mcp_servers.shipmyform]
url = "https://shipmyform.com/api/mcp"
bearer_token_env_var = "SMF_API_KEY"

Gemini CLI

code
gemini mcp add --transport http \
  --header "Authorization: Bearer smf_YOUR_KEY" \
  shipmyform https://shipmyform.com/api/mcp

Or in ~/.gemini/settings.json. Use httpUrl; Gemini reserves url for SSE servers:

settings.json
{
  "mcpServers": {
    "shipmyform": {
      "httpUrl": "https://shipmyform.com/api/mcp",
      "headers": { "Authorization": "Bearer smf_YOUR_KEY" }
    }
  }
}

Cline

MCP Servers → Remote Servers → choose Streamable HTTP, or edit cline_mcp_settings.json. Set the type explicitly; without it Cline assumes legacy SSE:

cline_mcp_settings.json
{
  "mcpServers": {
    "shipmyform": {
      "type": "streamableHttp",
      "url": "https://shipmyform.com/api/mcp",
      "headers": { "Authorization": "Bearer smf_YOUR_KEY" }
    }
  }
}

Zed

Settings → AI → MCP Servers, or settings.json:

settings.json
{
  "context_servers": {
    "shipmyform": {
      "url": "https://shipmyform.com/api/mcp",
      "headers": { "Authorization": "Bearer smf_YOUR_KEY" }
    }
  }
}

opencode

opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "shipmyform": {
      "type": "remote",
      "url": "https://shipmyform.com/api/mcp",
      "enabled": true,
      "headers": { "Authorization": "Bearer {env:SMF_API_KEY}" }
    }
  }
}

JetBrains AI Assistant and Junie

Settings → Tools → AI Assistant → Model Context Protocol (MCP). The remote-server form takes a URL but no headers, so add ShipMyForm as a command-based server through the same mcp-remote bridge shown under Claude Desktop; the JSON is identical.

Any other client

If your client supports remote Streamable HTTP servers with custom headers, give it the URL and the Authorization header from the table above. If it only supports stdio servers, or only OAuth for remote ones, use the mcp-remotebridge. Browser-only assistants that can do neither, which as far as we know includes ChatGPT's connector settings today, can't connect yet because the server authenticates with an API key and does not offer OAuth.

Check the connection

Ask your agent to “list my ShipMyForm forms”. It should call list_forms. To test without an agent:

code
curl -s https://shipmyform.com/api/mcp \
  -H "Authorization: Bearer $SMF_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

A 401 means the key is wrong, revoked, or rate-limited. A client that connects but shows no tools is usually using SSE instead of Streamable HTTP.

Tools

ToolWhat it doesPlan
sign_upStart an account from an email address; emails a 6-digit code (no auth needed)—
verify_accountProve the email with the code; returns the API key when called without one—
get_accountEmail, verification state, plan, limits, usageAll plans
create_api_key / list_api_keys / revoke_api_keyManage keysAll plans
start_upgradeCheckout URL for Starter or Pro; plan activates after paymentAll plans
get_billing_portalSelf-service billing portal linkPaid
create_formCreate a form, get its endpoint + HTML snippet; emails the owner by defaultAll plans
list_forms / get_formForms with endpoints; full settings, connectors, hosted pageAll plans
update_formName, purpose, allowed domains, redirect, spam mode, validation rules, blocklist, auto-responderAll plans (auto-responder: Pro)
delete_formDelete a formAll plans
get_form_snippetSnippet for html / react / vue / fetchAll plans
get_submission_statsDelivered / spam-held counts + quota usageAll plans
list_connector_typesEvery destination with its config fields and planAll plans
add_connector / test_connector / set_connector_active / remove_connectorRoute submissions to email, webhooks, Slack, Discord, Airtable, HubSpot, Telegram, …Per connector
publish_hosted_page / unpublish_hosted_pageA standalone form page on shipmyform.com, plus an embed scriptAll plans
list_submissionsRead submissions with filters: form, status, dates, search, country, attachments, failed deliveriesStarter and up
get_submissionOne submission with spam signals and delivery historyStarter and up
label_submissionsMark spam / ok / flagged; spam and ok also train the classifierStarter and up
delete_submissionsDelete submissionsStarter and up
export_submissionsCSV of a form's submissionsStarter and up

Scopes: give an agent only what it needs

Every key carries a scope set, enforced on both this server and the REST API. A key can never grant scopes it does not itself hold, so an agent cannot widen its own access — handing one a narrow key is a real limit, not a suggestion.

PresetScopesGood for
wiringforms:read, forms:writeA site builder. Cannot read a single submission.
triage+ submissions:read, submissions:writeAn inbox assistant that labels spam but cannot delete.
readonlyforms:read, submissions:readReporting and analysis.
fulleverythingYour own trusted agent. The default.

Pass preset (or an explicit scopes array) to sign_up, verify_account or create_api_key, or pick the access level when creating a key in Settings. The individual scopes are forms:read, forms:write, forms:delete, submissions:read, submissions:write, submissions:delete and account:manage.

What free includes (and why): The free plan covers the whole wiring flow — account, forms, snippets, connectors, hosted pages, stats — so an agent scaffolding your site never hits a wall. Reading, classifying and exporting submission content through MCP (or the REST API) is part of Starter, which start_upgrade sets up; your submissions are always available in the dashboard regardless.

Classify spam in your own environment

list_submissions gives an agent the same inbox the dashboard shows, with the same filters. Whatever it decides, it records with label_submissions: spam and ok are fed back to the classifier as training signal, so your own judgement (or your own model) sharpens the filtering on your forms. Group, summarise, draft replies, push to a CRM: the loop is read, decide, label, and none of it needs our UI.

Try a prompt

With the server connected, this is a one-shot in any agent:

code
Create a contact form called "Portfolio contact" on ShipMyForm and
add the React snippet to src/components/ContactForm.tsx.

Starting from nothing:

code
Set up ShipMyForm for me: sign up with [email protected], verify it with the
code from my inbox, create a "Contact" form that emails me, add the HTML to
contact.html, and send Slack alerts to #leads (webhook URL in my notes).

Triage:

code
Go through this week's submissions on the Contact form, mark the
obvious spam, group the rest by topic, and draft a reply to each real one.

Rate limits: 30 requests/minute per key on free, 120 on paid plans. Keys can be revoked anytime in Settings. The server never exposes submission content to free-plan keys, and never exposes other workspaces to any key.