{"openapi":"3.1.0","info":{"title":"2pr.io MCP API","version":"1.0.0","summary":"Draft, schedule and publish LinkedIn posts, manage replies, search viral posts and read analytics through a 2pr.io account.","description":"The 2pr.io agent surface is a Model Context Protocol server (MCP 2025-06-18, Streamable HTTP) at `/api/mcp`.\n\nIt exposes 46 tools; each one is described under `components.schemas` as a ready-to-send `tools/call` request.\n\nAccess requires a 2pr.io account on the PRO or Team plan, authenticated with OAuth 2.1 (PKCE, dynamic client registration) or a personal bearer token created in the app.\n\nIt is not a LinkedIn scraping API and only acts on accounts connected to 2pr.io through LinkedIn's official OAuth.","termsOfService":"https://2pr.io/terms","contact":{"name":"2pr.io","email":"hi@2pr.io","url":"https://2pr.io/contact"}},"externalDocs":{"description":"2pr.io MCP server documentation","url":"https://2pr.io/docs/mcp"},"servers":[{"url":"https://2pr.io"}],"security":[{"oauth2":["mcp"]},{"personalToken":[]}],"tags":[{"name":"mcp","description":"The MCP endpoint. JSON-RPC 2.0 over POST."},{"name":"oauth","description":"OAuth 2.1 authorization server used by \"Sign in with 2pr\"."},{"name":"discovery","description":"Unauthenticated metadata documents."}],"paths":{"/api/mcp":{"post":{"tags":["mcp"],"operationId":"mcpRequest","summary":"Send a JSON-RPC request to the MCP server","description":"Supported methods: `initialize`, `ping`, `tools/list`, `tools/call`. Notifications (no `id`) get an empty 202. Tool errors from the product (validation, LinkedIn, entitlement) come back as a 200 with `result.isError: true`; transport-level failures use JSON-RPC `error`. Requests are rate limited per token, with a tighter limit on tools that call an LLM or fetch LinkedIn data.","parameters":[{"name":"MCP-Protocol-Version","in":"header","required":false,"schema":{"type":"string","example":"2025-06-18"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/InitializeRequest"},{"$ref":"#/components/schemas/PingRequest"},{"$ref":"#/components/schemas/ToolsListRequest"},{"$ref":"#/components/schemas/ToolsCallRequest"}],"discriminator":{"propertyName":"method","mapping":{"initialize":"#/components/schemas/InitializeRequest","ping":"#/components/schemas/PingRequest","tools/list":"#/components/schemas/ToolsListRequest","tools/call":"#/components/schemas/ToolsCallRequest"}}}}}},"responses":{"200":{"description":"JSON-RPC response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"202":{"description":"Notification accepted. Empty body."},"401":{"description":"Missing, malformed, expired or revoked token. Carries `WWW-Authenticate: Bearer resource_metadata=\"…\"` pointing at the protected-resource metadata (RFC 9728).","headers":{"WWW-Authenticate":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The account is not on a PRO or Team plan. Checked on every request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/oauth/register":{"post":{"tags":["oauth"],"operationId":"oauthRegisterClient","summary":"Register a public OAuth client (RFC 7591)","description":"No authentication. Rate limited by IP.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["redirect_uris"],"properties":{"client_name":{"type":"string"},"redirect_uris":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1}}}}}},"responses":{"201":{"description":"Client registered.","content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string"},"client_name":{"type":"string"},"redirect_uris":{"type":"array","items":{"type":"string","format":"uri"}},"token_endpoint_auth_method":{"type":"string","const":"none"},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Invalid registration request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/api/oauth/token":{"post":{"tags":["oauth"],"operationId":"oauthToken","summary":"Exchange an authorization code or refresh token (OAuth 2.1)","description":"PKCE S256 is required for `authorization_code`. Public clients only (`token_endpoint_auth_method: none`). Refresh tokens rotate on use.","security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TokenRequest"}},"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}},"responses":{"200":{"description":"Tokens issued.","content":{"application/json":{"schema":{"type":"object","properties":{"access_token":{"type":"string"},"token_type":{"type":"string","const":"Bearer"},"expires_in":{"type":"integer"},"refresh_token":{"type":"string"},"scope":{"type":"string","const":"mcp"}}}}}},"400":{"description":"Invalid grant, client, code verifier or redirect URI.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"tags":["discovery"],"operationId":"protectedResourceMetadata","summary":"Protected resource metadata (RFC 9728)","security":[],"responses":{"200":{"description":"JSON metadata."}}}},"/.well-known/oauth-authorization-server":{"get":{"tags":["discovery"],"operationId":"authorizationServerMetadata","summary":"Authorization server metadata (RFC 8414)","security":[],"responses":{"200":{"description":"JSON metadata."}}}},"/.well-known/mcp.json":{"get":{"tags":["discovery"],"operationId":"mcpManifest","summary":"MCP discovery manifest","security":[],"responses":{"200":{"description":"JSON manifest."}}}}},"components":{"securitySchemes":{"oauth2":{"type":"oauth2","description":"\"Sign in with 2pr\". Authorization code with PKCE S256; register a client first.","flows":{"authorizationCode":{"authorizationUrl":"https://2pr.io/oauth/authorize","tokenUrl":"https://2pr.io/api/oauth/token","refreshUrl":"https://2pr.io/api/oauth/token","scopes":{"mcp":"Call the 2pr.io MCP tools on behalf of the signed-in user."}}}},"personalToken":{"type":"http","scheme":"bearer","description":"Personal token created by the account owner under https://2pr.io/app/preferences. Revocable there at any time."}},"schemas":{"InitializeRequest":{"type":"object","required":["jsonrpc","id","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"JSON-RPC request id. Omit it to send a notification (answered with 202).","oneOf":[{"type":"string"},{"type":"integer"}]},"method":{"type":"string","const":"initialize"},"params":{"type":"object","properties":{"protocolVersion":{"type":"string","example":"2025-06-18"},"capabilities":{"type":"object"},"clientInfo":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"}}}}}}},"PingRequest":{"type":"object","required":["jsonrpc","id","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"JSON-RPC request id. Omit it to send a notification (answered with 202).","oneOf":[{"type":"string"},{"type":"integer"}]},"method":{"type":"string","const":"ping"}}},"ToolsListRequest":{"type":"object","description":"Returns every tool with its live input schema and MCP annotations.","required":["jsonrpc","id","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"JSON-RPC request id. Omit it to send a notification (answered with 202).","oneOf":[{"type":"string"},{"type":"integer"}]},"method":{"type":"string","const":"tools/list"}}},"ToolsCallRequest":{"type":"object","description":"Invoke one of the 46 tools. `params.name` selects the tool and fixes the shape of `params.arguments`.","required":["jsonrpc","id","method","params"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"JSON-RPC request id. Omit it to send a notification (answered with 202).","oneOf":[{"type":"string"},{"type":"integer"}]},"method":{"type":"string","const":"tools/call"},"params":{"oneOf":[{"$ref":"#/components/schemas/AnalyticsGetPostingTimesParams"},{"$ref":"#/components/schemas/AnalyticsGetPostStatsParams"},{"$ref":"#/components/schemas/AnalyticsGetProfileStatsParams"},{"$ref":"#/components/schemas/CreatorAddParams"},{"$ref":"#/components/schemas/CreatorListParams"},{"$ref":"#/components/schemas/DraftAttachImagesParams"},{"$ref":"#/components/schemas/DraftAttachMediaParams"},{"$ref":"#/components/schemas/DraftCreateParams"},{"$ref":"#/components/schemas/DraftGetParams"},{"$ref":"#/components/schemas/DraftRefineParams"},{"$ref":"#/components/schemas/EngageDraftRepliesParams"},{"$ref":"#/components/schemas/EngageGetInboxParams"},{"$ref":"#/components/schemas/EngagePostReplyParams"},{"$ref":"#/components/schemas/EngageRefreshPostsParams"},{"$ref":"#/components/schemas/EngageSetReactionParams"},{"$ref":"#/components/schemas/InspirationGetTopicsParams"},{"$ref":"#/components/schemas/LinkedinFetchProfileParams"},{"$ref":"#/components/schemas/LinkedinSearchMentionsParams"},{"$ref":"#/components/schemas/MediaCommitUploadParams"},{"$ref":"#/components/schemas/MediaGenerateImageParams"},{"$ref":"#/components/schemas/MediaPrepareUploadParams"},{"$ref":"#/components/schemas/MediaUploadParams"},{"$ref":"#/components/schemas/MediaUploadFromUrlParams"},{"$ref":"#/components/schemas/OutboundAddProspectParams"},{"$ref":"#/components/schemas/OutboundGetFeedParams"},{"$ref":"#/components/schemas/OutboundListProspectsParams"},{"$ref":"#/components/schemas/OutboundRefreshFeedParams"},{"$ref":"#/components/schemas/OutboundRemoveProspectParams"},{"$ref":"#/components/schemas/PersonaAppendDirectiveParams"},{"$ref":"#/components/schemas/PersonaGetParams"},{"$ref":"#/components/schemas/PersonaReplaceParams"},{"$ref":"#/components/schemas/PostDeleteParams"},{"$ref":"#/components/schemas/PostListParams"},{"$ref":"#/components/schemas/PostPublishParams"},{"$ref":"#/components/schemas/PostScheduleParams"},{"$ref":"#/components/schemas/PostUpdateParams"},{"$ref":"#/components/schemas/PreferencesGetParams"},{"$ref":"#/components/schemas/PreferencesUpdateParams"},{"$ref":"#/components/schemas/ProfileListParams"},{"$ref":"#/components/schemas/ViralDismissPostParams"},{"$ref":"#/components/schemas/ViralSavePostParams"},{"$ref":"#/components/schemas/ViralSearchParams"},{"$ref":"#/components/schemas/XConnectParams"},{"$ref":"#/components/schemas/XGetStatsParams"},{"$ref":"#/components/schemas/XPublishParams"},{"$ref":"#/components/schemas/XScheduleParams"}],"discriminator":{"propertyName":"name","mapping":{"analytics_getPostingTimes":"#/components/schemas/AnalyticsGetPostingTimesParams","analytics_getPostStats":"#/components/schemas/AnalyticsGetPostStatsParams","analytics_getProfileStats":"#/components/schemas/AnalyticsGetProfileStatsParams","creator_add":"#/components/schemas/CreatorAddParams","creator_list":"#/components/schemas/CreatorListParams","draft_attachImages":"#/components/schemas/DraftAttachImagesParams","draft_attachMedia":"#/components/schemas/DraftAttachMediaParams","draft_create":"#/components/schemas/DraftCreateParams","draft_get":"#/components/schemas/DraftGetParams","draft_refine":"#/components/schemas/DraftRefineParams","engage_draftReplies":"#/components/schemas/EngageDraftRepliesParams","engage_getInbox":"#/components/schemas/EngageGetInboxParams","engage_postReply":"#/components/schemas/EngagePostReplyParams","engage_refreshPosts":"#/components/schemas/EngageRefreshPostsParams","engage_setReaction":"#/components/schemas/EngageSetReactionParams","inspiration_getTopics":"#/components/schemas/InspirationGetTopicsParams","linkedin_fetchProfile":"#/components/schemas/LinkedinFetchProfileParams","linkedin_searchMentions":"#/components/schemas/LinkedinSearchMentionsParams","media_commitUpload":"#/components/schemas/MediaCommitUploadParams","media_generateImage":"#/components/schemas/MediaGenerateImageParams","media_prepareUpload":"#/components/schemas/MediaPrepareUploadParams","media_upload":"#/components/schemas/MediaUploadParams","media_uploadFromUrl":"#/components/schemas/MediaUploadFromUrlParams","outbound_addProspect":"#/components/schemas/OutboundAddProspectParams","outbound_getFeed":"#/components/schemas/OutboundGetFeedParams","outbound_listProspects":"#/components/schemas/OutboundListProspectsParams","outbound_refreshFeed":"#/components/schemas/OutboundRefreshFeedParams","outbound_removeProspect":"#/components/schemas/OutboundRemoveProspectParams","persona_appendDirective":"#/components/schemas/PersonaAppendDirectiveParams","persona_get":"#/components/schemas/PersonaGetParams","persona_replace":"#/components/schemas/PersonaReplaceParams","post_delete":"#/components/schemas/PostDeleteParams","post_list":"#/components/schemas/PostListParams","post_publish":"#/components/schemas/PostPublishParams","post_schedule":"#/components/schemas/PostScheduleParams","post_update":"#/components/schemas/PostUpdateParams","preferences_get":"#/components/schemas/PreferencesGetParams","preferences_update":"#/components/schemas/PreferencesUpdateParams","profile_list":"#/components/schemas/ProfileListParams","viral_dismissPost":"#/components/schemas/ViralDismissPostParams","viral_savePost":"#/components/schemas/ViralSavePostParams","viral_search":"#/components/schemas/ViralSearchParams","x_connect":"#/components/schemas/XConnectParams","x_getStats":"#/components/schemas/XGetStatsParams","x_publish":"#/components/schemas/XPublishParams","x_schedule":"#/components/schemas/XScheduleParams"}}}}},"AnalyticsGetPostingTimesParams":{"type":"object","description":"Posting-time history for the profile over the last 365 days — ISO timestamps of every LinkedIn post. Use to build best-time-to-post heatmaps. Requires an active Pro/Team subscription.","x-mcp-annotations":{"title":"Get Best Posting Times","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"analytics_getPostingTimes"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"AnalyticsGetPostStatsParams":{"type":"object","description":"Per-post analytics for the profile's LinkedIn posts: likes, comments, post type, URL, posted timestamp, and time-series metrics snapshot (impressions, members reached, reactions, saves, clicks). If the stored metrics are older than 6 hours, the call first refreshes them from LinkedIn, which can take 10-40 seconds. Supports pagination and sorting; sort_by impressions ranks top posts by lifetime impressions, with posts that have no impression count last. Each post carries funnel_stage: tofu (visibility: opinions, takes, personal updates), mofu (education: how-tos, frameworks, case studies, lessons), bofu (explicit call to action), or null if not classified yet. Pass funnel_stage to only return posts of that stage, e.g. mofu for the user's educational posts. Requires an active Pro/Team subscription. LinkedIn only: for X (Twitter) posts use `x_getStats`.","x-mcp-annotations":{"title":"Get Post Analytics","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"analytics_getPostStats"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"start_date":{"type":"string","format":"date-time"},"end_date":{"type":"string","format":"date-time"},"offset":{"type":"integer","minimum":0},"limit":{"type":"integer","minimum":1,"maximum":100},"sort_by":{"type":"string","enum":["created_at_linkedin","likes","comments","impressions"]},"sort_direction":{"type":"string","enum":["asc","desc"]},"funnel_stage":{"type":"string","enum":["tofu","mofu","bofu"]}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"AnalyticsGetProfileStatsParams":{"type":"object","description":"Fetch the profile's LinkedIn analytics dashboard — current follower count, daily follower changes, daily impressions/reactions/comments, members reached total, and daily post counts. Works for personal and company profiles. Default window is the last 730 days (personal) or 365 days (company, LinkedIn cap). Requires an active Pro/Team subscription. LinkedIn only: for X (Twitter) stats use `x_getStats`.","x-mcp-annotations":{"title":"Get Profile Analytics","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"analytics_getProfileStats"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"start_date":{"type":"string","format":"date-time","description":"ISO 8601 timestamp. Defaults to ~2 years ago."},"end_date":{"type":"string","format":"date-time","description":"ISO 8601 timestamp. Defaults to now."}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"CreatorAddParams":{"type":"object","description":"Add a LinkedIn creator (by profile URL) to the caller's saved creator list. Capped at 20 creators per list.","x-mcp-annotations":{"title":"Add Creator","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"creator_add"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"creator_url":{"type":"string","minLength":1}},"required":["profile_id","creator_url"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"CreatorListParams":{"type":"object","description":"List the caller's saved LinkedIn creators (the \"My Creators\" list — auto-created on first call).","x-mcp-annotations":{"title":"List Saved Creators","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"creator_list"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"DraftAttachImagesParams":{"type":"object","description":"Attach one or more images to a draft by their public URL so they show in the draft editor and go out with the post. Pass the 2pr-hosted URLs surfaced in the conversation context when the user attaches an image, or a URL returned by media_upload / media_uploadFromUrl. LinkedIn allows up to 9 images per post; attaching images replaces any existing video or document on the draft (a post can carry only one media kind). `alt_texts` is optional and index-aligned to `image_urls`. Returns the updated draft with its full `image_urls` list.","x-mcp-annotations":{"title":"Attach Images to Draft","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"draft_attachImages"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"draft_id":{"type":"string","format":"uuid"},"image_urls":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":9},"alt_texts":{"type":"array","items":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}}},"required":["profile_id","draft_id","image_urls"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"DraftAttachMediaParams":{"type":"object","description":"Attach a single video or PDF document to a draft by its public 2pr URL so it shows in the draft editor and goes out with the post. Use draft_attachImages for images instead. A LinkedIn post can carry only one media kind, so attaching a video or document replaces any images, video, or document already on the draft. `media_type` must match the file: `video` for mp4/mov, `document` for PDF. Pass a URL returned by media_upload / media_uploadFromUrl / media_commitUpload — their `kind` tells you which `media_type` to use. `title` is optional; for a document it becomes the LinkedIn document title. Returns the updated draft.","x-mcp-annotations":{"title":"Attach Media to Draft","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"draft_attachMedia"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"draft_id":{"type":"string","format":"uuid"},"media_type":{"type":"string","enum":["video","document"]},"url":{"type":"string","format":"uri"},"title":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["profile_id","draft_id","media_type","url"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"DraftCreateParams":{"type":"object","description":"Create a new draft post from the user input text and render it as three variants of the SAME angle: `regular_body` (standard medium-form), `extended_body` (longer, more depth), and `original_body` (the raw frontier-model baseline: natural paragraphs, none of our house style layered on). The three variants are NOT different hook angles. For angle diversity (different framings of the same topic), call this tool multiple times with different `user_input_text` framings. To ground the post in a web page, article, YouTube video, LinkedIn post, or Reddit thread, pass its link as `source_url` and put the angle/instructions in `user_input_text` (e.g. \"a contrarian take on this\"); the server fetches and extracts the page and folds it into the generation. To draft FROM a file or voice note the user attached (its 2pr storage URL is given in the conversation context), pass that URL as `source_file_url`: documents (PDF/DOCX/PPTX/CSV) are read as the source and audio/video (voice notes, MP4) are transcribed, with `user_input_text` the optional instruction alongside it. There are two ways to pass an image the user attached, and picking the wrong one is the most common way to ruin a draft. Use `source_image_urls` ONLY when the post should be ABOUT what is in the picture: each image is vision-described into the generation, which makes the body explain the image and state that the post is based on a file the user shared. Use `attach_image_urls` when the picture is just the visual and the user already supplied the words (a meme caption, a screenshot they are reacting to): the image is attached to the draft but kept out of the generation, so the post does not narrate what the reader can already see. Either way the image ships on the draft, so neither needs a separate draft_attachImages call. When the user hands over FINISHED post copy (written in this conversation or another tool) and wants it kept as is, set `verbatim: true`: the text in `user_input_text` is stored unchanged as all three bodies and no generation runs, so nothing is rewritten. Provide at least one of `user_input_text`, `source_url`, `source_file_url`, or `source_image_urls`. Generation runs synchronously so the returned `url` opens an already-populated editor. Takes ~10-20s (longer with `source_url`). If generation fails the draft is still created with empty bodies and the caller can retry from the editor. For a long-form LinkedIn ARTICLE or NEWSLETTER EDITION (not a feed post), set `format: \"article\"` (optionally `length`): the draft holds one article in markdown (a `# Title` line, `##` section headings) instead of three post variants, written from `user_input_text` and optionally `source_url`. It takes 1-2 minutes. LinkedIn does not let apps publish articles, so the draft editor at `url` has copy buttons that paste it into the LinkedIn article editor with formatting intact; do not try to publish or schedule an article.","x-mcp-annotations":{"title":"Create Draft","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"draft_create"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"user_input_text":{"type":"string","minLength":1,"maxLength":10000},"source_url":{"type":"string","format":"uri","description":"Optional public http(s) link to ground the post in (article, YouTube, LinkedIn post, Reddit thread). Its extracted content is folded into the generation alongside user_input_text."},"source_file_url":{"type":"string","format":"uri"},"source_image_urls":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":4,"description":"2pr storage URLs of attached images to draft FROM: each is vision-described into the generation and auto-attached to the new draft. Use the hosted URLs given in the conversation context for user attachments. Use this ONLY when the post should be ABOUT what is in the picture: it routes generation down the write-about-this-source path, which describes the image in the body and states that the post is based on a file the user shared. For a picture that simply ships with the post, use attach_image_urls instead."},"attach_image_urls":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":9,"description":"2pr storage URLs of images to ATTACH to the new draft WITHOUT feeding them into generation. Use this whenever the picture is the visual and the words come from the user: a meme the user wrote the caption for, a screenshot they are reacting to, a chart they are commenting on. The body is generated from user_input_text alone, so the post does not narrate what the reader can already see."},"verbatim":{"type":"boolean","description":"Set true when user_input_text is the finished post and must not be rewritten. The text is saved exactly as sent and no generation runs. Requires user_input_text; cannot be combined with source_url, source_file_url, or source_image_urls. attach_image_urls still works."},"format":{"type":"string","enum":["post","article"],"description":"\"article\" writes a long-form LinkedIn article or newsletter edition instead of a feed post. Default \"post\"."},"length":{"type":"string","enum":["short","standard","deep"],"description":"Article length only: short is about 800 words, standard about 1,500 (default), deep about 2,500."}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"DraftGetParams":{"type":"object","description":"Read an existing draft by id. Returns the three variants of the same angle (`regular_body` standard, `extended_body` longer, `original_body` the raw frontier-model baseline in natural paragraphs), plus `first_comment`, any attached media (images/video/document) and the LinkedIn `poll` if the draft has one. On the chat surface the attached images are also shown to you as vision blocks alongside the result, so you can see and comment on their actual content. Use this before scheduling/publishing a draft when you need to inspect or copy its contents — or pass `draft_id` directly into post_publish/post_schedule and let them hydrate from the draft.","x-mcp-annotations":{"title":"Get Draft","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"draft_get"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"draft_id":{"type":"string","format":"uuid"}},"required":["profile_id","draft_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"DraftRefineParams":{"type":"object","description":"Replace a draft body WHOLESALE. Only the provided fields are written. Variants follow draft_create semantics: `regular_body` standard, `extended_body` longer, `original_body` the raw frontier-model baseline in natural paragraphs. Same angle, different treatment. Pass `variant` plus the MATCHING body field to rewrite a single variant and leave the other two untouched. WARNING: the body you send REPLACES what is stored, so any edit the user made in the editor since the draft was last read is overwritten. To attach or replace a LinkedIn poll on the draft, pass `poll` (it removes any images, video or document, since a poll cannot carry media); to drop the poll, set `remove_poll: true`. Returns the updated draft and a `url` deep link to the draft editor.","x-mcp-annotations":{"title":"Refine Draft","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"draft_refine"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"draft_id":{"type":"string","format":"uuid"},"variant":{"type":"string","enum":["regular","extended","original"]},"user_input_text":{"type":["string","null"]},"regular_body":{"type":["string","null"],"description":"To tag a person or company, paste the `mention` string from linkedin_searchMentions inline where the name should appear (`@[Name](urn:li:person:X)` or `@[Name](urn:li:organization:N)`). A mention string may end with a quoted profile URL inside the parentheses; keep it exactly as given. Never invent a URN. Tags do not work in first_comment."},"extended_body":{"type":["string","null"],"description":"To tag a person or company, paste the `mention` string from linkedin_searchMentions inline where the name should appear (`@[Name](urn:li:person:X)` or `@[Name](urn:li:organization:N)`). A mention string may end with a quoted profile URL inside the parentheses; keep it exactly as given. Never invent a URN. Tags do not work in first_comment."},"original_body":{"type":["string","null"],"description":"To tag a person or company, paste the `mention` string from linkedin_searchMentions inline where the name should appear (`@[Name](urn:li:person:X)` or `@[Name](urn:li:organization:N)`). A mention string may end with a quoted profile URL inside the parentheses; keep it exactly as given. Never invent a URN. Tags do not work in first_comment."},"first_comment":{"type":["string","null"]},"first_comment_image_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"is_copied":{"type":"boolean"},"is_shared":{"type":"boolean"},"seo_title":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"seo_description":{"anyOf":[{"type":"string","maxLength":400},{"type":"null"}]},"poll":{"type":"object","properties":{"question":{"type":"string","minLength":1,"maxLength":140},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":30},"minItems":2,"maxItems":4},"duration":{"type":"string","enum":["ONE_DAY","THREE_DAYS","SEVEN_DAYS","FOURTEEN_DAYS"]}},"required":["question","options","duration"],"additionalProperties":false,"description":"A LinkedIn poll to publish with the post. Send it ONLY when the user asked for a poll. It needs a question and answer options: at least two options, at most four, each a short phrase. `duration` is how long voting stays open. A poll cannot be combined with images, a video or a document, and it cannot be edited after it is published. The post text still goes out above the poll."},"remove_poll":{"type":"boolean","description":"Set true ONLY when the user asked to remove the poll from the draft."}},"required":["profile_id","draft_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"EngageDraftRepliesParams":{"type":"object","description":"Generate 5 AI reply variants for a LinkedIn comment, using the user's persona and custom engage instructions. Each reply is capped at 220 characters. Pass the comment text and (optionally) the parent post text and commenter identity for better grounding.","x-mcp-annotations":{"title":"Draft Comment Replies","readOnlyHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"engage_draftReplies"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"comment_text":{"type":"string","minLength":1,"maxLength":2000},"post_text":{"anyOf":[{"type":"string","maxLength":10000},{"type":"null"}]},"commenter_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"commenter_headline":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["profile_id","comment_text"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"EngageGetInboxParams":{"type":"object","description":"Fetch the user's LinkedIn engagement inbox: top-level comments on their recent posts (last 30 days, up to 20 posts), with parent post context and a `needs_reply` flag. Served from a stored copy that refreshes when the user opens the Engage page, not on every call; `freshness_note` in the result says how old the copy is, and a very recent comment may not be in it yet. Set only_needs_reply:true to filter out comments the user has already replied to.","x-mcp-annotations":{"title":"Get Engagement Inbox","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"engage_getInbox"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"only_needs_reply":{"type":"boolean"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"EngagePostReplyParams":{"type":"object","description":"Post a reply to a LinkedIn post or comment. Omit parent_comment_urn to post a top-level comment on the share; provide it (a urn:li:comment:(...) URN) to reply within a comment thread. Returns the new comment's URN.","x-mcp-annotations":{"title":"Post Comment Reply","destructiveHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"engage_postReply"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"share_urn":{"type":"string","pattern":"^urn:li:"},"text":{"type":"string","minLength":1,"maxLength":1250},"parent_comment_urn":{"anyOf":[{"type":"string","pattern":"^urn:li:comment:"},{"type":"null"}]}},"required":["profile_id","share_urn","text"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"EngageRefreshPostsParams":{"type":"object","description":"Refresh the user's LinkedIn posts from Datawave (the data source the engage inbox reads). Slow — can take 2-3 minutes. Use when the inbox looks stale or after the user posts off-platform. Also marks the profile as synced so the on-load auto-refresh won't re-fire. Returns the number of posts upserted.","x-mcp-annotations":{"title":"Refresh Engagement Posts","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"engage_refreshPosts"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"EngageSetReactionParams":{"type":"object","description":"Set or clear the caller's reaction to a LinkedIn comment. Pass a reaction_type to react (LIKE, PRAISE, EMPATHY, INTEREST, APPRECIATION, ENTERTAINMENT); pass null to clear an existing reaction. Idempotent: setting the same reaction twice is a no-op.","x-mcp-annotations":{"title":"React to Comment","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"engage_setReaction"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"comment_urn":{"type":"string","pattern":"^urn:li:comment:\\(urn:li:[a-z]+:[^,]+,\\d+\\)$"},"reaction_type":{"anyOf":[{"type":"string","enum":["LIKE","PRAISE","EMPATHY","INTEREST","APPRECIATION","ENTERTAINMENT"]},{"type":"null"}]}},"required":["profile_id","comment_urn","reaction_type"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"InspirationGetTopicsParams":{"type":"object","description":"Read the user's current inspiration feed: the personalized post ideas 2pr.io generates from their sources: viral LinkedIn posts, viral X (Twitter) posts, Reddit, Hacker News, GitHub trending, newsletters and blogs they added, and their own posts and comments. Returns each idea's title, source, and a source label. Use when the user asks for post ideas, inspiration, or \"what should I write about\", and also when they ask for trending tweets or X posts to repurpose. Returns an empty list when the feed has not been generated yet.","x-mcp-annotations":{"title":"Get Inspiration Topics","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"inspiration_getTopics"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"LinkedinFetchProfileParams":{"type":"object","description":"Kick off background enrichment of the caller's active profile from a LinkedIn URL (pulls headline, summary, recent posts, etc.). Returns the current profile snapshot immediately — the enriched fields land asynchronously.","x-mcp-annotations":{"title":"Fetch LinkedIn Profile","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"linkedin_fetchProfile"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"linkedin_url":{"type":"string","minLength":1}},"required":["profile_id","linkedin_url"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"LinkedinSearchMentionsParams":{"type":"object","description":"Resolve a person or company to the LinkedIn URN needed to @-mention them in a post. Query with a full name, a company name, or a LinkedIn profile or company page URL. Each result carries a ready-to-use `mention` string: paste it verbatim into the post `text` where the tag should appear and LinkedIn renders a real tag on publish. Tags only work in the post body, not in `first_comment`. Coverage is partial: a person is found only if they follow a page the user administers, have engaged with the user, or their profile URL is passed directly; a company is found only by an exact page name or URL. Needs the user's LinkedIn account connected. An empty list means no match: if the query was a name, ask the user for the profile URL once; otherwise post without the tag. Never invent a URN, an unknown one publishes as literal text.","x-mcp-annotations":{"title":"Search LinkedIn Mentions","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"linkedin_searchMentions"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"query":{"type":"string","minLength":1,"maxLength":200,"description":"A full name, a company name, or a LinkedIn profile or company page URL."},"type":{"type":"string","enum":["person","company"],"description":"Limit the search to people or companies. Omit to search both."}},"required":["profile_id","query"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"MediaCommitUploadParams":{"type":"object","description":"Step 2 of the two-step upload flow. Pass the `storage_path` returned by media_prepareUpload after your PUT upload completed. Server verifies the object exists, size is within the per-type limit, and content type is allowed, then returns the public URL and media `kind`. The returned `kind` tells you where the URL goes: attach to a draft with draft_attachImages (`image`) or draft_attachMedia (`video`/`document`), or pass it straight to post_publish/post_schedule as `image_urls` (image), `video_url` (video), or `document_url` + `document_title` (document — the `alt_text` you supply is used as the title).","x-mcp-annotations":{"title":"Commit Media Upload","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"media_commitUpload"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"storage_path":{"type":"string","minLength":1,"maxLength":500},"alt_text":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["profile_id","storage_path"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"MediaGenerateImageParams":{"type":"object","description":"Generate an AI image from a text prompt (the same \"create image\" pipeline the draft editor uses) and return a hosted 2pr public URL. Pass that url to draft_attachImages, or in `image_urls` on post_publish/post_schedule. `prompt` should describe the image you want (the generator targets a 4:5 portrait aspect ratio suited to LinkedIn). Optional `reference_image_urls` (up to 3 public image urls, <= 10 MB each) edits, varies, or combines those images instead of generating from scratch — pass several when the user gives you several (two brand logos to put in one scene, a screenshot plus a style reference), and describe in `prompt` what to do with each. An image the user attached in chat is already hosted: use its URL here rather than describing it from memory. `backend` defaults to a fast, cheap model for fresh generations and to `google_banana_pro` (best edit quality) when reference images are set; an explicit `backend` is always honored, so pass `openai` if the user asks for the OpenAI/ChatGPT image model. Requires an active subscription; rate-limited.","x-mcp-annotations":{"title":"Generate Image","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"media_generateImage"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"prompt":{"type":"string","minLength":1,"maxLength":2000},"backend":{"type":"string","enum":["google_banana_lite","google_banana_pro","openai"]},"reference_image_urls":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":3,"description":"Public image URLs to edit, vary, or combine. Prefer this over reference_image_url."},"reference_image_url":{"type":"string","format":"uri","description":"Deprecated single-image form of reference_image_urls; still accepted."},"alt_text":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"orientation":{"type":"string","enum":["portrait","landscape"],"description":"portrait (4:5, default) for feed posts; landscape (16:9) for a LinkedIn article cover."}},"required":["profile_id","prompt"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"MediaPrepareUploadParams":{"type":"object","description":"Step 1 of the two-step upload flow. Returns a signed PUT URL the agent can upload bytes to out-of-band (no base64 in context). After the PUT succeeds, call media_commitUpload with `storage_path` to finalise and get the public URL. Use this for any local file — it scales to the full per-type ceiling (10 MB images, 100 MB video/PDF), unlike media_upload which is bottlenecked by tool-call context limits. Supported content types: image/png|jpeg|webp|gif, video/mp4, video/quicktime, application/pdf.","x-mcp-annotations":{"title":"Prepare Media Upload","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"media_prepareUpload"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"content_type":{"type":"string","enum":["image/png","image/jpeg","image/jpg","image/webp","image/gif","video/mp4","video/quicktime","application/pdf"]}},"required":["profile_id","content_type"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"MediaUploadParams":{"type":"object","description":"Upload an image, video, or PDF document (base64-encoded) to 2pr storage and return its public URL and media `kind`. Supported content types: image/png, image/jpeg, image/webp, image/gif (<= 10 MB); video/mp4, video/quicktime (<= 100 MB); application/pdf (<= 100 MB). The returned `kind` tells you where the URL goes: attach to a draft with draft_attachImages (`image`) or draft_attachMedia (`video`/`document`), or pass it straight to post_publish/post_schedule as `image_urls` (image), `video_url` (video), or `document_url` + `document_title` (document — the `alt_text` you supply is used as the title). For video and PDF, prefer the two-step media_prepareUpload/media_commitUpload flow — files this large rarely fit inside a single tool call as base64.","x-mcp-annotations":{"title":"Upload Media","readOnlyHint":false,"destructiveHint":false},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"media_upload"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"filename":{"type":"string","minLength":1,"maxLength":200},"content_type":{"type":"string","enum":["image/png","image/jpeg","image/jpg","image/webp","image/gif","video/mp4","video/quicktime","application/pdf"]},"data_base64":{"type":"string","minLength":1,"description":"Raw base64 (no `data:` prefix). Decoded byte size must be within the per-type limit."},"alt_text":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"description":"Alt text for images; used as the document title for PDFs."}},"required":["profile_id","filename","content_type","data_base64"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"MediaUploadFromUrlParams":{"type":"object","description":"Fetch a media file from a public URL and re-upload it to 2pr storage. Returns the rehosted public URL and media `kind`. Use this when the user has the file at any public link (imgbb, Dropbox share, an existing CDN URL) and the agent cannot read the raw bytes itself. Allowed content types: image/png|jpeg|webp|gif (<= 10 MB); video/mp4, video/quicktime (<= 100 MB); application/pdf (<= 100 MB). The returned `kind` tells you where the URL goes: attach to a draft with draft_attachImages (`image`) or draft_attachMedia (`video`/`document`), or pass it straight to post_publish/post_schedule as `image_urls` (image), `video_url` (video), or `document_url` + `document_title` (document — the `alt_text` you supply is used as the title).","x-mcp-annotations":{"title":"Upload Media from URL","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"media_uploadFromUrl"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"filename":{"type":"string","minLength":1,"maxLength":200},"alt_text":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]}},"required":["profile_id","url"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"OutboundAddProspectParams":{"type":"object","description":"Add a person to the \"Engage with\" prospect list (Outbound comments) by LinkedIn member profile URL or bare vanity handle. A plain name only works when it matches exactly one creator we already index, so prefer the profile URL. Company pages are rejected. Adding someone already on the list returns the existing entry. The list has a fixed number of slots; outbound_listProspects reports how many are left.","x-mcp-annotations":{"title":"Add Prospect","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"outbound_addProspect"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"linkedin_url":{"type":"string","minLength":1,"maxLength":500}},"required":["profile_id","linkedin_url"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"OutboundGetFeedParams":{"type":"object","description":"Recent LinkedIn posts by the people on the \"Engage with\" prospect list (Outbound comments), newest first: the last few posts per prospect from the past 5 days, with engagement counts and whether the user already commented. Pass prospect_id to get one person's latest posts. Served from a stored copy that refreshes when the user opens the Outbound tab; it never contacts LinkedIn. `freshness_note` says how old the copy is; when `stale_prospects` is above zero, call outbound_refreshFeed and read again before concluding someone has not posted.","x-mcp-annotations":{"title":"Get Prospect Posts","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"outbound_getFeed"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"prospect_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"OutboundListProspectsParams":{"type":"object","description":"List the \"Engage with\" prospect list on the Engage page (Outbound comments): the people whose posts the user tracks to comment on. Separate from the \"My Creators\" list (creator_list), which feeds the viral library. Returns the prospects plus how many slots the list has and how many are still free.","x-mcp-annotations":{"title":"List Prospects","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"outbound_listProspects"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"OutboundRefreshFeedParams":{"type":"object","description":"Poll LinkedIn for new posts from the \"Engage with\" prospects whose stored copy is older than the refresh window, then read outbound_getFeed again for the result. Can take up to a minute for a full list. Each prospect polled spends provider budget, so there is a daily cap; `refresh_limited` is true when some prospects could not be refreshed today and the feed still holds their older posts. Set force:true to re-poll everyone regardless of age; it still counts against the cap.","x-mcp-annotations":{"title":"Refresh Prospect Posts","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"outbound_refreshFeed"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"force":{"type":"boolean"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"OutboundRemoveProspectParams":{"type":"object","description":"Remove a prospect from the \"Engage with\" list (Outbound comments) by the id outbound_listProspects returns. Frees a slot; their posts stop appearing in the outbound feed.","x-mcp-annotations":{"title":"Remove Prospect","destructiveHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"outbound_removeProspect"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"prospect_id":{"type":"string","format":"uuid"}},"required":["profile_id","prospect_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PersonaAppendDirectiveParams":{"type":"object","description":"Append a voice/style directive (single bullet, at most 1000 characters) into the user-directives section of a profile's persona. Creates a baseline persona from onboarding if none exists. For anything longer, split it across several calls or use persona_replace.","x-mcp-annotations":{"title":"Add Voice Directive","readOnlyHint":false,"destructiveHint":false,"idempotentHint":false},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"persona_appendDirective"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"directive":{"type":"string","minLength":1,"maxLength":1000}},"required":["profile_id","directive"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PersonaGetParams":{"type":"object","description":"Get the persona / voice brief for a profile in markdown. Returns null content if no persona is set and no onboarding data exists to derive one.","x-mcp-annotations":{"title":"Get Voice Profile","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"persona_get"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PersonaReplaceParams":{"type":"object","description":"Replace the full persona / voice brief content for a profile. Normalizes via cleanPersonaForEditor and writes a version snapshot.","x-mcp-annotations":{"title":"Replace Voice Profile","destructiveHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"persona_replace"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"content":{"type":"string","minLength":1}},"required":["profile_id","content"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PostDeleteParams":{"type":"object","description":"Cancel a scheduled post that hasn't been published yet — removes a queued post from the planner. This does NOT and cannot delete or remove a post that is already live on LinkedIn. Throws 404 if the post is already posted or does not exist.","x-mcp-annotations":{"title":"Delete Scheduled Post","destructiveHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"post_delete"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"scheduled_post_id":{"type":"string","format":"uuid"}},"required":["profile_id","scheduled_post_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PostListParams":{"type":"object","description":"List posts for a profile from the planner queue, across LinkedIn, X and Telegram (each row has a `platform`). Defaults to pending rows only (`status=scheduled`, soonest first); pass `posted`, `failed`, or `all` for history (most recent first). Use this to find a `scheduled_post_id` before calling post_update (LinkedIn rows only) or post_delete (any platform). Filter with `draftpost_id` when the user has a draft open and you need its queued slot. `text_preview` is TRUNCATED for display only. Never pass it back as post_update `text`, or you will overwrite the post with a clipped copy. Pass `full_text: true` to also get each post's complete `text`, e.g. to review or edit the copy of queued posts.","x-mcp-annotations":{"title":"List Scheduled Posts","readOnlyHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"post_list"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"draftpost_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["scheduled","posted","failed","all"]},"full_text":{"type":"boolean"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PostPublishParams":{"type":"object","description":"Publish a post to LinkedIn immediately. Persists a row in scheduled_posts with status=posted on success. Pass `draftpost_id` to hydrate text/image_urls/first_comment/video/document from the draft (defaults to the regular body — override with `body_variant`). Explicit fields take precedence over draft fields. `text` is optional when media is attached: a media-only call publishes with no caption. Set do_not_share:true to record the post as published without actually calling LinkedIn (used for tracking off-platform sharing). `first_comment_image_url` attaches an image (or GIF) to the first comment. To publish a LinkedIn poll, pass `poll` (question, options, duration) with the post text and no media; a draft that already carries a poll publishes it automatically.","x-mcp-annotations":{"title":"Publish Post","destructiveHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"post_publish"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"text":{"type":"string","minLength":1,"description":"To tag a person or company, paste the `mention` string from linkedin_searchMentions inline where the name should appear (`@[Name](urn:li:person:X)` or `@[Name](urn:li:organization:N)`). A mention string may end with a quoted profile URL inside the parentheses; keep it exactly as given. Never invent a URN. Tags do not work in first_comment."},"image_urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":9},"author_urn":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"first_comment":{"anyOf":[{"type":"string","maxLength":1250},{"type":"null"}]},"first_comment_image_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"draftpost_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"body_variant":{"type":"string","enum":["regular","extended","original"]},"do_not_share":{"type":"boolean"},"document_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"document_title":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}]},"video_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"poll":{"type":"object","properties":{"question":{"type":"string","minLength":1,"maxLength":140},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":30},"minItems":2,"maxItems":4},"duration":{"type":"string","enum":["ONE_DAY","THREE_DAYS","SEVEN_DAYS","FOURTEEN_DAYS"]}},"required":["question","options","duration"],"additionalProperties":false,"description":"A LinkedIn poll to publish with the post. Send it ONLY when the user asked for a poll. It needs a question and answer options: at least two options, at most four, each a short phrase. `duration` is how long voting stays open. A poll cannot be combined with images, a video or a document, and it cannot be edited after it is published. The post text still goes out above the poll."}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PostScheduleParams":{"type":"object","description":"Schedule a post for future delivery to LinkedIn. Pass `draftpost_id` to hydrate text/image_urls/first_comment/video/document from the draft (defaults to the regular body — override with `body_variant`). Explicit fields take precedence. `text` is optional when media is attached: a media-only call schedules with no caption. The scheduled-posts cron picks it up at scheduled_time. `first_comment_image_url` attaches an image (or GIF) to the first comment. To schedule a LinkedIn poll, pass `poll` (question, options, duration) with the post text and no media; a draft that already carries a poll schedules it automatically. Returns a `url` deep link to the planner, where the user reviews and edits scheduled posts.","x-mcp-annotations":{"title":"Schedule Post","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"post_schedule"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"text":{"type":"string","minLength":1,"description":"To tag a person or company, paste the `mention` string from linkedin_searchMentions inline where the name should appear (`@[Name](urn:li:person:X)` or `@[Name](urn:li:organization:N)`). A mention string may end with a quoted profile URL inside the parentheses; keep it exactly as given. Never invent a URN. Tags do not work in first_comment."},"image_urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":9},"author_urn":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"first_comment":{"anyOf":[{"type":"string","maxLength":1250},{"type":"null"}]},"first_comment_image_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"draftpost_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"body_variant":{"type":"string","enum":["regular","extended","original"]},"do_not_share":{"type":"boolean"},"document_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"document_title":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}]},"video_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"poll":{"type":"object","properties":{"question":{"type":"string","minLength":1,"maxLength":140},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":30},"minItems":2,"maxItems":4},"duration":{"type":"string","enum":["ONE_DAY","THREE_DAYS","SEVEN_DAYS","FOURTEEN_DAYS"]}},"required":["question","options","duration"],"additionalProperties":false,"description":"A LinkedIn poll to publish with the post. Send it ONLY when the user asked for a poll. It needs a question and answer options: at least two options, at most four, each a short phrase. `duration` is how long voting stays open. A poll cannot be combined with images, a video or a document, and it cannot be edited after it is published. The post text still goes out above the poll."},"scheduled_time":{"type":"string","format":"date-time"}},"required":["profile_id","scheduled_time"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PostUpdateParams":{"type":"object","description":"Update an existing scheduled LinkedIn post without cancelling and re-booking it. Pass `scheduled_post_id` from post_list (or from a prior post_schedule result). Provide only the fields that change: `text`, `image_urls`, `scheduled_time`, `first_comment`, video/document fields, or `poll` (a poll replaces any media on the queued post). Pass `draftpost_id` to pull the draft's current media onto the queued post (use this after draft_attachImages); it does NOT touch the queued text, which the user may have edited in the planner. To change copy, pass `text` explicitly. Use this when the user asks to add a photo, edit copy, or move the time on a post that is already in the planner. Does not work on posts that have already gone live. Returns the same planner deep link as post_schedule.","x-mcp-annotations":{"title":"Update Scheduled Post","destructiveHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"post_update"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"text":{"type":"string","minLength":1,"description":"To tag a person or company, paste the `mention` string from linkedin_searchMentions inline where the name should appear (`@[Name](urn:li:person:X)` or `@[Name](urn:li:organization:N)`). A mention string may end with a quoted profile URL inside the parentheses; keep it exactly as given. Never invent a URN. Tags do not work in first_comment."},"image_urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":9},"author_urn":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"first_comment":{"anyOf":[{"type":"string","maxLength":1250},{"type":"null"}]},"first_comment_image_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"draftpost_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"do_not_share":{"type":"boolean"},"document_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"document_title":{"anyOf":[{"type":"string","minLength":1,"maxLength":100},{"type":"null"}]},"video_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"poll":{"type":"object","properties":{"question":{"type":"string","minLength":1,"maxLength":140},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":30},"minItems":2,"maxItems":4},"duration":{"type":"string","enum":["ONE_DAY","THREE_DAYS","SEVEN_DAYS","FOURTEEN_DAYS"]}},"required":["question","options","duration"],"additionalProperties":false,"description":"A LinkedIn poll to publish with the post. Send it ONLY when the user asked for a poll. It needs a question and answer options: at least two options, at most four, each a short phrase. `duration` is how long voting stays open. A poll cannot be combined with images, a video or a document, and it cannot be edited after it is published. The post text still goes out above the poll."},"scheduled_post_id":{"type":"string","format":"uuid"},"scheduled_time":{"type":"string","format":"date-time"}},"required":["profile_id","scheduled_post_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PreferencesGetParams":{"type":"object","description":"Read the content preferences for a profile: language, theme, audience, goal, persona, tone, reach-vs-authenticity priority, geography, emoji use, Engage reply instructions, and which inspiration sources, subreddits and feeds are in play. Use this before changing anything so you say what is actually set. Not the voice brief, which is persona_get.","x-mcp-annotations":{"title":"Get Preferences","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"preferences_get"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"PreferencesUpdateParams":{"type":"object","description":"Change content preferences for a profile. Send only the fields to change; pass null to clear one. It takes effect everywhere at once (drafting, the inspiration feed, Engage replies). Cannot set the voice brief (use persona_replace / persona_appendDirective) or any internal flag.","x-mcp-annotations":{"title":"Update Preferences","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"preferences_update"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"language":{"type":["string","null"],"description":"Language the posts are written in, as an English language name (\"English\", \"Russian\")."},"content_primary_theme":{"type":["string","null"],"description":"Main subject the creator posts about. Changing it re-derives which subreddits we track."},"target_audience":{"type":["string","null"],"description":"Who the posts are written for."},"content_goal":{"type":["string","null"],"description":"What the creator wants the posts to achieve."},"content_persona":{"type":["string","null"],"description":"The role the creator writes as (founder, engineer, coach)."},"content_tone":{"type":["string","null"],"description":"How the posts should sound (casual, direct, warm)."},"content_priority":{"type":["string","null"],"description":"Balance between reach and authenticity. Canonical values: \"Performance only\", \"Metrics first\", \"Balance\", \"Authenticity first\", \"Authenticity only\"."},"geographic_focus":{"type":["string","null"],"description":"Region the audience sits in."},"emoji_preference":{"type":["string","null"],"description":"How much emoji the posts should use."},"engage_custom_instructions":{"type":["string","null"],"description":"Standing instructions for the replies drafted in the Engage inbox."},"custom_subreddits":{"type":["string","null"],"description":"Comma-separated subreddit names to pull inspiration from, without the \"r/\" prefix. Empty falls back to theme-derived subreddits."},"inspiration_feeds":{"type":["string","null"],"description":"Comma-separated newsletter / blog feed URLs used by the inspiration feed."},"inspiration_recency_days":{"anyOf":[{"type":"integer","minimum":3,"maximum":30},{"type":"null"}],"description":"How far back the inspiration sources may look, in days."},"muted_inspiration_sources":{"type":["string","null"],"description":"Comma-separated inspiration sources to switch off. Keys: linkedin_library, x, reddit, hackernews, github, substack, my_comments."}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"ProfileListParams":{"type":"object","description":"List all profiles the caller has access to. Call this first to discover available profile_id values — most other tools require a profile_id. Also reports whether X is connected for each profile, so you can tell before attempting to publish there.","x-mcp-annotations":{"title":"List Profiles","readOnlyHint":true,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"profile_list"},"arguments":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"ViralDismissPostParams":{"type":"object","description":"Remove a previously saved viral post from the caller's saved-posts collection.","x-mcp-annotations":{"title":"Dismiss Viral Post","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"viral_dismissPost"},"arguments":{"type":"object","properties":{"viral_post_id":{"type":"string","pattern":"^\\d+$"}},"required":["viral_post_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"ViralSavePostParams":{"type":"object","description":"Save a viral post to the caller's saved-posts collection with optional tags and notes.","x-mcp-annotations":{"title":"Save Viral Post","readOnlyHint":false,"destructiveHint":false,"idempotentHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"viral_savePost"},"arguments":{"type":"object","properties":{"viral_post_id":{"type":"string","pattern":"^\\d+$"},"tags":{"type":"array","items":{"type":"string"}},"notes":{"type":"string"}},"required":["viral_post_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"ViralSearchParams":{"type":"object","description":"Search the viral LinkedIn post catalog by keyword, with optional filters (language, creator URLs, engagement thresholds, date range). Defaults to the last 12 months and reference-quality posts. Put the subject in search_query; every extra filter narrows the catalog, and results are already ranked by relevance weighted by engagement. Phrase search_query as the subject (\"warm outreach\"), not as the request (\"show me posts about warm outreach\"): every word has to appear in the post. The search broadens itself when a query is too narrow, so a thin result means the shorter query was tried too. This catalog is LinkedIn only: for trending X (Twitter) posts, use inspiration_getTopics, whose feed includes viral X posts.","x-mcp-annotations":{"title":"Search Viral Posts","readOnlyHint":true,"idempotentHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"viral_search"},"arguments":{"type":"object","properties":{"search_query":{"type":"string","minLength":1},"geographic_focus":{"type":"string"},"languages":{"type":"string"},"user_url":{"type":"string"},"user_urls":{"type":"array","items":{"type":"string"}},"date_posted_start":{"type":"string"},"date_posted_end":{"type":"string"},"min_likes":{"type":"integer","minimum":0,"description":"Only when the user named a floor (\"at least 1000 likes\"). Omit otherwise."},"max_likes":{"type":"integer","minimum":0,"description":"Only when the user named a ceiling. Omit otherwise; never send 0."},"min_comments":{"type":"integer","minimum":0},"max_comments":{"type":"integer","minimum":0,"description":"Only when the user named a ceiling. Omit otherwise; never send 0."},"min_followers":{"type":"integer","minimum":0},"max_followers":{"type":"integer","minimum":0,"description":"Only when the user named a ceiling. Omit otherwise; never send 0."},"min_posts":{"type":"integer","minimum":0},"max_posts":{"type":"integer","minimum":0,"description":"Only when the user named a ceiling. Omit otherwise; never send 0."},"min_articles":{"type":"integer","minimum":0},"max_articles":{"type":"integer","minimum":0,"description":"Only when the user named a ceiling. Omit otherwise; never send 0."},"has_images":{"type":"boolean","description":"Set true to require an image. Omit for any post; false is ignored."},"has_videos":{"type":"boolean","description":"Set true to require a video. Omit for any post; false is ignored."},"has_carousel":{"type":"boolean","description":"Set true to require a carousel. Omit for any post; false is ignored."},"account_type":{"type":"string","enum":["person","company"]},"sort_by":{"type":"string","enum":["likes","date","relevance","outliers"],"description":"outliers keeps posts with 3x+ their creator's usual likes (median of the creator's recent posts) and ranks by that multiple. Only posts with 300+ likes qualify."},"limit":{"type":"integer","minimum":1,"maximum":50},"offset":{"type":"integer","minimum":0}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"XConnectParams":{"type":"object","description":"ONLY for X (Twitter), and ONLY when the user explicitly asked about X or Twitter. Connecting LinkedIn is a separate flow and this tool has no effect on it. Get a one-tap URL the user opens to connect (or reconnect) their X account to a 2pr profile. There is no X settings screen yet, so this URL is the only way to authorize. Give the user the link and ask them to open it; the page tells them when it worked. The link is single-use and valid for 7 days, so it still works if they come back to it later. Also reports whether X is already connected.","x-mcp-annotations":{"title":"Connect X Account","readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"x_connect"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"XGetStatsParams":{"type":"object","description":"ONLY for X (Twitter), and ONLY when the user asked about X or Twitter. The analytics_* tools are LinkedIn-only; this is the tool for X. Fetch live stats for the connected X account: followers, following, total post count, and the most recent original posts (replies and reposts excluded) with impressions, likes, reposts, replies, quotes, bookmarks and a link for each. Default 20 posts, 5 to 100 via `limit`. Read live from X on every call, so there is no history over time and no follower trend: say so if the user asks for growth over a period. Requires X to be connected: call `x_connect` first if it isn't.","x-mcp-annotations":{"title":"Get X Stats","readOnlyHint":true,"idempotentHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"x_getStats"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"limit":{"type":"integer","minimum":5,"maximum":100}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"XPublishParams":{"type":"object","description":"ONLY for X (Twitter), and ONLY when the user explicitly asked for X or Twitter. LinkedIn is the default destination for every post: if the user did not name X, use `post_publish` instead. Publishing to the wrong network cannot be undone. Publish to X immediately. Pass `text` for a single post or `thread` as an ordered array of segments for a thread. Links count as 23 characters regardless of their real length. The per-post limit depends on the connected account: 280 characters on a free account, up to 25000 on X Premium. Do NOT pre-split long content into a thread assuming 280: send it as one `text`, and only split if the tool rejects it for length. A thread of N segments costs N posts where one long post costs 1. Up to 4 images via `image_urls`, attached to the first segment of a thread. Pass `draftpost_id` to hydrate the body from a draft; a draft that is too long is an error, never a silent trim. X has no document post type, no first comment and no company pages, so PDFs, carousels and video are rejected. Requires X to be connected: call `x_connect` first if it isn't. Set do_not_share:true to record the post without actually calling X. Scheduled X posts show in the 2pr planner next to LinkedIn; use `x_schedule` and `post_delete` to manage them.","x-mcp-annotations":{"title":"Publish to X","destructiveHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"x_publish"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"text":{"type":"string","minLength":1},"thread":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":25},"image_urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":4},"draftpost_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"body_variant":{"type":"string","enum":["regular","extended","original"]},"do_not_share":{"type":"boolean"}},"required":["profile_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"XScheduleParams":{"type":"object","description":"ONLY for X (Twitter), and ONLY when the user explicitly asked for X or Twitter. LinkedIn is the default destination for every post: if the user did not name X, use `post_schedule` instead. Schedule a post or thread for future delivery to X. Same content rules as `x_publish`: `text` (280 characters on a free account, up to 25000 on X Premium, so do not pre-split) or `thread` as an array of segments, up to 4 images, no documents or video. The every-minute publish cron delivers it at `scheduled_time`. Scheduled X posts show in the 2pr planner, where the user can reschedule or cancel them; the returned `id` is what you pass to `post_delete` to cancel it before it goes out.","x-mcp-annotations":{"title":"Schedule to X","destructiveHint":true,"openWorldHint":true},"required":["name","arguments"],"properties":{"name":{"type":"string","const":"x_schedule"},"arguments":{"type":"object","properties":{"profile_id":{"type":"string","format":"uuid"},"text":{"type":"string","minLength":1},"thread":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":25},"image_urls":{"type":"array","items":{"type":"string","format":"uri"},"maxItems":4},"draftpost_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}]},"body_variant":{"type":"string","enum":["regular","extended","original"]},"do_not_share":{"type":"boolean"},"scheduled_time":{"type":"string","format":"date-time"}},"required":["profile_id","scheduled_time"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}},"JsonRpcResponse":{"type":"object","required":["jsonrpc","id"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"JSON-RPC request id. Omit it to send a notification (answered with 202).","oneOf":[{"type":"string"},{"type":"integer"}]},"result":{"type":"object","description":"For `tools/call`: `{ content: [{ type: \"text\", text: <JSON string> }], isError: boolean }`. The text is the tool output serialized as JSON."},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{}}}}},"TokenRequest":{"type":"object","required":["grant_type","client_id"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string"},"code":{"type":"string","description":"authorization_code grant."},"code_verifier":{"type":"string","description":"authorization_code grant (PKCE)."},"redirect_uri":{"type":"string","format":"uri","description":"authorization_code grant."},"refresh_token":{"type":"string","description":"refresh_token grant."},"resource":{"type":"string","format":"uri","example":"https://2pr.io/api/mcp"}}},"ApiError":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string"}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}}}}}