posterly-mcp-server 0.8.3 → 0.19.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.
Files changed (119) hide show
  1. package/README.md +35 -1
  2. package/dist/generated/platform-manifest.d.ts +392 -0
  3. package/dist/generated/platform-manifest.js +695 -0
  4. package/dist/index.js +321 -1
  5. package/dist/lib/api-client.d.ts +346 -0
  6. package/dist/lib/api-client.js +173 -0
  7. package/dist/tools/audit-google-business-profile.d.ts +24 -0
  8. package/dist/tools/audit-google-business-profile.js +27 -0
  9. package/dist/tools/create-oauth-client.d.ts +40 -0
  10. package/dist/tools/create-oauth-client.js +20 -0
  11. package/dist/tools/create-post.d.ts +7 -20
  12. package/dist/tools/create-post.js +4 -3
  13. package/dist/tools/create-posts-batch.d.ts +5 -5
  14. package/dist/tools/create-signed-upload.d.ts +24 -0
  15. package/dist/tools/create-signed-upload.js +20 -0
  16. package/dist/tools/create-webhook.d.ts +36 -0
  17. package/dist/tools/create-webhook.js +20 -0
  18. package/dist/tools/delete-google-business-review-reply.d.ts +32 -0
  19. package/dist/tools/delete-google-business-review-reply.js +17 -0
  20. package/dist/tools/delete-oauth-client.d.ts +20 -0
  21. package/dist/tools/delete-oauth-client.js +13 -0
  22. package/dist/tools/delete-post-group.d.ts +20 -0
  23. package/dist/tools/delete-post-group.js +13 -0
  24. package/dist/tools/delete-webhook.d.ts +20 -0
  25. package/dist/tools/delete-webhook.js +13 -0
  26. package/dist/tools/disconnect-account.d.ts +20 -0
  27. package/dist/tools/disconnect-account.js +22 -0
  28. package/dist/tools/generate-image.d.ts +4 -4
  29. package/dist/tools/generate-video.d.ts +59 -0
  30. package/dist/tools/generate-video.js +34 -0
  31. package/dist/tools/get-connect-link.d.ts +24 -0
  32. package/dist/tools/get-connect-link.js +46 -0
  33. package/dist/tools/get-google-business-review-link.d.ts +24 -0
  34. package/dist/tools/get-google-business-review-link.js +19 -0
  35. package/dist/tools/get-platform-schema.d.ts +24 -0
  36. package/dist/tools/get-platform-schema.js +44 -0
  37. package/dist/tools/get-post-missing.d.ts +16 -0
  38. package/dist/tools/get-post-missing.js +12 -0
  39. package/dist/tools/get-video-job.d.ts +24 -0
  40. package/dist/tools/get-video-job.js +31 -0
  41. package/dist/tools/get-video-options.d.ts +8 -0
  42. package/dist/tools/get-video-options.js +26 -0
  43. package/dist/tools/get-x-posting-quota.d.ts +16 -0
  44. package/dist/tools/get-x-posting-quota.js +21 -0
  45. package/dist/tools/list-activity.d.ts +32 -0
  46. package/dist/tools/list-activity.js +35 -0
  47. package/dist/tools/list-google-business-reviews.d.ts +32 -0
  48. package/dist/tools/list-google-business-reviews.js +28 -0
  49. package/dist/tools/list-oauth-clients.d.ts +8 -0
  50. package/dist/tools/list-oauth-clients.js +15 -0
  51. package/dist/tools/list-platforms.d.ts +16 -0
  52. package/dist/tools/list-platforms.js +20 -0
  53. package/dist/tools/list-posts.d.ts +3 -3
  54. package/dist/tools/list-posts.js +3 -2
  55. package/dist/tools/list-webhooks.d.ts +16 -0
  56. package/dist/tools/list-webhooks.js +24 -0
  57. package/dist/tools/reply-google-business-review.d.ts +36 -0
  58. package/dist/tools/reply-google-business-review.js +18 -0
  59. package/dist/tools/run-video-function.d.ts +24 -0
  60. package/dist/tools/run-video-function.js +25 -0
  61. package/dist/tools/suggest-google-business-review-reply.d.ts +48 -0
  62. package/dist/tools/suggest-google-business-review-reply.js +27 -0
  63. package/dist/tools/test-webhook.d.ts +20 -0
  64. package/dist/tools/test-webhook.js +14 -0
  65. package/dist/tools/trigger-platform-helper.d.ts +28 -0
  66. package/dist/tools/trigger-platform-helper.js +22 -0
  67. package/dist/tools/update-oauth-client.d.ts +44 -0
  68. package/dist/tools/update-oauth-client.js +21 -0
  69. package/dist/tools/update-post-release-id.d.ts +28 -0
  70. package/dist/tools/update-post-release-id.js +18 -0
  71. package/dist/tools/update-post-status.d.ts +28 -0
  72. package/dist/tools/update-post-status.js +23 -0
  73. package/dist/tools/update-post.js +1 -1
  74. package/dist/tools/update-webhook.d.ts +40 -0
  75. package/dist/tools/update-webhook.js +21 -0
  76. package/dist/tools/upload-media-from-url.d.ts +24 -0
  77. package/dist/tools/upload-media-from-url.js +26 -0
  78. package/dist/tools/webhook-events.d.ts +3 -0
  79. package/dist/tools/webhook-events.js +13 -0
  80. package/package.json +1 -1
  81. package/src/generated/platform-manifest.ts +695 -0
  82. package/src/index.ts +481 -1
  83. package/src/lib/api-client.ts +461 -0
  84. package/src/tools/audit-google-business-profile.ts +38 -0
  85. package/src/tools/create-oauth-client.ts +36 -0
  86. package/src/tools/create-post.ts +5 -17
  87. package/src/tools/create-signed-upload.ts +27 -0
  88. package/src/tools/create-webhook.ts +35 -0
  89. package/src/tools/delete-google-business-review-reply.ts +30 -0
  90. package/src/tools/delete-oauth-client.ts +23 -0
  91. package/src/tools/delete-post-group.ts +17 -0
  92. package/src/tools/delete-webhook.ts +18 -0
  93. package/src/tools/disconnect-account.ts +30 -0
  94. package/src/tools/generate-video.ts +38 -0
  95. package/src/tools/get-connect-link.ts +56 -0
  96. package/src/tools/get-google-business-review-link.ts +30 -0
  97. package/src/tools/get-platform-schema.ts +52 -0
  98. package/src/tools/get-post-missing.ts +17 -0
  99. package/src/tools/get-video-job.ts +36 -0
  100. package/src/tools/get-video-options.ts +30 -0
  101. package/src/tools/get-x-posting-quota.ts +25 -0
  102. package/src/tools/list-activity.ts +51 -0
  103. package/src/tools/list-google-business-reviews.ts +43 -0
  104. package/src/tools/list-oauth-clients.ts +18 -0
  105. package/src/tools/list-platforms.ts +25 -0
  106. package/src/tools/list-posts.ts +3 -2
  107. package/src/tools/list-webhooks.ts +30 -0
  108. package/src/tools/reply-google-business-review.ts +32 -0
  109. package/src/tools/run-video-function.ts +32 -0
  110. package/src/tools/suggest-google-business-review-reply.ts +44 -0
  111. package/src/tools/test-webhook.ts +18 -0
  112. package/src/tools/trigger-platform-helper.ts +34 -0
  113. package/src/tools/update-oauth-client.ts +38 -0
  114. package/src/tools/update-post-release-id.ts +26 -0
  115. package/src/tools/update-post-status.ts +31 -0
  116. package/src/tools/update-post.ts +1 -1
  117. package/src/tools/update-webhook.ts +37 -0
  118. package/src/tools/upload-media-from-url.ts +33 -0
  119. package/src/tools/webhook-events.ts +16 -0
@@ -211,6 +211,172 @@ export interface PostAnalyticsResponse {
211
211
  offset: number;
212
212
  }
213
213
 
214
+ export interface GoogleBusinessReview {
215
+ name?: string;
216
+ reviewId?: string;
217
+ reviewer?: { displayName?: string; profilePhotoUrl?: string };
218
+ starRating: number;
219
+ comment?: string;
220
+ createTime?: string;
221
+ updateTime?: string;
222
+ reviewReply?: Record<string, unknown>;
223
+ locationId?: string | null;
224
+ locationName?: string | null;
225
+ businessName?: string | null;
226
+ socialAccountId?: number;
227
+ account_id?: number;
228
+ }
229
+
230
+ export interface GoogleBusinessReviewsResponse {
231
+ reviews: GoogleBusinessReview[];
232
+ total: number;
233
+ locations: Array<Record<string, unknown>>;
234
+ }
235
+
236
+ export interface GoogleBusinessReviewLinkResponse {
237
+ reviewLink: string;
238
+ placeId: string;
239
+ businessName: string;
240
+ locationId: string | null;
241
+ account_id: number;
242
+ }
243
+
244
+ export interface ActivityItem {
245
+ id: string;
246
+ type: 'activity' | 'publish';
247
+ post_id: number | null;
248
+ action?: string | null;
249
+ description?: string | null;
250
+ platform?: string | null;
251
+ outcome?: string | null;
252
+ error_code?: string | null;
253
+ error_message?: string | null;
254
+ http_status?: number | null;
255
+ retry_number?: number | null;
256
+ duration_ms?: number | null;
257
+ pipeline_step?: string | null;
258
+ metadata?: Record<string, unknown> | null;
259
+ context?: Record<string, unknown> | null;
260
+ created_at: string;
261
+ }
262
+
263
+ export interface ActivityResponse {
264
+ items: ActivityItem[];
265
+ total: number;
266
+ limit: number;
267
+ }
268
+
269
+ export interface ApiWebhook {
270
+ id: string;
271
+ workspace_id: string | null;
272
+ url: string;
273
+ description: string | null;
274
+ events: string[];
275
+ is_active: boolean;
276
+ created_at: string;
277
+ updated_at: string | null;
278
+ last_delivery_at: string | null;
279
+ last_delivery_status: number | null;
280
+ last_error: string | null;
281
+ }
282
+
283
+ export interface WebhookResponse {
284
+ webhook: ApiWebhook;
285
+ secret?: string;
286
+ }
287
+
288
+ export interface PlatformSchema {
289
+ id: string;
290
+ aliases: string[];
291
+ label: string;
292
+ status: 'supported' | 'planned';
293
+ summary: string;
294
+ content_limit: number | null;
295
+ media: Record<string, unknown>;
296
+ post_types: string[];
297
+ analytics_supported: boolean;
298
+ settings_schema: {
299
+ fields: Array<Record<string, unknown>>;
300
+ example: Record<string, unknown>;
301
+ };
302
+ helper_tools: Array<Record<string, unknown>>;
303
+ native_features: string[];
304
+ }
305
+
306
+ export interface ConnectOption {
307
+ platform: string;
308
+ aliases: string[];
309
+ label: string;
310
+ status: 'supported' | 'planned';
311
+ method: 'dashboard_oauth' | 'manual_credentials' | 'planned';
312
+ provider_platforms: string[];
313
+ dashboard_url: string;
314
+ connection_url: string | null;
315
+ oauth_url: string | null;
316
+ supports_direct_oauth_url: boolean;
317
+ requires_browser_session: boolean;
318
+ configured: boolean | null;
319
+ env_requirements: Array<Record<string, unknown>>;
320
+ missing_env: string[][];
321
+ scopes: string[];
322
+ credential_fields: Array<Record<string, unknown>>;
323
+ notes: string[];
324
+ connected_count?: number;
325
+ connected_accounts?: Account[];
326
+ }
327
+
328
+ export interface OAuthDeveloperClient {
329
+ id: string;
330
+ client_id: string;
331
+ client_name: string;
332
+ description?: string | null;
333
+ homepage?: string | null;
334
+ allowed_redirect_uris: string[];
335
+ default_scopes: string[];
336
+ is_active: boolean;
337
+ created_at?: string;
338
+ updated_at?: string;
339
+ }
340
+
341
+ export interface VideoJob {
342
+ id: string;
343
+ status: string;
344
+ prompt: string;
345
+ model: string;
346
+ duration_seconds: number;
347
+ aspect_ratio?: string;
348
+ resolution?: string;
349
+ generation_mode?: string | null;
350
+ credit_cost: number;
351
+ included_credit_cost?: number;
352
+ purchased_credit_cost?: number;
353
+ source_image_url?: string | null;
354
+ end_image_url?: string | null;
355
+ source_video_url?: string | null;
356
+ reference_images?: unknown;
357
+ generate_audio?: boolean | null;
358
+ video_url?: string | null;
359
+ error_message?: string | null;
360
+ created_at: string;
361
+ started_at?: string | null;
362
+ completed_at?: string | null;
363
+ }
364
+
365
+ export interface XPostingQuotaResponse {
366
+ workspace_id: string | null;
367
+ tier: string | null;
368
+ base_posts: number;
369
+ addon_posts: number;
370
+ total_posts: number;
371
+ used_posts: number;
372
+ remaining_posts: number;
373
+ current_period_start: string;
374
+ current_period_end: string;
375
+ urls_blocked: boolean;
376
+ addon?: Record<string, unknown>;
377
+ plans?: Array<Record<string, unknown>>;
378
+ }
379
+
214
380
  export class PosterlyClient {
215
381
  private baseUrl: string;
216
382
  private apiKey: string;
@@ -265,6 +431,58 @@ export class PosterlyClient {
265
431
  return data.accounts;
266
432
  }
267
433
 
434
+ async disconnectAccount(accountId: string): Promise<Record<string, any>> {
435
+ return this.request('DELETE', `/accounts/${encodeURIComponent(accountId)}`);
436
+ }
437
+
438
+ async getConnectLink(params?: {
439
+ platform?: string;
440
+ workspace_id?: string;
441
+ include_planned?: boolean;
442
+ }): Promise<{ connect?: ConnectOption; connect_options?: ConnectOption[] }> {
443
+ const searchParams = new URLSearchParams();
444
+ if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
445
+ if (params?.include_planned) searchParams.set('include_planned', 'true');
446
+ const qs = searchParams.toString();
447
+ const path = params?.platform
448
+ ? `/connect/${encodeURIComponent(params.platform)}`
449
+ : '/connect';
450
+ return this.request('GET', `${path}${qs ? `?${qs}` : ''}`);
451
+ }
452
+
453
+ async listOAuthClients(): Promise<{ clients: OAuthDeveloperClient[] }> {
454
+ return this.request('GET', '/oauth/clients');
455
+ }
456
+
457
+ async createOAuthClient(data: {
458
+ client_name: string;
459
+ description?: string;
460
+ homepage?: string;
461
+ allowed_redirect_uris: string[];
462
+ default_scopes: string[];
463
+ is_active?: boolean;
464
+ }): Promise<{ client: OAuthDeveloperClient }> {
465
+ return this.request('POST', '/oauth/clients', data);
466
+ }
467
+
468
+ async updateOAuthClient(
469
+ clientId: string,
470
+ data: {
471
+ client_name?: string;
472
+ description?: string;
473
+ homepage?: string | null;
474
+ allowed_redirect_uris?: string[];
475
+ default_scopes?: string[];
476
+ is_active?: boolean;
477
+ },
478
+ ): Promise<{ client: OAuthDeveloperClient }> {
479
+ return this.request('PATCH', `/oauth/clients/${encodeURIComponent(clientId)}`, data);
480
+ }
481
+
482
+ async deleteOAuthClient(clientId: string): Promise<{ deleted: boolean; client_id: string }> {
483
+ return this.request('DELETE', `/oauth/clients/${encodeURIComponent(clientId)}`);
484
+ }
485
+
268
486
  async listBrands(params?: { workspace_id?: string }): Promise<Brand[]> {
269
487
  const searchParams = new URLSearchParams();
270
488
  if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
@@ -318,6 +536,10 @@ export class PosterlyClient {
318
536
  return this.request('GET', `/posts/${id}`);
319
537
  }
320
538
 
539
+ async getPostMissing(id: number): Promise<Record<string, any>> {
540
+ return this.request('GET', `/posts/${id}/missing`);
541
+ }
542
+
321
543
  async updatePost(
322
544
  id: number,
323
545
  data: {
@@ -340,6 +562,24 @@ export class PosterlyClient {
340
562
  return this.request('DELETE', `/posts/${id}`);
341
563
  }
342
564
 
565
+ async updatePostStatus(
566
+ id: number,
567
+ data: { status: 'scheduled' | 'paused' | 'draft'; scheduled_at?: string },
568
+ ): Promise<Record<string, any>> {
569
+ return this.request('PUT', `/posts/${id}/status`, data);
570
+ }
571
+
572
+ async updatePostReleaseId(
573
+ id: number,
574
+ data: { release_id: string; group_id?: string },
575
+ ): Promise<Record<string, any>> {
576
+ return this.request('PUT', `/posts/${id}/release-id`, data);
577
+ }
578
+
579
+ async deletePostGroup(groupId: string): Promise<Record<string, any>> {
580
+ return this.request('DELETE', `/posts/group/${encodeURIComponent(groupId)}?confirm=true`);
581
+ }
582
+
343
583
  async generateImage(data: {
344
584
  prompt: string;
345
585
  aspect_ratio?: string;
@@ -364,6 +604,49 @@ export class PosterlyClient {
364
604
  return this.request('POST', '/ai/generate-image', data);
365
605
  }
366
606
 
607
+ async getVideoOptions(): Promise<Record<string, any>> {
608
+ return this.request('GET', '/ai/video-options');
609
+ }
610
+
611
+ async runVideoFunction(data: {
612
+ identifier: string;
613
+ functionName: string;
614
+ params?: Record<string, unknown>;
615
+ }): Promise<Record<string, any>> {
616
+ return this.request('POST', '/ai/video-function', data);
617
+ }
618
+
619
+ async generateVideo(data: {
620
+ prompt: string;
621
+ negative_prompt?: string;
622
+ aspect_ratio?: '16:9' | '9:16';
623
+ duration_seconds?: 4 | 6 | 8;
624
+ resolution?: '720p' | '1080p';
625
+ model?: 'fast' | 'standard';
626
+ image_url?: string;
627
+ end_image_url?: string;
628
+ reference_images?: Array<{ url: string; type: 'character' | 'object' | 'scene' }>;
629
+ generate_audio?: boolean;
630
+ source_video_url?: string;
631
+ }): Promise<Record<string, any>> {
632
+ return this.request('POST', '/ai/generate-video', data);
633
+ }
634
+
635
+ async getVideoJob(params?: {
636
+ job_id?: string;
637
+ status?: string;
638
+ limit?: number;
639
+ }): Promise<{ job?: VideoJob; jobs?: VideoJob[] }> {
640
+ if (params?.job_id) {
641
+ return this.request('GET', `/ai/video-jobs/${encodeURIComponent(params.job_id)}`);
642
+ }
643
+ const searchParams = new URLSearchParams();
644
+ if (params?.status) searchParams.set('status', params.status);
645
+ if (params?.limit) searchParams.set('limit', String(params.limit));
646
+ const qs = searchParams.toString();
647
+ return this.request('GET', `/ai/video-jobs${qs ? `?${qs}` : ''}`);
648
+ }
649
+
367
650
  async findAvailableSlots(params?: {
368
651
  account_ids?: string[];
369
652
  timezone?: string;
@@ -409,6 +692,172 @@ export class PosterlyClient {
409
692
  return this.request('GET', `/analytics/posts?${searchParams.toString()}`);
410
693
  }
411
694
 
695
+ async listGoogleBusinessReviews(params?: {
696
+ workspace_id?: string;
697
+ account_id?: number;
698
+ location_id?: string;
699
+ rating?: number;
700
+ unanswered?: boolean;
701
+ }): Promise<GoogleBusinessReviewsResponse> {
702
+ const searchParams = new URLSearchParams();
703
+ if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
704
+ if (params?.account_id) searchParams.set('account_id', String(params.account_id));
705
+ if (params?.location_id) searchParams.set('location_id', params.location_id);
706
+ if (params?.rating) searchParams.set('rating', String(params.rating));
707
+ if (params?.unanswered) searchParams.set('unanswered', 'true');
708
+ const qs = searchParams.toString();
709
+ return this.request('GET', `/google-business/reviews${qs ? `?${qs}` : ''}`);
710
+ }
711
+
712
+ async getGoogleBusinessReviewLink(params: {
713
+ workspace_id?: string;
714
+ account_id?: number;
715
+ location_id?: string;
716
+ }): Promise<GoogleBusinessReviewLinkResponse> {
717
+ const searchParams = new URLSearchParams();
718
+ if (params.workspace_id) searchParams.set('workspace_id', params.workspace_id);
719
+ if (params.account_id) searchParams.set('account_id', String(params.account_id));
720
+ if (params.location_id) searchParams.set('location_id', params.location_id);
721
+ return this.request('GET', `/google-business/review-link?${searchParams.toString()}`);
722
+ }
723
+
724
+ async auditGoogleBusinessProfile(params: {
725
+ workspace_id?: string;
726
+ account_id?: number;
727
+ location_id?: string;
728
+ }): Promise<Record<string, any>> {
729
+ const searchParams = new URLSearchParams();
730
+ if (params.workspace_id) searchParams.set('workspace_id', params.workspace_id);
731
+ if (params.account_id) searchParams.set('account_id', String(params.account_id));
732
+ if (params.location_id) searchParams.set('location_id', params.location_id);
733
+ return this.request('GET', `/google-business/audit?${searchParams.toString()}`);
734
+ }
735
+
736
+ async suggestGoogleBusinessReviewReply(data: {
737
+ workspace_id?: string;
738
+ account_id?: number;
739
+ location_id?: string;
740
+ review_text?: string;
741
+ star_rating: number;
742
+ reviewer_name?: string;
743
+ business_name?: string;
744
+ tone?: 'professional' | 'friendly' | 'apologetic';
745
+ count?: number;
746
+ }): Promise<Record<string, any>> {
747
+ return this.request('POST', '/google-business/reviews/suggest-reply', data);
748
+ }
749
+
750
+ async replyGoogleBusinessReview(data: {
751
+ workspace_id?: string;
752
+ account_id?: number;
753
+ location_id?: string;
754
+ review_name: string;
755
+ comment: string;
756
+ }): Promise<Record<string, any>> {
757
+ return this.request('POST', '/google-business/reviews/reply', data);
758
+ }
759
+
760
+ async deleteGoogleBusinessReviewReply(data: {
761
+ workspace_id?: string;
762
+ account_id?: number;
763
+ location_id?: string;
764
+ review_name: string;
765
+ }): Promise<Record<string, any>> {
766
+ return this.request('DELETE', '/google-business/reviews/reply', data);
767
+ }
768
+
769
+ async listActivity(params?: {
770
+ workspace_id?: string;
771
+ post_id?: number;
772
+ since?: string;
773
+ type?: 'all' | 'activity' | 'publish';
774
+ limit?: number;
775
+ }): Promise<ActivityResponse> {
776
+ const searchParams = new URLSearchParams();
777
+ if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
778
+ if (params?.post_id) searchParams.set('post_id', String(params.post_id));
779
+ if (params?.since) searchParams.set('since', params.since);
780
+ if (params?.type) searchParams.set('type', params.type);
781
+ if (params?.limit) searchParams.set('limit', String(params.limit));
782
+ const qs = searchParams.toString();
783
+ return this.request('GET', `/activity${qs ? `?${qs}` : ''}`);
784
+ }
785
+
786
+ async listWebhooks(params?: { workspace_id?: string }): Promise<{ webhooks: ApiWebhook[] }> {
787
+ const searchParams = new URLSearchParams();
788
+ if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
789
+ const qs = searchParams.toString();
790
+ return this.request('GET', `/webhooks${qs ? `?${qs}` : ''}`);
791
+ }
792
+
793
+ async createWebhook(data: {
794
+ url: string;
795
+ events?: string[];
796
+ workspace_id?: string;
797
+ description?: string;
798
+ is_active?: boolean;
799
+ }): Promise<WebhookResponse> {
800
+ return this.request('POST', '/webhooks', data);
801
+ }
802
+
803
+ async updateWebhook(
804
+ id: string,
805
+ data: {
806
+ url?: string;
807
+ events?: string[];
808
+ workspace_id?: string | null;
809
+ description?: string | null;
810
+ is_active?: boolean;
811
+ },
812
+ ): Promise<WebhookResponse> {
813
+ return this.request('PATCH', `/webhooks/${encodeURIComponent(id)}`, data);
814
+ }
815
+
816
+ async deleteWebhook(id: string): Promise<{ deleted: boolean; id: string }> {
817
+ return this.request('DELETE', `/webhooks/${encodeURIComponent(id)}`);
818
+ }
819
+
820
+ async testWebhook(id: string): Promise<{ delivery: Record<string, unknown> }> {
821
+ return this.request('POST', `/webhooks/${encodeURIComponent(id)}/test`, {});
822
+ }
823
+
824
+ async listPlatforms(params?: { include_planned?: boolean }): Promise<{ platforms: PlatformSchema[] }> {
825
+ const searchParams = new URLSearchParams();
826
+ if (params?.include_planned) searchParams.set('include_planned', 'true');
827
+ const qs = searchParams.toString();
828
+ return this.request('GET', `/platforms${qs ? `?${qs}` : ''}`);
829
+ }
830
+
831
+ async getPlatformSchema(params: {
832
+ account_id?: string;
833
+ platform?: string;
834
+ include_planned?: boolean;
835
+ }): Promise<{ account?: Record<string, unknown>; schema?: PlatformSchema; platform?: PlatformSchema; platforms?: PlatformSchema[] }> {
836
+ if (params.account_id) {
837
+ return this.request('GET', `/accounts/${encodeURIComponent(params.account_id)}/schema`);
838
+ }
839
+ const searchParams = new URLSearchParams();
840
+ if (params.platform) searchParams.set('platform', params.platform);
841
+ if (params.include_planned) searchParams.set('include_planned', 'true');
842
+ return this.request('GET', `/platforms?${searchParams.toString()}`);
843
+ }
844
+
845
+ async triggerPlatformHelper(data: {
846
+ helper: string;
847
+ account_id?: string;
848
+ platform?: string;
849
+ params?: Record<string, unknown>;
850
+ }): Promise<{ helper: string; account: Record<string, unknown> | null; result: unknown }> {
851
+ return this.request('POST', '/platforms/trigger', data);
852
+ }
853
+
854
+ async getXPostingQuota(params?: { workspace_id?: string }): Promise<XPostingQuotaResponse> {
855
+ const searchParams = new URLSearchParams();
856
+ if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
857
+ const qs = searchParams.toString();
858
+ return this.request('GET', `/x-posting/quota${qs ? `?${qs}` : ''}`);
859
+ }
860
+
412
861
  async getSignedUploadUrl(
413
862
  filename: string,
414
863
  contentType: string,
@@ -476,6 +925,18 @@ export class PosterlyClient {
476
925
  content_type: contentType,
477
926
  });
478
927
  }
928
+
929
+ async uploadMediaFromUrl(input: {
930
+ url: string;
931
+ filename?: string;
932
+ contentType?: string;
933
+ }): Promise<{ url: string; path: string; source_url: string; content_type: string; size: number }> {
934
+ return this.request('POST', '/media/upload-from-url', {
935
+ url: input.url,
936
+ filename: input.filename,
937
+ content_type: input.contentType,
938
+ });
939
+ }
479
940
  }
480
941
 
481
942
  function guessContentType(filename: string): string {
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+
4
+ export const auditGoogleBusinessProfileTool = {
5
+ name: 'audit_google_business_profile',
6
+ description:
7
+ 'Run a live local-profile audit for a connected Google Business Profile location, including completeness, categories, media, attributes, reviews, and recommendations.',
8
+ inputSchema: z.object({
9
+ workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
10
+ account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts.'),
11
+ location_id: z.string().optional().describe('Google Business location/platform_user_id.'),
12
+ }),
13
+
14
+ async execute(
15
+ client: PosterlyClient,
16
+ input: {
17
+ workspace_id?: string;
18
+ account_id?: number;
19
+ location_id?: string;
20
+ },
21
+ ) {
22
+ const result = await client.auditGoogleBusinessProfile(input);
23
+ const audit = result.audit || {};
24
+ const recommendations = Array.isArray(audit.recommendations) ? audit.recommendations : [];
25
+ const lines = [
26
+ `Google Business audit: ${audit.businessName || 'Unknown business'}`,
27
+ `Score: ${audit.overallScore ?? 'n/a'} (${audit.overallGrade || 'n/a'})`,
28
+ `Fetched: ${result.fetchedAt || 'unknown'}`,
29
+ ];
30
+ if (recommendations.length > 0) {
31
+ lines.push('Top recommendations:');
32
+ for (const rec of recommendations.slice(0, 5)) {
33
+ lines.push(`- ${rec.priority || 'medium'}: ${rec.description || rec.title || 'Recommendation'}`);
34
+ }
35
+ }
36
+ return lines.join('\n');
37
+ },
38
+ };
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+
4
+ const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read']);
5
+
6
+ export const createOAuthClientTool = {
7
+ name: 'create_oauth_client',
8
+ description:
9
+ 'Create a public OAuth 2.1 + PKCE developer client. WRITE: confirm app name, redirect URIs, and scopes with the user before calling.',
10
+ inputSchema: z.object({
11
+ client_name: z.string().min(1).max(120),
12
+ description: z.string().max(500).optional(),
13
+ homepage: z.string().url().optional(),
14
+ allowed_redirect_uris: z.array(z.string().url()).min(1).max(10),
15
+ default_scopes: z.array(scopeSchema).min(1),
16
+ is_active: z.boolean().optional(),
17
+ confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
18
+ }),
19
+
20
+ async execute(
21
+ client: PosterlyClient,
22
+ input: {
23
+ client_name: string;
24
+ description?: string;
25
+ homepage?: string;
26
+ allowed_redirect_uris: string[];
27
+ default_scopes: string[];
28
+ is_active?: boolean;
29
+ confirm: true;
30
+ },
31
+ ) {
32
+ const { confirm: _confirm, ...payload } = input;
33
+ const result = await client.createOAuthClient(payload);
34
+ return `OAuth client created: ${result.client.client_name}\nclient_id: ${result.client.client_id}\nRedirect URIs: ${result.client.allowed_redirect_uris.join(', ')}`;
35
+ },
36
+ };
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import type { PosterlyClient } from '../lib/api-client.js';
3
+ import { SUPPORTED_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
3
4
 
4
5
  export const instagramSettingsSchema = z.object({
5
6
  __type: z.enum(['instagram', 'instagram-standalone']).optional(),
@@ -75,9 +76,9 @@ export const createPostInputSchema = z.object({
75
76
  account_id: z.string().optional().describe('Social account ID (from list_accounts)'),
76
77
  username: z.string().optional().describe('Account username (alternative to account_id)'),
77
78
  platform: z
78
- .string()
79
+ .enum(SUPPORTED_PLATFORM_INPUTS)
79
80
  .optional()
80
- .describe('Platform name (required with username): instagram, twitter, linkedin, facebook, tiktok, threads, youtube, pinterest, google_business'),
81
+ .describe(`Platform name (required with username). Supported: ${SUPPORTED_PLATFORM_IDS.join(', ')}. Aliases such as x, gmb, and instagram-standalone are accepted.`),
81
82
  caption: z.string().optional().describe('The post caption/text content. Ignored when `thread_posts` is provided.'),
82
83
  scheduled_at: z
83
84
  .string()
@@ -164,25 +165,12 @@ export const createPostTool = {
164
165
  '4. If scheduling multiple posts in one turn, list every post first and confirm the batch as a whole, then prefer `create_posts_batch` so they are created in one API request.\n\n' +
165
166
  'Provide either account_id OR username+platform to identify the account. If scheduled_at is omitted, the post publishes immediately. If workspace_id is omitted, the server resolves one from the social account, falling back to the caller\'s default (personal) workspace — pass workspace_id explicitly if the user has more than one workspace.\n\n' +
166
167
  'THREADS: Pass `thread_posts` (an array of 2+ strings) to schedule a multi-post thread on X (Twitter) or Threads (Meta). The first entry is the lead post; the rest are published as replies in the same chain. X entries are capped at 280 characters each (4000 for verified, 25000 for organization accounts); Threads entries are capped at 500 characters each. When `thread_posts` is set, `caption` is ignored.\n\n' +
167
- 'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/playlist, LinkedIn document titles/mentions/video thumbnails, TikTok direct-post privacy/toggles/commercial disclosure, Pinterest board/link/title/video cover, GBP event/offer/CTA, X reply settings/polls, Threads reply controls/text attachments, Bluesky languages/alt text, and Instagram feed/story/reel/carousel/collaborators/first comments/trial Reels. `instagram_settings` remains as a backwards-compatible alias.',
168
+ 'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/playlist, LinkedIn document titles/mentions/video thumbnails, TikTok direct-post privacy/toggles/commercial disclosure, Pinterest post_type (image/video/carousel), board (from pinterest.boards helper), title, link, and cover_image_url (video Pins), GBP event/offer/CTA, X reply settings/polls, Threads reply controls/text attachments, Bluesky languages/alt text, and Instagram feed/story/reel/carousel/collaborators/first comments/trial Reels. `instagram_settings` remains as a backwards-compatible alias.',
168
169
  inputSchema: createPostInputSchema,
169
170
 
170
171
  async execute(
171
172
  client: PosterlyClient,
172
- input: {
173
- account_id?: string;
174
- username?: string;
175
- platform?: string;
176
- caption?: string;
177
- scheduled_at?: string;
178
- media_url?: string;
179
- media_urls?: string[];
180
- post_type?: string;
181
- thread_posts?: string[];
182
- instagram_settings?: z.infer<typeof instagramSettingsSchema>;
183
- platform_settings?: z.infer<typeof platformSettingsSchema>;
184
- workspace_id?: string;
185
- }
173
+ input: CreatePostInput,
186
174
  ) {
187
175
  const payload = buildCreatePostPayload(input);
188
176
  const { thread_posts, instagram_settings, platform_settings } = input;
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+
4
+ export const createSignedUploadTool = {
5
+ name: 'create_signed_upload',
6
+ description:
7
+ 'Create a signed direct-upload URL for a larger image or video. Upload the binary to upload_url, then use public_url with create_post.',
8
+ inputSchema: z.object({
9
+ filename: z.string().describe('Filename with extension, e.g. launch-video.mp4'),
10
+ content_type: z.string().describe('MIME type, e.g. video/mp4'),
11
+ size: z.number().int().positive().describe('File size in bytes'),
12
+ }),
13
+
14
+ async execute(
15
+ client: PosterlyClient,
16
+ input: { filename: string; content_type: string; size: number },
17
+ ) {
18
+ const signed = await client.getSignedUploadUrl(input.filename, input.content_type, input.size);
19
+ return [
20
+ 'Signed upload created.',
21
+ `• Upload URL: ${signed.upload_url}`,
22
+ `• Token: ${signed.token}`,
23
+ `• Storage path: ${signed.path}`,
24
+ `• Public URL after upload: ${signed.public_url}`,
25
+ ].join('\n');
26
+ },
27
+ };
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ import { webhookEventSchema } from './webhook-events.js';
4
+
5
+ export const createWebhookTool = {
6
+ name: 'create_webhook',
7
+ description:
8
+ 'Create a webhook subscription for post/account/analytics events. WRITE WITH OUTBOUND SIDE EFFECTS: show the user the target URL, workspace, events, and active state, then get explicit confirmation before calling. The response includes the signing secret once.',
9
+ inputSchema: z.object({
10
+ url: z.string().url().describe('HTTPS webhook endpoint URL. Localhost HTTP is allowed by the API for development.'),
11
+ events: z.array(webhookEventSchema).optional().describe('Events to deliver. Defaults to core post lifecycle events.'),
12
+ workspace_id: z.string().optional().describe('Optional workspace filter. Omit for all workspaces owned by the API key user.'),
13
+ description: z.string().max(240).optional(),
14
+ is_active: z.boolean().optional().describe('Defaults to true.'),
15
+ confirm: z.literal(true).describe('Must be true after the user explicitly confirms webhook creation.'),
16
+ }),
17
+
18
+ async execute(
19
+ client: PosterlyClient,
20
+ input: {
21
+ url: string;
22
+ events?: string[];
23
+ workspace_id?: string;
24
+ description?: string;
25
+ is_active?: boolean;
26
+ confirm: true;
27
+ },
28
+ ) {
29
+ const { confirm: _confirm, ...payload } = input;
30
+ const result = await client.createWebhook(payload);
31
+ const secret = result.secret ? `\nSigning secret: ${result.secret}` : '';
32
+ return `Webhook created: ${result.webhook.id}\nURL: ${result.webhook.url}\nEvents: ${result.webhook.events.join(', ')}${secret}`;
33
+ },
34
+ };
35
+