@omnisocials/mcp-server 1.21.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -188,7 +188,7 @@ Apply a set when creating a post: `create_post` / `create_and_publish_post` acce
188
188
 
189
189
  | Tool | Description |
190
190
  |------|-------------|
191
- | `list_inbox_conversations` | List conversations — Instagram/Facebook DMs, comments, mentions, and LinkedIn company-page comments/mentions; filter by platform, type, or unread |
191
+ | `list_inbox_conversations` | List conversations — Instagram/Facebook DMs, comments, mentions, LinkedIn company-page comments/mentions, TikTok video comments, and X DMs (opt-in); filter by platform, type, or unread |
192
192
  | `get_inbox_conversation` | Get a conversation's full message history |
193
193
  | `mark_inbox_read` | Mark all incoming messages in a conversation as read |
194
194
  | `reply_to_inbox` | Reply to a DM, comment, or mention (existing conversations only) |
package/build/client.d.ts CHANGED
@@ -101,11 +101,16 @@ export interface MastodonPostOptionsUpdate {
101
101
  thread_parts?: MastodonThreadPartInput[] | null;
102
102
  }
103
103
  /** Non-sponsored LinkedIn poll: question + 2-4 options + duration. */
104
- export interface LinkedInPollInput {
104
+ export interface LinkedInPollFields {
105
105
  question: string;
106
106
  options: string[];
107
107
  duration: "ONE_DAY" | "THREE_DAYS" | "SEVEN_DAYS" | "FOURTEEN_DAYS";
108
108
  }
109
+ /** LinkedIn poll(s), independent per channel. A channel's poll is omitted/null when it isn't a poll. */
110
+ export interface LinkedInPollInput {
111
+ linkedin?: LinkedInPollFields | null;
112
+ linkedin_page?: LinkedInPollFields | null;
113
+ }
109
114
  /**
110
115
  * Request body for POST /posts/create. `createAndPublishPost` accepts the
111
116
  * same shape minus `scheduled_at` (publishing is immediate).
@@ -4,12 +4,13 @@ const PLATFORM_EMOJI = {
4
4
  instagram: "📸",
5
5
  facebook: "📘",
6
6
  linkedin: "💼",
7
+ tiktok: "🎵",
7
8
  x: "🐦",
8
9
  };
9
10
  export function registerInboxTools(server, getClient) {
10
- server.tool("list_inbox_conversations", "List social inbox conversations (Instagram/Facebook DMs, comments, mentions, LinkedIn company-page comments/mentions, and X DMs where the workspace has opted into X DMs), newest activity first. Cursor-paginated: pass the returned cursor to get the next page.", {
11
+ server.tool("list_inbox_conversations", "List social inbox conversations (Instagram/Facebook DMs, comments, mentions, LinkedIn company-page comments/mentions, TikTok video comments, and X DMs where the workspace has opted into X DMs), newest activity first. Cursor-paginated: pass the returned cursor to get the next page.", {
11
12
  platform: z
12
- .enum(["instagram", "facebook", "linkedin", "x"])
13
+ .enum(["instagram", "facebook", "linkedin", "tiktok", "x"])
13
14
  .optional()
14
15
  .describe("Filter to one platform"),
15
16
  type: z
@@ -137,9 +138,11 @@ export function registerInboxTools(server, getClient) {
137
138
  ],
138
139
  };
139
140
  });
140
- server.tool("reply_to_inbox", "Reply to an existing inbox conversation (DM, comment, or mention) on Instagram, Facebook, LinkedIn, or X. You can only reply to conversations that already exist. Meta direct-message replies must be within the platform's 24-hour messaging window. X replies are DM-only and use 2 prepaid credits per send (X's API fee passed through at cost) — a 402 insufficient_credits error means the organisation needs to top up at https://app.omnisocials.com/credits; relay that link to the user rather than retrying. Each workspace can send up to 1,000 replies per day.", {
141
+ server.tool("reply_to_inbox", "Reply to an existing inbox conversation (DM, comment, or mention) on Instagram, Facebook, LinkedIn, TikTok, or X. You can only reply to conversations that already exist. Meta direct-message replies must be within the platform's 24-hour messaging window. TikTok replies are comments only, text-only, and capped at 150 characters; they can take a few minutes to appear on TikTok while they pass spam review. X replies are DM-only and use 2 prepaid credits per send (X's API fee passed through at cost) — a 402 insufficient_credits error means the organisation needs to top up at https://app.omnisocials.com/credits; relay that link to the user rather than retrying. Each workspace can send up to 1,000 replies per day.", {
141
142
  conversation_id: z.string().describe("The conversation ID to reply to"),
142
- text: z.string().describe("The reply text (max 2000 characters)"),
143
+ text: z
144
+ .string()
145
+ .describe("The reply text (max 2000 characters; TikTok comments max 150)"),
143
146
  }, async ({ conversation_id, text }) => {
144
147
  const result = await getClient().replyToInboxConversation(conversation_id, {
145
148
  text,
@@ -83,8 +83,7 @@ const LINKEDIN_PAGE_OPTIONS = z
83
83
  })
84
84
  .optional()
85
85
  .describe("LinkedIn Company Page options");
86
- const LINKEDIN_POLL_OPTIONS = z
87
- .object({
86
+ const LINKEDIN_POLL_FIELDS = z.object({
88
87
  question: z.string().max(140).describe("The poll question (max 140 characters)."),
89
88
  options: z
90
89
  .array(z.string().max(30))
@@ -94,10 +93,19 @@ const LINKEDIN_POLL_OPTIONS = z
94
93
  duration: z
95
94
  .enum(["ONE_DAY", "THREE_DAYS", "SEVEN_DAYS", "FOURTEEN_DAYS"])
96
95
  .describe("How long the poll stays open for votes."),
96
+ });
97
+ const LINKEDIN_POLL_OPTIONS = z
98
+ .object({
99
+ linkedin: LINKEDIN_POLL_FIELDS.nullable()
100
+ .optional()
101
+ .describe("Poll for the personal profile post. Omit or null = not a poll."),
102
+ linkedin_page: LINKEDIN_POLL_FIELDS.nullable()
103
+ .optional()
104
+ .describe("Poll for the company page post. Omit or null = not a poll."),
97
105
  })
98
106
  .nullable()
99
107
  .optional()
100
- .describe("Non-sponsored LinkedIn poll, posted to whichever of `linkedin`/`linkedin_page` is selected in `accounts`. Mutually exclusive with media and a link share — a poll takes priority over both at publish time. Still requires `content.linkedin` (or `content.default`) as the post's caption; the poll itself only carries the question/options/duration. Pass `null` on update_post to clear a poll and revert to a normal post.");
108
+ .describe("Non-sponsored LinkedIn poll(s) independent per channel, keyed by `linkedin` (personal profile) / `linkedin_page` (company page). A poll is mutually exclusive with media and a link share on that channel's post — a poll takes priority over both at publish time. Still requires `content.linkedin` (or `content.default`) as that channel's caption; the poll itself only carries the question/options/duration. On update_post, set a channel's key to `null` to clear that channel's poll and revert it to a normal post — send the full desired state for both channels, since the whole object replaces wholesale.");
101
109
  // Per-platform option schemas shared verbatim by create_post,
102
110
  // create_and_publish_post and update_post (they were byte-identical in all
103
111
  // three). Platform blocks that genuinely differ per tool (instagram, youtube,
@@ -274,7 +282,7 @@ export function registerPostTools(server, getClient) {
274
282
  content: [{ type: "text", text: md }],
275
283
  };
276
284
  });
277
- server.tool("get_post", "Get details of a specific post by ID — content, channels, media (with URLs + any per-media alt text), first comment, Instagram collaborators/user tags/location/Trial Reel state/Reel cover (thumbnail_type + thumb_offset in ms), dates and live URLs, enough to fully verify a scheduled post without opening the dashboard. When a post has per-platform caption overrides (e.g. a shorter X version alongside the default), every variant is rendered as its own labeled block under `### Content` so you can see exactly what each platform will publish. X threads are rendered under `### X Thread` with each tweet labeled in publish order — read this to see the full chained tweet text, since thread-only posts have no caption in `content`. A LinkedIn poll is rendered under `### LinkedIn Poll` with the question, options, and duration. After publishing, includes `published_urls` — a map of platform → live URL for each platform that successfully posted (e.g. facebook, instagram, linkedin, x). Useful for polling: when a post's status is `published`, read `published_urls` to surface the live links.", {
285
+ server.tool("get_post", "Get details of a specific post by ID — content, channels, media (with URLs + any per-media alt text), first comment, Instagram collaborators/user tags/location/Trial Reel state/Reel cover (thumbnail_type + thumb_offset in ms), dates and live URLs, enough to fully verify a scheduled post without opening the dashboard. When a post has per-platform caption overrides (e.g. a shorter X version alongside the default), every variant is rendered as its own labeled block under `### Content` so you can see exactly what each platform will publish. X threads are rendered under `### X Thread` with each tweet labeled in publish order — read this to see the full chained tweet text, since thread-only posts have no caption in `content`. A LinkedIn poll is rendered under `### LinkedIn Profile Poll` and/or `### LinkedIn Page Poll` (independent per channel) with the question, options, and duration. After publishing, includes `published_urls` — a map of platform → live URL for each platform that successfully posted (e.g. facebook, instagram, linkedin, x). Useful for polling: when a post's status is `published`, read `published_urls` to surface the live links.", {
278
286
  id: z.string().describe("The post ID"),
279
287
  }, async ({ id }) => {
280
288
  const result = await getClient().getPost(id);
@@ -407,11 +415,15 @@ export function registerPostTools(server, getClient) {
407
415
  md += `\n`;
408
416
  }
409
417
  }
410
- // LinkedIn poll: mutually exclusive with media/link-share, so surface
411
- // it prominently rather than leaving callers to infer it's a poll.
412
- if (p.linkedin_poll?.question) {
413
- const poll = p.linkedin_poll;
414
- md += `\n\n### LinkedIn Poll\n\n`;
418
+ // LinkedIn poll(s): independent per channel, mutually exclusive with
419
+ // media/link-share on that channel, so surface each prominently
420
+ // rather than leaving callers to infer it's a poll.
421
+ for (const channel of ["linkedin", "linkedin_page"]) {
422
+ const poll = p.linkedin_poll?.[channel];
423
+ if (!poll?.question)
424
+ continue;
425
+ const label = channel === "linkedin_page" ? "LinkedIn Page" : "LinkedIn Profile";
426
+ md += `\n\n### ${label} Poll\n\n`;
415
427
  md += `**${poll.question}**\n\n`;
416
428
  for (const option of poll.options || []) {
417
429
  md += `- ${option}\n`;
package/build/types.d.ts CHANGED
@@ -28,7 +28,7 @@ export interface InboxParticipant {
28
28
  }
29
29
  export interface InboxConversation {
30
30
  conversation_id: string;
31
- platform: "instagram" | "facebook" | "linkedin";
31
+ platform: "instagram" | "facebook" | "linkedin" | "tiktok" | "x";
32
32
  type: "dm" | "comment" | "mention";
33
33
  participant: InboxParticipant;
34
34
  unread_count: number;
@@ -152,11 +152,18 @@ export interface ApiPost {
152
152
  facebook?: ApiPlatformOptionsOut;
153
153
  linkedin?: ApiPlatformOptionsOut;
154
154
  linkedin_page?: ApiPlatformOptionsOut;
155
- /** Non-sponsored LinkedIn poll echoed back after create/update/publish. */
155
+ /** Non-sponsored LinkedIn poll(s), independent per channel — echoed back after create/update/publish. */
156
156
  linkedin_poll?: {
157
- question: string;
158
- options: string[];
159
- duration: string;
157
+ linkedin?: {
158
+ question: string;
159
+ options: string[];
160
+ duration: string;
161
+ } | null;
162
+ linkedin_page?: {
163
+ question: string;
164
+ options: string[];
165
+ duration: string;
166
+ } | null;
160
167
  } | null;
161
168
  tiktok?: ApiPlatformOptionsOut;
162
169
  google_business?: ApiPlatformOptionsOut;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnisocials/mcp-server",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "MCP server for OmniSocials API - manage social media posts, media, accounts, analytics, and webhooks",
5
5
  "type": "module",
6
6
  "main": "build/index.js",