Agent
Tool reference
Every tool the MCP server exposes, what it takes, and what it returns.
Tools are listed only when your token carries the scope they need. An agent that cannot see a tool will not call it.
Every tool calls the same REST route the API uses. There is no second implementation, so a limit enforced over REST is enforced over MCP.
Scopes
| Name | Type | Description |
|---|---|---|
accounts:read | read | List connected channels. |
posts:read | read | Read publishing history, replies and insights. |
posts:write | write | Publish, delete, reply, hide and upload. |
offline_access | oauth | Keep the agent connected after the browser closes. |
accounts:read reads connected channels. posts:read reads publishing history. posts:write publishes, deletes, replies and uploads. offline_access keeps the connection alive after you close the browser.
Tools
| Name | Type | Description |
|---|---|---|
list_accounts | accounts:read | Connected channels with their ids. Call it before publishing. |
select_channels | accounts:read | Pick which channels to post to, and show the result to the person before publishing. |
list_platforms | accounts:read | Every channel, its limits, and the JSON Schema of what options.<channel> takes. |
list_posts | posts:read | Recent publishes made through uplika, scheduled and draft posts included. status, since, until and sort=scheduled answer "what is going out this week". |
list_channel_posts | posts:read | What is on the channel right now, including posts written in the app. Each item carries a permalink. |
publish | posts:write | content, accountIds, optional mediaIds, and options for per-channel settings (the shape is in GET /v1/platforms under options; every channel takes content for a different text on that channel alone). scheduledAt holds the post and sends it at that time on any channel; draft: true saves it without sending. YouTube needs options.youtube.title and exactly one video. Instagram needs media on every post; a single video becomes a reel. Facebook publishes to a Page. Bluesky counts graphemes and also caps UTF-8 bytes; set options.bluesky.langs. On Naver Blog the body places media itself with  and @video(media:<id>), each alone on its line. |
open_post | posts:read | One post's text, reply thread and metrics in a single call. The right tool when someone hands you a link. |
get_post | posts:read | One publish and the status of each target. |
retry_post | posts:write | Retry only the targets that failed. force: true retries a naver_draft_unknown or naver_reply_unknown target. If another post with the same content and files is already on the same account, it answers duplicate_post and retries nothing; force: true sends it anyway. |
update_post | posts:write | Change a scheduled or draft post: content, mediaIds, accountIds, options, threadItems, scheduledAt (null turns it into a draft) or draft. Fields left out keep their value. A scheduled or draft chain can be changed too: content edits the first item, threadItems replaces the whole chain. Posts that went out can be rewritten in place on channels that support it (list_platforms feature update): a published Naver Blog post keeps its URL when you pass content, mediaIds or options (a post going only to Naver takes its media list from the new body's media: references when you leave mediaIds out; more than Naver takes answers naver_media_over_limit), and YouTube posts and the text of Facebook text, link and photo posts are edited in place. A post written directly on the channel (origin imported) is read from the channel first, so fields you leave out keep the channel's values; it can be edited on YouTube, on Facebook text, link and photo posts, and on Naver Blog (feature update_imported), elsewhere post_not_editable. On Naver the post is rewritten from its current source, where the blocks uplika cannot rewrite (photos, videos, cards, tables, styled headings and quotes, whole text blocks with colors, sizes or alignment) are @keep(...) lines; the first call answers naver_source_required with that source and its sourceVersion, and you send the edited source back as content with sourceVersion. That needs extension 0.7.8 or later. If the Naver edit screen shows a missing-image alert on an old post, this rewrites it in place and fixes it (same URL). Naver may answer 429 naver_rate_limited with retryAfterSeconds when its per-ID cap is hit. Editing a post that is already live is asynchronous: pass wait: true to hold the response for the result (up to 75 seconds), or read it with get_post. If the account that published the post was disconnected, this answers 409 account_disconnected; reconnect the same account and the post id comes back. |
publish_now | posts:write | Send a scheduled or draft post right now. Same function the scheduler runs, so it cannot go out twice. |
delete_post | posts:write | Delete the post from the channel. On a scheduled or draft post nothing is on any channel yet, so this cancels it and removes our record. If the account that published the post was disconnected, this answers 409 account_disconnected and deletes nothing on any channel; reconnect the same account first. |
list_replies | posts:read | The reply thread under a post. |
reply | posts:write | id, content, optional replyTo for a nested reply, optional secret (Naver Blog), optional wait to hold until the reply is out (up to 10 seconds). On Naver Blog the id can be a link to someone else's post. |
like | posts:write | id, optional replyTo to like a reply instead of the post. Idempotent, no unlike. Works on Naver Blog, where someone else's post works by link, and on Instagram accounts connected through Facebook that hold the like permission. Any other Instagram account and every other channel answer not_supported. A Facebook-connected Instagram account without that permission answers feature_in_review while the permission is in Meta app review. |
follow | posts:write | blog, optional mutual and message. Naver Blog only today: adds the blog as a neighbor, or sends a mutual-neighbor request that comes back pending. |
hide_reply | posts:write | replyId, postId, optional hide (default true). |
delete_reply | posts:write | replyId, postId. Deletes the comment for good, which hide_reply does not. Instagram and Facebook remove anyone's comment on your post; on Threads and Bluesky a reply is itself a post, so only the connected account's own replies go. Read features in GET /v1/platforms for which channels have it at all. |
get_insights | posts:read | Views, likes, replies, reposts, quotes, shares. |
get_quota | posts:read | How much of the 24 hour allowance is used. Live usage from the platform, not the static limits. |
diagnose_naver_blog | posts:read | Naver Blog only: reads any public blog's RSS feed (latest posts, at most 50) and says whether its titles are written for search: the share with a search intent word, how many start with a date or episode label, posts per month, categories, and the words repeated in titles as candidates to research. Carries the date range it read. Cached 24 hours; a miss spends one of 20 diagnoses a day. |
expand_naver_keywords | posts:read | Naver Blog only: expands up to 10 seed keywords one level through Naver autocomplete, returning each word with the seed it came from. The person's uplika Chrome extension looks them up in the background when it is on, otherwise our server does. When the answer is naver_autocomplete_unavailable, pass your own keyword list to research_naver_keywords. |
research_naver_keywords | posts:read | Naver Blog only: measures up to 60 keywords (searches, documents, posts per month), judges each, groups them into sets of one main plus two to five subs with the same intent, and gives every set a ready-to-paste Korean prompt. Runs against a time budget: partial: true with unmeasured[] means call again with the same keywords to finish from cache. Saved as a report (reportId), deduplicated within ten minutes. |
list_naver_keyword_reports | posts:read | The person's saved keyword research, newest first, with each set and its prompt. |
get_naver_keyword_history | posts:read | One keyword's measurement history, one point per measured day, newest first. |
search_youtube_videos | posts:read | Searches YouTube for a keyword and returns the top videos with views, subscribers, the views-to-subscribers ratio, Shorts or long-form, length and publish date, plus a Korean prompt that turns the table into title and hook ideas (pass topic to fill it in). The same search is cached 24 hours and does not count; a new one spends one of the person's daily searches and shared YouTube Data API units. Saved to the person's search history. Like the Naver research tools, answers research_tool_disabled with enableUrl until the person turns the tool on for connectors. |
get_publish_options | posts:read | What a channel needs to know before you publish to it. TikTok only: the creator nickname the post goes out as, the privacy levels this account may use right now, whether it can post at all, and its video length limit. Other channels answer not_supported. |
get_help | accounts:read | Troubleshooting answers: why something did not work and what to do. Pass code (an error code), query (a few words in any language), id, platform or locale; without arguments it lists every question. The same answers are on the Troubleshooting and FAQ page, and errors that have one carry its address in error.help. |
describe_grammar | posts:read | How to write the body for a channel that has its own markup. Naver Blog has one; every other platform answers not_supported. Without a topic you get an overview and the list of topics (directives, attributes, inline, blocks, highlight, media, limits); with one you get that section in full, including the mistakes that fail silently. The values come from the same grammar the publisher validates against. |
naver_layout | posts:read | Naver Blog only: previews how publish will lay the body out (the house form: underlined quote headings, a rule between sections, one photo per section). Text never changes. Pass options.naver_blog.layout: "as-is" to publish exactly what you wrote. Pass forms: "all" to get every preset laid out side by side and let the person choose. |
bridge_status | posts:read | Naver Blog only: whether the browser extension is online, when it was last seen, and per-OS commands that open Chrome in the profile that has the extension. publish and 503 bridge_offline carry the same object. |
list_naver_drafts | posts:read | Naver Blog only: the drafts (temp-saved posts) in the blog's draft box with logNo, title and last-saved time, newest first. A post published with options.naver_blog.draftOnly is one of them, and so is anything saved by hand in the Naver editor. Needs extension 0.7.2 or later; older ones answer 422 extension_outdated. |
publish_naver_draft | posts:write | Naver Blog only: publishes a draft from the draft box exactly as it is in Naver, keeping edits made by hand in the editor. Do not send content; category, tags and openType come from the draft unless you pass them. Same result shape as publish. A draft made through uplika (externalId draft:<logNo>) flips that post to published instead of creating a second one. |
refresh_account | posts:write | Re-read a channel's metadata. On Naver Blog this re-reads the categories through the browser extension and waits up to a minute for it. |
media_upload_link | posts:write | Ask the person to upload from their own device. Returns a short-lived link to hand them. |
media_upload_status | posts:read | Has the person uploaded yet? Returns waiting, ready or expired, plus every media id uploaded through that link. Ready images also come back as image blocks (previewIds keeps the order) so the agent can see each photo; a media with duplicateOf is the same bytes as another id |
media_from_url | posts:write | Attach a file that is already on the public web. One step, no upload. Share links from Drive and Dropbox return HTML, not the file. |
media_presign | posts:write | Get a media id and a one-time upload URL. |
media_complete | posts:write | Confirm the upload before publishing. |
update_media | posts:write | id, aiGenerated. Marks or clears the AI-made flag on an upload. Posts published or edited afterwards follow it: Naver Blog photos get the AI usage label, Instagram, YouTube and TikTok get their AI declaration. Posts already out do not change. |
list_automation_templates | posts:read | Every automation template with the params it takes. Start here before create_automation. |
create_comment_to_dm | posts:write | The common one: someone comments, the account DMs them. Creates a draft; nothing goes out until enable_automation. Instagram can gate delivery on following; Facebook cannot and Threads has no DMs. |
create_automation | posts:write | Create a draft from any template in list_automation_templates. For a flow no template covers, build the document and call put_automation on the draft. |
list_automations | posts:read | Automations in the workspace with their status, trigger kinds and run counts. |
get_automation | posts:read | One automation as a document: triggers, nodes, start, plus the version put_automation needs. |
put_automation | posts:write | Edit an automation by replacing its whole document, for flows no template covers or already edited on the canvas. A flow that still has its template form answers automation_is_template: change it with update_automation, or pass detachTemplate: true. Send the version you read or it answers version_conflict. |
enable_automation | posts:write | Turn it live. This is the moment messages start going to real people, so confirm first. Answers reconsent_required when the account lacks the DM permissions (reconnect it), feature_in_review when the flow uses a feature whose Meta permission is still in app review, and automation_catch_all_taken when another message flow without keywords is already live on the account. |
disable_automation | posts:write | Stop it. Runs already waiting for a button stay waiting; nothing new starts. |
send_to_past_comments | posts:write | Run a live comment automation on comments already on the post, the ones it missed. mode preview counts who would get it and who would not (older than 7 days, already answered, keyword, the same person's other comments), start queues the sends a few at a time, stop halts them. Instagram and Facebook. Preview first and confirm with the person before start. |
update_automation | posts:write | Edit an automation that still has its template shape: pass only the template params you want to change (objects such as deliver merge one level deep, null removes a field), plus the version you read. Also renames and turns it on or off. A flow edited on the canvas answers automation_not_template; use put_automation there. |
delete_automation | posts:write | Delete an automation and its runs. A live one is refused with automation_live unless force is true. |
duplicate_automation | posts:write | Copy an automation as a new draft with the same settings, optionally renamed. |
validate_automation | posts:read | Check a flow document for an account without saving it. Answers the same problems put_automation would. |
list_automation_versions | posts:read | Saved versions of one automation; read one with get_automation and version. |
list_automation_runs | posts:read | Runs of one automation with why each one stopped: the window, an opt-out, a human reply, the channel, or the AI step. |
list_conversations | posts:read | The inbox. Each conversation says whether the 24-hour messaging window is open. |
read_conversation | posts:read | One conversation with its messages, the contact, and how long the window has left. |
send_dm | posts:write | Reply inside the open window. Refuses with window_closed outside it; the account cannot start a conversation. |
get_contact | posts:read | One contact: name, tags, opt-out, window, and on Instagram whether they follow the account. Follower lists do not exist on any channel. |
list_mentions | posts:read | Threads only: posts where other people mentioned the account, from the mentions webhook. Mentions need a permission of their own; while it is in Meta app review and no connected account holds it, the list is empty and the response carries a notice saying so. |
approve_reply | posts:write | Threads only: approve or ignore a reply held by reply approval. Omit replyId to read the queue. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "publish",
"arguments": {
"content": "Shipped nested replies today.",
"accountIds": ["acc_01H..."],
"mediaIds": ["med_01H..."]
}
}
}Common questions
Why can my agent not see a tool?
Because the token does not carry that tool's scope. Tools are listed per scope, so a read-only connection never shows publish at all rather than failing when it is called.
Which tool should the agent call first?
list_accounts. publish takes account ids, not handles, so an agent that skips it either guesses or asks you a question it could have answered itself.
Do the tools behave differently from the REST API?
No. Every tool calls the same route REST does. There is no second implementation, so a limit enforced over REST is enforced over MCP.