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 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.37.0` exposes 86 tools.
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);
@@ -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';
@@ -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
  }
@@ -1 +1 @@
1
- export declare const POSTERLY_MCP_VERSION = "0.37.0";
1
+ export declare const POSTERLY_MCP_VERSION = "0.39.0";
@@ -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.37.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.37.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.37.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.37.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,
@@ -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';
@@ -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.37.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
+ };
@@ -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
  }