postproxy-mcp 1.0.2 → 1.2.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/dist/api/client.d.ts +27 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +99 -0
- package/dist/api/client.js.map +1 -1
- package/dist/server.d.ts +192 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +197 -6
- package/dist/server.js.map +1 -1
- package/dist/tools/history.d.ts.map +1 -1
- package/dist/tools/history.js +6 -2
- package/dist/tools/history.js.map +1 -1
- package/dist/tools/post.d.ts +6 -0
- package/dist/tools/post.d.ts.map +1 -1
- package/dist/tools/post.js +22 -8
- package/dist/tools/post.js.map +1 -1
- package/dist/tools/queue.d.ts +73 -0
- package/dist/tools/queue.d.ts.map +1 -0
- package/dist/tools/queue.js +167 -0
- package/dist/tools/queue.js.map +1 -0
- package/dist/types/index.d.ts +91 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/validation.d.ts +63 -2
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +12 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +1 -1
- package/src/api/client.ts +112 -0
- package/src/server.ts +204 -6
- package/src/tools/history.ts +5 -2
- package/src/tools/post.ts +29 -14
- package/src/tools/queue.ts +230 -0
- package/src/types/index.ts +84 -11
- package/src/utils/validation.ts +13 -0
- package/worker/index.ts +366 -19
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue tools: queues.list, queues.get, queues.create, queues.update, queues.delete, queues.next_slot
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostProxyClient } from "../api/client.js";
|
|
6
|
+
import { createError, ErrorCodes } from "../utils/errors.js";
|
|
7
|
+
import { logError } from "../utils/logger.js";
|
|
8
|
+
|
|
9
|
+
const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
10
|
+
|
|
11
|
+
function formatTimeslots(timeslots: Array<{ id: number; day: number; time: string }>): string[] {
|
|
12
|
+
return timeslots.map((ts) => `${DAY_NAMES[ts.day]} at ${ts.time} (id: ${ts.id})`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function handleQueuesList(
|
|
16
|
+
client: PostProxyClient,
|
|
17
|
+
args: { profile_group_id?: string }
|
|
18
|
+
) {
|
|
19
|
+
try {
|
|
20
|
+
const queues = await client.listQueues(args.profile_group_id);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: JSON.stringify(
|
|
27
|
+
{
|
|
28
|
+
queues: queues.map((q) => ({
|
|
29
|
+
id: q.id,
|
|
30
|
+
name: q.name,
|
|
31
|
+
description: q.description,
|
|
32
|
+
timezone: q.timezone,
|
|
33
|
+
enabled: q.enabled,
|
|
34
|
+
jitter: q.jitter,
|
|
35
|
+
profile_group_id: q.profile_group_id,
|
|
36
|
+
timeslots: formatTimeslots(q.timeslots),
|
|
37
|
+
posts_count: q.posts_count,
|
|
38
|
+
})),
|
|
39
|
+
},
|
|
40
|
+
null,
|
|
41
|
+
2
|
|
42
|
+
),
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logError(error as Error, "queues.list");
|
|
48
|
+
throw createError(ErrorCodes.API_ERROR, `Failed to list queues: ${(error as Error).message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function handleQueuesGet(
|
|
53
|
+
client: PostProxyClient,
|
|
54
|
+
args: { queue_id: string }
|
|
55
|
+
) {
|
|
56
|
+
if (!args.queue_id) {
|
|
57
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "queue_id is required");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const queue = await client.getQueue(args.queue_id);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: JSON.stringify(
|
|
68
|
+
{
|
|
69
|
+
...queue,
|
|
70
|
+
timeslots_formatted: formatTimeslots(queue.timeslots),
|
|
71
|
+
},
|
|
72
|
+
null,
|
|
73
|
+
2
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logError(error as Error, "queues.get");
|
|
80
|
+
throw createError(ErrorCodes.API_ERROR, `Failed to get queue: ${(error as Error).message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function handleQueuesCreate(
|
|
85
|
+
client: PostProxyClient,
|
|
86
|
+
args: {
|
|
87
|
+
profile_group_id: string;
|
|
88
|
+
name: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
timezone?: string;
|
|
91
|
+
jitter?: number;
|
|
92
|
+
timeslots?: Array<{ day: number; time: string }>;
|
|
93
|
+
}
|
|
94
|
+
) {
|
|
95
|
+
if (!args.profile_group_id) {
|
|
96
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "profile_group_id is required");
|
|
97
|
+
}
|
|
98
|
+
if (!args.name) {
|
|
99
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "name is required");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const queue = await client.createQueue({
|
|
104
|
+
profile_group_id: args.profile_group_id,
|
|
105
|
+
name: args.name,
|
|
106
|
+
description: args.description,
|
|
107
|
+
timezone: args.timezone,
|
|
108
|
+
jitter: args.jitter,
|
|
109
|
+
timeslots: args.timeslots,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
...queue,
|
|
119
|
+
timeslots_formatted: formatTimeslots(queue.timeslots),
|
|
120
|
+
message: "Queue created successfully",
|
|
121
|
+
},
|
|
122
|
+
null,
|
|
123
|
+
2
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logError(error as Error, "queues.create");
|
|
130
|
+
throw createError(ErrorCodes.API_ERROR, `Failed to create queue: ${(error as Error).message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function handleQueuesUpdate(
|
|
135
|
+
client: PostProxyClient,
|
|
136
|
+
args: {
|
|
137
|
+
queue_id: string;
|
|
138
|
+
name?: string;
|
|
139
|
+
description?: string;
|
|
140
|
+
timezone?: string;
|
|
141
|
+
enabled?: boolean;
|
|
142
|
+
jitter?: number;
|
|
143
|
+
timeslots?: Array<{ day: number; time: string } | { id: number; _destroy: true }>;
|
|
144
|
+
}
|
|
145
|
+
) {
|
|
146
|
+
if (!args.queue_id) {
|
|
147
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "queue_id is required");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const queue = await client.updateQueue(args.queue_id, {
|
|
152
|
+
name: args.name,
|
|
153
|
+
description: args.description,
|
|
154
|
+
timezone: args.timezone,
|
|
155
|
+
enabled: args.enabled,
|
|
156
|
+
jitter: args.jitter,
|
|
157
|
+
timeslots: args.timeslots,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: JSON.stringify(
|
|
165
|
+
{
|
|
166
|
+
...queue,
|
|
167
|
+
timeslots_formatted: formatTimeslots(queue.timeslots),
|
|
168
|
+
message: "Queue updated successfully",
|
|
169
|
+
},
|
|
170
|
+
null,
|
|
171
|
+
2
|
|
172
|
+
),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
} catch (error) {
|
|
177
|
+
logError(error as Error, "queues.update");
|
|
178
|
+
throw createError(ErrorCodes.API_ERROR, `Failed to update queue: ${(error as Error).message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function handleQueuesDelete(
|
|
183
|
+
client: PostProxyClient,
|
|
184
|
+
args: { queue_id: string }
|
|
185
|
+
) {
|
|
186
|
+
if (!args.queue_id) {
|
|
187
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "queue_id is required");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
await client.deleteQueue(args.queue_id);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
content: [
|
|
195
|
+
{
|
|
196
|
+
type: "text",
|
|
197
|
+
text: JSON.stringify({ queue_id: args.queue_id, deleted: true }, null, 2),
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
logError(error as Error, "queues.delete");
|
|
203
|
+
throw createError(ErrorCodes.API_ERROR, `Failed to delete queue: ${(error as Error).message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function handleQueuesNextSlot(
|
|
208
|
+
client: PostProxyClient,
|
|
209
|
+
args: { queue_id: string }
|
|
210
|
+
) {
|
|
211
|
+
if (!args.queue_id) {
|
|
212
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "queue_id is required");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const result = await client.getQueueNextSlot(args.queue_id);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
content: [
|
|
220
|
+
{
|
|
221
|
+
type: "text",
|
|
222
|
+
text: JSON.stringify(result, null, 2),
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
} catch (error) {
|
|
227
|
+
logError(error as Error, "queues.next_slot");
|
|
228
|
+
throw createError(ErrorCodes.API_ERROR, `Failed to get next slot: ${(error as Error).message}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -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,22 @@ 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)
|
|
48
|
+
queue_id?: string; // Queue ID to add post to
|
|
49
|
+
queue_priority?: "high" | "medium" | "low"; // Queue priority (default: medium)
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
export interface CreatePostResponse {
|
|
36
53
|
id: string;
|
|
37
54
|
body?: string; // API returns "body" field
|
|
38
55
|
content?: string; // Some responses use "content"
|
|
39
|
-
status: "draft" | "pending" | "processing" | "processed" | "scheduled";
|
|
56
|
+
status: "draft" | "pending" | "processing" | "processed" | "scheduled" | "media_processing_failed";
|
|
40
57
|
draft: boolean;
|
|
41
58
|
scheduled_at: string | null;
|
|
42
59
|
created_at: string;
|
|
60
|
+
media?: MediaAttachment[];
|
|
43
61
|
platforms: PlatformOutcome[];
|
|
62
|
+
thread?: Array<{ id: string; body: string; media?: MediaAttachment[] }>;
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
export interface PlatformOutcome {
|
|
@@ -62,24 +81,28 @@ export interface PostDetails {
|
|
|
62
81
|
id: string;
|
|
63
82
|
body?: string; // API returns "body" field
|
|
64
83
|
content?: string; // Some responses use "content"
|
|
65
|
-
status: "draft" | "pending" | "processing" | "processed" | "scheduled";
|
|
84
|
+
status: "draft" | "pending" | "processing" | "processed" | "scheduled" | "media_processing_failed";
|
|
66
85
|
draft: boolean;
|
|
67
86
|
scheduled_at: string | null;
|
|
68
87
|
created_at: string;
|
|
69
88
|
updated_at?: string;
|
|
89
|
+
media?: MediaAttachment[];
|
|
70
90
|
platforms: PlatformOutcome[];
|
|
91
|
+
thread?: Array<{ id: string; body: string; media?: MediaAttachment[] }>;
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
export interface Post {
|
|
74
95
|
id: string;
|
|
75
96
|
body?: string; // API returns "body" field
|
|
76
97
|
content?: string; // Some responses use "content"
|
|
77
|
-
status: "draft" | "pending" | "processing" | "processed" | "scheduled";
|
|
98
|
+
status: "draft" | "pending" | "processing" | "processed" | "scheduled" | "media_processing_failed";
|
|
78
99
|
draft: boolean;
|
|
79
100
|
scheduled_at: string | null;
|
|
80
101
|
created_at: string;
|
|
81
102
|
updated_at?: string;
|
|
103
|
+
media?: MediaAttachment[];
|
|
82
104
|
platforms: PlatformOutcome[];
|
|
105
|
+
thread?: Array<{ id: string; body: string; media?: MediaAttachment[] }>;
|
|
83
106
|
}
|
|
84
107
|
|
|
85
108
|
/**
|
|
@@ -102,21 +125,23 @@ export interface YouTubeParams {
|
|
|
102
125
|
title?: string; // Video title
|
|
103
126
|
privacy_status?: "public" | "unlisted" | "private"; // Video visibility
|
|
104
127
|
cover_url?: string; // Custom thumbnail URL
|
|
128
|
+
made_for_kids?: boolean; // Whether the video is made for kids
|
|
105
129
|
}
|
|
106
130
|
|
|
107
131
|
/**
|
|
108
132
|
* Platform-specific parameters for TikTok
|
|
109
133
|
*/
|
|
110
134
|
export interface TikTokParams {
|
|
135
|
+
format?: "video" | "image"; // Content format (video is default)
|
|
111
136
|
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
|
|
116
|
-
disable_duet?: boolean; // Disable duets
|
|
117
|
-
disable_stitch?: boolean; // Disable stitches
|
|
118
|
-
brand_content_toggle?: boolean; // Mark
|
|
119
|
-
brand_organic_toggle?: boolean; // Mark
|
|
137
|
+
photo_cover_index?: number; // Index (0-based) of photo to use as cover (image format)
|
|
138
|
+
auto_add_music?: boolean; // Enable automatic music (image format)
|
|
139
|
+
made_with_ai?: boolean; // Mark content as AI-generated (video format)
|
|
140
|
+
disable_comment?: boolean; // Disable comments
|
|
141
|
+
disable_duet?: boolean; // Disable duets (video format)
|
|
142
|
+
disable_stitch?: boolean; // Disable stitches (video format)
|
|
143
|
+
brand_content_toggle?: boolean; // Mark as paid partnership promoting a third-party business
|
|
144
|
+
brand_organic_toggle?: boolean; // Mark as paid partnership promoting your own brand
|
|
120
145
|
}
|
|
121
146
|
|
|
122
147
|
/**
|
|
@@ -198,3 +223,51 @@ export interface PostStats {
|
|
|
198
223
|
export interface StatsResponse {
|
|
199
224
|
data: Record<string, PostStats>;
|
|
200
225
|
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Queue timeslot definition
|
|
229
|
+
*/
|
|
230
|
+
export interface QueueTimeslot {
|
|
231
|
+
id: number;
|
|
232
|
+
day: number; // 0=Sunday through 6=Saturday
|
|
233
|
+
time: string; // HH:MM format
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Post queue
|
|
238
|
+
*/
|
|
239
|
+
export interface PostQueue {
|
|
240
|
+
id: string;
|
|
241
|
+
name: string;
|
|
242
|
+
description: string | null;
|
|
243
|
+
timezone: string;
|
|
244
|
+
enabled: boolean;
|
|
245
|
+
jitter: number;
|
|
246
|
+
profile_group_id: string;
|
|
247
|
+
timeslots: QueueTimeslot[];
|
|
248
|
+
posts_count: number;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Parameters for creating a queue
|
|
253
|
+
*/
|
|
254
|
+
export interface CreateQueueParams {
|
|
255
|
+
profile_group_id: string;
|
|
256
|
+
name: string;
|
|
257
|
+
description?: string;
|
|
258
|
+
timezone?: string;
|
|
259
|
+
jitter?: number;
|
|
260
|
+
timeslots?: Array<{ day: number; time: string }>;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Parameters for updating a queue
|
|
265
|
+
*/
|
|
266
|
+
export interface UpdateQueueParams {
|
|
267
|
+
name?: string;
|
|
268
|
+
description?: string;
|
|
269
|
+
timezone?: string;
|
|
270
|
+
enabled?: boolean;
|
|
271
|
+
jitter?: number;
|
|
272
|
+
timeslots?: Array<{ day: number; time: string } | { id: number; _destroy: true }>;
|
|
273
|
+
}
|
package/src/utils/validation.ts
CHANGED
|
@@ -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
|
/**
|