Provider readinessInterfaces and provider certification may evolve. Check account capabilities before production use.Read the readiness contract

Connect an AI client through the App9 Post MCP server

Configure a supported MCP client, authenticate safely, discover all social tools, and operate drafts, publishing, analytics, and inbox workflows.

Direct answer

App9 Post has a deployed, stateless production MCP server at https://postapi.app9.co/mcp. The staging target is configured as https://postapi-staging.app9.co/mcp, but its live deployment did not expose the route when this guide was reviewed on 2026-08-06. Authenticate production with a brand-scoped API key or an App9-issued delegated token. The server exposes 19 tools and enforces the same scopes, brand roles, capabilities, approval policy, billing rules, and provider release controls as the REST API.

1. Create the brand and credential

Sign in to the production console at https://post.app9.co, create or select a brand, and open Developers. Only a brand owner or admin can create API keys. Connect social accounts under Connections, then query each account's capabilities before exposing tools that create or publish content.

  1. Open https://post.app9.co/brands and create or select the production brand. For testing, use the isolated staging console at https://post-staging.app9.co.

  2. After selecting the brand, copy its ID from the browser URL's brand query parameter, such as ?brand=brand_two. Send it as X-App9-Post-Brand so a mismatch fails closed.

  3. Open https://post.app9.co/developers, choose Create API key, select the smallest required scopes, and save the raw key immediately. It is shown in full only once.

  4. Open https://post.app9.co/connections and connect a test social account. Environment-specific brands, keys, provider apps, and accounts must remain separate.

  5. Store the key in a secret manager or local environment variable. Rotate or revoke it when an operator, environment, or integration changes.

Integration profileRecommended starting scopes
Discovery onlyaccounts:read
Read and reportingaccounts:read plus only the needed posts:read, analytics:read, or inbox:read scopes
Media and draftsaccounts:read, media:write, posts:read, posts:write
PublishingAdd posts:publish; add brands:admin only when an API-key client must approve or reject
Inbox repliesinbox:read and inbox:reply only when the assistant is allowed to reply

2. Choose the environment and transport

The MCP endpoint is Streamable HTTP over POST and is stateless. GET /mcp returns 405. Requests are limited to 128 KiB; an oversized body returns HTTP 413 with JSON-RPC -32600. Responses are private and use Cache-Control: no-store. A missing Origin header is accepted for server clients, while a disallowed cross-origin browser Origin is rejected before authentication.

Shell — local environment
export APP9_POST_MCP_URL='https://postapi.app9.co/mcp'
export APP9_POST_API_KEY='replace-with-the-key-shown-once-in-the-console'
export APP9_POST_BRAND_ID='replace-with-your-brand-id'

3. Configure a compatible MCP client

Codex CLI, the Codex IDE extension, and ChatGPT desktop

These Codex surfaces share MCP configuration on the same host. Add the following block to ~/.codex/config.toml after setting APP9_POST_API_KEY and APP9_POST_BRAND_ID in the environment that launches the client. The allowlist starts with discovery, preview, draft, and result tools; expand it only after reviewing scopes and approval behavior.

TOML — ~/.codex/config.toml
[mcp_servers.app9_post]
url = "https://postapi.app9.co/mcp"
bearer_token_env_var = "APP9_POST_API_KEY"
env_http_headers = { "X-App9-Post-Brand" = "APP9_POST_BRAND_ID" }
enabled_tools = ["list_social_accounts", "get_capabilities", "create_post_preview", "create_draft", "list_post_results"]
default_tools_approval_mode = "writes"
startup_timeout_sec = 20
tool_timeout_sec = 60
enabled = true

Restart the desktop app or IDE extension after changing its launch environment. Run codex mcp list in the CLI or open /mcp in an interactive Codex session to confirm that app9_post connects and that the allowlisted tools are visible.

Claude Code

Claude Code supports remote HTTP MCP servers and environment expansion in .mcp.json. Keep the environment references in the file and the secret values outside it. A project-scoped .mcp.json should be reviewed before it is trusted or shared.

JSON — .mcp.json
{
  "mcpServers": {
    "app9-post": {
      "type": "http",
      "url": "https://postapi.app9.co/mcp",
      "headers": {
        "Authorization": "Bearer ${APP9_POST_API_KEY}",
        "X-App9-Post-Brand": "${APP9_POST_BRAND_ID}"
      }
    }
  }
}

Restart Claude Code, run claude mcp list, then use /mcp to inspect connection status. Configure tool permissions in Claude Code so mutation tools require review before execution.

4. Verify the 2026-07-28 protocol directly

Compatible clients normally negotiate the protocol for you. The manual requests below are useful for diagnostics. On every 2026-07-28 request, MCP-Protocol-Version must be 2026-07-28, Mcp-Method must exactly match the JSON-RPC method, and tools/call must also carry Mcp-Name equal to the requested tool. params._meta carries the protocol version and client capabilities on every request.

Discover the server

Shell — server/discover
curl --fail-with-body --silent --show-error --request POST "$APP9_POST_MCP_URL" \
  --header "Authorization: Bearer $APP9_POST_API_KEY" \
  --header "X-App9-Post-Brand: $APP9_POST_BRAND_ID" \
  --header 'Content-Type: application/json' \
  --header 'MCP-Protocol-Version: 2026-07-28' \
  --header 'Mcp-Method: server/discover' \
  --data '{
    "jsonrpc": "2.0",
    "id": "discover-1",
    "method": "server/discover",
    "params": {
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {},
        "io.modelcontextprotocol/clientInfo": {
          "name": "app9-post-setup-check",
          "version": "1.0.0"
        }
      }
    }
  }'

List the tools

Shell — tools/list
curl --fail-with-body --silent --show-error --request POST "$APP9_POST_MCP_URL" \
  --header "Authorization: Bearer $APP9_POST_API_KEY" \
  --header "X-App9-Post-Brand: $APP9_POST_BRAND_ID" \
  --header 'Content-Type: application/json' \
  --header 'MCP-Protocol-Version: 2026-07-28' \
  --header 'Mcp-Method: tools/list' \
  --data '{
    "jsonrpc": "2.0",
    "id": "tools-1",
    "method": "tools/list",
    "params": {
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {},
        "io.modelcontextprotocol/clientInfo": {
          "name": "app9-post-setup-check",
          "version": "1.0.0"
        }
      }
    }
  }'

Make a read-only tool call

Shell — tools/call
curl --fail-with-body --silent --show-error --request POST "$APP9_POST_MCP_URL" \
  --header "Authorization: Bearer $APP9_POST_API_KEY" \
  --header "X-App9-Post-Brand: $APP9_POST_BRAND_ID" \
  --header 'Content-Type: application/json' \
  --header 'MCP-Protocol-Version: 2026-07-28' \
  --header 'Mcp-Method: tools/call' \
  --header 'Mcp-Name: list_social_accounts' \
  --data '{
    "jsonrpc": "2.0",
    "id": "call-1",
    "method": "tools/call",
    "params": {
      "name": "list_social_accounts",
      "arguments": {},
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientCapabilities": {},
        "io.modelcontextprotocol/clientInfo": {
          "name": "app9-post-setup-check",
          "version": "1.0.0"
        }
      }
    }
  }'

The discovery response identifies app9-post version 1.0.0-beta.1 and lists the supported protocol versions. On 2026-07-28 requests, tools/list returns JSON Schemas for every tool with a private, zero-TTL cache scope; legacy responses omit the cache scope. A successful modern tools/call response uses resultType complete and includes both human-readable content and structuredContent.

Legacy initialization for older clients

App9 Post also accepts 2025-11-25, 2025-06-18, and 2025-03-26. Older MCP clients generally negotiate this automatically. Use initialize only when diagnosing one of those clients.

Shell — legacy initialize
curl --fail-with-body --silent --show-error --request POST "$APP9_POST_MCP_URL" \
  --header "Authorization: Bearer $APP9_POST_API_KEY" \
  --header "X-App9-Post-Brand: $APP9_POST_BRAND_ID" \
  --header 'Content-Type: application/json' \
  --header 'MCP-Protocol-Version: 2025-11-25' \
  --data '{
    "jsonrpc": "2.0",
    "id": "initialize-1",
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-11-25",
      "capabilities": {},
      "clientInfo": {
        "name": "legacy-mcp-client",
        "version": "1.0.0"
      }
    }
  }'

5. Review the complete tool catalog

Scopes and brand roles are both enforced. Session users use their membership role. Delegated tokens preserve their issuing user's role and scopes. For API keys, read means any effective role, edit/publish/reply accepts editor or higher, and approve accepts owner, admin, or approver; the API-key mapping caveat in step 1 still applies.

ToolRequired scopeRole permissionRequired argumentsServer approval
list_adaptive_campaignsposts:readread / any roleNoneNo
get_adaptive_campaignposts:readread / any rolecampaign_idNo
get_adaptive_queueposts:readread / any roleNoneNo
create_adaptive_campaignposts:writeeditcontent_sets, external_id, idempotency_key, target_account_ids, timezoneDelegated: when creation activates without review
adaptive_campaign_actionposts:writeeditaction, campaign_id, idempotency_keyDelegated: approve
adaptive_candidate_target_actionposts:writeedit; publish_now also requires posts:publishaction, candidate_target_id, idempotency_keyDelegated: publish_now
list_social_accountsaccounts:readread / any roleNoneNo
get_capabilitiesaccounts:readread / any roleaccount_idNo
create_media_uploadmedia:writeeditcontent_type, file_name, kind, size_bytes, idempotency_keyNo
create_post_previewposts:writeeditmode, target_account_idsNo; validation only
prepare_mcp_actionSame as target toolSame as target toolarguments, idempotency_key, tool_nameIssues approval for delegated high-impact actions
create_postposts:writeeditmode, target_account_ids, idempotency_keyDelegated: non-draft when policy is not none
create_draftposts:writeedittarget_account_ids, idempotency_keyNo
schedule_postposts:writeeditpublish_at, target_account_ids, idempotency_keyDelegated: when policy is not none
update_postposts:writeeditpost_id, idempotency_keyNo
submit_postposts:writeeditpost_id, idempotency_keyNo
approve_postposts:publishapprovepost_id, idempotency_keyAlways for delegated callers
reject_postposts:publishapprovepost_id, idempotency_keyAlways for delegated callers
publish_postposts:publishpublishpost_id, idempotency_keyDelegated: when policy is not none
cancel_postposts:writeeditpost_id, idempotency_keyNo
retry_postposts:publishpublishpost_id, idempotency_keyNo
list_post_resultsposts:readread / any rolepost_idNo
get_analyticsanalytics:readread / any roleNone; range defaults to 30dNo
list_inbox_threadsinbox:readread / any roleNoneNo
reply_to_inbox_threadinbox:replyreplymessage, thread_id, idempotency_keyAlways for delegated callers

Post inputs may also include caption, external_id, media_ids, platform_configurations, target_overrides, timezone, and schedule_strategy where the tool schema permits them. A post must contain a caption or media. Target account IDs must be unique; the current schema permits 1–100 targets and up to 35 media assets. Scheduled mode requires publish_at, while other modes reject it. Set schedule_strategy to next_available when publish_at is an earliest acceptable time and App9 Post should resolve each account's recommended cadence-safe slot.

6. Run a safe first workflow

  1. Call list_social_accounts and choose an account from the selected brand.

  2. Call get_capabilities with that account ID. Treat the returned provider and account capability matrix as authoritative.

  3. Call create_post_preview with mode draft, the selected targets, caption, media IDs, and any per-platform overrides. Correct every validation issue.

  4. Call create_draft with a new idempotency key. Draft creation does not require a server-issued approval.

  5. Have a human inspect the draft in the App9 Post console before granting schedule or publish tools.

  6. After dispatch, call list_post_results and inspect every target result. One provider can fail while another succeeds.

When sending this body manually, use the same headers as the first tools/call example and set Mcp-Name to create_draft.

JSON — create_draft request body
{
  "jsonrpc": "2.0",
  "id": "draft-1",
  "method": "tools/call",
  "params": {
    "name": "create_draft",
    "arguments": {
      "caption": "A review-ready launch update.",
      "media_ids": [],
      "target_account_ids": ["ACCOUNT_ID"],
      "timezone": "UTC",
      "idempotency_key": "draft-launch-20270803-001"
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "my-social-assistant",
        "version": "1.0.0"
      }
    }
  }
}

7. Make every mutation durable and replay-safe

Every mutation requires idempotency_key with 8–128 characters drawn from A–Z, a–z, 0–9, period, underscore, colon, or hyphen. Generate one key for one intended operation and persist it alongside the calling job or conversation action.

  • The server binds the key to tenant, brand, actor kind and ID, tool, and canonical arguments before application or provider work starts.

  • An exact retry with the same key and arguments returns the stored result without running the mutation again.

  • Reusing a key with a different tool or argument set returns mcp_idempotency_conflict. Keys are scoped per actor and brand, so the same key used by a different actor or brand is a separate operation, not a conflict.

  • Mutation results remain replayable for 24 hours.

  • mcp_mutation_indeterminate means the final result could not be durably recorded, or the same key is currently in flight. Do not retry with a new key; inspect the App9 Post resource and provider state before operator recovery.

Mutation tools are create_media_upload, create_post, create_draft, schedule_post, update_post, submit_post, approve_post, reject_post, publish_post, cancel_post, retry_post, and reply_to_inbox_thread. prepare_mcp_action also takes an idempotency key so its approval is bound to the exact future action.

8. Handle delegated high-impact approvals

This server-issued flow applies only to short-lived App9 delegated tokens, such as a Jaxia-assisted console conversation. App9 issues those tokens internally; they expire after five minutes and preserve the caller's brand and role, while scopes are re-derived from the role (sessions) or filtered through the delegated-scope allowlist (API keys), which never includes brands:admin. Calling prepare_mcp_action with an ordinary API key returns mcp_approval_not_required; those integrations should rely on MCP-host review prompts plus App9 Post's normal workflow policy.

  1. Call prepare_mcp_action with a high-impact tool_name, a top-level idempotency_key, and the exact future arguments.

  2. Omit approval_id, idempotency_key, and confirmed from the nested arguments. confirmed is not an approval field and is rejected.

  3. Show the action details to the user and obtain approval in the trusted App9/Jaxia flow.

  4. Within five minutes, call the target tool with the exact same arguments, the same idempotency key, and the returned approval_id.

  5. If an exact retry is needed after success, reuse the same mutation key and arguments; the stored result can replay even though the one-time approval is consumed.

Prepare the exact delegated action

JSON — prepare_mcp_action request body
{
  "jsonrpc": "2.0",
  "id": "prepare-1",
  "method": "tools/call",
  "params": {
    "name": "prepare_mcp_action",
    "arguments": {
      "tool_name": "schedule_post",
      "idempotency_key": "schedule-launch-20270803-001",
      "arguments": {
        "caption": "The launch is live tomorrow.",
        "media_ids": [],
        "publish_at": "2027-08-03T14:00:00Z",
        "target_account_ids": ["ACCOUNT_ID"],
        "timezone": "UTC"
      }
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "jaxia",
        "version": "1.0.0"
      }
    }
  }
}

Execute with the returned approval

JSON — schedule_post request body
{
  "jsonrpc": "2.0",
  "id": "schedule-1",
  "method": "tools/call",
  "params": {
    "name": "schedule_post",
    "arguments": {
      "caption": "The launch is live tomorrow.",
      "media_ids": [],
      "publish_at": "2027-08-03T14:00:00Z",
      "target_account_ids": ["ACCOUNT_ID"],
      "timezone": "UTC",
      "idempotency_key": "schedule-launch-20270803-001",
      "approval_id": "mcp_approval_REPLACE_WITH_SERVER_VALUE"
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "jaxia",
        "version": "1.0.0"
      }
    }
  }
}

The one-time approval is bound to tenant, brand, delegated actor, tool, canonical arguments, and idempotency key, and is claimed atomically with the mutation key. Expired, consumed, mismatched, cross-actor, and cross-brand approvals fail closed.

9. Interpret responses, errors, and limits

ConditionHTTP / JSON-RPCWhat to do
Missing or invalid bearer credential401 problem+jsonReplace or rotate the credential; do not retry blindly
Brand missing, conflicting, forbidden, or not found400, 403, or 404 problem+jsonVerify key binding and X-App9-Post-Brand
Disallowed browser Origin403 / -32001Move the call to an allowed server-side MCP host
Malformed JSON400 / -32700Correct the JSON body
Invalid JSON-RPC request or ID400 / -32600Use jsonrpc 2.0 and a non-null string or integer id
Request body over 128 KiB413 / -32600Reduce the request body below the 128 KiB limit
Required MCP header missing or not matching the body400 / -32020Send MCP-Protocol-Version, Mcp-Method, and Mcp-Name exactly matching the body
Unsupported protocol version400 / -32022Choose a version from the returned supported list
Unknown method404 / -32601 for modern requestsUse server/discover, tools/list, or tools/call
Invalid tool arguments400 / -32602 for modern requestsValidate against the schema returned by tools/list
MCP request rate limited429 / -32603Back off and retry safely with the same mutation key when applicable
MCP rate limiter unavailable503 / -32603Fail closed and retry later
Remote media import limitedHTTP 200 tool result with remote_media_rate_limitedUse structuredContent.error.retry_after_seconds

Tool execution errors are normally returned inside an HTTP 200 JSON-RPC result with isError true. Read structuredContent.error.code and status when present; the text is safe for display but should not be the only input to recovery logic. Provider credentials and raw provider error payloads are never returned.

10. Production-readiness checklist

  • Use separate production and staging brands, keys, social accounts, and provider apps.

  • Keep an explicit enabled_tools allowlist and require host approval for all writes; consider enabling only draft creation initially.

  • Confirm scopes and effective roles for each enabled tool, especially approve_post, reject_post, publish_post, and reply_to_inbox_thread.

  • Query get_capabilities after account connection and whenever permissions or provider access change.

  • Persist idempotency keys and request context for every mutation; never invent a new key merely because a response was lost.

  • Log request IDs, tool names, brand IDs, sanitized resource IDs, and target results without logging Authorization headers or prompt secrets.

  • Reconcile list_post_results, events, or webhooks per target account rather than collapsing partial success into one status.

  • Exercise key rotation, revocation, provider reconnect, approval expiry, rate limiting, and partial delivery failure in staging.

  • Keep the App9 Post console usable when an assistant or MCP host is unavailable.

11. References and next steps

Frequently asked questions

Does App9 Post currently have a deployed MCP server?

Yes in production at https://postapi.app9.co/mcp. The staging URL is configured as https://postapi-staging.app9.co/mcp, but its live deployment did not expose /mcp when this guide was reviewed on 2026-08-06.

Should a 2026-07-28 client call initialize?

No. The modern protocol is stateless and uses optional server/discover. initialize is supported only for the legacy 2025-11-25, 2025-06-18, and 2025-03-26 flows.

Can I paste the MCP URL into ChatGPT web or Claude.ai?

Not by itself. App9 Post currently uses a bearer API key plus an explicit brand header and does not advertise an OAuth connector. Use a client that supports custom headers, such as Codex or Claude Code, or a trusted backend integration.

Do I need a delegated token?

No. Use a brand-scoped API key for normal external server integrations. Delegated tokens are short-lived credentials issued inside App9 experiences such as Jaxia-assisted operations.

Are the REST API and MCP server the same interface?

They enforce the same brand, scope, role, capability, approval, and billing rules, but they are distinct transports. Use tools/list for MCP schemas and OpenAPI for REST schemas.

Was this useful? This documentation is reviewed against the public App9 Post contract. Use the API reference and live capability response for machine-enforced details.

Open API reference