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.
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.
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.
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.
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.
Open https://post.app9.co/connections and connect a test social account. Environment-specific brands, keys, provider apps, and accounts must remain separate.
Store the key in a secret manager or local environment variable. Rotate or revoke it when an operator, environment, or integration changes.
| Integration profile | Recommended starting scopes |
|---|---|
| Discovery only | accounts:read |
| Read and reporting | accounts:read plus only the needed posts:read, analytics:read, or inbox:read scopes |
| Media and drafts | accounts:read, media:write, posts:read, posts:write |
| Publishing | Add posts:publish; add brands:admin only when an API-key client must approve or reject |
| Inbox replies | inbox:read and inbox:reply only when the assistant is allowed to reply |
2. Choose the environment and transport
| Purpose | Production | Staging |
|---|---|---|
| MCP server | https://postapi.app9.co/mcp — deployed | https://postapi-staging.app9.co/mcp — configured target; route not currently deployed |
| Web console | https://post.app9.co | https://post-staging.app9.co |
| REST reference | https://postapi.app9.co/docs | https://postapi-staging.app9.co/docs |
| OpenAPI | https://postapi.app9.co/openapi.json | https://postapi-staging.app9.co/openapi.json |
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.
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.
[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 = trueRestart 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.
{
"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
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
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
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.
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.
| Tool | Required scope | Role permission | Required arguments | Server approval |
|---|---|---|---|---|
| list_adaptive_campaigns | posts:read | read / any role | None | No |
| get_adaptive_campaign | posts:read | read / any role | campaign_id | No |
| get_adaptive_queue | posts:read | read / any role | None | No |
| create_adaptive_campaign | posts:write | edit | content_sets, external_id, idempotency_key, target_account_ids, timezone | Delegated: when creation activates without review |
| adaptive_campaign_action | posts:write | edit | action, campaign_id, idempotency_key | Delegated: approve |
| adaptive_candidate_target_action | posts:write | edit; publish_now also requires posts:publish | action, candidate_target_id, idempotency_key | Delegated: publish_now |
| list_social_accounts | accounts:read | read / any role | None | No |
| get_capabilities | accounts:read | read / any role | account_id | No |
| create_media_upload | media:write | edit | content_type, file_name, kind, size_bytes, idempotency_key | No |
| create_post_preview | posts:write | edit | mode, target_account_ids | No; validation only |
| prepare_mcp_action | Same as target tool | Same as target tool | arguments, idempotency_key, tool_name | Issues approval for delegated high-impact actions |
| create_post | posts:write | edit | mode, target_account_ids, idempotency_key | Delegated: non-draft when policy is not none |
| create_draft | posts:write | edit | target_account_ids, idempotency_key | No |
| schedule_post | posts:write | edit | publish_at, target_account_ids, idempotency_key | Delegated: when policy is not none |
| update_post | posts:write | edit | post_id, idempotency_key | No |
| submit_post | posts:write | edit | post_id, idempotency_key | No |
| approve_post | posts:publish | approve | post_id, idempotency_key | Always for delegated callers |
| reject_post | posts:publish | approve | post_id, idempotency_key | Always for delegated callers |
| publish_post | posts:publish | publish | post_id, idempotency_key | Delegated: when policy is not none |
| cancel_post | posts:write | edit | post_id, idempotency_key | No |
| retry_post | posts:publish | publish | post_id, idempotency_key | No |
| list_post_results | posts:read | read / any role | post_id | No |
| get_analytics | analytics:read | read / any role | None; range defaults to 30d | No |
| list_inbox_threads | inbox:read | read / any role | None | No |
| reply_to_inbox_thread | inbox:reply | reply | message, thread_id, idempotency_key | Always 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
Call list_social_accounts and choose an account from the selected brand.
Call get_capabilities with that account ID. Treat the returned provider and account capability matrix as authoritative.
Call create_post_preview with mode draft, the selected targets, caption, media IDs, and any per-platform overrides. Correct every validation issue.
Call create_draft with a new idempotency key. Draft creation does not require a server-issued approval.
Have a human inspect the draft in the App9 Post console before granting schedule or publish tools.
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.
{
"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.
Call prepare_mcp_action with a high-impact tool_name, a top-level idempotency_key, and the exact future arguments.
Omit approval_id, idempotency_key, and confirmed from the nested arguments. confirmed is not an approval field and is rejected.
Show the action details to the user and obtain approval in the trusted App9/Jaxia flow.
Within five minutes, call the target tool with the exact same arguments, the same idempotency key, and the returned approval_id.
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
{
"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
{
"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
| Condition | HTTP / JSON-RPC | What to do |
|---|---|---|
| Missing or invalid bearer credential | 401 problem+json | Replace or rotate the credential; do not retry blindly |
| Brand missing, conflicting, forbidden, or not found | 400, 403, or 404 problem+json | Verify key binding and X-App9-Post-Brand |
| Disallowed browser Origin | 403 / -32001 | Move the call to an allowed server-side MCP host |
| Malformed JSON | 400 / -32700 | Correct the JSON body |
| Invalid JSON-RPC request or ID | 400 / -32600 | Use jsonrpc 2.0 and a non-null string or integer id |
| Request body over 128 KiB | 413 / -32600 | Reduce the request body below the 128 KiB limit |
| Required MCP header missing or not matching the body | 400 / -32020 | Send MCP-Protocol-Version, Mcp-Method, and Mcp-Name exactly matching the body |
| Unsupported protocol version | 400 / -32022 | Choose a version from the returned supported list |
| Unknown method | 404 / -32601 for modern requests | Use server/discover, tools/list, or tools/call |
| Invalid tool arguments | 400 / -32602 for modern requests | Validate against the schema returned by tools/list |
| MCP request rate limited | 429 / -32603 | Back off and retry safely with the same mutation key when applicable |
| MCP rate limiter unavailable | 503 / -32603 | Fail closed and retry later |
| Remote media import limited | HTTP 200 tool result with remote_media_rate_limited | Use 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
App9 Post REST API reference — Interactive documentation for HTTP resources outside MCP.
App9 Post OpenAPI document — Machine-readable REST contract; it is separate from MCP tools/list.
OpenAI Codex MCP configuration — Current Codex transport, header, allowlist, and approval settings.
Claude Code MCP configuration — Current remote HTTP and environment-header configuration.
MCP 2026-07-28 release — Protocol changes including stateless requests and server/discover.
App9 Post capability discovery — Use live account capabilities before enabling assistant actions.
App9 Post API keys and scopes — Design and rotate least-privilege credentials.
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.