@omnisocials/mcp-server 1.20.0 → 1.21.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/build/client.d.ts +102 -100
- package/build/client.js +21 -4
- package/build/index.js +1 -1
- package/build/tools/accounts.js +2 -2
- package/build/tools/analytics.js +15 -8
- package/build/tools/hashtag-sets.js +11 -1
- package/build/tools/inbox.js +4 -3
- package/build/tools/media.js +19 -3
- package/build/tools/posts.js +198 -203
- package/build/tools/webhooks.js +21 -1
- package/build/types.d.ts +301 -31
- package/package.json +1 -1
package/build/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApiResponse, MediaItem, PdfUploadResult, InboxConversation, InboxMessage } from "./types.js";
|
|
1
|
+
import type { Account, AnalyticsOverview, AccountAnalyticsEntry, ApiPost, ApiResponse, AudioSearchResult, BestTimesResult, HashtagSet, LocationSearchResult, MediaCheckResult, MediaCompatibility, MediaFolder, MediaItem, PdfUploadResult, PostAnalytics, PublishPostResult, RecentPlatformPost, RetryPostResult, InboxConversation, InboxMessage, Webhook } from "./types.js";
|
|
2
2
|
export declare function fetchImageAsBase64(url: string): Promise<{
|
|
3
3
|
data: string;
|
|
4
4
|
mimeType: string;
|
|
@@ -100,6 +100,52 @@ export interface MastodonPostOptions {
|
|
|
100
100
|
export interface MastodonPostOptionsUpdate {
|
|
101
101
|
thread_parts?: MastodonThreadPartInput[] | null;
|
|
102
102
|
}
|
|
103
|
+
/** Non-sponsored LinkedIn poll: question + 2-4 options + duration. */
|
|
104
|
+
export interface LinkedInPollInput {
|
|
105
|
+
question: string;
|
|
106
|
+
options: string[];
|
|
107
|
+
duration: "ONE_DAY" | "THREE_DAYS" | "SEVEN_DAYS" | "FOURTEEN_DAYS";
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Request body for POST /posts/create. `createAndPublishPost` accepts the
|
|
111
|
+
* same shape minus `scheduled_at` (publishing is immediate).
|
|
112
|
+
*/
|
|
113
|
+
export interface CreatePostInput {
|
|
114
|
+
content: string | Record<string, string>;
|
|
115
|
+
channels?: string[];
|
|
116
|
+
scheduled_at?: string;
|
|
117
|
+
media_ids?: MediaIdEntry[] | Record<string, MediaIdEntry[]>;
|
|
118
|
+
media_urls?: MediaUrlEntry[] | Record<string, MediaUrlEntry[]>;
|
|
119
|
+
type?: string;
|
|
120
|
+
source?: string;
|
|
121
|
+
link_url?: string;
|
|
122
|
+
link_title?: string;
|
|
123
|
+
link_description?: string;
|
|
124
|
+
link_thumbnail_url?: string;
|
|
125
|
+
location_id?: string;
|
|
126
|
+
collaborators?: string[];
|
|
127
|
+
user_tags?: Array<{
|
|
128
|
+
username: string;
|
|
129
|
+
x: number;
|
|
130
|
+
y: number;
|
|
131
|
+
image_index?: number;
|
|
132
|
+
}>;
|
|
133
|
+
pinterest?: Record<string, unknown>;
|
|
134
|
+
youtube?: Record<string, unknown>;
|
|
135
|
+
instagram?: Record<string, unknown>;
|
|
136
|
+
facebook?: Record<string, unknown>;
|
|
137
|
+
linkedin?: Record<string, unknown>;
|
|
138
|
+
linkedin_page?: Record<string, unknown>;
|
|
139
|
+
linkedin_poll?: LinkedInPollInput | null;
|
|
140
|
+
tiktok?: Record<string, unknown>;
|
|
141
|
+
x?: XPostOptions;
|
|
142
|
+
bluesky?: BlueskyPostOptions;
|
|
143
|
+
mastodon?: MastodonPostOptions;
|
|
144
|
+
google_business?: Record<string, unknown>;
|
|
145
|
+
hashtag_set?: string;
|
|
146
|
+
hashtag_placement?: "caption_append" | "first_comment";
|
|
147
|
+
hashtag_platforms?: string[];
|
|
148
|
+
}
|
|
103
149
|
export declare class OmniSocialsClient {
|
|
104
150
|
private baseUrl;
|
|
105
151
|
private apiKey;
|
|
@@ -109,81 +155,20 @@ export declare class OmniSocialsClient {
|
|
|
109
155
|
status?: string;
|
|
110
156
|
limit?: string;
|
|
111
157
|
offset?: string;
|
|
112
|
-
}): Promise<ApiResponse<
|
|
113
|
-
getPost(id: string): Promise<ApiResponse<
|
|
158
|
+
}): Promise<ApiResponse<ApiPost[]>>;
|
|
159
|
+
getPost(id: string): Promise<ApiResponse<ApiPost>>;
|
|
114
160
|
getRecentPlatformPosts(params?: {
|
|
115
161
|
limit?: string;
|
|
116
162
|
platforms?: string;
|
|
117
|
-
}): Promise<ApiResponse<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
link_url?: string;
|
|
127
|
-
link_title?: string;
|
|
128
|
-
link_description?: string;
|
|
129
|
-
link_thumbnail_url?: string;
|
|
130
|
-
location_id?: string;
|
|
131
|
-
collaborators?: string[];
|
|
132
|
-
user_tags?: Array<{
|
|
133
|
-
username: string;
|
|
134
|
-
x: number;
|
|
135
|
-
y: number;
|
|
136
|
-
image_index?: number;
|
|
137
|
-
}>;
|
|
138
|
-
pinterest?: Record<string, unknown>;
|
|
139
|
-
youtube?: Record<string, unknown>;
|
|
140
|
-
instagram?: Record<string, unknown>;
|
|
141
|
-
facebook?: Record<string, unknown>;
|
|
142
|
-
linkedin?: Record<string, unknown>;
|
|
143
|
-
linkedin_page?: Record<string, unknown>;
|
|
144
|
-
tiktok?: Record<string, unknown>;
|
|
145
|
-
x?: XPostOptions;
|
|
146
|
-
bluesky?: BlueskyPostOptions;
|
|
147
|
-
mastodon?: MastodonPostOptions;
|
|
148
|
-
google_business?: Record<string, unknown>;
|
|
149
|
-
hashtag_set?: string;
|
|
150
|
-
hashtag_placement?: "caption_append" | "first_comment";
|
|
151
|
-
hashtag_platforms?: string[];
|
|
152
|
-
}): Promise<ApiResponse<unknown>>;
|
|
153
|
-
createAndPublishPost(data: {
|
|
154
|
-
content: string | Record<string, string>;
|
|
155
|
-
channels?: string[];
|
|
156
|
-
media_ids?: MediaIdEntry[] | Record<string, MediaIdEntry[]>;
|
|
157
|
-
media_urls?: MediaUrlEntry[] | Record<string, MediaUrlEntry[]>;
|
|
158
|
-
type?: string;
|
|
159
|
-
source?: string;
|
|
160
|
-
link_url?: string;
|
|
161
|
-
link_title?: string;
|
|
162
|
-
link_description?: string;
|
|
163
|
-
link_thumbnail_url?: string;
|
|
164
|
-
location_id?: string;
|
|
165
|
-
collaborators?: string[];
|
|
166
|
-
user_tags?: Array<{
|
|
167
|
-
username: string;
|
|
168
|
-
x: number;
|
|
169
|
-
y: number;
|
|
170
|
-
image_index?: number;
|
|
171
|
-
}>;
|
|
172
|
-
pinterest?: Record<string, unknown>;
|
|
173
|
-
youtube?: Record<string, unknown>;
|
|
174
|
-
instagram?: Record<string, unknown>;
|
|
175
|
-
facebook?: Record<string, unknown>;
|
|
176
|
-
linkedin?: Record<string, unknown>;
|
|
177
|
-
linkedin_page?: Record<string, unknown>;
|
|
178
|
-
tiktok?: Record<string, unknown>;
|
|
179
|
-
x?: XPostOptions;
|
|
180
|
-
bluesky?: BlueskyPostOptions;
|
|
181
|
-
mastodon?: MastodonPostOptions;
|
|
182
|
-
google_business?: Record<string, unknown>;
|
|
183
|
-
hashtag_set?: string;
|
|
184
|
-
hashtag_placement?: "caption_append" | "first_comment";
|
|
185
|
-
hashtag_platforms?: string[];
|
|
186
|
-
}): Promise<ApiResponse<unknown>>;
|
|
163
|
+
}): Promise<ApiResponse<RecentPlatformPost[]> & {
|
|
164
|
+
count?: number;
|
|
165
|
+
connected_platforms?: string[];
|
|
166
|
+
errors?: Record<string, string>;
|
|
167
|
+
note?: string;
|
|
168
|
+
current_date?: string;
|
|
169
|
+
}>;
|
|
170
|
+
createPost(data: CreatePostInput): Promise<ApiResponse<ApiPost>>;
|
|
171
|
+
createAndPublishPost(data: Omit<CreatePostInput, "scheduled_at">): Promise<ApiResponse<ApiPost>>;
|
|
187
172
|
updatePost(id: string, data: {
|
|
188
173
|
content?: string | Record<string, string>;
|
|
189
174
|
scheduled_at?: string;
|
|
@@ -205,31 +190,32 @@ export declare class OmniSocialsClient {
|
|
|
205
190
|
facebook?: Record<string, unknown>;
|
|
206
191
|
linkedin?: Record<string, unknown>;
|
|
207
192
|
linkedin_page?: Record<string, unknown>;
|
|
193
|
+
linkedin_poll?: LinkedInPollInput | null;
|
|
208
194
|
tiktok?: Record<string, unknown>;
|
|
209
195
|
x?: XPostOptionsUpdate;
|
|
210
196
|
bluesky?: BlueskyPostOptionsUpdate;
|
|
211
197
|
mastodon?: MastodonPostOptionsUpdate;
|
|
212
198
|
google_business?: Record<string, unknown>;
|
|
213
|
-
}): Promise<ApiResponse<
|
|
199
|
+
}): Promise<ApiResponse<ApiPost>>;
|
|
214
200
|
deletePost(id: string): Promise<ApiResponse<unknown>>;
|
|
215
|
-
publishPost(id: string): Promise<ApiResponse<
|
|
216
|
-
retryPost(id: string): Promise<ApiResponse<
|
|
217
|
-
searchLocations(query: string): Promise<
|
|
201
|
+
publishPost(id: string): Promise<ApiResponse<PublishPostResult>>;
|
|
202
|
+
retryPost(id: string): Promise<ApiResponse<RetryPostResult>>;
|
|
203
|
+
searchLocations(query: string): Promise<LocationSearchResult>;
|
|
218
204
|
validateLocation(id: string): Promise<ApiResponse<unknown>>;
|
|
219
|
-
searchInstagramAudio(query?: string, type?: "music" | "original_sound"): Promise<
|
|
205
|
+
searchInstagramAudio(query?: string, type?: "music" | "original_sound"): Promise<AudioSearchResult>;
|
|
220
206
|
listMedia(params?: {
|
|
221
207
|
limit?: string;
|
|
222
208
|
offset?: string;
|
|
223
209
|
search?: string;
|
|
224
210
|
folder_id?: string;
|
|
225
|
-
}): Promise<ApiResponse<
|
|
211
|
+
}): Promise<ApiResponse<MediaItem[]>>;
|
|
226
212
|
uploadMedia(data: {
|
|
227
213
|
url: string;
|
|
228
214
|
filename?: string;
|
|
229
215
|
name?: string;
|
|
230
216
|
folder?: string;
|
|
231
217
|
}): Promise<ApiResponse<MediaItem> & Partial<PdfUploadResult> & {
|
|
232
|
-
compatibility?:
|
|
218
|
+
compatibility?: MediaCompatibility;
|
|
233
219
|
message?: string;
|
|
234
220
|
}>;
|
|
235
221
|
uploadMediaFromBase64(data: {
|
|
@@ -239,7 +225,7 @@ export declare class OmniSocialsClient {
|
|
|
239
225
|
name?: string;
|
|
240
226
|
folder?: string;
|
|
241
227
|
}): Promise<ApiResponse<MediaItem> & Partial<PdfUploadResult> & {
|
|
242
|
-
compatibility?:
|
|
228
|
+
compatibility?: MediaCompatibility;
|
|
243
229
|
message?: string;
|
|
244
230
|
}>;
|
|
245
231
|
/**
|
|
@@ -252,57 +238,73 @@ export declare class OmniSocialsClient {
|
|
|
252
238
|
media_id?: string;
|
|
253
239
|
size_bytes?: number;
|
|
254
240
|
mime?: string;
|
|
255
|
-
}): Promise<
|
|
241
|
+
}): Promise<MediaCheckResult>;
|
|
256
242
|
deleteMedia(id: string): Promise<ApiResponse<unknown>>;
|
|
257
243
|
updateMedia(id: string, data: {
|
|
258
244
|
name?: string;
|
|
259
245
|
folder_id?: string | null;
|
|
260
|
-
}): Promise<ApiResponse<
|
|
261
|
-
listFolders(): Promise<ApiResponse<
|
|
246
|
+
}): Promise<ApiResponse<MediaItem>>;
|
|
247
|
+
listFolders(): Promise<ApiResponse<MediaFolder[]>>;
|
|
262
248
|
createFolder(data: {
|
|
263
249
|
name: string;
|
|
264
250
|
parent_id?: string;
|
|
265
|
-
}): Promise<ApiResponse<
|
|
266
|
-
listHashtagSets(): Promise<ApiResponse<
|
|
251
|
+
}): Promise<ApiResponse<MediaFolder>>;
|
|
252
|
+
listHashtagSets(): Promise<ApiResponse<HashtagSet[]>>;
|
|
267
253
|
createHashtagSet(data: {
|
|
268
254
|
name: string;
|
|
269
255
|
hashtags: string[];
|
|
270
|
-
}): Promise<ApiResponse<
|
|
256
|
+
}): Promise<ApiResponse<HashtagSet>>;
|
|
271
257
|
updateHashtagSet(id: string, data: {
|
|
272
258
|
name?: string;
|
|
273
259
|
hashtags?: string[];
|
|
274
|
-
}): Promise<ApiResponse<
|
|
260
|
+
}): Promise<ApiResponse<HashtagSet>>;
|
|
275
261
|
deleteHashtagSet(id: string): Promise<ApiResponse<unknown>>;
|
|
276
|
-
listAccounts(): Promise<ApiResponse<
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
262
|
+
listAccounts(): Promise<ApiResponse<Account[]> & {
|
|
263
|
+
workspace_id?: number | string | null;
|
|
264
|
+
workspace_name?: string | null;
|
|
265
|
+
workspace_icon?: string | null;
|
|
266
|
+
}>;
|
|
267
|
+
getAccount(id: string): Promise<ApiResponse<Account>>;
|
|
268
|
+
getPostAnalytics(postId: string): Promise<ApiResponse<PostAnalytics>>;
|
|
269
|
+
getPostsAnalytics(postIds: string[]): Promise<ApiResponse<PostAnalytics[]>>;
|
|
280
270
|
getAnalyticsOverview(params?: {
|
|
281
271
|
period?: string;
|
|
282
272
|
start_date?: string;
|
|
283
273
|
end_date?: string;
|
|
284
|
-
}): Promise<ApiResponse<
|
|
274
|
+
}): Promise<ApiResponse<AnalyticsOverview> & {
|
|
275
|
+
period?: string;
|
|
276
|
+
start_date?: string;
|
|
277
|
+
end_date?: string;
|
|
278
|
+
current_date?: string;
|
|
279
|
+
}>;
|
|
285
280
|
getAccountAnalytics(params?: {
|
|
286
281
|
platform?: string;
|
|
287
282
|
date?: string;
|
|
288
|
-
}): Promise<ApiResponse<
|
|
283
|
+
}): Promise<ApiResponse<AccountAnalyticsEntry[]> & {
|
|
284
|
+
date?: string;
|
|
285
|
+
}>;
|
|
289
286
|
getBestTimes(params: {
|
|
290
287
|
platform: string;
|
|
291
288
|
timezone?: string;
|
|
292
|
-
}): Promise<
|
|
293
|
-
listWebhooks(): Promise<ApiResponse<
|
|
289
|
+
}): Promise<BestTimesResult>;
|
|
290
|
+
listWebhooks(): Promise<ApiResponse<Webhook[]>>;
|
|
294
291
|
createWebhook(data: {
|
|
295
292
|
url: string;
|
|
296
293
|
events: string[];
|
|
297
|
-
}): Promise<ApiResponse<
|
|
294
|
+
}): Promise<ApiResponse<Webhook & {
|
|
295
|
+
secret: string;
|
|
296
|
+
}>>;
|
|
298
297
|
deleteWebhook(id: string): Promise<ApiResponse<unknown>>;
|
|
299
|
-
getWebhook(id: string): Promise<ApiResponse<
|
|
298
|
+
getWebhook(id: string): Promise<ApiResponse<Webhook>>;
|
|
300
299
|
updateWebhook(id: string, data: {
|
|
301
300
|
url?: string;
|
|
302
301
|
events?: string[];
|
|
303
302
|
is_active?: boolean;
|
|
304
|
-
}): Promise<ApiResponse<
|
|
305
|
-
rotateWebhookSecret(id: string): Promise<ApiResponse<
|
|
303
|
+
}): Promise<ApiResponse<Webhook>>;
|
|
304
|
+
rotateWebhookSecret(id: string): Promise<ApiResponse<{
|
|
305
|
+
id: string;
|
|
306
|
+
secret: string;
|
|
307
|
+
}>>;
|
|
306
308
|
listInboxConversations(params?: {
|
|
307
309
|
platform?: string;
|
|
308
310
|
type?: string;
|
package/build/client.js
CHANGED
|
@@ -201,6 +201,8 @@ export class OmniSocialsClient {
|
|
|
201
201
|
// Recent posts fetched live from the connected platform APIs (including
|
|
202
202
|
// content published outside OmniSocials). The fallback for brand-new
|
|
203
203
|
// workspaces where listPosts is empty. Requires the analytics:read scope.
|
|
204
|
+
// `connected_platforms`/`errors`/`note`/`current_date` ride top-level on the
|
|
205
|
+
// envelope (backend/routes/api/v1/posts.js recent-platform route).
|
|
204
206
|
async getRecentPlatformPosts(params) {
|
|
205
207
|
return this.request("GET", "/posts/recent-platform", undefined, params);
|
|
206
208
|
}
|
|
@@ -224,14 +226,17 @@ export class OmniSocialsClient {
|
|
|
224
226
|
async retryPost(id) {
|
|
225
227
|
return this.request("POST", `/posts/${id}/retry`);
|
|
226
228
|
}
|
|
227
|
-
// Locations (Instagram place tagging)
|
|
229
|
+
// Locations (Instagram place tagging). NOT the standard envelope — see
|
|
230
|
+
// LocationSearchResult (degraded-path `error` is a plain string).
|
|
228
231
|
async searchLocations(query) {
|
|
229
|
-
return this.request("GET", "/locations/search", undefined, {
|
|
232
|
+
return this.request("GET", "/locations/search", undefined, {
|
|
233
|
+
q: query,
|
|
234
|
+
});
|
|
230
235
|
}
|
|
231
236
|
async validateLocation(id) {
|
|
232
237
|
return this.request("GET", "/locations/validate", undefined, { id });
|
|
233
238
|
}
|
|
234
|
-
// Audio (Instagram Reel music)
|
|
239
|
+
// Audio (Instagram Reel music). Same nonstandard string-`error` envelope.
|
|
235
240
|
async searchInstagramAudio(query, type) {
|
|
236
241
|
const params = {};
|
|
237
242
|
if (query)
|
|
@@ -262,6 +267,7 @@ export class OmniSocialsClient {
|
|
|
262
267
|
* or `size_bytes` + `mime`.
|
|
263
268
|
*/
|
|
264
269
|
async checkMediaCompatibility(data) {
|
|
270
|
+
// Nonstandard envelope: compatibility fields ride top-level, no `data`.
|
|
265
271
|
return this.request("POST", "/media/check", data);
|
|
266
272
|
}
|
|
267
273
|
async deleteMedia(id) {
|
|
@@ -290,7 +296,9 @@ export class OmniSocialsClient {
|
|
|
290
296
|
async deleteHashtagSet(id) {
|
|
291
297
|
return this.request("DELETE", `/hashtag-sets/${id}`);
|
|
292
298
|
}
|
|
293
|
-
// Accounts
|
|
299
|
+
// Accounts. Alongside `data`, the list response carries the workspace's
|
|
300
|
+
// identity top-level (backend/routes/api/v1/accounts.js) — the workspaces
|
|
301
|
+
// tool reads it to label multi-workspace setups.
|
|
294
302
|
async listAccounts() {
|
|
295
303
|
return this.request("GET", "/accounts");
|
|
296
304
|
}
|
|
@@ -308,12 +316,18 @@ export class OmniSocialsClient {
|
|
|
308
316
|
ids: postIds.join(","),
|
|
309
317
|
});
|
|
310
318
|
}
|
|
319
|
+
// The resolved range and today's date ride top-level on the envelope
|
|
320
|
+
// (backend/routes/api/v1/analytics.js overview route) so clients can render
|
|
321
|
+
// an accurate "as of <today>" line.
|
|
311
322
|
async getAnalyticsOverview(params) {
|
|
312
323
|
return this.request("GET", "/analytics/overview", undefined, params);
|
|
313
324
|
}
|
|
325
|
+
// The resolved `date` rides top-level on the envelope.
|
|
314
326
|
async getAccountAnalytics(params) {
|
|
315
327
|
return this.request("GET", "/analytics/accounts", undefined, params);
|
|
316
328
|
}
|
|
329
|
+
// Nonstandard envelope: the result object is returned top-level, no `data`
|
|
330
|
+
// (backend/routes/api/v1/analytics.js best-times route).
|
|
317
331
|
async getBestTimes(params) {
|
|
318
332
|
return this.request("GET", "/analytics/best-times", undefined, params);
|
|
319
333
|
}
|
|
@@ -321,6 +335,7 @@ export class OmniSocialsClient {
|
|
|
321
335
|
async listWebhooks() {
|
|
322
336
|
return this.request("GET", "/webhooks");
|
|
323
337
|
}
|
|
338
|
+
// The signing secret is only ever returned at creation time.
|
|
324
339
|
async createWebhook(data) {
|
|
325
340
|
return this.request("POST", "/webhooks", data);
|
|
326
341
|
}
|
|
@@ -333,6 +348,8 @@ export class OmniSocialsClient {
|
|
|
333
348
|
async updateWebhook(id, data) {
|
|
334
349
|
return this.request("PATCH", `/webhooks/${id}`, data);
|
|
335
350
|
}
|
|
351
|
+
// Returns only { id, secret } — not the full webhook
|
|
352
|
+
// (backend/routes/api/v1/webhooks.js rotate-secret route).
|
|
336
353
|
async rotateWebhookSecret(id) {
|
|
337
354
|
return this.request("POST", `/webhooks/${id}/rotate-secret`);
|
|
338
355
|
}
|
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.21.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/accounts.js
CHANGED
|
@@ -8,7 +8,7 @@ export function registerAccountTools(server, getClient) {
|
|
|
8
8
|
content: [{ type: "text", text: `Error (${result.error.code}): ${result.error.message}` }],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
const accounts = Array.isArray(result.data) ? result.data :
|
|
11
|
+
const accounts = Array.isArray(result.data) ? result.data : [];
|
|
12
12
|
if (!accounts.length) {
|
|
13
13
|
return {
|
|
14
14
|
content: [{ type: "text", text: "No connected accounts found." }],
|
|
@@ -42,7 +42,7 @@ export function registerAccountTools(server, getClient) {
|
|
|
42
42
|
// Fetch profile pictures
|
|
43
43
|
const content = [];
|
|
44
44
|
const imagePromises = accounts
|
|
45
|
-
.filter((a) => a.profile_picture)
|
|
45
|
+
.filter((a) => !!a.profile_picture)
|
|
46
46
|
.slice(0, 6)
|
|
47
47
|
.map(async (a) => {
|
|
48
48
|
const img = await fetchImageAsBase64(a.profile_picture);
|
package/build/tools/analytics.js
CHANGED
|
@@ -17,7 +17,7 @@ export function registerAnalyticsTools(server, getClient) {
|
|
|
17
17
|
// (thread posts publish as a chain), so we just total across platforms here.
|
|
18
18
|
// The legacy flat shape (d.impressions / d.platform_stats) doesn't exist on
|
|
19
19
|
// this endpoint and reading those keys returned "—" for every field.
|
|
20
|
-
const platforms = d?.platforms
|
|
20
|
+
const platforms = d?.platforms ?? {};
|
|
21
21
|
const platformEntries = Object.entries(platforms);
|
|
22
22
|
if (platformEntries.length === 0) {
|
|
23
23
|
return {
|
|
@@ -110,9 +110,9 @@ export function registerAnalyticsTools(server, getClient) {
|
|
|
110
110
|
// API returns { data: [{ post_id, platforms: { <platform>: { metrics } } }] }.
|
|
111
111
|
// Per-platform metrics are already thread-summed server-side; collapse each
|
|
112
112
|
// post's per-platform metrics into totals, mirroring get_post_analytics.
|
|
113
|
-
const entries = result.data
|
|
113
|
+
const entries = result.data ?? [];
|
|
114
114
|
const rows = entries.map((entry) => {
|
|
115
|
-
const platforms = entry?.platforms
|
|
115
|
+
const platforms = entry?.platforms ?? {};
|
|
116
116
|
const platformEntries = Object.entries(platforms);
|
|
117
117
|
let impressions = 0;
|
|
118
118
|
let engagements = 0;
|
|
@@ -167,6 +167,11 @@ export function registerAnalyticsTools(server, getClient) {
|
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
const d = result.data;
|
|
170
|
+
if (!d) {
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: "text", text: "No analytics overview data returned." }],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
170
175
|
const resolvedStart = result.start_date;
|
|
171
176
|
const resolvedEnd = result.end_date;
|
|
172
177
|
const currentDate = result.current_date;
|
|
@@ -189,8 +194,7 @@ export function registerAnalyticsTools(server, getClient) {
|
|
|
189
194
|
md += `|----------|-------|-------------|-------------|----------|\n`;
|
|
190
195
|
// Sort by impressions descending
|
|
191
196
|
const sorted = Object.entries(d.platform_breakdown).sort((a, b) => (b[1].total_impressions || 0) - (a[1].total_impressions || 0));
|
|
192
|
-
for (const [platform,
|
|
193
|
-
const s = stats;
|
|
197
|
+
for (const [platform, s] of sorted) {
|
|
194
198
|
const rate = s.engagement_rate !== undefined
|
|
195
199
|
? `${s.engagement_rate.toFixed(2)}%`
|
|
196
200
|
: "—";
|
|
@@ -276,7 +280,10 @@ export function registerAnalyticsTools(server, getClient) {
|
|
|
276
280
|
};
|
|
277
281
|
}
|
|
278
282
|
const d = result;
|
|
279
|
-
|
|
283
|
+
// On success the service always returns platform/timezone; params is the
|
|
284
|
+
// typed fallback so the label never renders "undefined".
|
|
285
|
+
const platformLabel = capitalize(d.platform || params.platform);
|
|
286
|
+
let md = `## Best times to post — ${platformLabel} (${d.timezone})\n\n`;
|
|
280
287
|
md += `**Top slots:**\n`;
|
|
281
288
|
(d.recommendations || []).forEach((r, i) => {
|
|
282
289
|
const extras = [];
|
|
@@ -309,10 +316,10 @@ export function registerAnalyticsTools(server, getClient) {
|
|
|
309
316
|
md += `\n### Best hours per day (score 0-100)\n\n| Day | Best hours |\n|-----|------------|\n${dayRows.join("\n")}\n`;
|
|
310
317
|
}
|
|
311
318
|
if (d.basis === "defaults") {
|
|
312
|
-
md += `\n_Not enough posting history on ${
|
|
319
|
+
md += `\n_Not enough posting history on ${platformLabel} yet (${d.sample_size} analyzed post${d.sample_size === 1 ? "" : "s"}). These are cross-industry averages — publish ${d.posts_needed} more post${d.posts_needed === 1 ? "" : "s"} to unlock recommendations from this audience's own data._\n`;
|
|
313
320
|
}
|
|
314
321
|
else {
|
|
315
|
-
md += `\n_Based on ${d.sample_size} ${
|
|
322
|
+
md += `\n_Based on ${d.sample_size} ${platformLabel} posts from the last ${d.window_days} days (metric: ${d.metric}, times in ${d.timezone})._\n`;
|
|
316
323
|
}
|
|
317
324
|
return {
|
|
318
325
|
content: [{ type: "text", text: md }],
|
|
@@ -7,7 +7,7 @@ export function registerHashtagSetTools(server, getClient) {
|
|
|
7
7
|
content: [{ type: "text", text: `Error (${result.error.code}): ${result.error.message}` }],
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
-
const sets = Array.isArray(result.data) ? result.data :
|
|
10
|
+
const sets = Array.isArray(result.data) ? result.data : [];
|
|
11
11
|
if (!sets.length) {
|
|
12
12
|
return {
|
|
13
13
|
content: [{ type: "text", text: "No hashtag sets yet. Create one with create_hashtag_set." }],
|
|
@@ -36,6 +36,11 @@ export function registerHashtagSetTools(server, getClient) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
const s = result.data;
|
|
39
|
+
if (!s) {
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: "Hashtag set created, but the API returned no details. List it with list_hashtag_sets." }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
39
44
|
return {
|
|
40
45
|
content: [{
|
|
41
46
|
type: "text",
|
|
@@ -55,6 +60,11 @@ export function registerHashtagSetTools(server, getClient) {
|
|
|
55
60
|
};
|
|
56
61
|
}
|
|
57
62
|
const s = result.data;
|
|
63
|
+
if (!s) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: "Hashtag set updated, but the API returned no details. List it with list_hashtag_sets." }],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
58
68
|
return {
|
|
59
69
|
content: [{
|
|
60
70
|
type: "text",
|
package/build/tools/inbox.js
CHANGED
|
@@ -4,11 +4,12 @@ const PLATFORM_EMOJI = {
|
|
|
4
4
|
instagram: "📸",
|
|
5
5
|
facebook: "📘",
|
|
6
6
|
linkedin: "💼",
|
|
7
|
+
x: "🐦",
|
|
7
8
|
};
|
|
8
9
|
export function registerInboxTools(server, getClient) {
|
|
9
|
-
server.tool("list_inbox_conversations", "List social inbox conversations (Instagram/Facebook DMs, comments, mentions,
|
|
10
|
+
server.tool("list_inbox_conversations", "List social inbox conversations (Instagram/Facebook DMs, comments, mentions, LinkedIn company-page comments/mentions, and X DMs where the workspace has opted into X DMs), newest activity first. Cursor-paginated: pass the returned cursor to get the next page.", {
|
|
10
11
|
platform: z
|
|
11
|
-
.enum(["instagram", "facebook", "linkedin"])
|
|
12
|
+
.enum(["instagram", "facebook", "linkedin", "x"])
|
|
12
13
|
.optional()
|
|
13
14
|
.describe("Filter to one platform"),
|
|
14
15
|
type: z
|
|
@@ -136,7 +137,7 @@ export function registerInboxTools(server, getClient) {
|
|
|
136
137
|
],
|
|
137
138
|
};
|
|
138
139
|
});
|
|
139
|
-
server.tool("reply_to_inbox", "Reply to an existing inbox conversation (DM, comment, or mention) on Instagram, Facebook, or
|
|
140
|
+
server.tool("reply_to_inbox", "Reply to an existing inbox conversation (DM, comment, or mention) on Instagram, Facebook, LinkedIn, or X. You can only reply to conversations that already exist. Meta direct-message replies must be within the platform's 24-hour messaging window. 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.", {
|
|
140
141
|
conversation_id: z.string().describe("The conversation ID to reply to"),
|
|
141
142
|
text: z.string().describe("The reply text (max 2000 characters)"),
|
|
142
143
|
}, async ({ conversation_id, text }) => {
|
package/build/tools/media.js
CHANGED
|
@@ -13,7 +13,7 @@ export function registerMediaTools(server, getClient) {
|
|
|
13
13
|
content: [{ type: "text", text: `Error (${result.error.code}): ${result.error.message}` }],
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
const items = Array.isArray(result.data) ? result.data :
|
|
16
|
+
const items = Array.isArray(result.data) ? result.data : [];
|
|
17
17
|
if (!items.length) {
|
|
18
18
|
const hint = params?.search
|
|
19
19
|
? `No media found matching "${params.search}". It hasn't been uploaded yet — upload it (and pass a \`name\`) so it's findable next time.`
|
|
@@ -122,6 +122,11 @@ When the user provides an image in the conversation (not a URL), use base64_data
|
|
|
122
122
|
return { content: [{ type: "text", text: md }] };
|
|
123
123
|
}
|
|
124
124
|
const m = result.data;
|
|
125
|
+
if (!m) {
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: "text", text: "Upload succeeded, but the API returned no media details. Find the file with list_media." }],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
125
130
|
const isProcessing = m.status === "processing";
|
|
126
131
|
let md = isProcessing ? `## Media Processing\n\n` : `## Media Uploaded\n\n`;
|
|
127
132
|
md += `| Field | Value |\n`;
|
|
@@ -170,7 +175,8 @@ Provide ONE of: a public 'url' (its size/type is read via a HEAD request), an ex
|
|
|
170
175
|
const connected = r.connected_platforms || [];
|
|
171
176
|
let md = `## Compatibility Check\n\n`;
|
|
172
177
|
if (r.file) {
|
|
173
|
-
|
|
178
|
+
// size_known implies size_bytes is a finite number (media.js check route).
|
|
179
|
+
const sz = r.file.size_known ? formatBytes(r.file.size_bytes ?? 0) : "unknown size";
|
|
174
180
|
md += `File: ${sz}${r.file.mime ? ` · ${r.file.mime}` : ""}\n`;
|
|
175
181
|
}
|
|
176
182
|
md += `Connected platforms: ${connected.length ? connected.join(", ") : "none"}\n\n`;
|
|
@@ -204,6 +210,11 @@ Provide ONE of: a public 'url' (its size/type is read via a HEAD request), an ex
|
|
|
204
210
|
};
|
|
205
211
|
}
|
|
206
212
|
const m = result.data;
|
|
213
|
+
if (!m) {
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: "Media updated, but the API returned no details. Verify with list_media." }],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
207
218
|
return {
|
|
208
219
|
content: [
|
|
209
220
|
{
|
|
@@ -218,7 +229,7 @@ Provide ONE of: a public 'url' (its size/type is read via a HEAD request), an ex
|
|
|
218
229
|
if (result.error) {
|
|
219
230
|
return { content: [{ type: "text", text: `Error (${result.error.code}): ${result.error.message}` }] };
|
|
220
231
|
}
|
|
221
|
-
const items = Array.isArray(result.data) ? result.data :
|
|
232
|
+
const items = Array.isArray(result.data) ? result.data : [];
|
|
222
233
|
if (!items.length) {
|
|
223
234
|
return { content: [{ type: "text", text: "No folders yet. Create one with create_folder." }] };
|
|
224
235
|
}
|
|
@@ -238,6 +249,11 @@ Provide ONE of: a public 'url' (its size/type is read via a HEAD request), an ex
|
|
|
238
249
|
return { content: [{ type: "text", text: `Error (${result.error.code}): ${result.error.message}` }] };
|
|
239
250
|
}
|
|
240
251
|
const f = result.data;
|
|
252
|
+
if (!f) {
|
|
253
|
+
return {
|
|
254
|
+
content: [{ type: "text", text: "Folder created, but the API returned no details. Verify with list_folders." }],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
241
257
|
return {
|
|
242
258
|
content: [
|
|
243
259
|
{
|