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:
| URL | https://shipmyform.com/api/mcp |
| Transport | Streamable HTTP (not SSE, not stdio) |
| Auth header | Authorization: Bearer smf_YOUR_KEY |
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:
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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
[mcp_servers.shipmyform]
url = "https://shipmyform.com/api/mcp"
bearer_token_env_var = "SMF_API_KEY"Gemini CLI
gemini mcp add --transport http \
--header "Authorization: Bearer smf_YOUR_KEY" \
shipmyform https://shipmyform.com/api/mcpOr in ~/.gemini/settings.json. Use httpUrl; Gemini reserves url for SSE servers:
{
"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:
{
"mcpServers": {
"shipmyform": {
"type": "streamableHttp",
"url": "https://shipmyform.com/api/mcp",
"headers": { "Authorization": "Bearer smf_YOUR_KEY" }
}
}
}Zed
Settings → AI → MCP Servers, or settings.json:
{
"context_servers": {
"shipmyform": {
"url": "https://shipmyform.com/api/mcp",
"headers": { "Authorization": "Bearer smf_YOUR_KEY" }
}
}
}opencode
{
"$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:
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
| Tool | What it does | Plan |
|---|---|---|
| sign_up | Start an account from an email address; emails a 6-digit code (no auth needed) | — |
| verify_account | Prove the email with the code; returns the API key when called without one | — |
| get_account | Email, verification state, plan, limits, usage | All plans |
| create_api_key / list_api_keys / revoke_api_key | Manage keys | All plans |
| start_upgrade | Checkout URL for Starter or Pro; plan activates after payment | All plans |
| get_billing_portal | Self-service billing portal link | Paid |
| create_form | Create a form, get its endpoint + HTML snippet; emails the owner by default | All plans |
| list_forms / get_form | Forms with endpoints; full settings, connectors, hosted page | All plans |
| update_form | Name, purpose, allowed domains, redirect, spam mode, validation rules, blocklist, auto-responder | All plans (auto-responder: Pro) |
| delete_form | Delete a form | All plans |
| get_form_snippet | Snippet for html / react / vue / fetch | All plans |
| get_submission_stats | Delivered / spam-held counts + quota usage | All plans |
| list_connector_types | Every destination with its config fields and plan | All plans |
| add_connector / test_connector / set_connector_active / remove_connector | Route submissions to email, webhooks, Slack, Discord, Airtable, HubSpot, Telegram, … | Per connector |
| publish_hosted_page / unpublish_hosted_page | A standalone form page on shipmyform.com, plus an embed script | All plans |
| list_submissions | Read submissions with filters: form, status, dates, search, country, attachments, failed deliveries | Starter and up |
| get_submission | One submission with spam signals and delivery history | Starter and up |
| label_submissions | Mark spam / ok / flagged; spam and ok also train the classifier | Starter and up |
| delete_submissions | Delete submissions | Starter and up |
| export_submissions | CSV of a form's submissions | Starter 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.
| Preset | Scopes | Good for |
|---|---|---|
| wiring | forms:read, forms:write | A site builder. Cannot read a single submission. |
| triage | + submissions:read, submissions:write | An inbox assistant that labels spam but cannot delete. |
| readonly | forms:read, submissions:read | Reporting and analysis. |
| full | everything | Your 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.
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:
Create a contact form called "Portfolio contact" on ShipMyForm and
add the React snippet to src/components/ContactForm.tsx.Starting from nothing:
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:
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.