posterly-mcp-server 0.37.0 → 0.39.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 +4 -1
- package/dist/index.js +30 -0
- package/dist/lib/api-client.d.ts +30 -0
- package/dist/lib/api-client.js +22 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/create-media-drop.d.ts +20 -0
- package/dist/tools/create-media-drop.js +32 -0
- package/dist/tools/create-signed-upload.d.ts +2 -2
- package/dist/tools/delete-comment.d.ts +24 -0
- package/dist/tools/delete-comment.js +22 -0
- package/dist/tools/list-media.d.ts +20 -0
- package/dist/tools/list-media.js +38 -0
- package/dist/tools/upload-media-from-url.d.ts +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/index.ts +48 -0
- package/src/lib/api-client.ts +47 -0
- package/src/lib/version.ts +1 -1
- package/src/tools/create-media-drop.ts +39 -0
- package/src/tools/delete-comment.ts +29 -0
- package/src/tools/list-media.ts +48 -0
- package/tool-annotations.json +8 -0
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ Add the same server definition to your Cursor MCP settings:
|
|
|
122
122
|
|
|
123
123
|
## Available tools
|
|
124
124
|
|
|
125
|
-
`posterly-mcp-server@0.
|
|
125
|
+
`posterly-mcp-server@0.39.0` exposes 89 tools.
|
|
126
126
|
|
|
127
127
|
Public setup tools work before `POSTERLY_API_KEY` exists:
|
|
128
128
|
|
|
@@ -176,6 +176,8 @@ Authenticated tools require `POSTERLY_API_KEY`:
|
|
|
176
176
|
- `upload_media`
|
|
177
177
|
- `upload_media_from_url`
|
|
178
178
|
- `create_signed_upload`
|
|
179
|
+
- `create_media_drop` (human drop page at https://www.poster.ly/drop/<token> for ChatGPT/Claude laptop files)
|
|
180
|
+
- `list_media`
|
|
179
181
|
- `find_available_slot`
|
|
180
182
|
- `generate_captions`
|
|
181
183
|
- `generate_image`
|
|
@@ -197,6 +199,7 @@ Authenticated tools require `POSTERLY_API_KEY`:
|
|
|
197
199
|
- `get_comment`
|
|
198
200
|
- `reply_to_comment` (comment reply after explicit confirmation)
|
|
199
201
|
- `update_comment` (hide/unhide or mark read; hide is reversible)
|
|
202
|
+
- `delete_comment` (delete an Instagram or Facebook comment after confirmation; Threads replies cannot be deleted)
|
|
200
203
|
- `sync_inbox` (pull latest inbox data for one account; 30-second cooldown)
|
|
201
204
|
- `list_google_business_reviews`
|
|
202
205
|
- `get_google_business_review_link`
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,7 @@ import { listCommentsTool } from './tools/list-comments.js';
|
|
|
60
60
|
import { getCommentTool } from './tools/get-comment.js';
|
|
61
61
|
import { replyToCommentTool } from './tools/reply-to-comment.js';
|
|
62
62
|
import { updateCommentTool } from './tools/update-comment.js';
|
|
63
|
+
import { deleteCommentTool } from './tools/delete-comment.js';
|
|
63
64
|
import { syncInboxTool } from './tools/sync-inbox.js';
|
|
64
65
|
import { listActivityTool } from './tools/list-activity.js';
|
|
65
66
|
import { getUpdatesTool } from './tools/get-updates.js';
|
|
@@ -73,6 +74,8 @@ import { getPlatformSchemaTool } from './tools/get-platform-schema.js';
|
|
|
73
74
|
import { triggerPlatformHelperTool } from './tools/trigger-platform-helper.js';
|
|
74
75
|
import { getXPostingQuotaTool } from './tools/get-x-posting-quota.js';
|
|
75
76
|
import { createSignedUploadTool } from './tools/create-signed-upload.js';
|
|
77
|
+
import { createMediaDropTool } from './tools/create-media-drop.js';
|
|
78
|
+
import { listMediaTool } from './tools/list-media.js';
|
|
76
79
|
import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
|
|
77
80
|
import { getConnectLinkTool } from './tools/get-connect-link.js';
|
|
78
81
|
import { connectAccountTool } from './tools/connect-account.js';
|
|
@@ -501,6 +504,24 @@ server.tool(createSignedUploadTool.name, createSignedUploadTool.description, cre
|
|
|
501
504
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
502
505
|
}
|
|
503
506
|
});
|
|
507
|
+
server.tool(createMediaDropTool.name, createMediaDropTool.description, createMediaDropTool.inputSchema.shape, getToolAnnotations(createMediaDropTool.name), async (input) => {
|
|
508
|
+
try {
|
|
509
|
+
const text = await createMediaDropTool.execute(client, input);
|
|
510
|
+
return { content: [{ type: 'text', text }] };
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
server.tool(listMediaTool.name, listMediaTool.description, listMediaTool.inputSchema.shape, getToolAnnotations(listMediaTool.name), async (input) => {
|
|
517
|
+
try {
|
|
518
|
+
const text = await listMediaTool.execute(client, input);
|
|
519
|
+
return { content: [{ type: 'text', text }] };
|
|
520
|
+
}
|
|
521
|
+
catch (err) {
|
|
522
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
523
|
+
}
|
|
524
|
+
});
|
|
504
525
|
server.tool(getVideoOptionsTool.name, getVideoOptionsTool.description, getVideoOptionsTool.inputSchema.shape, getToolAnnotations(getVideoOptionsTool.name), async () => {
|
|
505
526
|
try {
|
|
506
527
|
const text = await getVideoOptionsTool.execute(client);
|
|
@@ -789,6 +810,15 @@ server.tool(updateCommentTool.name, updateCommentTool.description, updateComment
|
|
|
789
810
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
790
811
|
}
|
|
791
812
|
});
|
|
813
|
+
server.tool(deleteCommentTool.name, deleteCommentTool.description, deleteCommentTool.inputSchema.shape, getToolAnnotations(deleteCommentTool.name), async (input) => {
|
|
814
|
+
try {
|
|
815
|
+
const text = await deleteCommentTool.execute(client, input);
|
|
816
|
+
return { content: [{ type: 'text', text }] };
|
|
817
|
+
}
|
|
818
|
+
catch (err) {
|
|
819
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
820
|
+
}
|
|
821
|
+
});
|
|
792
822
|
server.tool(syncInboxTool.name, syncInboxTool.description, syncInboxTool.inputSchema.shape, getToolAnnotations(syncInboxTool.name), async (input) => {
|
|
793
823
|
try {
|
|
794
824
|
const text = await syncInboxTool.execute(client, input);
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -1262,6 +1262,33 @@ export declare class PosterlyClient {
|
|
|
1262
1262
|
public_url: string;
|
|
1263
1263
|
headers?: Record<string, string>;
|
|
1264
1264
|
}>;
|
|
1265
|
+
createMediaDrop(input?: {
|
|
1266
|
+
filename?: string;
|
|
1267
|
+
maxFiles?: number;
|
|
1268
|
+
}): Promise<{
|
|
1269
|
+
session_id: string;
|
|
1270
|
+
drop_url: string;
|
|
1271
|
+
expires_at: string;
|
|
1272
|
+
max_bytes: number;
|
|
1273
|
+
max_files: number;
|
|
1274
|
+
instructions: string;
|
|
1275
|
+
}>;
|
|
1276
|
+
listMedia(params?: {
|
|
1277
|
+
limit?: number;
|
|
1278
|
+
dropSessionId?: string;
|
|
1279
|
+
}): Promise<{
|
|
1280
|
+
media: Array<{
|
|
1281
|
+
id: string;
|
|
1282
|
+
public_url: string;
|
|
1283
|
+
filename: string;
|
|
1284
|
+
path: string;
|
|
1285
|
+
mime: string | null;
|
|
1286
|
+
bytes: number | null;
|
|
1287
|
+
created_at: string;
|
|
1288
|
+
drop_session_id: string | null;
|
|
1289
|
+
}>;
|
|
1290
|
+
total: number;
|
|
1291
|
+
}>;
|
|
1265
1292
|
uploadMedia(input: {
|
|
1266
1293
|
filePath?: string;
|
|
1267
1294
|
base64Data?: string;
|
|
@@ -1311,6 +1338,9 @@ export declare class PosterlyClient {
|
|
|
1311
1338
|
is_read?: boolean;
|
|
1312
1339
|
workspace_id?: string;
|
|
1313
1340
|
}): Promise<InboxCommentResponse>;
|
|
1341
|
+
deleteComment(id: string, workspaceId?: string): Promise<{
|
|
1342
|
+
success: true;
|
|
1343
|
+
}>;
|
|
1314
1344
|
syncInbox(data: {
|
|
1315
1345
|
account_id: number;
|
|
1316
1346
|
sync_type?: 'all' | 'dm' | 'comments';
|
package/dist/lib/api-client.js
CHANGED
|
@@ -506,6 +506,21 @@ export class PosterlyClient {
|
|
|
506
506
|
size,
|
|
507
507
|
});
|
|
508
508
|
}
|
|
509
|
+
async createMediaDrop(input) {
|
|
510
|
+
return this.request('POST', '/media/drop-sessions', {
|
|
511
|
+
filename: input?.filename,
|
|
512
|
+
max_files: input?.maxFiles,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
async listMedia(params) {
|
|
516
|
+
const searchParams = new URLSearchParams();
|
|
517
|
+
if (params?.limit)
|
|
518
|
+
searchParams.set('limit', String(params.limit));
|
|
519
|
+
if (params?.dropSessionId)
|
|
520
|
+
searchParams.set('drop_session_id', params.dropSessionId);
|
|
521
|
+
const qs = searchParams.toString();
|
|
522
|
+
return this.request('GET', `/media${qs ? `?${qs}` : ''}`);
|
|
523
|
+
}
|
|
509
524
|
async uploadMedia(input) {
|
|
510
525
|
this.requireApiKey();
|
|
511
526
|
let fileBuffer;
|
|
@@ -623,6 +638,13 @@ export class PosterlyClient {
|
|
|
623
638
|
body.workspace_id = data.workspace_id;
|
|
624
639
|
return this.request('PATCH', `/inbox/comments/${encodeURIComponent(data.comment_id)}`, body);
|
|
625
640
|
}
|
|
641
|
+
async deleteComment(id, workspaceId) {
|
|
642
|
+
const searchParams = new URLSearchParams();
|
|
643
|
+
if (workspaceId)
|
|
644
|
+
searchParams.set('workspace_id', workspaceId);
|
|
645
|
+
const qs = searchParams.toString();
|
|
646
|
+
return this.request('DELETE', `/inbox/comments/${encodeURIComponent(id)}${qs ? `?${qs}` : ''}`);
|
|
647
|
+
}
|
|
626
648
|
async syncInbox(data) {
|
|
627
649
|
return this.request('POST', '/inbox/sync', data);
|
|
628
650
|
}
|
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.39.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.39.0';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const createMediaDropTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
filename: z.ZodOptional<z.ZodString>;
|
|
8
|
+
max_files: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
filename?: string | undefined;
|
|
11
|
+
max_files?: number | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
filename?: string | undefined;
|
|
14
|
+
max_files?: number | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
execute(client: PosterlyClient, input: {
|
|
17
|
+
filename?: string;
|
|
18
|
+
max_files?: number;
|
|
19
|
+
}): Promise<string>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const createMediaDropTool = {
|
|
3
|
+
name: 'create_media_drop',
|
|
4
|
+
description: 'Create a human drop page so ChatGPT or Claude users can upload a laptop file without dashboard login. Returns drop_url (https://www.poster.ly/drop/<token>). Send that URL to the user, wait, then call list_media. This is not the chat paperclip and not create_signed_upload.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
filename: z.string().optional().describe('Optional filename hint for the user, e.g. launch-video.mp4'),
|
|
7
|
+
max_files: z
|
|
8
|
+
.number()
|
|
9
|
+
.int()
|
|
10
|
+
.min(1)
|
|
11
|
+
.max(10)
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('How many files the drop can accept. Default 10, max 10.'),
|
|
14
|
+
}),
|
|
15
|
+
async execute(client, input) {
|
|
16
|
+
const drop = await client.createMediaDrop({
|
|
17
|
+
filename: input.filename,
|
|
18
|
+
maxFiles: input.max_files,
|
|
19
|
+
});
|
|
20
|
+
return [
|
|
21
|
+
'Media drop created. Send this URL to the user. They open it with no posterly login and drop the file.',
|
|
22
|
+
`• Drop URL: ${drop.drop_url}`,
|
|
23
|
+
`• Session ID: ${drop.session_id}`,
|
|
24
|
+
`• Expires: ${drop.expires_at}`,
|
|
25
|
+
`• Max bytes: ${drop.max_bytes}`,
|
|
26
|
+
`• Max files: ${drop.max_files}`,
|
|
27
|
+
drop.instructions,
|
|
28
|
+
'After they finish, call list_media with this drop_session_id, then validate_post / create_post using public_url.',
|
|
29
|
+
'Chat attachments never reach MCP. HEIC and PDF are rejected.',
|
|
30
|
+
].join('\n');
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -8,12 +8,12 @@ export declare const createSignedUploadTool: {
|
|
|
8
8
|
content_type: z.ZodString;
|
|
9
9
|
size: z.ZodNumber;
|
|
10
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
|
-
content_type: string;
|
|
12
11
|
filename: string;
|
|
12
|
+
content_type: string;
|
|
13
13
|
size: number;
|
|
14
14
|
}, {
|
|
15
|
-
content_type: string;
|
|
16
15
|
filename: string;
|
|
16
|
+
content_type: string;
|
|
17
17
|
size: number;
|
|
18
18
|
}>;
|
|
19
19
|
execute(client: PosterlyClient, input: {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const deleteCommentTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
comment_id: z.ZodString;
|
|
8
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
9
|
+
confirm: z.ZodLiteral<true>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
confirm: true;
|
|
12
|
+
comment_id: string;
|
|
13
|
+
workspace_id?: string | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
confirm: true;
|
|
16
|
+
comment_id: string;
|
|
17
|
+
workspace_id?: string | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
execute(client: PosterlyClient, input: {
|
|
20
|
+
comment_id: string;
|
|
21
|
+
workspace_id?: string;
|
|
22
|
+
confirm: true;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const deleteCommentTool = {
|
|
3
|
+
name: 'delete_comment',
|
|
4
|
+
description: 'Delete an Instagram or Facebook Page inbox comment. DESTRUCTIVE and IRREVERSIBLE. Threads replies cannot be deleted; hide them with update_comment instead. Requires a Pro plan or higher, posts:write, and admin workspace access. Fetch with get_comment first and pass confirm=true only after explicit user confirmation.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
comment_id: z.string().min(1).describe('Comment ID from list_comments or get_comment.'),
|
|
7
|
+
workspace_id: z.string().optional().describe('Workspace ID from whoami.'),
|
|
8
|
+
confirm: z.literal(true).describe('Must be true after explicit user confirmation of the comment deletion.'),
|
|
9
|
+
}),
|
|
10
|
+
async execute(client, input) {
|
|
11
|
+
if (input.confirm !== true) {
|
|
12
|
+
throw new Error('Refusing to delete comment without confirm=true after explicit user confirmation.');
|
|
13
|
+
}
|
|
14
|
+
await client.deleteComment(input.comment_id, input.workspace_id);
|
|
15
|
+
return [
|
|
16
|
+
`Comment ${input.comment_id} deleted successfully.`,
|
|
17
|
+
'It has been deleted from the posterly inbox. On Instagram or Facebook it is also removed on the network when the platform call succeeds.',
|
|
18
|
+
'',
|
|
19
|
+
'Next step for the AI agent: tell the user the comment is gone. Threads replies cannot be deleted; hide them with update_comment instead.',
|
|
20
|
+
].join('\n');
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const listMediaTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
drop_session_id: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
limit?: number | undefined;
|
|
11
|
+
drop_session_id?: string | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
limit?: number | undefined;
|
|
14
|
+
drop_session_id?: string | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
execute(client: PosterlyClient, input: {
|
|
17
|
+
limit?: number;
|
|
18
|
+
drop_session_id?: string;
|
|
19
|
+
}): Promise<string>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const listMediaTool = {
|
|
3
|
+
name: 'list_media',
|
|
4
|
+
description: 'List the newest media assets for the authenticated user. After create_media_drop, pass drop_session_id to see files the human uploaded on /drop/<token>. Use public_url with validate_post / create_post.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
limit: z
|
|
7
|
+
.number()
|
|
8
|
+
.int()
|
|
9
|
+
.min(1)
|
|
10
|
+
.max(50)
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('Number of assets to return (default 10, max 50)'),
|
|
13
|
+
drop_session_id: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Filter to files uploaded through one create_media_drop session'),
|
|
17
|
+
}),
|
|
18
|
+
async execute(client, input) {
|
|
19
|
+
const result = await client.listMedia({
|
|
20
|
+
limit: input.limit,
|
|
21
|
+
dropSessionId: input.drop_session_id,
|
|
22
|
+
});
|
|
23
|
+
if (result.media.length === 0) {
|
|
24
|
+
return 'No media found. If you just sent a drop URL, wait for the user to finish uploading, then call list_media again.';
|
|
25
|
+
}
|
|
26
|
+
const lines = result.media.map((item) => {
|
|
27
|
+
const size = typeof item.bytes === 'number' ? `${item.bytes} bytes` : 'unknown size';
|
|
28
|
+
const drop = item.drop_session_id ? ` drop=${item.drop_session_id}` : '';
|
|
29
|
+
return `• ${item.filename} (${item.mime || 'unknown'}, ${size})${drop}\n public_url: ${item.public_url}`;
|
|
30
|
+
});
|
|
31
|
+
return [
|
|
32
|
+
`Media (${result.media.length} of ${result.total}):`,
|
|
33
|
+
...lines,
|
|
34
|
+
'',
|
|
35
|
+
'Pass public_url to validate_post / create_post. Do not invent URLs.',
|
|
36
|
+
].join('\n');
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -9,12 +9,12 @@ export declare const uploadMediaFromUrlTool: {
|
|
|
9
9
|
content_type: z.ZodOptional<z.ZodString>;
|
|
10
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
11
|
url: string;
|
|
12
|
-
content_type?: string | undefined;
|
|
13
12
|
filename?: string | undefined;
|
|
13
|
+
content_type?: string | undefined;
|
|
14
14
|
}, {
|
|
15
15
|
url: string;
|
|
16
|
-
content_type?: string | undefined;
|
|
17
16
|
filename?: string | undefined;
|
|
17
|
+
content_type?: string | undefined;
|
|
18
18
|
}>;
|
|
19
19
|
execute(client: PosterlyClient, input: {
|
|
20
20
|
url: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posterly-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.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.39.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.39.0",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
},
|
package/src/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ import { listCommentsTool } from './tools/list-comments.js';
|
|
|
61
61
|
import { getCommentTool } from './tools/get-comment.js';
|
|
62
62
|
import { replyToCommentTool } from './tools/reply-to-comment.js';
|
|
63
63
|
import { updateCommentTool } from './tools/update-comment.js';
|
|
64
|
+
import { deleteCommentTool } from './tools/delete-comment.js';
|
|
64
65
|
import { syncInboxTool } from './tools/sync-inbox.js';
|
|
65
66
|
import { listActivityTool } from './tools/list-activity.js';
|
|
66
67
|
import { getUpdatesTool } from './tools/get-updates.js';
|
|
@@ -74,6 +75,8 @@ import { getPlatformSchemaTool } from './tools/get-platform-schema.js';
|
|
|
74
75
|
import { triggerPlatformHelperTool } from './tools/trigger-platform-helper.js';
|
|
75
76
|
import { getXPostingQuotaTool } from './tools/get-x-posting-quota.js';
|
|
76
77
|
import { createSignedUploadTool } from './tools/create-signed-upload.js';
|
|
78
|
+
import { createMediaDropTool } from './tools/create-media-drop.js';
|
|
79
|
+
import { listMediaTool } from './tools/list-media.js';
|
|
77
80
|
import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
|
|
78
81
|
import { getConnectLinkTool } from './tools/get-connect-link.js';
|
|
79
82
|
import { connectAccountTool } from './tools/connect-account.js';
|
|
@@ -775,6 +778,36 @@ server.tool(
|
|
|
775
778
|
}
|
|
776
779
|
);
|
|
777
780
|
|
|
781
|
+
server.tool(
|
|
782
|
+
createMediaDropTool.name,
|
|
783
|
+
createMediaDropTool.description,
|
|
784
|
+
createMediaDropTool.inputSchema.shape,
|
|
785
|
+
getToolAnnotations(createMediaDropTool.name),
|
|
786
|
+
async (input) => {
|
|
787
|
+
try {
|
|
788
|
+
const text = await createMediaDropTool.execute(client, input as any);
|
|
789
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
790
|
+
} catch (err: any) {
|
|
791
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
server.tool(
|
|
797
|
+
listMediaTool.name,
|
|
798
|
+
listMediaTool.description,
|
|
799
|
+
listMediaTool.inputSchema.shape,
|
|
800
|
+
getToolAnnotations(listMediaTool.name),
|
|
801
|
+
async (input) => {
|
|
802
|
+
try {
|
|
803
|
+
const text = await listMediaTool.execute(client, input as any);
|
|
804
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
805
|
+
} catch (err: any) {
|
|
806
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
);
|
|
810
|
+
|
|
778
811
|
server.tool(
|
|
779
812
|
getVideoOptionsTool.name,
|
|
780
813
|
getVideoOptionsTool.description,
|
|
@@ -1255,6 +1288,21 @@ server.tool(
|
|
|
1255
1288
|
}
|
|
1256
1289
|
);
|
|
1257
1290
|
|
|
1291
|
+
server.tool(
|
|
1292
|
+
deleteCommentTool.name,
|
|
1293
|
+
deleteCommentTool.description,
|
|
1294
|
+
deleteCommentTool.inputSchema.shape,
|
|
1295
|
+
getToolAnnotations(deleteCommentTool.name),
|
|
1296
|
+
async (input) => {
|
|
1297
|
+
try {
|
|
1298
|
+
const text = await deleteCommentTool.execute(client, input as any);
|
|
1299
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1300
|
+
} catch (err: any) {
|
|
1301
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1258
1306
|
server.tool(
|
|
1259
1307
|
syncInboxTool.name,
|
|
1260
1308
|
syncInboxTool.description,
|
package/src/lib/api-client.ts
CHANGED
|
@@ -1702,6 +1702,46 @@ export class PosterlyClient {
|
|
|
1702
1702
|
});
|
|
1703
1703
|
}
|
|
1704
1704
|
|
|
1705
|
+
async createMediaDrop(input?: {
|
|
1706
|
+
filename?: string;
|
|
1707
|
+
maxFiles?: number;
|
|
1708
|
+
}): Promise<{
|
|
1709
|
+
session_id: string;
|
|
1710
|
+
drop_url: string;
|
|
1711
|
+
expires_at: string;
|
|
1712
|
+
max_bytes: number;
|
|
1713
|
+
max_files: number;
|
|
1714
|
+
instructions: string;
|
|
1715
|
+
}> {
|
|
1716
|
+
return this.request('POST', '/media/drop-sessions', {
|
|
1717
|
+
filename: input?.filename,
|
|
1718
|
+
max_files: input?.maxFiles,
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
async listMedia(params?: {
|
|
1723
|
+
limit?: number;
|
|
1724
|
+
dropSessionId?: string;
|
|
1725
|
+
}): Promise<{
|
|
1726
|
+
media: Array<{
|
|
1727
|
+
id: string;
|
|
1728
|
+
public_url: string;
|
|
1729
|
+
filename: string;
|
|
1730
|
+
path: string;
|
|
1731
|
+
mime: string | null;
|
|
1732
|
+
bytes: number | null;
|
|
1733
|
+
created_at: string;
|
|
1734
|
+
drop_session_id: string | null;
|
|
1735
|
+
}>;
|
|
1736
|
+
total: number;
|
|
1737
|
+
}> {
|
|
1738
|
+
const searchParams = new URLSearchParams();
|
|
1739
|
+
if (params?.limit) searchParams.set('limit', String(params.limit));
|
|
1740
|
+
if (params?.dropSessionId) searchParams.set('drop_session_id', params.dropSessionId);
|
|
1741
|
+
const qs = searchParams.toString();
|
|
1742
|
+
return this.request('GET', `/media${qs ? `?${qs}` : ''}`);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1705
1745
|
async uploadMedia(input: {
|
|
1706
1746
|
filePath?: string;
|
|
1707
1747
|
base64Data?: string;
|
|
@@ -1862,6 +1902,13 @@ export class PosterlyClient {
|
|
|
1862
1902
|
return this.request('PATCH', `/inbox/comments/${encodeURIComponent(data.comment_id)}`, body);
|
|
1863
1903
|
}
|
|
1864
1904
|
|
|
1905
|
+
async deleteComment(id: string, workspaceId?: string): Promise<{ success: true }> {
|
|
1906
|
+
const searchParams = new URLSearchParams();
|
|
1907
|
+
if (workspaceId) searchParams.set('workspace_id', workspaceId);
|
|
1908
|
+
const qs = searchParams.toString();
|
|
1909
|
+
return this.request('DELETE', `/inbox/comments/${encodeURIComponent(id)}${qs ? `?${qs}` : ''}`);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1865
1912
|
async syncInbox(data: {
|
|
1866
1913
|
account_id: number;
|
|
1867
1914
|
sync_type?: 'all' | 'dm' | 'comments';
|
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.39.0';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const createMediaDropTool = {
|
|
5
|
+
name: 'create_media_drop',
|
|
6
|
+
description:
|
|
7
|
+
'Create a human drop page so ChatGPT or Claude users can upload a laptop file without dashboard login. Returns drop_url (https://www.poster.ly/drop/<token>). Send that URL to the user, wait, then call list_media. This is not the chat paperclip and not create_signed_upload.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
filename: z.string().optional().describe('Optional filename hint for the user, e.g. launch-video.mp4'),
|
|
10
|
+
max_files: z
|
|
11
|
+
.number()
|
|
12
|
+
.int()
|
|
13
|
+
.min(1)
|
|
14
|
+
.max(10)
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('How many files the drop can accept. Default 10, max 10.'),
|
|
17
|
+
}),
|
|
18
|
+
|
|
19
|
+
async execute(
|
|
20
|
+
client: PosterlyClient,
|
|
21
|
+
input: { filename?: string; max_files?: number },
|
|
22
|
+
) {
|
|
23
|
+
const drop = await client.createMediaDrop({
|
|
24
|
+
filename: input.filename,
|
|
25
|
+
maxFiles: input.max_files,
|
|
26
|
+
});
|
|
27
|
+
return [
|
|
28
|
+
'Media drop created. Send this URL to the user. They open it with no posterly login and drop the file.',
|
|
29
|
+
`• Drop URL: ${drop.drop_url}`,
|
|
30
|
+
`• Session ID: ${drop.session_id}`,
|
|
31
|
+
`• Expires: ${drop.expires_at}`,
|
|
32
|
+
`• Max bytes: ${drop.max_bytes}`,
|
|
33
|
+
`• Max files: ${drop.max_files}`,
|
|
34
|
+
drop.instructions,
|
|
35
|
+
'After they finish, call list_media with this drop_session_id, then validate_post / create_post using public_url.',
|
|
36
|
+
'Chat attachments never reach MCP. HEIC and PDF are rejected.',
|
|
37
|
+
].join('\n');
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const deleteCommentTool = {
|
|
5
|
+
name: 'delete_comment',
|
|
6
|
+
description:
|
|
7
|
+
'Delete an Instagram or Facebook Page inbox comment. DESTRUCTIVE and IRREVERSIBLE. Threads replies cannot be deleted; hide them with update_comment instead. Requires a Pro plan or higher, posts:write, and admin workspace access. Fetch with get_comment first and pass confirm=true only after explicit user confirmation.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
comment_id: z.string().min(1).describe('Comment ID from list_comments or get_comment.'),
|
|
10
|
+
workspace_id: z.string().optional().describe('Workspace ID from whoami.'),
|
|
11
|
+
confirm: z.literal(true).describe('Must be true after explicit user confirmation of the comment deletion.'),
|
|
12
|
+
}),
|
|
13
|
+
|
|
14
|
+
async execute(
|
|
15
|
+
client: PosterlyClient,
|
|
16
|
+
input: { comment_id: string; workspace_id?: string; confirm: true },
|
|
17
|
+
) {
|
|
18
|
+
if (input.confirm !== true) {
|
|
19
|
+
throw new Error('Refusing to delete comment without confirm=true after explicit user confirmation.');
|
|
20
|
+
}
|
|
21
|
+
await client.deleteComment(input.comment_id, input.workspace_id);
|
|
22
|
+
return [
|
|
23
|
+
`Comment ${input.comment_id} deleted successfully.`,
|
|
24
|
+
'It has been deleted from the posterly inbox. On Instagram or Facebook it is also removed on the network when the platform call succeeds.',
|
|
25
|
+
'',
|
|
26
|
+
'Next step for the AI agent: tell the user the comment is gone. Threads replies cannot be deleted; hide them with update_comment instead.',
|
|
27
|
+
].join('\n');
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const listMediaTool = {
|
|
5
|
+
name: 'list_media',
|
|
6
|
+
description:
|
|
7
|
+
'List the newest media assets for the authenticated user. After create_media_drop, pass drop_session_id to see files the human uploaded on /drop/<token>. Use public_url with validate_post / create_post.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
limit: z
|
|
10
|
+
.number()
|
|
11
|
+
.int()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(50)
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Number of assets to return (default 10, max 50)'),
|
|
16
|
+
drop_session_id: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Filter to files uploaded through one create_media_drop session'),
|
|
20
|
+
}),
|
|
21
|
+
|
|
22
|
+
async execute(
|
|
23
|
+
client: PosterlyClient,
|
|
24
|
+
input: { limit?: number; drop_session_id?: string },
|
|
25
|
+
) {
|
|
26
|
+
const result = await client.listMedia({
|
|
27
|
+
limit: input.limit,
|
|
28
|
+
dropSessionId: input.drop_session_id,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (result.media.length === 0) {
|
|
32
|
+
return 'No media found. If you just sent a drop URL, wait for the user to finish uploading, then call list_media again.';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lines = result.media.map((item) => {
|
|
36
|
+
const size = typeof item.bytes === 'number' ? `${item.bytes} bytes` : 'unknown size';
|
|
37
|
+
const drop = item.drop_session_id ? ` drop=${item.drop_session_id}` : '';
|
|
38
|
+
return `• ${item.filename} (${item.mime || 'unknown'}, ${size})${drop}\n public_url: ${item.public_url}`;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
`Media (${result.media.length} of ${result.total}):`,
|
|
43
|
+
...lines,
|
|
44
|
+
'',
|
|
45
|
+
'Pass public_url to validate_post / create_post. Do not invent URLs.',
|
|
46
|
+
].join('\n');
|
|
47
|
+
},
|
|
48
|
+
};
|
package/tool-annotations.json
CHANGED
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"upload_media",
|
|
46
46
|
"upload_media_from_url",
|
|
47
47
|
"create_signed_upload",
|
|
48
|
+
"create_media_drop",
|
|
49
|
+
"list_media",
|
|
48
50
|
"get_video_options",
|
|
49
51
|
"run_video_function",
|
|
50
52
|
"generate_video",
|
|
@@ -58,6 +60,7 @@
|
|
|
58
60
|
"update_post_status",
|
|
59
61
|
"update_post_release_id",
|
|
60
62
|
"delete_post",
|
|
63
|
+
"delete_comment",
|
|
61
64
|
"delete_post_group",
|
|
62
65
|
"get_account_analytics",
|
|
63
66
|
"get_post_analytics",
|
|
@@ -112,6 +115,7 @@
|
|
|
112
115
|
"validate_post",
|
|
113
116
|
"find_available_slot",
|
|
114
117
|
"list_posts",
|
|
118
|
+
"list_media",
|
|
115
119
|
"get_video_options",
|
|
116
120
|
"get_video_job",
|
|
117
121
|
"get_post",
|
|
@@ -143,6 +147,7 @@
|
|
|
143
147
|
"update_post",
|
|
144
148
|
"update_post_status",
|
|
145
149
|
"delete_post",
|
|
150
|
+
"delete_comment",
|
|
146
151
|
"delete_post_group",
|
|
147
152
|
"reply_google_business_review",
|
|
148
153
|
"delete_google_business_review_reply",
|
|
@@ -165,6 +170,7 @@
|
|
|
165
170
|
"update_post_status",
|
|
166
171
|
"update_post_release_id",
|
|
167
172
|
"delete_post",
|
|
173
|
+
"delete_comment",
|
|
168
174
|
"delete_post_group",
|
|
169
175
|
"reply_google_business_review",
|
|
170
176
|
"delete_google_business_review_reply",
|
|
@@ -193,6 +199,7 @@
|
|
|
193
199
|
"create_posts_batch",
|
|
194
200
|
"upload_media_from_url",
|
|
195
201
|
"create_signed_upload",
|
|
202
|
+
"create_media_drop",
|
|
196
203
|
"run_video_function",
|
|
197
204
|
"generate_video",
|
|
198
205
|
"get_video_job",
|
|
@@ -218,6 +225,7 @@
|
|
|
218
225
|
"send_message",
|
|
219
226
|
"reply_to_comment",
|
|
220
227
|
"update_comment",
|
|
228
|
+
"delete_comment",
|
|
221
229
|
"sync_inbox"
|
|
222
230
|
]
|
|
223
231
|
}
|