@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.
@@ -8,7 +8,7 @@ export function registerWebhookTools(server, getClient) {
8
8
  content: [{ type: "text", text: `Error (${result.error.code}): ${result.error.message}` }],
9
9
  };
10
10
  }
11
- const webhooks = Array.isArray(result.data) ? result.data : result.data?.webhooks || [];
11
+ const webhooks = Array.isArray(result.data) ? result.data : [];
12
12
  if (!webhooks.length) {
13
13
  return {
14
14
  content: [{ type: "text", text: "No webhooks configured." }],
@@ -39,6 +39,11 @@ export function registerWebhookTools(server, getClient) {
39
39
  };
40
40
  }
41
41
  const w = result.data;
42
+ if (!w) {
43
+ return {
44
+ content: [{ type: "text", text: "Webhook created, but the API returned no details. Verify with list_webhooks." }],
45
+ };
46
+ }
42
47
  let md = `## Webhook Created\n\n`;
43
48
  md += `| Field | Value |\n`;
44
49
  md += `|-------|-------|\n`;
@@ -64,6 +69,11 @@ export function registerWebhookTools(server, getClient) {
64
69
  };
65
70
  }
66
71
  const w = result.data;
72
+ if (!w) {
73
+ return {
74
+ content: [{ type: "text", text: "Webhook not found." }],
75
+ };
76
+ }
67
77
  let md = `## Webhook Details\n\n`;
68
78
  md += `| Field | Value |\n`;
69
79
  md += `|-------|-------|\n`;
@@ -95,6 +105,11 @@ export function registerWebhookTools(server, getClient) {
95
105
  };
96
106
  }
97
107
  const w = result.data;
108
+ if (!w) {
109
+ return {
110
+ content: [{ type: "text", text: "Webhook updated, but the API returned no details. Verify with list_webhooks." }],
111
+ };
112
+ }
98
113
  let md = `## Webhook Updated\n\n`;
99
114
  md += `| Field | Value |\n`;
100
115
  md += `|-------|-------|\n`;
@@ -124,6 +139,11 @@ export function registerWebhookTools(server, getClient) {
124
139
  };
125
140
  }
126
141
  const w = result.data;
142
+ if (!w) {
143
+ return {
144
+ content: [{ type: "text", text: "Secret rotated, but the API returned no details." }],
145
+ };
146
+ }
127
147
  let md = `## Webhook Secret Rotated\n\n`;
128
148
  md += `| Field | Value |\n`;
129
149
  md += `|-------|-------|\n`;
package/build/types.d.ts CHANGED
@@ -4,6 +4,14 @@ export interface ApiResponse<T = unknown> {
4
4
  code: string;
5
5
  message: string;
6
6
  };
7
+ warnings?: Array<{
8
+ code: string;
9
+ message: string;
10
+ credits_required?: number;
11
+ credits_balance?: number | null;
12
+ enforced?: boolean;
13
+ enforce_from?: string;
14
+ }>;
7
15
  pagination?: {
8
16
  next_cursor: string | null;
9
17
  has_more: boolean;
@@ -56,39 +64,183 @@ export interface InboxMessage {
56
64
  thumbnail: string | null;
57
65
  } | null;
58
66
  }
59
- export interface Post {
67
+ /** One media entry on a post — a plain URL string, or an object carrying the
68
+ * url plus an optional accessibility description (alt text). */
69
+ export type ApiPostMediaEntry = string | {
70
+ id?: string | number;
71
+ url?: string;
72
+ alt?: string;
73
+ [k: string]: unknown;
74
+ };
75
+ /** A thread part as echoed back by the API (X / Bluesky / Mastodon). */
76
+ export interface ApiThreadPartOut {
77
+ id?: string;
78
+ text?: string;
79
+ media_urls?: Array<string | {
80
+ url?: string;
81
+ alt?: string;
82
+ }>;
83
+ [k: string]: unknown;
84
+ }
85
+ /** Per-platform option block echoed on a post. First comments (input +
86
+ * post-publish outcome), thread parts, and platform-specific settings all
87
+ * ride here under their platform key, mirroring the request shape. */
88
+ export interface ApiPlatformOptionsOut {
89
+ first_comment?: string;
90
+ first_comment_result?: {
91
+ status?: string;
92
+ error?: string;
93
+ [k: string]: unknown;
94
+ } | null;
95
+ thread_parts?: ApiThreadPartOut[];
96
+ [k: string]: unknown;
97
+ }
98
+ /** Instagram option block — adds the Reel-cover and Trial-Reel read-backs. */
99
+ export interface ApiInstagramOptionsOut extends ApiPlatformOptionsOut {
100
+ is_trial_reel?: boolean;
101
+ trial_graduation_strategy?: string;
102
+ thumbnail_type?: string;
103
+ thumb_offset?: number | string;
104
+ cover_url?: string;
105
+ }
106
+ export interface ApiPost {
60
107
  id: string;
61
- content: string;
108
+ /** DB "posted" is mapped to "published" by the serializer. */
62
109
  status: string;
63
- scheduled_at: string | null;
64
- published_at: string | null;
65
- channels: string[];
66
- media: string[];
67
- created_at: string;
68
- /**
69
- * Per-platform user-friendly error messages, keyed by platform identifier
70
- * (facebook, instagram, linkedin, linkedin_page, youtube, tiktok, pinterest,
71
- * x, threads, bluesky, mastodon, google_business). Populated when `status`
72
- * is `failed` or `warning`. Only failed platforms appear. `null` while the
73
- * post is still draft/scheduled/processing or every platform succeeded.
74
- */
110
+ /** "post" | "story" | "reel" */
111
+ type?: string;
112
+ /** Per-platform captions keyed by platform id plus "default". Legacy
113
+ * servers returned a plain string. */
114
+ content?: Record<string, string> | string;
115
+ /** Selected platforms — an array of platform ids on current servers;
116
+ * legacy servers returned a {platform: boolean} map. */
117
+ accounts?: string[] | Record<string, boolean>;
118
+ /** legacy servers (older key for `accounts`) */
119
+ channels?: string[] | Record<string, boolean>;
120
+ /** Flat array (same set everywhere) or per-platform map keyed
121
+ * "default"/platform. */
122
+ media?: ApiPostMediaEntry[] | Record<string, ApiPostMediaEntry[]>;
123
+ schedule_at?: string | null;
124
+ /** legacy servers */
125
+ scheduled_at?: string | null;
126
+ /** legacy servers */
127
+ published_at?: string | null;
128
+ approval_status?: string | null;
129
+ /** Deep link into the OmniSocials dashboard for this post. */
130
+ app_url?: string;
131
+ /** platform → live URL for each platform that successfully published. */
132
+ published_urls?: Record<string, string>;
133
+ /** Set on a retry post: the original failed post's id. */
134
+ retry_of?: string;
135
+ /** Set on the original post: ids of its retry posts. */
136
+ retries?: string[];
137
+ location_id?: string;
138
+ collaborators?: string[];
139
+ user_tags?: Array<string | {
140
+ username?: string;
141
+ x?: number;
142
+ y?: number;
143
+ image_index?: number;
144
+ }>;
145
+ link_url?: string;
146
+ link_title?: string;
147
+ link_description?: string;
148
+ link_thumbnail_url?: string;
149
+ youtube?: ApiPlatformOptionsOut;
150
+ pinterest?: ApiPlatformOptionsOut;
151
+ instagram?: ApiInstagramOptionsOut;
152
+ facebook?: ApiPlatformOptionsOut;
153
+ linkedin?: ApiPlatformOptionsOut;
154
+ linkedin_page?: ApiPlatformOptionsOut;
155
+ tiktok?: ApiPlatformOptionsOut;
156
+ google_business?: ApiPlatformOptionsOut;
157
+ x?: ApiPlatformOptionsOut;
158
+ bluesky?: ApiPlatformOptionsOut;
159
+ mastodon?: ApiPlatformOptionsOut;
160
+ /** platform → user-friendly error message; only failed platforms appear.
161
+ * null while draft/scheduled/processing or when every platform succeeded. */
75
162
  errors?: Record<string, string> | null;
76
- x?: {
77
- reply_settings?: string;
78
- paid_partnership?: boolean;
79
- made_with_ai?: boolean;
80
- thread_parts?: Array<{
81
- id?: string;
82
- text: string;
83
- /** Entries are plain URLs, or { url, alt } when the media carries an
84
- * accessibility description (alt text). */
85
- media_urls?: Array<string | {
86
- url: string;
87
- alt?: string;
88
- }>;
89
- }>;
90
- [key: string]: unknown;
163
+ source?: string | null;
164
+ created_at?: string;
165
+ updated_at?: string;
166
+ }
167
+ /** `data` of POST /posts/:id/publish — a small ack, not a full post
168
+ * (backend/routes/api/v1/posts.js publish route). */
169
+ export interface PublishPostResult {
170
+ id: string;
171
+ status: string;
172
+ message?: string;
173
+ }
174
+ /** `data` of POST /posts/:id/retry (backend/routes/api/v1/posts.js:4194). */
175
+ export interface RetryPostResult {
176
+ id: string;
177
+ status: string;
178
+ /** The failed platforms being re-published. */
179
+ platforms?: string[];
180
+ message?: string;
181
+ }
182
+ /** One place from GET /locations/search
183
+ * (@openapi block in backend/routes/api/v1/locations.js). */
184
+ export interface LocationPlace {
185
+ /** Facebook Place ID — use as `location_id`. */
186
+ id: string;
187
+ name: string;
188
+ address?: string | null;
189
+ city?: string | null;
190
+ country?: string | null;
191
+ }
192
+ /** GET /locations/search envelope — NOT the standard data/error envelope:
193
+ * on the documented degraded path (no Facebook account / missing permission)
194
+ * `error` is a human-readable STRING next to an empty `data`; the standard
195
+ * { code, message } object appears only on transport/HTTP failures. */
196
+ export interface LocationSearchResult {
197
+ data?: LocationPlace[];
198
+ error?: string | {
199
+ code: string;
200
+ message: string;
91
201
  };
202
+ needsPermission?: boolean;
203
+ }
204
+ /** One track from GET /audio/search
205
+ * (@openapi block in backend/routes/api/v1/audio.js). */
206
+ export interface InstagramAudioTrack {
207
+ /** Use as `instagram.audio_id` on a reel. */
208
+ audio_id: string;
209
+ title?: string | null;
210
+ artist?: string | null;
211
+ duration_ms?: number | null;
212
+ /** Creator handle (original sounds only). */
213
+ ig_username?: string | null;
214
+ [k: string]: unknown;
215
+ }
216
+ /** GET /audio/search envelope — same nonstandard string-`error` convention
217
+ * as LocationSearchResult. */
218
+ export interface AudioSearchResult {
219
+ data?: InstagramAudioTrack[];
220
+ error?: string | {
221
+ code: string;
222
+ message: string;
223
+ };
224
+ }
225
+ /** One record from GET /posts/recent-platform — normalized in
226
+ * backend/services/voice-fetcher/recentPlatformPostsService.js (:125-149). */
227
+ export interface RecentPlatformPost {
228
+ platform: string;
229
+ /** The platform's own post id — the stable dedupe key. */
230
+ id: string | null;
231
+ permalink: string | null;
232
+ /** Always a string; "" when the post has no caption. */
233
+ text: string;
234
+ format?: string;
235
+ media_count?: number;
236
+ timestamp?: string | null;
237
+ image_url?: string | null;
238
+ /** Video length in whole seconds; null for images / platforms without it. */
239
+ duration_seconds?: number | null;
240
+ metrics?: Record<string, unknown>;
241
+ engagement?: number | null;
242
+ impressions?: number | null;
243
+ engagement_rate?: number | null;
92
244
  }
93
245
  export interface MediaItem {
94
246
  id: string;
@@ -170,11 +322,123 @@ export interface HashtagSet {
170
322
  created_at: string;
171
323
  updated_at: string;
172
324
  }
325
+ /** Per-platform stats block inside AnalyticsOverview.platform_breakdown.
326
+ * Shape from backend/routes/api/v1/analytics.js (overview route). */
327
+ export interface AnalyticsPlatformBreakdown {
328
+ posts: number;
329
+ total_engagement: number;
330
+ total_impressions: number;
331
+ average_engagement: number;
332
+ /** 0 when total_impressions is below the shared minimum-impressions floor. */
333
+ engagement_rate: number;
334
+ }
335
+ /** `data` payload of GET /analytics/overview. The resolved range
336
+ * (`period`/`start_date`/`end_date`/`current_date`) rides top-level on the
337
+ * response envelope, not inside `data` — see getAnalyticsOverview(). */
173
338
  export interface AnalyticsOverview {
174
339
  total_posts: number;
340
+ total_platforms: number;
341
+ total_engagement: number;
175
342
  total_impressions: number;
176
- total_engagements: number;
177
- period: string;
343
+ average_engagement_rate: number;
344
+ top_performing_platform: string | null;
345
+ platform_breakdown: Record<string, AnalyticsPlatformBreakdown>;
346
+ }
347
+ /** One connected platform that would reject the file
348
+ * (backend/services/media/mediaCompatibility.js getMediaWarnings). */
349
+ export interface MediaCompatWarning {
350
+ platform: string;
351
+ display_name: string;
352
+ reasons: string[];
353
+ }
354
+ /** Advisory compatibility block on /media/upload* and /media/check responses.
355
+ * Built by buildCompatibility() in backend/routes/api/v1/media.js:
356
+ * empty warnings = the file posts to every connected platform. */
357
+ export interface MediaCompatibility {
358
+ compatible: boolean;
359
+ warnings: MediaCompatWarning[];
360
+ /** One-line human summary, or null when fully compatible. */
361
+ summary: string | null;
362
+ }
363
+ /** POST /media/check response — the compatibility block is spread top-level
364
+ * next to `file` and `connected_platforms`; there is no `data` key
365
+ * (backend/routes/api/v1/media.js check route). */
366
+ export interface MediaCheckResult {
367
+ error?: {
368
+ code: string;
369
+ message: string;
370
+ };
371
+ file?: {
372
+ size_bytes: number | null;
373
+ mime: string | null;
374
+ size_known: boolean;
375
+ };
376
+ connected_platforms?: string[];
377
+ compatible?: boolean;
378
+ warnings?: MediaCompatWarning[];
379
+ summary?: string | null;
380
+ }
381
+ /** One folder from GET /folders — formatFolder in
382
+ * backend/routes/api/v1/folders.js (:15-23). */
383
+ export interface MediaFolder {
384
+ id: string;
385
+ name: string;
386
+ parent_id: string | null;
387
+ item_count?: number;
388
+ created_at?: string;
389
+ }
390
+ /** One row of GET /analytics/accounts `data`. `metrics` is the platform's
391
+ * account-level snapshot JSONB — counters are numbers; `note` (when present)
392
+ * explains non-obvious metric scope and must be relayed to the user. */
393
+ export interface AccountAnalyticsEntry {
394
+ platform: string;
395
+ platform_account_id?: string | null;
396
+ date?: string;
397
+ metrics?: {
398
+ followers?: number;
399
+ subscribers?: number;
400
+ posts?: number;
401
+ following?: number;
402
+ impressions?: number;
403
+ engagement?: number;
404
+ total_views?: number;
405
+ total_videos?: number;
406
+ note?: string;
407
+ [k: string]: number | string | undefined;
408
+ };
409
+ }
410
+ /** GET /analytics/best-times response — top-level, not the data/error
411
+ * envelope (backend/services/analytics/bestTimeService.js getBestTimes). */
412
+ export interface BestTimeRecommendation {
413
+ day: string;
414
+ /** "HH:00" in the resolved timezone. */
415
+ time: string;
416
+ score: number;
417
+ /** Number of posts behind the slot (own-data basis only). */
418
+ n?: number;
419
+ typical_engagement?: number;
420
+ }
421
+ export interface BestTimeGridCell {
422
+ day: string;
423
+ hour: number;
424
+ score: number;
425
+ [k: string]: unknown;
426
+ }
427
+ export interface BestTimesResult {
428
+ error?: {
429
+ code: string;
430
+ message: string;
431
+ };
432
+ platform?: string;
433
+ timezone?: string;
434
+ metric?: string;
435
+ window_days?: number;
436
+ sample_size?: number;
437
+ basis?: "own_data" | "defaults";
438
+ posts_needed?: number;
439
+ /** Absent on the defaults basis. */
440
+ grid?: BestTimeGridCell[];
441
+ recommendations?: BestTimeRecommendation[];
178
442
  }
179
443
  export interface PostAnalyticsPlatformEntry {
180
444
  platform: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnisocials/mcp-server",
3
- "version": "1.20.0",
3
+ "version": "1.20.1",
4
4
  "description": "MCP server for OmniSocials API - manage social media posts, media, accounts, analytics, and webhooks",
5
5
  "type": "module",
6
6
  "main": "build/index.js",