posterly-mcp-server 0.38.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.38.0` exposes 87 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`
package/dist/index.js CHANGED
@@ -74,6 +74,8 @@ import { getPlatformSchemaTool } from './tools/get-platform-schema.js';
74
74
  import { triggerPlatformHelperTool } from './tools/trigger-platform-helper.js';
75
75
  import { getXPostingQuotaTool } from './tools/get-x-posting-quota.js';
76
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';
77
79
  import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
78
80
  import { getConnectLinkTool } from './tools/get-connect-link.js';
79
81
  import { connectAccountTool } from './tools/connect-account.js';
@@ -502,6 +504,24 @@ server.tool(createSignedUploadTool.name, createSignedUploadTool.description, cre
502
504
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
503
505
  }
504
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
+ });
505
525
  server.tool(getVideoOptionsTool.name, getVideoOptionsTool.description, getVideoOptionsTool.inputSchema.shape, getToolAnnotations(getVideoOptionsTool.name), async () => {
506
526
  try {
507
527
  const text = await getVideoOptionsTool.execute(client);
@@ -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;
@@ -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;
@@ -1 +1 @@
1
- export declare const POSTERLY_MCP_VERSION = "0.38.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.38.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,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.38.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.38.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.38.0",
17
+ "version": "0.39.0",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },
package/src/index.ts CHANGED
@@ -75,6 +75,8 @@ import { getPlatformSchemaTool } from './tools/get-platform-schema.js';
75
75
  import { triggerPlatformHelperTool } from './tools/trigger-platform-helper.js';
76
76
  import { getXPostingQuotaTool } from './tools/get-x-posting-quota.js';
77
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';
78
80
  import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
79
81
  import { getConnectLinkTool } from './tools/get-connect-link.js';
80
82
  import { connectAccountTool } from './tools/connect-account.js';
@@ -776,6 +778,36 @@ server.tool(
776
778
  }
777
779
  );
778
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
+
779
811
  server.tool(
780
812
  getVideoOptionsTool.name,
781
813
  getVideoOptionsTool.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;
@@ -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.38.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,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",
@@ -113,6 +115,7 @@
113
115
  "validate_post",
114
116
  "find_available_slot",
115
117
  "list_posts",
118
+ "list_media",
116
119
  "get_video_options",
117
120
  "get_video_job",
118
121
  "get_post",
@@ -196,6 +199,7 @@
196
199
  "create_posts_batch",
197
200
  "upload_media_from_url",
198
201
  "create_signed_upload",
202
+ "create_media_drop",
199
203
  "run_video_function",
200
204
  "generate_video",
201
205
  "get_video_job",