posterly-mcp-server 0.45.3 → 0.47.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 (45) hide show
  1. package/README.md +6 -4
  2. package/dist/lib/api-client.d.ts +29 -2
  3. package/dist/lib/api-client.js +18 -4
  4. package/dist/lib/version.d.ts +1 -1
  5. package/dist/lib/version.js +1 -1
  6. package/dist/tools/create-media-drop.d.ts +4 -0
  7. package/dist/tools/create-media-drop.js +2 -0
  8. package/dist/tools/create-post.d.ts +6 -6
  9. package/dist/tools/create-posts-batch.d.ts +4 -4
  10. package/dist/tools/create-signed-upload.d.ts +4 -0
  11. package/dist/tools/create-signed-upload.js +2 -1
  12. package/dist/tools/delete-post.d.ts +4 -0
  13. package/dist/tools/delete-post.js +10 -5
  14. package/dist/tools/generate-image.d.ts +4 -0
  15. package/dist/tools/generate-image.js +1 -0
  16. package/dist/tools/generate-video.d.ts +3 -0
  17. package/dist/tools/generate-video.js +1 -0
  18. package/dist/tools/list-analytics.js +4 -1
  19. package/dist/tools/list-posts.d.ts +4 -0
  20. package/dist/tools/list-posts.js +5 -1
  21. package/dist/tools/list-workspace-members.d.ts +10 -0
  22. package/dist/tools/list-workspace-members.js +20 -2
  23. package/dist/tools/manage-workspace-member.d.ts +4 -0
  24. package/dist/tools/manage-workspace-member.js +17 -4
  25. package/dist/tools/update-post.d.ts +14 -2
  26. package/dist/tools/update-post.js +27 -1
  27. package/dist/tools/upload-media.d.ts +4 -0
  28. package/dist/tools/upload-media.js +3 -0
  29. package/dist/tools/validate-post.d.ts +4 -4
  30. package/package.json +1 -1
  31. package/server.json +2 -2
  32. package/src/lib/api-client.ts +43 -3
  33. package/src/lib/version.ts +1 -1
  34. package/src/tools/create-media-drop.ts +3 -1
  35. package/src/tools/create-signed-upload.ts +3 -2
  36. package/src/tools/delete-post.ts +16 -7
  37. package/src/tools/generate-image.ts +2 -0
  38. package/src/tools/generate-video.ts +1 -0
  39. package/src/tools/list-analytics.ts +4 -1
  40. package/src/tools/list-posts.ts +6 -1
  41. package/src/tools/list-workspace-members.ts +16 -2
  42. package/src/tools/manage-workspace-member.ts +16 -4
  43. package/src/tools/update-post.ts +46 -1
  44. package/src/tools/upload-media.ts +7 -2
  45. package/tool-registry.json +46 -0
@@ -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, workspace_id, approval_status, or brand_id. Returns posts in your workspaces, teammates\' too, per your role there (brand-locked seats only see their assigned brands). If workspace_id is omitted, this searches every workspace you belong to.',
6
+ description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, account_id, workspace_id, approval_status, approval_waiting_on, or brand_id. approval_waiting_on=client is the same queue as approval_status=pending_client. Each post includes approval.pending_client_approval. Returns posts in your workspaces, teammates\' too, per your role there (brand-locked seats only see their assigned brands). If workspace_id is omitted, this searches every workspace you belong to.',
7
7
  inputSchema: z.object({
8
8
  status: z
9
9
  .string()
@@ -31,6 +31,10 @@ export const listPostsTool = {
31
31
  .enum(['none', 'pending_client', 'approved', 'changes_requested', 'rejected'])
32
32
  .optional()
33
33
  .describe('Filter by client review approval status. pending_client means the post is waiting on the client.'),
34
+ approval_waiting_on: z
35
+ .enum(['client', 'team'])
36
+ .optional()
37
+ .describe('client: approval_status pending_client. team: changes_requested or rejected. Conflicts with a non-overlapping approval_status.'),
34
38
  brand_id: z
35
39
  .string()
36
40
  .optional()
@@ -1,6 +1,16 @@
1
1
  import { z } from 'zod';
2
2
  import type { PosterlyClient } from '../lib/api-client.js';
3
3
  export declare function formatBrandIds(value: string[] | null | undefined): string;
4
+ /** API and AI app access for one member (issue #1170). */
5
+ export declare function formatApiAccess(member: {
6
+ role?: string | null;
7
+ api_access?: boolean;
8
+ api_usage?: {
9
+ key_count: number;
10
+ last_used_at: string | null;
11
+ connected_apps: string[];
12
+ };
13
+ }): string;
4
14
  export declare const listWorkspaceMembersTool: {
5
15
  name: string;
6
16
  description: string;
@@ -5,19 +5,37 @@ export function formatBrandIds(value) {
5
5
  return 'hidden';
6
6
  return value.length === 0 ? 'every brand' : value.map((id) => code(id)).join(', ');
7
7
  }
8
+ /** API and AI app access for one member (issue #1170). */
9
+ export function formatApiAccess(member) {
10
+ if (member.api_access === undefined)
11
+ return '';
12
+ const base = member.role === 'owner' ? 'owner' : member.api_access ? 'on' : 'off';
13
+ const usage = member.api_usage;
14
+ if (!usage)
15
+ return base;
16
+ const parts = [];
17
+ if (usage.key_count > 0)
18
+ parts.push(`${usage.key_count} key${usage.key_count === 1 ? '' : 's'}`);
19
+ if (usage.connected_apps.length > 0)
20
+ parts.push(usage.connected_apps.join(', '));
21
+ if (usage.last_used_at)
22
+ parts.push(`last used ${usage.last_used_at}`);
23
+ return parts.length > 0 ? `${base} (${parts.join('; ')})` : base;
24
+ }
8
25
  function formatWorkspaceMembers(result) {
9
26
  const members = result.members || [];
10
27
  const invites = result.invites || [];
11
28
  return [
12
29
  mdTitle(`Team (${members.length} members, ${invites.length} pending invites)`, `Workspace ${result.workspace_id}`),
13
30
  mdSection('Members', members.length > 0
14
- ? mdTable(['Name', 'Member ID', 'Email', 'Role', 'Status', 'Brands'], members.map((member) => [
31
+ ? mdTable(['Name', 'Member ID', 'Email', 'Role', 'Status', 'Brands', 'API access'], members.map((member) => [
15
32
  member.name || member.email || member.id,
16
33
  code(member.id),
17
34
  member.email,
18
35
  member.role,
19
36
  member.status,
20
37
  formatBrandIds(member.brand_ids),
38
+ formatApiAccess(member),
21
39
  ]))
22
40
  : 'No members found.'),
23
41
  mdSection('Pending invites', invites.length > 0
@@ -38,7 +56,7 @@ function formatWorkspaceMembers(result) {
38
56
  }
39
57
  export const listWorkspaceMembersTool = {
40
58
  name: 'list_workspace_members',
41
- description: 'List the people in a workspace: members (with role, status and brand access) and pending invites, plus seats in use. Needs an API key with the Manage team permission. Any workspace member can list; brand-limited teammates only see brand ids they can access.',
59
+ description: 'List the people in a workspace: members (with role, status, brand access and API access) and pending invites, plus seats in use. API access says whether a teammate can use this workspace\'s API plan from their own keys and AI apps; owners and admins also see each member\'s key count, connected AI apps and last use. Needs an API key with the Manage team permission. Any workspace member can list; brand-limited teammates only see brand ids they can access.',
42
60
  inputSchema: z.object({
43
61
  workspace_id: z.string().describe('Workspace ID (from whoami).'),
44
62
  }),
@@ -10,6 +10,7 @@ export type ManageWorkspaceMemberInput = {
10
10
  resend?: boolean;
11
11
  member_id?: string;
12
12
  invite_id?: string;
13
+ api_access?: boolean;
13
14
  confirm?: boolean;
14
15
  preview_id?: string;
15
16
  };
@@ -27,6 +28,7 @@ export declare const manageWorkspaceMemberTool: {
27
28
  resend: z.ZodOptional<z.ZodBoolean>;
28
29
  member_id: z.ZodOptional<z.ZodString>;
29
30
  invite_id: z.ZodOptional<z.ZodString>;
31
+ api_access: z.ZodOptional<z.ZodBoolean>;
30
32
  confirm: z.ZodOptional<z.ZodBoolean>;
31
33
  preview_id: z.ZodOptional<z.ZodString>;
32
34
  }, "strip", z.ZodTypeAny, {
@@ -40,6 +42,7 @@ export declare const manageWorkspaceMemberTool: {
40
42
  member_id?: string | undefined;
41
43
  invite_id?: string | undefined;
42
44
  resend?: boolean | undefined;
45
+ api_access?: boolean | undefined;
43
46
  }, {
44
47
  workspace_id: string;
45
48
  action: "invite" | "update" | "remove";
@@ -51,6 +54,7 @@ export declare const manageWorkspaceMemberTool: {
51
54
  member_id?: string | undefined;
52
55
  invite_id?: string | undefined;
53
56
  resend?: boolean | undefined;
57
+ api_access?: boolean | undefined;
54
58
  }>;
55
59
  execute(client: PosterlyClient, input: ManageWorkspaceMemberInput): Promise<string>;
56
60
  };
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { code, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
3
- import { formatBrandIds } from './list-workspace-members.js';
3
+ import { formatApiAccess, formatBrandIds } from './list-workspace-members.js';
4
4
  const ACTIONS = ['invite', 'update', 'remove'];
5
5
  const ROLES = ['publisher', 'editor', 'viewer'];
6
6
  const TITLES = {
@@ -28,6 +28,9 @@ function formatChange(result) {
28
28
  ['Seats', preview.counts_toward_seats && preview.seat_limit != null
29
29
  ? `${preview.seats_used} in use of ${preview.seat_limit === -1 ? 'unlimited' : preview.seat_limit}`
30
30
  : ''],
31
+ ['API access', typeof preview.api_access === 'boolean'
32
+ ? `${preview.previous_api_access ? 'on' : 'off'} -> ${preview.api_access ? 'on' : 'off'}`
33
+ : ''],
31
34
  ])),
32
35
  mdSection('Preview ID', `${code(result.preview_id || '')} (expires ${result.preview_expires_at || 'soon'})`),
33
36
  mdSection('Next step', 'Show this preview to the user. Only after they explicitly approve, call manage_workspace_member again with the same fields plus confirm: true and this preview_id.'),
@@ -43,6 +46,7 @@ function formatChange(result) {
43
46
  ['Role', subject?.role],
44
47
  ['Status', subject?.status],
45
48
  ['Brands', subject?.brand_ids ? formatBrandIds(subject.brand_ids) : ''],
49
+ ['API access', result.member ? formatApiAccess(result.member) : ''],
46
50
  ['Invite email sent', typeof result.email_sent === 'boolean' ? (result.email_sent ? 'yes' : 'no, ask them to check spam or resend later') : ''],
47
51
  ['Workspace owner notified', result.owner_notified ? 'yes' : ''],
48
52
  ])),
@@ -69,21 +73,28 @@ export function validateManageWorkspaceMemberInput(input) {
69
73
  return 'action=invite needs email and role (publisher, editor or viewer).';
70
74
  return null;
71
75
  }
72
- const notAllowed = input.action === 'update' ? ['email', 'resend'] : ['email', 'resend', 'role', 'brand_ids'];
76
+ const notAllowed = input.action === 'update' ? ['email', 'resend'] : ['email', 'resend', 'role', 'brand_ids', 'api_access'];
73
77
  const extra = notAllowed.filter((key) => has(raw, key));
74
78
  if (extra.length)
75
79
  return `action=${input.action} does not take ${extra.join(', ')}.`;
76
80
  if (has(raw, 'member_id') === has(raw, 'invite_id')) {
77
81
  return `action=${input.action} needs exactly one of member_id or invite_id (from list_workspace_members).`;
78
82
  }
83
+ if (input.action === 'update' && has(raw, 'api_access')) {
84
+ if (has(raw, 'invite_id'))
85
+ return 'api_access can only be set on a member (member_id), not a pending invite.';
86
+ if (has(raw, 'role') || has(raw, 'brand_ids'))
87
+ return 'Change api_access in its own call, without role or brand_ids.';
88
+ return null;
89
+ }
79
90
  if (input.action === 'update' && !has(raw, 'role') && !has(raw, 'brand_ids')) {
80
- return 'action=update needs role, brand_ids, or both.';
91
+ return 'action=update needs role, brand_ids, or both; or api_access on its own.';
81
92
  }
82
93
  return null;
83
94
  }
84
95
  export const manageWorkspaceMemberTool = {
85
96
  name: 'manage_workspace_member',
86
- description: 'Invite, update, or remove a workspace colleague. This SENDS EMAILS and CHANGES WHO CAN ACCESS the workspace. Needs an API key with the Manage team permission, and the caller must be a workspace owner or admin. ALWAYS preview first: call without confirm, show the returned preview summary to the user, and only call again with confirm: true plus the returned preview_id after the user explicitly approves. action=invite: email and role required (publisher, editor or viewer), optional brand_ids and resend; always sends a pending invite email, never adds anyone instantly. action=update: member_id or invite_id (from list_workspace_members) plus role and/or brand_ids. action=remove: member_id (removes the teammate and revokes their API keys for this workspace) or invite_id (cancels a pending invite). Admin seats can only be granted in the posterly dashboard. The workspace owner is emailed about every change.',
97
+ description: 'Invite, update, or remove a workspace colleague. This SENDS EMAILS and CHANGES WHO CAN ACCESS the workspace. Needs an API key with the Manage team permission, and the caller must be a workspace owner or admin. ALWAYS preview first: call without confirm, show the returned preview summary to the user, and only call again with confirm: true plus the returned preview_id after the user explicitly approves. action=invite: email and role required (publisher, editor or viewer), optional brand_ids and resend; always sends a pending invite email, never adds anyone instantly. action=update: member_id or invite_id (from list_workspace_members) plus role and/or brand_ids; or member_id plus api_access (true or false, on its own) to turn API and AI app access on or off for that teammate, which lets them use the workspace API plan from their own keys and AI apps (off by default; you cannot change your own; only the owner can change it for an admin; turning it off stops their keys, AI app connections and webhooks for this workspace). action=remove: member_id (removes the teammate and revokes their API keys for this workspace) or invite_id (cancels a pending invite). Admin seats can only be granted in the posterly dashboard. The workspace owner is emailed about every change.',
87
98
  inputSchema: z.object({
88
99
  action: z.enum(ACTIONS).describe('invite, update, or remove.'),
89
100
  workspace_id: z.string().describe('Workspace ID (from whoami).'),
@@ -93,6 +104,7 @@ export const manageWorkspaceMemberTool = {
93
104
  resend: z.boolean().optional().describe('action=invite only: email an unchanged pending invite again.'),
94
105
  member_id: z.string().optional().describe('action=update or action=remove: a member ID from list_workspace_members.'),
95
106
  invite_id: z.string().optional().describe('action=update or action=remove: a pending invite ID from list_workspace_members.'),
107
+ api_access: z.boolean().optional().describe('action=update with member_id only, on its own: true lets the teammate use this workspace\'s API plan from their own keys and AI apps; false turns it off.'),
96
108
  confirm: z.boolean().optional().describe('Leave out to get a preview. Set true only after the user explicitly approved the preview, together with preview_id.'),
97
109
  preview_id: z.string().optional().describe('The preview_id returned by the preview call. Required with confirm: true.'),
98
110
  }),
@@ -120,6 +132,7 @@ export const manageWorkspaceMemberTool = {
120
132
  result = await client.updateWorkspaceMember(workspaceId, target, {
121
133
  ...(input.role !== undefined ? { role: input.role } : {}),
122
134
  ...(input.brand_ids !== undefined ? { brand_ids: input.brand_ids } : {}),
135
+ ...(input.api_access !== undefined ? { api_access: input.api_access } : {}),
123
136
  ...confirmFields,
124
137
  });
125
138
  }
@@ -497,6 +497,9 @@ type UpdatePostInput = EditInput & {
497
497
  status?: 'scheduled' | 'paused' | 'draft';
498
498
  release_id?: string;
499
499
  group_id?: string;
500
+ approval_action?: 'request' | 'approve' | 'request_changes' | 'reject';
501
+ approval_axis?: 'asset' | 'caption';
502
+ approval_note?: string;
500
503
  };
501
504
  export declare const updatePostTool: {
502
505
  name: string;
@@ -995,11 +998,15 @@ export declare const updatePostTool: {
995
998
  status: z.ZodOptional<z.ZodEnum<["scheduled", "paused", "draft"]>>;
996
999
  release_id: z.ZodOptional<z.ZodString>;
997
1000
  group_id: z.ZodOptional<z.ZodString>;
1001
+ approval_action: z.ZodOptional<z.ZodEnum<["request", "approve", "request_changes", "reject"]>>;
1002
+ approval_axis: z.ZodOptional<z.ZodEnum<["asset", "caption"]>>;
1003
+ approval_note: z.ZodOptional<z.ZodString>;
998
1004
  confirm: z.ZodLiteral<true>;
999
1005
  }, "strip", z.ZodTypeAny, {
1000
1006
  confirm: true;
1001
1007
  post_id: number;
1002
1008
  status?: "scheduled" | "paused" | "draft" | undefined;
1009
+ caption?: string | undefined;
1003
1010
  scheduled_at?: string | undefined;
1004
1011
  media_url?: string | undefined;
1005
1012
  media_urls?: string[] | undefined;
@@ -1007,7 +1014,6 @@ export declare const updatePostTool: {
1007
1014
  reel_cover_url?: string | undefined;
1008
1015
  reel_cover_method?: string | undefined;
1009
1016
  reel_thumb_offset?: number | undefined;
1010
- caption?: string | undefined;
1011
1017
  instagram_settings?: {
1012
1018
  post_type?: "post" | "feed" | "story" | "reel" | "carousel" | undefined;
1013
1019
  reel_cover_url?: string | undefined;
@@ -1163,10 +1169,14 @@ export declare const updatePostTool: {
1163
1169
  }, z.ZodTypeAny, "passthrough"> | undefined;
1164
1170
  release_id?: string | undefined;
1165
1171
  group_id?: string | undefined;
1172
+ approval_action?: "request" | "approve" | "request_changes" | "reject" | undefined;
1173
+ approval_axis?: "asset" | "caption" | undefined;
1174
+ approval_note?: string | undefined;
1166
1175
  }, {
1167
1176
  confirm: true;
1168
1177
  post_id: number;
1169
1178
  status?: "scheduled" | "paused" | "draft" | undefined;
1179
+ caption?: string | undefined;
1170
1180
  scheduled_at?: string | undefined;
1171
1181
  media_url?: string | undefined;
1172
1182
  media_urls?: string[] | undefined;
@@ -1174,7 +1184,6 @@ export declare const updatePostTool: {
1174
1184
  reel_cover_url?: string | undefined;
1175
1185
  reel_cover_method?: string | undefined;
1176
1186
  reel_thumb_offset?: number | undefined;
1177
- caption?: string | undefined;
1178
1187
  instagram_settings?: {
1179
1188
  post_type?: "post" | "feed" | "story" | "reel" | "carousel" | undefined;
1180
1189
  reel_cover_url?: string | undefined;
@@ -1330,6 +1339,9 @@ export declare const updatePostTool: {
1330
1339
  }, z.ZodTypeAny, "passthrough"> | undefined;
1331
1340
  release_id?: string | undefined;
1332
1341
  group_id?: string | undefined;
1342
+ approval_action?: "request" | "approve" | "request_changes" | "reject" | undefined;
1343
+ approval_axis?: "asset" | "caption" | undefined;
1344
+ approval_note?: string | undefined;
1333
1345
  }>;
1334
1346
  /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
1335
1347
  legacySchemas: {
@@ -108,7 +108,7 @@ const platformSettingsSchema = z.object({
108
108
  }).passthrough();
109
109
  export const updatePostTool = {
110
110
  name: 'update_post',
111
- description: 'Change a scheduled or draft post. DESTRUCTIVE WRITE. Edit (default): caption, media, schedule, post_type, or settings; overwrites the existing post; cannot edit published or currently publishing posts; if a client has already approved the post, a caption edit reopens caption approval, a media or thumbnail edit reopens asset approval, and a reschedule, account change or settings change reopens both. status (scheduled, paused, or draft, with scheduled_at when scheduling a draft): pause, resume, or move back to draft; send it on its own. release_id (with optional group_id): set or repair external release/group IDs; send it on its own.\n\n' +
111
+ description: 'Change a scheduled or draft post. DESTRUCTIVE WRITE. Edit (default): caption, media, schedule, post_type, or settings; overwrites the existing post; cannot edit published or currently publishing posts; if a client has already approved the post, a caption edit reopens caption approval, a media or thumbnail edit reopens asset approval, and a reschedule, account change or settings change reopens both. status (scheduled, paused, or draft, with scheduled_at when scheduling a draft): pause, resume, or move back to draft; send it on its own. release_id (with optional group_id): set or repair external release/group IDs; send it on its own. approval_action (request, approve, request_changes, or reject): change client approval. Only a workspace owner or admin may do this. The actor is the API key user, never a brand guest. request emails the brand reviewers. approve, request_changes, and reject are team-side decisions. request_changes needs approval_note. Optional approval_axis is asset or caption; omit it for both. Send approval_action on its own.\n\n' +
112
112
  'REQUIRED BEFORE CALLING: Show the user a side-by-side of the CURRENT post (caption, scheduled time, media, status) and the PROPOSED change, and get explicit confirmation ("yes update it", "apply those changes") before calling. Do not auto-edit posts based on a general instruction - each change needs its own confirmation.',
113
113
  inputSchema: z.object({
114
114
  post_id: z.number().describe('The post ID to update'),
@@ -146,6 +146,18 @@ export const updatePostTool = {
146
146
  .optional()
147
147
  .describe('Set an external release ID instead of editing the post. Send with post_id and optional group_id only.'),
148
148
  group_id: z.string().min(1).max(160).optional().describe('With release_id only: an external group/campaign ID.'),
149
+ approval_action: z
150
+ .enum(['request', 'approve', 'request_changes', 'reject'])
151
+ .optional()
152
+ .describe('Change client approval instead of editing the post. Owner or admin only. The actor is the API key user, never a brand guest. Send on its own.'),
153
+ approval_axis: z
154
+ .enum(['asset', 'caption'])
155
+ .optional()
156
+ .describe('With approval_action approve, request_changes, or reject: which axis to decide. Omit for both. Not allowed with request.'),
157
+ approval_note: z
158
+ .string()
159
+ .optional()
160
+ .describe('Note stored with the approval change. Required for request_changes.'),
149
161
  confirm: z.literal(true).describe('Must be true after explicit user confirmation of the proposed change.'),
150
162
  }),
151
163
  /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
@@ -169,6 +181,9 @@ export const updatePostTool = {
169
181
  return updateStatus(client, args);
170
182
  if (mode === 'release_id')
171
183
  return updateReleaseId(client, args);
184
+ if (mode === 'approval') {
185
+ return updateApproval(client, args);
186
+ }
172
187
  return editPost(client, args);
173
188
  },
174
189
  };
@@ -209,6 +224,17 @@ async function updateStatus(client, input) {
209
224
  const transition = result.transition || {};
210
225
  return `Post #${input.post_id} status changed from ${transition.old_status || 'unknown'} to ${transition.new_status || input.status}.`;
211
226
  }
227
+ async function updateApproval(client, input) {
228
+ if (input.confirm !== true) {
229
+ throw new Error('Refusing to change client approval without confirm=true after explicit user confirmation. Only a workspace owner or admin can do this, and the actor is the API key user, never a brand guest.');
230
+ }
231
+ const result = await client.setPostApproval(input.post_id, {
232
+ action: input.approval_action,
233
+ axis: input.approval_axis,
234
+ note: input.approval_note,
235
+ });
236
+ return JSON.stringify(result, null, 2);
237
+ }
212
238
  async function updateReleaseId(client, input) {
213
239
  const result = await client.updatePostReleaseId(input.post_id, {
214
240
  release_id: input.release_id,
@@ -7,6 +7,7 @@ type UploadMediaInput = {
7
7
  url?: string;
8
8
  filename?: string;
9
9
  content_type?: string;
10
+ workspace_id?: string;
10
11
  };
11
12
  export declare const uploadMediaTool: {
12
13
  name: string;
@@ -17,13 +18,16 @@ export declare const uploadMediaTool: {
17
18
  url: z.ZodOptional<z.ZodString>;
18
19
  filename: z.ZodOptional<z.ZodString>;
19
20
  content_type: z.ZodOptional<z.ZodString>;
21
+ workspace_id: z.ZodOptional<z.ZodString>;
20
22
  }, "strip", z.ZodTypeAny, {
23
+ workspace_id?: string | undefined;
21
24
  filename?: string | undefined;
22
25
  url?: string | undefined;
23
26
  content_type?: string | undefined;
24
27
  file_path?: string | undefined;
25
28
  base64_data?: string | undefined;
26
29
  }, {
30
+ workspace_id?: string | undefined;
27
31
  filename?: string | undefined;
28
32
  url?: string | undefined;
29
33
  content_type?: string | undefined;
@@ -27,6 +27,7 @@ export const uploadMediaTool = {
27
27
  .string()
28
28
  .optional()
29
29
  .describe('MIME type (auto-detected from filename if omitted)'),
30
+ workspace_id: z.string().optional().describe('Optional. The workspace this acts in and whose plan pays (from whoami). Needed only when your connection can act in several workspaces.'),
30
31
  }),
31
32
  /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
32
33
  legacySchemas: {
@@ -52,6 +53,7 @@ async function uploadFile(client, input) {
52
53
  base64Data: input.base64_data,
53
54
  filename: input.filename,
54
55
  contentType: input.content_type,
56
+ workspaceId: input.workspace_id,
55
57
  });
56
58
  return mdSuccess('Media uploaded', [
57
59
  ['URL', result.url],
@@ -63,6 +65,7 @@ async function uploadFromUrl(client, input) {
63
65
  url: input.url,
64
66
  filename: input.filename,
65
67
  contentType: input.content_type,
68
+ workspaceId: input.workspace_id,
66
69
  });
67
70
  return [
68
71
  'Remote media uploaded successfully.',
@@ -494,11 +494,11 @@ export declare const validatePostInputSchema: import("zod").ZodObject<{
494
494
  username?: string | undefined;
495
495
  workspace_id?: string | undefined;
496
496
  account_id?: string | undefined;
497
+ caption?: string | undefined;
497
498
  scheduled_at?: string | undefined;
498
499
  media_url?: string | undefined;
499
500
  media_urls?: string[] | undefined;
500
501
  post_type?: string | undefined;
501
- caption?: string | undefined;
502
502
  thread_posts?: string[] | undefined;
503
503
  instagram_settings?: {
504
504
  post_type?: "post" | "feed" | "story" | "reel" | "carousel" | undefined;
@@ -657,11 +657,11 @@ export declare const validatePostInputSchema: import("zod").ZodObject<{
657
657
  username?: string | undefined;
658
658
  workspace_id?: string | undefined;
659
659
  account_id?: string | undefined;
660
+ caption?: string | undefined;
660
661
  scheduled_at?: string | undefined;
661
662
  media_url?: string | undefined;
662
663
  media_urls?: string[] | undefined;
663
664
  post_type?: string | undefined;
664
- caption?: string | undefined;
665
665
  thread_posts?: string[] | undefined;
666
666
  instagram_settings?: {
667
667
  post_type?: "post" | "feed" | "story" | "reel" | "carousel" | undefined;
@@ -1314,11 +1314,11 @@ export declare const validatePostTool: {
1314
1314
  username?: string | undefined;
1315
1315
  workspace_id?: string | undefined;
1316
1316
  account_id?: string | undefined;
1317
+ caption?: string | undefined;
1317
1318
  scheduled_at?: string | undefined;
1318
1319
  media_url?: string | undefined;
1319
1320
  media_urls?: string[] | undefined;
1320
1321
  post_type?: string | undefined;
1321
- caption?: string | undefined;
1322
1322
  thread_posts?: string[] | undefined;
1323
1323
  instagram_settings?: {
1324
1324
  post_type?: "post" | "feed" | "story" | "reel" | "carousel" | undefined;
@@ -1477,11 +1477,11 @@ export declare const validatePostTool: {
1477
1477
  username?: string | undefined;
1478
1478
  workspace_id?: string | undefined;
1479
1479
  account_id?: string | undefined;
1480
+ caption?: string | undefined;
1480
1481
  scheduled_at?: string | undefined;
1481
1482
  media_url?: string | undefined;
1482
1483
  media_urls?: string[] | undefined;
1483
1484
  post_type?: string | undefined;
1484
- caption?: string | undefined;
1485
1485
  thread_posts?: string[] | undefined;
1486
1486
  instagram_settings?: {
1487
1487
  post_type?: "post" | "feed" | "story" | "reel" | "carousel" | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posterly-mcp-server",
3
- "version": "0.45.3",
3
+ "version": "0.47.0",
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.45.3",
6
+ "version": "0.47.0",
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.45.3",
17
+ "version": "0.47.0",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },
@@ -190,6 +190,7 @@ export interface ApiPostApproval {
190
190
  asset_status: ApiPostApprovalStatus;
191
191
  caption_status: ApiPostApprovalStatus;
192
192
  waiting_on: 'client' | 'team' | null;
193
+ pending_client_approval: boolean;
193
194
  requested_at: string | null;
194
195
  completed_at: string | null;
195
196
  }
@@ -390,6 +391,10 @@ export type WorkspaceMemberSummary = {
390
391
  status: string | null;
391
392
  /** [] = every brand; missing = not visible to a brand-limited caller. */
392
393
  brand_ids?: string[];
394
+ /** Can this teammate use the workspace's API and AI app access. Always true for the owner. */
395
+ api_access?: boolean;
396
+ /** Owners and admins only: live keys and AI app connections for this workspace. */
397
+ api_usage?: { key_count: number; last_used_at: string | null; connected_apps: string[] };
393
398
  joined_at: string | null;
394
399
  created_at: string | null;
395
400
  };
@@ -423,6 +428,9 @@ export type WorkspaceMemberPreview = {
423
428
  counts_toward_seats: boolean;
424
429
  seats_used: number | null;
425
430
  seat_limit: number | null;
431
+ /** action=set_api_access only. */
432
+ api_access?: boolean;
433
+ previous_api_access?: boolean | null;
426
434
  };
427
435
 
428
436
  export type WorkspaceMemberChangeResponse = {
@@ -1333,7 +1341,7 @@ export class PosterlyClient {
1333
1341
  async updateWorkspaceMember(
1334
1342
  workspaceId: string,
1335
1343
  target: { member_id?: string; invite_id?: string },
1336
- data: { role?: WorkspaceMemberRole; brand_ids?: string[] } & WorkspaceMemberConfirmFields,
1344
+ data: { role?: WorkspaceMemberRole; brand_ids?: string[]; api_access?: boolean } & WorkspaceMemberConfirmFields,
1337
1345
  ): Promise<WorkspaceMemberChangeResponse> {
1338
1346
  const base = `/workspaces/${encodeURIComponent(workspaceId)}`;
1339
1347
  const path = target.member_id
@@ -1537,6 +1545,7 @@ export class PosterlyClient {
1537
1545
  offset?: number;
1538
1546
  workspace_id?: string;
1539
1547
  approval_status?: string;
1548
+ approval_waiting_on?: string;
1540
1549
  brand_id?: string;
1541
1550
  }): Promise<{ posts: Post[]; total: number }> {
1542
1551
  const searchParams = new URLSearchParams();
@@ -1547,6 +1556,7 @@ export class PosterlyClient {
1547
1556
  if (params?.offset) searchParams.set('offset', String(params.offset));
1548
1557
  if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
1549
1558
  if (params?.approval_status) searchParams.set('approval_status', params.approval_status);
1559
+ if (params?.approval_waiting_on) searchParams.set('approval_waiting_on', params.approval_waiting_on);
1550
1560
  if (params?.brand_id) searchParams.set('brand_id', params.brand_id);
1551
1561
 
1552
1562
  const qs = searchParams.toString();
@@ -1607,8 +1617,27 @@ export class PosterlyClient {
1607
1617
  return this.request('PATCH', `/posts/${id}`, data);
1608
1618
  }
1609
1619
 
1610
- async deletePost(id: number): Promise<{ deleted: boolean; id: number }> {
1611
- return this.request('DELETE', `/posts/${id}`);
1620
+ async deletePost(
1621
+ id: number,
1622
+ options?: { confirmPublished?: boolean },
1623
+ ): Promise<{ deleted: boolean; id: number; platform_delete?: string }> {
1624
+ const qs = options?.confirmPublished ? '?confirm_published=true' : '';
1625
+ return this.request('DELETE', `/posts/${id}${qs}`);
1626
+ }
1627
+
1628
+ async setPostApproval(
1629
+ id: number,
1630
+ data: {
1631
+ action: 'request' | 'approve' | 'request_changes' | 'reject';
1632
+ axis?: 'asset' | 'caption';
1633
+ note?: string;
1634
+ },
1635
+ ): Promise<Record<string, unknown>> {
1636
+ return this.request('POST', `/posts/${id}/approval`, {
1637
+ action: data.action,
1638
+ ...(data.axis ? { axis: data.axis } : {}),
1639
+ ...(data.note ? { note: data.note } : {}),
1640
+ });
1612
1641
  }
1613
1642
 
1614
1643
  async updatePostStatus(
@@ -1640,6 +1669,7 @@ export class PosterlyClient {
1640
1669
  quality?: 'low' | 'medium' | 'high';
1641
1670
  thinking_level?: 'minimal' | 'high';
1642
1671
  reference_image_urls?: string[];
1672
+ workspace_id?: string;
1643
1673
  }): Promise<{
1644
1674
  job_id: string;
1645
1675
  status: string;
@@ -1685,6 +1715,7 @@ export class PosterlyClient {
1685
1715
  reference_images?: Array<{ url: string; type: 'character' | 'object' | 'scene' }>;
1686
1716
  generate_audio?: boolean;
1687
1717
  source_video_url?: string;
1718
+ workspace_id?: string;
1688
1719
  }): Promise<Record<string, any>> {
1689
1720
  return this.request('POST', '/ai/generate-video', data);
1690
1721
  }
@@ -1984,6 +2015,7 @@ export class PosterlyClient {
1984
2015
  filename: string,
1985
2016
  contentType: string,
1986
2017
  size: number,
2018
+ workspaceId?: string,
1987
2019
  ): Promise<{
1988
2020
  upload_url: string;
1989
2021
  token: string;
@@ -1995,12 +2027,14 @@ export class PosterlyClient {
1995
2027
  filename,
1996
2028
  content_type: contentType,
1997
2029
  size,
2030
+ ...(workspaceId ? { workspace_id: workspaceId } : {}),
1998
2031
  });
1999
2032
  }
2000
2033
 
2001
2034
  async createMediaDrop(input?: {
2002
2035
  filename?: string;
2003
2036
  maxFiles?: number;
2037
+ workspaceId?: string;
2004
2038
  }): Promise<{
2005
2039
  session_id: string;
2006
2040
  drop_url: string;
@@ -2012,6 +2046,7 @@ export class PosterlyClient {
2012
2046
  return this.request('POST', '/media/drop-sessions', {
2013
2047
  filename: input?.filename,
2014
2048
  max_files: input?.maxFiles,
2049
+ ...(input?.workspaceId ? { workspace_id: input.workspaceId } : {}),
2015
2050
  });
2016
2051
  }
2017
2052
 
@@ -2043,6 +2078,7 @@ export class PosterlyClient {
2043
2078
  base64Data?: string;
2044
2079
  filename: string;
2045
2080
  contentType?: string;
2081
+ workspaceId?: string;
2046
2082
  }): Promise<{ url: string; path: string }> {
2047
2083
  this.requireApiKey();
2048
2084
 
@@ -2068,6 +2104,7 @@ export class PosterlyClient {
2068
2104
  input.filename,
2069
2105
  contentType,
2070
2106
  fileBuffer.length,
2107
+ input.workspaceId,
2071
2108
  );
2072
2109
 
2073
2110
  const uploadRes = await fetch(signed.upload_url, {
@@ -2094,6 +2131,7 @@ export class PosterlyClient {
2094
2131
  filename: input.filename,
2095
2132
  data,
2096
2133
  content_type: contentType,
2134
+ ...(input.workspaceId ? { workspace_id: input.workspaceId } : {}),
2097
2135
  });
2098
2136
  }
2099
2137
 
@@ -2101,11 +2139,13 @@ export class PosterlyClient {
2101
2139
  url: string;
2102
2140
  filename?: string;
2103
2141
  contentType?: string;
2142
+ workspaceId?: string;
2104
2143
  }): Promise<{ url: string; path: string; source_url: string; content_type: string; size: number }> {
2105
2144
  return this.request('POST', '/media/upload-from-url', {
2106
2145
  url: input.url,
2107
2146
  filename: input.filename,
2108
2147
  content_type: input.contentType,
2148
+ ...(input.workspaceId ? { workspace_id: input.workspaceId } : {}),
2109
2149
  });
2110
2150
  }
2111
2151
 
@@ -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.45.3';
8
+ export const POSTERLY_MCP_VERSION = '0.47.0';
@@ -14,15 +14,17 @@ export const createMediaDropTool = {
14
14
  .max(10)
15
15
  .optional()
16
16
  .describe('How many files the drop can accept. Default 10, max 10.'),
17
+ workspace_id: z.string().optional().describe('Optional. The workspace this acts in and whose plan pays (from whoami). Needed only when your connection can act in several workspaces.'),
17
18
  }),
18
19
 
19
20
  async execute(
20
21
  client: PosterlyClient,
21
- input: { filename?: string; max_files?: number },
22
+ input: { filename?: string; max_files?: number; workspace_id?: string },
22
23
  ) {
23
24
  const drop = await client.createMediaDrop({
24
25
  filename: input.filename,
25
26
  maxFiles: input.max_files,
27
+ workspaceId: input.workspace_id,
26
28
  });
27
29
  return [
28
30
  'Media drop created. Send this URL to the user. They open it with no posterly login and drop the file.',