@omnisocials/mcp-server 1.32.0 → 1.33.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 +2 -1
- package/build/client.d.ts +22 -0
- package/build/client.js +4 -0
- package/build/tools/posts.js +94 -2
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ OmniSocials accepts the following channel IDs in `create_post`, `create_and_publ
|
|
|
115
115
|
|
|
116
116
|
## Available Tools
|
|
117
117
|
|
|
118
|
-
### Posts (
|
|
118
|
+
### Posts (13 tools)
|
|
119
119
|
|
|
120
120
|
| Tool | Description |
|
|
121
121
|
|------|-------------|
|
|
@@ -127,6 +127,7 @@ OmniSocials accepts the following channel IDs in `create_post`, `create_and_publ
|
|
|
127
127
|
| `delete_post` | Delete a post |
|
|
128
128
|
| `publish_post` | Publish a draft or scheduled post now |
|
|
129
129
|
| `retry_post` | Retry the failed platforms of a failed or partially failed post |
|
|
130
|
+
| `list_approval_workflows` | List the workspace's approval workflows (steps + approvers); pass an id as `approval_workflow_id` on `create_post` to hold the post for review |
|
|
130
131
|
| `approve_post` | Approve the current step of a post's approval workflow (the connected user must be a listed approver for that step) |
|
|
131
132
|
| `reject_post` | Reject a post's approval workflow, stopping it immediately |
|
|
132
133
|
| `search_locations` | Find an Instagram or Threads location to tag (Instagram place IDs for `location_id`, Threads location ids for `threads.location_id`; Threads also searches by latitude/longitude) |
|
package/build/client.d.ts
CHANGED
|
@@ -184,6 +184,27 @@ export interface CreatePostInput {
|
|
|
184
184
|
hashtag_set?: string;
|
|
185
185
|
hashtag_placement?: "caption_append" | "first_comment";
|
|
186
186
|
hashtag_platforms?: string[];
|
|
187
|
+
/** Route the post through a saved approval workflow (GET /approval-workflows). */
|
|
188
|
+
approval_workflow_id?: string;
|
|
189
|
+
}
|
|
190
|
+
export interface ApprovalWorkflowApprover {
|
|
191
|
+
id: string;
|
|
192
|
+
name: string | null;
|
|
193
|
+
email: string | null;
|
|
194
|
+
}
|
|
195
|
+
export interface ApprovalWorkflowStep {
|
|
196
|
+
order: number;
|
|
197
|
+
name: string;
|
|
198
|
+
require_mode: "any" | "all";
|
|
199
|
+
approvers: ApprovalWorkflowApprover[];
|
|
200
|
+
}
|
|
201
|
+
export interface ApprovalWorkflow {
|
|
202
|
+
id: string;
|
|
203
|
+
name: string;
|
|
204
|
+
workspace_id: number | null;
|
|
205
|
+
steps: ApprovalWorkflowStep[];
|
|
206
|
+
created_at: string;
|
|
207
|
+
updated_at: string;
|
|
187
208
|
}
|
|
188
209
|
export declare class OmniSocialsClient {
|
|
189
210
|
private baseUrl;
|
|
@@ -298,6 +319,7 @@ export declare class OmniSocialsClient {
|
|
|
298
319
|
name: string;
|
|
299
320
|
parent_id?: string;
|
|
300
321
|
}): Promise<ApiResponse<MediaFolder>>;
|
|
322
|
+
listApprovalWorkflows(): Promise<ApiResponse<ApprovalWorkflow[]>>;
|
|
301
323
|
listHashtagSets(): Promise<ApiResponse<HashtagSet[]>>;
|
|
302
324
|
createHashtagSet(data: {
|
|
303
325
|
name: string;
|
package/build/client.js
CHANGED
|
@@ -350,6 +350,10 @@ export class OmniSocialsClient {
|
|
|
350
350
|
async createFolder(data) {
|
|
351
351
|
return this.request("POST", "/folders", data);
|
|
352
352
|
}
|
|
353
|
+
// Approval workflows (read-only; configured in the dashboard)
|
|
354
|
+
async listApprovalWorkflows() {
|
|
355
|
+
return this.request("GET", "/approval-workflows");
|
|
356
|
+
}
|
|
353
357
|
// Hashtag Sets
|
|
354
358
|
async listHashtagSets() {
|
|
355
359
|
return this.request("GET", "/hashtag-sets");
|
package/build/tools/posts.js
CHANGED
|
@@ -555,6 +555,57 @@ export function registerPostTools(server, getClient) {
|
|
|
555
555
|
if (firstCommentRows.length) {
|
|
556
556
|
md += `\n\n### First Comments\n\n${firstCommentRows.join("\n")}\n`;
|
|
557
557
|
}
|
|
558
|
+
// Per-platform publish options read-back (TikTok cover frame, privacy,
|
|
559
|
+
// disclosures; YouTube title/privacy/tags; Pinterest board/title/link),
|
|
560
|
+
// so a caller can verify what was set without opening the dashboard.
|
|
561
|
+
// Only values that were actually chosen are listed.
|
|
562
|
+
const optionRows = [];
|
|
563
|
+
const tk = p.tiktok && typeof p.tiktok === "object" ? p.tiktok : {};
|
|
564
|
+
if (typeof tk.title === "string" && tk.title)
|
|
565
|
+
optionRows.push(["TikTok title", tk.title]);
|
|
566
|
+
if (typeof tk.privacy_level === "string" && tk.privacy_level)
|
|
567
|
+
optionRows.push(["TikTok privacy", tk.privacy_level]);
|
|
568
|
+
const coverMs = Number(tk.video_cover_timestamp_ms) || 0;
|
|
569
|
+
if (coverMs > 0)
|
|
570
|
+
optionRows.push(["TikTok cover frame", `${coverMs} ms (${(coverMs / 1000).toFixed(1)}s into the video)`]);
|
|
571
|
+
for (const [key, label] of [
|
|
572
|
+
["disable_comment", "TikTok comments"],
|
|
573
|
+
["disable_duet", "TikTok duet"],
|
|
574
|
+
["disable_stitch", "TikTok stitch"],
|
|
575
|
+
]) {
|
|
576
|
+
if (tk[key] === true)
|
|
577
|
+
optionRows.push([label, "disabled"]);
|
|
578
|
+
}
|
|
579
|
+
for (const [key, label] of [
|
|
580
|
+
["is_aigc", "TikTok AI-generated label"],
|
|
581
|
+
["brand_content_toggle", "TikTok paid partnership"],
|
|
582
|
+
["brand_organic_toggle", "TikTok own-brand disclosure"],
|
|
583
|
+
["auto_add_music", "TikTok auto-add music"],
|
|
584
|
+
]) {
|
|
585
|
+
if (tk[key] === true)
|
|
586
|
+
optionRows.push([label, "on"]);
|
|
587
|
+
}
|
|
588
|
+
const yt = p.youtube && typeof p.youtube === "object" ? p.youtube : {};
|
|
589
|
+
if (typeof yt.title === "string" && yt.title)
|
|
590
|
+
optionRows.push(["YouTube title", yt.title]);
|
|
591
|
+
if (typeof yt.privacy_status === "string" && yt.privacy_status)
|
|
592
|
+
optionRows.push(["YouTube privacy", yt.privacy_status]);
|
|
593
|
+
if (Array.isArray(yt.tags) && yt.tags.length)
|
|
594
|
+
optionRows.push(["YouTube tags", yt.tags.join(", ")]);
|
|
595
|
+
if (yt.made_for_kids === true)
|
|
596
|
+
optionRows.push(["YouTube made for kids", "yes"]);
|
|
597
|
+
const pin = p.pinterest && typeof p.pinterest === "object" ? p.pinterest : {};
|
|
598
|
+
if (typeof pin.board_id === "string" && pin.board_id)
|
|
599
|
+
optionRows.push(["Pinterest board", pin.board_id]);
|
|
600
|
+
if (typeof pin.title === "string" && pin.title)
|
|
601
|
+
optionRows.push(["Pinterest title", pin.title]);
|
|
602
|
+
if (typeof pin.link === "string" && pin.link)
|
|
603
|
+
optionRows.push(["Pinterest link", pin.link]);
|
|
604
|
+
if (optionRows.length) {
|
|
605
|
+
md += `\n\n### Platform Options\n\n| Option | Value |\n|--------|-------|\n`;
|
|
606
|
+
for (const [k, v] of optionRows)
|
|
607
|
+
md += `| ${k} | ${v.replace(/\|/g, "\\|")} |\n`;
|
|
608
|
+
}
|
|
558
609
|
// LinkedIn multi-image style: nested under each channel's options
|
|
559
610
|
// object by the API. Only rendered when the post opts out of the
|
|
560
611
|
// default PDF document carousel.
|
|
@@ -709,6 +760,7 @@ Do NOT call this tool without media when creating stories, reels, Instagram post
|
|
|
709
760
|
thread_parts: z.array(THREADS_THREAD_PART).min(2).max(25).optional().describe("Publish as a chained Threads (Meta) thread instead of a single post. Provide 2 to 25 parts; parts after the first are posted as replies to the previous part. Each part holds up to 500 characters and up to 10 media (images and videos can be mixed) via media_ids (from upload_media) or media_urls. When set, the Threads caption is taken from part 1 and content is ignored for Threads. For a single post, omit thread_parts and use content."),
|
|
710
761
|
}).optional().describe("Threads (Meta) options: thread mode via `thread_parts`, location tag via `location_id`."),
|
|
711
762
|
google_business: GOOGLE_BUSINESS_OPTIONS,
|
|
763
|
+
approval_workflow_id: z.string().optional().describe("Route the post through a saved approval workflow (id from list_approval_workflows). The post is created as `in_approval` instead of `scheduled`: its approvers are notified, they review it on the Approvals page, and it publishes at scheduled_at only once the workflow's last step approves it (or approve_post). Use this whenever the user wants to review/approve agent-created posts before they go out. Requires scheduled_at; not allowed with publish_now (use create_post, never create_and_publish_post). A workflow with no approvers on its first step is rejected with validation_error."),
|
|
712
764
|
}, { title: "Create Post", readOnlyHint: false, destructiveHint: false, openWorldHint: false }, async (params) => {
|
|
713
765
|
const result = await getClient().createPost({ ...params, source: "mcp" });
|
|
714
766
|
if (result.error) {
|
|
@@ -731,18 +783,26 @@ Do NOT call this tool without media when creating stories, reels, Instagram post
|
|
|
731
783
|
try {
|
|
732
784
|
const schedAt = p.schedule_at ?? p.scheduled_at;
|
|
733
785
|
const channels = selectedChannels(p);
|
|
734
|
-
|
|
786
|
+
const inApproval = p.status === "in_approval";
|
|
787
|
+
let md = inApproval
|
|
788
|
+
? `## Post Submitted for Approval\n\n`
|
|
789
|
+
: `## Draft Created (not yet published)\n\n`;
|
|
735
790
|
md += `| Field | Value |\n`;
|
|
736
791
|
md += `|-------|-------|\n`;
|
|
737
792
|
md += `| **ID** | \`${p.id}\` |\n`;
|
|
738
793
|
md += `| **Status** | ${capitalize(p.status || "draft")} |\n`;
|
|
794
|
+
if (inApproval)
|
|
795
|
+
md += `| **Approval status** | ${capitalize(p.approval_status || "pending")} |\n`;
|
|
739
796
|
if (schedAt)
|
|
740
|
-
md += `|
|
|
797
|
+
md += `| **${inApproval ? "Publishes (once approved)" : "Scheduled"}** | ${formatDateTime(schedAt)} |\n`;
|
|
741
798
|
if (channels.length)
|
|
742
799
|
md += `| **Channels** | ${channels.join(", ")} |\n`;
|
|
743
800
|
if (appUrl)
|
|
744
801
|
md += `| **Open in OmniSocials** | ${appUrl} |\n`;
|
|
745
802
|
md += `\n**Content:** ${truncate(p.content, 100)}\n\n`;
|
|
803
|
+
if (inApproval) {
|
|
804
|
+
md += `_The approvers have been notified. The post publishes at the time above as soon as the workflow's last step approves it (Approvals page in OmniSocials, or approve_post). Nothing else is needed from the user until then._\n\n`;
|
|
805
|
+
}
|
|
746
806
|
if (Array.isArray(result.warnings) && result.warnings.length > 0) {
|
|
747
807
|
for (const w of result.warnings) {
|
|
748
808
|
md += `⚠️ ${w.message}\n`;
|
|
@@ -1063,6 +1123,38 @@ The retry runs asynchronously (usually within a few minutes). Poll \`get_post\`
|
|
|
1063
1123
|
content: [{ type: "text", text: md }],
|
|
1064
1124
|
};
|
|
1065
1125
|
});
|
|
1126
|
+
server.tool("list_approval_workflows", `List the approval workflows this workspace can use (company-wide ones plus workspace-bound ones), with their steps and named approvers. Workflows are created in the OmniSocials dashboard (Approvals); this tool only lists them.
|
|
1127
|
+
|
|
1128
|
+
Pass a workflow's id as \`approval_workflow_id\` on \`create_post\` to hold a scheduled post for review: it is created as \`in_approval\`, the approvers are notified, and it publishes at \`scheduled_at\` once the last step approves. Call this first whenever the user wants agent-created posts to be reviewed/approved before they go out; if the list is empty, tell the user to create a workflow under Approvals in the dashboard.`, {}, { title: "List Approval Workflows", readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async () => {
|
|
1129
|
+
const result = await getClient().listApprovalWorkflows();
|
|
1130
|
+
if (result.error) {
|
|
1131
|
+
return {
|
|
1132
|
+
content: [{ type: "text", text: `Error (${result.error.code}): ${result.error.message}` }],
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
const items = Array.isArray(result.data) ? result.data : [];
|
|
1136
|
+
if (!items.length) {
|
|
1137
|
+
return {
|
|
1138
|
+
content: [{
|
|
1139
|
+
type: "text",
|
|
1140
|
+
text: "No approval workflows yet. The user can create one in the OmniSocials dashboard under Approvals (choose the approvers per step); then create_post with approval_workflow_id routes posts through it.",
|
|
1141
|
+
}],
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
let md = `## Approval Workflows (${items.length})\n\n`;
|
|
1145
|
+
for (const w of items) {
|
|
1146
|
+
md += `### \`${w.id}\` ${w.name}${w.workspace_id ? "" : " _(company-wide)_"}\n\n`;
|
|
1147
|
+
for (const s of w.steps || []) {
|
|
1148
|
+
const approvers = (s.approvers || []).map((a) => a.name || a.email || a.id).join(", ") || "*(no approvers)*";
|
|
1149
|
+
md += `- Step ${s.order}: ${s.name || "Review"} — ${s.require_mode === "all" ? "all must approve" : "any one approves"}: ${approvers}\n`;
|
|
1150
|
+
}
|
|
1151
|
+
md += `\n`;
|
|
1152
|
+
}
|
|
1153
|
+
md += `Use create_post with approval_workflow_id="<id>" (plus scheduled_at) to submit a post for review.`;
|
|
1154
|
+
return {
|
|
1155
|
+
content: [{ type: "text", text: md }],
|
|
1156
|
+
};
|
|
1157
|
+
});
|
|
1066
1158
|
server.tool("approve_post", `Approve the current step of a post's approval workflow. Only works on a post with \`approval_status: "pending"\` (post status \`in_approval\`) — check \`get_post\` first.
|
|
1067
1159
|
|
|
1068
1160
|
IMPORTANT: this only succeeds if the connected user is a listed approver for the workflow's CURRENT step — steps approve in order, so being an approver on a later step is not enough yet. A \`forbidden\` error means the user isn't (or isn't yet) an approver for this step; tell them who needs to approve instead, don't retry.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnisocials/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"mcpName": "io.github.omnisocials/mcp-server",
|
|
5
5
|
"description": "MCP server for OmniSocials API - manage social media posts, media, accounts, analytics, and webhooks",
|
|
6
6
|
"type": "module",
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"overrides": {
|
|
55
55
|
"zod": "4.4.0",
|
|
56
|
-
"fast-uri": "3.1.
|
|
57
|
-
"hono": "4.
|
|
56
|
+
"fast-uri": "^3.1.7",
|
|
57
|
+
"hono": "^4.13.7",
|
|
58
|
+
"qs": "^6.16.0"
|
|
58
59
|
}
|
|
59
60
|
}
|