@omnisocials/mcp-server 1.4.0 → 1.5.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
@@ -192,6 +192,15 @@ Full API docs: [docs.omnisocials.com](https://docs.omnisocials.com)
192
192
 
193
193
  ## Changelog
194
194
 
195
+ ### 1.5.0
196
+
197
+ - **Added:** Attach uploaded media to any tweet in an X thread. `x.thread_parts[]` now accepts `media_ids` (the same numeric Library IDs returned by `upload_media`) on any part, first tweet or reply, alongside the existing `media_urls`. Combined cap is 4 media per part. This makes graphic-led threads possible without self-hosting image URLs: for example a graphic on the first tweet and the signup link alone in a reply (which keeps the first tweet's reach). Companion server change: media attached to the parent post was previously dropped in thread mode and is now folded into the first tweet.
198
+ - **Changed:** `upload_media` now shows the uploaded file's public URL in its output, so it can be reused anywhere `media_urls` is accepted.
199
+
200
+ ### 1.4.1
201
+
202
+ - **Fixed:** Long-form X posts from Premium / Premium+ accounts are no longer wrongly capped at 280 characters. Companion server fix — the API validated X text without checking the account's subscription tier, so single posts over 280 chars were rejected even on Premium. Existing clients benefit automatically once the backend deploys; 1.4.1 only refreshes the tool guidance so agents put Premium long-form (up to 25,000 chars) in `content` instead of force-splitting it into a thread. Note: X *threads* still cap each part at 280 chars regardless of tier.
203
+
195
204
  ### 1.3.8
196
205
 
197
206
  - **Fixed:** Pinterest pins created via `create_post` / `create_and_publish_post` / `update_post` now publish with title, link, video cover, and alt text. Companion server fix — the API was storing those fields under unprefixed keys while the publisher read them under `pinterest_*` prefixes, so pins published with no metadata even when the agent passed it. Existing clients on 1.3.7 benefit automatically once the backend deploys.
package/build/client.d.ts CHANGED
@@ -12,7 +12,9 @@ export declare function capitalize(str: string): string;
12
12
  export interface XThreadPartInput {
13
13
  /** Tweet text — ≤ 280 chars (the API enforces 280 even for X Premium). */
14
14
  text: string;
15
- /** Optional per-part media URLs (max 4). Overrides top-level media_urls.x. */
15
+ /** Optional per-part media as Library IDs from upload_media (max 4 combined with media_urls). */
16
+ media_ids?: string[];
17
+ /** Optional per-part media as external URLs (max 4 combined with media_ids). */
16
18
  media_urls?: string[];
17
19
  }
18
20
  export interface XPostOptions {
@@ -86,7 +86,9 @@ When the user provides an image in the conversation (not a URL), use base64_data
86
86
  md += `| **Type** | ${m.type || "—"} |\n`;
87
87
  md += `| **Filename** | ${m.filename || "—"} |\n`;
88
88
  md += `| **Size** | ${formatBytes(m.size || 0)} |\n`;
89
- md += `\nUse this Media ID with \`media_ids\` when creating posts.`;
89
+ if (m.url)
90
+ md += `| **URL** | ${m.url} |\n`;
91
+ md += `\nUse this Media ID with \`media_ids\` when creating posts (including inside \`x.thread_parts[].media_ids\`). The public URL above also works anywhere \`media_urls\` is accepted.`;
90
92
  return {
91
93
  content: [{ type: "text", text: md }],
92
94
  };
@@ -206,7 +206,7 @@ IMPORTANT — Before calling this tool, make sure you have all required informat
206
206
  If the user named a board ("post to my Marketing board") or specified one in the request, use that one instead of the first — match on name (case-insensitive) against the boards list.
207
207
  - YouTube: Title, privacy status, tags?
208
208
  - TikTok: Privacy level?
209
- - **X threads**: When the user asks for an X/Twitter "thread" (or anything that exceeds 280 chars on X), DO NOT cram it into \`content\` with "1/", "2/" prefixes. Instead pass \`x.thread_parts\` as an array of 2–25 \`{ text }\` objects (each ≤ 280 chars). The \`content\` field is then ignored for X. For a single tweet, omit \`thread_parts\` and use \`content\` normally.
209
+ - **X threads vs long-form**: A chained "thread" (the user explicitly asks for one) pass \`x.thread_parts\` as an array of 2–25 \`{ text }\` objects (each ≤ 280 chars); the \`content\` field is then ignored for X. A single **long-form** post on a Premium / Premium+ account → just put the full text (up to 25,000 chars) in \`content\` no threading needed (check \`platform_details.subscription_type\` via list_accounts). On free / Basic, X caps a single post at 280 chars, so either split into a thread or shorten. Never cram "1/", "2/" prefixes into \`content\` — that posts one tweet, not a thread.
210
210
 
211
211
  Do NOT call this tool without media when creating stories, reels, Instagram posts, TikTok posts, or Pinterest posts — it will fail.
212
212
 
@@ -264,8 +264,9 @@ Do NOT call this tool without media when creating stories, reels, Instagram post
264
264
  made_with_ai: z.boolean().optional().describe("Mark as AI-generated content"),
265
265
  thread_parts: z.array(z.object({
266
266
  text: z.string().describe("Tweet text (≤ 280 chars)"),
267
- media_urls: z.array(z.string()).max(4).optional().describe("Per-tweet media URLs (max 4)"),
268
- })).min(2).max(25).optional().describe("Publish as a chained X thread instead of a single tweet. Provide 2–25 parts; each is posted in order via in_reply_to_tweet_id. Per-part media_urls override top-level media for that specific tweet. For a single tweet, omit thread_parts and use content."),
267
+ media_ids: z.array(z.string()).max(4).optional().describe("Per-tweet media as Library IDs from upload_media (max 4 combined with media_urls). Attach your uploaded graphics to any tweet in the thread."),
268
+ media_urls: z.array(z.string()).max(4).optional().describe("Per-tweet media as external URLs (max 4 combined with media_ids)"),
269
+ })).min(2).max(25).optional().describe("Publish as a chained X thread instead of a single tweet. Provide 2–25 parts; each is posted in order via in_reply_to_tweet_id. Attach media to any part (first tweet or reply) via media_ids (from upload_media) or media_urls — max 4 per part. For a single tweet, omit thread_parts and use content."),
269
270
  }).optional().describe("X (Twitter) options"),
270
271
  google_business: z.object({
271
272
  topic_type: z.enum(["STANDARD", "EVENT", "OFFER"]).optional().describe("Local post type. Defaults to STANDARD. ALERT is reserved by Google and not exposed."),
@@ -352,7 +353,7 @@ IMPORTANT — Before calling this tool, make sure you have all required informat
352
353
  - **Max items per platform** (same caps as create_post — exceeding returns 400): Bluesky/X/Mastodon ≤4, Instagram/Threads ≤10, TikTok ≤35 photos with no photo/video mix, Pinterest carousel 2–5 same-ratio images.
353
354
  - **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.
354
355
  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.
355
- 5. **X threads**: When the user asks for an X/Twitter "thread" (or anything > 280 chars on X), pass \`x.thread_parts\` as a 2–25 entry array of \`{ text }\` objects. Do NOT split into "1/", "2/" inside \`content\` — that produces a single tweet, not a thread.
356
+ 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.
356
357
 
357
358
  Do NOT call without required media — it will fail.`, {
358
359
  content: z.union([z.string(), z.record(z.string(), z.string())]).describe("Post caption. String for same text on all channels, or object with platform keys for per-channel captions: { \"default\": \"fallback\", \"linkedin\": \"long version\", \"threads\": \"short version\" }. The \"default\" key is used for any selected channel without its own key."),
@@ -387,8 +388,9 @@ Do NOT call without required media — it will fail.`, {
387
388
  made_with_ai: z.boolean().optional().describe("Mark as AI-generated content"),
388
389
  thread_parts: z.array(z.object({
389
390
  text: z.string().describe("Tweet text (≤ 280 chars)"),
390
- media_urls: z.array(z.string()).max(4).optional().describe("Per-tweet media URLs (max 4)"),
391
- })).min(2).max(25).optional().describe("Publish as a chained X thread (2–25 parts). Each is posted in order via in_reply_to_tweet_id. Per-part media_urls override top-level media."),
391
+ media_ids: z.array(z.string()).max(4).optional().describe("Per-tweet media as Library IDs from upload_media (max 4 combined with media_urls). Attach your uploaded graphics to any tweet in the thread."),
392
+ media_urls: z.array(z.string()).max(4).optional().describe("Per-tweet media as external URLs (max 4 combined with media_ids)"),
393
+ })).min(2).max(25).optional().describe("Publish as a chained X thread (2–25 parts). Each is posted in order via in_reply_to_tweet_id. Attach media to any part via media_ids (from upload_media) or media_urls — max 4 per part."),
392
394
  }).optional().describe("X (Twitter) options"),
393
395
  google_business: z.object({
394
396
  topic_type: z.enum(["STANDARD", "EVENT", "OFFER"]).optional(),
@@ -496,8 +498,9 @@ Do NOT call without required media — it will fail.`, {
496
498
  made_with_ai: z.boolean().optional(),
497
499
  thread_parts: z.array(z.object({
498
500
  text: z.string().describe("Tweet text (≤ 280 chars)"),
499
- media_urls: z.array(z.string()).max(4).optional().describe("Per-tweet media URLs (max 4)"),
500
- })).min(2).max(25).nullable().optional().describe("Replace the X thread shape on this post. Pass an array (2–25 parts) to update/create the thread, or `null` to revert to single-tweet mode."),
501
+ media_ids: z.array(z.string()).max(4).optional().describe("Per-tweet media as Library IDs from upload_media (max 4 combined with media_urls). Attach your uploaded graphics to any tweet in the thread."),
502
+ media_urls: z.array(z.string()).max(4).optional().describe("Per-tweet media as external URLs (max 4 combined with media_ids)"),
503
+ })).min(2).max(25).nullable().optional().describe("Replace the X thread shape on this post. Pass an array (2–25 parts) to update/create the thread (attach media to any part via media_ids or media_urls, max 4 per part), or `null` to revert to single-tweet mode."),
501
504
  }).optional().describe("X (Twitter) options"),
502
505
  google_business: z.object({
503
506
  topic_type: z.enum(["STANDARD", "EVENT", "OFFER"]).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnisocials/mcp-server",
3
- "version": "1.4.0",
3
+ "version": "1.5.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",