@omnisocials/mcp-server 1.21.0 → 1.23.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 +1 -1
- package/build/client.d.ts +6 -1
- package/build/tools/inbox.js +7 -4
- package/build/tools/posts.js +27 -14
- package/build/types.d.ts +12 -5
- package/package.json +16 -3
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,
|
|
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
|
|
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).
|
package/build/tools/inbox.js
CHANGED
|
@@ -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
|
|
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,
|
package/build/tools/posts.js
CHANGED
|
@@ -83,8 +83,7 @@ const LINKEDIN_PAGE_OPTIONS = z
|
|
|
83
83
|
})
|
|
84
84
|
.optional()
|
|
85
85
|
.describe("LinkedIn Company Page options");
|
|
86
|
-
const
|
|
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
|
|
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,
|
|
@@ -133,7 +141,7 @@ const USER_TAG_ENTRY = z.object({
|
|
|
133
141
|
// z.array(...).min(2).max(25)[.nullable()].optional().describe(...) stays
|
|
134
142
|
// per-tool (update_post allows null to clear the thread).
|
|
135
143
|
const X_THREAD_PART = z.object({
|
|
136
|
-
text: z.string().describe("Tweet text (≤ 280 chars). X counts every link as 23 characters (its t.co length)
|
|
144
|
+
text: z.string().describe("Tweet text (≤ 280 chars by X's weighted count). X counts every link as 23 characters (its t.co length) and every emoji or non-Latin symbol (🚀, …, ₹, CJK) as 2 — so text that looks under 280 can still be over. When trimming generated copy to fit, leave headroom instead of cutting to exactly 280 raw characters."),
|
|
137
145
|
media_ids: z.array(mediaIdEntry).max(4).optional().describe("Per-tweet media as Library IDs from upload_media (max 4 combined with media_urls). Each entry is a plain ID string or { id, alt } to attach alt text (X applies it to photos/GIFs). Attach your uploaded graphics to any tweet in the thread."),
|
|
138
146
|
media_urls: z.array(mediaUrlEntry).max(4).optional().describe("Per-tweet media as external URLs (max 4 combined with media_ids). Each entry is a plain URL string or { url, alt } to attach alt text (X applies it to photos/GIFs)."),
|
|
139
147
|
});
|
|
@@ -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
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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`;
|
|
@@ -511,10 +523,11 @@ IMPORTANT — Before calling this tool, make sure you have all required informat
|
|
|
511
523
|
- Other platforms (LinkedIn, LinkedIn Page, X, Bluesky, etc.): Media is optional.
|
|
512
524
|
|
|
513
525
|
**Per-platform max media items (enforced at submit, returns 400 validation_error if exceeded):**
|
|
514
|
-
- Bluesky, X, Mastodon — max **4** items per post
|
|
515
|
-
- Instagram, Threads — max **10** items per carousel
|
|
526
|
+
- Bluesky, X, Mastodon — max **4** items per post; cannot mix images and video
|
|
527
|
+
- Instagram, Threads — max **10** items per carousel; images and videos CAN be mixed in one carousel
|
|
516
528
|
- TikTok — max **35** photos per photo-post; cannot mix photos and videos in one post
|
|
517
|
-
- Pinterest carousel — 2–5 images, all same aspect ratio
|
|
529
|
+
- Pinterest carousel — 2–5 images, all same aspect ratio (a video splits off into its own video pin)
|
|
530
|
+
- Facebook, LinkedIn, LinkedIn Page — cannot mix images and video in one post (images only, or a single video)
|
|
518
531
|
|
|
519
532
|
**Per-platform video caps (duration + size, checked via ffprobe at submit — exceeding either returns a 400 validation_error stating the exact cap and the file's value, e.g. "X only allows videos up to 2min 20s — yours is 2min 35s. Trim the video or deselect X."):**
|
|
520
533
|
- X — **140s (2min 20s)** · **512MB** (X also can't mix a video with images in one tweet: a tweet is either 1 video OR up to 4 images)
|
|
@@ -674,7 +687,7 @@ IMPORTANT — Before calling this tool, make sure you have all required informat
|
|
|
674
687
|
- Instagram/TikTok posts: ALWAYS need at least one image or video.
|
|
675
688
|
- Pinterest: ALWAYS need an image + board_id.
|
|
676
689
|
- Ask the user which media to use. Use list_media to show options.
|
|
677
|
-
- **Max items per platform** (same caps as create_post — exceeding returns 400): Bluesky/X/Mastodon ≤4, Instagram/Threads ≤10, TikTok ≤35 photos
|
|
690
|
+
- **Max items per platform** (same caps as create_post — exceeding returns 400): Bluesky/X/Mastodon ≤4, Instagram/Threads ≤10, TikTok ≤35 photos, Pinterest carousel 2–5 same-ratio images. Mixing images+video is rejected on Facebook/LinkedIn/X/Bluesky/Mastodon/TikTok; Instagram and Threads mixed carousels are allowed.
|
|
678
691
|
- **Video duration/size caps** (ffprobe at submit — over either returns 400 validation_error with the exact cap + the file's value): X **140s/512MB** (no video+image mix in one tweet), Bluesky 180s, Threads 5min, Instagram 15min, TikTok 10min, YouTube Short 3min, LinkedIn 10min, Facebook Reel 90s.
|
|
679
692
|
4. **Pinterest board (auto-default to first board)**: If Pinterest is in \`channels\` and \`pinterest.board_id\` is NOT provided, do NOT block on asking — and do NOT skip Pinterest. Call \`get_account\` on the Pinterest account, take the FIRST board from the returned boards list, and pass its \`id\` as \`pinterest.board_id\`. In your reply, mention which board you used (e.g. "Published to your 'Marketing' board on Pinterest — let me know if you'd prefer a different one.") so the user can redirect. If the user named a specific board in the request, match it (case-insensitive) against the list and use that one instead.
|
|
680
693
|
5. **X threads vs long-form**: A chained "thread" → pass \`x.thread_parts\` as a 2–25 entry array of \`{ text }\` objects (each ≤ 280 chars). A single long-form post on a Premium / Premium+ account → put the full text (up to 25,000 chars) in \`content\`; no threading needed. On free / Basic, X caps a single post at 280 chars. Do NOT split into "1/", "2/" inside \`content\` — that produces a single tweet, not a thread.
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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.
|
|
3
|
+
"version": "1.23.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",
|
|
@@ -19,11 +19,24 @@
|
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"mcp",
|
|
22
|
+
"mcp-server",
|
|
23
|
+
"model-context-protocol",
|
|
22
24
|
"omnisocials",
|
|
23
25
|
"social-media",
|
|
26
|
+
"social-media-scheduling",
|
|
27
|
+
"scheduling",
|
|
24
28
|
"claude",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
29
|
+
"ai",
|
|
30
|
+
"instagram",
|
|
31
|
+
"facebook",
|
|
32
|
+
"linkedin",
|
|
33
|
+
"tiktok",
|
|
34
|
+
"youtube",
|
|
35
|
+
"twitter",
|
|
36
|
+
"pinterest",
|
|
37
|
+
"bluesky",
|
|
38
|
+
"threads",
|
|
39
|
+
"mastodon"
|
|
27
40
|
],
|
|
28
41
|
"homepage": "https://docs.omnisocials.com",
|
|
29
42
|
"engines": {
|