How do you add the Social by InstantDM MCP server to OpenClaw?

openclaw mcp add social-by-idm \
  --url https://social-api.instantdm.com/mcp \
  --transport streamable-http \
  --header "X-Api-Key: sk_live_YOUR_KEY"

That is the client-side half. Replace sk_live_YOUR_KEY with a real workspace key. Then probe. Empty tools/list means URL, transport, or key — not “the model is bad at social.” The skill is the manners (draft: true, offsets, confirm). This page is the socket.

I build the server. We sell the scheduler. I am not going to pretend OpenClaw ships Instagram. Background on the protocol: What is an MCP server for social media?. Operator walkthrough once tools appear: How to let OpenClaw post.

Social by InstantDM MCP page — the URL and tool list you are pointing at socialbyidm.com/mcp. Notice the hosted URL is https://social-api.instantdm.com/mcp, transport is Streamable HTTP, and auth is X-Api-Key (or ?key=). Copy this URL. Do not append /sse or guess localhost:3000.

What is MCP doing here, in one screen?

MCP is JSON-RPC 2.0 over a transport. The host (OpenClaw Gateway) spins up a client per server. The server exposes tools (callable functions with JSON Schema), optionally resources and prompts. A social publisher is almost entirely tools, because publishing is an action.

Two transports:

TransportWhere the server runsSocial publisher?
stdioChild process on the laptop. stdin/stdout. One client.No. We do not hold your Instagram token on your Mac.
Streamable HTTPRemote HTTP POST, optional SSE. Many clients.Yes. This is us.

If mcp add defaults to stdio, you will launch nothing useful and tools/list will be empty or an error about a missing command. Force --transport streamable-http. Some OpenClaw builds say streamable_http or http. openclaw mcp --help is canonical. Names of subcommands move with releases.

Under the hood the client sends tools/list, then tools/call with arguments matching the schema. The model reads the schema. You do not hand-code the integration. That is the whole point of MCP versus a REST API you wrap yourself.

Auth on our server:

  • Header: X-Api-Key: sk_live_… (preferred on OpenClaw)
  • Header: Authorization: Bearer sk_live_… (also accepted)
  • Query: https://social-api.instantdm.com/mcp?key=sk_live_… (URL-only clients)

OpenClaw can send headers. Use them. Query-string keys leak via access logs, screenshots, and shell history. Mint a dedicated key if you must use ?key=.

Rate limit: 120 requests/minute/key → 429 rate_limited.

How do you confirm the add actually stuck?

openclaw mcp list
openclaw mcp probe social-by-idm

You want the name social-by-idm in the list, transport HTTP-ish, and a probe that prints tool names. If probe errors, do not skip to Telegram. Fix the socket.

Restart if needed:

openclaw gateway restart

Some builds hot-reload MCP. Some freeze the tool list at Gateway boot. Restart is cheaper than a philosophy thread.

In the Control UI or Telegram:

What social tools do you have?

The agent should call tools/list and read names back. Empty list = wrong URL, wrong transport, or key not applied.

First real call:

List my connected accounts. Do not create a post.

That is list_accounts. It proves the header authenticated and that OAuth accounts exist. It cannot go live.

JSON some builds want in ~/.openclaw/openclaw.json if the CLI wrapper is missing or you prefer files:

{
  "mcp": {
    "servers": {
      "social-by-idm": {
        "url": "https://social-api.instantdm.com/mcp",
        "transport": "streamable-http",
        "headers": { "X-Api-Key": "sk_live_YOUR_KEY" }
      }
    }
  }
}

Do not commit that file. chmod 600. If you already pasted it into Slack, rotate the key.

Environment form, if your build interpolates:

export SOCIAL_BY_IDM_API_KEY=sk_live_YOUR_KEY

The Gateway process — not a random interactive shell — must see it. systemd EnvironmentFile=, Docker secrets, or the profile the daemon was started from.

OpenClaw documentation — MCP client configuration docs.openclaw.ai. Notice MCP is configured on the Gateway (CLI or JSON), and transport is a first-class field. Match —help on your installed version; do not copy a 2025 stdio example.

What should tools/list return — all 16, with scopes?

Same tools as REST. Full parity. Product page: /mcp. Reference: /docs.

ToolScopeWhat a correct call looks like
list_accountsaccounts:readNo body. Returns accountId, platform, handle.
create_postposts:writeaccountIds, content and/or mediaIds/thread, plus draft or scheduledAt or publishNow.
update_postposts:writeid plus fields to change. Activates a draft if you pass scheduledAt or publishNow.
get_postposts:readid. Full post + targets.
get_post_statusposts:readid. Lightweight. Honor partial.
list_postsposts:readOptional filters. Queue view.
delete_postposts:writeid. Check manual for platforms without delete.
upload_media_from_urlmedia:write{ "url": "https://…" }mediaId.
create_upload_urlmedia:write{ "filename": "sunset.jpg", "contentType": "image/jpeg" }.
complete_uploadmedia:writeAfter PUT to uploadUrl.
list_mediamedia:readLibrary.
delete_mediamedia:writemediaId.
get_analyticsanalytics:readTotals; do not invent.
list_platform_postsposts:readNative posts, including ones not created here.
delete_platform_postposts:writePlatform post id.
platform_requestplatform:proxyOpt-in native proxy. X currently disabled.

If you see four tools, you are on a cached, filtered, or ancient probe. Re-probe. If you see zero, stay on this page — do not start drafting captions.

A list_accounts result:

{
  "accounts": [
    { "accountId": "acc_8fK2qz", "platform": "instagram", "handle": "@acme", "displayName": "Acme Inc" },
    { "accountId": "acc_p1L9wd", "platform": "linkedin", "handle": "acme-inc", "displayName": "Acme Inc" },
    { "accountId": "acc_x7n2ab", "platform": "twitter", "handle": "@acme", "displayName": "Acme" }
  ]
}

Empty accounts with a full tool list: OAuth is not connected in the dashboard, or the key belongs to a different workspace. MCP is fine.

What payloads should OpenClaw send once the socket works?

Draft — first write on a new connection:

{
  "accountIds": ["acc_x7n2ab", "acc_p1L9wd"],
  "content": "Dark mode is on by default for new workspaces. Existing users: Settings → Appearance.",
  "draft": true
}

Scheduled with offset — always an offset. No offset = workspace timezone, not UTC.

{
  "accountIds": ["acc_x7n2ab"],
  "content": "Dark mode is on by default for new workspaces.",
  "scheduledAt": "2026-09-16T09:00:00+05:30"
}

platformContent for twitter / linkedin / instagram:

{
  "accountIds": ["acc_x7n2ab", "acc_p1L9wd", "acc_8fK2qz"],
  "content": "Dark mode is on by default for new workspaces.",
  "platformContent": {
    "twitter": "Dark mode is the default for new workspaces. Existing users: Settings → Appearance.",
    "linkedin": "We turned dark mode on by default for new workspaces.\n\nExisting users keep their current theme. Toggle: Settings → Appearance.\n\nWhat else should default to on?",
    "instagram": "Dark mode, on by default.\n\nSettings → Appearance for older workspaces."
  },
  "draft": true
}

platformSchedules for staggered times (same offset rule):

{
  "accountIds": ["acc_p1L9wd", "acc_8fK2qz"],
  "content": "Dark mode is on by default for new workspaces.",
  "scheduledAt": "2026-09-16T08:30:00-04:00",
  "platformSchedules": {
    "linkedin": "2026-09-16T08:30:00-04:00",
    "instagram": "2026-09-16T11:00:00-04:00"
  }
}

A past or near-now scheduledAt (≤30 seconds) publishes immediately. That is how a naive stamp becomes publishNow in disguise.

Instagram image: upload first, then attach. Text-only IG → 422 content_invalid. Carousel needs ≥2 media ids → else 422 post_type_invalid.

{
  "accountIds": ["acc_8fK2qz"],
  "postType": "image",
  "content": "Dark mode, on by default.",
  "mediaIds": ["md_1a2b3c"],
  "draft": true
}

After any live attempt, get_post_status. HTTP 200 on create_post is not five green checkmarks. Status partial means some platforms published and others failed:

{
  "postId": "pst_3kqz1a",
  "status": "partial",
  "ready": true,
  "targets": [
    {
      "accountId": "acc_8fK2qz",
      "platform": "instagram",
      "status": "published",
      "permalink": "https://www.instagram.com/p/…",
      "error": null
    },
    {
      "accountId": "acc_tk01",
      "platform": "tiktok",
      "status": "failed",
      "error": "Photo must be JPEG ≤1920px and <10MB"
    }
  ]
}

REST sanity check that bypasses OpenClaw entirely:

curl "https://social-api.instantdm.com/v1/accounts" \
  -H "X-Api-Key: $SOCIAL_BY_IDM_API_KEY"

If curl works and mcp probe does not, the bug is the client config. If curl 401s, stop blaming OpenClaw.

Social by InstantDM 2026 dashboard — same pipeline as these tools The 2026 app. If you can do it in this UI, the agent can do it with a key. After create_post with draft true, the row appears here. Screenshot of our app.

What if your other clients are URL-only?

ChatGPT’s connector screen historically did not document custom headers. Some Claude.ai paths hide the key under Advanced settings and people skip it. We accept:

https://social-api.instantdm.com/mcp?key=sk_live_YOUR_KEY

OpenClaw can send headers — use them. Do not “standardize” on query strings just because ChatGPT needs them. Dedicated key for URL-only clients so a leaked plugin URL does not take down the Gateway key. Revoke is instant; we re-check the key every request.

Claude app path, so you do not mix docs:

  1. Composer +ConnectorsAdd connectorAdd custom connector
  2. Name, URL https://social-api.instantdm.com/mcp
  3. Expand Advanced settings, paste API KEY
  4. Leave OAuth Client Secret empty

Claude composer Connectors menu Claude.ai. Notice this is a UI path, not openclaw mcp add. Same server underneath. OpenClaw users should not hunt this menu unless they also use Claude.

Claude Add custom connector with Advanced settings Claude’s Add custom connector dialog. Notice the API KEY field lives under Advanced settings, collapsed by default. That is why Claude connectors save “fine” and load no tools. OpenClaw’s equivalent bug is a missing —header.

Full Claude / ChatGPT / Codex menus: schedule social posts from Claude or ChatGPT.

Hermes YAML sibling — not mcp add:

mcp_servers:
  social-by-idm:
    url: "https://social-api.instantdm.com/mcp"
    headers:
      X-Api-Key: "${SOCIAL_BY_IDM_API_KEY}"
    enabled: true

Hermes posting guide. Hermes skill.

Codex, for completeness, uses ~/.codex/config.toml with http_headers or env_http_headers. Same URL.

What goes wrong — 401, 403, empty tools, empty accounts, agent publishes anyway?

SymptomLikely causeFix
No toolsHeader not sent; placeholder sk_live_YOUR_KEY; stdio transport; typo in URL (/mcp/ extra slash, http://, /sse)Exact URL. streamable-http. Real key. Probe. Restart Gateway.
Probe TLS errorsCorporate proxy, clock skew, old NodeFix TLS. Do not disable verify “just to test” on a box that holds posting keys.
401 unauthorizedRevoked, typo, leading space, extra quotes in JSON "\"sk_live_…\""Recopy from dashboard. JSON value is the raw key.
403 forbiddenKey valid, scope missingAdd accounts:read / posts:write / media:write. Do not rotate first.
Tools present, empty accountsOAuth not connected; wrong workspaceDashboard → Accounts. Check which workspace minted the key.
Agent publishes anywayNo skill, no SOUL.md, cron said “post”Install the skill. draft: true. SOUL.md.
422 content_invalidIG without media; caption over limitUpload media. platformContent.
422 post_type_invalidCarousel with one image≥2 or postType: "image".
404Hallucinated idslist_accounts again.
409 publishingDelete in flightWait, retry.
429>120/minStop the retry loop.
Tools vanish after upgradeConfig key renamed (mcp vs mcpServers); Gateway did not reloadRe-read --help. Re-add.
Key in TelegramAgent asked; you pastedRotate. Put key in header/env only. Skill forbids hunting.
Posted at 3amNaive scheduledAtOffset. Workspace TZ, not UTC.
partial treated as successNever called get_post_statusCall it. Read per-target error.

401 body:

{ "error": { "code": "unauthorized", "message": "Missing or invalid API key." } }

403 body:

{ "error": { "code": "forbidden", "message": "Key lacks posts:write." } }

400 on create_post usually means empty accountIds, neither content nor media nor thread, or none of draft / scheduledAt / publishNow. Add draft: true. Do not “fix” a 400 by flipping publishNow.

Gateway bind reminder while you have a posting key attached: port 18789 on loopback, not 0.0.0.0. An exposed Gateway plus MCP posts:write is a prompt-injection path onto Instagram.

OpenClaw homepage — the host that runs this MCP client openclaw.ai. Notice OpenClaw is the host (Gateway + channels). MCP servers are extras you add. This command is how social becomes one of those extras.

How do you debug the HTTP socket without asking the model?

The model is a bad debugger. Use curl against the same URL the Gateway uses.

Initialize (MCP handshake is JSON-RPC). A minimal check that the host is alive and the key authenticates:

curl -sS -D - "https://social-api.instantdm.com/mcp" \
  -H "X-Api-Key: $SOCIAL_BY_IDM_API_KEY" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

If this 401s, OpenClaw will 401. If this 200s and OpenClaw shows no tools, the Gateway is not sending the header. Common causes: --header swallowed by the shell because you used smart quotes; JSON headers key misspelled; placeholder sk_live_YOUR_KEY still in openclaw.json.

tools/list after a session exists is what mcp probe wraps. You want sixteen name fields. A tools/call for the first live test should look like this conceptually (the Gateway builds it; you should recognise it in logs):

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "list_accounts",
    "arguments": {}
  }
}

Verbose header dump when you think the key is right:

curl -v "https://social-api.instantdm.com/v1/accounts" \
  -H "X-Api-Key: $SOCIAL_BY_IDM_API_KEY"

REST and MCP share the key and the scopes. REST is simpler to stare at. If REST lists accounts and MCP does not, stay on transport and headers. If both 401, stay on the key.

Windows path for the JSON file:

notepad $env:USERPROFILE\.openclaw\openclaw.json
icacls $env:USERPROFILE\.openclaw\openclaw.json /inheritance:r /grant:r "$($env:USERNAME):(R,W)"

Do not commit that file. Do not sync it to a public gist “as a backup.”

Multiple MCP servers on one Gateway are fine (filesystem, browser, social). Name this one social-by-idm so logs are greppable. Two social servers (ours plus a hobby wrapper) will duplicate create_post names or confuse the model. One publisher.

Timeouts: a create_post that uploads a carousel can sit in publishing for a while. Claude’s MCP timeout is 300 seconds in the app; OpenClaw’s varies. Do not set a 2-second client timeout and then decide the server is dead. Poll get_post_status instead of retrying create_post.

Node: OpenClaw wants current Node (22.22.3+, 24.15+ or 25.9+ in the install docs I last checked). An ancient Node with a broken fetch/undici against Streamable HTTP looks like “empty tools.” node -v before you rewrite YAML.

What does the three-step local upload look like over MCP?

Agents skip step 3. Spell it.

  1. create_upload_url
{ "filename": "dark-mode.jpg", "contentType": "image/jpeg" }

Response: mediaId, uploadUrl, key.

  1. HTTP PUT the file bytes to uploadUrl. This is not an MCP tool. It is a presigned URL. The Gateway (or a script) must PUT. Content-Type should match what you declared.
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --data-binary @dark-mode.jpg
  1. complete_upload with that mediaId (optional size in bytes). Status becomes uploaded. Then create_post with mediaIds: ["md_…"] and draft: true.

If you only have a public URL, skip all three and call upload_media_from_url. If the file is on your laptop and not on a CDN, you need the three steps. Google Drive and Notion image URLs are not CDNs.

How do you mint the key so MCP is not over-privileged?

Dashboard → Developer → API keys. Workspace-scoped. Shown once. Stored hashed.

Day-one scopes:

  • accounts:read — required or list_accounts 403s
  • posts:read — status, list, get
  • posts:write — create, update, delete
  • media:write — uploads
  • media:read — optional
  • analytics:read — optional
  • platform:proxyoff until you need platform_request

One key per client (OpenClaw vs Claude vs a CI script). One workspace per brand. Agencies that share a key across clients will watch the agent pick the wrong acc_.

Name the key openclaw-gateway-prod so a leak in logs is identifiable. Rotate from the same screen. There is no cache; revoke stops the next request.

How does this sit next to the ClawHub skill?

LayerJobCommand
MCPTyped tools over Streamable HTTPopenclaw mcp add … (this page)
SkillWhen to draft, offsets, confirm, no key-huntopenclaw skills install @sanjaykhanssk/social-by-idm
SOUL.mdIdentity, never publish_nowYou write it
DashboardOAuth, calendar, human reviewsocial-app.instantdm.com

MCP without skill: tools appear, UTC happens, live posts happen. Skill without MCP: a lecture. Use both. Skill install notes: Install the Social by InstantDM skill on OpenClaw. Format: SKILL.md.

Hermes install is GitHub, not ClawHub:

hermes skills install github:instant-dm/agent-mode/skills/social-by-idm

Do not run that in an OpenClaw shell. Do not run openclaw mcp add expecting Hermes to read it. Different config trees.

What should a weekday automation do once MCP is live?

Not publish. Wake, write, draft, ping.

Isolated session. Read SOUL.md. List accounts. Produce five drafts for empty slots. draft: true. Never publish_now. Deliver postIds to Telegram. Stop.

If you also use Hermes cron, same policy:

hermes cron create "0 8 * * 1-5" "Read SOUL.md. Draft five posts for empty slots this week. draft true. Never publish_now. Deliver IDs to Telegram." --name "weekday-drafts" --deliver telegram

Always-on calendars. Agent wake ≠ network go-live. platformSchedules owns the second clock.

Freeze:

Freeze social. Do not create, schedule, or publish. Confirm the queue is untouched.

If MCP is the only thing you added today, freeze still works — the tools are there, the prompt tells the model not to use them.

Social by InstantDM agents page socialbyidm.com/agents. Notice OpenClaw is a supported client of the same 16 tools Claude gets. mcp add is how OpenClaw joins that list.

How do you know you are done?

  1. The exact openclaw mcp add command ran with a real key and --transport streamable-http.
  2. openclaw mcp probe social-by-idm prints 16 names.
  3. Telegram: “What social tools do you have?” matches the table.
  4. Telegram: “List my connected accounts. Do not create a post.” returns acc_ IDs.
  5. Optional but recommended: skill installed, SOUL.md forbids publish_now.
  6. A create_post with draft: true shows up in the calendar.
  7. You have practiced freeze.
  8. 18789 is not on the public internet.

Done is tools/list plus a draft, not a live grid post. I sell the hosted server because OAuth tokens should not live in an OpenClaw plugin. Point the Gateway at one URL, send a header, call list_accounts first.

What does an empty-tools session look like, turn by turn?

You type: “What social tools do you have?”

Healthy: the model calls tools/list (or the Gateway already injected the list) and reads back the sixteen names, including list_accounts and get_post_status. Then you type “List my connected accounts. Do not create a post.” and you see acc_ IDs.

Empty tools: the model says it cannot access social, or it offers to write a Python script against Graph, or it asks for your Instagram password. None of those is a model intelligence problem. Walk the socket:

  1. openclaw mcp list — is social-by-idm there?
  2. openclaw mcp probe social-by-idm — names or error?
  3. curl REST /v1/accounts with the same key — 200 or 401/403?
  4. openclaw.json — is the key still sk_live_YOUR_KEY? Is transport stdio?
  5. Gateway restart.

If probe works and chat does not, the session started before the add. New session. If probe fails and curl REST works, transport/header. If curl REST 403s, scopes. If curl REST 401s, the key string.

Do not “debug” by pasting the key into Telegram so the model can retry with ?key=. That creates a second, leaked credential and teaches the agent that chatting secrets is allowed. The skill, once installed, should refuse that. MCP add is the place the secret belongs — in a header the Gateway sends, not in a chat transcript.

I sell this hosted endpoint so you do not run nine OAuth refresh loops inside OpenClaw. Add it, probe it, call list_accounts, draft. That is the whole job of this command.

Product: /mcp · /agents · docs. Claude sibling: schedule from Claude or ChatGPT.