@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 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 (12 tools)
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");
@@ -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
- let md = `## Draft Created (not yet published)\n\n`;
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 += `| **Scheduled** | ${formatDateTime(schedAt)} |\n`;
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.32.0",
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.1",
57
- "hono": "4.12.18"
56
+ "fast-uri": "^3.1.7",
57
+ "hono": "^4.13.7",
58
+ "qs": "^6.16.0"
58
59
  }
59
60
  }