1. Home
  2. MCP server
MCP · Model Context Protocol

Run TikTok DM outreach from your LLM

The Hooka MCP server gives Claude, ChatGPT, Cursor or your own agent 26 tools over the Hooka API. With them it can connect TikTok accounts by QR, create and launch DM campaigns, work the inbox and manage your Do-Not-Contact list. Use the hosted server at mcp.hooka.to (sign in with OAuth or send an API key), or run it locally over stdio with npx hooka-mcp.

Last updated: September 30, 2026 · hooka-mcp 0.3.0 · By the Hooka team

What is the Hooka MCP server?

The Hooka MCP server is a Model Context Protocol server. It exposes Hooka's REST API v1 as tools your LLM can call. Every tool maps to exactly one endpoint of https://api.hooka.to/v1 and forwards your own credential (an API key or an OAuth token). The server stores no credentials, and the same server-side limits apply as when you call the API directly.

On top of the plain API calls, the server does a few things for the model:

  • It returns the connection QR code as an image, so the assistant can show it to you.
  • It sends an idempotency key on the write tools that support one, so a retried call doesn't create a second campaign or send a second DM.
  • It turns API errors into one readable line with the error code, the field at fault and the request id.

Tool titles and descriptions, server instructions, prompts, resources, and the notes and errors the server writes itself (missing key, timeout, network error) are in Spanish. Picking a prompt adds a Spanish message to the conversation. Errors returned by the Hooka API come through unchanged, in English, as do schema validation errors (-32602).

26
tools
12
read-only
3
guided prompts
0.3.0
documented version

How do I connect Hooka to Claude, ChatGPT or Cursor?

There are three ways to connect. All of them give access to the same 26 tools:

OptionWhere it runsCredentialTypical clients
Hosted + OAuthhttps://mcp.hooka.to/mcp (Streamable HTTP)Sign in to Hooka and approve accessClaude.ai, Claude Desktop (Connectors), ChatGPT
Hosted + API keyhttps://mcp.hooka.to/mcp (Streamable HTTP)Header Authorization: Bearer hk_live_…Claude Code, Cursor, custom agents
LocalYour machine, over stdio (npx hooka-mcp, Node.js 20+)Env var HOOKA_API_KEYClaude Desktop config file, Cursor, any stdio client

Hosted, with OAuth (Claude.ai, Claude Desktop, ChatGPT)

Add Hooka as a custom connector and paste this URL. There is nothing to install and no key to copy:

https://mcp.hooka.to/mcp

The client discovers Hooka's authorization server, registers itself and sends you to Hooka to sign in and approve access (OAuth 2.1, PKCE S256 required). Then the tools appear. Some details worth knowing:

  • The approval gives full access to your workspace, the same as a live API key. The one exception is API key management, which OAuth tokens can't do.
  • Access tokens last 30 minutes. The client renews them with a refresh token, which lasts 60 days and is rotated on every use.
  • The sign-in and consent screen is in Spanish only.

Hosted, with an API key (Claude Code, Cursor, custom agents)

Clients that can send HTTP headers can skip OAuth and send your key. In Claude Code:

claude mcp add --transport http hooka https://mcp.hooka.to/mcp --header "Authorization: Bearer hk_live_..."

In Cursor (mcp.json) and other clients that use the url + headers format:

{
  "mcpServers": {
    "hooka": {
      "url": "https://mcp.hooka.to/mcp",
      "headers": { "Authorization": "Bearer hk_live_..." }
    }
  }
}

The hosted server doesn't check the key when you connect. An invalid or revoked key still lists the tools, and then every tool call returns 401 authentication_error/invalid_api_key. To check your setup, ask the assistant to call get_workspace.

Local, over stdio (npx)

The npm package hooka-mcp runs the same server on your machine, speaking stdio. Add this to your client's MCP config:

{
  "mcpServers": {
    "hooka": {
      "command": "npx",
      "args": ["-y", "hooka-mcp"],
      "env": { "HOOKA_API_KEY": "hk_live_..." }
    }
  }
}

In Claude Desktop this goes in claude_desktop_config.json. That file only loads local servers, so use a connector for the hosted URL. In Cursor, use the same command, args and env. Requires Node.js 20 or later. The local server reads these environment variables:

  • HOOKA_API_KEY (required): your key. Without it the server still starts, but every tool returns an error asking for the key.
  • HOOKA_API_BASE (optional): the API base URL. Default https://api.hooka.to.
  • HOOKA_API_TIMEOUT_MS (optional): the timeout for each API call. Default 30000.

This page documents hooka-mcp 0.3.0, the version behind the hosted server. Older 0.1.x releases have 23 tools and don't include the QR image, the verification tool or the retry behaviour described here. Before you run it locally, check which version npx will fetch with npm view hooka-mcp version. If it prints 0.1.x, 0.2.0 isn't on npm yet: use the hosted server until it is. On connect, the server also reports its version to your client (serverInfo).

Which credential should the agent use?

CredentialHow you get itWhat the agent can do
hk_live_…The Developers page of your Hooka dashboard (it always creates live keys), or POST /v1/api_keysRead and write: real actions on your real TikTok accounts
hk_test_…POST /v1/api_keys with {"mode":"test"}, authenticated with a live keyRead only: every tool that writes returns 403 permission_error/test_key_read_only
OAuth tokenIssued when you approve the connector (hosted server only)Read and write, like a live key. It can't manage API keys.

There is no sandbox. A test key reads your real workspace; it doesn't simulate sending. It works well when an agent should only look: reporting, inbox summaries, campaign stats. To create one, call the API with a live key:

curl https://api.hooka.to/v1/api_keys \
  -H "Authorization: Bearer hk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"mode":"test","label":"agent read-only"}'
{
  "id": "key_6a1e…",
  "object": "api_key",
  "mode": "test",
  "prefix": "hk_test_1a2b3c",
  "secret": "hk_test_1a2b3c…"
}

The secret is shown only once. With a test key, reads work, including get_workspace. Every write fails the same way, whatever the tool:

403 permission_error/test_key_read_only: Test keys (hk_test_) are read-only: Hooka has no sandbox, so any write would act on your real TikTok accounts. Use an hk_live_ key to create, start or send. [request_id req_…]

All the keys and OAuth tokens of a workspace share its rate limit of 300 API requests per minute, together with any direct API traffic.

What can the agent do? The 26 tools

Each tool calls one endpoint of the Hooka API v1. Read tools carry the MCP readOnlyHint annotation. Tools marked destructive carry destructiveHint because they send real DMs, cut connections or re-allow contact. Clients that honour annotations can ask you before running them.

ToolWhat it doesAPI callKind
Senders (TikTok accounts) · 7
list_sendersEvery TikTok account (sender) in the workspace, in any status, with status_reason, health, daily_limit and sent_today. Optional status filter.GET /v1/sendersread
get_senderOne sender. This is the tool to poll during a connection: while connection.status is requires_scan, the QR comes back as an image.GET /v1/senders/{id}read
create_senderStarts connecting a TikTok account by QR (no password). country is the account's country: it gets its own dedicated residential IP there, and the country can't be changed later. It returns the sender in awaiting_scan with no QR yet. It takes a seat and cancels any other connection in progress in the workspace.POST /v1/sendersdestructive
refresh_sender_connectionA new connection attempt (new QR) for a sender that isn't connected: expired, disconnected, cooldown or banned. It keeps the sender's own IP.POST /v1/senders/{id}/connection/refreshdestructive
verify_sender_connectionCompletes TikTok's "Verify it's you" step after the scan: it chooses email or phone, submits the code or asks for a new one.POST /v1/senders/{id}/connection/verifywrite
update_senderChanges a sender's label, the only field the API can edit.PATCH /v1/senders/{id}write
delete_senderDisconnects an account. The sender stays listed as disconnected and its seat is freed. It also cancels a QR connection in progress for that sender. Running campaigns left with no connected sender are paused, and pending follow-ups that only this account could send are skipped (sender_unavailable).DELETE /v1/senders/{id}destructive
Campaigns · 8
list_campaignsCampaigns, newest first. Archived ones aren't listed.GET /v1/campaignsread
get_campaignOne campaign with live stats: pending, processing, sent, failed, skipped, replied, reply_rate.GET /v1/campaigns/{id}read
create_campaignCreates a draft. It takes a message or up to 20 variants, up to 10 follow-ups, up to 1,000 targets, up to 10 senders and pacing settings. It sends nothing.POST /v1/campaignswrite
add_targetsAdds up to 1,000 targets per call. On a running campaign they start getting DMs within seconds. A completed campaign reopens (resumed: true) if one of its senders is connected. If none is, the response carries resume_blocked_reason: "no_connected_sender": reconnect a sender, then call start_campaign or resume_campaign.POST /v1/campaigns/{id}/targetsdestructive
list_targetsTargets in the order they were added, with their status and a stable error code when one failed or was skipped. Optional status filter.GET /v1/campaigns/{id}/targetsread
start_campaignStarts sending real DMs. Works on a draft, a paused campaign, or a completed one that still has pending targets.POST /v1/campaigns/{id}/startdestructive
pause_campaignPauses a running campaign. A DM already being sent can still go out.POST /v1/campaigns/{id}/pausewrite
resume_campaignResumes sending on a paused campaign, or a completed one that still has pending targets.POST /v1/campaigns/{id}/resumedestructive
Inbox · 4
list_conversationsConversations by latest activity. Optional status and sender_id filters.GET /v1/conversationsread
get_conversationOne conversation: which sender, the other person, status, unread count and last message.GET /v1/conversations/{id}read
get_messagesThe messages of a conversation, oldest first (in = them, out = you).GET /v1/conversations/{id}/messagesread
send_replyQueues a reply of 1 to 2,000 characters. It goes out as a real DM from the conversation's sender within seconds.POST /v1/conversations/{id}/messagesdestructive
Do-Not-Contact list · 3
list_suppressionsThe usernames that must never be contacted, with their reason (manual, opt_out…).GET /v1/suppressionsread
add_suppressionsAdds 1 to 1,000 usernames. Their pending targets in every campaign are skipped right away.POST /v1/suppressionswrite
remove_suppressionRemoves one username. That can re-allow DMs to someone who asked to stop.DELETE /v1/suppressions/{username}destructive
Events, webhooks and workspace · 4
list_eventsWorkspace events, the same ones webhooks deliver, kept for 30 days. Browse newest first, or poll new events with after.GET /v1/eventsread
list_webhook_endpointsYour webhook endpoints, without their secrets.GET /v1/webhook_endpointsread
create_webhook_endpointCreates an endpoint (public https:// URL). It returns the whsec_… signing secret once.POST /v1/webhook_endpointswrite
get_workspaceWhoami: workspace id, default country for new accounts (exit_ip.region) and seats.GET /v1/workspaceread

Paginated lists take limit (1-100, default 50) and starting_after (the previous page's next_cursor). Pass it while has_more is true. list_webhook_endpoints isn't paginated.

Examples

Connect a TikTok account

You say: "Connect my TikTok account and label it Shop ES." The agent then goes through these steps:

  1. get_workspace: it checks that a seat is free (seats.used < seats.limit; a null limit means no cap) and reads the default country (exit_ip.region). If the account is from another country, the agent asks you.
  2. create_sender {"label":"Shop ES","country":"ES"}: the sender comes back in awaiting_scan with connection.status "pending" and no QR yet. The account gets its own dedicated residential IP in that country; while it's being prepared, exit_ip.status is "provisioning".
  3. get_sender every 3-5 seconds. connection.status moves through pending, requires_scan, scanned, sometimes verification_required, and ends with the sender connected.
  4. At requires_scan, the tool result carries the QR as an image, and you scan it with the TikTok app. The QR renews itself; the agent shows the latest one.
  5. At verification_required, it asks you where to receive the code and what the code is. Then it calls verify_sender_connection, for example {"id":"snd_…","action":"submit_code","code":"482913"}.

This is what a get_sender result looks like while the QR is ready (abridged):

{
  "content": [
    { "type": "text", "text": "{ \"id\": \"snd_…\", \"object\": \"sender\", \"status\": \"awaiting_scan\", \"connection\": { \"status\": \"requires_scan\", \"qr_code\": \"[QR adjunto como imagen en esta respuesta: …]\", … } }" },
    { "type": "image", "data": "iVBORw0KGgo…", "mimeType": "image/png" }
  ]
}

The attempt has failed if connection.status becomes expired or failed (connection.error says why), or if the sender becomes disconnected (status_reason says why). The agent stops polling and can offer refresh_sender_connection for a new QR. Connect accounts one at a time, because each new attempt cancels the one in progress.

Create a campaign, then start it only after you confirm

You say: "Draft a campaign for these two creators with a follow-up after 3 days, 90 seconds between DMs and at most 40 a day." The agent calls:

{
  "name": "create_campaign",
  "arguments": {
    "name": "Creators ES - October",
    "message": {
      "variants": [
        "Hi {{first_name}}, loved your latest video. Open to a paid collab?",
        "Hey {{first_name}}, we're looking for creators like you. Interested?"
      ]
    },
    "follow_ups": [
      { "wait_days": 3, "variants": ["{{first_name}}, did you get a chance to see my message?"] }
    ],
    "targets": [
      { "username": "@maria.ugc", "variables": { "first_name": "Maria" } },
      { "username": "@leo_creates", "variables": { "first_name": "Leo" } }
    ],
    "settings": { "rate_limit_seconds": 90, "daily_limit": 40 }
  }
}

The result is the campaign in "status": "draft", plus targets_accepted, target_ids and targets_skipped. Nothing has been sent at this point. Some notes on the arguments:

  • Without settings.sender_ids, the campaign uses up to 10 of the oldest connected senders of the workspace.
  • daily_limit caps the whole campaign per day. Each account also keeps its own daily cap.
  • Follow-ups go out from the same account that sent the first DM, and they stop when the person replies.

Next, the agent shows you a summary and asks before it calls start_campaign {"id":"cmp_…"}. Its server instructions and the launch_campaign prompt both tell it to do that.

Triage the inbox with a read-only key

With an hk_test_ key, you can let an agent run list_conversations {"status":"open"} and get_messages, sort replies into interested, question, not interested or spam, and draft answers for you. It can't send them: send_reply returns 403 test_key_read_only. You review the drafts and send them yourself, or switch to a live key.

Check what happened overnight

"What happened in my campaigns since yesterday?" The agent reads list_events, for example with type set to target.replied or sender.status_changed, and get_campaign for the stats. The hooka://event-types resource explains what each event type means.

Prompts, resources and server instructions

Prompts (guided workflows the user can pick in clients that support them):

  • launch_campaign (optional argument goal). It checks for a connected account, proposes a message with 2-3 variants and follow-ups, creates the draft, and asks for your explicit confirmation before start_campaign.
  • triage_inbox. It pages through open conversations, classifies them and drafts short replies. It waits for your OK before any send_reply.
  • connect_account (optional argument label). It runs the QR flow above, including TikTok's verification step.

Resources (read-only text; no API call, so they work with any key):

  • hooka://event-types lists the 12 webhook event types plus ping, with what each one means.
  • hooka://api-reference covers the base URL, id prefixes, pagination, idempotency, the error format and the rate limit.

Server instructions are sent on initialize. They cover the following: test keys are read-only, which tools send real DMs and need your confirmation, the connection flow, pagination, idempotency and the error format.

What can't the agent do?

The MCP server can only do what the API allows. Some things are dashboard-only or not exposed at all:

  • Billing, plan and the default country. Subscribing and changing the default country are done in the dashboard; get_workspace only reads them. An account's own country is set with create_sender and can't be changed later (Hooka support can move it).
  • Per-account daily cap and warm-up. These are dashboard-only. The cap defaults to 30 DMs a day per account (adjustable up to 100). A campaign's daily_limit can't raise it.
  • Editing campaigns. No endpoint edits, archives or deletes a campaign. Through the API, a campaign's message, follow-ups and senders can't change after creation (the dashboard's campaign editor can change all of them).
  • Conversation status. open, interested and the other statuses are read-only through the API.
  • Webhook maintenance. Testing, editing or deleting an endpoint, and fetching a single event by id, exist in the API but not as tools.
  • API keys. There is no tool for them. OAuth tokens get 403 oauth_token_not_allowed on the key routes.
  • Comment-to-DM automations. They're managed in the dashboard and aren't exposed through the API.
  • Settings that don't exist. create_campaign.settings is strict, so sender_pool, stop_on_reply or any other unknown key is a validation error. Follow-ups always stop when the person replies.
  • Other platforms. TikTok only.
  • Push notifications. The hosted server is stateless and sends no notifications. Poll list_events, or create a webhook endpoint for your backend.

What "sent" means. send_reply answers {"id":"rpl_…","object":"message","status":"queued"}, and the DM goes out within seconds. A target counts as sent when TikTok accepted the message and showed no "not delivered" notice in the next few seconds. It doesn't prove the person read it. When the send can't be confirmed, the target ends failed with send_unconfirmed and is never retried, to avoid a duplicate DM.

Is it safe to let an AI send DMs?

Four actions send real, irreversible DMs: start_campaign, resume_campaign, send_reply, and add_targets on a campaign that is running or completed. The server instructions tell the model to ask for your explicit confirmation before each of them.

Eight tools carry the destructiveHint annotation:

  • the four above;
  • create_sender and refresh_sender_connection, which cancel other connections in progress;
  • delete_sender;
  • remove_suppression, which can re-allow contact with someone who opted out.

Whether a client asks before a destructive tool depends on the client and your settings.

Hooka applies the same limits whoever calls it: dashboard, API or MCP.

  • A daily cap per account.
  • Spacing between DMs from the same account: rate_limit_seconds, 30-3600, default 60.
  • An optional daily cap per campaign.
  • Follow-ups that stop when the person replies.
  • Deduplication across campaigns (on by default).
  • The Do-Not-Contact list.

Accounts connect by QR; Hooka never asks for a TikTok password.

Honest note: TikTok DM automation runs outside TikTok's official API and carries real risk, and no tool can promise zero bans. The MCP server doesn't change that. It gives your LLM a structured way to drive Hooka with your confirmation. Use dedicated outreach accounts and start with low volumes.

Retries, idempotency and errors

These 10 write tools accept an optional idempotency_key and always send an Idempotency-Key header:

  • create_sender, refresh_sender_connection, verify_sender_connection
  • create_campaign, add_targets, start_campaign, pause_campaign, resume_campaign
  • send_reply, create_webhook_endpoint

How the key works:

  • Derived key. Without idempotency_key, the key comes from the tool name, the arguments and the current fixed 10-minute clock window (12:00-12:10, 12:10-12:20…). An identical call in the same window returns the stored result with a note instead of running again. In the next window it runs again, even seconds later. Two exceptions, where the MCP moves to the next key in the series (up to 5 per window) and the call runs again: the stored result was a 4xx error, or, for create_sender and refresh_sender_connection, the stored connection attempt has already ended (the sender is no longer awaiting_scan, or connection.status is expired or failed). So repeating the same create_sender call in the same window, for another account after the first one connected, does start a new connection. An explicit idempotency_key has no such exceptions.
  • Timeouts and network errors. The error text includes the exact key that was sent, so the call can be retried with idempotency_key set to it: if the original finished you get its stored result instead of a second run; if it is still running you get 409 idempotency_key_in_use (wait and retry with the same key). A request stuck for more than 5 minutes is treated as dead and runs again. Hooka keeps keys for 24 hours.
  • 409 idempotency_key_in_use. The original request is still running. Wait a few seconds and retry with the same key.
  • Repeating on purpose. To pause again after resuming, or to ask for another verification code, pass a new idempotency_key.
  • Tools without a key. add_suppressions (naturally idempotent), update_sender, delete_sender and remove_suppression send none.

Tool errors come back with isError: true and a single line of text:

<status> <type>/<code>: <message> (param: <field>) [request_id req_…]
409 conflict_error/invalid_campaign_state: Cannot start a campaign that is running. [request_id req_…]
400 invalid_request_error/no_sender_available: Cannot start this campaign: none of its senders is connected. Reconnect one (POST /v1/senders/{id}/connection/refresh) and retry. [request_id req_…]
429 rate_limit_error/rate_limited: Too many requests. Retry in 7 s. (Retry-After: 7 s) [request_id req_…]

The server checks arguments against each tool's schema before calling the API. A bad argument returns MCP error -32602: Input validation error: …. Unknown top-level arguments are dropped silently. create_campaign.settings is the exception: there, an unknown key is an error. Each API call times out after 30 seconds, and the error then says the action may or may not have been applied.

Can I call the hosted server from my own code?

Yes. https://mcp.hooka.to/mcp speaks MCP Streamable HTTP in stateless mode. It follows these rules:

  • Send JSON-RPC with POST /mcp. GET and DELETE return 405, and there is no Mcp-Session-Id.
  • Send Accept: application/json, text/event-stream. Without both types you get 406.
  • Responses come back as server-sent events.
  • Always send an arguments object, even an empty one: a tools/call without it fails validation.
curl -N https://mcp.hooka.to/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer hk_live_..." \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_workspace","arguments":{}}}'
event: message
data: {"result":{"content":[{"type":"text","text":"{\n  \"id\": \"ws_…\",\n  \"object\": \"workspace\",\n  \"name\": \"Default\",\n  \"created\": 1790726400,\n  \"exit_ip\": {\n    \"status\": \"ready\",\n    \"region\": \"ES\",\n    \"suggested_region\": null,\n    \"per_sender\": true\n  },\n  \"seats\": {\n    \"limit\": 3,\n    \"used\": 1\n  }\n}"}]},"jsonrpc":"2.0","id":1}

A tool result is a text item holding the API's JSON (pretty-printed), plus an image item when a QR is attached. When the API returns no body, the text is OK. On a replayed call (the same idempotent call again inside the key's 10-minute window, or a repeated idempotency_key), the first text item is a Spanish NOTA: … line, the JSON is the next text item, and no QR image is attached. For create_sender, refresh_sender_connection, verify_sender_connection, start_campaign, pause_campaign and resume_campaign, that note also carries the current state (Estado ACTUAL: …). Look for the JSON item instead of assuming content[0]. There is no structuredContent. A real client normally calls initialize first, to receive the server instructions.

Other endpoints on the hosted server:

  • GET https://mcp.hooka.to/health returns {"ok":true,"service":"hooka-mcp","version":"0.3.0","transport":"streamable-http"}.
  • OAuth clients discover the authorization server through /.well-known/oauth-protected-resource/mcp. The authorization server is https://api.hooka.to. It supports dynamic client registration and requires PKCE S256, and the resource is https://mcp.hooka.to/mcp.

If you only need HTTP, you can skip MCP and call the REST API directly. Full reference: app.hooka.to/docs.html.

Frequently asked questions

What is the Hooka MCP server?

A Model Context Protocol server that exposes the Hooka v1 API as 26 tools, so Claude, ChatGPT, Cursor or your own agent can connect TikTok accounts, create and launch DM campaigns, work the inbox and manage the Do-Not-Contact list in natural language. Each tool calls one Hooka API endpoint with your credential, and the server stores no credentials. This page documents version 0.2.0 (MIT license); older 0.1.x releases on npm have 23 tools.

How do I connect Hooka to Claude, ChatGPT or Cursor?

Hosted: add https://mcp.hooka.to/mcp as a custom connector in Claude.ai, Claude Desktop or ChatGPT and sign in to Hooka (OAuth 2.1). In clients that send headers, such as Claude Code and Cursor, use the same URL with Authorization: Bearer hk_live_.... Local: run npx -y hooka-mcp over stdio with HOOKA_API_KEY set to your key (Node.js 20+). The 26 tools need hooka-mcp 0.2.0 or later: check with npm view hooka-mcp version.

Do I need an API key?

Not with the OAuth connector: you sign in to Hooka and approve access. The header option and the local server need a key: create an hk_live_ key on the Developers page of your dashboard. hk_test_ keys are read-only: every tool that writes returns 403 permission_error/test_key_read_only.

Can I try it without sending real DMs?

There is no sandbox. An hk_test_ key lets an agent read your real workspace without changing anything. With a live key, create_campaign only creates a draft and nothing is sent until start_campaign. The tools that send real DMs are start_campaign, resume_campaign, send_reply, and add_targets on a running or completed campaign.

Is it safe to let an AI send TikTok DMs?

The server marks 8 tools as destructive (they send DMs, cut connections or re-allow contact), so clients that honour MCP annotations can ask you first, and its instructions tell the model to get your explicit confirmation before sending. Hooka applies the same limits to every caller: a daily cap per account, spacing between DMs, follow-ups that stop when the person replies, and the Do-Not-Contact list. TikTok automation always carries some risk, so review each action before you confirm it.

Can the agent show me the QR code to connect an account?

Yes. create_sender starts the connection and the agent polls get_sender every 3-5 seconds. When connection.status is requires_scan, the QR comes back as an image in the tool result, and you scan it with the TikTok app. If TikTok then asks you to verify it's you, the agent completes that step with verify_sender_connection and the code you receive.

What does it cost?

The MCP server is free (MIT license). Connecting TikTok accounts and sending DMs requires an active Hooka subscription, priced per connected TikTok account. Without one, create_sender returns 403 permission_error/no_active_subscription.

Keep reading

Let your LLM run the outreach

Cancel anytime. We never ask for your TikTok password. Secure payment with Stripe.