posterly-mcp-server 0.19.7 → 0.19.9

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 (56) hide show
  1. package/README.md +9 -2
  2. package/dist/generated/platform-manifest.d.ts +1 -1
  3. package/dist/generated/platform-manifest.js +2 -1
  4. package/dist/index.js +11 -1
  5. package/dist/lib/api-client.d.ts +30 -0
  6. package/dist/lib/api-client.js +3 -0
  7. package/dist/lib/format.d.ts +21 -0
  8. package/dist/lib/format.js +125 -0
  9. package/dist/tools/create-connect-session.d.ts +2 -2
  10. package/dist/tools/create-post.d.ts +17 -4
  11. package/dist/tools/create-post.js +5 -3
  12. package/dist/tools/create-posts-batch.d.ts +11 -4
  13. package/dist/tools/find-slot.js +10 -3
  14. package/dist/tools/generate-captions.d.ts +76 -0
  15. package/dist/tools/generate-captions.js +69 -0
  16. package/dist/tools/generate-image.d.ts +2 -2
  17. package/dist/tools/generate-image.js +17 -19
  18. package/dist/tools/get-brand-profile.js +34 -40
  19. package/dist/tools/get-brand.js +14 -16
  20. package/dist/tools/get-connect-link.d.ts +2 -2
  21. package/dist/tools/get-platform-schema.d.ts +2 -2
  22. package/dist/tools/get-video-job.d.ts +2 -2
  23. package/dist/tools/list-accounts.js +12 -4
  24. package/dist/tools/list-brand-accounts.js +11 -3
  25. package/dist/tools/list-brands.js +12 -11
  26. package/dist/tools/list-posts.d.ts +2 -2
  27. package/dist/tools/suggest-google-business-review-reply.d.ts +2 -2
  28. package/dist/tools/trigger-platform-helper.d.ts +2 -2
  29. package/dist/tools/update-post.js +10 -4
  30. package/dist/tools/upload-media.js +6 -2
  31. package/dist/tools/whoami.js +18 -17
  32. package/package.json +1 -1
  33. package/src/generated/platform-manifest.ts +2 -1
  34. package/src/index.ts +16 -1
  35. package/src/lib/api-client.ts +45 -0
  36. package/src/lib/format.ts +133 -0
  37. package/src/tools/create-post.ts +5 -3
  38. package/src/tools/create-webhook.ts +0 -1
  39. package/src/tools/delete-webhook.ts +0 -1
  40. package/src/tools/find-slot.ts +13 -5
  41. package/src/tools/generate-captions.ts +77 -0
  42. package/src/tools/generate-image.ts +20 -20
  43. package/src/tools/get-brand-profile.ts +34 -38
  44. package/src/tools/get-brand.ts +14 -14
  45. package/src/tools/get-post-missing.ts +0 -1
  46. package/src/tools/list-accounts.ts +15 -6
  47. package/src/tools/list-activity.ts +0 -1
  48. package/src/tools/list-brand-accounts.ts +14 -6
  49. package/src/tools/list-brands.ts +16 -13
  50. package/src/tools/list-webhooks.ts +0 -1
  51. package/src/tools/update-post-release-id.ts +0 -1
  52. package/src/tools/update-post.ts +10 -5
  53. package/src/tools/update-webhook.ts +0 -1
  54. package/src/tools/upload-media.ts +6 -2
  55. package/src/tools/webhook-events.ts +0 -1
  56. package/src/tools/whoami.ts +21 -23
package/README.md CHANGED
@@ -10,10 +10,11 @@ This package gives Claude Desktop, Cursor, Windsurf, Cline, and other local MCP
10
10
  - resolve brands/clients into the right accounts
11
11
  - schedule and manage posts
12
12
  - upload media
13
+ - generate captions
13
14
  - generate images
14
15
  - read account and post analytics
15
16
 
16
- Posterly also exposes the same toolset over HTTP at [poster.ly/mcp](https://www.poster.ly/mcp), but this npm package is the local `stdio` transport.
17
+ Posterly also exposes the same authenticated toolset over HTTP at [poster.ly/mcp](https://www.poster.ly/mcp), but this npm package is the local `stdio` transport.
17
18
 
18
19
  ## Requirements
19
20
 
@@ -63,6 +64,10 @@ After paid signup is complete and Posterly shows an API key, add `POSTERLY_API_K
63
64
  5. Restart your AI client.
64
65
  6. Ask the AI to call `whoami`, then continue by connecting your first social account.
65
66
 
67
+ The signup and connect tools return user-facing next steps by default, so agents should report progress in plain language instead of showing raw curl, HTTP payloads, or JSON. Pass `debug: true` to `start_signup`, `get_signup_session`, `get_connect_link`, `create_connect_session`, or `get_connect_session` only when troubleshooting.
68
+
69
+ Post tools return `View in Posterly` dashboard links. After scheduling, listing, reading, or deleting posts, share the returned link with the user. Current-month scheduled posts open in Calendar with the post selected; broader/future post views use Table.
70
+
66
71
  ## Example configs
67
72
 
68
73
  ### Claude Desktop
@@ -103,7 +108,7 @@ Add the same server definition to your Cursor MCP settings:
103
108
 
104
109
  ## Available tools
105
110
 
106
- `posterly-mcp-server@0.19.7` exposes 54 tools.
111
+ `posterly-mcp-server@0.19.9` exposes 55 tools.
107
112
 
108
113
  Public signup tools work before `POSTERLY_API_KEY` exists:
109
114
 
@@ -144,6 +149,7 @@ Authenticated tools require `POSTERLY_API_KEY`:
144
149
  - `upload_media_from_url`
145
150
  - `create_signed_upload`
146
151
  - `find_available_slot`
152
+ - `generate_captions`
147
153
  - `generate_image`
148
154
  - `get_video_options`
149
155
  - `run_video_function` (read-only Veo helpers for cost estimation and request validation)
@@ -198,6 +204,7 @@ much more reliable than forcing the agent to guess from raw account handles alon
198
204
  - `Schedule this as an Instagram Story with a first comment and @partner as collaborator`
199
205
  - `Schedule this YouTube video as unlisted, add the thumbnail URL, and put it in our launch playlist`
200
206
  - `Post this TikTok with direct-post privacy set to public and stitch disabled`
207
+ - `Schedule these 5 photos as a TikTok` (image posts auto-detect as a photo slideshow of 1 to 35 images, no post type needed)
201
208
  - `How did Grassroots perform on Instagram in the last 30 days?`
202
209
 
203
210
  ## Pricing
@@ -93,7 +93,7 @@ export declare const PLATFORM_MANIFEST: {
93
93
  readonly label: "TikTok";
94
94
  readonly status: "supported";
95
95
  readonly aliases: readonly [];
96
- readonly postTypes: readonly ["video", "image"];
96
+ readonly postTypes: readonly ["video", "image", "carousel"];
97
97
  readonly analyticsSupported: false;
98
98
  readonly helpers: readonly ["tiktok.creator_info"];
99
99
  }, {
@@ -279,7 +279,8 @@ export const PLATFORM_MANIFEST = {
279
279
  "aliases": [],
280
280
  "postTypes": [
281
281
  "video",
282
- "image"
282
+ "image",
283
+ "carousel"
283
284
  ],
284
285
  "analyticsSupported": false,
285
286
  "helpers": [
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ import { updatePostReleaseIdTool } from './tools/update-post-release-id.js';
24
24
  import { deletePostTool } from './tools/delete-post.js';
25
25
  import { deletePostGroupTool } from './tools/delete-post-group.js';
26
26
  import { whoamiTool } from './tools/whoami.js';
27
+ import { generateCaptionsTool } from './tools/generate-captions.js';
27
28
  import { generateImageTool } from './tools/generate-image.js';
28
29
  import { getVideoOptionsTool } from './tools/get-video-options.js';
29
30
  import { runVideoFunctionTool } from './tools/run-video-function.js';
@@ -58,7 +59,7 @@ import { updateOAuthClientTool } from './tools/update-oauth-client.js';
58
59
  import { deleteOAuthClientTool } from './tools/delete-oauth-client.js';
59
60
  const server = new McpServer({
60
61
  name: 'posterly',
61
- version: '0.19.7',
62
+ version: '0.19.8',
62
63
  });
63
64
  const client = new PosterlyClient();
64
65
  // Register tools
@@ -341,6 +342,15 @@ server.tool(getVideoJobTool.name, getVideoJobTool.description, getVideoJobTool.i
341
342
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
342
343
  }
343
344
  });
345
+ server.tool(generateCaptionsTool.name, generateCaptionsTool.description, generateCaptionsTool.inputSchema.shape, async (input) => {
346
+ try {
347
+ const text = await generateCaptionsTool.execute(client, input);
348
+ return { content: [{ type: 'text', text }] };
349
+ }
350
+ catch (err) {
351
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
352
+ }
353
+ });
344
354
  server.tool(generateImageTool.name, generateImageTool.description, generateImageTool.inputSchema.shape, async (input) => {
345
355
  try {
346
356
  const text = await generateImageTool.execute(client, input);
@@ -120,6 +120,35 @@ export type BatchCreatePostsResponse = {
120
120
  replayed: number;
121
121
  failed: number;
122
122
  };
123
+ export type CaptionPlatform = 'instagram' | 'facebook' | 'tiktok' | 'twitter' | 'threads' | 'linkedin' | 'youtube' | 'pinterest' | 'google_business';
124
+ export type GenerateCaptionsPayload = {
125
+ platforms: CaptionPlatform[];
126
+ brief?: string;
127
+ tone?: string;
128
+ allow_emojis?: boolean;
129
+ hashtag_strategy?: 'none' | 'smart' | 'minimal' | 'aggressive';
130
+ count?: number;
131
+ mode?: 'generate' | 'adapt';
132
+ source_caption?: string;
133
+ source_platform?: CaptionPlatform;
134
+ preserve_links?: boolean;
135
+ preserve_mentions?: boolean;
136
+ preserve_call_to_action?: boolean;
137
+ social_account_id?: string;
138
+ workspace_id?: string;
139
+ manual_brand_voice?: {
140
+ summary: string;
141
+ traits?: string[];
142
+ style_guidelines?: string[];
143
+ };
144
+ };
145
+ export type GenerateCaptionsResponse = {
146
+ success: boolean;
147
+ suggestions: Record<string, string[]>;
148
+ workspace_id?: string | null;
149
+ social_account_id?: number | null;
150
+ usage?: Record<string, unknown>;
151
+ };
123
152
  export interface Slot {
124
153
  time: string;
125
154
  local_time: string;
@@ -552,6 +581,7 @@ export declare class PosterlyClient {
552
581
  createPostsBatch(data: {
553
582
  posts: CreatePostPayload[];
554
583
  }): Promise<BatchCreatePostsResponse>;
584
+ generateCaptions(data: GenerateCaptionsPayload): Promise<GenerateCaptionsResponse>;
555
585
  getPost(id: number): Promise<{
556
586
  post: Post;
557
587
  }>;
@@ -143,6 +143,9 @@ export class PosterlyClient {
143
143
  async createPostsBatch(data) {
144
144
  return this.request('POST', '/posts/batch', data);
145
145
  }
146
+ async generateCaptions(data) {
147
+ return this.request('POST', '/ai/generate-captions', data);
148
+ }
146
149
  async getPost(id) {
147
150
  return this.request('GET', `/posts/${id}`);
148
151
  }
@@ -1,4 +1,5 @@
1
1
  import type { Account, Post } from './api-client.js';
2
+ type CellValue = string | number | boolean | null | undefined;
2
3
  export declare const DASHBOARD_CALENDAR_URL: string;
3
4
  export declare const DASHBOARD_TABLE_URL: string;
4
5
  export declare const DASHBOARD_CONNECT_URL: string;
@@ -9,3 +10,23 @@ export declare function dashboardUrlForPostList(posts: Array<Pick<Post, 'schedul
9
10
  export declare function truncateText(value: string | null | undefined, max?: number): string;
10
11
  export declare function formatAccountLabel(account: Account): string;
11
12
  export declare function formatConnectedAccounts(accounts?: Account[]): string;
13
+ export declare function mdTitle(title: string, subtitle?: string): string;
14
+ export declare function mdTable(headers: string[], rows: CellValue[][]): string;
15
+ export declare function mdKeyValue(rows: Array<[string, CellValue]>): string;
16
+ export declare function mdSection(title: string, body?: string): string;
17
+ export declare function mdBullets(items: Array<string | null | undefined>): string;
18
+ export declare function mdJson(title: string, value: unknown): string;
19
+ export declare function mdQuote(text: string): string;
20
+ export declare function mdEmpty(title: string, detail: string, nextStep?: string): string;
21
+ export declare function mdSuccess(title: string, rows: Array<[string, CellValue]>, nextStep?: string): string;
22
+ export declare function mdError(message: string): string;
23
+ export declare function formatNumber(value: number | null | undefined): string;
24
+ export declare function formatPercent(value: number | null | undefined): string;
25
+ export declare function formatDelta(value: number | null | undefined): string;
26
+ export declare function formatDateTime(value: string | null | undefined): string;
27
+ export declare function formatDate(value: string | null | undefined): string;
28
+ export declare function formatBytes(value: number | null | undefined): string;
29
+ export declare function compactText(value: string | null | undefined, maxLength?: number): string;
30
+ export declare function statusLabel(status: string | null | undefined): string;
31
+ export declare function code(value: CellValue): string;
32
+ export {};
@@ -40,6 +40,131 @@ export function formatConnectedAccounts(accounts) {
40
40
  return 'none yet';
41
41
  return accounts.map(formatAccountLabel).join(', ');
42
42
  }
43
+ export function mdTitle(title, subtitle) {
44
+ return [`## ${title}`, subtitle ? `_${subtitle}_` : ''].filter(Boolean).join('\n');
45
+ }
46
+ export function mdTable(headers, rows) {
47
+ const visibleRows = rows.filter((row) => row.some((cell) => cell !== undefined && cell !== null && cell !== ''));
48
+ if (visibleRows.length === 0)
49
+ return '';
50
+ const header = `| ${headers.map(escapeCell).join(' | ')} |`;
51
+ const divider = `| ${headers.map(() => '---').join(' | ')} |`;
52
+ const body = visibleRows.map((row) => `| ${row.map((cell) => escapeCell(formatCell(cell))).join(' | ')} |`);
53
+ return [header, divider, ...body].join('\n');
54
+ }
55
+ export function mdKeyValue(rows) {
56
+ return mdTable(['Field', 'Value'], rows);
57
+ }
58
+ export function mdSection(title, body) {
59
+ if (!body)
60
+ return '';
61
+ return [`### ${title}`, body].join('\n');
62
+ }
63
+ export function mdBullets(items) {
64
+ return items.filter(Boolean).map((item) => `- ${item}`).join('\n');
65
+ }
66
+ export function mdJson(title, value) {
67
+ return [`### ${title}`, '```json', JSON.stringify(value, null, 2), '```'].join('\n');
68
+ }
69
+ export function mdQuote(text) {
70
+ return text.split('\n').map((line) => `> ${line}`).join('\n');
71
+ }
72
+ export function mdEmpty(title, detail, nextStep) {
73
+ return [
74
+ mdTitle(`No ${title}`),
75
+ detail,
76
+ nextStep ? `\n**Next step:** ${nextStep}` : '',
77
+ ].filter(Boolean).join('\n');
78
+ }
79
+ export function mdSuccess(title, rows, nextStep) {
80
+ return [
81
+ mdTitle(`✅ ${title}`),
82
+ mdKeyValue(rows),
83
+ nextStep ? `\n**Next step:** ${nextStep}` : '',
84
+ ].filter(Boolean).join('\n');
85
+ }
86
+ export function mdError(message) {
87
+ return [
88
+ mdTitle('⚠️ Posterly tool error'),
89
+ `**Message:** ${message}`,
90
+ ].join('\n');
91
+ }
92
+ export function formatNumber(value) {
93
+ return value == null ? 'n/a' : value.toLocaleString();
94
+ }
95
+ export function formatPercent(value) {
96
+ return value == null ? 'n/a' : `${value.toLocaleString()}%`;
97
+ }
98
+ export function formatDelta(value) {
99
+ if (value == null)
100
+ return 'n/a';
101
+ return value >= 0 ? `+${value.toLocaleString()}` : value.toLocaleString();
102
+ }
103
+ export function formatDateTime(value) {
104
+ if (!value)
105
+ return 'n/a';
106
+ const date = new Date(value);
107
+ if (Number.isNaN(date.getTime()))
108
+ return value;
109
+ return date.toLocaleString();
110
+ }
111
+ export function formatDate(value) {
112
+ if (!value)
113
+ return 'n/a';
114
+ const date = new Date(value);
115
+ if (Number.isNaN(date.getTime()))
116
+ return value;
117
+ return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' });
118
+ }
119
+ export function formatBytes(value) {
120
+ if (value == null)
121
+ return 'n/a';
122
+ if (value < 1024)
123
+ return `${value} B`;
124
+ const units = ['KB', 'MB', 'GB', 'TB'];
125
+ let size = value / 1024;
126
+ let unitIndex = 0;
127
+ while (size >= 1024 && unitIndex < units.length - 1) {
128
+ size /= 1024;
129
+ unitIndex += 1;
130
+ }
131
+ return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unitIndex]}`;
132
+ }
133
+ export function compactText(value, maxLength = 90) {
134
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
135
+ if (!text)
136
+ return '';
137
+ return text.length > maxLength ? `${text.slice(0, Math.max(0, maxLength - 1))}…` : text;
138
+ }
139
+ export function statusLabel(status) {
140
+ const value = String(status || 'unknown').toLowerCase();
141
+ if (['created', 'success', 'succeeded', 'completed', 'published', 'active'].includes(value))
142
+ return `✅ ${status}`;
143
+ if (['scheduled', 'queued', 'pending', 'processing', 'opened', 'awaiting_provider', 'awaiting_credentials'].includes(value))
144
+ return `🕒 ${status}`;
145
+ if (['failed', 'error', 'inactive', 'deleted', 'cancelled', 'expired'].includes(value))
146
+ return `⚠️ ${status}`;
147
+ if (['draft', 'paused'].includes(value))
148
+ return `⏸️ ${status}`;
149
+ return status || 'unknown';
150
+ }
151
+ export function code(value) {
152
+ return `\`${formatCell(value).replace(/`/g, '\\`')}\``;
153
+ }
154
+ function formatCell(value) {
155
+ if (value === null || value === undefined || value === '')
156
+ return 'n/a';
157
+ if (typeof value === 'boolean')
158
+ return value ? 'yes' : 'no';
159
+ if (typeof value === 'number')
160
+ return value.toLocaleString();
161
+ return String(value);
162
+ }
163
+ function escapeCell(value) {
164
+ return formatCell(value)
165
+ .replace(/\|/g, '\\|')
166
+ .replace(/\r?\n/g, '<br>');
167
+ }
43
168
  function isInCurrentMonth(value) {
44
169
  if (!value)
45
170
  return false;
@@ -9,12 +9,12 @@ export declare const createConnectSessionTool: {
9
9
  auto_start: z.ZodOptional<z.ZodBoolean>;
10
10
  debug: z.ZodOptional<z.ZodBoolean>;
11
11
  }, "strip", z.ZodTypeAny, {
12
- platform: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "reddit" | "wordpress" | "mastodon" | "medium" | "devto" | "hashnode" | "discord" | "slack" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "x" | "meta" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "instagram_direct" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
12
+ platform: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "reddit" | "wordpress" | "mastodon" | "medium" | "devto" | "hashnode" | "discord" | "slack" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "x" | "meta" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "instagram_direct" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
13
13
  workspace_id?: string | undefined;
14
14
  auto_start?: boolean | undefined;
15
15
  debug?: boolean | undefined;
16
16
  }, {
17
- platform: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "reddit" | "wordpress" | "mastodon" | "medium" | "devto" | "hashnode" | "discord" | "slack" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "x" | "meta" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "instagram_direct" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
17
+ platform: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "reddit" | "wordpress" | "mastodon" | "medium" | "devto" | "hashnode" | "discord" | "slack" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "x" | "meta" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "instagram_direct" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
18
18
  workspace_id?: string | undefined;
19
19
  auto_start?: boolean | undefined;
20
20
  debug?: boolean | undefined;
@@ -105,6 +105,7 @@ export declare const platformSettingsSchema: z.ZodObject<{
105
105
  allow_duet: z.ZodOptional<z.ZodBoolean>;
106
106
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
107
107
  title: z.ZodOptional<z.ZodString>;
108
+ media_type: z.ZodOptional<z.ZodString>;
108
109
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
109
110
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
110
111
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -153,6 +154,7 @@ export declare const platformSettingsSchema: z.ZodObject<{
153
154
  allow_duet: z.ZodOptional<z.ZodBoolean>;
154
155
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
155
156
  title: z.ZodOptional<z.ZodString>;
157
+ media_type: z.ZodOptional<z.ZodString>;
156
158
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
157
159
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
158
160
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -201,6 +203,7 @@ export declare const platformSettingsSchema: z.ZodObject<{
201
203
  allow_duet: z.ZodOptional<z.ZodBoolean>;
202
204
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
203
205
  title: z.ZodOptional<z.ZodString>;
206
+ media_type: z.ZodOptional<z.ZodString>;
204
207
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
205
208
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
206
209
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -334,6 +337,7 @@ export declare const createPostInputSchema: z.ZodObject<{
334
337
  allow_duet: z.ZodOptional<z.ZodBoolean>;
335
338
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
336
339
  title: z.ZodOptional<z.ZodString>;
340
+ media_type: z.ZodOptional<z.ZodString>;
337
341
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
338
342
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
339
343
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -382,6 +386,7 @@ export declare const createPostInputSchema: z.ZodObject<{
382
386
  allow_duet: z.ZodOptional<z.ZodBoolean>;
383
387
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
384
388
  title: z.ZodOptional<z.ZodString>;
389
+ media_type: z.ZodOptional<z.ZodString>;
385
390
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
386
391
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
387
392
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -430,6 +435,7 @@ export declare const createPostInputSchema: z.ZodObject<{
430
435
  allow_duet: z.ZodOptional<z.ZodBoolean>;
431
436
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
432
437
  title: z.ZodOptional<z.ZodString>;
438
+ media_type: z.ZodOptional<z.ZodString>;
433
439
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
434
440
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
435
441
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -451,7 +457,7 @@ export declare const createPostInputSchema: z.ZodObject<{
451
457
  workspace_id: z.ZodOptional<z.ZodString>;
452
458
  }, "strip", z.ZodTypeAny, {
453
459
  workspace_id?: string | undefined;
454
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
460
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
455
461
  account_id?: string | undefined;
456
462
  scheduled_at?: string | undefined;
457
463
  media_url?: string | undefined;
@@ -511,6 +517,7 @@ export declare const createPostInputSchema: z.ZodObject<{
511
517
  allow_duet: z.ZodOptional<z.ZodBoolean>;
512
518
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
513
519
  title: z.ZodOptional<z.ZodString>;
520
+ media_type: z.ZodOptional<z.ZodString>;
514
521
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
515
522
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
516
523
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -531,7 +538,7 @@ export declare const createPostInputSchema: z.ZodObject<{
531
538
  }, z.ZodTypeAny, "passthrough"> | undefined;
532
539
  }, {
533
540
  workspace_id?: string | undefined;
534
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
541
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
535
542
  account_id?: string | undefined;
536
543
  scheduled_at?: string | undefined;
537
544
  media_url?: string | undefined;
@@ -591,6 +598,7 @@ export declare const createPostInputSchema: z.ZodObject<{
591
598
  allow_duet: z.ZodOptional<z.ZodBoolean>;
592
599
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
593
600
  title: z.ZodOptional<z.ZodString>;
601
+ media_type: z.ZodOptional<z.ZodString>;
594
602
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
595
603
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
596
604
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -730,6 +738,7 @@ export declare const createPostTool: {
730
738
  allow_duet: z.ZodOptional<z.ZodBoolean>;
731
739
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
732
740
  title: z.ZodOptional<z.ZodString>;
741
+ media_type: z.ZodOptional<z.ZodString>;
733
742
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
734
743
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
735
744
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -778,6 +787,7 @@ export declare const createPostTool: {
778
787
  allow_duet: z.ZodOptional<z.ZodBoolean>;
779
788
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
780
789
  title: z.ZodOptional<z.ZodString>;
790
+ media_type: z.ZodOptional<z.ZodString>;
781
791
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
782
792
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
783
793
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -826,6 +836,7 @@ export declare const createPostTool: {
826
836
  allow_duet: z.ZodOptional<z.ZodBoolean>;
827
837
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
828
838
  title: z.ZodOptional<z.ZodString>;
839
+ media_type: z.ZodOptional<z.ZodString>;
829
840
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
830
841
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
831
842
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -847,7 +858,7 @@ export declare const createPostTool: {
847
858
  workspace_id: z.ZodOptional<z.ZodString>;
848
859
  }, "strip", z.ZodTypeAny, {
849
860
  workspace_id?: string | undefined;
850
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
861
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
851
862
  account_id?: string | undefined;
852
863
  scheduled_at?: string | undefined;
853
864
  media_url?: string | undefined;
@@ -907,6 +918,7 @@ export declare const createPostTool: {
907
918
  allow_duet: z.ZodOptional<z.ZodBoolean>;
908
919
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
909
920
  title: z.ZodOptional<z.ZodString>;
921
+ media_type: z.ZodOptional<z.ZodString>;
910
922
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
911
923
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
912
924
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -927,7 +939,7 @@ export declare const createPostTool: {
927
939
  }, z.ZodTypeAny, "passthrough"> | undefined;
928
940
  }, {
929
941
  workspace_id?: string | undefined;
930
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
942
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
931
943
  account_id?: string | undefined;
932
944
  scheduled_at?: string | undefined;
933
945
  media_url?: string | undefined;
@@ -987,6 +999,7 @@ export declare const createPostTool: {
987
999
  allow_duet: z.ZodOptional<z.ZodBoolean>;
988
1000
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
989
1001
  title: z.ZodOptional<z.ZodString>;
1002
+ media_type: z.ZodOptional<z.ZodString>;
990
1003
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
991
1004
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
992
1005
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -47,6 +47,7 @@ export const platformSettingsSchema = z.object({
47
47
  allow_duet: z.boolean().optional(),
48
48
  allow_stitch: z.boolean().optional(),
49
49
  title: z.string().optional(),
50
+ media_type: z.string().optional(), // TikTok: 'PHOTO' forces a photo slideshow; image media is auto-detected when omitted
50
51
  is_commercial_content: z.boolean().optional(),
51
52
  is_your_brand: z.boolean().optional(),
52
53
  is_branded_content: z.boolean().optional(),
@@ -85,11 +86,11 @@ export const createPostInputSchema = z.object({
85
86
  media_urls: z
86
87
  .array(z.string())
87
88
  .optional()
88
- .describe('Multiple media URLs for carousel posts'),
89
+ .describe('Multiple media URLs for carousel or multi-image posts. For TikTok, 2 or more images auto-create a photo slideshow (up to 35 images) and every image is posted.'),
89
90
  post_type: z
90
91
  .string()
91
92
  .optional()
92
- .describe('Post type: text, image, video, carousel, reel, story. Auto-set to x_thread/threads_thread when `thread_posts` is provided.'),
93
+ .describe('Post type: text, image, video, carousel, reel, story. Auto-set to x_thread/threads_thread when `thread_posts` is provided. For TikTok you can omit it: image media auto-detects as a photo slideshow (use "carousel" for 2 or more images, or "image" for one), while a single video posts as a video.'),
93
94
  thread_posts: z
94
95
  .array(z.string())
95
96
  .optional()
@@ -153,7 +154,8 @@ export const createPostTool = {
153
154
  'AFTER CALLING: Tell the user the post was created, include the Posterly dashboard link returned by the tool, and offer the next natural action. Do not narrate raw HTTP, curl, or API plumbing.\n\n' +
154
155
  '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' +
155
156
  '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' +
156
- '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.',
157
+ '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.\n\n' +
158
+ 'TIKTOK MEDIA: TikTok supports a single video or a photo slideshow of 1 to 35 images, never multiple videos. Image media auto-detects as a slideshow, so you usually do not set post_type or media_type for photos and every image is posted. Pass `platform_settings.media_type: "PHOTO"` to force a slideshow explicitly.',
157
159
  inputSchema: createPostInputSchema,
158
160
  async execute(client, input) {
159
161
  const payload = buildCreatePostPayload(input);
@@ -120,6 +120,7 @@ export declare const createPostsBatchTool: {
120
120
  allow_duet: z.ZodOptional<z.ZodBoolean>;
121
121
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
122
122
  title: z.ZodOptional<z.ZodString>;
123
+ media_type: z.ZodOptional<z.ZodString>;
123
124
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
124
125
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
125
126
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -168,6 +169,7 @@ export declare const createPostsBatchTool: {
168
169
  allow_duet: z.ZodOptional<z.ZodBoolean>;
169
170
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
170
171
  title: z.ZodOptional<z.ZodString>;
172
+ media_type: z.ZodOptional<z.ZodString>;
171
173
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
172
174
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
173
175
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -216,6 +218,7 @@ export declare const createPostsBatchTool: {
216
218
  allow_duet: z.ZodOptional<z.ZodBoolean>;
217
219
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
218
220
  title: z.ZodOptional<z.ZodString>;
221
+ media_type: z.ZodOptional<z.ZodString>;
219
222
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
220
223
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
221
224
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -237,7 +240,7 @@ export declare const createPostsBatchTool: {
237
240
  workspace_id: z.ZodOptional<z.ZodString>;
238
241
  }, "strip", z.ZodTypeAny, {
239
242
  workspace_id?: string | undefined;
240
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
243
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
241
244
  account_id?: string | undefined;
242
245
  scheduled_at?: string | undefined;
243
246
  media_url?: string | undefined;
@@ -297,6 +300,7 @@ export declare const createPostsBatchTool: {
297
300
  allow_duet: z.ZodOptional<z.ZodBoolean>;
298
301
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
299
302
  title: z.ZodOptional<z.ZodString>;
303
+ media_type: z.ZodOptional<z.ZodString>;
300
304
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
301
305
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
302
306
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -317,7 +321,7 @@ export declare const createPostsBatchTool: {
317
321
  }, z.ZodTypeAny, "passthrough"> | undefined;
318
322
  }, {
319
323
  workspace_id?: string | undefined;
320
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
324
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
321
325
  account_id?: string | undefined;
322
326
  scheduled_at?: string | undefined;
323
327
  media_url?: string | undefined;
@@ -377,6 +381,7 @@ export declare const createPostsBatchTool: {
377
381
  allow_duet: z.ZodOptional<z.ZodBoolean>;
378
382
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
379
383
  title: z.ZodOptional<z.ZodString>;
384
+ media_type: z.ZodOptional<z.ZodString>;
380
385
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
381
386
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
382
387
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -399,7 +404,7 @@ export declare const createPostsBatchTool: {
399
404
  }, "strip", z.ZodTypeAny, {
400
405
  posts: {
401
406
  workspace_id?: string | undefined;
402
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
407
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
403
408
  account_id?: string | undefined;
404
409
  scheduled_at?: string | undefined;
405
410
  media_url?: string | undefined;
@@ -459,6 +464,7 @@ export declare const createPostsBatchTool: {
459
464
  allow_duet: z.ZodOptional<z.ZodBoolean>;
460
465
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
461
466
  title: z.ZodOptional<z.ZodString>;
467
+ media_type: z.ZodOptional<z.ZodString>;
462
468
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
463
469
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
464
470
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -481,7 +487,7 @@ export declare const createPostsBatchTool: {
481
487
  }, {
482
488
  posts: {
483
489
  workspace_id?: string | undefined;
484
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
490
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "telegram" | "bluesky" | "gmb" | "google-business" | "google_business_profile" | "x" | undefined;
485
491
  account_id?: string | undefined;
486
492
  scheduled_at?: string | undefined;
487
493
  media_url?: string | undefined;
@@ -541,6 +547,7 @@ export declare const createPostsBatchTool: {
541
547
  allow_duet: z.ZodOptional<z.ZodBoolean>;
542
548
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
543
549
  title: z.ZodOptional<z.ZodString>;
550
+ media_type: z.ZodOptional<z.ZodString>;
544
551
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
545
552
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
546
553
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { formatDate, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
2
3
  export const findSlotTool = {
3
4
  name: 'find_available_slot',
4
5
  description: 'Find available time slots for posting. Respects a 1-hour gap between posts and preferred hours (8am–10pm in the given timezone). Returns up to 10 slots. IMPORTANT: pass a timezone explicitly — default is America/New_York and slots will be off if the user is elsewhere. Pass workspace_id to only avoid collisions with posts in that workspace.',
@@ -25,9 +26,15 @@ export const findSlotTool = {
25
26
  async execute(client, input) {
26
27
  const slots = await client.findAvailableSlots(input);
27
28
  if (slots.length === 0) {
28
- return 'No available slots found in the next 14 days.';
29
+ return mdEmpty('available slots', 'No available posting slots were found in the next 14 days.');
29
30
  }
30
- const lines = slots.map((s, i) => `${i + 1}. ${s.local_time} — ${new Date(s.time).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' })}`);
31
- return `Available posting slots:\n${lines.join('\n')}`;
31
+ return [
32
+ mdTitle(`Available posting slots (${slots.length})`),
33
+ mdTable(['Option', 'Local time', 'Date'], slots.map((slot, index) => [
34
+ index + 1,
35
+ slot.local_time,
36
+ formatDate(slot.time),
37
+ ])),
38
+ ].join('\n\n');
32
39
  },
33
40
  };