@omnisocials/mcp-server 1.3.3 → 1.3.5
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 +5 -0
- package/build/tools/posts.js +52 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,6 +191,11 @@ Full API docs: [docs.omnisocials.com](https://docs.omnisocials.com)
|
|
|
191
191
|
|
|
192
192
|
## Changelog
|
|
193
193
|
|
|
194
|
+
### 1.3.4
|
|
195
|
+
|
|
196
|
+
- **Prevent accidental publishing.** Tool descriptions now front-load the difference: `create_post` is explicitly the **draft** path, `create_and_publish_post` carries a `STOP` gate listing every phrasing ("draft", "schedule", "review first", "save it", etc.) that should make the agent fall back to `create_post`. Default is "create draft" whenever there's ambiguity.
|
|
197
|
+
- **Draft + publish responses now include an `Open in OmniSocials` link.** Drafts and scheduled posts link to the editor (`/create-post/:id`) so the user can review or edit before it goes live. Published posts link to the details view (`/posts/:id`). Applies to `create_post`, `create_and_publish_post`, `update_post`, and `get_post`.
|
|
198
|
+
|
|
194
199
|
### 1.3.2 / 1.3.3
|
|
195
200
|
|
|
196
201
|
- **Fixed:** `create_post` and `create_and_publish_post` no longer throw `str.replace is not a function` when the API returns per-platform `content` as an object. Response formatters now wrapped in a safe fallback so a formatting throw can never surface as a tool error (closes the duplicate-on-retry trap).
|
package/build/tools/posts.js
CHANGED
|
@@ -39,6 +39,19 @@ function selectedChannels(p) {
|
|
|
39
39
|
function displayDate(p) {
|
|
40
40
|
return p?.schedule_at ?? p?.scheduled_at ?? p?.published_at ?? p?.created_at ?? null;
|
|
41
41
|
}
|
|
42
|
+
// Direct link into the OmniSocials dashboard so the user can verify a draft
|
|
43
|
+
// or jump to a published post. Drafts and scheduled posts open in the editor
|
|
44
|
+
// at /create-post/:id; published posts open in the details view at /posts/:id.
|
|
45
|
+
function postAppUrl(p) {
|
|
46
|
+
const id = p?.id;
|
|
47
|
+
if (!id)
|
|
48
|
+
return null;
|
|
49
|
+
const status = String(p?.status || "").toLowerCase();
|
|
50
|
+
const isEditable = status === "draft" || status === "scheduled" || status === "" || status === "failed";
|
|
51
|
+
return isEditable
|
|
52
|
+
? `https://app.omnisocials.com/create-post/${id}`
|
|
53
|
+
: `https://app.omnisocials.com/posts/${id}`;
|
|
54
|
+
}
|
|
42
55
|
export function registerPostTools(server, getClient) {
|
|
43
56
|
server.tool("list_posts", "List all posts in the workspace. Optionally filter by status. The content column shows a preview of the default caption; if a post has per-platform overrides (e.g. a custom X version), the preview is suffixed with *(per-platform)* — call `get_post` to see every variant.", {
|
|
44
57
|
status: z.string().optional().describe("Filter by status: draft, scheduled, published, failed"),
|
|
@@ -106,6 +119,7 @@ export function registerPostTools(server, getClient) {
|
|
|
106
119
|
md += `| Field | Value |\n`;
|
|
107
120
|
md += `|-------|-------|\n`;
|
|
108
121
|
const schedAt = p.schedule_at ?? p.scheduled_at;
|
|
122
|
+
const appUrl = postAppUrl(p);
|
|
109
123
|
md += `| **ID** | \`${p.id}\` |\n`;
|
|
110
124
|
md += `| **Status** | ${capitalize(p.status || "—")} |\n`;
|
|
111
125
|
md += `| **Type** | ${p.type || "post"} |\n`;
|
|
@@ -115,6 +129,8 @@ export function registerPostTools(server, getClient) {
|
|
|
115
129
|
if (p.published_at)
|
|
116
130
|
md += `| **Published** | ${formatDateTime(p.published_at)} |\n`;
|
|
117
131
|
md += `| **Created** | ${formatDateTime(p.created_at)} |\n`;
|
|
132
|
+
if (appUrl)
|
|
133
|
+
md += `| **Open in OmniSocials** | ${appUrl} |\n`;
|
|
118
134
|
if (p.media?.length) {
|
|
119
135
|
md += `| **Media** | ${p.media.length} file(s) |\n`;
|
|
120
136
|
}
|
|
@@ -150,7 +166,11 @@ export function registerPostTools(server, getClient) {
|
|
|
150
166
|
content: [{ type: "text", text: md }],
|
|
151
167
|
};
|
|
152
168
|
});
|
|
153
|
-
server.tool("create_post", `Create a
|
|
169
|
+
server.tool("create_post", `Create a **DRAFT** post, story, or reel in OmniSocials. The post is saved to the workspace but is NOT published to any social platform until you explicitly call \`publish_post\` or the user schedules it for a future time.
|
|
170
|
+
|
|
171
|
+
USE THIS TOOL FOR: drafts, scheduled posts, "save it for later", review-before-publish, "make a draft", "write a draft post", "create a post" (when the user has NOT explicitly asked to publish/post right now), or any case where the user might want to review or tweak the post before it goes live.
|
|
172
|
+
|
|
173
|
+
DO NOT use \`create_and_publish_post\` instead unless the user has explicitly said something like "post it now", "publish immediately", "go live", "send it". When in doubt, default to this tool — drafts can always be published later via \`publish_post\` and that's reversible; publishing is not.
|
|
154
174
|
|
|
155
175
|
IMPORTANT — Before calling this tool, make sure you have all required information from the user. If anything is missing, ASK the user before calling:
|
|
156
176
|
|
|
@@ -196,14 +216,14 @@ Do NOT call this tool without media when creating stories, reels, Instagram post
|
|
|
196
216
|
link: z.string().optional(),
|
|
197
217
|
}).optional().describe("Pinterest-specific options"),
|
|
198
218
|
youtube: z.object({
|
|
199
|
-
title: z.string().optional(),
|
|
219
|
+
title: z.string().optional().describe("Short title shown on YouTube. Falls back to \"YouTube Short\" when omitted."),
|
|
200
220
|
tags: z.array(z.string()).optional(),
|
|
201
221
|
privacy_status: z.enum(["public", "private", "unlisted"]).optional(),
|
|
202
222
|
category_id: z.string().optional(),
|
|
203
223
|
made_for_kids: z.boolean().optional(),
|
|
204
224
|
notify_subscribers: z.boolean().optional(),
|
|
205
225
|
contains_synthetic_media: z.boolean().optional(),
|
|
206
|
-
}).optional().describe("YouTube Shorts options"),
|
|
226
|
+
}).optional().describe("YouTube Shorts options. Only applies when type is 'reel' and youtube is among the selected channels."),
|
|
207
227
|
instagram: z.object({
|
|
208
228
|
share_to_feed: z.boolean().optional(),
|
|
209
229
|
thumbnail_type: z.enum(["from-video", "from-library"]).optional(),
|
|
@@ -238,10 +258,11 @@ Do NOT call this tool without media when creating stories, reels, Instagram post
|
|
|
238
258
|
// The post is already created by the time this runs. If response
|
|
239
259
|
// formatting throws, we must NOT surface it as a tool error — agents
|
|
240
260
|
// retry on errors and that produces orphan duplicate posts.
|
|
261
|
+
const appUrl = postAppUrl(p);
|
|
241
262
|
try {
|
|
242
263
|
const schedAt = p.schedule_at ?? p.scheduled_at;
|
|
243
264
|
const channels = selectedChannels(p);
|
|
244
|
-
let md = `##
|
|
265
|
+
let md = `## Draft Created (not yet published)\n\n`;
|
|
245
266
|
md += `| Field | Value |\n`;
|
|
246
267
|
md += `|-------|-------|\n`;
|
|
247
268
|
md += `| **ID** | \`${p.id}\` |\n`;
|
|
@@ -250,17 +271,31 @@ Do NOT call this tool without media when creating stories, reels, Instagram post
|
|
|
250
271
|
md += `| **Scheduled** | ${formatDateTime(schedAt)} |\n`;
|
|
251
272
|
if (channels.length)
|
|
252
273
|
md += `| **Channels** | ${channels.join(", ")} |\n`;
|
|
253
|
-
|
|
274
|
+
if (appUrl)
|
|
275
|
+
md += `| **Open in OmniSocials** | ${appUrl} |\n`;
|
|
276
|
+
md += `\n**Content:** ${truncate(p.content, 100)}\n\n`;
|
|
277
|
+
md += `_Share the link above with the user so they can review or edit the draft. Publish later with \`publish_post\` once they confirm._`;
|
|
254
278
|
return { content: [{ type: "text", text: md }] };
|
|
255
279
|
}
|
|
256
280
|
catch {
|
|
257
281
|
// Fall back to the minimum the agent needs to confirm success — never error.
|
|
282
|
+
const fallback = `## Draft Created\n\n- **ID:** \`${p.id}\`\n- **Status:** ${p.status || "draft"}${appUrl ? `\n- **Open in OmniSocials:** ${appUrl}` : ""}\n\n_Share the link above with the user so they can review the draft._`;
|
|
258
283
|
return {
|
|
259
|
-
content: [{ type: "text", text:
|
|
284
|
+
content: [{ type: "text", text: fallback }],
|
|
260
285
|
};
|
|
261
286
|
}
|
|
262
287
|
});
|
|
263
|
-
server.tool("create_and_publish_post", `Create a
|
|
288
|
+
server.tool("create_and_publish_post", `Create a post AND publish it to every selected social platform IMMEDIATELY. The post goes live within seconds, is publicly visible to followers on each platform, and **cannot be unpublished** through the OmniSocials API — the user would have to delete each post on each platform manually.
|
|
289
|
+
|
|
290
|
+
**STOP — do NOT call this tool if the user said any of the following anywhere in the conversation:**
|
|
291
|
+
- "draft", "make a draft", "save as draft", "save it"
|
|
292
|
+
- "schedule", "later", "tomorrow", "next week", a specific time
|
|
293
|
+
- "review first", "let me see it", "preview", "show me"
|
|
294
|
+
- "create a post" without an explicit "now"/"publish"/"go live"
|
|
295
|
+
|
|
296
|
+
In any of those cases, use \`create_post\` instead. It saves a draft you can show to the user, edit, and publish later via \`publish_post\` — that path is reversible at every step. This path is not. When in doubt, choose \`create_post\`.
|
|
297
|
+
|
|
298
|
+
USE THIS TOOL ONLY when the user has explicitly asked to publish/post right now: "publish it", "post it now", "send it", "go live", "fire it off", etc.
|
|
264
299
|
|
|
265
300
|
IMPORTANT — Before calling this tool, make sure you have all required information. If anything is missing, ASK the user first:
|
|
266
301
|
|
|
@@ -294,10 +329,10 @@ Do NOT call without required media — it will fail.`, {
|
|
|
294
329
|
link: z.string().optional(),
|
|
295
330
|
}).optional().describe("Pinterest-specific options"),
|
|
296
331
|
youtube: z.object({
|
|
297
|
-
title: z.string().optional(),
|
|
332
|
+
title: z.string().optional().describe("Short title shown on YouTube."),
|
|
298
333
|
tags: z.array(z.string()).optional(),
|
|
299
334
|
privacy_status: z.enum(["public", "private", "unlisted"]).optional(),
|
|
300
|
-
}).optional().describe("YouTube Shorts options"),
|
|
335
|
+
}).optional().describe("YouTube Shorts options. Only applies when type is 'reel' and youtube is among the selected channels."),
|
|
301
336
|
tiktok: z.object({
|
|
302
337
|
privacy_level: z.enum(["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "FOLLOWER_OF_CREATOR", "SELF_ONLY"]).optional(),
|
|
303
338
|
}).optional().describe("TikTok options"),
|
|
@@ -318,6 +353,7 @@ Do NOT call without required media — it will fail.`, {
|
|
|
318
353
|
};
|
|
319
354
|
}
|
|
320
355
|
const p = result.data;
|
|
356
|
+
const appUrl = postAppUrl(p);
|
|
321
357
|
try {
|
|
322
358
|
const channels = selectedChannels(p);
|
|
323
359
|
let md = `## Post Created & Publishing\n\n`;
|
|
@@ -327,13 +363,16 @@ Do NOT call without required media — it will fail.`, {
|
|
|
327
363
|
md += `| **Status** | ${capitalize(p.status || "publishing")} |\n`;
|
|
328
364
|
if (channels.length)
|
|
329
365
|
md += `| **Channels** | ${channels.join(", ")} |\n`;
|
|
366
|
+
if (appUrl)
|
|
367
|
+
md += `| **Open in OmniSocials** | ${appUrl} |\n`;
|
|
330
368
|
md += `\n**Content:** ${truncate(p.content, 100)}`;
|
|
331
369
|
return { content: [{ type: "text", text: md }] };
|
|
332
370
|
}
|
|
333
371
|
catch {
|
|
334
372
|
// The post is already queued; never let a formatting throw look like an API error.
|
|
373
|
+
const fallback = `## Post Created & Publishing\n\n- **ID:** \`${p.id}\`\n- **Status:** ${p.status || "publishing"}${appUrl ? `\n- **Open in OmniSocials:** ${appUrl}` : ""}\n\n_Post was queued successfully._`;
|
|
335
374
|
return {
|
|
336
|
-
content: [{ type: "text", text:
|
|
375
|
+
content: [{ type: "text", text: fallback }],
|
|
337
376
|
};
|
|
338
377
|
}
|
|
339
378
|
});
|
|
@@ -370,6 +409,7 @@ Do NOT call without required media — it will fail.`, {
|
|
|
370
409
|
}
|
|
371
410
|
const p = result.data;
|
|
372
411
|
const schedAt = p.schedule_at ?? p.scheduled_at;
|
|
412
|
+
const appUrl = postAppUrl(p);
|
|
373
413
|
let md = `## Post Updated\n\n`;
|
|
374
414
|
md += `| Field | Value |\n`;
|
|
375
415
|
md += `|-------|-------|\n`;
|
|
@@ -377,6 +417,8 @@ Do NOT call without required media — it will fail.`, {
|
|
|
377
417
|
md += `| **Status** | ${capitalize(p.status || "—")} |\n`;
|
|
378
418
|
if (schedAt)
|
|
379
419
|
md += `| **Scheduled** | ${formatDateTime(schedAt)} |\n`;
|
|
420
|
+
if (appUrl)
|
|
421
|
+
md += `| **Open in OmniSocials** | ${appUrl} |\n`;
|
|
380
422
|
return {
|
|
381
423
|
content: [{ type: "text", text: md }],
|
|
382
424
|
};
|
package/package.json
CHANGED