antwork

Draft, schedule, and publish social media posts without leaving your AI assistant.

Community: Submitted by a user or imported; check the owner before granting accessOnlineNo sign-inGlobalFreeCan modify data

What it can do

  • Whoami: Get the current authenticated user's profile. Returns user ID, email, display name, subscription status, and the persisted default workspace (if set).
  • List Workspaces: List all workspaces the authenticated user belongs to. Call this first if you need to know which workspaces are available. If the user has only one workspace, other tools auto-select
  • Set Default Workspace: Pin (or clear) the user's default workspace for subsequent tool calls. Pass a workspace ID or name to PIN: every workspace-scoped tool called without an explicit `workspace_id`

What data it sees

Do you need an account

No: the server works without sign-in

Draft, schedule, and publish social media posts without leaving your AI assistant.

Antwork connects your social accounts (LinkedIn, X, Instagram, TikTok, Facebook, and more) to your AI. Ask it to write a post in your brand voice, then schedule or publish it across platforms, straight from the chat. No copy-pasting into a separate dashboard.

What you can do

  • Draft on-brand posts. Antwork learns each account's voice from its own post history and writes in that style.
  • Schedule and publish. Queue posts for the best times, or publish instantly.
  • Plan campaigns. Coordinate posts across multiple accounts from one request.
  • Track performance. Pull engagement metrics, recent post history, and optimal posting times.
  • Handle media. Attach images and video to your posts.

How it works

  1. Connect Antwork from your AI client and authorize your workspace (OAuth, nothing to paste).
  2. Link your social accounts at antwork.io.
  3. Ask your AI to draft, schedule, or publish. It calls Antwork's tools directly.

Free to start. Your AI does the writing, Antwork handles the posting.

Server tool list (38)

Raw names from tools/list. Only developers need these.

whoamiGet the current authenticated user's profile. Returns user ID, email, display name, subscription status, and the persisted default workspace (if set).
list_workspacesList all workspaces the authenticated user belongs to. Call this first if you need to know which workspaces are available. If the user has only one workspace, other tools auto-select it. Each entry is flagged with `isDefault: true` when it matches the user's persisted default workspace — that's the one any tool will auto-select when `workspace_id` is omitted. Each entry also includes a `logoData` field — a small inlined data: URL of the workspace logo, used by the MCP App iframes' workspace header. Sandbox CSPs block cross-origin image loads, so MCP-App consumers should render `logoData`, not `logoUrl`, to display the logo.
set_default_workspacePin (or clear) the user's default workspace for subsequent tool calls. Pass a workspace ID or name to PIN: every workspace-scoped tool called without an explicit `workspace_id` thereafter resolves to this workspace — instead of erroring out with "multiple workspaces". Use this the moment the user names which workspace they want to work in for the conversation; you won't need to thread `workspace_id` through every follow-up call. Pass `null` (or an empty string) to CLEAR the default. The next ambiguous tool call will again return "multiple workspaces" until a new default is set. Use this when the user says "stop defaulting to X" or wants to be prompted each time. The default persists across conversations and clients (it lives on the user's account). Calling this with a different ID overwrites the previous one — no clearing required first. An explicit `workspace_id` on any individual tool call always overrides the default for that one call without changing what's persisted. Args: workspace_id: Workspace ID or name to make the default. Pass null or an empty string to clear the persisted default.
create_workspaceCreate a new workspace owned by the authenticated user. Use this when the user wants a separate brand / client / project space — each workspace has its own connected social accounts, voice profiles, posts, and media library. Subsequent tool calls can target the new workspace by passing `workspace_id` (its returned ID or its name). Args: name: Workspace name (required, 1–50 characters).
list_postsRetrieve recent posts from a workspace. Results are pre-sorted scheduled → draft → published → failed. Within scheduled, the closest publish appears first (so the user sees "what's about to go live" at the top); other groups are newest-first. The posts-table iframe paginates client-side at 8 rows per page, so the visual footprint is bounded regardless of how many posts come back. Default is 50 (max) so the user can flip through pages without another tool call. Args: status: Filter by status: "draft", "scheduled", "published", or "failed". limit: Max posts to return (default 50, max 50). workspace_id: Workspace ID or name. Optional if user has only one workspace.
list_social_accountsList the social media accounts currently connected to the workspace. Text-only and fast — this does NOT render the connections panel iframe. Use it to answer account questions and to confirm target platforms are connected before drafting / scheduling / publishing. When the user wants to actually connect, disconnect, or re-authenticate an account, call `get_connection_urls` instead — that one renders the interactive connections panel (connect grid + per-account reauth / disconnect). Returns one entry per active connection with platform, account name, handle, account ID, token health, and a per-account `reauthUrl` the user can open to refresh the OAuth token. Call get_connection_urls if any expected platform is missing. Account state is mutable across turns — the user may connect or disconnect accounts in the connections panel without telling you. Other tools return a `connectionsModifiedAt` stamp; when it's newer than the one you got from your most recent list_social_accounts, the cached account list is stale and you should re-call this tool before answering. Args: workspace_id: Workspace ID or name. Optional if user has only one workspace.
disconnect_social_accountDisconnect a connected social media account (SOFT DELETE). This is a soft-delete — the social_accounts doc stays in Firestore with `isActive: false`. Nothing on the user's social platform is touched: no OAuth revocation, no app uninstall, no post deletion. The user's posts and analytics history remain attached to the account, so if they reconnect later (via get_connection_urls) the same account stitches back into the workspace without losing history. Tell the user this clearly when they ask to disconnect — don't say "deleted" or "removed permanently," say "disconnected from Antwork." Scheduled posts targeting this account stay in place but won't publish until the user reconnects. Args: account_id: The social account document ID (from `list_social_accounts`). workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_connection_urlsOpen the connections panel to connect, disconnect, or re-auth accounts. This renders the interactive connections-panel iframe (connect grid + per-account reauth / disconnect controls) and returns OAuth connect URLs per platform. Call this whenever the user wants to MANAGE connections — connect a new account, reconnect a stale one, or see the connect options. For just reading what's already connected, use the lighter `list_social_accounts` (no iframe). On hosts without UI, present the returned URLs as clickable links. Args: workspace_id: Workspace ID or name. Optional if user has only one workspace.
fetch_platform_postsFetch recent posts from connected social media platforms. Retrieves actual published posts from your social accounts (not just Antwork drafts), including text, engagement metrics, publish date, and permalink. Useful for understanding what you've been posting and what's performing well. A workspace can have MULTIPLE accounts on the same platform (e.g., a personal LinkedIn + a brand LinkedIn). When that's the case, posts from every matching account are returned side-by-side in `accounts`, each tagged with its `accountId`. If the user is asking about a specific account, pass `account_id` (from `list_social_accounts`) to narrow the result. Platform limitations to know about (surface these to the user when relevant — they explain "why am I seeing no posts?" upfront): - LinkedIn personal accounts: NOT fetchable. LinkedIn closed the r_member_social scope, so this tool can only read posts from LinkedIn Organization pages, never from personal profiles. If the only connected LinkedIn account is personal, tell the user this before calling — they'll save a round trip. If you do call anyway, the response marks unfetched accounts with `unsupported: true` and an `unsupportedReason` you can quote. For Antwork-published LinkedIn posts on personal accounts: metrics still work (memberCreatorPostAnalytics), and you can see them via get_performance, get_post, or refresh_post_metrics. It's only the "list the user's posts FROM LinkedIn" path that's blocked. Args: platform: Filter to a specific platform (e.g., "linkedin", "x"). If omitted, fetches from all connected platforms. account_id: Filter to a specific connected account. Use this when the workspace has multiple accounts on the same platform and the user means one in particular. max_posts: Maximum posts per account (1-25, default 10). workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_workspace_settingsGet the workspace's configuration: name, default posting schedule, timezone, and connected platforms. Use this to discover the workspace's preferred posting times and timezone before suggesting schedule slots, or to confirm which platforms the user has selected as their primary publishing targets. Args: workspace_id: Workspace ID or name. Optional if user has only one workspace.
prepare_voice_analysisStep 1 of voice-profile extraction. Returns the posts + schema YOU (the host LLM) should analyze yourself, then write back via `save_voice_analysis`. USE THIS WHEN: `get_post_context` returned `voiceStale: true` (or null voice) and you're about to draft a post for that account. It's also how you (re)build the profile on demand at the user's request ("re-extract my LinkedIn voice"). Antwork no longer runs Vertex on its side for voice analysis. You do the analysis using your native capabilities — no Gemini, no separate model. Then call `save_voice_analysis` with the JSON matching `schema`. Source-of-truth rules (READ BEFORE EXPLAINING RESULTS TO THE USER): - For most accounts, `posts` are pulled live from the platform's API. - For LinkedIn PERSONAL accounts, LinkedIn does not expose an API for an account to read its own posts (`r_member_social` scope is closed). This tool transparently falls back to posts the user has published through Antwork to that account. When that happens, the response carries `unsupportedFetch: true`, a human-readable `unsupportedReason`, and a `sourceNote` explaining the caveat. DO NOT tell the user "no posts came back" — explain the LinkedIn limitation honestly so they don't think their connection is broken. - If BOTH the platform API AND the Antwork DB return nothing (e.g. a LinkedIn personal account that hasn't published anything through Antwork yet), the response has `posts: []`, `unsupportedFetch: true`, and an `instruction` describing what to tell the user. Skip save_voice_analysis in that case. What this tool returns: - `accountId` / `platform` / `accountName` / `accountHandle` — the target account context (echo `accountId` back to save_voice_analysis) - `posts` — up to `max_posts` recent published posts (text + date + engagement metrics) for you to analyze - `schema` — required field shape for `save_voice_analysis.analysis` - `instruction` — terse playbook of what to do next - `unsupportedFetch`, `unsupportedReason`, `sourceNote` — present only when the platform API can't enumerate own posts and we fell back to Antwork's DB. Surface these honestly to the user. Drafting protocol when voice is stale: 1. Call prepare_voice_analysis 2. Read the posts, derive the voice profile in your head against `schema` (keep examplePhrases in their ORIGINAL language) 3. Call save_voice_analysis with the result 4. Draft the post using the new voice context Args: platform: Platform key ("linkedin", "x", "facebook", "instagram", "threads", "tiktok", "youtube", "pinterest"). account_id: Specific social account ID. Optional — if omitted, picks the most recently connected active account for the platform in this workspace. max_posts: How many recent posts to ship for analysis (default 50, clamped 5-100). workspace_id: Workspace ID or name. Optional if user has only one workspace.
save_voice_analysisStep 2 of voice-profile extraction. Persists the analysis you produced in response to `prepare_voice_analysis`. Call this with EXACTLY the `accountId` returned by prepare_voice_analysis and an `analysis` object matching the `schema` from that call. The server validates the shape and stores the profile on the social account. Args: account_id: Social account ID from prepare_voice_analysis. analysis: Voice profile object matching the schema returned by prepare_voice_analysis. All string fields required; list fields require ≥1 entry. post_count: Number of posts you analyzed (for telemetry). Optional. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_post_contextOne-stop drafting bundle: brand identity, voice, and recent posts. Call this BEFORE writing a post. It returns everything the model needs to produce on-brand, on-tone copy for a specific (workspace, platform, account) target in a single round-trip: - `workspace`: name, website, logoUrl, brandVersion - `account`: platform, accountId, accountName, accountHandle - `voice`: slim single-locale voice profile (tone, style, emoji / hashtag policy, CTA pattern, up to 3 example phrases, mannerisms) - `recentPosts`: last 3 published posts on this account (for anti-repetition + thematic continuity) - `voiceStale`, `voiceLastSyncAt`: signal if the voice profile is older than 30 days. To re-extract, run `prepare_voice_analysis` → analyze the returned posts yourself → `save_voice_analysis`. - `hostSupportsUi`: True when the host renders MCP-App iframes (claude.ai etc). Use for media-attach routing later in the flow: on UI hosts, `get_post`/`create_post` render an Add media button the user can drop a file on; on CLI hosts, use the signed-URL flow instead. Workspace identity (`name + website + logoUrl + brandVersion`) is safe to cache in host memory keyed by `antwork:ws:<workspaceId>:brand`. Re-fetch when `brandVersion` differs from the value in `get_workspace_settings`. Args: platform: Platform key ("linkedin", "x", "facebook", "instagram", "threads", "tiktok", "youtube", "pinterest"). account_id: Specific social account ID. Optional — if omitted, picks the most recently connected active account for the platform. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_performanceGet lifetime engagement totals per published post, as a tabular result. Returns a BigQuery-style payload: - `schema.fields`: column definitions (name, type, mode) - `rows`: one row per published post with lifetime metrics - `rowCount`: number of rows - `summary`: range totals (likes/comments/shares/impressions/postCount) Columns: postId, text, platform, goal, publishedAt, likes, comments, shares, impressions, totalEngagement (likes+comments+shares — handy for sorting). Use this for "top performers" / top-N rankings, and to render bar charts or rankings inline. For per-day time series, pair with get_engagement_history. Args: limit: Max posts to return (default 30, max 50). workspace_id: Workspace ID or name. Optional if user has only one workspace.
refresh_post_metricsFetch fresh engagement metrics from each post's platform, RIGHT NOW. Unlike get_performance / get_post (which read cached numbers refreshed by a 6-hour cron), this tool hits the platform APIs live and writes the result back to Firestore. Use it when freshness matters: - "How's my latest post doing?" - "Up-to-the-minute numbers on these 5 posts." - The user just published something and wants to see real engagement. Trade-offs to know before calling: - Latency: each platform call is throttled 200 ms; a 10-post batch across 2-3 platforms typically runs 5-15 s. Don't reach for this when the user asked a broad question — get_performance is instant. - Rate limits: LinkedIn Insights ~500 req/day per app, X v2 reads ~75/15min, TikTok/Pinterest stingier. One batch of 25 posts can consume meaningful quota. - Capped at 25 posts per call. For per-day historical series (last N days) use get_engagement_history / get_post_history — those need accumulated snapshots that platforms don't expose retroactively, so live refresh can't reconstruct them. Args: post_ids: A single post ID or a list of post IDs to refresh. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_engagement_historyGet historical per-day engagement, as a tabular result. Returns a BigQuery-style payload in *long format* — one row per (date, platform) tuple, plus an "all" platform aggregate row per date. The host LLM can chart this directly (line per platform, stacked bars, pie of platform share, etc.) without a prebuilt visualization. Schema: - date (DATE), platform (STRING), likes, comments, shares, impressions (INTEGER) - "platform" values: each connected platform (e.g. "linkedin", "x") plus "all" for the daily total across platforms. Also returned at the top level: - `platforms`: list of platforms that appeared in the window - `platformTotals`: per-platform range totals - `rangeTotals`: workspace-wide range totals - `startDate` / `endDate`: window bounds This is the *real* day-by-day engagement earned during the window, not lifetime totals on posts published in the window. Numbers come from diffing cumulative metrics_history snapshots. Use this for: "how am I doing this week", "compare last week vs this week", "show me engagement over the last 30 days", "which day peaked". For per-post lifetime totals, use get_performance. Args: days: Trailing window in days (1–365, default 30). Includes today. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_postGet a single post by ID, including aggregated and per-platform metrics. Returns the post content, target platforms, status, and the latest LIFETIME engagement metrics: - `metrics`: aggregated totals across all platforms ({likes, comments, shares, impressions}) - `platformMetrics`: per-platform breakdown ({linkedin: {likes, comments, shares, impressions, ...}, x: {...}}) - `metricsUpdatedAt`: when these numbers were last refreshed - `hostSupportsUi`: True when the host renders MCP-App iframes — i.e. the response also renders an editable post-card with an Add media button. Use this for media-attach routing: if True, tell the user to drop the file on Add media (one step, auto- attaches). If False, fall back to the signed-URL flow. For per-day historical engagement on this specific post (e.g. "what did this post earn each day for the last 14 days"), use get_post_history instead. Args: post_id: The post ID. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_post_historyGet the per-day historical metrics for a single post, as a tabular result. Returns a BigQuery-style payload in *long format* — one row per (date, platform) tuple. Both cumulative lifetime totals and the daily delta (engagement earned that day) are included as columns so the host LLM can render either a cumulative growth chart or a daily bar chart from the same payload. Schema: - date (DATE), platform (STRING) - cumulativeLikes / Comments / Shares / Impressions (INTEGER) — totals as of that day - dailyLikes / Comments / Shares / Impressions (INTEGER) — earned on that day Use this for: "how did this post grow over time", "which day peaked", "did this post stop earning engagement". Snapshots accumulate from when the metrics_history cron started tracking the post, so older posts may have a shorter history than the requested window. Args: post_id: The post ID to fetch history for. days: Optional trailing window (1–365). If omitted, returns the full history. workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_optimal_posting_timesGet the workspace's preferred posting times, timezone, and selected accounts per platform. Returns the schedule the user configured (e.g. ["09:00", "18:00"] in their timezone) plus the per-platform account selections. Use this when scheduling a post to pick a time that aligns with the user's posting cadence rather than guessing. Args: workspace_id: Workspace ID or name. Optional if user has only one workspace.
create_postCreate a draft post bound to ONE social account. The post is saved as a DRAFT only — to actually schedule or publish, call schedule_post or publish_post with the returned postId. Single-account model (IMPORTANT — read carefully): - Each call creates ONE draft for ONE specific account. There is no multi-platform fan-out from a single call. To post the same idea on multiple platforms (or on multiple accounts of the same platform), call this tool ONCE PER target account — give each variant the copy and tone that fits that platform / account voice. Group related variants by passing the same `campaign_id` so they show up linked. - `account_id` MUST be a social_account doc ID from `list_social_accounts` — never a platform name like "linkedin". The platform is derived from the account. If you don't know which account the user means and there are multiple on the platform, ASK before drafting. Brainstorming one post → make at least 3 versions: when the user wants a post for this account but hasn't pinned the exact copy, call this tool 3+ times for the SAME account_id, each a genuinely different angle / hook / structure (not minor rewordings). Share one `campaign_id` so they group as a set; the user picks the winner from the cards and deletes the rest. Make a single draft only when the user gave you the copy or asked for just one. THIS IS THE ONLY WAY TO SHOW A POST DRAFT TO THE USER. The response renders an interactive post-card iframe where the user can edit copy, attach media, and schedule / publish — all wired to the matching tools. Do NOT render drafts as inline markdown, a custom card, an artifact, or via any frontend-generation skill — those skip Antwork's edit and publish flow. This applies to vague creative prompts too ("show me what you'd write", "show me your magic", "demo what you can do") — those mean draft, not mockup. Args: text: Post copy. Include @mentions directly in text for X, Threads, TikTok, and Facebook — they auto-link. For LinkedIn rich mentions and Instagram photo tags, use `user_tags`. account_id: Social-account doc ID (from list_social_accounts) the post will publish to. Required. hashtags: Hashtags without the # prefix. goal: Post goal, e.g. "Educate", "Engage", "Promote", "Entertain". scheduled_for: ISO 8601 datetime to pre-fill the scheduled date on the draft. Does NOT actually schedule the post — call schedule_post separately. campaign_id: Optional opaque ID grouping related drafts (e.g. cross-platform variants of one announcement). Reuse the same string across multiple create_post calls to link them. user_tags: Structured tags for platform-specific tagging. Each tag: username (required), platform (e.g. "linkedin", "instagram"), platformId (LinkedIn URN like "urn:li:organization:12345"), type ("mention" or "photo_tag"), x and y (0.0–1.0 for Instagram photo tags). Example: [{"username": "Acme Corp", "platform": "linkedin", "platformId": "urn:li:organization:12345", "type": "mention"}, {"username": "johndoe", "platform": "instagram", "type": "photo_tag", "x": 0.5, "y": 0.5}]. workspace_id: Workspace ID or name. Optional if user has only one workspace.
update_postUpdate an existing post. Posts are single-account. To switch the target account, pass `account_id` and we re-bind the post (the platform is re-derived from the new account). There is no per-platform text variant — if the user wants the same idea on a different account with different copy, call `create_post` for a new draft (optionally sharing `campaign_id` to link them). Args: post_id: The post ID to update. text: New post text. account_id: Optional — switch the post's target social account (also re-derives `platform`). Must be a social_account doc ID in this workspace, from list_social_accounts. hashtags: Updated hashtags without # prefix. goal: Updated goal. status: "draft" or "scheduled". scheduled_for: ISO 8601 datetime for scheduling. campaign_id: Optional grouping ID; reuse across related drafts. workspace_id: Workspace ID or name. Optional if user has only one workspace.
delete_postDelete a post from the workspace. Drafts and scheduled posts are hard-deleted. Published posts are soft-deleted by default (the post is hidden from lists but metrics history is preserved); pass preserve_analytics=False to hard-delete a published post instead. Args: post_id: The post ID to delete. delete_from_platforms: If True, also delete the post from every social platform it was successfully published to. Pass a list of platform names (e.g. ["linkedin", "x"]) to delete from a subset only. TikTok does not expose a delete API and is never included. Omit or pass None to leave platform posts live. preserve_analytics: For published posts, keep metrics history and soft-delete the post. Defaults to True for published posts and False for drafts/scheduled. Pass False to force hard-delete. workspace_id: Workspace ID or name. Optional if user has only one workspace.
publish_postPublish a draft post immediately to its target account. Call this after create_post to publish right away instead of scheduling for later. Waits for the publish to complete on every target platform (up to ~60s) and returns the live post URLs in `url` / `urls` / per-platform entries. Args: post_id: The post ID returned by create_post. workspace_id: Workspace ID or name. Optional if user has only one workspace.
schedule_postSchedule a draft post for automatic publishing at a specific time. Call this after create_post to actually schedule the post. The post must exist and be bound to a social account. Args: post_id: The post ID returned by create_post. scheduled_for: ISO 8601 datetime (e.g. "2026-04-01T10:00:00Z"). Must be in the future. workspace_id: Workspace ID or name. Optional if user has only one workspace.
retry_failed_postRetry a post that failed to publish. Args: post_id: The failed post ID to retry. workspace_id: Workspace ID or name. Optional if user has only one workspace.
duplicate_postDuplicate an existing post into a new draft, optionally retargeting it to a different social account and / or rewriting the copy. Single-account model: the new draft binds to ONE account. If `account_id` is omitted, the new draft keeps the source's account. To produce variants on multiple accounts, call duplicate_post once per target account. The new draft inherits the source's `campaign_id` by default (so related variants stay grouped). Pass `campaign_id` explicitly to override, or pass an empty string to clear it. Args: post_id: The source post ID to duplicate. account_id: Optional — retarget the new draft to a different social account (from list_social_accounts). Defaults to the source's account. text: Override base text for the new post. Defaults to the source's text. campaign_id: Optional grouping ID. Defaults to the source's campaignId. workspace_id: Workspace ID or name. Optional if user has only one workspace.
list_mediaList media files in the workspace media library. Returns uploaded images, videos, and PDF documents with URLs that can be attached to posts. Use this to find existing media before creating posts for visual platforms like Instagram, TikTok, or Pinterest, or for LinkedIn document posts. For libraries larger than `limit`, the response includes `nextCursor` — pass it back as `cursor` to fetch the next page. `nextCursor` is null when there are no more items. Args: media_type: Filter by type: "image", "video", "document" (PDFs), or None for all. limit: Max items per page (default 30, max 100). workspace_id: Workspace ID or name. Optional if user has only one workspace. cursor: Pagination cursor from a previous call's `nextCursor`. Omit for the first page.
attach_mediaAttach media (images, videos, or PDFs) to an existing post. Sets the media URLs on the post. Use URLs from list_media or any Firebase Storage URL from the workspace. This replaces any existing media on the post. Required for publishing to visual platforms like Instagram, TikTok, and Pinterest which require at least one media file. PDFs are only rendered natively by LinkedIn (as document posts); other platforms will skip PDF attachments at publish time. Args: post_id: The post ID to attach media to. media_urls: List of media URLs (from list_media or Firebase Storage). workspace_id: Workspace ID or name. Optional if user has only one workspace.
upload_mediaAdd a file to the workspace media library from a public HTTPS URL. Antwork downloads the URL server-side and stores it. Use this for AI-generated content or anything else already reachable over HTTP. For LOCAL files (the user attached a screenshot, photo, video, etc.): NEVER read the file and pass it through here as base64 — that bloats the chat transcript and is extremely slow. Use the signed-URL flow instead: 1. `request_upload_url(mime_type, byte_size)` → returns `signedUrl` and `gcsPath`. 2. `curl -X PUT --upload-file <local_path> -H 'Content-Type: <mime_type>' '<signedUrl>'` 3. `register_uploaded_media(gcs_path, mime_type, byte_size)` — optionally with `attach_to_post_id` to attach to a draft in one step. Bytes go LLM→GCS direct via the signed URL — they never ride the transcript. Supported types: - Images: image/png, image/jpeg, image/gif, image/webp - Videos: video/mp4, video/quicktime, video/webm - Documents: application/pdf (LinkedIn document posts only) Args: image_url: Public HTTPS URL of the file. Despite the name, accepts images, videos, and PDFs. mime_type: MIME type (e.g. "image/png", "application/pdf", "video/mp4"). workspace_id: Workspace ID or name. Optional if user has only one workspace.
upload_media_inlineIFRAME-INTERNAL. DO NOT CALL FROM CHAT. Used by the post-card "Add media" picker to send file bytes over the MCP RPC channel (postMessage → tools/call) without touching the LLM's transcript. The LLM must NEVER call this tool. Passing a large base64 string as a tool argument balloons the conversation context and is orders of magnitude slower than the proper local-file flow. For local files in chat, use `request_upload_url` + `curl PUT` + `register_uploaded_media` instead — that streams bytes LLM→GCS direct. Args: image_base64: Base64-encoded file payload (no `data:` prefix). mime_type: MIME type (e.g. "image/png", "video/mp4"). workspace_id: Workspace ID or name. Optional if user has only one workspace. thumbnail_base64: Optional first-frame preview for video uploads (raw base64). Ignored for non-video uploads. from_ui_bridge: Set ONLY by the post-card iframe's Add media picker over the postMessage RPC bridge — never by the LLM. Marks the call as iframe-originated so it's accepted regardless of host name.
request_upload_urlMint a short-lived signed PUT URL for a direct upload to GCS. PREREQUISITE: do NOT call this until you have a tool response showing `hostSupportsUi: false` (typically from a prior `get_post(post_id)` or `get_post_context` call). On UI hosts (claude.ai web / desktop, where `hostSupportsUi: true`), this flow needs a shell to `curl` with — and the host doesn't give the model one. Use the post-card's Add media button on those hosts instead: `get_post(post_id)` renders the card, its Add media picker uploads + auto-attaches via `update_post` in one step. Skipping that check and firing this tool first is a common mistake that wastes a round-trip and confuses the user. LLM-on-CLI path (hostSupportsUi=false, shell available): call this, then run curl -X PUT --upload-file <local_path> \ -H 'Content-Type: <mime_type>' '<signedUrl>' On HTTP 200, call `register_uploaded_media(gcs_path, mime_type, byte_size, attach_to_post_id=…)` to finalize the upload, attach it to a post in the same step, and surface the media in the workspace library. Bytes go LLM→GCS direct, never entering the chat transcript. Supported mime types: - Images: image/png, image/jpeg, image/gif, image/webp (≤ 10 MB) - Videos: video/mp4, video/quicktime, video/webm (≤ 100 MB) - Documents: application/pdf (≤ 100 MB) Args: mime_type: One of the supported mime types above. byte_size: Exact file size in bytes. Must match the actual file — `register_uploaded_media` verifies the uploaded blob is this size. workspace_id: Workspace ID or name. Optional if user has only one workspace.
register_uploaded_mediaValidate a signed-PUT upload and register it in the workspace media library. Second half of the LLM-on-CLI upload flow. Call this AFTER a successful curl PUT against the URL from `request_upload_url`. Verifies the GCS object exists, matches the declared mime + size, attaches a download token, and writes the workspace media record. When `attach_to_post_id` is set, also attaches the uploaded media to that post — same behavior as calling `attach_media` immediately after. The attachment is best-effort: if it fails (e.g. wrong workspace), the response returns `attached: false` with `attachError`, but the media record still lands so you can retry `attach_media` cleanly. Args: gcs_path: The path returned by request_upload_url (under workspaces/{wid}/media/). Refused if it points outside this workspace's prefix. mime_type: Same value passed to request_upload_url. Checked against the object's stored Content-Type. byte_size: Same value passed to request_upload_url. Checked against the object's actual byte length. workspace_id: Workspace ID or name. Optional if user has only one workspace. attach_to_post_id: If set, attach the new media URL to this post after registering.
get_mediaGet a single media item by ID. Returns the media URL, name, type, size, and upload date. Args: media_id: The media item ID. workspace_id: Workspace ID or name. Optional if user has only one workspace.
delete_mediaDelete a media item from the workspace media library. This removes the Firestore record. The file in storage may still exist but will no longer appear in the media library. Args: media_id: The media item ID to delete. workspace_id: Workspace ID or name. Optional if user has only one workspace.
search_postsSearch posts by keyword, status, platform, or date range. Args: query: Search keyword to match against post text (case-insensitive). status: Filter by status: "draft", "scheduled", "published", or "failed". platform: Filter by platform (e.g., "linkedin", "x"). date_from: Filter posts from this date (ISO 8601, e.g., "2026-03-01"). date_to: Filter posts until this date (ISO 8601, e.g., "2026-03-31"). limit: Max posts to return (default 20, max 50). workspace_id: Workspace ID or name. Optional if user has only one workspace.
get_calendarGet scheduled and published posts as an interactive calendar view. Returns posts grouped by date, useful for seeing what's coming up and finding gaps in the content schedule. On supported clients this renders an interactive calendar UI. Args: date_from: Start date (ISO 8601, e.g., "2026-03-24"). Defaults to today. date_to: End date (ISO 8601, e.g., "2026-03-31"). Defaults to 7 days from start. workspace_id: Workspace ID or name. Optional if user has only one workspace.
update_workspace_identityUpdate the workspace's identity (name, website, logo). Only the provided fields are updated; others remain unchanged. Args: name: The workspace / brand name. website: Brand website URL. logo_url: URL to the brand logo image. workspace_id: Workspace ID or name. Optional if user has only one workspace.
update_workspace_settingsUpdate workspace settings such as the default posting schedule. Only the provided fields are updated; others remain unchanged. Args: timezone: IANA timezone for scheduling, e.g. "Europe/Madrid", "America/New_York". preferred_times: List of preferred posting times in HH:MM format, e.g. ["09:00", "18:00"]. workspace_id: Workspace ID or name. Optional if user has only one workspace.