@omnisocials/mcp-server 1.20.0 → 1.20.1
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 +94 -100
- package/build/client.js +21 -4
- 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 +168 -202
- package/build/tools/webhooks.js +21 -1
- package/build/types.d.ts +295 -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,45 @@ export interface MastodonPostOptions {
|
|
|
100
100
|
export interface MastodonPostOptionsUpdate {
|
|
101
101
|
thread_parts?: MastodonThreadPartInput[] | null;
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Request body for POST /posts/create. `createAndPublishPost` accepts the
|
|
105
|
+
* same shape minus `scheduled_at` (publishing is immediate).
|
|
106
|
+
*/
|
|
107
|
+
export interface CreatePostInput {
|
|
108
|
+
content: string | Record<string, string>;
|
|
109
|
+
channels?: string[];
|
|
110
|
+
scheduled_at?: string;
|
|
111
|
+
media_ids?: MediaIdEntry[] | Record<string, MediaIdEntry[]>;
|
|
112
|
+
media_urls?: MediaUrlEntry[] | Record<string, MediaUrlEntry[]>;
|
|
113
|
+
type?: string;
|
|
114
|
+
source?: string;
|
|
115
|
+
link_url?: string;
|
|
116
|
+
link_title?: string;
|
|
117
|
+
link_description?: string;
|
|
118
|
+
link_thumbnail_url?: string;
|
|
119
|
+
location_id?: string;
|
|
120
|
+
collaborators?: string[];
|
|
121
|
+
user_tags?: Array<{
|
|
122
|
+
username: string;
|
|
123
|
+
x: number;
|
|
124
|
+
y: number;
|
|
125
|
+
image_index?: number;
|
|
126
|
+
}>;
|
|
127
|
+
pinterest?: Record<string, unknown>;
|
|
128
|
+
youtube?: Record<string, unknown>;
|
|
129
|
+
instagram?: Record<string, unknown>;
|
|
130
|
+
facebook?: Record<string, unknown>;
|
|
131
|
+
linkedin?: Record<string, unknown>;
|
|
132
|
+
linkedin_page?: Record<string, unknown>;
|
|
133
|
+
tiktok?: Record<string, unknown>;
|
|
134
|
+
x?: XPostOptions;
|
|
135
|
+
bluesky?: BlueskyPostOptions;
|
|
136
|
+
mastodon?: MastodonPostOptions;
|
|
137
|
+
google_business?: Record<string, unknown>;
|
|
138
|
+
hashtag_set?: string;
|
|
139
|
+
hashtag_placement?: "caption_append" | "first_comment";
|
|
140
|
+
hashtag_platforms?: string[];
|
|
141
|
+
}
|
|
103
142
|
export declare class OmniSocialsClient {
|
|
104
143
|
private baseUrl;
|
|
105
144
|
private apiKey;
|
|
@@ -109,81 +148,20 @@ export declare class OmniSocialsClient {
|
|
|
109
148
|
status?: string;
|
|
110
149
|
limit?: string;
|
|
111
150
|
offset?: string;
|
|
112
|
-
}): Promise<ApiResponse<
|
|
113
|
-
getPost(id: string): Promise<ApiResponse<
|
|
151
|
+
}): Promise<ApiResponse<ApiPost[]>>;
|
|
152
|
+
getPost(id: string): Promise<ApiResponse<ApiPost>>;
|
|
114
153
|
getRecentPlatformPosts(params?: {
|
|
115
154
|
limit?: string;
|
|
116
155
|
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>>;
|
|
156
|
+
}): Promise<ApiResponse<RecentPlatformPost[]> & {
|
|
157
|
+
count?: number;
|
|
158
|
+
connected_platforms?: string[];
|
|
159
|
+
errors?: Record<string, string>;
|
|
160
|
+
note?: string;
|
|
161
|
+
current_date?: string;
|
|
162
|
+
}>;
|
|
163
|
+
createPost(data: CreatePostInput): Promise<ApiResponse<ApiPost>>;
|
|
164
|
+
createAndPublishPost(data: Omit<CreatePostInput, "scheduled_at">): Promise<ApiResponse<ApiPost>>;
|
|
187
165
|
updatePost(id: string, data: {
|
|
188
166
|
content?: string | Record<string, string>;
|
|
189
167
|
scheduled_at?: string;
|
|
@@ -210,26 +188,26 @@ export declare class OmniSocialsClient {
|
|
|
210
188
|
bluesky?: BlueskyPostOptionsUpdate;
|
|
211
189
|
mastodon?: MastodonPostOptionsUpdate;
|
|
212
190
|
google_business?: Record<string, unknown>;
|
|
213
|
-
}): Promise<ApiResponse<
|
|
191
|
+
}): Promise<ApiResponse<ApiPost>>;
|
|
214
192
|
deletePost(id: string): Promise<ApiResponse<unknown>>;
|
|
215
|
-
publishPost(id: string): Promise<ApiResponse<
|
|
216
|
-
retryPost(id: string): Promise<ApiResponse<
|
|
217
|
-
searchLocations(query: string): Promise<
|
|
193
|
+
publishPost(id: string): Promise<ApiResponse<PublishPostResult>>;
|
|
194
|
+
retryPost(id: string): Promise<ApiResponse<RetryPostResult>>;
|
|
195
|
+
searchLocations(query: string): Promise<LocationSearchResult>;
|
|
218
196
|
validateLocation(id: string): Promise<ApiResponse<unknown>>;
|
|
219
|
-
searchInstagramAudio(query?: string, type?: "music" | "original_sound"): Promise<
|
|
197
|
+
searchInstagramAudio(query?: string, type?: "music" | "original_sound"): Promise<AudioSearchResult>;
|
|
220
198
|
listMedia(params?: {
|
|
221
199
|
limit?: string;
|
|
222
200
|
offset?: string;
|
|
223
201
|
search?: string;
|
|
224
202
|
folder_id?: string;
|
|
225
|
-
}): Promise<ApiResponse<
|
|
203
|
+
}): Promise<ApiResponse<MediaItem[]>>;
|
|
226
204
|
uploadMedia(data: {
|
|
227
205
|
url: string;
|
|
228
206
|
filename?: string;
|
|
229
207
|
name?: string;
|
|
230
208
|
folder?: string;
|
|
231
209
|
}): Promise<ApiResponse<MediaItem> & Partial<PdfUploadResult> & {
|
|
232
|
-
compatibility?:
|
|
210
|
+
compatibility?: MediaCompatibility;
|
|
233
211
|
message?: string;
|
|
234
212
|
}>;
|
|
235
213
|
uploadMediaFromBase64(data: {
|
|
@@ -239,7 +217,7 @@ export declare class OmniSocialsClient {
|
|
|
239
217
|
name?: string;
|
|
240
218
|
folder?: string;
|
|
241
219
|
}): Promise<ApiResponse<MediaItem> & Partial<PdfUploadResult> & {
|
|
242
|
-
compatibility?:
|
|
220
|
+
compatibility?: MediaCompatibility;
|
|
243
221
|
message?: string;
|
|
244
222
|
}>;
|
|
245
223
|
/**
|
|
@@ -252,57 +230,73 @@ export declare class OmniSocialsClient {
|
|
|
252
230
|
media_id?: string;
|
|
253
231
|
size_bytes?: number;
|
|
254
232
|
mime?: string;
|
|
255
|
-
}): Promise<
|
|
233
|
+
}): Promise<MediaCheckResult>;
|
|
256
234
|
deleteMedia(id: string): Promise<ApiResponse<unknown>>;
|
|
257
235
|
updateMedia(id: string, data: {
|
|
258
236
|
name?: string;
|
|
259
237
|
folder_id?: string | null;
|
|
260
|
-
}): Promise<ApiResponse<
|
|
261
|
-
listFolders(): Promise<ApiResponse<
|
|
238
|
+
}): Promise<ApiResponse<MediaItem>>;
|
|
239
|
+
listFolders(): Promise<ApiResponse<MediaFolder[]>>;
|
|
262
240
|
createFolder(data: {
|
|
263
241
|
name: string;
|
|
264
242
|
parent_id?: string;
|
|
265
|
-
}): Promise<ApiResponse<
|
|
266
|
-
listHashtagSets(): Promise<ApiResponse<
|
|
243
|
+
}): Promise<ApiResponse<MediaFolder>>;
|
|
244
|
+
listHashtagSets(): Promise<ApiResponse<HashtagSet[]>>;
|
|
267
245
|
createHashtagSet(data: {
|
|
268
246
|
name: string;
|
|
269
247
|
hashtags: string[];
|
|
270
|
-
}): Promise<ApiResponse<
|
|
248
|
+
}): Promise<ApiResponse<HashtagSet>>;
|
|
271
249
|
updateHashtagSet(id: string, data: {
|
|
272
250
|
name?: string;
|
|
273
251
|
hashtags?: string[];
|
|
274
|
-
}): Promise<ApiResponse<
|
|
252
|
+
}): Promise<ApiResponse<HashtagSet>>;
|
|
275
253
|
deleteHashtagSet(id: string): Promise<ApiResponse<unknown>>;
|
|
276
|
-
listAccounts(): Promise<ApiResponse<
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
254
|
+
listAccounts(): Promise<ApiResponse<Account[]> & {
|
|
255
|
+
workspace_id?: number | string | null;
|
|
256
|
+
workspace_name?: string | null;
|
|
257
|
+
workspace_icon?: string | null;
|
|
258
|
+
}>;
|
|
259
|
+
getAccount(id: string): Promise<ApiResponse<Account>>;
|
|
260
|
+
getPostAnalytics(postId: string): Promise<ApiResponse<PostAnalytics>>;
|
|
261
|
+
getPostsAnalytics(postIds: string[]): Promise<ApiResponse<PostAnalytics[]>>;
|
|
280
262
|
getAnalyticsOverview(params?: {
|
|
281
263
|
period?: string;
|
|
282
264
|
start_date?: string;
|
|
283
265
|
end_date?: string;
|
|
284
|
-
}): Promise<ApiResponse<
|
|
266
|
+
}): Promise<ApiResponse<AnalyticsOverview> & {
|
|
267
|
+
period?: string;
|
|
268
|
+
start_date?: string;
|
|
269
|
+
end_date?: string;
|
|
270
|
+
current_date?: string;
|
|
271
|
+
}>;
|
|
285
272
|
getAccountAnalytics(params?: {
|
|
286
273
|
platform?: string;
|
|
287
274
|
date?: string;
|
|
288
|
-
}): Promise<ApiResponse<
|
|
275
|
+
}): Promise<ApiResponse<AccountAnalyticsEntry[]> & {
|
|
276
|
+
date?: string;
|
|
277
|
+
}>;
|
|
289
278
|
getBestTimes(params: {
|
|
290
279
|
platform: string;
|
|
291
280
|
timezone?: string;
|
|
292
|
-
}): Promise<
|
|
293
|
-
listWebhooks(): Promise<ApiResponse<
|
|
281
|
+
}): Promise<BestTimesResult>;
|
|
282
|
+
listWebhooks(): Promise<ApiResponse<Webhook[]>>;
|
|
294
283
|
createWebhook(data: {
|
|
295
284
|
url: string;
|
|
296
285
|
events: string[];
|
|
297
|
-
}): Promise<ApiResponse<
|
|
286
|
+
}): Promise<ApiResponse<Webhook & {
|
|
287
|
+
secret: string;
|
|
288
|
+
}>>;
|
|
298
289
|
deleteWebhook(id: string): Promise<ApiResponse<unknown>>;
|
|
299
|
-
getWebhook(id: string): Promise<ApiResponse<
|
|
290
|
+
getWebhook(id: string): Promise<ApiResponse<Webhook>>;
|
|
300
291
|
updateWebhook(id: string, data: {
|
|
301
292
|
url?: string;
|
|
302
293
|
events?: string[];
|
|
303
294
|
is_active?: boolean;
|
|
304
|
-
}): Promise<ApiResponse<
|
|
305
|
-
rotateWebhookSecret(id: string): Promise<ApiResponse<
|
|
295
|
+
}): Promise<ApiResponse<Webhook>>;
|
|
296
|
+
rotateWebhookSecret(id: string): Promise<ApiResponse<{
|
|
297
|
+
id: string;
|
|
298
|
+
secret: string;
|
|
299
|
+
}>>;
|
|
306
300
|
listInboxConversations(params?: {
|
|
307
301
|
platform?: string;
|
|
308
302
|
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/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
|
{
|