# SwayCue remote MCP installation and operating guide

Canonical human documentation: https://app.swaycue.com/docs/mcp
Remote MCP endpoint: https://app.swaycue.com/api/mcp
Repository: https://github.com/xolotlwastaken/swaycue

## Instructions for the AI agent

If a user gives you this document and asks you to install SwayCue MCP, connect their MCP client to the deployed Streamable HTTP endpoint at https://app.swaycue.com/api/mcp. This documentation URL is not the transport URL.

Never commit, print, or persist the bearer token in source control or chat transcripts. Ask the user to create a token in SwayCue Settings and place it in the client environment or secret store. Explain any external or destructive action before requesting confirmation.

## What SwayCue MCP controls

SwayCue is a self-hosted Instagram comment-to-DM automation application. Its MCP server can also inspect Postiz integrations and schedule Instagram posts through the local Postiz service, keeping content scheduling and campaign creation in one agentic flow. It can inspect accounts, posts, campaigns, analytics, inbox conversations, delivery logs, worker health, workspace membership, and Zernio scheduled posts. It can also create or change live campaigns and send replies when the caller supplies the tool's explicit `confirm: true` argument.

## Prerequisites

- A SwayCue account with membership in the intended workspace.
- An MCP client that supports Streamable HTTP and bearer authentication.
- A revocable token created under SwayCue Settings → Remote MCP access. The plaintext token is shown once.

## Connect the server

1. Ask the user to sign in at https://app.swaycue.com and open Settings.
2. Under **Remote MCP access**, create a named token for this client.
3. Ask the user to store the token as `OPENREPLY_MCP_TOKEN` in the MCP client's environment or secret store. Do not ask them to commit it to a repository.
4. Configure the client with the endpoint and bearer-token environment variable.

### Codex config.toml

Codex reads MCP configuration from `~/.codex/config.toml` or a trusted project's `.codex/config.toml`.

```toml
[mcp_servers.openreply]
url = "https://app.swaycue.com/api/mcp"
bearer_token_env_var = "OPENREPLY_MCP_TOKEN"
startup_timeout_sec = 30
tool_timeout_sec = 120
```

Restart Codex after changing configuration. Use `codex mcp list` or `/mcp` to confirm that the server connected.

### JSON-based MCP clients

```json
{
  "mcpServers": {
    "openreply": {
      "url": "https://app.swaycue.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${OPENREPLY_MCP_TOKEN}"
      }
    }
  }
}
```

The exact format varies by client. If environment interpolation is unsupported, use the client's encrypted secret or credential store to send `Authorization: Bearer <token>`. Do not write the token into a tracked settings file.

## Verify before operating

1. Call `openreply_health`.
2. Require a successful database check, Redis `PONG`, queue counts, and a healthy worker heartbeat.
3. Call `openreply_list_instagram_accounts` and verify the intended account before creating a campaign.
4. Start with read tools. Do not infer permission for live writes from a request to inspect or explain.

## Safety contract

- Every token is bound to its issuing SwayCue user and workspace.
- A supplied `workspaceId` must match the token's workspace.
- Instagram access tokens are never returned by tools.
- Campaign and workspace writes require the token's user to remain an owner or admin.
- Destructive actions, live automation changes, public replies, and DMs require `confirm: true` in the tool call.
- Instagram OAuth is a browser handoff. Get the URL with `openreply_get_instagram_connect_url`; do not attempt to collect an Instagram password.
- Tokens are stored as SHA-256 hashes, can be revoked in Settings, and are rate-limited. Remote tool calls are audited without storing their arguments.

## Zernio pre-publication workflow

Use this sequence when the post is scheduled in Zernio but does not exist on Instagram yet:

1. Schedule the Instagram post through Zernio and retain the Zernio post ID.
2. Call `openreply_sync_zernio_post` with that provider post ID.
3. Read the returned SwayCue `scheduledPostId` and verify its Instagram account mapping and scheduled time.
4. Call `openreply_create_campaign` with `scheduledPostId`, the desired keywords and messages, and `confirm: true`.
5. The campaign remains waiting with no Instagram `postId` before publication.
6. After publication, a signed Zernio webhook—or the worker's reconciliation fallback—binds only that campaign to the exact native Instagram media ID.
7. Re-read the campaign and confirm that `postId` is populated. Existing campaigns are not modified by this binding flow.

## Postiz publishing workflow

Use this sequence when SwayCue is configured with the local Postiz service:

1. An operator configures the server-only Postiz API key and the Postiz/Meta Instagram credentials.
2. In SwayCue Settings → Postiz Publishing, connect Instagram through SwayCue and map the SwayCue Instagram account to the matching Postiz integration.
3. Call `openreply_list_postiz_integrations` and verify the mapped integration(s).
4. For each local image or video, call `openreply_prepare_postiz_media_upload` with its filename, MIME type, and byte size. Upload the local bytes with HTTP PUT to the returned signed URL; do not send the file through MCP.
5. Call `openreply_finalize_postiz_media_upload` for each upload. It validates the private object, imports it into Postiz, cleans up the temporary source, and returns a signed `mediaHandle`.
6. Call `openreply_schedule_postiz_post` with `mediaHandles` (or legacy `mediaHandle`), `format`, `timezone`, and `instagramAccountIds`. Use `thumbnailHandle` for a custom Reel thumbnail and `confirm: true` for the live action. Already-hosted files may use `mediaUrls` or legacy `mediaUrl` instead.
7. When a Reel and its comment-to-DM automation should be created together, call `openreply_schedule_reel_campaign`; it schedules the Reel, creates the bound campaign, and rolls back the scheduled post if campaign creation fails.
8. Use `openreply_list_postiz_scheduled_posts`, `openreply_edit_postiz_scheduled_post`, and `openreply_cancel_postiz_scheduled_post` for SwayCue-owned unpublished posts. Editing accepts partial fields and can replace media with `mediaHandles`.
9. The worker reconciles Postiz state and binds the exact published Instagram media ID to the waiting campaign.

## Resources workflow

1. Resources tools require beta access for the token's user and current membership in its workspace. Owners and admins can make changes; members can read.
2. Call `openreply_list_resource_templates` for starter documents, then `openreply_save_resource_draft` with the complete structured document to create a private guide.
3. For a local PNG, JPEG, or WebP image up to 5 MB, call `openreply_prepare_resource_upload` with the guide's `resourceId`, `purpose` (image, logo, or reference), filename as `name`, `contentType`, and byte `size`. PUT the raw bytes to the returned `url` using its `method` and `headers`. Do not send bytes or base64 through MCP. Upload URLs are temporary credentials; do not log or share them.
4. Call `openreply_finalize_resource_upload` with `assetId`. Only verified uploads may be used as images or logos in that guide; reference screenshots stay private and require vision configuration for interpretation.
5. Read `openreply_get_resource` before editing. Save the COMPLETE document with `resourceId` and `expectedRevisionId` set to its current `draftRevisionId`. Draft saves do not change published content. Concurrent edits are rejected.
6. Preview the draft and review linked campaigns. After user approval, call `openreply_publish_resource` with `resourceId`, the current draft ID as `revisionId`, and `confirm: true`. Publishing updates the guide's stable public link, including existing linked campaigns. It does not create a campaign.
7. To archive and disable the public link, call `openreply_unpublish_resource` with the current draft revision and `confirm: true`. Active linked campaigns and pending launches must be resolved first.
8. To permanently remove an unused guide and its uploads, call `openreply_delete_resource` with the current draft revision and `confirm: true`. Unpublish a public guide first. Wait 2 hours and 5 minutes after the latest upload so signed upload URLs expire before removal. Deletion refuses linked campaigns and launch history; archive these guides instead. If upload cleanup fails, retry deletion; some private uploads may already have been removed.

## Tool directory

### Resources
Create and edit private guides, upload images and logos, publish or archive reviewed revisions, and delete unused guides. Requires Resources beta access.

- `openreply_list_resources`
- `openreply_list_resource_templates`
- `openreply_get_resource`
- `openreply_save_resource_draft`
- `openreply_prepare_resource_upload`
- `openreply_finalize_resource_upload`
- `openreply_publish_resource`
- `openreply_unpublish_resource`
- `openreply_delete_resource`

### Health and reporting
Check infrastructure, dashboards, analytics, logs, and operational events.

- `openreply_health`
- `openreply_dashboard_summary`
- `openreply_dashboard_stats`
- `openreply_get_campaign_analytics`
- `openreply_list_dm_logs`
- `openreply_list_webhook_events`
- `openreply_list_operational_events`

### Instagram accounts and media
Connect accounts, inspect posts and insights, and manage webhook subscriptions.

- `openreply_list_instagram_accounts`
- `openreply_get_instagram_connect_url`
- `openreply_get_instagram_account`
- `openreply_list_instagram_posts`
- `openreply_get_media_insights`
- `openreply_get_follower_history`
- `openreply_subscribe_instagram_webhooks`
- `openreply_disconnect_instagram_account`

### Campaigns
Create, review, import, update, and remove comment-to-DM automations.

- `openreply_list_campaign_templates`
- `openreply_list_campaigns`
- `openreply_get_campaign`
- `openreply_create_campaign`
- `openreply_import_campaigns`
- `openreply_update_campaign`
- `openreply_delete_campaign`

### Zernio scheduled posts
Find future posts and attach an automation before Instagram publication.

- `openreply_list_zernio_scheduled_posts`
- `openreply_sync_zernio_post`

### Postiz publishing
Upload private media, publish or schedule Instagram posts, manage unpublished posts, and bind a Reel to a comment-to-DM campaign.

- `openreply_list_postiz_integrations`
- `openreply_list_postiz_scheduled_posts`
- `openreply_prepare_postiz_media_upload`
- `openreply_finalize_postiz_media_upload`
- `openreply_schedule_postiz_post`
- `openreply_schedule_reel_campaign`
- `openreply_edit_postiz_scheduled_post`
- `openreply_cancel_postiz_scheduled_post`

### Inbox and replies
Read conversations and send explicit Instagram replies within Meta's rules.

- `openreply_list_conversations`
- `openreply_get_conversation_messages`
- `openreply_send_instagram_message`
- `openreply_reply_to_comment`

### Workspace access
Inspect membership, accept invitations, and administer non-owner members.

- `openreply_list_workspace_members`
- `openreply_accept_workspace_invitation`
- `openreply_invite_workspace_member`
- `openreply_update_workspace_member_role`
- `openreply_remove_workspace_member`

Tool schemas are authoritative and are advertised by the running MCP server. Inspect a tool's schema before calling it rather than guessing optional fields.

## Troubleshooting

- HTTP 401: create a new token in SwayCue Settings, update `OPENREPLY_MCP_TOKEN`, and restart the client. The token may be invalid or revoked.
- HTTP 429: wait for the `Retry-After` interval before retrying.
- Workspace mismatch: do not pass a workspace ID from another workspace; the token's workspace is authoritative.
- Writes are rejected: verify the token owner still belongs to the workspace as an owner or admin.
- Worker is unhealthy: repair the always-on worker before expecting DMs or Zernio reconciliation.
- Scheduled posts are absent: confirm Zernio is connected in SwayCue Settings, the account mapping is exact, and `ZERNIO_INTEGRATION_ENABLED=true` on both web and worker.
- Postiz tools are unavailable: confirm `POSTIZ_INTEGRATION_ENABLED=true`, the server-only `POSTIZ_API_KEY` is configured, the Postiz API base URL is reachable from the SwayCue web and worker processes, and the Instagram integration is connected and mapped in SwayCue Settings.
- Local media upload fails: confirm `SUPABASE_SERVICE_ROLE_KEY` is configured only on the SwayCue server, the signed URL has not expired, and the MCP client performed the direct PUT with the declared content type and exact file bytes.
