@omnisocials/mcp-server 1.33.0 → 1.34.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 +21 -6
- package/build/client.d.ts +11 -1
- package/build/client.js +19 -2
- package/build/index.js +1 -1
- package/build/tools/inbox.js +196 -87
- package/build/tools/media.js +2 -2
- package/build/tools/posts.js +5 -5
- package/build/types.d.ts +34 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,15 +189,18 @@ Apply a set when creating a post: `create_post` / `create_and_publish_post` acce
|
|
|
189
189
|
| `delete_webhook` | Delete a webhook |
|
|
190
190
|
| `rotate_webhook_secret` | Rotate a webhook's signing secret |
|
|
191
191
|
|
|
192
|
-
### Social Inbox (
|
|
192
|
+
### Social Inbox (8 tools)
|
|
193
193
|
|
|
194
194
|
| Tool | Description |
|
|
195
195
|
|------|-------------|
|
|
196
|
-
| `list_inbox_conversations` | List conversations
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
196
|
+
| `list_inbox_conversations` | List conversations: Instagram/Facebook DMs, comments, mentions, LinkedIn company-page comments/mentions, TikTok video comments, YouTube video comments, X DMs (opt-in), and Threads replies on your posts plus mentions; filter by platform, type, unread, or `unanswered` |
|
|
197
|
+
| `get_next_unanswered` | The next item that still needs an answer (longest-waiting first) with the whole thread and the post it is on; filter by platform or type, `include_read`, `exclude` |
|
|
198
|
+
| `get_inbox_conversation` | Get a conversation's full message history with the post it belongs to; every message shows its `#id` |
|
|
199
|
+
| `mark_inbox_read` | Mark all incoming messages in a conversation as read (also the durable skip for `get_next_unanswered`) |
|
|
200
|
+
| `reply_to_inbox` | Reply to a DM, comment, or mention (existing conversations only); `include_next` returns the next unanswered item in the same call |
|
|
201
|
+
| `hide_inbox_comment` | Hide or unhide a comment on one of your posts (Facebook, Instagram, TikTok, YouTube, Threads) |
|
|
202
|
+
| `delete_inbox_comment` | Permanently delete a comment on one of your posts, with the replies under it (Facebook, Instagram, TikTok) |
|
|
203
|
+
| `hide_inbox_reply` | Older name for `hide_inbox_comment`, kept for compatibility |
|
|
201
204
|
|
|
202
205
|
Requires an API key with the opt-in `inbox:read` / `inbox:write` scopes — enable "Social Inbox access" when you create the key.
|
|
203
206
|
|
|
@@ -224,6 +227,18 @@ Full API docs: [docs.omnisocials.com](https://docs.omnisocials.com)
|
|
|
224
227
|
|
|
225
228
|
## Changelog
|
|
226
229
|
|
|
230
|
+
### 1.34.0 (2026-09-06)
|
|
231
|
+
|
|
232
|
+
- **Added: work through the inbox one item at a time.** New tool `get_next_unanswered` returns the next inbox item that still needs an answer, with the whole conversation and the post it is on, in one call: the customer's latest DM with no reply after it (Instagram/Facebook DMs within the 24-hour reply window), or a comment/mention nobody replied to and that is not hidden. Longest-waiting first; filter with `platform` and `type`; only unread items by default (`mark_inbox_read` is the durable skip, `include_read: true` widens it, `exclude` skips up to 100 conversation ids for one call); looks back 30 days and reports `remaining`. `reply_to_inbox` accepts `include_next: true` and returns the next item in the same response. `list_inbox_conversations` gains an `unanswered` filter. Replies typed in the native apps now appear as outgoing messages and count as answers. Same on the companion server (`mcp.omnisocials.com`).
|
|
233
|
+
- **Added: hide and delete comments on every comment platform.** New tool `hide_inbox_comment` hides (or unhides with `hide: false`) a comment on one of your posts on Facebook, Instagram, TikTok, YouTube and Threads (on YouTube, hide sets the comment to `rejected` and unhide publishes it again); `hide_inbox_reply` stays as an alias. New tool `delete_inbox_comment` permanently deletes a comment on Facebook, Instagram and TikTok, on the platform and from the inbox, with the replies under it (YouTube cannot delete other people's comments: hide instead). Both take the message id (`#id` in the thread). Errors: `403 reconnect_required`, `429 quota_exceeded` (YouTube hide), `502 platform_error`, `404 account_not_connected`, `401 reauth_required` (TikTok/Threads). `hidden` is now a boolean for every comment platform, `null` for DMs. Same on the companion server.
|
|
234
|
+
- **Added: post context in inbox threads.** The `post` object on conversations and messages carries `url` and `media_type` next to `id`, `caption` and `thumbnail`. `get_inbox_conversation` and `get_next_unanswered` render the post above the thread, and every message shows its `#id` plus `hidden` and reply-to-a-comment markers. Same on the companion server.
|
|
235
|
+
|
|
236
|
+
### 1.33.0 (2026-09-06)
|
|
237
|
+
|
|
238
|
+
- **Added: submit posts for approval.** New tool `list_approval_workflows` lists the workspace's approval workflows (id, steps, named approvers; workflows are created in the dashboard under Approvals). `create_post` accepts `approval_workflow_id`: the post is created as `in_approval` instead of `scheduled`, the approvers are notified (dashboard badge, email, Slack), and it publishes at `scheduled_at` once the workflow's last step approves it (Approvals page, or `approve_post`). This is the flow for "the agent prepares everything, a human only approves". Requires `scheduled_at`; not allowed with `publish_now`. Errors: `404 workflow_not_found`, `400 validation_error` (no `scheduled_at`, or no approvers on the first step). Same on the companion server (`mcp.omnisocials.com`).
|
|
239
|
+
- **Added: platform options in `get_post`.** A new "Platform Options" section renders the TikTok options that were set (title, privacy level, cover frame `video_cover_timestamp_ms`, comment/duet/stitch toggles, AI-generated and brand disclosures, auto-add music), the YouTube options (title, privacy, tags, made for kids) and the Pinterest options (board, title, link). Previously only `first_comment` was rendered, so a pinned TikTok cover could not be verified without opening the post. Same on the companion server.
|
|
240
|
+
- **Changed: inbox attachment types.** Inbox `attachment.type` now reflects the fetched bytes rather than the platform's label (a generic photo DM is `image`, an MP4 story mention is `video`; `file` only when we could not classify the media), and a new `link` type carries the platform's own URL when it did not point at media (for example a shared post). `reply_to_inbox` now returns clear errors instead of `platform_error` for Instagram DM text over 1,000 characters (`validation_error`), replies outside the 24-hour messaging window (`outside_messaging_window`), and replies to a comment or message that no longer exists on the platform (`not_found`).
|
|
241
|
+
|
|
227
242
|
### 1.32.0 (2026-09-02)
|
|
228
243
|
|
|
229
244
|
- **Added: TikTok Photo Mode title.** The `tiktok` option object on `create_post`, `create_and_publish_post`, and `update_post` accepts `title` (max 90 characters): the title TikTok shows above the caption on photo carousels. TikTok's photo endpoint takes the title and the caption as two separate fields, and until now only the caption (`content`) could be set here, so photo posts created through the MCP always published with an empty title. Video posts have a single caption field and ignore `title`. `get_post` returns it as `tiktok.title`. Same on the companion server (`mcp.omnisocials.com`).
|
package/build/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Account, AnalyticsOverview, AccountAnalyticsEntry, ApiPost, ApiResponse, AudioSearchResult, BestTimesResult, HashtagSet, LocationSearchResult, MediaCheckResult, MediaCompatibility, MediaFolder, MediaItem, PdfUploadResult, PostAnalytics, PublishPostResult, RecentPlatformPost, RetryPostResult, ApprovePostResult, RejectPostResult, InboxConversation, InboxMessage, Webhook } from "./types.js";
|
|
1
|
+
import type { Account, AnalyticsOverview, AccountAnalyticsEntry, ApiPost, ApiResponse, AudioSearchResult, BestTimesResult, HashtagSet, LocationSearchResult, MediaCheckResult, MediaCompatibility, MediaFolder, MediaItem, PdfUploadResult, PostAnalytics, PublishPostResult, RecentPlatformPost, RetryPostResult, ApprovePostResult, RejectPostResult, InboxConversation, InboxDeleteResult, InboxMessage, InboxNextUnanswered, Webhook } from "./types.js";
|
|
2
2
|
export declare function fetchImageAsBase64(url: string): Promise<{
|
|
3
3
|
data: string;
|
|
4
4
|
mimeType: string;
|
|
@@ -380,6 +380,7 @@ export declare class OmniSocialsClient {
|
|
|
380
380
|
platform?: string;
|
|
381
381
|
type?: string;
|
|
382
382
|
unread?: string;
|
|
383
|
+
unanswered?: string;
|
|
383
384
|
limit?: string;
|
|
384
385
|
cursor?: string;
|
|
385
386
|
}): Promise<ApiResponse<InboxConversation[]>>;
|
|
@@ -392,6 +393,15 @@ export declare class OmniSocialsClient {
|
|
|
392
393
|
text: string;
|
|
393
394
|
attachment_url?: string;
|
|
394
395
|
attachment_type?: string;
|
|
396
|
+
include_next?: boolean;
|
|
395
397
|
}): Promise<ApiResponse<InboxMessage>>;
|
|
398
|
+
getNextUnanswered(params?: {
|
|
399
|
+
platform?: string;
|
|
400
|
+
type?: string;
|
|
401
|
+
order?: string;
|
|
402
|
+
include_read?: string;
|
|
403
|
+
exclude?: string;
|
|
404
|
+
}): Promise<ApiResponse<InboxNextUnanswered | null>>;
|
|
396
405
|
hideInboxMessage(messageId: string, hide?: boolean): Promise<ApiResponse<InboxMessage>>;
|
|
406
|
+
deleteInboxMessage(messageId: string): Promise<ApiResponse<InboxDeleteResult>>;
|
|
397
407
|
}
|
package/build/client.js
CHANGED
|
@@ -435,12 +435,29 @@ export class OmniSocialsClient {
|
|
|
435
435
|
async markInboxRead(conversationId) {
|
|
436
436
|
return this.request("POST", `/inbox/conversations/${encodeURIComponent(conversationId)}/read`);
|
|
437
437
|
}
|
|
438
|
+
// `include_next: true` makes the response also carry `next` (the next
|
|
439
|
+
// conversation that needs an answer, same shape as getNextUnanswered data)
|
|
440
|
+
// and `remaining` at the top level.
|
|
438
441
|
async replyToInboxConversation(conversationId, data) {
|
|
439
442
|
return this.request("POST", `/inbox/conversations/${encodeURIComponent(conversationId)}/reply`, data);
|
|
440
443
|
}
|
|
441
|
-
//
|
|
442
|
-
//
|
|
444
|
+
// The next inbox item that needs an answer (GET /inbox/next): `data` is
|
|
445
|
+
// { conversation, message, messages } or null, `remaining` sits next to it.
|
|
446
|
+
// Only unread items by default (include_read: "true" widens it); `exclude`
|
|
447
|
+
// is a comma-separated list of conversation ids to skip for this call.
|
|
448
|
+
async getNextUnanswered(params) {
|
|
449
|
+
return this.request("GET", "/inbox/next", undefined, params);
|
|
450
|
+
}
|
|
451
|
+
// Hide/unhide a comment on one of the workspace's posts (Facebook,
|
|
452
|
+
// Instagram, TikTok, YouTube, Threads). `hide: false` unhides. Needs the
|
|
453
|
+
// inbox:write scope.
|
|
443
454
|
async hideInboxMessage(messageId, hide = true) {
|
|
444
455
|
return this.request("POST", `/inbox/messages/${encodeURIComponent(messageId)}/hide`, { hide });
|
|
445
456
|
}
|
|
457
|
+
// Delete a comment on the platform and from the inbox (Facebook, Instagram,
|
|
458
|
+
// TikTok; YouTube cannot delete other people's comments, hide instead).
|
|
459
|
+
// Replies under it go too. Needs the inbox:write scope.
|
|
460
|
+
async deleteInboxMessage(messageId) {
|
|
461
|
+
return this.request("DELETE", `/inbox/messages/${encodeURIComponent(messageId)}`);
|
|
462
|
+
}
|
|
446
463
|
}
|
package/build/index.js
CHANGED
|
@@ -29,7 +29,7 @@ const sessionState = { activeIndex: 0 };
|
|
|
29
29
|
const getActiveClient = () => workspaceClients[sessionState.activeIndex].client;
|
|
30
30
|
const server = new McpServer({
|
|
31
31
|
name: "OmniSocials",
|
|
32
|
-
version: "1.
|
|
32
|
+
version: "1.34.0",
|
|
33
33
|
});
|
|
34
34
|
// Register all tools - pass getter function so tools always use the active workspace's client
|
|
35
35
|
registerPostTools(server, getActiveClient);
|
package/build/tools/inbox.js
CHANGED
|
@@ -9,10 +9,88 @@ const PLATFORM_EMOJI = {
|
|
|
9
9
|
x: "🐦",
|
|
10
10
|
threads: "🧵",
|
|
11
11
|
};
|
|
12
|
+
const INBOX_PLATFORMS = [
|
|
13
|
+
"instagram",
|
|
14
|
+
"facebook",
|
|
15
|
+
"linkedin",
|
|
16
|
+
"tiktok",
|
|
17
|
+
"youtube",
|
|
18
|
+
"x",
|
|
19
|
+
"threads",
|
|
20
|
+
];
|
|
21
|
+
function errorResult(error) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: `Error (${error.code}): ${error.message}`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Inbox thread rendering shared by get_inbox_conversation, get_next_unanswered
|
|
32
|
+
// and reply_to_inbox (include_next). The post comes first (a comment like
|
|
33
|
+
// "what size is this?" only makes sense with the post in view), then the
|
|
34
|
+
// messages oldest first, each with its inbox id so hide/delete can target it.
|
|
35
|
+
function renderInboxPost(post) {
|
|
36
|
+
if (!post || (!post.caption && !post.url && !post.thumbnail))
|
|
37
|
+
return "";
|
|
38
|
+
let md = `**Post**`;
|
|
39
|
+
if (post.media_type) {
|
|
40
|
+
md += ` _(${String(post.media_type).toLowerCase().replace(/_/g, " ")})_`;
|
|
41
|
+
}
|
|
42
|
+
md += `\n`;
|
|
43
|
+
if (post.caption) {
|
|
44
|
+
md += `> ${post.caption.replace(/\s*\n+\s*/g, " ").slice(0, 600)}\n`;
|
|
45
|
+
}
|
|
46
|
+
if (post.url)
|
|
47
|
+
md += `🔗 ${post.url}\n`;
|
|
48
|
+
if (post.thumbnail)
|
|
49
|
+
md += `🖼️ ${post.thumbnail}\n`;
|
|
50
|
+
return md + `\n`;
|
|
51
|
+
}
|
|
52
|
+
function renderInboxMessages(msgs, highlightId) {
|
|
53
|
+
let md = "";
|
|
54
|
+
for (const m of msgs) {
|
|
55
|
+
const who = m.direction === "outgoing"
|
|
56
|
+
? "→ You"
|
|
57
|
+
: `← ${m.sender?.name || m.sender?.username || "Them"}`;
|
|
58
|
+
const flags = [];
|
|
59
|
+
if (highlightId && String(m.id) === String(highlightId)) {
|
|
60
|
+
flags.push("**needs a reply**");
|
|
61
|
+
}
|
|
62
|
+
if (m.hidden)
|
|
63
|
+
flags.push("hidden");
|
|
64
|
+
if (m.parent_comment_id)
|
|
65
|
+
flags.push("reply to a comment");
|
|
66
|
+
md += `**${who}** _(${formatDateTime(m.timestamp)})_ \`#${m.id}\`${flags.length ? " · " + flags.join(" · ") : ""}\n${m.text || ""}\n`;
|
|
67
|
+
if (m.attachment?.url) {
|
|
68
|
+
md += `📎 [${m.attachment.type}] ${m.attachment.url}\n`;
|
|
69
|
+
}
|
|
70
|
+
if (m.permalink)
|
|
71
|
+
md += `${m.permalink}\n`;
|
|
72
|
+
md += `\n`;
|
|
73
|
+
}
|
|
74
|
+
return md;
|
|
75
|
+
}
|
|
76
|
+
function renderNextUnanswered(result) {
|
|
77
|
+
const item = result.data;
|
|
78
|
+
if (!item)
|
|
79
|
+
return "Nothing is waiting for an answer.";
|
|
80
|
+
const c = item.conversation;
|
|
81
|
+
const who = c.participant?.name || c.participant?.username || "Unknown";
|
|
82
|
+
let md = `## Next: ${PLATFORM_EMOJI[c.platform] || ""} ${c.platform} ${c.type} from ${who}\n\n`;
|
|
83
|
+
md += `Conversation ID: \`${c.conversation_id}\`\nMessage to answer: \`#${item.message.id}\`\n\n`;
|
|
84
|
+
md += renderInboxPost(item.message.post || c.post);
|
|
85
|
+
md += renderInboxMessages(item.messages, item.message.id);
|
|
86
|
+
md += `_${result.remaining || 0} more waiting._\n\n`;
|
|
87
|
+
md += `_Reply: reply_to_inbox with the conversation ID (add include_next: true to get the next one in the same call). Skip for good: mark_inbox_read. Comments: hide_inbox_comment / delete_inbox_comment with the message id._`;
|
|
88
|
+
return md;
|
|
89
|
+
}
|
|
12
90
|
export function registerInboxTools(server, getClient) {
|
|
13
|
-
server.tool("list_inbox_conversations", "List social inbox conversations (Instagram/Facebook DMs, comments, mentions, LinkedIn company-page comments/mentions, TikTok video comments, YouTube video comments, X DMs where the workspace has opted into X DMs, and Threads replies on the workspace's posts plus mentions), newest activity first.
|
|
91
|
+
server.tool("list_inbox_conversations", "List social inbox conversations (Instagram/Facebook DMs, comments, mentions, LinkedIn company-page comments/mentions, TikTok video comments, YouTube video comments, X DMs where the workspace has opted into X DMs, and Threads replies on the workspace's posts plus mentions), newest activity first. Cursor-paginated: pass the returned cursor to get the next page. To work through what needs answering one item at a time, use get_next_unanswered instead.", {
|
|
14
92
|
platform: z
|
|
15
|
-
.enum(
|
|
93
|
+
.enum(INBOX_PLATFORMS)
|
|
16
94
|
.optional()
|
|
17
95
|
.describe("Filter to one platform"),
|
|
18
96
|
type: z
|
|
@@ -23,6 +101,10 @@ export function registerInboxTools(server, getClient) {
|
|
|
23
101
|
.boolean()
|
|
24
102
|
.optional()
|
|
25
103
|
.describe("Only conversations with unread incoming messages"),
|
|
104
|
+
unanswered: z
|
|
105
|
+
.boolean()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("Only conversations that still need an answer: the customer's latest DM has no reply after it (Instagram/Facebook DMs within the 24-hour reply window), or a comment/mention nobody replied to and that is not hidden. Replies typed in the native apps count as answers."),
|
|
26
108
|
limit: z
|
|
27
109
|
.number()
|
|
28
110
|
.optional()
|
|
@@ -39,21 +121,16 @@ export function registerInboxTools(server, getClient) {
|
|
|
39
121
|
query.type = params.type;
|
|
40
122
|
if (params.unread !== undefined)
|
|
41
123
|
query.unread = String(params.unread);
|
|
124
|
+
if (params.unanswered !== undefined) {
|
|
125
|
+
query.unanswered = String(params.unanswered);
|
|
126
|
+
}
|
|
42
127
|
if (params.limit !== undefined)
|
|
43
128
|
query.limit = String(params.limit);
|
|
44
129
|
if (params.cursor)
|
|
45
130
|
query.cursor = params.cursor;
|
|
46
131
|
const result = await getClient().listInboxConversations(query);
|
|
47
|
-
if (result.error)
|
|
48
|
-
return
|
|
49
|
-
content: [
|
|
50
|
-
{
|
|
51
|
-
type: "text",
|
|
52
|
-
text: `Error (${result.error.code}): ${result.error.message}`,
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
};
|
|
56
|
-
}
|
|
132
|
+
if (result.error)
|
|
133
|
+
return errorResult(result.error);
|
|
57
134
|
const convos = result.data || [];
|
|
58
135
|
if (!convos.length) {
|
|
59
136
|
return {
|
|
@@ -69,17 +146,20 @@ export function registerInboxTools(server, getClient) {
|
|
|
69
146
|
md += `| ${PLATFORM_EMOJI[c.platform] || ""} ${c.platform} | ${c.type} | ${who} | ${c.unread_count || 0} | ${text} | \`${c.conversation_id}\` |\n`;
|
|
70
147
|
}
|
|
71
148
|
if (result.pagination?.next_cursor) {
|
|
72
|
-
md += `\n_More available
|
|
149
|
+
md += `\n_More available: pass \`cursor: "${result.pagination.next_cursor}"\` to list_inbox_conversations for the next page._`;
|
|
73
150
|
}
|
|
74
151
|
return { content: [{ type: "text", text: md }] };
|
|
75
152
|
});
|
|
76
|
-
server.tool("get_inbox_conversation", "Get the full message history of one inbox conversation, oldest first. Use the conversation_id from list_inbox_conversations.", {
|
|
153
|
+
server.tool("get_inbox_conversation", "Get the full message history of one inbox conversation, oldest first, with the post it belongs to (caption, link, image) for comment/mention threads. Each message shows its inbox id (#id): that id is what hide_inbox_comment and delete_inbox_comment take. Use the conversation_id from list_inbox_conversations. Replies sent from the native apps appear as outgoing messages too.", {
|
|
77
154
|
conversation_id: z.string().describe("The conversation ID to fetch"),
|
|
78
155
|
limit: z
|
|
79
156
|
.number()
|
|
80
157
|
.optional()
|
|
81
158
|
.describe("Max messages per page (1-100, default 50)"),
|
|
82
|
-
cursor: z
|
|
159
|
+
cursor: z
|
|
160
|
+
.string()
|
|
161
|
+
.optional()
|
|
162
|
+
.describe("Pagination cursor from a previous response"),
|
|
83
163
|
}, { title: "Get Inbox Conversation", readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async ({ conversation_id, limit, cursor }) => {
|
|
84
164
|
const query = {};
|
|
85
165
|
if (limit !== undefined)
|
|
@@ -87,16 +167,8 @@ export function registerInboxTools(server, getClient) {
|
|
|
87
167
|
if (cursor)
|
|
88
168
|
query.cursor = cursor;
|
|
89
169
|
const result = await getClient().getInboxMessages(conversation_id, query);
|
|
90
|
-
if (result.error)
|
|
91
|
-
return
|
|
92
|
-
content: [
|
|
93
|
-
{
|
|
94
|
-
type: "text",
|
|
95
|
-
text: `Error (${result.error.code}): ${result.error.message}`,
|
|
96
|
-
},
|
|
97
|
-
],
|
|
98
|
-
};
|
|
99
|
-
}
|
|
170
|
+
if (result.error)
|
|
171
|
+
return errorResult(result.error);
|
|
100
172
|
const msgs = result.data || [];
|
|
101
173
|
if (!msgs.length) {
|
|
102
174
|
return {
|
|
@@ -106,24 +178,11 @@ export function registerInboxTools(server, getClient) {
|
|
|
106
178
|
};
|
|
107
179
|
}
|
|
108
180
|
let md = `## Conversation \`${conversation_id}\`\n\n`;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
: `← ${m.sender?.name || m.sender?.username || "Them"}`;
|
|
113
|
-
// The message id is what hide_inbox_reply takes, so always render it.
|
|
114
|
-
const flags = [`id ${m.id}`];
|
|
115
|
-
if (m.hidden === true)
|
|
116
|
-
flags.push("hidden on Threads");
|
|
117
|
-
md += `**${who}** _(${formatDateTime(m.timestamp)})_ \`${flags.join(" · ")}\`\n${m.text || ""}\n`;
|
|
118
|
-
if (m.attachment?.url) {
|
|
119
|
-
md += `📎 [${m.attachment.type}] ${m.attachment.url}\n`;
|
|
120
|
-
}
|
|
121
|
-
if (m.permalink)
|
|
122
|
-
md += `${m.permalink}\n`;
|
|
123
|
-
md += `\n`;
|
|
124
|
-
}
|
|
181
|
+
const post = msgs.find((m) => m.post && (m.post.caption || m.post.url))?.post;
|
|
182
|
+
md += renderInboxPost(post);
|
|
183
|
+
md += renderInboxMessages(msgs);
|
|
125
184
|
if (result.pagination?.next_cursor) {
|
|
126
|
-
md += `_More messages
|
|
185
|
+
md += `_More messages: pass \`cursor: "${result.pagination.next_cursor}"\` for the next page._`;
|
|
127
186
|
}
|
|
128
187
|
return { content: [{ type: "text", text: md }] };
|
|
129
188
|
});
|
|
@@ -131,16 +190,8 @@ export function registerInboxTools(server, getClient) {
|
|
|
131
190
|
conversation_id: z.string().describe("The conversation ID to mark read"),
|
|
132
191
|
}, { title: "Mark Inbox Message Read", readOnlyHint: false, destructiveHint: false, openWorldHint: false }, async ({ conversation_id }) => {
|
|
133
192
|
const result = await getClient().markInboxRead(conversation_id);
|
|
134
|
-
if (result.error)
|
|
135
|
-
return
|
|
136
|
-
content: [
|
|
137
|
-
{
|
|
138
|
-
type: "text",
|
|
139
|
-
text: `Error (${result.error.code}): ${result.error.message}`,
|
|
140
|
-
},
|
|
141
|
-
],
|
|
142
|
-
};
|
|
143
|
-
}
|
|
193
|
+
if (result.error)
|
|
194
|
+
return errorResult(result.error);
|
|
144
195
|
return {
|
|
145
196
|
content: [
|
|
146
197
|
{
|
|
@@ -150,7 +201,7 @@ export function registerInboxTools(server, getClient) {
|
|
|
150
201
|
],
|
|
151
202
|
};
|
|
152
203
|
});
|
|
153
|
-
server.tool("reply_to_inbox", "Reply to an existing inbox conversation (DM, comment, or mention) on Instagram, Facebook, LinkedIn, TikTok,
|
|
204
|
+
server.tool("reply_to_inbox", "Reply to an existing inbox conversation (DM, comment, or mention) on Instagram, Facebook, LinkedIn, TikTok, YouTube, X, or Threads. 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. YouTube replies are comments only. Threads replies are on the workspace's posts and mentions; a 401 reauth_required means the Threads connection must be reconnected to grant the reply permission. 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. attachment_url/attachment_type let you send media on a Facebook or Instagram DM (text is optional when an attachment is set); other platforms are text-only. Set include_next: true to get the next conversation that needs an answer back in the same call (same as get_next_unanswered), so the inbox can be worked through as: get_next_unanswered → draft → reply_to_inbox(include_next) → draft → repeat.", {
|
|
154
205
|
conversation_id: z.string().describe("The conversation ID to reply to"),
|
|
155
206
|
text: z
|
|
156
207
|
.string()
|
|
@@ -164,60 +215,118 @@ export function registerInboxTools(server, getClient) {
|
|
|
164
215
|
.enum(["image", "video", "audio", "file"])
|
|
165
216
|
.optional()
|
|
166
217
|
.describe("Required if attachment_url is set"),
|
|
167
|
-
|
|
218
|
+
include_next: z
|
|
219
|
+
.boolean()
|
|
220
|
+
.optional()
|
|
221
|
+
.describe("After sending, also return the next conversation that needs an answer (full thread + post context), or 'nothing waiting'"),
|
|
222
|
+
}, { title: "Reply to Inbox Message", readOnlyHint: false, destructiveHint: true, openWorldHint: true }, async ({ conversation_id, text, attachment_url, attachment_type, include_next }) => {
|
|
168
223
|
const result = await getClient().replyToInboxConversation(conversation_id, {
|
|
169
224
|
text: text ?? "",
|
|
170
225
|
attachment_url,
|
|
171
226
|
attachment_type,
|
|
227
|
+
...(include_next ? { include_next: true } : {}),
|
|
172
228
|
});
|
|
173
|
-
if (result.error)
|
|
174
|
-
return
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
229
|
+
if (result.error)
|
|
230
|
+
return errorResult(result.error);
|
|
231
|
+
const m = result.data;
|
|
232
|
+
let md = `Reply sent to \`${conversation_id}\` on ${m?.platform || "the platform"}.`;
|
|
233
|
+
if (include_next) {
|
|
234
|
+
md +=
|
|
235
|
+
`\n\n` +
|
|
236
|
+
renderNextUnanswered({
|
|
237
|
+
data: result.next || null,
|
|
238
|
+
remaining: result.remaining || 0,
|
|
239
|
+
});
|
|
182
240
|
}
|
|
241
|
+
return { content: [{ type: "text", text: md }] };
|
|
242
|
+
});
|
|
243
|
+
server.tool("get_next_unanswered", "Get the next inbox item that needs an answer, with the whole conversation and the post it is on, in one call. Serves the longest-waiting item first: the customer's latest DM with no reply after it (Instagram/Facebook DMs within the 24-hour reply window only, since Meta refuses later replies), or a comment/mention nobody replied to and that is not hidden. Replies typed in the native apps count as answered (they are mirrored into the inbox), so a thread a colleague answered on their phone is not served again. Instagram mentions are skipped (no reply path). Only unread items are served by default: to skip one for good, mark_inbox_read it; pass include_read to also see read-but-unanswered items. Looks back 30 days. Loop: get_next_unanswered → draft a reply → reply_to_inbox with include_next: true → repeat until nothing is waiting.", {
|
|
244
|
+
platform: z
|
|
245
|
+
.enum(INBOX_PLATFORMS)
|
|
246
|
+
.optional()
|
|
247
|
+
.describe("Only items from one platform"),
|
|
248
|
+
type: z
|
|
249
|
+
.enum(["dm", "comment", "mention"])
|
|
250
|
+
.optional()
|
|
251
|
+
.describe("Only items of one type"),
|
|
252
|
+
order: z
|
|
253
|
+
.enum(["oldest", "newest"])
|
|
254
|
+
.optional()
|
|
255
|
+
.describe("oldest (default): the item that has waited longest first; newest: the most recent first"),
|
|
256
|
+
include_read: z
|
|
257
|
+
.boolean()
|
|
258
|
+
.optional()
|
|
259
|
+
.describe("Also serve items that were marked read but never answered"),
|
|
260
|
+
exclude: z
|
|
261
|
+
.string()
|
|
262
|
+
.optional()
|
|
263
|
+
.describe("Comma-separated conversation ids to leave out of this call (a temporary skip; up to 100)"),
|
|
264
|
+
}, { title: "Get Next Unanswered", readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (params) => {
|
|
265
|
+
const query = {};
|
|
266
|
+
if (params.platform)
|
|
267
|
+
query.platform = params.platform;
|
|
268
|
+
if (params.type)
|
|
269
|
+
query.type = params.type;
|
|
270
|
+
if (params.order)
|
|
271
|
+
query.order = params.order;
|
|
272
|
+
if (params.include_read !== undefined) {
|
|
273
|
+
query.include_read = String(params.include_read);
|
|
274
|
+
}
|
|
275
|
+
if (params.exclude)
|
|
276
|
+
query.exclude = params.exclude;
|
|
277
|
+
const result = await getClient().getNextUnanswered(query);
|
|
278
|
+
if (result.error)
|
|
279
|
+
return errorResult(result.error);
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: "text", text: renderNextUnanswered(result) }],
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
const hideCommentSchema = {
|
|
285
|
+
message_id: z
|
|
286
|
+
.string()
|
|
287
|
+
.describe("The inbox message id of the comment (the #id in the thread)"),
|
|
288
|
+
hide: z
|
|
289
|
+
.boolean()
|
|
290
|
+
.optional()
|
|
291
|
+
.describe("true (default) to hide, false to unhide"),
|
|
292
|
+
};
|
|
293
|
+
const hideComment = async ({ message_id, hide, }) => {
|
|
294
|
+
const result = await getClient().hideInboxMessage(message_id, hide !== false);
|
|
295
|
+
if (result.error)
|
|
296
|
+
return errorResult(result.error);
|
|
183
297
|
const m = result.data;
|
|
184
298
|
return {
|
|
185
299
|
content: [
|
|
186
300
|
{
|
|
187
301
|
type: "text",
|
|
188
|
-
text: `
|
|
302
|
+
text: `Comment \`#${m?.id ?? message_id}\` on ${m?.platform || "the platform"} is now ${m?.hidden ? "hidden" : "visible"}.`,
|
|
189
303
|
},
|
|
190
304
|
],
|
|
191
305
|
};
|
|
192
|
-
}
|
|
193
|
-
server.tool("
|
|
306
|
+
};
|
|
307
|
+
server.tool("hide_inbox_comment", "Hide (or unhide) a comment someone left on one of the workspace's posts, on the platform, as the post owner. Facebook, Instagram, TikTok, YouTube and Threads comments. On YouTube, hide sets the comment to 'rejected' (removed from public view together with its replies) and unhide publishes it again. Takes the message id (the #id shown by get_inbox_conversation and get_next_unanswered), not the conversation id. A hidden comment no longer counts as unanswered. A 403 reconnect_required means the account was connected without the comment-moderation permission and must be reconnected in the dashboard; relay that to the user rather than retrying.", hideCommentSchema, { title: "Hide Inbox Comment", readOnlyHint: false, destructiveHint: false, openWorldHint: true }, hideComment);
|
|
308
|
+
// Older name from 1.28.0 (when hiding was Threads-only). Kept so existing
|
|
309
|
+
// setups keep working; same call, same platforms as hide_inbox_comment.
|
|
310
|
+
server.tool("hide_inbox_reply", "Older name for hide_inbox_comment, kept so existing setups keep working; prefer hide_inbox_comment. Same behaviour: hide (or unhide with hide: false) a comment someone left on one of the workspace's posts, as the post owner, on Facebook, Instagram, TikTok, YouTube or Threads. Takes the message id (the #id in the thread), not the conversation id. A 403 reconnect_required means the account must be reconnected in the dashboard to grant the comment-moderation permission.", hideCommentSchema, { title: "Hide Inbox Comment (older name)", readOnlyHint: false, destructiveHint: false, openWorldHint: true }, hideComment);
|
|
311
|
+
server.tool("delete_inbox_comment", "Permanently delete a comment someone left on one of the workspace's posts, on the platform and from the inbox. Facebook, Instagram and TikTok comments only (YouTube's API cannot delete other people's comments: use hide_inbox_comment there). Replies under the comment are deleted with it. This cannot be undone: confirm with the user before calling. Takes the message id (the #id in the thread), not the conversation id. A 403 reconnect_required means the account must be reconnected in the dashboard to grant the moderation permission.", {
|
|
194
312
|
message_id: z
|
|
195
313
|
.string()
|
|
196
|
-
.describe("The inbox message id of the
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
content: [
|
|
206
|
-
{
|
|
207
|
-
type: "text",
|
|
208
|
-
text: `Error (${result.error.code}): ${result.error.message}`,
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
const hidden = result.data?.hidden === true;
|
|
314
|
+
.describe("The inbox message id of the comment (the #id in the thread)"),
|
|
315
|
+
}, { title: "Delete Inbox Comment", readOnlyHint: false, destructiveHint: true, openWorldHint: true }, async ({ message_id }) => {
|
|
316
|
+
const result = await getClient().deleteInboxMessage(message_id);
|
|
317
|
+
if (result.error)
|
|
318
|
+
return errorResult(result.error);
|
|
319
|
+
const d = result.data;
|
|
320
|
+
const replies = Array.isArray(d?.removed_reply_ids)
|
|
321
|
+
? d.removed_reply_ids.length
|
|
322
|
+
: 0;
|
|
214
323
|
return {
|
|
215
324
|
content: [
|
|
216
325
|
{
|
|
217
326
|
type: "text",
|
|
218
|
-
text:
|
|
219
|
-
? `
|
|
220
|
-
:
|
|
327
|
+
text: `Comment \`#${d?.id ?? message_id}\` deleted${replies
|
|
328
|
+
? ` together with ${replies} repl${replies === 1 ? "y" : "ies"}`
|
|
329
|
+
: ""}.`,
|
|
221
330
|
},
|
|
222
331
|
],
|
|
223
332
|
};
|
package/build/tools/media.js
CHANGED
|
@@ -72,7 +72,7 @@ When the user provides an image in the conversation (not a URL), use base64_data
|
|
|
72
72
|
filename: z.string().optional().describe("Optional filename for the uploaded media"),
|
|
73
73
|
name: z.string().optional().describe('Human-readable label so you can find this asset by name later instead of re-uploading it, e.g. "pp-play5get50". Strongly recommended on every upload.'),
|
|
74
74
|
folder: z.string().optional().describe('Optional folder name to file this asset under (created at the top level if it does not exist), e.g. "win-graphics". See list_folders.'),
|
|
75
|
-
}, { title: "Upload Media", readOnlyHint: false, destructiveHint: false, openWorldHint:
|
|
75
|
+
}, { title: "Upload Media", readOnlyHint: false, destructiveHint: false, openWorldHint: true }, async (params) => {
|
|
76
76
|
let result;
|
|
77
77
|
if (params.base64_data && params.mime_type) {
|
|
78
78
|
result = await getClient().uploadMediaFromBase64({
|
|
@@ -159,7 +159,7 @@ Provide ONE of: a public 'url' (its size/type is read via a HEAD request), an ex
|
|
|
159
159
|
media_id: z.string().optional().describe("Id of an already-uploaded library item to check."),
|
|
160
160
|
size_bytes: z.number().optional().describe("File size in bytes (use with mime when you already know them)."),
|
|
161
161
|
mime: z.string().optional().describe("MIME type, e.g. 'video/mp4' (use with size_bytes)."),
|
|
162
|
-
}, { title: "Check Media Compatibility", readOnlyHint: true, destructiveHint: false, openWorldHint:
|
|
162
|
+
}, { title: "Check Media Compatibility", readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async (params) => {
|
|
163
163
|
if (!params.url && !params.media_id && !(params.size_bytes && params.mime)) {
|
|
164
164
|
return {
|
|
165
165
|
content: [{ type: "text", text: "Error: Provide one of 'url', 'media_id', or 'size_bytes' + 'mime'." }],
|
package/build/tools/posts.js
CHANGED
|
@@ -917,7 +917,7 @@ Do NOT call without required media — it will fail.`, {
|
|
|
917
917
|
thread_parts: z.array(THREADS_THREAD_PART).min(2).max(25).optional().describe("Publish as a chained Threads (Meta) thread (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 or media_urls. The Threads caption is taken from part 1."),
|
|
918
918
|
}).optional().describe("Threads (Meta) options: thread mode via `thread_parts`, location tag via `location_id`."),
|
|
919
919
|
google_business: GOOGLE_BUSINESS_OPTIONS,
|
|
920
|
-
}, { title: "Create and Publish Post", readOnlyHint: false, destructiveHint:
|
|
920
|
+
}, { title: "Create and Publish Post", readOnlyHint: false, destructiveHint: true, openWorldHint: true }, async (params) => {
|
|
921
921
|
const result = await getClient().createAndPublishPost({ ...params, source: "mcp" });
|
|
922
922
|
if (result.error) {
|
|
923
923
|
return {
|
|
@@ -1071,7 +1071,7 @@ Do NOT call without required media — it will fail.`, {
|
|
|
1071
1071
|
|
|
1072
1072
|
IMPORTANT: Before publishing, verify the post has all required media. If publishing a story or reel, ensure media was attached when the post was created/updated. If it's missing, use update_post to add media first, or inform the user.`, {
|
|
1073
1073
|
id: z.string().describe("The post ID to publish"),
|
|
1074
|
-
}, { title: "Publish Post", readOnlyHint: false, destructiveHint:
|
|
1074
|
+
}, { title: "Publish Post", readOnlyHint: false, destructiveHint: true, openWorldHint: true }, async ({ id }) => {
|
|
1075
1075
|
const result = await getClient().publishPost(id);
|
|
1076
1076
|
if (result.error) {
|
|
1077
1077
|
return {
|
|
@@ -1097,7 +1097,7 @@ IMPORTANT: Before publishing, verify the post has all required media. If publish
|
|
|
1097
1097
|
|
|
1098
1098
|
The retry runs asynchronously (usually within a few minutes). Poll \`get_post\` afterwards: on success the status becomes \`published\` and \`published_urls\` gains the platform's live URL; on another failure the platform's entry reappears under errors. Each platform can be retried at most 3 times — after that, recreate the post. Note \`publish_post\` refuses failed posts; this is the tool for them.`, {
|
|
1099
1099
|
id: z.string().describe("The failed or partially failed post ID to retry"),
|
|
1100
|
-
}, { title: "Retry Failed Post", readOnlyHint: false, destructiveHint:
|
|
1100
|
+
}, { title: "Retry Failed Post", readOnlyHint: false, destructiveHint: true, openWorldHint: true }, async ({ id }) => {
|
|
1101
1101
|
const result = await getClient().retryPost(id);
|
|
1102
1102
|
if (result.error) {
|
|
1103
1103
|
return {
|
|
@@ -1161,7 +1161,7 @@ IMPORTANT: this only succeeds if the connected user is a listed approver for the
|
|
|
1161
1161
|
|
|
1162
1162
|
If this is the workflow's last step, the post is finalized immediately: it moves to \`scheduled\` (future time) or \`posting\` (due now/in the past). If steps remain, the post stays \`in_approval\` and the next step's approvers are notified — call this again once they've approved, or use \`reject_post\` to stop the workflow instead.`, {
|
|
1163
1163
|
id: z.string().describe("The post ID to approve (must have approval_status 'pending')"),
|
|
1164
|
-
}, { title: "Approve Post", readOnlyHint: false, destructiveHint:
|
|
1164
|
+
}, { title: "Approve Post", readOnlyHint: false, destructiveHint: true, openWorldHint: true }, async ({ id }) => {
|
|
1165
1165
|
const result = await getClient().approvePost(id);
|
|
1166
1166
|
if (result.error) {
|
|
1167
1167
|
return {
|
|
@@ -1195,7 +1195,7 @@ IMPORTANT: this only succeeds if the connected user is a listed approver for the
|
|
|
1195
1195
|
.max(2000)
|
|
1196
1196
|
.optional()
|
|
1197
1197
|
.describe("Optional reason for the rejection, shown to the requester and other approvers."),
|
|
1198
|
-
}, { title: "Reject Post", readOnlyHint: false, destructiveHint: false, openWorldHint:
|
|
1198
|
+
}, { title: "Reject Post", readOnlyHint: false, destructiveHint: false, openWorldHint: false }, async ({ id, comment }) => {
|
|
1199
1199
|
const result = await getClient().rejectPost(id, comment);
|
|
1200
1200
|
if (result.error) {
|
|
1201
1201
|
return {
|
package/build/types.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface ApiResponse<T = unknown> {
|
|
|
19
19
|
};
|
|
20
20
|
conversation_id?: string;
|
|
21
21
|
marked_read?: number;
|
|
22
|
+
next?: InboxNextUnanswered | null;
|
|
23
|
+
remaining?: number;
|
|
22
24
|
}
|
|
23
25
|
export interface InboxParticipant {
|
|
24
26
|
id: string;
|
|
@@ -26,6 +28,16 @@ export interface InboxParticipant {
|
|
|
26
28
|
username: string;
|
|
27
29
|
profile_picture: string | null;
|
|
28
30
|
}
|
|
31
|
+
/** The post a comment/mention thread belongs to. `url` is the public link to
|
|
32
|
+
* the post and `media_type` the platform's own label (IMAGE, VIDEO,
|
|
33
|
+
* CAROUSEL_ALBUM, ...); both nullable. null on DM conversations. */
|
|
34
|
+
export interface InboxPostContext {
|
|
35
|
+
id: string | null;
|
|
36
|
+
caption: string | null;
|
|
37
|
+
thumbnail: string | null;
|
|
38
|
+
url: string | null;
|
|
39
|
+
media_type: string | null;
|
|
40
|
+
}
|
|
29
41
|
export interface InboxConversation {
|
|
30
42
|
conversation_id: string;
|
|
31
43
|
platform: "instagram" | "facebook" | "linkedin" | "tiktok" | "x" | "youtube" | "threads";
|
|
@@ -39,11 +51,7 @@ export interface InboxConversation {
|
|
|
39
51
|
timestamp: string;
|
|
40
52
|
is_read: boolean;
|
|
41
53
|
};
|
|
42
|
-
post:
|
|
43
|
-
id: string | null;
|
|
44
|
-
caption: string | null;
|
|
45
|
-
thumbnail: string | null;
|
|
46
|
-
} | null;
|
|
54
|
+
post: InboxPostContext | null;
|
|
47
55
|
}
|
|
48
56
|
export interface InboxMessage {
|
|
49
57
|
id: string;
|
|
@@ -57,8 +65,9 @@ export interface InboxMessage {
|
|
|
57
65
|
is_replied: boolean;
|
|
58
66
|
reaction: string | null;
|
|
59
67
|
parent_comment_id: string | null;
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
68
|
+
/** Comments on every comment platform (Facebook, Instagram, TikTok,
|
|
69
|
+
* YouTube, Threads): true when the comment is hidden on the platform (flip
|
|
70
|
+
* it with hide_inbox_comment). null for DMs. */
|
|
62
71
|
hidden?: boolean | null;
|
|
63
72
|
/** Link to the reply or mentioning post on the platform, when known. */
|
|
64
73
|
permalink?: string | null;
|
|
@@ -70,11 +79,24 @@ export interface InboxMessage {
|
|
|
70
79
|
type: "image" | "video" | "audio" | "file";
|
|
71
80
|
} | null;
|
|
72
81
|
sender: InboxParticipant;
|
|
73
|
-
post:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
post: InboxPostContext | null;
|
|
83
|
+
}
|
|
84
|
+
/** What GET /inbox/next serves (and what `reply_to_inbox` returns as `next`
|
|
85
|
+
* when `include_next` is set). `message` is the unanswered incoming item: its
|
|
86
|
+
* `id` is what hide/delete take, its `conversation_id` is what reply takes.
|
|
87
|
+
* `messages` is the thread oldest first (the most recent 50, plus the target
|
|
88
|
+
* and its parent when they fall outside that window). */
|
|
89
|
+
export interface InboxNextUnanswered {
|
|
90
|
+
conversation: InboxConversation;
|
|
91
|
+
message: InboxMessage;
|
|
92
|
+
messages: InboxMessage[];
|
|
93
|
+
}
|
|
94
|
+
/** Response of DELETE /inbox/messages/:id. Replies under the comment are
|
|
95
|
+
* removed with it; their inbox ids are listed in `removed_reply_ids`. */
|
|
96
|
+
export interface InboxDeleteResult {
|
|
97
|
+
id: string;
|
|
98
|
+
conversation_id: string;
|
|
99
|
+
removed_reply_ids: string[];
|
|
78
100
|
}
|
|
79
101
|
/** One media entry on a post — a plain URL string, or an object carrying the
|
|
80
102
|
* url plus an optional accessibility description (alt text). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnisocials/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.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",
|