{"serverId":"mcp-registry/dev.postlake/social","tools":[{"name":"cancel_post","fixed":{"name":"cancel_post","description":"Cancel a scheduled post so it never fires. Only works while it is still in 'scheduled' state. Once it has gone out, use delete_post to retract it from the networks instead. Cancelling is free and refunds nothing because nothing was spent.","inputSchema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The scheduled post id (post_…)."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: true, idempotentHint: true (inferred from the name).","verify":true}],"tokensBefore":123,"tokensAfter":123},{"name":"create_api_key","fixed":{"name":"create_api_key","description":"Create a new PostLake API key. Useful when you are setting up another agent or service that needs its own credentials rather than sharing yours. The key has full access to this account and is shown ONCE, in this response. It cannot be retrieved again, so pass it on or store it immediately. Give it a name that says what will use it, because the name is all anyone has later when deciding whether a key is still needed.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"What this key is for, e.g. 'nightly digest worker'. Shown in the dashboard."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":188,"tokensAfter":188},{"name":"create_post","fixed":{"name":"create_post","description":"Publish (or schedule) one post to one or more connected accounts. Call validate_post first: it is free and catches caption, media and option problems before anything is queued. TikTok photo posts need JPEG or WebP (PNG is rejected), at most 1080x1920, and a title via platformOptions.tiktok.title. Say WHERE to post the simple way, `profile` (a profile name) optionally narrowed with `platforms`, or the precise way, explicit `accounts` ids. Provide at least one. Returns a normalised result with per","inputSchema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"The post body (the master caption)."},"media":{"type":"array","items":{"type":"string"},"description":"Optional media ids (med_…) from a prior upload. Required by image/video-only platforms (Instagram, Pinterest, TikTok, YouTube)."},"thread":{"type":"array","items":{"type":"string"},"description":"Optional follow-on post bodies, published as posts 2..n in a thread. Check platform capabilities or validate first."},"profile":{"type":"string","description":"A profile name (from list_profiles) posts to every account that profile owns. The simplest way to address accounts; combine with `platforms` to narrow it."},"accounts":{"type":"array","items":{"type":"string"},"description":"Connected account ids (acc_…) from list_social_accounts. An alternative (or addition) to `profile`."},"mediaAlt":{"type":"array","items":{"type":"string"},"description":"Optional alt text (accessibility) per media item, aligned by index to `media`. Used by platforms that support it (Bluesky, X); others ignore it."},"timezone":{"type":"string","description":"Optional IANA timezone (e.g. Europe/London). Interprets a naive scheduledAt as wall time in that zone. Stored fire time is always UTC."},"platforms":{"type":"array","items":{"type":"string"},"description":"Optional filter: keep only these networks from the resolved set. e.g. with profile \"my-brand\", platforms [\"bluesky\"] posts to just its Bluesky account."},"scheduledAt":{"type":"string","description":"Optional ISO 8601 time to publish later. UTC with a trailing Z, or a naive local time together with `timezone`. Omit to publish now."},"firstComment":{"type":"string","description":"Optional 'first comment' posted as a reply the moment the post goes live. Classic use: keep hashtags/links out of the main caption. Instagram, TikTok and Pinterest have no comment API we can use, so they publish the post WITHOUT it, so check `firstComment` in get_platform_capabilities, or dry-run with validate_post, before relying on it. Best-effort: a failed comment does not fail the post, and each target reports what happened in its own `firstComment` field."},"textOverrides":{"type":"object","description":"Optional per-platform caption overrides, keyed by platform. e.g. { \"x\": \"short punchy version\", \"linkedin\": \"the long professional version\" }. Any platform without an override uses `text`. A blank override falls back to `text`.","additionalProperties":{"type":"string"}},"idempotencyKey":{"type":"string","description":"Optional. Reuse the same key to safely retry without double-posting."},"mediaOverrides":{"type":"object","description":"Optional per-platform media, keyed by platform, e.g. { \"tiktok\": [\"med_vertical\"], \"instagram\": [\"med_square\"] }. A platform with an override publishes ITS media instead of `media`; others fall back to `media`.","additionalProperties":{"type":"array","items":{"type":"string"}}},"platformOptions":{"type":"object","description":"Optional per-platform extras, namespaced by platform. e.g. { \"pinterest\": { \"boardId\": \"...\" }, \"youtube\": { \"privacyStatus\": \"unlisted\" } }.","additionalProperties":true},"mediaAltOverrides":{"type":"object","description":"Optional alt text for the override media, keyed by platform, aligned to mediaOverrides[platform].","additionalProperties":{"type":"array","items":{"type":"string"}}},"firstCommentOverrides":{"type":"object","description":"Optional per-platform first-comment overrides, keyed by platform (mirrors textOverrides). e.g. { \"instagram\": \"#tags here\" }.","additionalProperties":{"type":"string"}}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":1179,"tokensAfter":1179},{"name":"create_profile","fixed":{"name":"create_profile","description":"Create a profile: a named set of connected channels, usually one brand or client. Give the name as a person would write it, spaces and capitals included; it is slugified into the identifier you then pass as `profile` on other calls, and the response tells you what that became. Create one before connecting channels when an account is running more than one brand.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"e.g. \"Otaku Gems News\", which becomes otaku-gems-news."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":167,"tokensAfter":167},{"name":"delete_post","fixed":{"name":"delete_post","description":"Retract a post that is ALREADY LIVE, removing it from each network it went to. This is the undo: use it when a post was wrong. Reports every target separately, because a post can be gone from one network and still public on another, and networks that do not allow deletion through their API say so. Use cancel_post instead for something scheduled that has not gone out yet. Irreversible.","inputSchema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The post id (post_…)."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: true, idempotentHint: true (inferred from the name).","verify":true}],"tokensBefore":162,"tokensAfter":162},{"name":"delete_profile","fixed":{"name":"delete_profile","description":"Delete a profile. This ALSO disconnects every channel in it, and reconnecting each one needs its owner to approve access on that network again, which you cannot do for them. So it refuses by default while channels are attached and tells you exactly which ones would go. Only pass force after a human has agreed to lose them.","inputSchema":{"type":"object","required":["profile"],"properties":{"force":{"type":"boolean","description":"Go ahead even though channels will be disconnected. Ask a human first."},"profile":{"type":"string","description":"The profile identifier."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: true, idempotentHint: true (inferred from the name).","verify":true}],"tokensBefore":180,"tokensAfter":180},{"name":"edit_post","fixed":{"name":"edit_post","description":"Change a scheduled post's text and/or its fire time before it goes out. Only 'scheduled' posts can be edited and any new time must be in the future. To change where it posts, cancel it and create a new one; targets are fixed once scheduled.","inputSchema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The scheduled post id (post_…)."},"text":{"type":"string","description":"Optional new post body."},"timezone":{"type":"string","description":"Optional IANA timezone (e.g. Europe/London). Interprets a naive scheduledAt as wall time in that zone."},"scheduledAt":{"type":"string","description":"Optional new fire time (future). UTC with a trailing Z, or a naive local time together with `timezone`."},"textOverrides":{"type":"object","description":"Optional new per-platform caption overrides, keyed by platform. Replaces the stored overrides.","additionalProperties":{"type":"string"}}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":276,"tokensAfter":276},{"name":"get_analytics","fixed":{"name":"get_analytics","description":"Cross-platform analytics over a period: decisions, not just measurements. Returns a readable summary, totals, a per-platform breakdown with engagement rates, recent daily activity, top posts, best posting times from this account's own history, and recommendations with their reason and confidence. Missing data is reported honestly, never fabricated. periodDays defaults to 30.","inputSchema":{"type":"object","properties":{"periodDays":{"type":"number","description":"Lookback window in days (1–365, default 30)."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":163,"tokensAfter":163},{"name":"get_connect_link","fixed":{"name":"get_connect_link","description":"Create a one-tap link the account owner can open to connect a social channel. Connecting requires a human: the networks make a person approve access on their own screen, and nothing can automate that. So when list_social_accounts is empty, or a channel shows needs_reauth, call this and give the person the link rather than telling them to find the dashboard. The link is signed, works once it is opened, and expires in 30 minutes. Optionally pass a profile to add the channel to that profile.","inputSchema":{"type":"object","properties":{"profile":{"type":"string","description":"Optional profile username to attach the new channel to. Omit for the default."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":205,"tokensAfter":205},{"name":"get_credits","fixed":{"name":"get_credits","description":"The account's credit balance: total, the monthly allowance and what is left of it, and never-expiring pack credits. Publishing spends credits on every network. X also charges for reads, engagement, replies and messages. Check this before planning a batch, so you do not start work the balance cannot finish. Returns the same figures as whoami, for when you only need the balance.","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":138,"tokensAfter":138},{"name":"get_platform_capabilities","fixed":{"name":"get_platform_capabilities","description":"Machine-readable capabilities per platform: character limits, media rules (required/video-only/max images/formats/sizes), whether a first comment is supported, whether publishing is async, and EVERY platformOptions field with its valid values. Call this before create_post to construct a valid post. Omit `platform` for all platforms.","inputSchema":{"type":"object","properties":{"full":{"type":"boolean","description":"Return every field instead of the headline limits. Large (~4k tokens); only ask for it if the summary is not enough."},"platform":{"type":"string","description":"Optional single platform, e.g. 'tiktok'. Omit for all."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":201,"tokensAfter":201},{"name":"get_post","fixed":{"name":"get_post","description":"Fetch one post by id (post_…) as the COMPLETE record: text, any per-platform overrides, media, first comment, and every target with its state, live URL and error. Use this when you need the whole object; create_post already told you what happened, and list_posts gives you the summary rows.","inputSchema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The post id (post_…)."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":134,"tokensAfter":134},{"name":"get_post_analytics","fixed":{"name":"get_post_analytics","description":"Get normalised performance metrics for one post, per platform: impressions, views, likes, comments and shares, with the live URL. A platform whose analytics are not available yet says so in the cell rather than reporting zeros, because 'no data' and 'nobody saw it' are different answers. Follower counts are deliberately absent: they are an account-level number and mean nothing attached to a single post.","inputSchema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The post id (post_…)."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":169,"tokensAfter":169},{"name":"get_publish_info","fixed":{"name":"get_publish_info","description":"Live creator-level publish constraints for one connected account (TikTok: available privacy options, whether comments/duet/stitch are allowed, max video duration for this creator). Use it to pick a valid privacyLevel before posting to TikTok.","inputSchema":{"type":"object","required":["accountId"],"properties":{"accountId":{"type":"string","description":"A connected account id (acc_…) from list_social_accounts."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":137,"tokensAfter":137},{"name":"list_account_targets","fixed":{"name":"list_account_targets","description":"List the postable destinations INSIDE one connected account, for the platforms where a single login exposes several: Facebook Pages, Pinterest boards, YouTube channels. Pass the returned id in platformOptions when publishing. Accounts on platforms without sub-destinations return none, and you post to the account itself.","inputSchema":{"type":"object","required":["accountId"],"properties":{"accountId":{"type":"string","description":"A connected account id (acc_…) from list_social_accounts."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":160,"tokensAfter":160},{"name":"list_conversations","fixed":{"name":"list_conversations","description":"Direct message threads across every connected network that has them, most recent first, with who is in each, the last message and an unread count. Start here for 'any new DMs'. Cursor-paged. `problems` names any network that could not be read.","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"1-100 (default 25)."},"cursor":{"type":"string","description":"Opaque cursor from a previous page."},"account":{"type":"string","description":"Optional single account id (acc_…). Omit for all."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":167,"tokensAfter":167},{"name":"list_notifications","fixed":{"name":"list_notifications","description":"What happened across every connected network since you last looked: likes, replies, mentions, follows, reposts and quotes, newest first, in ONE normalised shape. This is the read to start from when asked 'what needs my attention' or 'did anyone reply'. Cursor-paged: pass the returned `cursor` back verbatim for the next page. `problems` names any network that could not be read, so an empty list never silently means 'we could not look'.","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Items per page, 1-100 (default 25)."},"cursor":{"type":"string","description":"Opaque cursor from a previous page. Do not construct or parse one."},"account":{"type":"string","description":"Optional: one connected account id (acc_…). Omit to read every connected network."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":243,"tokensAfter":243},{"name":"list_posts","fixed":{"name":"list_posts","description":"List the caller's posts (most recent first), each with per-platform status. Pass state='scheduled' to see the upcoming schedule. Cursor pagination: pass `limit` (default 50, max 100) and `cursor` (the previous response's `nextCursor`).","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","maximum":100,"minimum":1,"description":"Page size, 1-100 (default 50)."},"state":{"type":"string","description":"Optional filter: queued | scheduled | partial | published | failed."},"cursor":{"type":"string","description":"Opaque cursor from a prior response's `nextCursor`. Omit on the first page."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":189,"tokensAfter":189},{"name":"list_profiles","fixed":{"name":"list_profiles","description":"List the caller's profiles: named buckets that group connected accounts, typically one per brand, client or project. The name is what you pass as `profile` when publishing, so call this first if you are not sure which one to post from. Most accounts have exactly one, called 'default'.","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":112,"tokensAfter":112},{"name":"list_social_accounts","fixed":{"name":"list_social_accounts","description":"List every connected social account with its id, profile, platform, handle and connection status. Call this before publishing: the ids are what create_post takes in `accounts`. If none are connected, use get_connect_link for OAuth networks or connect_account for supported credential-based networks. A status of 'needs_reauth' means the owner must reconnect that channel.","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":138,"tokensAfter":138},{"name":"read_comments","fixed":{"name":"read_comments","description":"Read the replies people left on a post you published, across every network it went to, in one normalised shape. Use it to see how a post landed, or to find what needs answering. Takes the PostLake post id (post_…), not a network id.","inputSchema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"The post id (post_…)."},"limit":{"type":"number","description":"Items per page, 1-100 (default 50)."},"cursor":{"type":"string","description":"Opaque cursor from a previous page."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":163,"tokensAfter":163},{"name":"read_conversation","fixed":{"name":"read_conversation","description":"The messages in one DM thread, newest first. `fromMe` on each message tells you which side sent it, so you do not have to compare handles. Needs both the conversation id and the account it belongs to, because a conversation id only means something on one connection.","inputSchema":{"type":"object","required":["id","account"],"properties":{"id":{"type":"string","description":"The conversation id from list_conversations."},"limit":{"type":"number","description":"1-100 (default 50)."},"cursor":{"type":"string","description":"Opaque cursor from a previous page."},"account":{"type":"string","description":"The connected account id (acc_…) the conversation belongs to."}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":208,"tokensAfter":208},{"name":"rename_profile","fixed":{"name":"rename_profile","description":"Rename a profile. The channels and posts inside it are untouched, but the identifier is re-derived from the new name, so anything holding the old `profile` string stops resolving. The response says whether the identifier actually moved, so you only have to update references when it did.","inputSchema":{"type":"object","required":["profile","name"],"properties":{"name":{"type":"string","description":"The new name, as a person would write it."},"profile":{"type":"string","description":"The current profile identifier."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":165,"tokensAfter":165},{"name":"send_message","fixed":{"name":"send_message","description":"Send a direct message. Give `conversation` to reply in an existing thread, or `to` (a handle) to start a new one. You do not need to find or create the conversation first. Links and mentions in the text are made real, not left as plain text. Free on supported networks except X, where the write costs 6 credits.","inputSchema":{"type":"object","required":["account","text"],"properties":{"to":{"type":"string","description":"A handle to start a conversation with, e.g. alice.bsky.social. Omit if using `conversation`."},"text":{"type":"string","description":"What to say."},"account":{"type":"string","description":"The connected account id (acc_…) sending it."},"conversation":{"type":"string","description":"An existing conversation id. Omit if using `to`."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":231,"tokensAfter":231},{"name":"update_profile","fixed":{"name":"update_profile","description":"Change a connected account's own profile: display name, bio, avatar or banner. Only the fields you give are changed; the rest are left alone. Images are given as public URLs and uploaded for you. A network that does not allow this refuses and says so.","inputSchema":{"type":"object","required":["account"],"properties":{"bio":{"type":"string","description":"The profile description."},"account":{"type":"string","description":"The connected account id (acc_…)."},"avatarUrl":{"type":"string","description":"Public URL of the new avatar image."},"bannerUrl":{"type":"string","description":"Public URL of the new banner image."},"displayName":{"type":"string","description":"The name shown on the profile."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: true (inferred from the name).","verify":true}],"tokensBefore":217,"tokensAfter":217},{"name":"upload_media","fixed":{"name":"upload_media","description":"Prepare or upload media so it can be attached to posts. For a public asset, pass `url` and PostLake fetches it. For a file on the agent's own machine, omit `url`, pass `contentType` and preferably `sizeBytes`, then PUT the file bytes to the short-lived upload target returned by this tool. Both paths validate type/size (images ≤20MB: jpeg/png/webp/gif; videos ≤200MB: mp4/mov/webm) and produce a med_… id for create_post. Never put file bytes or base64 in the tool call. For a carousel, use upload_m","inputSchema":{"type":"object","properties":{"url":{"type":"string","description":"Publicly accessible URL of the image or video. Omit for a local file."},"sizeBytes":{"type":"integer","minimum":1,"description":"Local file size in bytes. Recommended for local uploads so the signed target is bound to the exact file size."},"contentType":{"type":"string","description":"MIME type. Optional override for URL uploads; required for local-file uploads."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":284,"tokensAfter":284},{"name":"upload_media_batch","fixed":{"name":"upload_media_batch","description":"Upload or prepare several media items in one call (carousels). Each item is either a public `url` or a local file (`contentType` + `sizeBytes`, no url). Local items return a five-minute PUT target each; PUT the bytes from the agent's machine, then pass the med_… ids to create_post. Never put file bytes or base64 in the tool call. Max 12 items.","inputSchema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","description":"Publicly accessible URL. Omit for a local file."},"sizeBytes":{"type":"integer","minimum":1,"description":"Local file size in bytes."},"contentType":{"type":"string","description":"MIME type. Required for local files."}},"additionalProperties":false},"maxItems":12,"minItems":1,"description":"One entry per file, up to 12."}},"additionalProperties":false},"annotations":{"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: false, destructiveHint: false, idempotentHint: false (inferred from the name).","verify":true}],"tokensBefore":254,"tokensAfter":254},{"name":"validate_post","fixed":{"name":"validate_post","description":"Dry-run a post WITHOUT publishing: runs the exact validation create_post would run (media rules, per-platform character limits, option values, whether a first comment can be posted at all) and returns per-target errors and warnings. Call this before create_post, especially for scheduled posts and TikTok photo carousels (JPEG/WebP only, max 1080×1920, PNG rejected). It is free.","inputSchema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"The post body (the master caption)."},"media":{"type":"array","items":{"type":"string"},"description":"Optional media ids (med_…) from upload_media. The public url the upload returned is accepted too."},"thread":{"type":"array","items":{"type":"string"},"description":"Optional follow-on post bodies. Validation warns for networks that do not support threads."},"profile":{"type":"string","description":"A profile name. Validates against every account it owns. Alternative to `accounts`."},"accounts":{"type":"array","items":{"type":"string"},"description":"Connected account ids (acc_…). An alternative (or addition) to `profile`."},"mediaAlt":{"type":"array","items":{"type":"string"},"description":"Optional alt text per shared media item, aligned by index to `media`."},"timezone":{"type":"string","description":"Optional IANA timezone (e.g. Europe/London). Interprets a naive scheduledAt as wall time in that zone. Stored fire time is always UTC."},"platforms":{"type":"array","items":{"type":"string"},"description":"Optional filter: keep only these networks from the resolved set."},"scheduledAt":{"type":"string","description":"Optional ISO 8601 time. UTC with a trailing Z, or a naive local time together with `timezone`."},"firstComment":{"type":"string","description":"Optional first comment. Validating with it tells you which targets cannot post one."},"textOverrides":{"type":"object","description":"Optional per-platform caption overrides, keyed by platform.","additionalProperties":{"type":"string"}},"mediaOverrides":{"type":"object","description":"Optional per-platform media, keyed by platform.","additionalProperties":{"type":"array","items":{"type":"string"}}},"platformOptions":{"type":"object","description":"Optional per-platform extras, namespaced by platform.","additionalProperties":true},"mediaAltOverrides":{"type":"object","description":"Optional alt text for override media, keyed by platform and aligned by index.","additionalProperties":{"type":"array","items":{"type":"string"}}},"firstCommentOverrides":{"type":"object","description":"Optional per-platform first-comment overrides, keyed by platform.","additionalProperties":{"type":"string"}}},"additionalProperties":false},"annotations":{"readOnlyHint":true}},"changes":[{"kind":"annotations","detail":"Added readOnlyHint: true (inferred from the name).","verify":true}],"tokensBefore":760,"tokensAfter":760}],"toolsChanged":28,"tokensSaved":0,"todo":[],"note":"Deterministic fixes. Changes marked verify need a human look before shipping (renames, inferred annotations, inferred types, removed text). Parameter descriptions in `todo` need to be written by someone who knows the API."}