postproxy-mcp 1.0.1 → 1.1.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.
@@ -21,6 +21,20 @@ export interface Profile {
21
21
  avatar_url?: string;
22
22
  }
23
23
 
24
+ export interface ThreadChild {
25
+ body: string;
26
+ media?: string[];
27
+ }
28
+
29
+ export interface MediaAttachment {
30
+ id: string;
31
+ status: "pending" | "processed" | "failed";
32
+ error_message: string | null;
33
+ content_type: string;
34
+ source_url: string | null;
35
+ url: string | null;
36
+ }
37
+
24
38
  export interface CreatePostParams {
25
39
  content: string;
26
40
  profile_group_id?: number; // Not used by API, kept for compatibility
@@ -30,17 +44,20 @@ export interface CreatePostParams {
30
44
  idempotency_key?: string;
31
45
  draft?: boolean; // If true, creates a draft post that won't publish automatically
32
46
  platforms?: PlatformParams; // Platform-specific parameters
47
+ thread?: ThreadChild[]; // Thread posts (supported on X and Threads)
33
48
  }
34
49
 
35
50
  export interface CreatePostResponse {
36
51
  id: string;
37
52
  body?: string; // API returns "body" field
38
53
  content?: string; // Some responses use "content"
39
- status: "draft" | "pending" | "processing" | "processed" | "scheduled";
54
+ status: "draft" | "pending" | "processing" | "processed" | "scheduled" | "media_processing_failed";
40
55
  draft: boolean;
41
56
  scheduled_at: string | null;
42
57
  created_at: string;
58
+ media?: MediaAttachment[];
43
59
  platforms: PlatformOutcome[];
60
+ thread?: Array<{ id: string; body: string; media?: MediaAttachment[] }>;
44
61
  }
45
62
 
46
63
  export interface PlatformOutcome {
@@ -62,24 +79,28 @@ export interface PostDetails {
62
79
  id: string;
63
80
  body?: string; // API returns "body" field
64
81
  content?: string; // Some responses use "content"
65
- status: "draft" | "pending" | "processing" | "processed" | "scheduled";
82
+ status: "draft" | "pending" | "processing" | "processed" | "scheduled" | "media_processing_failed";
66
83
  draft: boolean;
67
84
  scheduled_at: string | null;
68
85
  created_at: string;
69
86
  updated_at?: string;
87
+ media?: MediaAttachment[];
70
88
  platforms: PlatformOutcome[];
89
+ thread?: Array<{ id: string; body: string; media?: MediaAttachment[] }>;
71
90
  }
72
91
 
73
92
  export interface Post {
74
93
  id: string;
75
94
  body?: string; // API returns "body" field
76
95
  content?: string; // Some responses use "content"
77
- status: "draft" | "pending" | "processing" | "processed" | "scheduled";
96
+ status: "draft" | "pending" | "processing" | "processed" | "scheduled" | "media_processing_failed";
78
97
  draft: boolean;
79
98
  scheduled_at: string | null;
80
99
  created_at: string;
81
100
  updated_at?: string;
101
+ media?: MediaAttachment[];
82
102
  platforms: PlatformOutcome[];
103
+ thread?: Array<{ id: string; body: string; media?: MediaAttachment[] }>;
83
104
  }
84
105
 
85
106
  /**
@@ -102,21 +123,23 @@ export interface YouTubeParams {
102
123
  title?: string; // Video title
103
124
  privacy_status?: "public" | "unlisted" | "private"; // Video visibility
104
125
  cover_url?: string; // Custom thumbnail URL
126
+ made_for_kids?: boolean; // Whether the video is made for kids
105
127
  }
106
128
 
107
129
  /**
108
130
  * Platform-specific parameters for TikTok
109
131
  */
110
132
  export interface TikTokParams {
133
+ format?: "video" | "image"; // Content format (video is default)
111
134
  privacy_status?: "PUBLIC_TO_EVERYONE" | "MUTUAL_FOLLOW_FRIENDS" | "FOLLOWER_OF_CREATOR" | "SELF_ONLY";
112
- photo_cover_index?: number; // Index (0-based) of photo to use as cover
113
- auto_add_music?: boolean; // Enable automatic music
114
- made_with_ai?: boolean; // Mark content as AI-generated
115
- disable_comment?: boolean; // Disable comments on the post
116
- disable_duet?: boolean; // Disable duets
117
- disable_stitch?: boolean; // Disable stitches
118
- brand_content_toggle?: boolean; // Mark video as paid partnership promoting a third-party business
119
- brand_organic_toggle?: boolean; // Mark video as paid partnership promoting your own brand
135
+ photo_cover_index?: number; // Index (0-based) of photo to use as cover (image format)
136
+ auto_add_music?: boolean; // Enable automatic music (image format)
137
+ made_with_ai?: boolean; // Mark content as AI-generated (video format)
138
+ disable_comment?: boolean; // Disable comments
139
+ disable_duet?: boolean; // Disable duets (video format)
140
+ disable_stitch?: boolean; // Disable stitches (video format)
141
+ brand_content_toggle?: boolean; // Mark as paid partnership promoting a third-party business
142
+ brand_organic_toggle?: boolean; // Mark as paid partnership promoting your own brand
120
143
  }
121
144
 
122
145
  /**
@@ -89,10 +89,14 @@ export const YouTubeParamsSchema = z.object({
89
89
  errorMap: () => ({ message: "YouTube privacy_status must be 'public', 'unlisted', or 'private'" }),
90
90
  }).optional(),
91
91
  cover_url: URLSchema.optional(),
92
+ made_for_kids: z.boolean().optional(),
92
93
  }).strict();
93
94
 
94
95
  // TikTok parameters validation
95
96
  export const TikTokParamsSchema = z.object({
97
+ format: z.enum(["video", "image"], {
98
+ errorMap: () => ({ message: "TikTok format must be 'video' or 'image'" }),
99
+ }).optional(),
96
100
  privacy_status: z.enum([
97
101
  "PUBLIC_TO_EVERYONE",
98
102
  "MUTUAL_FOLLOW_FRIENDS",
@@ -142,6 +146,14 @@ export const PlatformParamsSchema = z.object({
142
146
  threads: ThreadsParamsSchema.optional(),
143
147
  }).strict().optional();
144
148
 
149
+ /**
150
+ * Schema for thread child posts
151
+ */
152
+ export const ThreadChildSchema = z.object({
153
+ body: z.string().min(1, "Thread child body cannot be empty"),
154
+ media: z.array(MediaItemSchema).optional(),
155
+ });
156
+
145
157
  /**
146
158
  * Schema for post.publish parameters
147
159
  */
@@ -154,6 +166,7 @@ export const PostPublishSchema = z.object({
154
166
  require_confirmation: z.boolean().optional(),
155
167
  draft: z.boolean().optional(),
156
168
  platforms: PlatformParamsSchema,
169
+ thread: z.array(ThreadChildSchema).optional(),
157
170
  });
158
171
 
159
172
  /**
package/worker/index.ts CHANGED
@@ -36,15 +36,26 @@ interface PlatformOutcome {
36
36
  insights?: any;
37
37
  }
38
38
 
39
+ interface MediaAttachment {
40
+ id: string;
41
+ status: "pending" | "processed" | "failed";
42
+ error_message: string | null;
43
+ content_type: string;
44
+ source_url: string | null;
45
+ url: string | null;
46
+ }
47
+
39
48
  interface Post {
40
49
  id: string;
41
50
  body?: string;
42
51
  content?: string;
43
- status: "draft" | "pending" | "processing" | "processed" | "scheduled";
52
+ status: "draft" | "pending" | "processing" | "processed" | "scheduled" | "media_processing_failed";
44
53
  draft: boolean;
45
54
  scheduled_at: string | null;
46
55
  created_at: string;
56
+ media?: MediaAttachment[];
47
57
  platforms: PlatformOutcome[];
58
+ thread?: Array<{ id: string; body: string; media?: MediaAttachment[] }>;
48
59
  }
49
60
 
50
61
  export default class PostProxyMCP extends WorkerEntrypoint<Env> {
@@ -177,7 +188,8 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
177
188
  schedule?: string,
178
189
  draft?: boolean,
179
190
  platformParams?: Record<string, Record<string, any>>,
180
- idempotencyKey?: string
191
+ idempotencyKey?: string,
192
+ threadChildren?: Array<{ body: string; media?: string[] }>
181
193
  ): Promise<any> {
182
194
  const baseUrl = this.env.POSTPROXY_BASE_URL.replace(/\/$/, "");
183
195
  const url = `${baseUrl}/posts`;
@@ -224,6 +236,11 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
224
236
  formData.append("platforms", JSON.stringify(platformParams));
225
237
  }
226
238
 
239
+ // Add thread children
240
+ if (threadChildren && threadChildren.length > 0) {
241
+ formData.append("thread", JSON.stringify(threadChildren));
242
+ }
243
+
227
244
  // Build headers
228
245
  const headers: Record<string, string> = {
229
246
  Authorization: `Bearer ${this.getApiKey()}`,
@@ -290,7 +307,10 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
290
307
  */
291
308
  private determineOverallStatus(
292
309
  post: Post
293
- ): "pending" | "processing" | "complete" | "failed" | "draft" {
310
+ ): "pending" | "processing" | "complete" | "failed" | "draft" | "media_processing_failed" {
311
+ if (post.status === "media_processing_failed") {
312
+ return "media_processing_failed";
313
+ }
294
314
  if (post.status === "draft" || post.draft === true) {
295
315
  return "draft";
296
316
  }
@@ -382,6 +402,7 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
382
402
  * @param draft {boolean} If true, creates a draft post that won't publish automatically
383
403
  * @param platforms {string} Optional JSON string of platform-specific parameters
384
404
  * @param media_files {string} Optional JSON array of file objects with {filename, data (base64), content_type?}
405
+ * @param thread {string} Optional JSON array of thread child posts [{body, media?}]. Supported on X and Threads only.
385
406
  * @return {Promise<string>} Post creation result as JSON
386
407
  */
387
408
  async postPublish(
@@ -393,7 +414,8 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
393
414
  require_confirmation?: boolean,
394
415
  draft?: boolean,
395
416
  platforms?: string,
396
- media_files?: string
417
+ media_files?: string,
418
+ thread?: string
397
419
  ): Promise<string> {
398
420
  this.getApiKey(); // Validate API key is present
399
421
 
@@ -432,6 +454,27 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
432
454
  }
433
455
  }
434
456
 
457
+ // Parse thread JSON if provided
458
+ let threadChildren: Array<{ body: string; media?: string[] }> | undefined;
459
+ if (thread) {
460
+ try {
461
+ threadChildren = JSON.parse(thread);
462
+ if (!Array.isArray(threadChildren)) {
463
+ throw new Error("thread must be an array");
464
+ }
465
+ for (const child of threadChildren) {
466
+ if (!child.body) {
467
+ throw new Error("Each thread child must have a 'body' property");
468
+ }
469
+ }
470
+ } catch (e: any) {
471
+ if (e.message.includes("thread")) {
472
+ throw e;
473
+ }
474
+ throw new Error("Invalid thread parameter: must be valid JSON array");
475
+ }
476
+ }
477
+
435
478
  // Validate input
436
479
  if (!content || content.trim() === "") {
437
480
  throw new Error("Content cannot be empty");
@@ -452,6 +495,7 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
452
495
  schedule_time: schedule,
453
496
  draft: draft || false,
454
497
  platforms: platformParams || {},
498
+ thread: threadChildren || [],
455
499
  },
456
500
  },
457
501
  null,
@@ -503,7 +547,8 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
503
547
  schedule,
504
548
  draft,
505
549
  platformParams,
506
- finalIdempotencyKey
550
+ finalIdempotencyKey,
551
+ threadChildren
507
552
  );
508
553
  } else {
509
554
  // Create post with JSON (URLs only)
@@ -527,6 +572,10 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
527
572
  apiPayload.platforms = platformParams;
528
573
  }
529
574
 
575
+ if (threadChildren && threadChildren.length > 0) {
576
+ apiPayload.thread = threadChildren;
577
+ }
578
+
530
579
  const extraHeaders: Record<string, string> = {
531
580
  "Idempotency-Key": finalIdempotencyKey,
532
581
  };
@@ -579,17 +628,23 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
579
628
 
580
629
  const overallStatus = this.determineOverallStatus(postDetails);
581
630
 
582
- return JSON.stringify(
583
- {
584
- job_id: job_id,
585
- overall_status: overallStatus,
586
- draft: postDetails.draft || false,
587
- status: postDetails.status,
588
- platforms,
589
- },
590
- null,
591
- 2
592
- );
631
+ const result: any = {
632
+ job_id: job_id,
633
+ overall_status: overallStatus,
634
+ draft: postDetails.draft || false,
635
+ status: postDetails.status,
636
+ platforms,
637
+ };
638
+
639
+ if (postDetails.media && postDetails.media.length > 0) {
640
+ result.media = postDetails.media;
641
+ }
642
+
643
+ if (postDetails.thread && postDetails.thread.length > 0) {
644
+ result.thread = postDetails.thread;
645
+ }
646
+
647
+ return JSON.stringify(result, null, 2);
593
648
  }
594
649
 
595
650
  /**
@@ -748,7 +803,7 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
748
803
  },
749
804
  {
750
805
  name: "postPublish",
751
- description: "Publish a post to specified targets",
806
+ description: "Publish a post to specified targets. Supports threads (X and Threads only).",
752
807
  inputSchema: {
753
808
  type: "object",
754
809
  properties: {
@@ -759,8 +814,9 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
759
814
  idempotency_key: { type: "string", description: "Optional idempotency key for deduplication" },
760
815
  require_confirmation: { type: "boolean", description: "If true, return summary without publishing" },
761
816
  draft: { type: "boolean", description: "If true, creates a draft post" },
762
- platforms: { type: "string", description: "Optional JSON string of platform-specific parameters" },
817
+ platforms: { type: "string", description: "Optional JSON string of platform-specific parameters. YouTube supports: title, privacy_status, cover_url, made_for_kids. TikTok supports: format (video|image), privacy_status, and more." },
763
818
  media_files: { type: "string", description: "Optional JSON array of file objects for direct upload. Each object must have 'filename' and 'data' (base64-encoded file content), optionally 'content_type'. Example: [{\"filename\":\"photo.jpg\",\"data\":\"base64...\"}]" },
819
+ thread: { type: "string", description: "Optional JSON array of thread child posts. Supported on X and Threads only. Each object must have 'body' (string), optionally 'media' (array of URLs). Example: [{\"body\":\"Reply 1\"},{\"body\":\"Reply 2\",\"media\":[\"https://...\"]}]" },
764
820
  },
765
821
  required: ["content", "targets"],
766
822
  },
@@ -890,7 +946,8 @@ export default class PostProxyMCP extends WorkerEntrypoint<Env> {
890
946
  args.require_confirmation,
891
947
  args.draft,
892
948
  args.platforms,
893
- args.media_files
949
+ args.media_files,
950
+ args.thread
894
951
  );
895
952
  break;
896
953
  case "postStatus":