posterly-mcp-server 0.43.3 → 0.43.5

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 (74) hide show
  1. package/README.md +18 -18
  2. package/dist/lib/api-client.d.ts +14 -0
  3. package/dist/lib/api-client.js +6 -2
  4. package/dist/lib/format.js +1 -1
  5. package/dist/lib/version.d.ts +1 -1
  6. package/dist/lib/version.js +1 -1
  7. package/dist/tools/ask-support.js +3 -3
  8. package/dist/tools/create-api-key.js +1 -1
  9. package/dist/tools/create-connect-session.js +3 -3
  10. package/dist/tools/create-post.js +6 -6
  11. package/dist/tools/create-posts-batch.js +2 -2
  12. package/dist/tools/create-webhook.d.ts +3 -3
  13. package/dist/tools/delete-api-key.js +1 -1
  14. package/dist/tools/delete-post.js +2 -2
  15. package/dist/tools/generate-captions.js +1 -1
  16. package/dist/tools/generate-image.js +1 -1
  17. package/dist/tools/get-agent-signup-info.js +8 -8
  18. package/dist/tools/get-comment.js +1 -1
  19. package/dist/tools/get-connect-link.js +1 -1
  20. package/dist/tools/get-connect-session.js +4 -4
  21. package/dist/tools/get-mcp-status.js +4 -4
  22. package/dist/tools/get-post.js +1 -1
  23. package/dist/tools/get-signup-session.js +7 -7
  24. package/dist/tools/list-accounts.js +1 -1
  25. package/dist/tools/list-brands.js +1 -1
  26. package/dist/tools/list-comments.d.ts +4 -4
  27. package/dist/tools/list-comments.js +3 -3
  28. package/dist/tools/list-platforms.js +1 -1
  29. package/dist/tools/list-posts.d.ts +8 -0
  30. package/dist/tools/list-posts.js +24 -5
  31. package/dist/tools/reply-to-comment.js +1 -1
  32. package/dist/tools/start-signup.js +11 -11
  33. package/dist/tools/submit-agent-feedback.d.ts +4 -4
  34. package/dist/tools/submit-product-feedback.d.ts +4 -4
  35. package/dist/tools/sync-inbox.js +2 -2
  36. package/dist/tools/update-post.js +2 -2
  37. package/dist/tools/update-webhook.d.ts +3 -3
  38. package/dist/tools/upload-media-from-url.js +1 -1
  39. package/dist/tools/webhook-events.d.ts +2 -2
  40. package/dist/tools/webhook-events.js +5 -0
  41. package/dist/tools/whoami.js +1 -1
  42. package/package.json +1 -1
  43. package/server.json +2 -2
  44. package/src/lib/api-client.ts +20 -2
  45. package/src/lib/format.ts +1 -1
  46. package/src/lib/version.ts +1 -1
  47. package/src/tools/ask-support.ts +3 -3
  48. package/src/tools/create-api-key.ts +1 -1
  49. package/src/tools/create-connect-session.ts +3 -3
  50. package/src/tools/create-post.ts +6 -6
  51. package/src/tools/create-posts-batch.ts +2 -2
  52. package/src/tools/delete-api-key.ts +1 -1
  53. package/src/tools/delete-post.ts +2 -2
  54. package/src/tools/generate-captions.ts +1 -1
  55. package/src/tools/generate-image.ts +1 -1
  56. package/src/tools/get-agent-signup-info.ts +8 -8
  57. package/src/tools/get-comment.ts +1 -1
  58. package/src/tools/get-connect-link.ts +1 -1
  59. package/src/tools/get-connect-session.ts +4 -4
  60. package/src/tools/get-mcp-status.ts +4 -4
  61. package/src/tools/get-post.ts +1 -1
  62. package/src/tools/get-signup-session.ts +7 -7
  63. package/src/tools/list-accounts.ts +1 -1
  64. package/src/tools/list-brands.ts +1 -1
  65. package/src/tools/list-comments.ts +4 -4
  66. package/src/tools/list-platforms.ts +1 -1
  67. package/src/tools/list-posts.ts +23 -5
  68. package/src/tools/reply-to-comment.ts +1 -1
  69. package/src/tools/start-signup.ts +11 -11
  70. package/src/tools/sync-inbox.ts +2 -2
  71. package/src/tools/update-post.ts +2 -2
  72. package/src/tools/upload-media-from-url.ts +1 -1
  73. package/src/tools/webhook-events.ts +5 -0
  74. package/src/tools/whoami.ts +1 -1
@@ -14,7 +14,7 @@ export const listBrandsTool = {
14
14
  if (brands.length === 0) {
15
15
  return input.workspace_id
16
16
  ? mdEmpty('brands', 'This workspace does not have any brands or clients yet.')
17
- : mdEmpty('brands', 'No brands or clients were found for this API key.', 'Create a brand in the Posterly dashboard, then run this again.');
17
+ : mdEmpty('brands', 'No brands or clients were found for this API key.', 'Create a brand in the posterly dashboard, then run this again.');
18
18
  }
19
19
  return [
20
20
  mdTitle(`Brands (${brands.length})`),
@@ -6,13 +6,13 @@ export declare const listCommentsTool: {
6
6
  inputSchema: z.ZodObject<{
7
7
  workspace_id: z.ZodOptional<z.ZodString>;
8
8
  account_id: z.ZodOptional<z.ZodNumber>;
9
- platform: z.ZodOptional<z.ZodEnum<["instagram", "facebook", "threads"]>>;
9
+ platform: z.ZodOptional<z.ZodEnum<["instagram", "facebook", "threads", "linkedin"]>>;
10
10
  filter: z.ZodOptional<z.ZodEnum<["all", "unread", "flagged"]>>;
11
11
  search: z.ZodOptional<z.ZodString>;
12
12
  limit: z.ZodOptional<z.ZodNumber>;
13
13
  offset: z.ZodOptional<z.ZodNumber>;
14
14
  }, "strip", z.ZodTypeAny, {
15
- platform?: "instagram" | "facebook" | "threads" | undefined;
15
+ platform?: "instagram" | "facebook" | "threads" | "linkedin" | undefined;
16
16
  workspace_id?: string | undefined;
17
17
  account_id?: number | undefined;
18
18
  limit?: number | undefined;
@@ -20,7 +20,7 @@ export declare const listCommentsTool: {
20
20
  filter?: "all" | "unread" | "flagged" | undefined;
21
21
  search?: string | undefined;
22
22
  }, {
23
- platform?: "instagram" | "facebook" | "threads" | undefined;
23
+ platform?: "instagram" | "facebook" | "threads" | "linkedin" | undefined;
24
24
  workspace_id?: string | undefined;
25
25
  account_id?: number | undefined;
26
26
  limit?: number | undefined;
@@ -31,7 +31,7 @@ export declare const listCommentsTool: {
31
31
  execute(client: PosterlyClient, input: {
32
32
  workspace_id?: string;
33
33
  account_id?: number;
34
- platform?: "instagram" | "facebook" | "threads";
34
+ platform?: "instagram" | "facebook" | "threads" | "linkedin";
35
35
  filter?: "all" | "unread" | "flagged";
36
36
  search?: string;
37
37
  limit?: number;
@@ -1,13 +1,13 @@
1
1
  import { z } from 'zod';
2
2
  import { truncateText } from '../lib/format.js';
3
- const INBOX_PLATFORMS = ['instagram', 'facebook', 'threads'];
3
+ const INBOX_PLATFORMS = ['instagram', 'facebook', 'threads', 'linkedin'];
4
4
  export const listCommentsTool = {
5
5
  name: 'list_comments',
6
- description: 'List social inbox comments and replies for Instagram, Facebook Pages, and Threads. Filter by account, platform, unread/flagged, or search. Requires a Pro plan or higher plus posts:read.',
6
+ description: 'List social inbox comments and replies for Instagram, Facebook Pages, Threads, and LinkedIn Pages. Filter by account, platform, unread/flagged, or search. Requires a Pro plan or higher plus posts:read.',
7
7
  inputSchema: z.object({
8
8
  workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
9
9
  account_id: z.number().int().positive().optional().describe('Social account ID from list_accounts.'),
10
- platform: z.enum(INBOX_PLATFORMS).optional().describe('instagram, facebook, or threads.'),
10
+ platform: z.enum(INBOX_PLATFORMS).optional().describe('instagram, facebook, threads, or linkedin.'),
11
11
  filter: z.enum(['all', 'unread', 'flagged']).optional().describe('Comment filter. Defaults to all.'),
12
12
  search: z.string().optional().describe('Search comment content or author username.'),
13
13
  limit: z.number().int().min(1).max(100).optional().describe('Number of comments to return. Default 50, max 100.'),
@@ -15,6 +15,6 @@ export const listPlatformsTool = {
15
15
  const helperCount = platform.helper_tools?.length || 0;
16
16
  return `• ${platform.label} (${platform.id}): ${platform.status}; post types: ${platform.post_types.join(', ') || 'n/a'}; helpers: ${helperCount}; analytics: ${platform.analytics_supported ? 'yes' : 'no'}`;
17
17
  });
18
- return `Posterly platforms (${result.platforms.length}):\n${lines.join('\n')}`;
18
+ return `posterly platforms (${result.platforms.length}):\n${lines.join('\n')}`;
19
19
  },
20
20
  };
@@ -9,18 +9,24 @@ export declare const listPostsTool: {
9
9
  account_id: z.ZodOptional<z.ZodString>;
10
10
  limit: z.ZodOptional<z.ZodNumber>;
11
11
  workspace_id: z.ZodOptional<z.ZodString>;
12
+ approval_status: z.ZodOptional<z.ZodEnum<["none", "pending_client", "approved", "changes_requested", "rejected"]>>;
13
+ brand_id: z.ZodOptional<z.ZodString>;
12
14
  }, "strip", z.ZodTypeAny, {
13
15
  platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
14
16
  status?: string | undefined;
15
17
  workspace_id?: string | undefined;
16
18
  account_id?: string | undefined;
17
19
  limit?: number | undefined;
20
+ approval_status?: "none" | "pending_client" | "approved" | "changes_requested" | "rejected" | undefined;
21
+ brand_id?: string | undefined;
18
22
  }, {
19
23
  platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
20
24
  status?: string | undefined;
21
25
  workspace_id?: string | undefined;
22
26
  account_id?: string | undefined;
23
27
  limit?: number | undefined;
28
+ approval_status?: "none" | "pending_client" | "approved" | "changes_requested" | "rejected" | undefined;
29
+ brand_id?: string | undefined;
24
30
  }>;
25
31
  execute(client: PosterlyClient, input: {
26
32
  status?: string;
@@ -28,5 +34,7 @@ export declare const listPostsTool: {
28
34
  account_id?: string;
29
35
  limit?: number;
30
36
  workspace_id?: string;
37
+ approval_status?: string;
38
+ brand_id?: string;
31
39
  }): Promise<string>;
32
40
  };
@@ -3,7 +3,7 @@ import { dashboardUrlForPostList, formatLocalDateTime, truncateText } from '../l
3
3
  import { SUPPORTED_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
4
4
  export const listPostsTool = {
5
5
  name: 'list_posts',
6
- description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, account_id, or workspace_id. If workspace_id is omitted, posts across every workspace the caller is a member of are returned.',
6
+ description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, account_id, workspace_id, approval_status, or brand_id. If workspace_id is omitted, posts across every workspace the caller is a member of are returned.',
7
7
  inputSchema: z.object({
8
8
  status: z
9
9
  .string()
@@ -27,6 +27,14 @@ export const listPostsTool = {
27
27
  .string()
28
28
  .optional()
29
29
  .describe('Filter to posts in a specific workspace (get IDs via whoami).'),
30
+ approval_status: z
31
+ .enum(['none', 'pending_client', 'approved', 'changes_requested', 'rejected'])
32
+ .optional()
33
+ .describe('Filter by client review approval status. pending_client means the post is waiting on the client.'),
34
+ brand_id: z
35
+ .string()
36
+ .optional()
37
+ .describe('Filter to posts assigned to a specific brand/client (the id from list_brands).'),
30
38
  }),
31
39
  async execute(client, input) {
32
40
  const { posts, total } = await client.listPosts({
@@ -34,19 +42,30 @@ export const listPostsTool = {
34
42
  limit: input.limit || 20,
35
43
  });
36
44
  if (posts.length === 0) {
37
- return 'No posts found matching your criteria.\n\nNext step for the AI agent: tell the user their Posterly queue is clear for that filter, then ask what they want to schedule next.';
45
+ return 'No posts found matching your criteria.\n\nNext step for the AI agent: tell the user their posterly queue is clear for that filter, then ask what they want to schedule next.';
38
46
  }
39
47
  const lines = posts.map((p) => {
40
48
  const date = formatLocalDateTime(p.scheduled_at);
41
49
  const caption = truncateText(p.content, 60);
42
- return `• [${p.status}] #${p.id}: ${caption} (${date})`;
50
+ const approvalBits = [];
51
+ if (p.approval && p.approval.status !== 'none') {
52
+ approvalBits.push(`approval: ${p.approval.status}`);
53
+ if (p.approval.waiting_on)
54
+ approvalBits.push(`waiting on: ${p.approval.waiting_on}`);
55
+ }
56
+ if (p.brand_id)
57
+ approvalBits.push(`brand: ${p.brand_id}`);
58
+ if (p.content_plan_id)
59
+ approvalBits.push(`plan: ${p.content_plan_id}`);
60
+ const approvalSuffix = approvalBits.length > 0 ? ` [${approvalBits.join(', ')}]` : '';
61
+ return `• [${p.status}] #${p.id}: ${caption} (${date})${approvalSuffix}`;
43
62
  });
44
63
  return [
45
64
  `Posts (${posts.length} of ${total}):`,
46
65
  ...lines,
47
66
  '',
48
- `View in Posterly: ${dashboardUrlForPostList(posts)}`,
49
- 'Next step for the AI agent: summarize the queue in plain language and use the dashboard link above if the user wants to inspect it in Posterly.',
67
+ `View in posterly: ${dashboardUrlForPostList(posts)}`,
68
+ 'Next step for the AI agent: summarize the queue in plain language and use the dashboard link above if the user wants to inspect it in posterly.',
50
69
  ].join('\n');
51
70
  },
52
71
  };
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  export const replyToCommentTool = {
3
3
  name: 'reply_to_comment',
4
- description: 'Reply to an Instagram, Facebook Page, or Threads inbox comment. WRITE: posts to the social network. Facebook has no private comment replies. Threads has no DMs, so only public replies. Instagram supports public or private. Requires a Pro plan or higher plus posts:write. Pass confirm=true only after explicit user confirmation.',
4
+ description: 'Reply to an Instagram, Facebook Page, Threads, or LinkedIn Page inbox comment. WRITE: posts to the social network. Facebook has no private comment replies. Threads has no DMs, so only public replies. Instagram supports public or private. LinkedIn Page comments are public replies only. Requires a Pro plan or higher plus posts:write. Pass confirm=true only after explicit user confirmation.',
5
5
  inputSchema: z.object({
6
6
  comment_id: z.string().min(1).describe('Comment ID from list_comments.'),
7
7
  content: z.string().min(1).describe('Reply text to post.'),
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  const DEFAULT_RETURN_PATH = '/dashboard/api?api_signup=success';
3
3
  export const startSignupTool = {
4
4
  name: 'start_signup',
5
- description: 'Start the public Posterly paid signup flow for a user before Posterly API access exists. Returns a signup poll URL, a browser Checkout fallback URL, and, when enabled, a prepaid PaymentIntent the agent can pay with a Link Agent Wallet virtual card (credential_type: card). The user still sets a password and approves social OAuth in the browser. Never ask for card numbers, a Posterly password, or social passwords in chat.',
5
+ description: 'Start the public posterly paid signup flow for a user before posterly API access exists. Returns a signup poll URL, a browser Checkout fallback URL, and, when enabled, a prepaid PaymentIntent the agent can pay with a Link Agent Wallet virtual card (credential_type: card). The user still sets a password and approves social OAuth in the browser. Never ask for card numbers, a posterly password, or social passwords in chat.',
6
6
  inputSchema: z.object({
7
7
  email: z
8
8
  .string()
@@ -11,11 +11,11 @@ export const startSignupTool = {
11
11
  name: z
12
12
  .string()
13
13
  .optional()
14
- .describe('Optional user or workspace name to pass into Posterly signup.'),
14
+ .describe('Optional user or workspace name to pass into posterly signup.'),
15
15
  tier: z
16
16
  .enum(['starter', 'pro', 'power_user', 'agency'])
17
17
  .default('starter')
18
- .describe('Posterly plan to start. Use starter unless the user explicitly chooses another plan.'),
18
+ .describe('posterly plan to start. Use starter unless the user explicitly chooses another plan.'),
19
19
  interval: z
20
20
  .enum(['month'])
21
21
  .default('month')
@@ -27,7 +27,7 @@ export const startSignupTool = {
27
27
  return_path: z
28
28
  .string()
29
29
  .default(DEFAULT_RETURN_PATH)
30
- .describe('Where Posterly should continue after checkout and password setup.'),
30
+ .describe('Where posterly should continue after checkout and password setup.'),
31
31
  source: z
32
32
  .string()
33
33
  .default('posterly_mcp_pre_auth_signup')
@@ -72,7 +72,7 @@ function formatSignupStart(response, includeRaw) {
72
72
  const agentPay = response.agent_pay || session?.agent_pay;
73
73
  const payment = response.payment || session?.payment;
74
74
  const lines = [
75
- 'Posterly signup started.',
75
+ 'posterly signup started.',
76
76
  sessionId ? `Signup session: ${sessionId}` : '',
77
77
  `Status: ${status}`,
78
78
  message ? `Message: ${message}` : '',
@@ -109,17 +109,17 @@ function formatSignupStart(response, includeRaw) {
109
109
  '1. Prefer agent_pay: confirm the PaymentIntent with a one-time Link Agent Wallet virtual card (credential_type: card). Never paste card numbers into chat or send them to posterly.',
110
110
  '2. If the user would rather pay on the website, send them the Checkout handoff URL instead.',
111
111
  '3. Call get_signup_session with the signup session ID until the status changes.',
112
- '4. Tell the user when Posterly sends the password setup email. Password, social OAuth, and publish approval stay in the browser.',
113
- '5. If status becomes agent_access_required, ask the user to install or paste the Posterly MCP/API setup instructions into this trusted AI chat, then onboard them in this conversation (about you, connect, brand voice, first post). Do not send them to /onboarding-v2.',
114
- '6. Do not ask for card details, the Posterly password, social passwords, or OAuth codes.',
112
+ '4. Tell the user when posterly sends the password setup email. Password, social OAuth, and publish approval stay in the browser.',
113
+ '5. If status becomes agent_access_required, ask the user to install or paste the posterly MCP/API setup instructions into this trusted AI chat, then onboard them in this conversation (about you, connect, brand voice, first post). Do not send them to /onboarding-v2.',
114
+ '6. Do not ask for card details, the posterly password, social passwords, or OAuth codes.',
115
115
  ]
116
116
  : [
117
117
  '1. If you hold an approved payment credential such as a Link agent wallet, you can pay the Checkout handoff URL yourself: create a spend request for the amount and merchant above, get the user\'s approval in the wallet, then complete the Checkout page with that credential. Never paste card numbers into chat or send them to posterly.',
118
118
  '2. Otherwise, send the Checkout handoff URL to the user so they can pay securely in their browser. Use the raw Stripe checkout URL only if you preserve the full URL including any fragment. posterly does not email this link, so do not tell the user to check their inbox at this stage.',
119
119
  '3. Call get_signup_session with the signup session ID until the status changes.',
120
- '4. Tell the user when Posterly sends the password setup email.',
121
- '5. If status becomes agent_access_required, ask the user to install or paste the Posterly MCP/API setup instructions into this trusted AI chat, then onboard them in this conversation (about you, connect, brand voice, first post). Do not send them to /onboarding-v2.',
122
- '6. Do not ask for card details, the Posterly password, social passwords, or OAuth codes.',
120
+ '4. Tell the user when posterly sends the password setup email.',
121
+ '5. If status becomes agent_access_required, ask the user to install or paste the posterly MCP/API setup instructions into this trusted AI chat, then onboard them in this conversation (about you, connect, brand voice, first post). Do not send them to /onboarding-v2.',
122
+ '6. Do not ask for card details, the posterly password, social passwords, or OAuth codes.',
123
123
  ]),
124
124
  includeRaw ? `\nRaw signup response:\n${JSON.stringify(response, null, 2)}` : '',
125
125
  ];
@@ -27,8 +27,8 @@ export declare const submitAgentFeedbackInputSchema: z.ZodObject<{
27
27
  source: "mcp" | "cli" | "rest" | "skill";
28
28
  tool: string;
29
29
  outcome: "success" | "error" | "abandoned";
30
- error_code?: string | undefined;
31
30
  client?: string | undefined;
31
+ error_code?: string | undefined;
32
32
  comment?: string | undefined;
33
33
  context?: {
34
34
  request_id?: string | undefined;
@@ -40,8 +40,8 @@ export declare const submitAgentFeedbackInputSchema: z.ZodObject<{
40
40
  source: "mcp" | "cli" | "rest" | "skill";
41
41
  tool: string;
42
42
  outcome: "success" | "error" | "abandoned";
43
- error_code?: string | undefined;
44
43
  client?: string | undefined;
44
+ error_code?: string | undefined;
45
45
  comment?: string | undefined;
46
46
  context?: {
47
47
  request_id?: string | undefined;
@@ -81,8 +81,8 @@ export declare const submitAgentFeedbackTool: {
81
81
  source: "mcp" | "cli" | "rest" | "skill";
82
82
  tool: string;
83
83
  outcome: "success" | "error" | "abandoned";
84
- error_code?: string | undefined;
85
84
  client?: string | undefined;
85
+ error_code?: string | undefined;
86
86
  comment?: string | undefined;
87
87
  context?: {
88
88
  request_id?: string | undefined;
@@ -94,8 +94,8 @@ export declare const submitAgentFeedbackTool: {
94
94
  source: "mcp" | "cli" | "rest" | "skill";
95
95
  tool: string;
96
96
  outcome: "success" | "error" | "abandoned";
97
- error_code?: string | undefined;
98
97
  client?: string | undefined;
98
+ error_code?: string | undefined;
99
99
  comment?: string | undefined;
100
100
  context?: {
101
101
  request_id?: string | undefined;
@@ -21,9 +21,9 @@ export declare const submitProductFeedbackInputSchema: z.ZodObject<{
21
21
  title: string;
22
22
  confirm: true;
23
23
  category: "bug" | "idea" | "feedback";
24
+ client?: string | undefined;
24
25
  description?: string | undefined;
25
26
  source?: "mcp" | "cli" | "rest" | "skill" | undefined;
26
- client?: string | undefined;
27
27
  context?: {
28
28
  request_id?: string | undefined;
29
29
  related_tool?: string | undefined;
@@ -32,9 +32,9 @@ export declare const submitProductFeedbackInputSchema: z.ZodObject<{
32
32
  title: string;
33
33
  confirm: true;
34
34
  category: "bug" | "idea" | "feedback";
35
+ client?: string | undefined;
35
36
  description?: string | undefined;
36
37
  source?: "mcp" | "cli" | "rest" | "skill" | undefined;
37
- client?: string | undefined;
38
38
  context?: {
39
39
  request_id?: string | undefined;
40
40
  related_tool?: string | undefined;
@@ -65,9 +65,9 @@ export declare const submitProductFeedbackTool: {
65
65
  title: string;
66
66
  confirm: true;
67
67
  category: "bug" | "idea" | "feedback";
68
+ client?: string | undefined;
68
69
  description?: string | undefined;
69
70
  source?: "mcp" | "cli" | "rest" | "skill" | undefined;
70
- client?: string | undefined;
71
71
  context?: {
72
72
  request_id?: string | undefined;
73
73
  related_tool?: string | undefined;
@@ -76,9 +76,9 @@ export declare const submitProductFeedbackTool: {
76
76
  title: string;
77
77
  confirm: true;
78
78
  category: "bug" | "idea" | "feedback";
79
+ client?: string | undefined;
79
80
  description?: string | undefined;
80
81
  source?: "mcp" | "cli" | "rest" | "skill" | undefined;
81
- client?: string | undefined;
82
82
  context?: {
83
83
  request_id?: string | undefined;
84
84
  related_tool?: string | undefined;
@@ -1,10 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  export const syncInboxTool = {
3
3
  name: 'sync_inbox',
4
- description: 'Pull the latest Instagram, Facebook Page, or Threads inbox data for one connected account. Honors a 30-second cooldown. Threads syncs replies only (no DMs). Requires a Pro plan or higher plus posts:write.',
4
+ description: 'Pull the latest Instagram, Facebook Page, Threads, or LinkedIn inbox data for one connected account. Honors a 30-second cooldown (60 seconds for LinkedIn). Threads and LinkedIn sync comments only (no DMs). Requires a Pro plan or higher plus posts:write.',
5
5
  inputSchema: z.object({
6
6
  account_id: z.number().int().positive().describe('Social account ID from list_accounts.'),
7
- sync_type: z.enum(['all', 'dm', 'comments']).optional().describe('What to sync. Defaults to all. Threads is comments-only.'),
7
+ sync_type: z.enum(['all', 'dm', 'comments']).optional().describe('What to sync. Defaults to all. Threads and LinkedIn are comments-only.'),
8
8
  workspace_id: z.string().optional().describe('Workspace ID from whoami.'),
9
9
  }),
10
10
  async execute(client, input) {
@@ -116,8 +116,8 @@ export const updatePostTool = {
116
116
  .string()
117
117
  .optional()
118
118
  .describe('New scheduled time (ISO 8601, e.g. 2026-03-10T09:00:00Z)'),
119
- media_url: z.string().optional().describe('New media URL. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved. Short-lived signed URLs must still be live when this tool runs.'),
120
- media_urls: z.array(z.string()).optional().describe('New media URLs for carousel. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved.'),
119
+ media_url: z.string().optional().describe('New media URL. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into posterly storage before the post is saved. Short-lived signed URLs must still be live when this tool runs.'),
120
+ media_urls: z.array(z.string()).optional().describe('New media URLs for carousel. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into posterly storage before the post is saved.'),
121
121
  post_type: z
122
122
  .string()
123
123
  .optional()
@@ -6,7 +6,7 @@ export declare const updateWebhookTool: {
6
6
  inputSchema: z.ZodObject<{
7
7
  webhook_id: z.ZodString;
8
8
  url: z.ZodOptional<z.ZodString>;
9
- events: z.ZodOptional<z.ZodArray<z.ZodEnum<["webhook.test", "post.created", "post.updated", "post.deleted", "post.publishing", "post.published", "post.failed", "account.disconnected", "analytics.synced"]>, "many">>;
9
+ events: z.ZodOptional<z.ZodArray<z.ZodEnum<["webhook.test", "post.created", "post.updated", "post.deleted", "post.publishing", "post.published", "post.failed", "account.disconnected", "analytics.synced", "approval.requested", "approval.approved", "approval.changes_requested", "approval.rejected", "approval.commented"]>, "many">>;
10
10
  workspace_id: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
11
11
  description: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNull]>>;
12
12
  is_active: z.ZodOptional<z.ZodBoolean>;
@@ -17,7 +17,7 @@ export declare const updateWebhookTool: {
17
17
  description?: string | null | undefined;
18
18
  workspace_id?: string | null | undefined;
19
19
  url?: string | undefined;
20
- events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced")[] | undefined;
20
+ events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced" | "approval.requested" | "approval.approved" | "approval.changes_requested" | "approval.rejected" | "approval.commented")[] | undefined;
21
21
  is_active?: boolean | undefined;
22
22
  }, {
23
23
  confirm: true;
@@ -25,7 +25,7 @@ export declare const updateWebhookTool: {
25
25
  description?: string | null | undefined;
26
26
  workspace_id?: string | null | undefined;
27
27
  url?: string | undefined;
28
- events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced")[] | undefined;
28
+ events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced" | "approval.requested" | "approval.approved" | "approval.changes_requested" | "approval.rejected" | "approval.commented")[] | undefined;
29
29
  is_active?: boolean | undefined;
30
30
  }>;
31
31
  execute(client: PosterlyClient, input: {
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  export const uploadMediaFromUrlTool = {
3
3
  name: 'upload_media_from_url',
4
- description: 'Fetch an image or video from a public HTTPS URL into Posterly storage. Returns a URL that can be used with create_post. You can also pass public HTTP(S) media URLs directly to create_post/update_post; use this tool when you want to upload once and reuse the returned Posterly URL.',
4
+ description: 'Fetch an image or video from a public HTTPS URL into posterly storage. Returns a URL that can be used with create_post. You can also pass public HTTP(S) media URLs directly to create_post/update_post; use this tool when you want to upload once and reuse the returned posterly URL.',
5
5
  inputSchema: z.object({
6
6
  url: z.string().url().describe('Public HTTPS URL for the image or video. Local/private network URLs are blocked.'),
7
7
  filename: z.string().optional().describe('Optional filename override.'),
@@ -1,3 +1,3 @@
1
1
  import { z } from 'zod';
2
- export declare const WEBHOOK_EVENTS: readonly ["webhook.test", "post.created", "post.updated", "post.deleted", "post.publishing", "post.published", "post.failed", "account.disconnected", "analytics.synced"];
3
- export declare const webhookEventSchema: z.ZodEnum<["webhook.test", "post.created", "post.updated", "post.deleted", "post.publishing", "post.published", "post.failed", "account.disconnected", "analytics.synced"]>;
2
+ export declare const WEBHOOK_EVENTS: readonly ["webhook.test", "post.created", "post.updated", "post.deleted", "post.publishing", "post.published", "post.failed", "account.disconnected", "analytics.synced", "approval.requested", "approval.approved", "approval.changes_requested", "approval.rejected", "approval.commented"];
3
+ export declare const webhookEventSchema: z.ZodEnum<["webhook.test", "post.created", "post.updated", "post.deleted", "post.publishing", "post.published", "post.failed", "account.disconnected", "analytics.synced", "approval.requested", "approval.approved", "approval.changes_requested", "approval.rejected", "approval.commented"]>;
@@ -9,5 +9,10 @@ export const WEBHOOK_EVENTS = [
9
9
  'post.failed',
10
10
  'account.disconnected',
11
11
  'analytics.synced',
12
+ 'approval.requested',
13
+ 'approval.approved',
14
+ 'approval.changes_requested',
15
+ 'approval.rejected',
16
+ 'approval.commented',
12
17
  ];
13
18
  export const webhookEventSchema = z.enum(WEBHOOK_EVENTS);
@@ -7,7 +7,7 @@ export const whoamiTool = {
7
7
  async execute(client) {
8
8
  const info = await client.whoami();
9
9
  return [
10
- mdTitle('Posterly identity'),
10
+ mdTitle('posterly identity'),
11
11
  mdKeyValue([
12
12
  ['User', info.user.email || code(info.user.id)],
13
13
  ['API scopes', info.api_key.scopes.join(', ') || 'none'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posterly-mcp-server",
3
- "version": "0.43.3",
3
+ "version": "0.43.5",
4
4
  "mcpName": "io.github.awpthorp/posterly",
5
5
  "description": "MCP server for posterly: schedule and publish social media posts across 18 platforms from any MCP client (Claude, ChatGPT, Cursor, Windsurf, Cline, and more)",
6
6
  "license": "MIT",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.awpthorp/posterly",
4
4
  "title": "posterly",
5
5
  "description": "Validate, schedule, publish, and analyze social content across 18 platforms with posterly.",
6
- "version": "0.43.3",
6
+ "version": "0.43.5",
7
7
  "websiteUrl": "https://www.poster.ly/mcp",
8
8
  "repository": {
9
9
  "url": "https://github.com/awpthorp/posterly",
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "registryType": "npm",
16
16
  "identifier": "posterly-mcp-server",
17
- "version": "0.43.3",
17
+ "version": "0.43.5",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },
@@ -138,6 +138,17 @@ export interface DismissSuggestionResponse {
138
138
  status: string;
139
139
  }
140
140
 
141
+ export type ApiPostApprovalStatus = 'none' | 'pending_client' | 'approved' | 'changes_requested' | 'rejected';
142
+
143
+ export interface ApiPostApproval {
144
+ status: ApiPostApprovalStatus;
145
+ asset_status: ApiPostApprovalStatus;
146
+ caption_status: ApiPostApprovalStatus;
147
+ waiting_on: 'client' | 'team' | null;
148
+ requested_at: string | null;
149
+ completed_at: string | null;
150
+ }
151
+
141
152
  export interface Post {
142
153
  id: number;
143
154
  content: string;
@@ -154,6 +165,9 @@ export interface Post {
154
165
  reel_cover_url?: string | null;
155
166
  reel_cover_method?: string | null;
156
167
  reel_thumb_offset?: number | null;
168
+ brand_id?: string | null;
169
+ content_plan_id?: string | null;
170
+ approval?: ApiPostApproval;
157
171
  }
158
172
 
159
173
  export interface InstagramPostSettings {
@@ -1095,7 +1109,7 @@ export class PosterlyClient {
1095
1109
  private requireApiKey(): void {
1096
1110
  if (!this.apiKey) {
1097
1111
  throw new Error(
1098
- 'Posterly access is not installed yet. Use start_signup to begin paid setup, or set POSTERLY_API_KEY after signup.'
1112
+ 'posterly access is not installed yet. Use start_signup to begin paid setup, or set POSTERLY_API_KEY after signup.'
1099
1113
  );
1100
1114
  }
1101
1115
  }
@@ -1354,6 +1368,8 @@ export class PosterlyClient {
1354
1368
  limit?: number;
1355
1369
  offset?: number;
1356
1370
  workspace_id?: string;
1371
+ approval_status?: string;
1372
+ brand_id?: string;
1357
1373
  }): Promise<{ posts: Post[]; total: number }> {
1358
1374
  const searchParams = new URLSearchParams();
1359
1375
  if (params?.status) searchParams.set('status', params.status);
@@ -1362,6 +1378,8 @@ export class PosterlyClient {
1362
1378
  if (params?.limit) searchParams.set('limit', String(params.limit));
1363
1379
  if (params?.offset) searchParams.set('offset', String(params.offset));
1364
1380
  if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
1381
+ if (params?.approval_status) searchParams.set('approval_status', params.approval_status);
1382
+ if (params?.brand_id) searchParams.set('brand_id', params.brand_id);
1365
1383
 
1366
1384
  const qs = searchParams.toString();
1367
1385
  return this.request('GET', `/posts${qs ? `?${qs}` : ''}`);
@@ -2137,7 +2155,7 @@ export function validatePosterlyBaseUrlForKeyUse(baseUrl: string): void {
2137
2155
  !flagEnabled('POSTERLY_ALLOW_CUSTOM_BASE_URL')
2138
2156
  ) {
2139
2157
  throw new Error(
2140
- `Refusing to send an API key to custom host ${baseUrl}. Set POSTERLY_ALLOW_CUSTOM_BASE_URL=true only for a trusted Posterly deployment.`
2158
+ `Refusing to send an API key to custom host ${baseUrl}. Set POSTERLY_ALLOW_CUSTOM_BASE_URL=true only for a trusted posterly deployment.`
2141
2159
  );
2142
2160
  }
2143
2161
  }
package/src/lib/format.ts CHANGED
@@ -103,7 +103,7 @@ export function mdSuccess(title: string, rows: Array<[string, CellValue]>, nextS
103
103
 
104
104
  export function mdError(message: string): string {
105
105
  return [
106
- mdTitle('⚠️ Posterly tool error'),
106
+ mdTitle('⚠️ posterly tool error'),
107
107
  `**Message:** ${message}`,
108
108
  ].join('\n');
109
109
  }
@@ -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.43.3';
8
+ export const POSTERLY_MCP_VERSION = '0.43.5';
@@ -20,7 +20,7 @@ function formatSupportAnswer(result: AskSupportResponse): string {
20
20
  ].filter(Boolean);
21
21
 
22
22
  return [
23
- mdTitle('Posterly Support'),
23
+ mdTitle('posterly Support'),
24
24
  mdQuote(result.message?.content || 'No support answer returned.'),
25
25
  sources.length ? mdSection('Sources', mdBullets(sources)) : '',
26
26
  mdSection('Status', mdBullets(status)),
@@ -31,7 +31,7 @@ const supportInputSchema = z.object({
31
31
  question: z.string().trim().min(1).max(4000),
32
32
  workspace_id: z.string().optional().describe('Workspace to inspect. Omit to use the API-key scoped workspace or personal workspace.'),
33
33
  conversation_id: z.string().optional().describe('Continue a previous support conversation.'),
34
- page_url: z.string().max(2000).optional().describe('Optional Posterly page URL for context.'),
34
+ page_url: z.string().max(2000).optional().describe('Optional posterly page URL for context.'),
35
35
  post_id: z.number().int().positive().optional().describe('Optional post ID to inspect directly.'),
36
36
  referenced_post_ids: z.array(z.number().int().positive()).max(5).optional().describe('Optional post IDs to include in read-only diagnostics.'),
37
37
  request_human: z.boolean().default(false).optional().describe('Ask for human review. Does not create a ticket unless confirm_escalation is also true.'),
@@ -49,7 +49,7 @@ async function executeSupport(client: PosterlyClient, input: AskSupportPayload)
49
49
  export const askSupportTool = {
50
50
  name: 'ask_support',
51
51
  description:
52
- 'Ask Posterly Support AI an authenticated question using Posterly docs plus read-only account/post diagnostics for the caller workspace. Requires POSTERLY_API_KEY with accounts:read and posts:read scopes. Human ticket creation requires request_human=true and confirm_escalation=true after explicit user confirmation.',
52
+ 'Ask posterly Support AI an authenticated question using posterly docs plus read-only account/post diagnostics for the caller workspace. Requires POSTERLY_API_KEY with accounts:read and posts:read scopes. Human ticket creation requires request_human=true and confirm_escalation=true after explicit user confirmation.',
53
53
  inputSchema: supportInputSchema,
54
54
  execute: executeSupport,
55
55
  };
@@ -25,7 +25,7 @@ function formatCreatedApiKey(result: CreateApiKeyResponse): string {
25
25
  export const createApiKeyTool = {
26
26
  name: 'create_api_key',
27
27
  description:
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.',
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
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).'),
@@ -6,7 +6,7 @@ import { CONNECT_INPUTS } from '../generated/platform-manifest.js';
6
6
  export const createConnectSessionTool = {
7
7
  name: 'create_connect_session',
8
8
  description:
9
- 'Create a short-lived Posterly dashboard handoff session for connecting a social account. Send the secure connection URL to the user, then poll get_connect_session to narrate progress. Do not ask for social media passwords or OAuth codes; Posterly handles those browser steps.',
9
+ 'Create a short-lived posterly dashboard handoff session for connecting a social account. Send the secure connection URL to the user, then poll get_connect_session to narrate progress. Do not ask for social media passwords or OAuth codes; posterly handles those browser steps.',
10
10
  inputSchema: z.object({
11
11
  platform: z
12
12
  .enum(CONNECT_INPUTS)
@@ -38,7 +38,7 @@ export const createConnectSessionTool = {
38
38
  function formatConnectSession(session: ConnectSession, includeRaw: boolean): string {
39
39
  const connected = session.connected_count ?? session.connected_accounts?.length ?? 0;
40
40
  return [
41
- `Posterly connect session created: ${session.id}`,
41
+ `posterly connect session created: ${session.id}`,
42
42
  `Platform: ${session.platform}`,
43
43
  `Status: ${session.status}`,
44
44
  `Message: ${session.status_message}`,
@@ -49,7 +49,7 @@ function formatConnectSession(session: ConnectSession, includeRaw: boolean): str
49
49
  `Expires: ${formatLocalDateTime(session.expires_at)}`,
50
50
  `Dashboard connect page: ${DASHBOARD_CONNECT_URL}`,
51
51
  '',
52
- 'Next step for the AI agent: send the secure connection URL to the user, explain that Posterly will handle the browser OAuth step, then poll get_connect_session until the account is connected or the session ends.',
52
+ 'Next step for the AI agent: send the secure connection URL to the user, explain that posterly will handle the browser OAuth step, then poll get_connect_session until the account is connected or the session ends.',
53
53
  includeRaw ? `\nRaw connect session:\n${JSON.stringify(session, null, 2)}` : '',
54
54
  ].filter(Boolean).join('\n');
55
55
  }