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.
- package/README.md +6 -4
- package/dist/lib/api-client.d.ts +29 -2
- package/dist/lib/api-client.js +18 -4
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/create-media-drop.d.ts +4 -0
- package/dist/tools/create-media-drop.js +2 -0
- package/dist/tools/create-post.d.ts +6 -6
- package/dist/tools/create-posts-batch.d.ts +4 -4
- package/dist/tools/create-signed-upload.d.ts +4 -0
- package/dist/tools/create-signed-upload.js +2 -1
- package/dist/tools/delete-post.d.ts +4 -0
- package/dist/tools/delete-post.js +10 -5
- package/dist/tools/generate-image.d.ts +4 -0
- package/dist/tools/generate-image.js +1 -0
- package/dist/tools/generate-video.d.ts +3 -0
- package/dist/tools/generate-video.js +1 -0
- package/dist/tools/list-analytics.js +4 -1
- package/dist/tools/list-posts.d.ts +4 -0
- package/dist/tools/list-posts.js +5 -1
- package/dist/tools/list-workspace-members.d.ts +10 -0
- package/dist/tools/list-workspace-members.js +20 -2
- package/dist/tools/manage-workspace-member.d.ts +4 -0
- package/dist/tools/manage-workspace-member.js +17 -4
- package/dist/tools/update-post.d.ts +14 -2
- package/dist/tools/update-post.js +27 -1
- package/dist/tools/upload-media.d.ts +4 -0
- package/dist/tools/upload-media.js +3 -0
- package/dist/tools/validate-post.d.ts +4 -4
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/lib/api-client.ts +43 -3
- package/src/lib/version.ts +1 -1
- package/src/tools/create-media-drop.ts +3 -1
- package/src/tools/create-signed-upload.ts +3 -2
- package/src/tools/delete-post.ts +16 -7
- package/src/tools/generate-image.ts +2 -0
- package/src/tools/generate-video.ts +1 -0
- package/src/tools/list-analytics.ts +4 -1
- package/src/tools/list-posts.ts +6 -1
- package/src/tools/list-workspace-members.ts +16 -2
- package/src/tools/manage-workspace-member.ts +16 -4
- package/src/tools/update-post.ts +46 -1
- package/src/tools/upload-media.ts +7 -2
- package/tool-registry.json +46 -0
package/dist/tools/list-posts.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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.
|
|
17
|
+
"version": "0.47.0",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
},
|
package/src/lib/api-client.ts
CHANGED
|
@@ -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(
|
|
1611
|
-
|
|
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
|
|
package/src/lib/version.ts
CHANGED
|
@@ -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.
|
|
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.',
|