posterly-mcp-server 0.45.3 → 0.46.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 +3 -2
- package/dist/lib/api-client.d.ts +18 -1
- package/dist/lib/api-client.js +6 -2
- 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-signed-upload.d.ts +4 -0
- package/dist/tools/create-signed-upload.js +2 -1
- 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-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/upload-media.d.ts +4 -0
- package/dist/tools/upload-media.js +3 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/lib/api-client.ts +19 -1
- 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/generate-image.ts +2 -0
- package/src/tools/generate-video.ts +1 -0
- package/src/tools/list-workspace-members.ts +16 -2
- package/src/tools/manage-workspace-member.ts +16 -4
- package/src/tools/upload-media.ts +7 -2
- package/tool-registry.json +6 -0
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ name is `io.github.awpthorp/posterly`, matching this package's `mcpName`.
|
|
|
35
35
|
- Node.js `20+`
|
|
36
36
|
- No API key is required for the public setup tools: `whoami` with `view: "status"`, `get_agent_signup_info`, `start_signup`, and `get_signup_session`
|
|
37
37
|
- A posterly account, API add-on, and API key are required for authenticated tools like `whoami`, `list_accounts`, `create_connect_session`, and `create_post`
|
|
38
|
+
- A teammate does not need their own add-on when the workspace owner turned on **API and AI access** for them in Team settings: their key then works in that workspace, billed to the owner's plan ([Teammates and API access](https://www.poster.ly/docs/integrations/api#teammates-and-api-access))
|
|
38
39
|
|
|
39
40
|
## Install
|
|
40
41
|
|
|
@@ -122,7 +123,7 @@ Add the same server definition to your Cursor MCP settings:
|
|
|
122
123
|
|
|
123
124
|
## Available tools
|
|
124
125
|
|
|
125
|
-
`posterly-mcp-server@0.
|
|
126
|
+
`posterly-mcp-server@0.46.0` exposes 62 tools covering 94 actions. The hosted HTTP endpoint exposes the same catalog minus the three signup tools: 59 tools covering 91 actions.
|
|
126
127
|
|
|
127
128
|
Many tools pick what to do with one parameter: `view` (reads), `action` (writes), or `type`. A few pick their mode from which parameters you pass (for example `update_post` with `status` changes the status instead of editing). Every tool description names all of its views and actions.
|
|
128
129
|
|
|
@@ -335,7 +336,7 @@ API keys, OAuth clients, webhooks, and workspace members.
|
|
|
335
336
|
| Action | What it does |
|
|
336
337
|
| --- | --- |
|
|
337
338
|
| `invite` | Invite a colleague (preview then confirm) |
|
|
338
|
-
| `update` | Change role or brands (preview then confirm) |
|
|
339
|
+
| `update` | Change role or brands, or turn a teammate's API and AI access on or off (preview then confirm) |
|
|
339
340
|
| `remove` | Remove a member or invite (preview then confirm) |
|
|
340
341
|
|
|
341
342
|
**`manage_oauth_client`**: Manage an OAuth client
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -341,6 +341,14 @@ export type WorkspaceMemberSummary = {
|
|
|
341
341
|
status: string | null;
|
|
342
342
|
/** [] = every brand; missing = not visible to a brand-limited caller. */
|
|
343
343
|
brand_ids?: string[];
|
|
344
|
+
/** Can this teammate use the workspace's API and AI app access. Always true for the owner. */
|
|
345
|
+
api_access?: boolean;
|
|
346
|
+
/** Owners and admins only: live keys and AI app connections for this workspace. */
|
|
347
|
+
api_usage?: {
|
|
348
|
+
key_count: number;
|
|
349
|
+
last_used_at: string | null;
|
|
350
|
+
connected_apps: string[];
|
|
351
|
+
};
|
|
344
352
|
joined_at: string | null;
|
|
345
353
|
created_at: string | null;
|
|
346
354
|
};
|
|
@@ -375,6 +383,9 @@ export type WorkspaceMemberPreview = {
|
|
|
375
383
|
counts_toward_seats: boolean;
|
|
376
384
|
seats_used: number | null;
|
|
377
385
|
seat_limit: number | null;
|
|
386
|
+
/** action=set_api_access only. */
|
|
387
|
+
api_access?: boolean;
|
|
388
|
+
previous_api_access?: boolean | null;
|
|
378
389
|
};
|
|
379
390
|
export type WorkspaceMemberChangeResponse = {
|
|
380
391
|
requires_confirmation?: boolean;
|
|
@@ -1097,6 +1108,7 @@ export declare class PosterlyClient {
|
|
|
1097
1108
|
}, data: {
|
|
1098
1109
|
role?: WorkspaceMemberRole;
|
|
1099
1110
|
brand_ids?: string[];
|
|
1111
|
+
api_access?: boolean;
|
|
1100
1112
|
} & WorkspaceMemberConfirmFields): Promise<WorkspaceMemberChangeResponse>;
|
|
1101
1113
|
removeWorkspaceMember(workspaceId: string, target: {
|
|
1102
1114
|
member_id?: string;
|
|
@@ -1256,6 +1268,7 @@ export declare class PosterlyClient {
|
|
|
1256
1268
|
quality?: 'low' | 'medium' | 'high';
|
|
1257
1269
|
thinking_level?: 'minimal' | 'high';
|
|
1258
1270
|
reference_image_urls?: string[];
|
|
1271
|
+
workspace_id?: string;
|
|
1259
1272
|
}): Promise<{
|
|
1260
1273
|
job_id: string;
|
|
1261
1274
|
status: string;
|
|
@@ -1295,6 +1308,7 @@ export declare class PosterlyClient {
|
|
|
1295
1308
|
}>;
|
|
1296
1309
|
generate_audio?: boolean;
|
|
1297
1310
|
source_video_url?: string;
|
|
1311
|
+
workspace_id?: string;
|
|
1298
1312
|
}): Promise<Record<string, any>>;
|
|
1299
1313
|
getVideoJob(params?: {
|
|
1300
1314
|
job_id?: string;
|
|
@@ -1458,7 +1472,7 @@ export declare class PosterlyClient {
|
|
|
1458
1472
|
getXPostingQuota(params?: {
|
|
1459
1473
|
workspace_id?: string;
|
|
1460
1474
|
}): Promise<XPostingQuotaResponse>;
|
|
1461
|
-
getSignedUploadUrl(filename: string, contentType: string, size: number): Promise<{
|
|
1475
|
+
getSignedUploadUrl(filename: string, contentType: string, size: number, workspaceId?: string): Promise<{
|
|
1462
1476
|
upload_url: string;
|
|
1463
1477
|
token: string;
|
|
1464
1478
|
path: string;
|
|
@@ -1468,6 +1482,7 @@ export declare class PosterlyClient {
|
|
|
1468
1482
|
createMediaDrop(input?: {
|
|
1469
1483
|
filename?: string;
|
|
1470
1484
|
maxFiles?: number;
|
|
1485
|
+
workspaceId?: string;
|
|
1471
1486
|
}): Promise<{
|
|
1472
1487
|
session_id: string;
|
|
1473
1488
|
drop_url: string;
|
|
@@ -1497,6 +1512,7 @@ export declare class PosterlyClient {
|
|
|
1497
1512
|
base64Data?: string;
|
|
1498
1513
|
filename: string;
|
|
1499
1514
|
contentType?: string;
|
|
1515
|
+
workspaceId?: string;
|
|
1500
1516
|
}): Promise<{
|
|
1501
1517
|
url: string;
|
|
1502
1518
|
path: string;
|
|
@@ -1505,6 +1521,7 @@ export declare class PosterlyClient {
|
|
|
1505
1521
|
url: string;
|
|
1506
1522
|
filename?: string;
|
|
1507
1523
|
contentType?: string;
|
|
1524
|
+
workspaceId?: string;
|
|
1508
1525
|
}): Promise<{
|
|
1509
1526
|
url: string;
|
|
1510
1527
|
path: string;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -597,17 +597,19 @@ export class PosterlyClient {
|
|
|
597
597
|
const qs = searchParams.toString();
|
|
598
598
|
return this.request('GET', `/x-posting/quota${qs ? `?${qs}` : ''}`);
|
|
599
599
|
}
|
|
600
|
-
async getSignedUploadUrl(filename, contentType, size) {
|
|
600
|
+
async getSignedUploadUrl(filename, contentType, size, workspaceId) {
|
|
601
601
|
return this.request('POST', '/media/signed-upload', {
|
|
602
602
|
filename,
|
|
603
603
|
content_type: contentType,
|
|
604
604
|
size,
|
|
605
|
+
...(workspaceId ? { workspace_id: workspaceId } : {}),
|
|
605
606
|
});
|
|
606
607
|
}
|
|
607
608
|
async createMediaDrop(input) {
|
|
608
609
|
return this.request('POST', '/media/drop-sessions', {
|
|
609
610
|
filename: input?.filename,
|
|
610
611
|
max_files: input?.maxFiles,
|
|
612
|
+
...(input?.workspaceId ? { workspace_id: input.workspaceId } : {}),
|
|
611
613
|
});
|
|
612
614
|
}
|
|
613
615
|
async listMedia(params) {
|
|
@@ -636,7 +638,7 @@ export class PosterlyClient {
|
|
|
636
638
|
const SIZE_THRESHOLD = 3 * 1024 * 1024; // 3MB
|
|
637
639
|
if (fileBuffer.length > SIZE_THRESHOLD) {
|
|
638
640
|
// Large file: PUT to the object-storage signed URL (not a Vercel relay).
|
|
639
|
-
const signed = await this.getSignedUploadUrl(input.filename, contentType, fileBuffer.length);
|
|
641
|
+
const signed = await this.getSignedUploadUrl(input.filename, contentType, fileBuffer.length, input.workspaceId);
|
|
640
642
|
const uploadRes = await fetch(signed.upload_url, {
|
|
641
643
|
method: 'PUT',
|
|
642
644
|
headers: {
|
|
@@ -657,6 +659,7 @@ export class PosterlyClient {
|
|
|
657
659
|
filename: input.filename,
|
|
658
660
|
data,
|
|
659
661
|
content_type: contentType,
|
|
662
|
+
...(input.workspaceId ? { workspace_id: input.workspaceId } : {}),
|
|
660
663
|
});
|
|
661
664
|
}
|
|
662
665
|
async uploadMediaFromUrl(input) {
|
|
@@ -664,6 +667,7 @@ export class PosterlyClient {
|
|
|
664
667
|
url: input.url,
|
|
665
668
|
filename: input.filename,
|
|
666
669
|
content_type: input.contentType,
|
|
670
|
+
...(input.workspaceId ? { workspace_id: input.workspaceId } : {}),
|
|
667
671
|
});
|
|
668
672
|
}
|
|
669
673
|
appendInboxListParams(searchParams, params) {
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const POSTERLY_MCP_VERSION = "0.
|
|
1
|
+
export declare const POSTERLY_MCP_VERSION = "0.46.0";
|
package/dist/lib/version.js
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.46.0';
|
|
@@ -6,15 +6,19 @@ export declare const createMediaDropTool: {
|
|
|
6
6
|
inputSchema: z.ZodObject<{
|
|
7
7
|
filename: z.ZodOptional<z.ZodString>;
|
|
8
8
|
max_files: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
9
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
workspace_id?: string | undefined;
|
|
10
12
|
filename?: string | undefined;
|
|
11
13
|
max_files?: number | undefined;
|
|
12
14
|
}, {
|
|
15
|
+
workspace_id?: string | undefined;
|
|
13
16
|
filename?: string | undefined;
|
|
14
17
|
max_files?: number | undefined;
|
|
15
18
|
}>;
|
|
16
19
|
execute(client: PosterlyClient, input: {
|
|
17
20
|
filename?: string;
|
|
18
21
|
max_files?: number;
|
|
22
|
+
workspace_id?: string;
|
|
19
23
|
}): Promise<string>;
|
|
20
24
|
};
|
|
@@ -11,11 +11,13 @@ export const createMediaDropTool = {
|
|
|
11
11
|
.max(10)
|
|
12
12
|
.optional()
|
|
13
13
|
.describe('How many files the drop can accept. Default 10, max 10.'),
|
|
14
|
+
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.'),
|
|
14
15
|
}),
|
|
15
16
|
async execute(client, input) {
|
|
16
17
|
const drop = await client.createMediaDrop({
|
|
17
18
|
filename: input.filename,
|
|
18
19
|
maxFiles: input.max_files,
|
|
20
|
+
workspaceId: input.workspace_id,
|
|
19
21
|
});
|
|
20
22
|
return [
|
|
21
23
|
'Media drop created. Send this URL to the user. They open it with no posterly login and drop the file.',
|
|
@@ -7,18 +7,22 @@ export declare const createSignedUploadTool: {
|
|
|
7
7
|
filename: z.ZodString;
|
|
8
8
|
content_type: z.ZodString;
|
|
9
9
|
size: z.ZodNumber;
|
|
10
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
10
11
|
}, "strip", z.ZodTypeAny, {
|
|
11
12
|
filename: string;
|
|
12
13
|
content_type: string;
|
|
13
14
|
size: number;
|
|
15
|
+
workspace_id?: string | undefined;
|
|
14
16
|
}, {
|
|
15
17
|
filename: string;
|
|
16
18
|
content_type: string;
|
|
17
19
|
size: number;
|
|
20
|
+
workspace_id?: string | undefined;
|
|
18
21
|
}>;
|
|
19
22
|
execute(client: PosterlyClient, input: {
|
|
20
23
|
filename: string;
|
|
21
24
|
content_type: string;
|
|
22
25
|
size: number;
|
|
26
|
+
workspace_id?: string;
|
|
23
27
|
}): Promise<string>;
|
|
24
28
|
};
|
|
@@ -6,9 +6,10 @@ export const createSignedUploadTool = {
|
|
|
6
6
|
filename: z.string().describe('Filename with extension, e.g. launch-video.mp4'),
|
|
7
7
|
content_type: z.string().describe('MIME type, e.g. video/mp4'),
|
|
8
8
|
size: z.number().int().positive().describe('File size in bytes'),
|
|
9
|
+
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.'),
|
|
9
10
|
}),
|
|
10
11
|
async execute(client, input) {
|
|
11
|
-
const signed = await client.getSignedUploadUrl(input.filename, input.content_type, input.size);
|
|
12
|
+
const signed = await client.getSignedUploadUrl(input.filename, input.content_type, input.size, input.workspace_id);
|
|
12
13
|
return [
|
|
13
14
|
'Signed upload created.',
|
|
14
15
|
`• Upload URL: ${signed.upload_url}`,
|
|
@@ -14,10 +14,12 @@ export declare const generateImageTool: {
|
|
|
14
14
|
quality: z.ZodOptional<z.ZodEnum<["low", "medium", "high"]>>;
|
|
15
15
|
thinking_level: z.ZodOptional<z.ZodEnum<["minimal", "high"]>>;
|
|
16
16
|
reference_image_urls: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
17
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
17
18
|
confirm: z.ZodLiteral<true>;
|
|
18
19
|
}, "strip", z.ZodTypeAny, {
|
|
19
20
|
confirm: true;
|
|
20
21
|
prompt: string;
|
|
22
|
+
workspace_id?: string | undefined;
|
|
21
23
|
provider?: "google" | "xai" | undefined;
|
|
22
24
|
aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "1:4" | "1:8" | "21:9" | "4:1" | "8:1" | "5:4" | undefined;
|
|
23
25
|
resolution?: "512" | "1K" | "2K" | "4K" | undefined;
|
|
@@ -30,6 +32,7 @@ export declare const generateImageTool: {
|
|
|
30
32
|
}, {
|
|
31
33
|
confirm: true;
|
|
32
34
|
prompt: string;
|
|
35
|
+
workspace_id?: string | undefined;
|
|
33
36
|
provider?: "google" | "xai" | undefined;
|
|
34
37
|
aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | "4:5" | "1:4" | "1:8" | "21:9" | "4:1" | "8:1" | "5:4" | undefined;
|
|
35
38
|
resolution?: "512" | "1K" | "2K" | "4K" | undefined;
|
|
@@ -51,6 +54,7 @@ export declare const generateImageTool: {
|
|
|
51
54
|
quality?: "low" | "medium" | "high";
|
|
52
55
|
thinking_level?: "minimal" | "high";
|
|
53
56
|
reference_image_urls?: string[];
|
|
57
|
+
workspace_id?: string;
|
|
54
58
|
confirm: true;
|
|
55
59
|
}): Promise<string>;
|
|
56
60
|
};
|
|
@@ -62,6 +62,7 @@ export const generateImageTool = {
|
|
|
62
62
|
.max(14)
|
|
63
63
|
.optional()
|
|
64
64
|
.describe('Google only. Up to 14 HTTPS image URLs used as references (logo, product, style lock). Same as dashboard reference images.'),
|
|
65
|
+
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.'),
|
|
65
66
|
confirm: z.literal(true).describe('Must be true after explicit user confirmation of subject, style, aspect ratio, quality, and credit use.'),
|
|
66
67
|
}),
|
|
67
68
|
async execute(client, input) {
|
|
@@ -25,10 +25,12 @@ export declare const generateVideoTool: {
|
|
|
25
25
|
}>, "many">>;
|
|
26
26
|
generate_audio: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
27
27
|
source_video_url: z.ZodOptional<z.ZodString>;
|
|
28
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
28
29
|
confirm: z.ZodLiteral<true>;
|
|
29
30
|
}, "strip", z.ZodTypeAny, {
|
|
30
31
|
confirm: true;
|
|
31
32
|
prompt: string;
|
|
33
|
+
workspace_id?: string | undefined;
|
|
32
34
|
provider?: "google" | "xai" | undefined;
|
|
33
35
|
negative_prompt?: string | undefined;
|
|
34
36
|
aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | undefined;
|
|
@@ -46,6 +48,7 @@ export declare const generateVideoTool: {
|
|
|
46
48
|
}, {
|
|
47
49
|
confirm: true;
|
|
48
50
|
prompt: string;
|
|
51
|
+
workspace_id?: string | undefined;
|
|
49
52
|
provider?: "google" | "xai" | undefined;
|
|
50
53
|
negative_prompt?: string | undefined;
|
|
51
54
|
aspect_ratio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4" | "3:2" | "2:3" | undefined;
|
|
@@ -18,6 +18,7 @@ export const generateVideoTool = {
|
|
|
18
18
|
})).max(3).optional(),
|
|
19
19
|
generate_audio: z.boolean().default(true).optional().describe('Google can disable generated audio. xAI Grok includes audio and requires this to remain true.'),
|
|
20
20
|
source_video_url: z.string().url().optional(),
|
|
21
|
+
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.'),
|
|
21
22
|
confirm: z.literal(true).describe('Must be true after explicit user confirmation of prompt, model, duration, resolution, and credit cost.'),
|
|
22
23
|
}),
|
|
23
24
|
async execute(client, input) {
|
|
@@ -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
|
}
|
|
@@ -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.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posterly-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.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.46.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.46.0",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
},
|
package/src/lib/api-client.ts
CHANGED
|
@@ -390,6 +390,10 @@ export type WorkspaceMemberSummary = {
|
|
|
390
390
|
status: string | null;
|
|
391
391
|
/** [] = every brand; missing = not visible to a brand-limited caller. */
|
|
392
392
|
brand_ids?: string[];
|
|
393
|
+
/** Can this teammate use the workspace's API and AI app access. Always true for the owner. */
|
|
394
|
+
api_access?: boolean;
|
|
395
|
+
/** Owners and admins only: live keys and AI app connections for this workspace. */
|
|
396
|
+
api_usage?: { key_count: number; last_used_at: string | null; connected_apps: string[] };
|
|
393
397
|
joined_at: string | null;
|
|
394
398
|
created_at: string | null;
|
|
395
399
|
};
|
|
@@ -423,6 +427,9 @@ export type WorkspaceMemberPreview = {
|
|
|
423
427
|
counts_toward_seats: boolean;
|
|
424
428
|
seats_used: number | null;
|
|
425
429
|
seat_limit: number | null;
|
|
430
|
+
/** action=set_api_access only. */
|
|
431
|
+
api_access?: boolean;
|
|
432
|
+
previous_api_access?: boolean | null;
|
|
426
433
|
};
|
|
427
434
|
|
|
428
435
|
export type WorkspaceMemberChangeResponse = {
|
|
@@ -1333,7 +1340,7 @@ export class PosterlyClient {
|
|
|
1333
1340
|
async updateWorkspaceMember(
|
|
1334
1341
|
workspaceId: string,
|
|
1335
1342
|
target: { member_id?: string; invite_id?: string },
|
|
1336
|
-
data: { role?: WorkspaceMemberRole; brand_ids?: string[] } & WorkspaceMemberConfirmFields,
|
|
1343
|
+
data: { role?: WorkspaceMemberRole; brand_ids?: string[]; api_access?: boolean } & WorkspaceMemberConfirmFields,
|
|
1337
1344
|
): Promise<WorkspaceMemberChangeResponse> {
|
|
1338
1345
|
const base = `/workspaces/${encodeURIComponent(workspaceId)}`;
|
|
1339
1346
|
const path = target.member_id
|
|
@@ -1640,6 +1647,7 @@ export class PosterlyClient {
|
|
|
1640
1647
|
quality?: 'low' | 'medium' | 'high';
|
|
1641
1648
|
thinking_level?: 'minimal' | 'high';
|
|
1642
1649
|
reference_image_urls?: string[];
|
|
1650
|
+
workspace_id?: string;
|
|
1643
1651
|
}): Promise<{
|
|
1644
1652
|
job_id: string;
|
|
1645
1653
|
status: string;
|
|
@@ -1685,6 +1693,7 @@ export class PosterlyClient {
|
|
|
1685
1693
|
reference_images?: Array<{ url: string; type: 'character' | 'object' | 'scene' }>;
|
|
1686
1694
|
generate_audio?: boolean;
|
|
1687
1695
|
source_video_url?: string;
|
|
1696
|
+
workspace_id?: string;
|
|
1688
1697
|
}): Promise<Record<string, any>> {
|
|
1689
1698
|
return this.request('POST', '/ai/generate-video', data);
|
|
1690
1699
|
}
|
|
@@ -1984,6 +1993,7 @@ export class PosterlyClient {
|
|
|
1984
1993
|
filename: string,
|
|
1985
1994
|
contentType: string,
|
|
1986
1995
|
size: number,
|
|
1996
|
+
workspaceId?: string,
|
|
1987
1997
|
): Promise<{
|
|
1988
1998
|
upload_url: string;
|
|
1989
1999
|
token: string;
|
|
@@ -1995,12 +2005,14 @@ export class PosterlyClient {
|
|
|
1995
2005
|
filename,
|
|
1996
2006
|
content_type: contentType,
|
|
1997
2007
|
size,
|
|
2008
|
+
...(workspaceId ? { workspace_id: workspaceId } : {}),
|
|
1998
2009
|
});
|
|
1999
2010
|
}
|
|
2000
2011
|
|
|
2001
2012
|
async createMediaDrop(input?: {
|
|
2002
2013
|
filename?: string;
|
|
2003
2014
|
maxFiles?: number;
|
|
2015
|
+
workspaceId?: string;
|
|
2004
2016
|
}): Promise<{
|
|
2005
2017
|
session_id: string;
|
|
2006
2018
|
drop_url: string;
|
|
@@ -2012,6 +2024,7 @@ export class PosterlyClient {
|
|
|
2012
2024
|
return this.request('POST', '/media/drop-sessions', {
|
|
2013
2025
|
filename: input?.filename,
|
|
2014
2026
|
max_files: input?.maxFiles,
|
|
2027
|
+
...(input?.workspaceId ? { workspace_id: input.workspaceId } : {}),
|
|
2015
2028
|
});
|
|
2016
2029
|
}
|
|
2017
2030
|
|
|
@@ -2043,6 +2056,7 @@ export class PosterlyClient {
|
|
|
2043
2056
|
base64Data?: string;
|
|
2044
2057
|
filename: string;
|
|
2045
2058
|
contentType?: string;
|
|
2059
|
+
workspaceId?: string;
|
|
2046
2060
|
}): Promise<{ url: string; path: string }> {
|
|
2047
2061
|
this.requireApiKey();
|
|
2048
2062
|
|
|
@@ -2068,6 +2082,7 @@ export class PosterlyClient {
|
|
|
2068
2082
|
input.filename,
|
|
2069
2083
|
contentType,
|
|
2070
2084
|
fileBuffer.length,
|
|
2085
|
+
input.workspaceId,
|
|
2071
2086
|
);
|
|
2072
2087
|
|
|
2073
2088
|
const uploadRes = await fetch(signed.upload_url, {
|
|
@@ -2094,6 +2109,7 @@ export class PosterlyClient {
|
|
|
2094
2109
|
filename: input.filename,
|
|
2095
2110
|
data,
|
|
2096
2111
|
content_type: contentType,
|
|
2112
|
+
...(input.workspaceId ? { workspace_id: input.workspaceId } : {}),
|
|
2097
2113
|
});
|
|
2098
2114
|
}
|
|
2099
2115
|
|
|
@@ -2101,11 +2117,13 @@ export class PosterlyClient {
|
|
|
2101
2117
|
url: string;
|
|
2102
2118
|
filename?: string;
|
|
2103
2119
|
contentType?: string;
|
|
2120
|
+
workspaceId?: string;
|
|
2104
2121
|
}): Promise<{ url: string; path: string; source_url: string; content_type: string; size: number }> {
|
|
2105
2122
|
return this.request('POST', '/media/upload-from-url', {
|
|
2106
2123
|
url: input.url,
|
|
2107
2124
|
filename: input.filename,
|
|
2108
2125
|
content_type: input.contentType,
|
|
2126
|
+
...(input.workspaceId ? { workspace_id: input.workspaceId } : {}),
|
|
2109
2127
|
});
|
|
2110
2128
|
}
|
|
2111
2129
|
|
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.46.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.',
|
|
@@ -9,13 +9,14 @@ export const createSignedUploadTool = {
|
|
|
9
9
|
filename: z.string().describe('Filename with extension, e.g. launch-video.mp4'),
|
|
10
10
|
content_type: z.string().describe('MIME type, e.g. video/mp4'),
|
|
11
11
|
size: z.number().int().positive().describe('File size in bytes'),
|
|
12
|
+
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.'),
|
|
12
13
|
}),
|
|
13
14
|
|
|
14
15
|
async execute(
|
|
15
16
|
client: PosterlyClient,
|
|
16
|
-
input: { filename: string; content_type: string; size: number },
|
|
17
|
+
input: { filename: string; content_type: string; size: number; workspace_id?: string },
|
|
17
18
|
) {
|
|
18
|
-
const signed = await client.getSignedUploadUrl(input.filename, input.content_type, input.size);
|
|
19
|
+
const signed = await client.getSignedUploadUrl(input.filename, input.content_type, input.size, input.workspace_id);
|
|
19
20
|
return [
|
|
20
21
|
'Signed upload created.',
|
|
21
22
|
`• Upload URL: ${signed.upload_url}`,
|
|
@@ -65,6 +65,7 @@ export const generateImageTool = {
|
|
|
65
65
|
.max(14)
|
|
66
66
|
.optional()
|
|
67
67
|
.describe('Google only. Up to 14 HTTPS image URLs used as references (logo, product, style lock). Same as dashboard reference images.'),
|
|
68
|
+
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.'),
|
|
68
69
|
confirm: z.literal(true).describe('Must be true after explicit user confirmation of subject, style, aspect ratio, quality, and credit use.'),
|
|
69
70
|
}),
|
|
70
71
|
|
|
@@ -81,6 +82,7 @@ export const generateImageTool = {
|
|
|
81
82
|
quality?: 'low' | 'medium' | 'high';
|
|
82
83
|
thinking_level?: 'minimal' | 'high';
|
|
83
84
|
reference_image_urls?: string[];
|
|
85
|
+
workspace_id?: string;
|
|
84
86
|
confirm: true;
|
|
85
87
|
}
|
|
86
88
|
) {
|
|
@@ -21,6 +21,7 @@ export const generateVideoTool = {
|
|
|
21
21
|
})).max(3).optional(),
|
|
22
22
|
generate_audio: z.boolean().default(true).optional().describe('Google can disable generated audio. xAI Grok includes audio and requires this to remain true.'),
|
|
23
23
|
source_video_url: z.string().url().optional(),
|
|
24
|
+
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.'),
|
|
24
25
|
confirm: z.literal(true).describe('Must be true after explicit user confirmation of prompt, model, duration, resolution, and credit cost.'),
|
|
25
26
|
}),
|
|
26
27
|
|
|
@@ -7,6 +7,19 @@ export function formatBrandIds(value: string[] | null | undefined): string {
|
|
|
7
7
|
return value.length === 0 ? 'every brand' : value.map((id) => code(id)).join(', ');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/** API and AI app access for one member (issue #1170). */
|
|
11
|
+
export function formatApiAccess(member: { role?: string | null; api_access?: boolean; api_usage?: { key_count: number; last_used_at: string | null; connected_apps: string[] } }): string {
|
|
12
|
+
if (member.api_access === undefined) return '';
|
|
13
|
+
const base = member.role === 'owner' ? 'owner' : member.api_access ? 'on' : 'off';
|
|
14
|
+
const usage = member.api_usage;
|
|
15
|
+
if (!usage) return base;
|
|
16
|
+
const parts: string[] = [];
|
|
17
|
+
if (usage.key_count > 0) parts.push(`${usage.key_count} key${usage.key_count === 1 ? '' : 's'}`);
|
|
18
|
+
if (usage.connected_apps.length > 0) parts.push(usage.connected_apps.join(', '));
|
|
19
|
+
if (usage.last_used_at) parts.push(`last used ${usage.last_used_at}`);
|
|
20
|
+
return parts.length > 0 ? `${base} (${parts.join('; ')})` : base;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
function formatWorkspaceMembers(result: WorkspaceMembersResponse): string {
|
|
11
24
|
const members = result.members || [];
|
|
12
25
|
const invites = result.invites || [];
|
|
@@ -14,7 +27,7 @@ function formatWorkspaceMembers(result: WorkspaceMembersResponse): string {
|
|
|
14
27
|
mdTitle(`Team (${members.length} members, ${invites.length} pending invites)`, `Workspace ${result.workspace_id}`),
|
|
15
28
|
mdSection('Members', members.length > 0
|
|
16
29
|
? mdTable(
|
|
17
|
-
['Name', 'Member ID', 'Email', 'Role', 'Status', 'Brands'],
|
|
30
|
+
['Name', 'Member ID', 'Email', 'Role', 'Status', 'Brands', 'API access'],
|
|
18
31
|
members.map((member) => [
|
|
19
32
|
member.name || member.email || member.id,
|
|
20
33
|
code(member.id),
|
|
@@ -22,6 +35,7 @@ function formatWorkspaceMembers(result: WorkspaceMembersResponse): string {
|
|
|
22
35
|
member.role,
|
|
23
36
|
member.status,
|
|
24
37
|
formatBrandIds(member.brand_ids),
|
|
38
|
+
formatApiAccess(member),
|
|
25
39
|
]),
|
|
26
40
|
)
|
|
27
41
|
: 'No members found.'),
|
|
@@ -48,7 +62,7 @@ function formatWorkspaceMembers(result: WorkspaceMembersResponse): string {
|
|
|
48
62
|
export const listWorkspaceMembersTool = {
|
|
49
63
|
name: 'list_workspace_members',
|
|
50
64
|
description:
|
|
51
|
-
'List the people in a workspace: members (with role, status and
|
|
65
|
+
'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.',
|
|
52
66
|
inputSchema: z.object({
|
|
53
67
|
workspace_id: z.string().describe('Workspace ID (from whoami).'),
|
|
54
68
|
}),
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
WorkspaceMemberRole,
|
|
6
6
|
} from '../lib/api-client.js';
|
|
7
7
|
import { code, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
|
|
8
|
-
import { formatBrandIds } from './list-workspace-members.js';
|
|
8
|
+
import { formatApiAccess, formatBrandIds } from './list-workspace-members.js';
|
|
9
9
|
|
|
10
10
|
const ACTIONS = ['invite', 'update', 'remove'] as const;
|
|
11
11
|
const ROLES = ['publisher', 'editor', 'viewer'] as const;
|
|
@@ -19,6 +19,7 @@ export type ManageWorkspaceMemberInput = {
|
|
|
19
19
|
resend?: boolean;
|
|
20
20
|
member_id?: string;
|
|
21
21
|
invite_id?: string;
|
|
22
|
+
api_access?: boolean;
|
|
22
23
|
confirm?: boolean;
|
|
23
24
|
preview_id?: string;
|
|
24
25
|
};
|
|
@@ -49,6 +50,9 @@ function formatChange(result: WorkspaceMemberChangeResponse): string {
|
|
|
49
50
|
['Seats', preview.counts_toward_seats && preview.seat_limit != null
|
|
50
51
|
? `${preview.seats_used} in use of ${preview.seat_limit === -1 ? 'unlimited' : preview.seat_limit}`
|
|
51
52
|
: ''],
|
|
53
|
+
['API access', typeof preview.api_access === 'boolean'
|
|
54
|
+
? `${preview.previous_api_access ? 'on' : 'off'} -> ${preview.api_access ? 'on' : 'off'}`
|
|
55
|
+
: ''],
|
|
52
56
|
])),
|
|
53
57
|
mdSection('Preview ID', `${code(result.preview_id || '')} (expires ${result.preview_expires_at || 'soon'})`),
|
|
54
58
|
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.'),
|
|
@@ -64,6 +68,7 @@ function formatChange(result: WorkspaceMemberChangeResponse): string {
|
|
|
64
68
|
['Role', subject?.role],
|
|
65
69
|
['Status', subject?.status],
|
|
66
70
|
['Brands', subject?.brand_ids ? formatBrandIds(subject.brand_ids) : ''],
|
|
71
|
+
['API access', result.member ? formatApiAccess(result.member) : ''],
|
|
67
72
|
['Invite email sent', typeof result.email_sent === 'boolean' ? (result.email_sent ? 'yes' : 'no, ask them to check spam or resend later') : ''],
|
|
68
73
|
['Workspace owner notified', result.owner_notified ? 'yes' : ''],
|
|
69
74
|
])),
|
|
@@ -88,14 +93,19 @@ export function validateManageWorkspaceMemberInput(input: ManageWorkspaceMemberI
|
|
|
88
93
|
if (!has(raw, 'email') || !has(raw, 'role')) return 'action=invite needs email and role (publisher, editor or viewer).';
|
|
89
94
|
return null;
|
|
90
95
|
}
|
|
91
|
-
const notAllowed = input.action === 'update' ? ['email', 'resend'] : ['email', 'resend', 'role', 'brand_ids'];
|
|
96
|
+
const notAllowed = input.action === 'update' ? ['email', 'resend'] : ['email', 'resend', 'role', 'brand_ids', 'api_access'];
|
|
92
97
|
const extra = notAllowed.filter((key) => has(raw, key));
|
|
93
98
|
if (extra.length) return `action=${input.action} does not take ${extra.join(', ')}.`;
|
|
94
99
|
if (has(raw, 'member_id') === has(raw, 'invite_id')) {
|
|
95
100
|
return `action=${input.action} needs exactly one of member_id or invite_id (from list_workspace_members).`;
|
|
96
101
|
}
|
|
102
|
+
if (input.action === 'update' && has(raw, 'api_access')) {
|
|
103
|
+
if (has(raw, 'invite_id')) return 'api_access can only be set on a member (member_id), not a pending invite.';
|
|
104
|
+
if (has(raw, 'role') || has(raw, 'brand_ids')) return 'Change api_access in its own call, without role or brand_ids.';
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
97
107
|
if (input.action === 'update' && !has(raw, 'role') && !has(raw, 'brand_ids')) {
|
|
98
|
-
return 'action=update needs role, brand_ids, or both.';
|
|
108
|
+
return 'action=update needs role, brand_ids, or both; or api_access on its own.';
|
|
99
109
|
}
|
|
100
110
|
return null;
|
|
101
111
|
}
|
|
@@ -103,7 +113,7 @@ export function validateManageWorkspaceMemberInput(input: ManageWorkspaceMemberI
|
|
|
103
113
|
export const manageWorkspaceMemberTool = {
|
|
104
114
|
name: 'manage_workspace_member',
|
|
105
115
|
description:
|
|
106
|
-
'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.',
|
|
116
|
+
'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.',
|
|
107
117
|
inputSchema: z.object({
|
|
108
118
|
action: z.enum(ACTIONS).describe('invite, update, or remove.'),
|
|
109
119
|
workspace_id: z.string().describe('Workspace ID (from whoami).'),
|
|
@@ -113,6 +123,7 @@ export const manageWorkspaceMemberTool = {
|
|
|
113
123
|
resend: z.boolean().optional().describe('action=invite only: email an unchanged pending invite again.'),
|
|
114
124
|
member_id: z.string().optional().describe('action=update or action=remove: a member ID from list_workspace_members.'),
|
|
115
125
|
invite_id: z.string().optional().describe('action=update or action=remove: a pending invite ID from list_workspace_members.'),
|
|
126
|
+
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.'),
|
|
116
127
|
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.'),
|
|
117
128
|
preview_id: z.string().optional().describe('The preview_id returned by the preview call. Required with confirm: true.'),
|
|
118
129
|
}),
|
|
@@ -141,6 +152,7 @@ export const manageWorkspaceMemberTool = {
|
|
|
141
152
|
result = await client.updateWorkspaceMember(workspaceId, target, {
|
|
142
153
|
...(input.role !== undefined ? { role: input.role } : {}),
|
|
143
154
|
...(input.brand_ids !== undefined ? { brand_ids: input.brand_ids } : {}),
|
|
155
|
+
...(input.api_access !== undefined ? { api_access: input.api_access } : {}),
|
|
144
156
|
...confirmFields,
|
|
145
157
|
});
|
|
146
158
|
} else {
|
|
@@ -9,6 +9,7 @@ type UploadMediaInput = {
|
|
|
9
9
|
url?: string;
|
|
10
10
|
filename?: string;
|
|
11
11
|
content_type?: string;
|
|
12
|
+
workspace_id?: string;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export const uploadMediaTool = {
|
|
@@ -38,6 +39,7 @@ export const uploadMediaTool = {
|
|
|
38
39
|
.string()
|
|
39
40
|
.optional()
|
|
40
41
|
.describe('MIME type (auto-detected from filename if omitted)'),
|
|
42
|
+
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.'),
|
|
41
43
|
}),
|
|
42
44
|
/** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
|
|
43
45
|
legacySchemas: {
|
|
@@ -50,7 +52,7 @@ export const uploadMediaTool = {
|
|
|
50
52
|
|
|
51
53
|
async execute(client: PosterlyClient, input: UploadMediaInput, ctx: ToolContext) {
|
|
52
54
|
const { mode, args } = validateArgs(ctx, 'upload_media', input);
|
|
53
|
-
if (mode === 'url') return uploadFromUrl(client, args as { url: string; filename?: string; content_type?: string });
|
|
55
|
+
if (mode === 'url') return uploadFromUrl(client, args as { url: string; filename?: string; content_type?: string; workspace_id?: string });
|
|
54
56
|
return uploadFile(client, args as UploadMediaInput & { filename: string });
|
|
55
57
|
},
|
|
56
58
|
};
|
|
@@ -62,6 +64,7 @@ async function uploadFile(
|
|
|
62
64
|
base64_data?: string;
|
|
63
65
|
filename: string;
|
|
64
66
|
content_type?: string;
|
|
67
|
+
workspace_id?: string;
|
|
65
68
|
},
|
|
66
69
|
): Promise<string> {
|
|
67
70
|
if (!input.file_path && !input.base64_data) {
|
|
@@ -73,6 +76,7 @@ async function uploadFile(
|
|
|
73
76
|
base64Data: input.base64_data,
|
|
74
77
|
filename: input.filename,
|
|
75
78
|
contentType: input.content_type,
|
|
79
|
+
workspaceId: input.workspace_id,
|
|
76
80
|
});
|
|
77
81
|
|
|
78
82
|
return mdSuccess('Media uploaded', [
|
|
@@ -83,12 +87,13 @@ async function uploadFile(
|
|
|
83
87
|
|
|
84
88
|
async function uploadFromUrl(
|
|
85
89
|
client: PosterlyClient,
|
|
86
|
-
input: { url: string; filename?: string; content_type?: string },
|
|
90
|
+
input: { url: string; filename?: string; content_type?: string; workspace_id?: string },
|
|
87
91
|
): Promise<string> {
|
|
88
92
|
const result = await client.uploadMediaFromUrl({
|
|
89
93
|
url: input.url,
|
|
90
94
|
filename: input.filename,
|
|
91
95
|
contentType: input.content_type,
|
|
96
|
+
workspaceId: input.workspace_id,
|
|
92
97
|
});
|
|
93
98
|
return [
|
|
94
99
|
'Remote media uploaded successfully.',
|
package/tool-registry.json
CHANGED
|
@@ -823,6 +823,9 @@
|
|
|
823
823
|
},
|
|
824
824
|
"content_type": {
|
|
825
825
|
"type": "string"
|
|
826
|
+
},
|
|
827
|
+
"workspace_id": {
|
|
828
|
+
"type": "string"
|
|
826
829
|
}
|
|
827
830
|
}
|
|
828
831
|
},
|
|
@@ -845,6 +848,9 @@
|
|
|
845
848
|
},
|
|
846
849
|
"content_type": {
|
|
847
850
|
"type": "string"
|
|
851
|
+
},
|
|
852
|
+
"workspace_id": {
|
|
853
|
+
"type": "string"
|
|
848
854
|
}
|
|
849
855
|
}
|
|
850
856
|
}
|