@omnisocials/mcp-server 1.39.0 → 1.46.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 +55 -2
- package/build/client.d.ts +136 -1
- package/build/client.js +27 -0
- package/build/index.js +3 -1
- package/build/metrics.js +30 -0
- package/build/tools/accounts.js +35 -3
- package/build/tools/analytics.js +2 -2
- package/build/tools/automations.d.ts +25 -0
- package/build/tools/automations.js +199 -0
- package/build/tools/media.js +14 -1
- package/build/tools/posts.js +298 -29
- package/build/types.d.ts +224 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -112,6 +112,8 @@ OmniSocials accepts the following channel IDs in `create_post`, `create_and_publ
|
|
|
112
112
|
| `threads` | Threads | Posts, chained threads via `threads.thread_parts`, location tagging via `threads.location_id` (rolling out) |
|
|
113
113
|
| `bluesky` | Bluesky | Posts, chained threads via `bluesky.thread_parts` |
|
|
114
114
|
| `mastodon` | Mastodon | Posts, chained threads via `mastodon.thread_parts` |
|
|
115
|
+
| `telegram` | Telegram | A channel or group the OmniSocials bot posts to: text, photos, videos, GIFs, albums of up to 10, native polls and link buttons via `telegram` |
|
|
116
|
+
| `discord` | Discord | One Discord channel, posted through its webhook: text, up to 10 images, GIFs and videos, native polls, link buttons and forum post titles via `discord` |
|
|
115
117
|
|
|
116
118
|
## Available Tools
|
|
117
119
|
|
|
@@ -206,11 +208,24 @@ Apply a set when creating a post: `create_post` / `create_and_publish_post` acce
|
|
|
206
208
|
|
|
207
209
|
Requires an API key with the opt-in `inbox:read` / `inbox:write` scopes — enable "Social Inbox access" when you create the key.
|
|
208
210
|
|
|
211
|
+
### Automations (6 tools)
|
|
212
|
+
|
|
213
|
+
| Tool | Description |
|
|
214
|
+
|------|-------------|
|
|
215
|
+
| `list_automations` | List the comment-to-DM automations of the workspace (Instagram and Facebook Page), optionally by `status`, `post_id` or `platform` |
|
|
216
|
+
| `get_automation` | Get one automation: trigger, keywords, DM text, public reply, limits and stats |
|
|
217
|
+
| `create_automation` | Create a comment-to-DM automation: a keyword in a comment (or a DM) gets a DM back, once per person, with an optional public reply |
|
|
218
|
+
| `update_automation` | Pause, resume or change the keywords, DM text, public reply, limits or post of an automation |
|
|
219
|
+
| `delete_automation` | Delete an automation |
|
|
220
|
+
| `get_automation_activity` | One row per comment or message the automation acted on: the DM sent, a skip with its reason, or the platform's error |
|
|
221
|
+
|
|
222
|
+
Requires an API key with the opt-in `automations:read` / `automations:write` scopes (Settings > API, "Automations" on the key).
|
|
223
|
+
|
|
209
224
|
## Environment Variables
|
|
210
225
|
|
|
211
226
|
| Variable | Required | Description |
|
|
212
227
|
|----------|----------|-------------|
|
|
213
|
-
| `OMNISOCIALS_API_KEY` | Yes | Your API key (`omsk_live_
|
|
228
|
+
| `OMNISOCIALS_API_KEY` | Yes | Your API key (`omsk_live_*`; there are no test keys). Comma-separated for multiple workspaces. |
|
|
214
229
|
| `OMNISOCIALS_BASE_URL` | No | Custom API base URL (defaults to production) |
|
|
215
230
|
|
|
216
231
|
## Alternative: Agent Skills
|
|
@@ -229,6 +244,44 @@ Full API docs: [docs.omnisocials.com](https://docs.omnisocials.com)
|
|
|
229
244
|
|
|
230
245
|
## Changelog
|
|
231
246
|
|
|
247
|
+
### 1.46.0 (2026-10-23)
|
|
248
|
+
|
|
249
|
+
- **Added: comment-to-DM on a Facebook Page.** The automation tools now run on the connected Facebook Page as well as on Instagram. `create_automation` takes `platform` (`instagram`, the default, or `facebook`) and `facebook_post_id` (a Page post published outside OmniSocials, `{page-id}_{post-id}` or the post id alone; it implies `facebook`). On Facebook the DM lands in Messenger, `triggers` takes `comment` and `dm` (a keyword in a Messenger message to the Page), and `story_reply` is refused (Facebook has no story replies). Meta's rules are the same on both: one private reply per comment within 7 days, a follow-up only after the person answers. `list_automations` takes a `platform` filter and shows a Platform column; `get_automation` and `create_automation` print the platform. `update_automation` takes `facebook_post_id` to move a Facebook automation to another Page post; the platform itself cannot change (`platform_immutable`). `get_automation_activity` shows a Facebook person by name, without an @. Same on the companion server (`mcp.omnisocials.com`).
|
|
250
|
+
- **Added: `platforms` on `comment_dm_automation`.** `create_post`, `create_and_publish_post` and `update_post` take `comment_dm_automation.platforms`: `["instagram"]`, `["facebook"]` or both, one automation per channel with the same keywords, DM, public reply and limits. Each listed channel must be a channel of the post. Without it, the post gets one automation: Instagram when the post goes there, else Facebook. On `update_post` a channel no longer listed loses its automation, and `null` removes them all. The post shows every automation id with its channel; `get_post` shows them for a post that goes to Instagram or Facebook.
|
|
251
|
+
- **Added: Discord.** `discord` is a new channel ID: one Discord channel the user connected in OmniSocials (with Continue with Discord, or by pasting a webhook URL). The post is sent through the channel's webhook, so it shows the webhook's name and picture. `create_post`, `create_and_publish_post` and `update_post` take a `discord` object: `silent` (no notification), `link_preview` (`false` hides the link previews), `allow_mentions` (off by default: @everyone, @here and role mentions in the text ping nobody until it is `true`), `spoiler`, `thread_name` (the post title in a forum or media channel, where Discord requires one; max 100 chars), up to 5 `buttons` (`{ text, url }`, labels up to 80 chars, not with a poll) and a native `poll` (`{ options: 2-10 of up to 55 chars, duration_hours: 1-768 (default 24), multiple }`; the post text is the question, max 300 chars; no media). Text up to 2,000 characters; up to 10 images, GIFs and videos per message, which may mix; a video or GIF over Discord's 20 MB file limit is sent as a link in the message (listed in `result.video_links`); no Stories; a Reel is sent as one video message. On `update_post` every key merges into the stored options and `null` removes it. `get_post` prints the options and, once sent, the link to the message. `list_accounts` and `get_account` show the server, the channel name and type (`platform_details.channel_name` and `channel_type`: read by the OmniSocials bot, which Continue with Discord adds with no permissions, or given by the person when connecting), the webhook name, the channel id, how it was connected and the channel link; with the bot in the server, `get_account_analytics` shows the member count as followers. Discord gives webhooks no view counts: `get_post_analytics` shows the reactions (total and per emoji) and the poll votes. Same on the companion server (`mcp.omnisocials.com`), whose version is 1.46.0 too.
|
|
252
|
+
|
|
253
|
+
### 1.45.0 (not published; included in 1.46.0)
|
|
254
|
+
|
|
255
|
+
- **Added: a link to the post on every post tool.** `publish_post`, `retry_post`, `approve_post` and `reject_post` now end with the "Open in OmniSocials" row that `create_post`, `create_and_publish_post`, `get_post` and `update_post` already had, and `publish_post` says that `get_post` lists the live links once each platform has posted. Every app link carries `?ref=mcp`, so OmniSocials can count the visits from an agent's answer (the app ignores the parameter).
|
|
256
|
+
- **Added on the companion server (`mcp.omnisocials.com`): the post card.** In apps that show MCP Apps (Claude, ChatGPT, Cursor, VS Code), the results of `get_post`, `create_post`, `create_and_publish_post`, `update_post`, `publish_post`, `retry_post`, `approve_post` and `reject_post` appear as a card: the post the way each platform will show it, a channel list with each channel's state (the planned time, Posting, Posted with its live link, or Failed with the reason), and a link into OmniSocials. While a post publishes, the card follows it until every channel is done. Other clients (terminal agents, automations) get the same text answer as before. The card is built as an MCP App resource, `ui://omnisocials/post-card.html`; a new tool, `get_post_status`, is only for the card (hidden from the model in apps that support MCP Apps).
|
|
257
|
+
|
|
258
|
+
### 1.44.0 (2026-10-17, not published; included in 1.46.0)
|
|
259
|
+
|
|
260
|
+
- **Added: Telegram.** `telegram` is a new channel ID: a Telegram channel or group the user connected in OmniSocials, where the OmniSocials bot is an admin that can post. `create_post`, `create_and_publish_post` and `update_post` take a `telegram` object: `silent`, `link_preview`, `pin`, `protect_content`, `spoiler`, `caption_above_media`, up to 3 `buttons` (`{ text, url }`, one per row, not on an album or a poll), a native `poll` (`{ options: 2-12 of up to 100 chars, open_period: 5-2628000 seconds, multiple }`; the post text is the question, max 300 chars; no media) and `first_comment` (a reply under the post). Text up to 4,096 characters; with media, text over 1,024 characters is sent as a reply under the media. Albums hold up to 10 photos and videos; a GIF goes on its own; files up to 50 MB; no Stories; a Reel is sent as a video message. On `update_post` every key merges into the stored options and `null` removes it. `get_post` prints the options and, once sent, the message count, the pin and the link. `list_accounts` and `get_account` show the chat type, the subscribers and the link. Telegram gives bots no view counts: `get_post_analytics` shows the reactions (total and per emoji) and the poll votes. Same on the companion server (`mcp.omnisocials.com`).
|
|
261
|
+
- **Changed: the three version strings match again.** `package.json`, the stdio server and the companion server all say 1.44.0 (the stdio server said 1.42.0 and the companion server 1.41.0 after the last releases).
|
|
262
|
+
|
|
263
|
+
### 1.43.0 (2026-10-12, not published; included in 1.46.0)
|
|
264
|
+
|
|
265
|
+
- **Polls on X, Threads and Mastodon.** `create_post`, `create_and_publish_post` and `update_post` take `poll` under `x`, `threads` and `mastodon` (the post text is the question): X `{ options: 2-4 of up to 25 chars, duration_minutes: 5-10080 }`, Threads `{ options: 2-4 of 1-25 chars }` (closed by Threads after 24 hours), Mastodon `{ options, expires_in, multiple, hide_totals }` within the instance's limits. The API refuses a channel with a poll and media; on a thread the poll goes on the first part. `null` on `update_post` removes it. `get_post` prints the poll per channel.
|
|
266
|
+
|
|
267
|
+
### 1.42.0 (not published; included in 1.46.0)
|
|
268
|
+
|
|
269
|
+
- **Added: comment-to-DM automations.** Six new tools manage the Instagram comment-to-DM automations of the workspace: `list_automations`, `get_automation`, `create_automation`, `update_automation` (pause, resume, new keywords, new DM text, new limits, another post), `delete_automation` and `get_automation_activity` (one row per comment or message the automation acted on: the DM sent, a skip with its reason, or Instagram's error). An automation DMs a person once when they comment a keyword on a post, and can post a public reply under the comment; `triggers` also takes `dm` (a keyword in a DM to the account) and `story_reply` (a keyword in a reply to a story). `scope` is one post (`post_id` = an OmniSocials post, scheduled or live; `instagram_media_id` = a post published outside OmniSocials, read from Instagram to check it belongs to the account), `next_post` (the next Instagram post the workspace publishes) or `any` (every post). Backed by the new `/automations` endpoints, which need the opt-in API key scopes `automations:read` and `automations:write` (Settings > API, "Automations" on the key); the tools say so on a 403. Same on the companion server (`mcp.omnisocials.com`), where the OAuth session needs the `create_schedule_post` permission for the writes.
|
|
270
|
+
- **Added: `comment_dm_automation` on `create_post`, `create_and_publish_post` and `update_post`.** A comment-to-DM automation bound to the new post (Instagram must be a channel): `{ trigger: { keywords, match }, actions: { dm: { text }, public_reply: { enabled, variations } }, limits: { max_sends, stop_at } }`. The automation starts listening once the post is live on Instagram. On `update_post` the object replaces the post's automation and `null` removes it. The created automation comes back as `comment_dm_automation` on the post, and `get_post` shows it for a post that goes to Instagram. Same on the companion server.
|
|
271
|
+
|
|
272
|
+
### 1.41.0 (not published; included in 1.46.0)
|
|
273
|
+
|
|
274
|
+
- **Added: image fit per channel (`media_fit`).** A feed image whose ratio is outside what a channel shows uncropped is handled per channel at create time. Instagram shows feed images between 4:5 and 1.91:1: a 9:16 (story-style) or very wide image posted to the Instagram feed is now cropped to 4:5 / 1.91:1 around the subject by default, because Instagram cut it to that window anyway (a blind centre crop) and Meta cannot boost a feed post outside it. Pinterest keeps a Pin taller than 2:3 and reports it. `create_post`, `create_and_publish_post` and `update_post` take `media_fit: { instagram: "auto" | "crop" | "keep", pinterest: ... }`; every decision comes back as a `warnings` entry with code `image_fit` (`action` cropped / kept / crop_failed, the crop's url, and how to undo it). The crop is a separate file for that channel only; `default` and the other channels keep the original; stories, reels and videos are never cropped. On `update_post`, `media_fit` alone re-decides the stored images (`keep` gives the original back). Same on the companion server (`mcp.omnisocials.com`).
|
|
275
|
+
- **Changed: `check_media_compatibility` reports image fit.** For an image whose size is known (a library file of the workspace) the answer lists, per connected channel with a rule, whether its feed shows the image uncropped and what a post does by default, so an assistant can ask "crop to 4:5 or keep the original?" before it posts. Media items (`list_media`, `get_media`, uploads) now carry `width` and `height`.
|
|
276
|
+
|
|
277
|
+
### 1.40.0 (not published; included in 1.46.0)
|
|
278
|
+
|
|
279
|
+
- **Changed: the MCP SDK dependency moves to 1.32.1.** GHSA-6qxp-vccf-f47h (CVE-2026-104850) is in the SDK's OAuth client, which let an MCP server name the authorization server that received the client's credentials. This package is a server and talks to OmniSocials with an API key, so it was not affected; the move clears the advisory for everyone who installs it.
|
|
280
|
+
|
|
281
|
+
### 1.39.1 (2026-10-06, not published; included in 1.46.0)
|
|
282
|
+
|
|
283
|
+
- **Changed: `list_pinterest_products` names an account without a catalog.** Pinterest answers HTTP 409 ("Can't access this feature without an existing catalog") when the connection has catalog access but the account has no catalog yet. The API now returns `no_catalog: true` with empty `products` and `product_groups` instead of a `platform_error`, and the tool says where the user adds products (https://www.pinterest.com/business/catalogs/). Seen live on 2026-10-06, the first day of the feature.
|
|
284
|
+
|
|
232
285
|
### 1.39.0 (2026-10-05)
|
|
233
286
|
|
|
234
287
|
- **Added: tag products on a Pinterest Pin.** `pinterest.product_tags` on `create_post`, `create_and_publish_post` and `update_post` takes up to 24 product Pins of the connected Pinterest account (Pin IDs or Pin links), so people can shop the items in the image. Pinterest accepts a product only when its Pin is public, belongs to the same account and links to a website that account claimed; products of other merchants cannot be tagged. The tags are added right after the Pin is published. A product that Pinterest refuses never fails the post: the other products are still tagged, and `get_post` shows the outcome (`product_tags_result`, with Pinterest's reason for each product that was not tagged).
|
|
@@ -365,7 +418,7 @@ Full API docs: [docs.omnisocials.com](https://docs.omnisocials.com)
|
|
|
365
418
|
### 1.16.0
|
|
366
419
|
|
|
367
420
|
- **Hashtag sets:** save reusable, named groups of hashtags per workspace and apply one to a new post in a single call. Four new tools: `list_hashtag_sets`, `create_hashtag_set`, `update_hashtag_set`, `delete_hashtag_set`.
|
|
368
|
-
- `create_post` / `create_and_publish_post` accept `hashtag_set`, `hashtag_placement` (`caption_append` default, or `first_comment`), and `hashtag_platforms`. The set expands server-side once at create time — editing a set later never changes existing posts; duplicate tags are skipped; Instagram's
|
|
421
|
+
- `create_post` / `create_and_publish_post` accept `hashtag_set`, `hashtag_placement` (`caption_append` default, or `first_comment`), and `hashtag_platforms`. The set expands server-side once at create time — editing a set later never changes existing posts; duplicate tags are skipped; Instagram's 5-hashtag cap fails fast with `hashtag_limit_exceeded`.
|
|
369
422
|
- Same tools on the companion server (`mcp.omnisocials.com`).
|
|
370
423
|
|
|
371
424
|
### 1.15.0
|
package/build/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Account, AnalyticsOverview, AccountAnalyticsEntry, ApiPost, ApiResponse, ApiVideoCover, AudioSearchResult, BestTimesResult, HashtagSet, LocationSearchResult, PinterestProductCheck, PinterestProductsResult, MediaCheckResult, MediaCompatibility, MediaFolder, MediaItem, PdfUploadResult, PostAnalytics, PublishPostResult, RecentPlatformPost, RetryPostResult, ApprovePostResult, RejectPostResult, PostApproval, InboxConversation, InboxDeleteResult, InboxMessage, InboxNextUnanswered, Webhook } from "./types.js";
|
|
1
|
+
import type { Account, AnalyticsOverview, AccountAnalyticsEntry, ApiPost, ApiResponse, ApiVideoCover, AudioSearchResult, Automation, AutomationEvent, AutomationEventsResult, AutomationInput, BestTimesResult, CommentDmAutomationInput, HashtagSet, LocationSearchResult, PinterestProductCheck, PinterestProductsResult, MediaCheckResult, MediaCompatibility, MediaFolder, MediaItem, PdfUploadResult, PostAnalytics, PublishPostResult, RecentPlatformPost, RetryPostResult, ApprovePostResult, RejectPostResult, PostApproval, 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;
|
|
@@ -44,10 +44,27 @@ export interface XThreadPartInput {
|
|
|
44
44
|
* Entries may be `{ url, alt }` to attach alt text (photos/GIFs only). */
|
|
45
45
|
media_urls?: MediaUrlEntry[];
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* A poll on an X, Threads or Mastodon post (the post text is the question).
|
|
49
|
+
* X: 2 to 4 options of up to 25 characters, duration_minutes 5 to 10080
|
|
50
|
+
* (default 1440). Threads: 2 to 4 options of 1 to 25 characters, closed by
|
|
51
|
+
* Threads after 24 hours. Mastodon: options and expires_in (seconds) within
|
|
52
|
+
* the instance's limits, plus multiple and hide_totals. No media together
|
|
53
|
+
* with a poll; on a thread the poll goes on the first part. On update,
|
|
54
|
+
* `null` removes the poll.
|
|
55
|
+
*/
|
|
56
|
+
export interface ChannelPollInput {
|
|
57
|
+
options: string[];
|
|
58
|
+
duration_minutes?: number;
|
|
59
|
+
expires_in?: number;
|
|
60
|
+
multiple?: boolean;
|
|
61
|
+
hide_totals?: boolean;
|
|
62
|
+
}
|
|
47
63
|
export interface XPostOptions {
|
|
48
64
|
reply_settings?: "" | "following" | "mentionedUsers";
|
|
49
65
|
paid_partnership?: boolean;
|
|
50
66
|
made_with_ai?: boolean;
|
|
67
|
+
poll?: ChannelPollInput | null;
|
|
51
68
|
/** Provide 2–25 parts to publish as a thread. Omit for a single tweet. */
|
|
52
69
|
thread_parts?: XThreadPartInput[];
|
|
53
70
|
}
|
|
@@ -93,6 +110,7 @@ export interface MastodonThreadPartInput {
|
|
|
93
110
|
media_urls?: MediaUrlEntry[];
|
|
94
111
|
}
|
|
95
112
|
export interface MastodonPostOptions {
|
|
113
|
+
poll?: ChannelPollInput | null;
|
|
96
114
|
/** Provide 2–25 parts to publish as a thread. Omit for a single status. */
|
|
97
115
|
thread_parts?: MastodonThreadPartInput[];
|
|
98
116
|
}
|
|
@@ -101,8 +119,96 @@ export interface MastodonPostOptions {
|
|
|
101
119
|
* single status); `undefined` leaves the existing thread untouched.
|
|
102
120
|
*/
|
|
103
121
|
export interface MastodonPostOptionsUpdate {
|
|
122
|
+
poll?: ChannelPollInput | null;
|
|
104
123
|
thread_parts?: MastodonThreadPartInput[] | null;
|
|
105
124
|
}
|
|
125
|
+
export interface TelegramButtonInput {
|
|
126
|
+
/** Button label, 1 to 64 characters. */
|
|
127
|
+
text: string;
|
|
128
|
+
/** https://, http:// or tg:// link. */
|
|
129
|
+
url: string;
|
|
130
|
+
}
|
|
131
|
+
export interface TelegramPollInput {
|
|
132
|
+
/** 2 to 12 answer options of up to 100 characters, all different. */
|
|
133
|
+
options: string[];
|
|
134
|
+
/** Seconds the poll stays open (5 to 2628000). Omit to keep it open. */
|
|
135
|
+
open_period?: number;
|
|
136
|
+
/** Allow several answers per person. */
|
|
137
|
+
multiple?: boolean;
|
|
138
|
+
}
|
|
139
|
+
export interface TelegramPostOptions {
|
|
140
|
+
silent?: boolean | null;
|
|
141
|
+
link_preview?: boolean | null;
|
|
142
|
+
pin?: boolean | null;
|
|
143
|
+
protect_content?: boolean | null;
|
|
144
|
+
spoiler?: boolean | null;
|
|
145
|
+
caption_above_media?: boolean | null;
|
|
146
|
+
/** Up to 3 link buttons, one per row. Not on an album or a poll. */
|
|
147
|
+
buttons?: TelegramButtonInput[] | null;
|
|
148
|
+
/** A native poll; the post text is the question (max 300 characters). */
|
|
149
|
+
poll?: TelegramPollInput | null;
|
|
150
|
+
/** A reply under the post, sent right after it is published. */
|
|
151
|
+
first_comment?: string;
|
|
152
|
+
}
|
|
153
|
+
/** What GET /posts/{id} returns under `telegram` once the post went out. */
|
|
154
|
+
export interface TelegramPostResult {
|
|
155
|
+
chat_id: string | null;
|
|
156
|
+
message_ids: string[];
|
|
157
|
+
pinned: boolean;
|
|
158
|
+
pin_error?: string;
|
|
159
|
+
text_message_id?: string;
|
|
160
|
+
link_public: string | null;
|
|
161
|
+
link_private: string | null;
|
|
162
|
+
sent_at: string | null;
|
|
163
|
+
}
|
|
164
|
+
export interface DiscordButtonInput {
|
|
165
|
+
/** Button label, 1 to 80 characters. */
|
|
166
|
+
text: string;
|
|
167
|
+
/** https:// or http:// link. */
|
|
168
|
+
url: string;
|
|
169
|
+
}
|
|
170
|
+
export interface DiscordPollInput {
|
|
171
|
+
/** 2 to 10 answer options of up to 55 characters, all different. */
|
|
172
|
+
options: string[];
|
|
173
|
+
/** Hours the poll stays open (1 to 768). Default 24. */
|
|
174
|
+
duration_hours?: number;
|
|
175
|
+
/** Allow several answers per person. */
|
|
176
|
+
multiple?: boolean;
|
|
177
|
+
}
|
|
178
|
+
export interface DiscordPostOptions {
|
|
179
|
+
/** Send without a push or desktop notification (@silent). */
|
|
180
|
+
silent?: boolean | null;
|
|
181
|
+
/** false hides the previews of the links in the text. */
|
|
182
|
+
link_preview?: boolean | null;
|
|
183
|
+
/** true lets @everyone, @here, role and user mentions ping. Off by default. */
|
|
184
|
+
allow_mentions?: boolean | null;
|
|
185
|
+
/** Blur every image and video until someone clicks it. */
|
|
186
|
+
spoiler?: boolean | null;
|
|
187
|
+
/** Post title in a forum or media channel (required there), max 100 characters. */
|
|
188
|
+
thread_name?: string | null;
|
|
189
|
+
/** Up to 5 link buttons in one row. Not with a poll. */
|
|
190
|
+
buttons?: DiscordButtonInput[] | null;
|
|
191
|
+
/** A native poll; the post text is the question (max 300 characters). */
|
|
192
|
+
poll?: DiscordPollInput | null;
|
|
193
|
+
}
|
|
194
|
+
/** What GET /posts/{id} returns under `discord` once the message went out. */
|
|
195
|
+
export interface DiscordPostResult {
|
|
196
|
+
webhook_id: string | null;
|
|
197
|
+
guild_id: string | null;
|
|
198
|
+
channel_id: string | null;
|
|
199
|
+
/** The forum post the message opened, in a forum or media channel. */
|
|
200
|
+
thread_id: string | null;
|
|
201
|
+
message_id: string | null;
|
|
202
|
+
/** discord.com/channels/<server>/<channel or thread>/<message>. */
|
|
203
|
+
url: string | null;
|
|
204
|
+
sent_at: string | null;
|
|
205
|
+
/** Media sent as a link in the text (videos and GIFs over 20 MB). */
|
|
206
|
+
video_links: string[];
|
|
207
|
+
/** A second message with the media links, when they did not fit in the text. */
|
|
208
|
+
link_message_id: string | null;
|
|
209
|
+
/** Codes such as "media_sent_as_links" or "links_sent_as_follow_up". */
|
|
210
|
+
warnings: string[];
|
|
211
|
+
}
|
|
106
212
|
export interface ThreadsThreadPartInput {
|
|
107
213
|
/** Part text, 1 to 500 characters. */
|
|
108
214
|
text: string;
|
|
@@ -114,6 +220,7 @@ export interface ThreadsThreadPartInput {
|
|
|
114
220
|
media_urls?: MediaUrlEntry[];
|
|
115
221
|
}
|
|
116
222
|
export interface ThreadsPostOptions {
|
|
223
|
+
poll?: ChannelPollInput | null;
|
|
117
224
|
/** Provide 2 to 25 parts to publish as a thread. Omit for a single post.
|
|
118
225
|
* When set, the Threads caption is taken from part 1. */
|
|
119
226
|
thread_parts?: ThreadsThreadPartInput[];
|
|
@@ -128,6 +235,7 @@ export interface ThreadsPostOptions {
|
|
|
128
235
|
* single post); `undefined` leaves the existing thread untouched.
|
|
129
236
|
*/
|
|
130
237
|
export interface ThreadsPostOptionsUpdate {
|
|
238
|
+
poll?: ChannelPollInput | null;
|
|
131
239
|
thread_parts?: ThreadsThreadPartInput[] | null;
|
|
132
240
|
/** New Threads location id, or null to remove the tag. Omit to leave the
|
|
133
241
|
* existing tag untouched. */
|
|
@@ -152,6 +260,10 @@ export interface CreatePostInput {
|
|
|
152
260
|
content: string | Record<string, string>;
|
|
153
261
|
channels?: string[];
|
|
154
262
|
scheduled_at?: string;
|
|
263
|
+
/** With scheduled_at: keep the time but store the post as a draft (planned draft). */
|
|
264
|
+
draft?: boolean;
|
|
265
|
+
/** Image fit per channel: { instagram: "auto" | "crop" | "keep" }. */
|
|
266
|
+
media_fit?: Record<string, "auto" | "crop" | "keep">;
|
|
155
267
|
media_ids?: MediaIdEntry[] | Record<string, MediaIdEntry[]>;
|
|
156
268
|
media_urls?: MediaUrlEntry[] | Record<string, MediaUrlEntry[]>;
|
|
157
269
|
type?: string;
|
|
@@ -183,11 +295,15 @@ export interface CreatePostInput {
|
|
|
183
295
|
mastodon?: MastodonPostOptions;
|
|
184
296
|
threads?: ThreadsPostOptions;
|
|
185
297
|
google_business?: Record<string, unknown>;
|
|
298
|
+
telegram?: TelegramPostOptions | null;
|
|
299
|
+
discord?: DiscordPostOptions | null;
|
|
186
300
|
hashtag_set?: string;
|
|
187
301
|
hashtag_placement?: "caption_append" | "first_comment";
|
|
188
302
|
hashtag_platforms?: string[];
|
|
189
303
|
/** Route the post through a saved approval workflow (GET /approval-workflows). */
|
|
190
304
|
approval_workflow_id?: string;
|
|
305
|
+
/** Comment-to-DM automations bound to this post (Instagram or Facebook must be a channel). `null` removes them on update. */
|
|
306
|
+
comment_dm_automation?: CommentDmAutomationInput | null;
|
|
191
307
|
}
|
|
192
308
|
export interface ApprovalWorkflowApprover {
|
|
193
309
|
id: string;
|
|
@@ -234,6 +350,8 @@ export declare class OmniSocialsClient {
|
|
|
234
350
|
updatePost(id: string, data: {
|
|
235
351
|
content?: string | Record<string, string>;
|
|
236
352
|
scheduled_at?: string;
|
|
353
|
+
/** false = schedule a planned draft at its stored time; true = unschedule, keep the time. */
|
|
354
|
+
draft?: boolean;
|
|
237
355
|
channels?: string[];
|
|
238
356
|
media_ids?: MediaIdEntry[] | Record<string, MediaIdEntry[]>;
|
|
239
357
|
media_urls?: MediaUrlEntry[] | Record<string, MediaUrlEntry[]>;
|
|
@@ -258,12 +376,15 @@ export declare class OmniSocialsClient {
|
|
|
258
376
|
linkedin?: Record<string, unknown>;
|
|
259
377
|
linkedin_page?: Record<string, unknown>;
|
|
260
378
|
linkedin_poll?: LinkedInPollInput | null;
|
|
379
|
+
comment_dm_automation?: CommentDmAutomationInput | null;
|
|
261
380
|
tiktok?: Record<string, unknown>;
|
|
262
381
|
x?: XPostOptionsUpdate;
|
|
263
382
|
bluesky?: BlueskyPostOptionsUpdate;
|
|
264
383
|
mastodon?: MastodonPostOptionsUpdate;
|
|
265
384
|
threads?: ThreadsPostOptionsUpdate;
|
|
266
385
|
google_business?: Record<string, unknown>;
|
|
386
|
+
telegram?: TelegramPostOptions | null;
|
|
387
|
+
discord?: DiscordPostOptions | null;
|
|
267
388
|
}): Promise<ApiResponse<ApiPost>>;
|
|
268
389
|
deletePost(id: string): Promise<ApiResponse<unknown>>;
|
|
269
390
|
publishPost(id: string): Promise<ApiResponse<PublishPostResult>>;
|
|
@@ -343,6 +464,20 @@ export declare class OmniSocialsClient {
|
|
|
343
464
|
hashtags?: string[];
|
|
344
465
|
}): Promise<ApiResponse<HashtagSet>>;
|
|
345
466
|
deleteHashtagSet(id: string): Promise<ApiResponse<unknown>>;
|
|
467
|
+
listAutomations(params?: {
|
|
468
|
+
status?: string;
|
|
469
|
+
post_id?: string;
|
|
470
|
+
platform?: string;
|
|
471
|
+
}): Promise<ApiResponse<Automation[]>>;
|
|
472
|
+
getAutomation(id: string): Promise<ApiResponse<Automation>>;
|
|
473
|
+
createAutomation(data: AutomationInput): Promise<ApiResponse<Automation>>;
|
|
474
|
+
updateAutomation(id: string, data: AutomationInput): Promise<ApiResponse<Automation>>;
|
|
475
|
+
deleteAutomation(id: string): Promise<ApiResponse<unknown>>;
|
|
476
|
+
listAutomationEvents(id: string, params?: {
|
|
477
|
+
outcome?: string;
|
|
478
|
+
limit?: number;
|
|
479
|
+
offset?: number;
|
|
480
|
+
}): Promise<ApiResponse<AutomationEvent[]> & Partial<Omit<AutomationEventsResult, "data">>>;
|
|
346
481
|
listAccounts(): Promise<ApiResponse<Account[]> & {
|
|
347
482
|
workspace_id?: number | string | null;
|
|
348
483
|
workspace_name?: string | null;
|
package/build/client.js
CHANGED
|
@@ -198,6 +198,13 @@ export function metricRows(m) {
|
|
|
198
198
|
}
|
|
199
199
|
return rows;
|
|
200
200
|
}
|
|
201
|
+
// Query params as strings, without the undefined ones (request() takes
|
|
202
|
+
// Record<string, string>).
|
|
203
|
+
const queryString = (params) => params
|
|
204
|
+
? Object.fromEntries(Object.entries(params)
|
|
205
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
206
|
+
.map(([k, v]) => [k, String(v)]))
|
|
207
|
+
: undefined;
|
|
201
208
|
export class OmniSocialsClient {
|
|
202
209
|
baseUrl;
|
|
203
210
|
apiKey;
|
|
@@ -391,6 +398,26 @@ export class OmniSocialsClient {
|
|
|
391
398
|
async deleteHashtagSet(id) {
|
|
392
399
|
return this.request("DELETE", `/hashtag-sets/${id}`);
|
|
393
400
|
}
|
|
401
|
+
// Comment-to-DM automations (opt-in scopes automations:read / automations:write)
|
|
402
|
+
async listAutomations(params) {
|
|
403
|
+
return this.request("GET", "/automations", undefined, queryString(params));
|
|
404
|
+
}
|
|
405
|
+
async getAutomation(id) {
|
|
406
|
+
return this.request("GET", `/automations/${id}`);
|
|
407
|
+
}
|
|
408
|
+
async createAutomation(data) {
|
|
409
|
+
return this.request("POST", "/automations", data);
|
|
410
|
+
}
|
|
411
|
+
async updateAutomation(id, data) {
|
|
412
|
+
return this.request("PATCH", `/automations/${id}`, data);
|
|
413
|
+
}
|
|
414
|
+
async deleteAutomation(id) {
|
|
415
|
+
return this.request("DELETE", `/automations/${id}`);
|
|
416
|
+
}
|
|
417
|
+
// The events response is not the plain list envelope: { data, counts, pagination }.
|
|
418
|
+
async listAutomationEvents(id, params) {
|
|
419
|
+
return this.request("GET", `/automations/${id}/events`, undefined, queryString(params));
|
|
420
|
+
}
|
|
394
421
|
// Accounts. Alongside `data`, the list response carries the workspace's
|
|
395
422
|
// identity top-level (backend/routes/api/v1/accounts.js) — the workspaces
|
|
396
423
|
// tool reads it to label multi-workspace setups.
|
package/build/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { registerAccountTools } from "./tools/accounts.js";
|
|
|
9
9
|
import { registerAnalyticsTools } from "./tools/analytics.js";
|
|
10
10
|
import { registerWebhookTools } from "./tools/webhooks.js";
|
|
11
11
|
import { registerHashtagSetTools } from "./tools/hashtag-sets.js";
|
|
12
|
+
import { registerAutomationTools } from "./tools/automations.js";
|
|
12
13
|
import { registerInboxTools } from "./tools/inbox.js";
|
|
13
14
|
import { registerWorkspaceTools } from "./tools/workspaces.js";
|
|
14
15
|
const apiKeyEnv = process.env.OMNISOCIALS_API_KEY;
|
|
@@ -29,7 +30,7 @@ const sessionState = { activeIndex: 0 };
|
|
|
29
30
|
const getActiveClient = () => workspaceClients[sessionState.activeIndex].client;
|
|
30
31
|
const server = new McpServer({
|
|
31
32
|
name: "OmniSocials",
|
|
32
|
-
version: "1.
|
|
33
|
+
version: "1.46.0",
|
|
33
34
|
});
|
|
34
35
|
// Register all tools - pass getter function so tools always use the active workspace's client
|
|
35
36
|
registerPostTools(server, getActiveClient);
|
|
@@ -38,6 +39,7 @@ registerAccountTools(server, getActiveClient);
|
|
|
38
39
|
registerAnalyticsTools(server, getActiveClient);
|
|
39
40
|
registerWebhookTools(server, getActiveClient);
|
|
40
41
|
registerHashtagSetTools(server, getActiveClient);
|
|
42
|
+
registerAutomationTools(server, getActiveClient);
|
|
41
43
|
registerInboxTools(server, getActiveClient);
|
|
42
44
|
registerWorkspaceTools(server, workspaceClients, sessionState);
|
|
43
45
|
// Register prompts
|
package/build/metrics.js
CHANGED
|
@@ -57,6 +57,14 @@ export function getEngagement(platform, metrics) {
|
|
|
57
57
|
return sum("likes", "comments", "reposts", "quotes", "bookmarks");
|
|
58
58
|
case "mastodon":
|
|
59
59
|
return sum("likes", "comments", "reposts", "quotes");
|
|
60
|
+
case "telegram":
|
|
61
|
+
// Reactions are the only post number a bot gets (message_reaction_count),
|
|
62
|
+
// stored as `likes` (total) next to `reactions` (per emoji).
|
|
63
|
+
return sum("likes");
|
|
64
|
+
case "discord":
|
|
65
|
+
// A webhook reads back its own message: reactions only (2026-10-22),
|
|
66
|
+
// stored the Telegram way, `likes` (total) next to `reactions` (per emoji).
|
|
67
|
+
return sum("likes");
|
|
60
68
|
case "reddit":
|
|
61
69
|
// likes == upvote score; shares == crossposts.
|
|
62
70
|
return sum("likes", "comments", "shares");
|
|
@@ -177,6 +185,28 @@ export const UNSUPPORTED_POST_METRICS = {
|
|
|
177
185
|
video_views: "Mastodon does not report video views.",
|
|
178
186
|
engagement_rate: "Engagement rate needs impressions, which Mastodon does not report.",
|
|
179
187
|
},
|
|
188
|
+
telegram: {
|
|
189
|
+
impressions: "Telegram does not give view counts to bots. Open the post in Telegram to see its views.",
|
|
190
|
+
reach: "Telegram does not report reach to bots.",
|
|
191
|
+
comments: "Telegram does not report comment counts to bots.",
|
|
192
|
+
shares: "Telegram does not report forwards to bots.",
|
|
193
|
+
saves: "Telegram does not report saves.",
|
|
194
|
+
clicks: "Telegram does not report clicks.",
|
|
195
|
+
link_clicks: "Telegram does not report clicks.",
|
|
196
|
+
video_views: "Telegram does not give view counts to bots.",
|
|
197
|
+
engagement_rate: "Engagement rate needs views, which Telegram does not give to bots.",
|
|
198
|
+
},
|
|
199
|
+
discord: {
|
|
200
|
+
impressions: "Discord does not report views. Open the message in Discord to see who reacted.",
|
|
201
|
+
reach: "Discord does not report reach.",
|
|
202
|
+
comments: "Discord does not report replies to a webhook message.",
|
|
203
|
+
shares: "Discord does not report shares.",
|
|
204
|
+
saves: "Discord does not report saves.",
|
|
205
|
+
clicks: "Discord does not report clicks.",
|
|
206
|
+
link_clicks: "Discord does not report clicks.",
|
|
207
|
+
video_views: "Discord does not report video views.",
|
|
208
|
+
engagement_rate: "Engagement rate needs views, which Discord does not report.",
|
|
209
|
+
},
|
|
180
210
|
google_business: {
|
|
181
211
|
impressions: "Google discontinued per-post metrics for Business Profiles in 2023.",
|
|
182
212
|
reach: "Google discontinued per-post metrics for Business Profiles in 2023.",
|
package/build/tools/accounts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { fetchImageAsBase64, capitalize } from "../client.js";
|
|
3
3
|
export function registerAccountTools(server, getClient) {
|
|
4
|
-
server.tool("list_accounts", "List all connected social media accounts in the workspace. Each account includes its platform, display name, channel ID (used for create_post), supported content_types (post, story, reel), and `needs_reconnect` (true when the OAuth token has been revoked/expired and the user must reconnect before posts can succeed; also reflected in `status` as `needs_reconnect` instead of `active`). Pinterest accounts include a `boards` array with `{id, name}` — use the board `id` as `board_id` when creating Pinterest posts. X accounts with Premium include `platform_details` with `subscription_type` (e.g. \"Premium\", \"PremiumPlus\"). LinkedIn appears as two independent platforms: `linkedin` for a personal profile and `linkedin_page` for a company page. A workspace can have both connected at the same time and post to each separately. Call this to help users pick which platforms to post to.", {}, { title: "List Connected Accounts", readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async () => {
|
|
4
|
+
server.tool("list_accounts", "List all connected social media accounts in the workspace. Each account includes its platform, display name, channel ID (used for create_post), supported content_types (post, story, reel), and `needs_reconnect` (true when the OAuth token has been revoked/expired and the user must reconnect before posts can succeed; also reflected in `status` as `needs_reconnect` instead of `active`). Pinterest accounts include a `boards` array with `{id, name}` — use the board `id` as `board_id` when creating Pinterest posts. X accounts with Premium include `platform_details` with `subscription_type` (e.g. \"Premium\", \"PremiumPlus\"). Telegram accounts are a channel or group the OmniSocials bot posts to; `platform_details` carries `chat_type` (channel, supergroup or group), `subscribers` and `url`. Discord accounts are one Discord channel the workspace posts to through its webhook (posts show the webhook's name and picture); `platform_details` carries `guild_id`, `guild_name` (the server, when connected with Continue with Discord), `channel_id`, `channel_name` (read by the OmniSocials bot or given by the person, else null), `channel_type` (text, voice, announcement, stage, forum or media; a forum or media channel needs `discord.thread_name` on every post), `webhook_name`, `connect_method` (oauth or url) and `url`. LinkedIn appears as two independent platforms: `linkedin` for a personal profile and `linkedin_page` for a company page. A workspace can have both connected at the same time and post to each separately. Call this to help users pick which platforms to post to.", {}, { title: "List Connected Accounts", readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async () => {
|
|
5
5
|
const result = await getClient().listAccounts();
|
|
6
6
|
if (result.error) {
|
|
7
7
|
return {
|
|
@@ -20,13 +20,21 @@ export function registerAccountTools(server, getClient) {
|
|
|
20
20
|
md += `|---|----------|---------|---------------|------------|--------|\n`;
|
|
21
21
|
for (let i = 0; i < accounts.length; i++) {
|
|
22
22
|
const a = accounts[i];
|
|
23
|
-
|
|
23
|
+
// Discord: the server, then the channel the bot read or the person gave.
|
|
24
|
+
const channel = a.platform === "discord" && a.platform_details?.channel_name ? ` · #${a.platform_details.channel_name}` : "";
|
|
25
|
+
const name = `${a.username ? `@${a.username}` : a.display_name || "—"}${channel}`;
|
|
24
26
|
const types = (a.content_types || []).join(", ");
|
|
25
27
|
const platform = capitalize(a.platform);
|
|
26
28
|
const subType = a.platform_details?.subscription_type;
|
|
29
|
+
const chatType = a.platform === "telegram" ? a.platform_details?.chat_type : null;
|
|
30
|
+
const guildName = a.platform === "discord" ? a.platform_details?.guild_name : null;
|
|
27
31
|
const sub = subType === "Premium" || subType === "PremiumPlus"
|
|
28
32
|
? ` (${subType})`
|
|
29
|
-
:
|
|
33
|
+
: chatType
|
|
34
|
+
? ` (${chatType === "channel" ? "channel" : "group"})`
|
|
35
|
+
: guildName && guildName !== a.display_name
|
|
36
|
+
? ` (${guildName})`
|
|
37
|
+
: "";
|
|
30
38
|
const statusCell = a.needs_reconnect
|
|
31
39
|
? `⚠️ needs_reconnect`
|
|
32
40
|
: "active";
|
|
@@ -93,6 +101,30 @@ export function registerAccountTools(server, getClient) {
|
|
|
93
101
|
if (getSubType === "Premium" || getSubType === "PremiumPlus") {
|
|
94
102
|
md += `| **Subscription** | ${getSubType} |\n`;
|
|
95
103
|
}
|
|
104
|
+
if (a.platform === "telegram" && a.platform_details) {
|
|
105
|
+
const d = a.platform_details;
|
|
106
|
+
if (d.chat_type)
|
|
107
|
+
md += `| **Chat type** | ${d.chat_type} |\n`;
|
|
108
|
+
if (typeof d.subscribers === "number")
|
|
109
|
+
md += `| **Subscribers** | ${d.subscribers.toLocaleString("en-US")} |\n`;
|
|
110
|
+
if (d.url)
|
|
111
|
+
md += `| **Link** | ${d.url} |\n`;
|
|
112
|
+
}
|
|
113
|
+
if (a.platform === "discord" && a.platform_details) {
|
|
114
|
+
const d = a.platform_details;
|
|
115
|
+
if (d.guild_name)
|
|
116
|
+
md += `| **Server** | ${d.guild_name} |\n`;
|
|
117
|
+
if (d.channel_name)
|
|
118
|
+
md += `| **Channel** | #${d.channel_name}${d.channel_type ? ` (${d.channel_type})` : ""} |\n`;
|
|
119
|
+
if (d.webhook_name)
|
|
120
|
+
md += `| **Posts as** | ${d.webhook_name} (the webhook's name) |\n`;
|
|
121
|
+
if (d.channel_id)
|
|
122
|
+
md += `| **Channel ID** | ${d.channel_id} |\n`;
|
|
123
|
+
if (d.connect_method)
|
|
124
|
+
md += `| **Connected with** | ${d.connect_method === "oauth" ? "Continue with Discord" : "a webhook URL"} |\n`;
|
|
125
|
+
if (d.url)
|
|
126
|
+
md += `| **Link** | ${d.url} |\n`;
|
|
127
|
+
}
|
|
96
128
|
if (a.boards?.length) {
|
|
97
129
|
md += `\n### Pinterest Boards\n\n`;
|
|
98
130
|
md += `| Board Name | Board ID |\n`;
|
package/build/tools/analytics.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { formatNumber, capitalize, metricRows, metricBreakdownRows, sortMetricLabels } from "../client.js";
|
|
3
3
|
import { getEngagement, getImpressions, getEngagementRate, MIN_IMPRESSIONS_FOR_ENGAGEMENT_RATE, } from "../metrics.js";
|
|
4
4
|
export function registerAnalyticsTools(server, getClient) {
|
|
5
|
-
server.tool("get_post_analytics", "Get analytics/statistics for a specific published post. Renders every metric the platform reported per platform: impressions, reach, views, saves, likes, comments, shares, clicks, link_clicks, engagements, and more. A missing key means the platform does not report it: reach is absent (not 0) on X, Pinterest, YouTube, Threads, Bluesky, Mastodon and Facebook (Meta retired Facebook post reach in June 2026 and its replacement metric returns no data; a Facebook row collected before 2026-09-27 may still carry reach: 0, treat it as absent); link_clicks (clicks on links in the post) exists for X posts under 30 days, LinkedIn profile, Facebook feed posts, Pinterest and Instagram stories, while clicks is the platform's broadest click count. TikTok videos also carry average_time_watched, full_video_watched_rate, total_time_watched, favorites and reach when the workspace enabled TikTok comments (Business API authorization).", {
|
|
5
|
+
server.tool("get_post_analytics", "Get analytics/statistics for a specific published post. Renders every metric the platform reported per platform: impressions, reach, views, saves, likes, comments, shares, clicks, link_clicks, engagements, and more. A missing key means the platform does not report it: reach is absent (not 0) on X, Pinterest, YouTube, Threads, Bluesky, Mastodon, Telegram, Discord and Facebook (Meta retired Facebook post reach in June 2026 and its replacement metric returns no data; a Facebook row collected before 2026-09-27 may still carry reach: 0, treat it as absent); link_clicks (clicks on links in the post) exists for X posts under 30 days, LinkedIn profile, Facebook feed posts, Pinterest and Instagram stories, while clicks is the platform's broadest click count. TikTok videos also carry average_time_watched, full_video_watched_rate, total_time_watched, favorites and reach when the workspace enabled TikTok comments (Business API authorization). Telegram gives bots no view counts: a Telegram post carries likes (all reactions), reactions (per emoji) and, for a poll, the votes. Discord reports the same for a webhook message: likes (all reactions), reactions (per emoji) and the poll votes, no views.", {
|
|
6
6
|
post_id: z.string().describe("The numeric OmniSocials post id (the `id` field from list_posts). NOT a platform post id such as a YouTube video id or tweet id."),
|
|
7
7
|
}, { title: "Get Post Analytics", readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async ({ post_id }) => {
|
|
8
8
|
const result = await getClient().getPostAnalytics(post_id);
|
|
@@ -310,7 +310,7 @@ export function registerAnalyticsTools(server, getClient) {
|
|
|
310
310
|
};
|
|
311
311
|
});
|
|
312
312
|
server.tool("get_best_times", "Get recommended posting times (day + hour) for one platform, computed from the workspace's own posting history: publish time × engagement of every post published there, recency-weighted and outlier-damped, bucketed in the user's timezone, and blended with when the account's followers are online when the platform provides that (Instagram, TikTok Business; `basis: own_data_and_audience`, response carries `audience_online`). Returns the top 3 recommended slots plus a per-day breakdown. When the workspace has fewer than 15 analyzed posts on the platform, the audience-online profile alone is used (`basis: audience`) or industry-average defaults are returned (`basis: defaults`) with how many more posts unlock personalized recommendations — tell the user that so the numbers aren't mistaken for their own audience data. Use this before scheduling when the user asks 'when should I post?' or hasn't specified a time. Requires the analytics:read scope.", {
|
|
313
|
-
platform: z.string().describe("Platform identifier, e.g. instagram, tiktok, linkedin, linkedin_page, x, facebook, youtube, pinterest, threads, bluesky, mastodon, google_business"),
|
|
313
|
+
platform: z.string().describe("Platform identifier, e.g. instagram, tiktok, linkedin, linkedin_page, x, facebook, youtube, pinterest, threads, bluesky, mastodon, google_business, telegram, discord"),
|
|
314
314
|
timezone: z.string().optional().describe("IANA timezone for the buckets (e.g. Europe/Amsterdam). Defaults to the account's timezone."),
|
|
315
315
|
}, { title: "Get Best Times to Post", readOnlyHint: true, destructiveHint: false, openWorldHint: false }, async (params) => {
|
|
316
316
|
const result = await getClient().getBestTimes(params);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { OmniSocialsClient } from "../client.js";
|
|
3
|
+
import type { Automation, AutomationInput } from "../types.js";
|
|
4
|
+
export declare const automationChannelLabel: (platform: string | null | undefined) => "Facebook Page" | "Instagram";
|
|
5
|
+
type Fields = {
|
|
6
|
+
platform?: "instagram" | "facebook";
|
|
7
|
+
name?: string;
|
|
8
|
+
keywords?: string[];
|
|
9
|
+
match?: "word" | "contains" | "any";
|
|
10
|
+
dm_text?: string;
|
|
11
|
+
public_replies?: string[];
|
|
12
|
+
triggers?: Array<"comment" | "dm" | "story_reply">;
|
|
13
|
+
scope?: "post" | "next_post" | "any";
|
|
14
|
+
post_id?: string;
|
|
15
|
+
instagram_media_id?: string;
|
|
16
|
+
facebook_post_id?: string;
|
|
17
|
+
max_sends?: number | null;
|
|
18
|
+
stop_at?: string | null;
|
|
19
|
+
};
|
|
20
|
+
export declare function buildAutomationBody(f: Fields, { create }: {
|
|
21
|
+
create: boolean;
|
|
22
|
+
}): AutomationInput;
|
|
23
|
+
export declare function describeAutomation(a: Automation): string;
|
|
24
|
+
export declare function registerAutomationTools(server: McpServer, getClient: () => OmniSocialsClient): void;
|
|
25
|
+
export {};
|