posterly-mcp-server 0.24.0 → 0.25.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 (35) hide show
  1. package/README.md +6 -1
  2. package/dist/index.js +50 -0
  3. package/dist/lib/api-client.d.ts +45 -1
  4. package/dist/lib/api-client.js +15 -0
  5. package/dist/lib/version.d.ts +1 -1
  6. package/dist/lib/version.js +1 -1
  7. package/dist/tools/cancel-subscription.d.ts +30 -0
  8. package/dist/tools/cancel-subscription.js +41 -0
  9. package/dist/tools/create-api-key.d.ts +3 -3
  10. package/dist/tools/create-api-key.js +2 -2
  11. package/dist/tools/create-post.d.ts +18 -0
  12. package/dist/tools/create-post.js +2 -1
  13. package/dist/tools/create-posts-batch.d.ts +7 -0
  14. package/dist/tools/downgrade-subscription.d.ts +20 -0
  15. package/dist/tools/downgrade-subscription.js +23 -0
  16. package/dist/tools/get-subscription.d.ts +8 -0
  17. package/dist/tools/get-subscription.js +26 -0
  18. package/dist/tools/pause-subscription.d.ts +16 -0
  19. package/dist/tools/pause-subscription.js +21 -0
  20. package/dist/tools/resume-subscription.d.ts +8 -0
  21. package/dist/tools/resume-subscription.js +11 -0
  22. package/dist/tools/update-post.d.ts +8 -0
  23. package/dist/tools/update-post.js +2 -1
  24. package/package.json +1 -1
  25. package/src/index.ts +75 -0
  26. package/src/lib/api-client.ts +68 -1
  27. package/src/lib/version.ts +1 -1
  28. package/src/tools/cancel-subscription.ts +50 -0
  29. package/src/tools/create-api-key.ts +2 -2
  30. package/src/tools/create-post.ts +2 -1
  31. package/src/tools/downgrade-subscription.ts +29 -0
  32. package/src/tools/get-subscription.ts +31 -0
  33. package/src/tools/pause-subscription.ts +26 -0
  34. package/src/tools/resume-subscription.ts +15 -0
  35. package/src/tools/update-post.ts +2 -1
package/README.md CHANGED
@@ -108,7 +108,7 @@ Add the same server definition to your Cursor MCP settings:
108
108
 
109
109
  ## Available tools
110
110
 
111
- `posterly-mcp-server@0.23.0` exposes 63 tools.
111
+ `posterly-mcp-server@0.25.0` exposes 72 tools.
112
112
 
113
113
  Public setup tools work before `POSTERLY_API_KEY` exists:
114
114
 
@@ -127,6 +127,11 @@ Authenticated tools require `POSTERLY_API_KEY`:
127
127
  - `get_connect_session` (poll connection progress while the user approves OAuth or enters credentials)
128
128
  - `create_api_key` (create a new API key after explicit confirmation; scopes cannot exceed the calling dashboard key)
129
129
  - `delete_api_key` (revoke a user-created API key after explicit confirmation)
130
+ - `get_subscription` (read subscription status, tier, and cancel/pause state; requires `billing:read`)
131
+ - `cancel_subscription` (cancel after explicit confirmation; the agent must ask for a `reason` first; requires `billing:write`)
132
+ - `pause_subscription` (pause 30 days, one per 90-day cooldown, after confirmation; requires `billing:write`)
133
+ - `resume_subscription` (resume a paused subscription; requires `billing:write`)
134
+ - `downgrade_subscription` (downgrade one tier at next renewal after confirmation; requires `billing:write`)
130
135
  - `list_oauth_clients`
131
136
  - `create_oauth_client` (create a public PKCE client after explicit confirmation)
132
137
  - `update_oauth_client` (update redirect URIs/scopes after explicit confirmation)
package/dist/index.js CHANGED
@@ -66,6 +66,11 @@ import { createConnectSessionTool } from './tools/create-connect-session.js';
66
66
  import { getConnectSessionTool } from './tools/get-connect-session.js';
67
67
  import { createApiKeyTool } from './tools/create-api-key.js';
68
68
  import { deleteApiKeyTool } from './tools/delete-api-key.js';
69
+ import { getSubscriptionTool } from './tools/get-subscription.js';
70
+ import { cancelSubscriptionTool } from './tools/cancel-subscription.js';
71
+ import { pauseSubscriptionTool } from './tools/pause-subscription.js';
72
+ import { resumeSubscriptionTool } from './tools/resume-subscription.js';
73
+ import { downgradeSubscriptionTool } from './tools/downgrade-subscription.js';
69
74
  import { listOAuthClientsTool } from './tools/list-oauth-clients.js';
70
75
  import { createOAuthClientTool } from './tools/create-oauth-client.js';
71
76
  import { updateOAuthClientTool } from './tools/update-oauth-client.js';
@@ -184,6 +189,51 @@ server.tool(deleteApiKeyTool.name, deleteApiKeyTool.description, deleteApiKeyToo
184
189
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
185
190
  }
186
191
  });
192
+ server.tool(getSubscriptionTool.name, getSubscriptionTool.description, getSubscriptionTool.inputSchema.shape, async () => {
193
+ try {
194
+ const text = await getSubscriptionTool.execute(client);
195
+ return { content: [{ type: 'text', text }] };
196
+ }
197
+ catch (err) {
198
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
199
+ }
200
+ });
201
+ server.tool(cancelSubscriptionTool.name, cancelSubscriptionTool.description, cancelSubscriptionTool.inputSchema.shape, async (input) => {
202
+ try {
203
+ const text = await cancelSubscriptionTool.execute(client, input);
204
+ return { content: [{ type: 'text', text }] };
205
+ }
206
+ catch (err) {
207
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
208
+ }
209
+ });
210
+ server.tool(pauseSubscriptionTool.name, pauseSubscriptionTool.description, pauseSubscriptionTool.inputSchema.shape, async (input) => {
211
+ try {
212
+ const text = await pauseSubscriptionTool.execute(client, input);
213
+ return { content: [{ type: 'text', text }] };
214
+ }
215
+ catch (err) {
216
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
217
+ }
218
+ });
219
+ server.tool(resumeSubscriptionTool.name, resumeSubscriptionTool.description, resumeSubscriptionTool.inputSchema.shape, async () => {
220
+ try {
221
+ const text = await resumeSubscriptionTool.execute(client);
222
+ return { content: [{ type: 'text', text }] };
223
+ }
224
+ catch (err) {
225
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
226
+ }
227
+ });
228
+ server.tool(downgradeSubscriptionTool.name, downgradeSubscriptionTool.description, downgradeSubscriptionTool.inputSchema.shape, async (input) => {
229
+ try {
230
+ const text = await downgradeSubscriptionTool.execute(client, input);
231
+ return { content: [{ type: 'text', text }] };
232
+ }
233
+ catch (err) {
234
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
235
+ }
236
+ });
187
237
  server.tool(listOAuthClientsTool.name, listOAuthClientsTool.description, listOAuthClientsTool.inputSchema.shape, async () => {
188
238
  try {
189
239
  const text = await listOAuthClientsTool.execute(client);
@@ -237,7 +237,7 @@ export type AskSupportPayload = {
237
237
  request_human?: boolean;
238
238
  confirm_escalation?: boolean;
239
239
  };
240
- export type ApiKeyScope = 'accounts:read' | 'accounts:write' | 'posts:read' | 'posts:write' | 'media:write' | 'analytics:read';
240
+ export type ApiKeyScope = 'accounts:read' | 'accounts:write' | 'posts:read' | 'posts:write' | 'media:write' | 'analytics:read' | 'billing:read' | 'billing:write';
241
241
  export type CreateApiKeyPayload = {
242
242
  name?: string;
243
243
  scopes?: ApiKeyScope[];
@@ -279,6 +279,43 @@ export type DeleteApiKeyResponse = {
279
279
  is_revoked?: boolean;
280
280
  };
281
281
  };
282
+ export type SubscriptionTierName = 'starter' | 'pro' | 'power_user' | 'agency';
283
+ export type SubscriptionSummary = {
284
+ id: string | null;
285
+ status: string | null;
286
+ tier: string | null;
287
+ cancel_at_period_end: boolean | null;
288
+ current_period_end: string | null;
289
+ trial_ends_at: string | null;
290
+ paused_at: string | null;
291
+ pause_resumes_at: string | null;
292
+ stripe_subscription_id: string | null;
293
+ };
294
+ export type SubscriptionSummaryResponse = {
295
+ subscription: SubscriptionSummary;
296
+ };
297
+ export type CancelSubscriptionPayload = {
298
+ reason: string;
299
+ feedback?: string;
300
+ immediate?: boolean;
301
+ };
302
+ export type CancelSubscriptionResponse = {
303
+ success: true;
304
+ immediate: boolean;
305
+ canceled_at: string | null;
306
+ cancel_at_period_end: boolean;
307
+ };
308
+ export type PauseSubscriptionResponse = {
309
+ success: true;
310
+ pause_resumes_at: string;
311
+ };
312
+ export type ResumeSubscriptionResponse = {
313
+ success: true;
314
+ };
315
+ export type DowngradeSubscriptionResponse = {
316
+ success: true;
317
+ tier: SubscriptionTierName;
318
+ };
282
319
  export type AskSupportResponse = {
283
320
  success: boolean;
284
321
  workspace_id: string;
@@ -698,6 +735,13 @@ export declare class PosterlyClient {
698
735
  deleteApiKey(keyId: string, data: {
699
736
  confirm: true;
700
737
  }): Promise<DeleteApiKeyResponse>;
738
+ getSubscription(): Promise<SubscriptionSummaryResponse>;
739
+ cancelSubscription(data: CancelSubscriptionPayload): Promise<CancelSubscriptionResponse>;
740
+ pauseSubscription(): Promise<PauseSubscriptionResponse>;
741
+ resumeSubscription(): Promise<ResumeSubscriptionResponse>;
742
+ downgradeSubscription(data: {
743
+ tier?: SubscriptionTierName;
744
+ }): Promise<DowngradeSubscriptionResponse>;
701
745
  listAccounts(params?: {
702
746
  workspace_id?: string;
703
747
  }): Promise<Account[]>;
@@ -122,6 +122,21 @@ export class PosterlyClient {
122
122
  async deleteApiKey(keyId, data) {
123
123
  return this.request('DELETE', `/api-keys/${encodeURIComponent(keyId)}`, data);
124
124
  }
125
+ async getSubscription() {
126
+ return this.request('GET', '/subscription');
127
+ }
128
+ async cancelSubscription(data) {
129
+ return this.request('POST', '/subscription/cancel', data);
130
+ }
131
+ async pauseSubscription() {
132
+ return this.request('POST', '/subscription/pause', {});
133
+ }
134
+ async resumeSubscription() {
135
+ return this.request('POST', '/subscription/resume', {});
136
+ }
137
+ async downgradeSubscription(data) {
138
+ return this.request('POST', '/subscription/downgrade', data);
139
+ }
125
140
  async listAccounts(params) {
126
141
  const searchParams = new URLSearchParams();
127
142
  if (params?.workspace_id)
@@ -1 +1 @@
1
- export declare const POSTERLY_MCP_VERSION = "0.24.0";
1
+ export declare const POSTERLY_MCP_VERSION = "0.25.0";
@@ -5,4 +5,4 @@
5
5
  // tool set (minus the intentional pre-auth signup tools that only this stdio
6
6
  // package exposes). `npm run check:mcp-parity` enforces both the version match
7
7
  // and the tool-list match, and runs in the pre-commit hook.
8
- export const POSTERLY_MCP_VERSION = '0.24.0';
8
+ export const POSTERLY_MCP_VERSION = '0.25.0';
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ declare const CANCELLATION_REASONS: readonly ["too_expensive", "not_using", "missing_features", "technical_issues", "switching_service", "temporary_break", "other"];
4
+ export declare const cancelSubscriptionTool: {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: z.ZodObject<{
8
+ reason: z.ZodEnum<["too_expensive", "not_using", "missing_features", "technical_issues", "switching_service", "temporary_break", "other"]>;
9
+ feedback: z.ZodOptional<z.ZodString>;
10
+ immediate: z.ZodOptional<z.ZodBoolean>;
11
+ confirm: z.ZodLiteral<true>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ confirm: true;
14
+ reason: "too_expensive" | "not_using" | "missing_features" | "technical_issues" | "switching_service" | "temporary_break" | "other";
15
+ feedback?: string | undefined;
16
+ immediate?: boolean | undefined;
17
+ }, {
18
+ confirm: true;
19
+ reason: "too_expensive" | "not_using" | "missing_features" | "technical_issues" | "switching_service" | "temporary_break" | "other";
20
+ feedback?: string | undefined;
21
+ immediate?: boolean | undefined;
22
+ }>;
23
+ execute(client: PosterlyClient, input: {
24
+ reason: (typeof CANCELLATION_REASONS)[number];
25
+ feedback?: string;
26
+ immediate?: boolean;
27
+ confirm: true;
28
+ }): Promise<string>;
29
+ };
30
+ export {};
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
3
+ // Mirrors CANCELLATION_REASONS in the main app (lib/subscription-reasons.ts).
4
+ // Kept in sync manually because the npm package cannot import from the app.
5
+ const CANCELLATION_REASONS = [
6
+ 'too_expensive',
7
+ 'not_using',
8
+ 'missing_features',
9
+ 'technical_issues',
10
+ 'switching_service',
11
+ 'temporary_break',
12
+ 'other',
13
+ ];
14
+ function formatCanceled(result) {
15
+ return [
16
+ mdTitle(result.immediate ? 'Subscription cancelled immediately' : 'Subscription set to cancel at period end'),
17
+ mdSection('Details', mdKeyValue([
18
+ ['Immediate', result.immediate ? 'yes' : 'no'],
19
+ ['Cancel at period end', result.cancel_at_period_end ? 'yes' : 'no'],
20
+ ['Canceled at', result.canceled_at || 'at end of current period'],
21
+ ])),
22
+ ].join('\n\n');
23
+ }
24
+ export const cancelSubscriptionTool = {
25
+ name: 'cancel_subscription',
26
+ description: "Cancel the authenticated user's posterly subscription. DESTRUCTIVE billing action; requires the billing:write scope. ALWAYS ask the user why they are cancelling FIRST and pass their answer as `reason` (one of the allowed values). By default the subscription is set to cancel at the end of the current period; pass immediate=true only if the user explicitly wants it cancelled right now. Only call after the user explicitly confirms.",
27
+ inputSchema: z.object({
28
+ reason: z.enum(CANCELLATION_REASONS).describe('Why the user is cancelling. Ask the user before calling; do not guess.'),
29
+ feedback: z.string().max(2000).optional().describe('Optional free-text detail the user gave about why they are cancelling.'),
30
+ immediate: z.boolean().optional().describe('Cancel immediately instead of at period end. Defaults to false (cancel at period end).'),
31
+ confirm: z.literal(true).describe('Must be true after the user explicitly confirms the cancellation.'),
32
+ }),
33
+ async execute(client, input) {
34
+ const result = await client.cancelSubscription({
35
+ reason: input.reason,
36
+ feedback: input.feedback,
37
+ immediate: input.immediate === true,
38
+ });
39
+ return formatCanceled(result);
40
+ },
41
+ };
@@ -5,7 +5,7 @@ export declare const createApiKeyTool: {
5
5
  description: string;
6
6
  inputSchema: z.ZodObject<{
7
7
  name: z.ZodOptional<z.ZodString>;
8
- scopes: z.ZodOptional<z.ZodArray<z.ZodEnum<["accounts:read", "accounts:write", "posts:read", "posts:write", "media:write", "analytics:read"]>, "many">>;
8
+ scopes: z.ZodOptional<z.ZodArray<z.ZodEnum<["accounts:read", "accounts:write", "posts:read", "posts:write", "media:write", "analytics:read", "billing:read", "billing:write"]>, "many">>;
9
9
  workspace_id: z.ZodOptional<z.ZodString>;
10
10
  expires_in_days: z.ZodOptional<z.ZodNumber>;
11
11
  confirm: z.ZodLiteral<true>;
@@ -13,13 +13,13 @@ export declare const createApiKeyTool: {
13
13
  confirm: true;
14
14
  workspace_id?: string | undefined;
15
15
  name?: string | undefined;
16
- scopes?: ("accounts:read" | "accounts:write" | "posts:read" | "posts:write" | "media:write" | "analytics:read")[] | undefined;
16
+ scopes?: ("accounts:read" | "accounts:write" | "posts:read" | "posts:write" | "media:write" | "analytics:read" | "billing:read" | "billing:write")[] | undefined;
17
17
  expires_in_days?: number | undefined;
18
18
  }, {
19
19
  confirm: true;
20
20
  workspace_id?: string | undefined;
21
21
  name?: string | undefined;
22
- scopes?: ("accounts:read" | "accounts:write" | "posts:read" | "posts:write" | "media:write" | "analytics:read")[] | undefined;
22
+ scopes?: ("accounts:read" | "accounts:write" | "posts:read" | "posts:write" | "media:write" | "analytics:read" | "billing:read" | "billing:write")[] | undefined;
23
23
  expires_in_days?: number | undefined;
24
24
  }>;
25
25
  execute(client: PosterlyClient, input: CreateApiKeyPayload): Promise<string>;
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { code, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
3
- const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read']);
3
+ const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read', 'billing:read', 'billing:write']);
4
4
  function formatCreatedApiKey(result) {
5
5
  const key = result.api_key;
6
6
  return [
@@ -23,7 +23,7 @@ export const createApiKeyTool = {
23
23
  description: 'Create a new Posterly API key for the authenticated user. SECRET-CREATING WRITE: only use after explicit user confirmation. The new key can only request scopes already present on the calling dashboard-created API key; OAuth and managed assistant tokens cannot mint keys.',
24
24
  inputSchema: z.object({
25
25
  name: z.string().trim().min(1).max(120).optional().describe('Human-readable key name.'),
26
- scopes: z.array(scopeSchema).min(1).max(6).optional().describe('Scopes for the new key. Omit to copy the calling key scopes. Cannot exceed the calling key scopes.'),
26
+ scopes: z.array(scopeSchema).min(1).max(8).optional().describe('Scopes for the new key. Omit to copy the calling key scopes. Cannot exceed the calling key scopes. billing:read and billing:write allow managing the posterly subscription (cancel/pause/resume/downgrade).'),
27
27
  workspace_id: z.string().trim().min(1).optional().describe('Optional workspace restriction. Workspace-scoped calling keys cannot create keys outside their workspace.'),
28
28
  expires_in_days: z.number().int().min(1).max(365).optional().describe('Optional expiry from now, up to 365 days. Omit for no expiry.'),
29
29
  confirm: z.literal(true).describe('Must be true after the user explicitly confirms that a new secret API key should be created.'),
@@ -117,6 +117,7 @@ export declare const platformSettingsSchema: z.ZodObject<{
117
117
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
118
118
  title: z.ZodOptional<z.ZodString>;
119
119
  media_type: z.ZodOptional<z.ZodString>;
120
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
120
121
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
121
122
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
122
123
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -193,6 +194,7 @@ export declare const platformSettingsSchema: z.ZodObject<{
193
194
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
194
195
  title: z.ZodOptional<z.ZodString>;
195
196
  media_type: z.ZodOptional<z.ZodString>;
197
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
196
198
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
197
199
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
198
200
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -269,6 +271,7 @@ export declare const platformSettingsSchema: z.ZodObject<{
269
271
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
270
272
  title: z.ZodOptional<z.ZodString>;
271
273
  media_type: z.ZodOptional<z.ZodString>;
274
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
272
275
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
273
276
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
274
277
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -430,6 +433,7 @@ export declare const createPostPayloadInputSchema: z.ZodObject<{
430
433
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
431
434
  title: z.ZodOptional<z.ZodString>;
432
435
  media_type: z.ZodOptional<z.ZodString>;
436
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
433
437
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
434
438
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
435
439
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -506,6 +510,7 @@ export declare const createPostPayloadInputSchema: z.ZodObject<{
506
510
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
507
511
  title: z.ZodOptional<z.ZodString>;
508
512
  media_type: z.ZodOptional<z.ZodString>;
513
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
509
514
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
510
515
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
511
516
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -582,6 +587,7 @@ export declare const createPostPayloadInputSchema: z.ZodObject<{
582
587
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
583
588
  title: z.ZodOptional<z.ZodString>;
584
589
  media_type: z.ZodOptional<z.ZodString>;
590
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
585
591
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
586
592
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
587
593
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -691,6 +697,7 @@ export declare const createPostPayloadInputSchema: z.ZodObject<{
691
697
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
692
698
  title: z.ZodOptional<z.ZodString>;
693
699
  media_type: z.ZodOptional<z.ZodString>;
700
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
694
701
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
695
702
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
696
703
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -799,6 +806,7 @@ export declare const createPostPayloadInputSchema: z.ZodObject<{
799
806
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
800
807
  title: z.ZodOptional<z.ZodString>;
801
808
  media_type: z.ZodOptional<z.ZodString>;
809
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
802
810
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
803
811
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
804
812
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -961,6 +969,7 @@ export declare const createPostInputSchema: z.ZodObject<{
961
969
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
962
970
  title: z.ZodOptional<z.ZodString>;
963
971
  media_type: z.ZodOptional<z.ZodString>;
972
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
964
973
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
965
974
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
966
975
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1037,6 +1046,7 @@ export declare const createPostInputSchema: z.ZodObject<{
1037
1046
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1038
1047
  title: z.ZodOptional<z.ZodString>;
1039
1048
  media_type: z.ZodOptional<z.ZodString>;
1049
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1040
1050
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1041
1051
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1042
1052
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1113,6 +1123,7 @@ export declare const createPostInputSchema: z.ZodObject<{
1113
1123
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1114
1124
  title: z.ZodOptional<z.ZodString>;
1115
1125
  media_type: z.ZodOptional<z.ZodString>;
1126
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1116
1127
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1117
1128
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1118
1129
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1225,6 +1236,7 @@ export declare const createPostInputSchema: z.ZodObject<{
1225
1236
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1226
1237
  title: z.ZodOptional<z.ZodString>;
1227
1238
  media_type: z.ZodOptional<z.ZodString>;
1239
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1228
1240
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1229
1241
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1230
1242
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1334,6 +1346,7 @@ export declare const createPostInputSchema: z.ZodObject<{
1334
1346
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1335
1347
  title: z.ZodOptional<z.ZodString>;
1336
1348
  media_type: z.ZodOptional<z.ZodString>;
1349
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1337
1350
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1338
1351
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1339
1352
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1502,6 +1515,7 @@ export declare const createPostTool: {
1502
1515
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1503
1516
  title: z.ZodOptional<z.ZodString>;
1504
1517
  media_type: z.ZodOptional<z.ZodString>;
1518
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1505
1519
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1506
1520
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1507
1521
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1578,6 +1592,7 @@ export declare const createPostTool: {
1578
1592
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1579
1593
  title: z.ZodOptional<z.ZodString>;
1580
1594
  media_type: z.ZodOptional<z.ZodString>;
1595
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1581
1596
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1582
1597
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1583
1598
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1654,6 +1669,7 @@ export declare const createPostTool: {
1654
1669
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1655
1670
  title: z.ZodOptional<z.ZodString>;
1656
1671
  media_type: z.ZodOptional<z.ZodString>;
1672
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1657
1673
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1658
1674
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1659
1675
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1766,6 +1782,7 @@ export declare const createPostTool: {
1766
1782
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1767
1783
  title: z.ZodOptional<z.ZodString>;
1768
1784
  media_type: z.ZodOptional<z.ZodString>;
1785
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1769
1786
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1770
1787
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1771
1788
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -1875,6 +1892,7 @@ export declare const createPostTool: {
1875
1892
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
1876
1893
  title: z.ZodOptional<z.ZodString>;
1877
1894
  media_type: z.ZodOptional<z.ZodString>;
1895
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
1878
1896
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
1879
1897
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
1880
1898
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -53,6 +53,7 @@ export const platformSettingsSchema = z.object({
53
53
  allow_stitch: z.boolean().optional(),
54
54
  title: z.string().optional(),
55
55
  media_type: z.string().optional(), // TikTok: 'PHOTO' forces a photo slideshow; image media is auto-detected when omitted
56
+ video_cover_timestamp_ms: z.number().nonnegative().optional(), // TikTok: millisecond timestamp of the video frame to use as the cover (direct post only)
56
57
  is_commercial_content: z.boolean().optional(),
57
58
  is_your_brand: z.boolean().optional(),
58
59
  is_branded_content: z.boolean().optional(),
@@ -106,7 +107,7 @@ export const createPostPayloadInputSchema = z.object({
106
107
  post_type: z
107
108
  .string()
108
109
  .optional()
109
- .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.'),
110
+ .describe('Optional post type: text, image, video, carousel, reel, story, story_series, document, photo, cover_photo, x_thread, or threads_thread. Omit it for auto-detection from media. For TikTok photo posts, use photo/image/carousel or omit it; for Facebook cover photos, use cover_photo or platform_settings.post_type.'),
110
111
  thread_posts: z
111
112
  .array(z.string())
112
113
  .optional()
@@ -132,6 +132,7 @@ export declare const createPostsBatchTool: {
132
132
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
133
133
  title: z.ZodOptional<z.ZodString>;
134
134
  media_type: z.ZodOptional<z.ZodString>;
135
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
135
136
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
136
137
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
137
138
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -208,6 +209,7 @@ export declare const createPostsBatchTool: {
208
209
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
209
210
  title: z.ZodOptional<z.ZodString>;
210
211
  media_type: z.ZodOptional<z.ZodString>;
212
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
211
213
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
212
214
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
213
215
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -284,6 +286,7 @@ export declare const createPostsBatchTool: {
284
286
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
285
287
  title: z.ZodOptional<z.ZodString>;
286
288
  media_type: z.ZodOptional<z.ZodString>;
289
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
287
290
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
288
291
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
289
292
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -393,6 +396,7 @@ export declare const createPostsBatchTool: {
393
396
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
394
397
  title: z.ZodOptional<z.ZodString>;
395
398
  media_type: z.ZodOptional<z.ZodString>;
399
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
396
400
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
397
401
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
398
402
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -501,6 +505,7 @@ export declare const createPostsBatchTool: {
501
505
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
502
506
  title: z.ZodOptional<z.ZodString>;
503
507
  media_type: z.ZodOptional<z.ZodString>;
508
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
504
509
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
505
510
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
506
511
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -613,6 +618,7 @@ export declare const createPostsBatchTool: {
613
618
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
614
619
  title: z.ZodOptional<z.ZodString>;
615
620
  media_type: z.ZodOptional<z.ZodString>;
621
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
616
622
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
617
623
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
618
624
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -724,6 +730,7 @@ export declare const createPostsBatchTool: {
724
730
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
725
731
  title: z.ZodOptional<z.ZodString>;
726
732
  media_type: z.ZodOptional<z.ZodString>;
733
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
727
734
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
728
735
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
729
736
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient, SubscriptionTierName } from '../lib/api-client.js';
3
+ export declare const downgradeSubscriptionTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{
7
+ tier: z.ZodOptional<z.ZodEnum<["starter", "pro", "power_user", "agency"]>>;
8
+ confirm: z.ZodLiteral<true>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ confirm: true;
11
+ tier?: "starter" | "pro" | "power_user" | "agency" | undefined;
12
+ }, {
13
+ confirm: true;
14
+ tier?: "starter" | "pro" | "power_user" | "agency" | undefined;
15
+ }>;
16
+ execute(client: PosterlyClient, input: {
17
+ tier?: SubscriptionTierName;
18
+ confirm: true;
19
+ }): Promise<string>;
20
+ };
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ import { mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
3
+ const TIER_VALUES = ['starter', 'pro', 'power_user', 'agency'];
4
+ function formatDowngraded(result) {
5
+ return [
6
+ mdTitle('Subscription downgrade scheduled'),
7
+ mdSection('Details', mdKeyValue([
8
+ ['New tier at next renewal', result.tier],
9
+ ])),
10
+ ].join('\n\n');
11
+ }
12
+ export const downgradeSubscriptionTool = {
13
+ name: 'downgrade_subscription',
14
+ description: "Downgrade the authenticated user's posterly subscription one tier (or to an explicit lower `tier`) at the next renewal with no proration. Billing action; requires the billing:write scope. Only call after the user explicitly confirms.",
15
+ inputSchema: z.object({
16
+ tier: z.enum(TIER_VALUES).optional().describe('Optional explicit target tier. Omit to drop one tier automatically.'),
17
+ confirm: z.literal(true).describe('Must be true after the user explicitly confirms the downgrade.'),
18
+ }),
19
+ async execute(client, input) {
20
+ const result = await client.downgradeSubscription({ tier: input.tier });
21
+ return formatDowngraded(result);
22
+ },
23
+ };
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ export declare const getSubscriptionTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
7
+ execute(client: PosterlyClient): Promise<string>;
8
+ };
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+ import { formatDateTime, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
3
+ function formatSubscription(result) {
4
+ const sub = result.subscription;
5
+ return [
6
+ mdTitle('posterly subscription'),
7
+ mdSection('Details', mdKeyValue([
8
+ ['Status', sub.status || 'unknown'],
9
+ ['Tier', sub.tier || 'unknown'],
10
+ ['Cancel at period end', sub.cancel_at_period_end ? 'yes' : 'no'],
11
+ ['Current period end', sub.current_period_end ? formatDateTime(sub.current_period_end) : 'n/a'],
12
+ ['Trial ends', sub.trial_ends_at ? formatDateTime(sub.trial_ends_at) : 'n/a'],
13
+ ['Paused at', sub.paused_at ? formatDateTime(sub.paused_at) : 'n/a'],
14
+ ['Pause resumes at', sub.pause_resumes_at ? formatDateTime(sub.pause_resumes_at) : 'n/a'],
15
+ ])),
16
+ ].join('\n\n');
17
+ }
18
+ export const getSubscriptionTool = {
19
+ name: 'get_subscription',
20
+ description: "Get the authenticated user's posterly subscription: status, tier, cancel-at-period-end, current period end, trial end, and pause state. Read-only. Requires the billing:read scope.",
21
+ inputSchema: z.object({}),
22
+ async execute(client) {
23
+ const result = await client.getSubscription();
24
+ return formatSubscription(result);
25
+ },
26
+ };
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ export declare const pauseSubscriptionTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{
7
+ confirm: z.ZodLiteral<true>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ confirm: true;
10
+ }, {
11
+ confirm: true;
12
+ }>;
13
+ execute(client: PosterlyClient, _input: {
14
+ confirm: true;
15
+ }): Promise<string>;
16
+ };
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+ import { formatDateTime, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
3
+ function formatPaused(result) {
4
+ return [
5
+ mdTitle('Subscription paused'),
6
+ mdSection('Details', mdKeyValue([
7
+ ['Resumes at', result.pause_resumes_at ? formatDateTime(result.pause_resumes_at) : 'n/a'],
8
+ ])),
9
+ ].join('\n\n');
10
+ }
11
+ export const pauseSubscriptionTool = {
12
+ name: 'pause_subscription',
13
+ description: "Pause the authenticated user's posterly subscription for 30 days (one pause allowed per 90-day cooldown). Billing action; requires the billing:write scope. Only call after the user explicitly confirms.",
14
+ inputSchema: z.object({
15
+ confirm: z.literal(true).describe('Must be true after the user explicitly confirms pausing the subscription.'),
16
+ }),
17
+ async execute(client, _input) {
18
+ const result = await client.pauseSubscription();
19
+ return formatPaused(result);
20
+ },
21
+ };
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ export declare const resumeSubscriptionTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
7
+ execute(client: PosterlyClient): Promise<string>;
8
+ };
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ import { mdTitle } from '../lib/format.js';
3
+ export const resumeSubscriptionTool = {
4
+ name: 'resume_subscription',
5
+ description: "Resume the authenticated user's paused posterly subscription, restoring active billing. Requires the billing:write scope.",
6
+ inputSchema: z.object({}),
7
+ async execute(client) {
8
+ await client.resumeSubscription();
9
+ return mdTitle('Subscription resumed', 'Billing has been restored to active.');
10
+ },
11
+ };
@@ -116,6 +116,7 @@ declare const platformSettingsSchema: z.ZodObject<{
116
116
  allow_duet: z.ZodOptional<z.ZodBoolean>;
117
117
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
118
118
  title: z.ZodOptional<z.ZodString>;
119
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
119
120
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
120
121
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
121
122
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -191,6 +192,7 @@ declare const platformSettingsSchema: z.ZodObject<{
191
192
  allow_duet: z.ZodOptional<z.ZodBoolean>;
192
193
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
193
194
  title: z.ZodOptional<z.ZodString>;
195
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
194
196
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
195
197
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
196
198
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -266,6 +268,7 @@ declare const platformSettingsSchema: z.ZodObject<{
266
268
  allow_duet: z.ZodOptional<z.ZodBoolean>;
267
269
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
268
270
  title: z.ZodOptional<z.ZodString>;
271
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
269
272
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
270
273
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
271
274
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -426,6 +429,7 @@ export declare const updatePostTool: {
426
429
  allow_duet: z.ZodOptional<z.ZodBoolean>;
427
430
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
428
431
  title: z.ZodOptional<z.ZodString>;
432
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
429
433
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
430
434
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
431
435
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -501,6 +505,7 @@ export declare const updatePostTool: {
501
505
  allow_duet: z.ZodOptional<z.ZodBoolean>;
502
506
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
503
507
  title: z.ZodOptional<z.ZodString>;
508
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
504
509
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
505
510
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
506
511
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -576,6 +581,7 @@ export declare const updatePostTool: {
576
581
  allow_duet: z.ZodOptional<z.ZodBoolean>;
577
582
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
578
583
  title: z.ZodOptional<z.ZodString>;
584
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
579
585
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
580
586
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
581
587
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -681,6 +687,7 @@ export declare const updatePostTool: {
681
687
  allow_duet: z.ZodOptional<z.ZodBoolean>;
682
688
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
683
689
  title: z.ZodOptional<z.ZodString>;
690
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
684
691
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
685
692
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
686
693
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -785,6 +792,7 @@ export declare const updatePostTool: {
785
792
  allow_duet: z.ZodOptional<z.ZodBoolean>;
786
793
  allow_stitch: z.ZodOptional<z.ZodBoolean>;
787
794
  title: z.ZodOptional<z.ZodString>;
795
+ video_cover_timestamp_ms: z.ZodOptional<z.ZodNumber>;
788
796
  is_commercial_content: z.ZodOptional<z.ZodBoolean>;
789
797
  is_your_brand: z.ZodOptional<z.ZodBoolean>;
790
798
  is_branded_content: z.ZodOptional<z.ZodBoolean>;
@@ -40,6 +40,7 @@ const platformSettingsSchema = z.object({
40
40
  allow_duet: z.boolean().optional(),
41
41
  allow_stitch: z.boolean().optional(),
42
42
  title: z.string().optional(),
43
+ video_cover_timestamp_ms: z.number().nonnegative().optional(), // TikTok: millisecond timestamp of the video frame to use as the cover (direct post only)
43
44
  is_commercial_content: z.boolean().optional(),
44
45
  is_your_brand: z.boolean().optional(),
45
46
  is_branded_content: z.boolean().optional(),
@@ -81,7 +82,7 @@ export const updatePostTool = {
81
82
  post_type: z
82
83
  .string()
83
84
  .optional()
84
- .describe('New post type: text, image, video, carousel, reel, story'),
85
+ .describe('New post type: text, image, video, carousel, reel, story, story_series, document, photo, or cover_photo'),
85
86
  instagram_settings: instagramSettingsSchema
86
87
  .optional()
87
88
  .describe('Instagram-specific updates: post_type, collaborators, user_tags, first_comment, media_alt_texts, is_trial_reel, graduation_strategy (defaults to MANUAL), reel_cover_url, reel_thumb_offset.'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posterly-mcp-server",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "MCP server for posterly: schedule and publish social media posts across 11 platforms from any MCP client (Claude, ChatGPT, Cursor, Windsurf, Cline, and more)",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.poster.ly/mcp",
package/src/index.ts CHANGED
@@ -67,6 +67,11 @@ import { createConnectSessionTool } from './tools/create-connect-session.js';
67
67
  import { getConnectSessionTool } from './tools/get-connect-session.js';
68
68
  import { createApiKeyTool } from './tools/create-api-key.js';
69
69
  import { deleteApiKeyTool } from './tools/delete-api-key.js';
70
+ import { getSubscriptionTool } from './tools/get-subscription.js';
71
+ import { cancelSubscriptionTool } from './tools/cancel-subscription.js';
72
+ import { pauseSubscriptionTool } from './tools/pause-subscription.js';
73
+ import { resumeSubscriptionTool } from './tools/resume-subscription.js';
74
+ import { downgradeSubscriptionTool } from './tools/downgrade-subscription.js';
70
75
  import { listOAuthClientsTool } from './tools/list-oauth-clients.js';
71
76
  import { createOAuthClientTool } from './tools/create-oauth-client.js';
72
77
  import { updateOAuthClientTool } from './tools/update-oauth-client.js';
@@ -248,6 +253,76 @@ server.tool(
248
253
  }
249
254
  );
250
255
 
256
+ server.tool(
257
+ getSubscriptionTool.name,
258
+ getSubscriptionTool.description,
259
+ getSubscriptionTool.inputSchema.shape,
260
+ async () => {
261
+ try {
262
+ const text = await getSubscriptionTool.execute(client);
263
+ return { content: [{ type: 'text' as const, text }] };
264
+ } catch (err: any) {
265
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
266
+ }
267
+ }
268
+ );
269
+
270
+ server.tool(
271
+ cancelSubscriptionTool.name,
272
+ cancelSubscriptionTool.description,
273
+ cancelSubscriptionTool.inputSchema.shape,
274
+ async (input) => {
275
+ try {
276
+ const text = await cancelSubscriptionTool.execute(client, input as any);
277
+ return { content: [{ type: 'text' as const, text }] };
278
+ } catch (err: any) {
279
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
280
+ }
281
+ }
282
+ );
283
+
284
+ server.tool(
285
+ pauseSubscriptionTool.name,
286
+ pauseSubscriptionTool.description,
287
+ pauseSubscriptionTool.inputSchema.shape,
288
+ async (input) => {
289
+ try {
290
+ const text = await pauseSubscriptionTool.execute(client, input as any);
291
+ return { content: [{ type: 'text' as const, text }] };
292
+ } catch (err: any) {
293
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
294
+ }
295
+ }
296
+ );
297
+
298
+ server.tool(
299
+ resumeSubscriptionTool.name,
300
+ resumeSubscriptionTool.description,
301
+ resumeSubscriptionTool.inputSchema.shape,
302
+ async () => {
303
+ try {
304
+ const text = await resumeSubscriptionTool.execute(client);
305
+ return { content: [{ type: 'text' as const, text }] };
306
+ } catch (err: any) {
307
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
308
+ }
309
+ }
310
+ );
311
+
312
+ server.tool(
313
+ downgradeSubscriptionTool.name,
314
+ downgradeSubscriptionTool.description,
315
+ downgradeSubscriptionTool.inputSchema.shape,
316
+ async (input) => {
317
+ try {
318
+ const text = await downgradeSubscriptionTool.execute(client, input as any);
319
+ return { content: [{ type: 'text' as const, text }] };
320
+ } catch (err: any) {
321
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
322
+ }
323
+ }
324
+ );
325
+
251
326
  server.tool(
252
327
  listOAuthClientsTool.name,
253
328
  listOAuthClientsTool.description,
@@ -255,7 +255,9 @@ export type ApiKeyScope =
255
255
  | 'posts:read'
256
256
  | 'posts:write'
257
257
  | 'media:write'
258
- | 'analytics:read';
258
+ | 'analytics:read'
259
+ | 'billing:read'
260
+ | 'billing:write';
259
261
 
260
262
  export type CreateApiKeyPayload = {
261
263
  name?: string;
@@ -301,6 +303,51 @@ export type DeleteApiKeyResponse = {
301
303
  };
302
304
  };
303
305
 
306
+ export type SubscriptionTierName = 'starter' | 'pro' | 'power_user' | 'agency';
307
+
308
+ export type SubscriptionSummary = {
309
+ id: string | null;
310
+ status: string | null;
311
+ tier: string | null;
312
+ cancel_at_period_end: boolean | null;
313
+ current_period_end: string | null;
314
+ trial_ends_at: string | null;
315
+ paused_at: string | null;
316
+ pause_resumes_at: string | null;
317
+ stripe_subscription_id: string | null;
318
+ };
319
+
320
+ export type SubscriptionSummaryResponse = {
321
+ subscription: SubscriptionSummary;
322
+ };
323
+
324
+ export type CancelSubscriptionPayload = {
325
+ reason: string;
326
+ feedback?: string;
327
+ immediate?: boolean;
328
+ };
329
+
330
+ export type CancelSubscriptionResponse = {
331
+ success: true;
332
+ immediate: boolean;
333
+ canceled_at: string | null;
334
+ cancel_at_period_end: boolean;
335
+ };
336
+
337
+ export type PauseSubscriptionResponse = {
338
+ success: true;
339
+ pause_resumes_at: string;
340
+ };
341
+
342
+ export type ResumeSubscriptionResponse = {
343
+ success: true;
344
+ };
345
+
346
+ export type DowngradeSubscriptionResponse = {
347
+ success: true;
348
+ tier: SubscriptionTierName;
349
+ };
350
+
304
351
  export type AskSupportResponse = {
305
352
  success: boolean;
306
353
  workspace_id: string;
@@ -862,6 +909,26 @@ export class PosterlyClient {
862
909
  return this.request('DELETE', `/api-keys/${encodeURIComponent(keyId)}`, data);
863
910
  }
864
911
 
912
+ async getSubscription(): Promise<SubscriptionSummaryResponse> {
913
+ return this.request('GET', '/subscription');
914
+ }
915
+
916
+ async cancelSubscription(data: CancelSubscriptionPayload): Promise<CancelSubscriptionResponse> {
917
+ return this.request('POST', '/subscription/cancel', data);
918
+ }
919
+
920
+ async pauseSubscription(): Promise<PauseSubscriptionResponse> {
921
+ return this.request('POST', '/subscription/pause', {});
922
+ }
923
+
924
+ async resumeSubscription(): Promise<ResumeSubscriptionResponse> {
925
+ return this.request('POST', '/subscription/resume', {});
926
+ }
927
+
928
+ async downgradeSubscription(data: { tier?: SubscriptionTierName }): Promise<DowngradeSubscriptionResponse> {
929
+ return this.request('POST', '/subscription/downgrade', data);
930
+ }
931
+
865
932
  async listAccounts(params?: { workspace_id?: string }): Promise<Account[]> {
866
933
  const searchParams = new URLSearchParams();
867
934
  if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
@@ -5,4 +5,4 @@
5
5
  // tool set (minus the intentional pre-auth signup tools that only this stdio
6
6
  // package exposes). `npm run check:mcp-parity` enforces both the version match
7
7
  // and the tool-list match, and runs in the pre-commit hook.
8
- export const POSTERLY_MCP_VERSION = '0.24.0';
8
+ export const POSTERLY_MCP_VERSION = '0.25.0';
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod';
2
+ import type { CancelSubscriptionResponse, PosterlyClient } from '../lib/api-client.js';
3
+ import { mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
4
+
5
+ // Mirrors CANCELLATION_REASONS in the main app (lib/subscription-reasons.ts).
6
+ // Kept in sync manually because the npm package cannot import from the app.
7
+ const CANCELLATION_REASONS = [
8
+ 'too_expensive',
9
+ 'not_using',
10
+ 'missing_features',
11
+ 'technical_issues',
12
+ 'switching_service',
13
+ 'temporary_break',
14
+ 'other',
15
+ ] as const;
16
+
17
+ function formatCanceled(result: CancelSubscriptionResponse): string {
18
+ return [
19
+ mdTitle(result.immediate ? 'Subscription cancelled immediately' : 'Subscription set to cancel at period end'),
20
+ mdSection('Details', mdKeyValue([
21
+ ['Immediate', result.immediate ? 'yes' : 'no'],
22
+ ['Cancel at period end', result.cancel_at_period_end ? 'yes' : 'no'],
23
+ ['Canceled at', result.canceled_at || 'at end of current period'],
24
+ ])),
25
+ ].join('\n\n');
26
+ }
27
+
28
+ export const cancelSubscriptionTool = {
29
+ name: 'cancel_subscription',
30
+ description:
31
+ "Cancel the authenticated user's posterly subscription. DESTRUCTIVE billing action; requires the billing:write scope. ALWAYS ask the user why they are cancelling FIRST and pass their answer as `reason` (one of the allowed values). By default the subscription is set to cancel at the end of the current period; pass immediate=true only if the user explicitly wants it cancelled right now. Only call after the user explicitly confirms.",
32
+ inputSchema: z.object({
33
+ reason: z.enum(CANCELLATION_REASONS).describe('Why the user is cancelling. Ask the user before calling; do not guess.'),
34
+ feedback: z.string().max(2000).optional().describe('Optional free-text detail the user gave about why they are cancelling.'),
35
+ immediate: z.boolean().optional().describe('Cancel immediately instead of at period end. Defaults to false (cancel at period end).'),
36
+ confirm: z.literal(true).describe('Must be true after the user explicitly confirms the cancellation.'),
37
+ }),
38
+
39
+ async execute(
40
+ client: PosterlyClient,
41
+ input: { reason: (typeof CANCELLATION_REASONS)[number]; feedback?: string; immediate?: boolean; confirm: true },
42
+ ) {
43
+ const result = await client.cancelSubscription({
44
+ reason: input.reason,
45
+ feedback: input.feedback,
46
+ immediate: input.immediate === true,
47
+ });
48
+ return formatCanceled(result);
49
+ },
50
+ };
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import type { CreateApiKeyPayload, CreateApiKeyResponse, PosterlyClient } from '../lib/api-client.js';
3
3
  import { code, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
4
4
 
5
- const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read']);
5
+ const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read', 'billing:read', 'billing:write']);
6
6
 
7
7
  function formatCreatedApiKey(result: CreateApiKeyResponse): string {
8
8
  const key = result.api_key;
@@ -28,7 +28,7 @@ export const createApiKeyTool = {
28
28
  'Create a new Posterly API key for the authenticated user. SECRET-CREATING WRITE: only use after explicit user confirmation. The new key can only request scopes already present on the calling dashboard-created API key; OAuth and managed assistant tokens cannot mint keys.',
29
29
  inputSchema: z.object({
30
30
  name: z.string().trim().min(1).max(120).optional().describe('Human-readable key name.'),
31
- scopes: z.array(scopeSchema).min(1).max(6).optional().describe('Scopes for the new key. Omit to copy the calling key scopes. Cannot exceed the calling key scopes.'),
31
+ scopes: z.array(scopeSchema).min(1).max(8).optional().describe('Scopes for the new key. Omit to copy the calling key scopes. Cannot exceed the calling key scopes. billing:read and billing:write allow managing the posterly subscription (cancel/pause/resume/downgrade).'),
32
32
  workspace_id: z.string().trim().min(1).optional().describe('Optional workspace restriction. Workspace-scoped calling keys cannot create keys outside their workspace.'),
33
33
  expires_in_days: z.number().int().min(1).max(365).optional().describe('Optional expiry from now, up to 365 days. Omit for no expiry.'),
34
34
  confirm: z.literal(true).describe('Must be true after the user explicitly confirms that a new secret API key should be created.'),
@@ -56,6 +56,7 @@ export const platformSettingsSchema = z.object({
56
56
  allow_stitch: z.boolean().optional(),
57
57
  title: z.string().optional(),
58
58
  media_type: z.string().optional(), // TikTok: 'PHOTO' forces a photo slideshow; image media is auto-detected when omitted
59
+ video_cover_timestamp_ms: z.number().nonnegative().optional(), // TikTok: millisecond timestamp of the video frame to use as the cover (direct post only)
59
60
  is_commercial_content: z.boolean().optional(),
60
61
  is_your_brand: z.boolean().optional(),
61
62
  is_branded_content: z.boolean().optional(),
@@ -110,7 +111,7 @@ export const createPostPayloadInputSchema = z.object({
110
111
  post_type: z
111
112
  .string()
112
113
  .optional()
113
- .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.'),
114
+ .describe('Optional post type: text, image, video, carousel, reel, story, story_series, document, photo, cover_photo, x_thread, or threads_thread. Omit it for auto-detection from media. For TikTok photo posts, use photo/image/carousel or omit it; for Facebook cover photos, use cover_photo or platform_settings.post_type.'),
114
115
  thread_posts: z
115
116
  .array(z.string())
116
117
  .optional()
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ import type { DowngradeSubscriptionResponse, PosterlyClient, SubscriptionTierName } from '../lib/api-client.js';
3
+ import { mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
4
+
5
+ const TIER_VALUES = ['starter', 'pro', 'power_user', 'agency'] as const;
6
+
7
+ function formatDowngraded(result: DowngradeSubscriptionResponse): string {
8
+ return [
9
+ mdTitle('Subscription downgrade scheduled'),
10
+ mdSection('Details', mdKeyValue([
11
+ ['New tier at next renewal', result.tier],
12
+ ])),
13
+ ].join('\n\n');
14
+ }
15
+
16
+ export const downgradeSubscriptionTool = {
17
+ name: 'downgrade_subscription',
18
+ description:
19
+ "Downgrade the authenticated user's posterly subscription one tier (or to an explicit lower `tier`) at the next renewal with no proration. Billing action; requires the billing:write scope. Only call after the user explicitly confirms.",
20
+ inputSchema: z.object({
21
+ tier: z.enum(TIER_VALUES).optional().describe('Optional explicit target tier. Omit to drop one tier automatically.'),
22
+ confirm: z.literal(true).describe('Must be true after the user explicitly confirms the downgrade.'),
23
+ }),
24
+
25
+ async execute(client: PosterlyClient, input: { tier?: SubscriptionTierName; confirm: true }) {
26
+ const result = await client.downgradeSubscription({ tier: input.tier });
27
+ return formatDowngraded(result);
28
+ },
29
+ };
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient, SubscriptionSummaryResponse } from '../lib/api-client.js';
3
+ import { formatDateTime, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
4
+
5
+ function formatSubscription(result: SubscriptionSummaryResponse): string {
6
+ const sub = result.subscription;
7
+ return [
8
+ mdTitle('posterly subscription'),
9
+ mdSection('Details', mdKeyValue([
10
+ ['Status', sub.status || 'unknown'],
11
+ ['Tier', sub.tier || 'unknown'],
12
+ ['Cancel at period end', sub.cancel_at_period_end ? 'yes' : 'no'],
13
+ ['Current period end', sub.current_period_end ? formatDateTime(sub.current_period_end) : 'n/a'],
14
+ ['Trial ends', sub.trial_ends_at ? formatDateTime(sub.trial_ends_at) : 'n/a'],
15
+ ['Paused at', sub.paused_at ? formatDateTime(sub.paused_at) : 'n/a'],
16
+ ['Pause resumes at', sub.pause_resumes_at ? formatDateTime(sub.pause_resumes_at) : 'n/a'],
17
+ ])),
18
+ ].join('\n\n');
19
+ }
20
+
21
+ export const getSubscriptionTool = {
22
+ name: 'get_subscription',
23
+ description:
24
+ "Get the authenticated user's posterly subscription: status, tier, cancel-at-period-end, current period end, trial end, and pause state. Read-only. Requires the billing:read scope.",
25
+ inputSchema: z.object({}),
26
+
27
+ async execute(client: PosterlyClient) {
28
+ const result = await client.getSubscription();
29
+ return formatSubscription(result);
30
+ },
31
+ };
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+ import type { PauseSubscriptionResponse, PosterlyClient } from '../lib/api-client.js';
3
+ import { formatDateTime, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
4
+
5
+ function formatPaused(result: PauseSubscriptionResponse): string {
6
+ return [
7
+ mdTitle('Subscription paused'),
8
+ mdSection('Details', mdKeyValue([
9
+ ['Resumes at', result.pause_resumes_at ? formatDateTime(result.pause_resumes_at) : 'n/a'],
10
+ ])),
11
+ ].join('\n\n');
12
+ }
13
+
14
+ export const pauseSubscriptionTool = {
15
+ name: 'pause_subscription',
16
+ description:
17
+ "Pause the authenticated user's posterly subscription for 30 days (one pause allowed per 90-day cooldown). Billing action; requires the billing:write scope. Only call after the user explicitly confirms.",
18
+ inputSchema: z.object({
19
+ confirm: z.literal(true).describe('Must be true after the user explicitly confirms pausing the subscription.'),
20
+ }),
21
+
22
+ async execute(client: PosterlyClient, _input: { confirm: true }) {
23
+ const result = await client.pauseSubscription();
24
+ return formatPaused(result);
25
+ },
26
+ };
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ import { mdTitle } from '../lib/format.js';
4
+
5
+ export const resumeSubscriptionTool = {
6
+ name: 'resume_subscription',
7
+ description:
8
+ "Resume the authenticated user's paused posterly subscription, restoring active billing. Requires the billing:write scope.",
9
+ inputSchema: z.object({}),
10
+
11
+ async execute(client: PosterlyClient) {
12
+ await client.resumeSubscription();
13
+ return mdTitle('Subscription resumed', 'Billing has been restored to active.');
14
+ },
15
+ };
@@ -43,6 +43,7 @@ const platformSettingsSchema = z.object({
43
43
  allow_duet: z.boolean().optional(),
44
44
  allow_stitch: z.boolean().optional(),
45
45
  title: z.string().optional(),
46
+ video_cover_timestamp_ms: z.number().nonnegative().optional(), // TikTok: millisecond timestamp of the video frame to use as the cover (direct post only)
46
47
  is_commercial_content: z.boolean().optional(),
47
48
  is_your_brand: z.boolean().optional(),
48
49
  is_branded_content: z.boolean().optional(),
@@ -86,7 +87,7 @@ export const updatePostTool = {
86
87
  post_type: z
87
88
  .string()
88
89
  .optional()
89
- .describe('New post type: text, image, video, carousel, reel, story'),
90
+ .describe('New post type: text, image, video, carousel, reel, story, story_series, document, photo, or cover_photo'),
90
91
  instagram_settings: instagramSettingsSchema
91
92
  .optional()
92
93
  .describe('Instagram-specific updates: post_type, collaborators, user_tags, first_comment, media_alt_texts, is_trial_reel, graduation_strategy (defaults to MANUAL), reel_cover_url, reel_thumb_offset.'),