postproxy-mcp 0.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.
- package/LICENSE +21 -0
- package/README.md +635 -0
- package/dist/api/client.d.ts +71 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +432 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth/credentials.d.ts +19 -0
- package/dist/auth/credentials.d.ts.map +1 -0
- package/dist/auth/credentials.js +40 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +162 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +220 -0
- package/dist/server.js.map +1 -0
- package/dist/setup-cli.d.ts +6 -0
- package/dist/setup-cli.d.ts.map +1 -0
- package/dist/setup-cli.js +10 -0
- package/dist/setup-cli.js.map +1 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +143 -0
- package/dist/setup.js.map +1 -0
- package/dist/tools/accounts.d.ts +11 -0
- package/dist/tools/accounts.d.ts.map +1 -0
- package/dist/tools/accounts.js +53 -0
- package/dist/tools/accounts.js.map +1 -0
- package/dist/tools/auth.d.ts +11 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +35 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/history.d.ts +13 -0
- package/dist/tools/history.d.ts.map +1 -0
- package/dist/tools/history.js +79 -0
- package/dist/tools/history.js.map +1 -0
- package/dist/tools/post.d.ts +44 -0
- package/dist/tools/post.d.ts.map +1 -0
- package/dist/tools/post.js +251 -0
- package/dist/tools/post.js.map +1 -0
- package/dist/tools/profiles.d.ts +11 -0
- package/dist/tools/profiles.d.ts.map +1 -0
- package/dist/tools/profiles.js +52 -0
- package/dist/tools/profiles.js.map +1 -0
- package/dist/types/index.d.ts +147 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/errors.d.ts +21 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +33 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/idempotency.d.ts +8 -0
- package/dist/utils/idempotency.d.ts.map +1 -0
- package/dist/utils/idempotency.js +23 -0
- package/dist/utils/idempotency.js.map +1 -0
- package/dist/utils/logger.d.ts +20 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +68 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/validation.d.ts +555 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +145 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +39 -0
- package/src/api/client.ts +497 -0
- package/src/auth/credentials.ts +43 -0
- package/src/index.ts +57 -0
- package/src/server.ts +235 -0
- package/src/setup-cli.ts +11 -0
- package/src/setup.ts +187 -0
- package/src/tools/auth.ts +45 -0
- package/src/tools/history.ts +89 -0
- package/src/tools/post.ts +338 -0
- package/src/tools/profiles.ts +69 -0
- package/src/types/index.ts +161 -0
- package/src/utils/errors.ts +38 -0
- package/src/utils/idempotency.ts +31 -0
- package/src/utils/logger.ts +75 -0
- package/src/utils/validation.ts +171 -0
- package/tsconfig.json +19 -0
- package/worker/index.ts +901 -0
- package/wrangler.toml +11 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post tools: post.publish, post.status, post.delete
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostProxyClient } from "../api/client.js";
|
|
6
|
+
import { PostPublishSchema } from "../utils/validation.js";
|
|
7
|
+
import { generateIdempotencyKey } from "../utils/idempotency.js";
|
|
8
|
+
import { createError, ErrorCodes } from "../utils/errors.js";
|
|
9
|
+
import { logError } from "../utils/logger.js";
|
|
10
|
+
import { handleProfilesList } from "./profiles.js";
|
|
11
|
+
|
|
12
|
+
export async function handlePostPublish(
|
|
13
|
+
client: PostProxyClient,
|
|
14
|
+
args: {
|
|
15
|
+
content: string;
|
|
16
|
+
targets: string[];
|
|
17
|
+
schedule?: string;
|
|
18
|
+
media?: string[];
|
|
19
|
+
idempotency_key?: string;
|
|
20
|
+
require_confirmation?: boolean;
|
|
21
|
+
draft?: boolean;
|
|
22
|
+
platforms?: Record<string, Record<string, any>>;
|
|
23
|
+
}
|
|
24
|
+
) {
|
|
25
|
+
// Validate input
|
|
26
|
+
try {
|
|
27
|
+
PostPublishSchema.parse(args);
|
|
28
|
+
} catch (error: any) {
|
|
29
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, `Invalid input: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If require_confirmation, return summary without publishing
|
|
33
|
+
if (args.require_confirmation) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: JSON.stringify(
|
|
39
|
+
{
|
|
40
|
+
summary: {
|
|
41
|
+
targets: args.targets,
|
|
42
|
+
content_preview: args.content.substring(0, 100) + (args.content.length > 100 ? "..." : ""),
|
|
43
|
+
media_count: args.media?.length || 0,
|
|
44
|
+
schedule_time: args.schedule,
|
|
45
|
+
draft: args.draft || false,
|
|
46
|
+
platforms: args.platforms || {},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
null,
|
|
50
|
+
2
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Get profiles to convert target IDs to platform names
|
|
58
|
+
const profilesResult = await handleProfilesList(client);
|
|
59
|
+
const profilesData = JSON.parse(profilesResult.content[0]?.text || "{}");
|
|
60
|
+
const targetsMap = new Map<string, any>();
|
|
61
|
+
for (const target of profilesData.targets || []) {
|
|
62
|
+
targetsMap.set(target.id, target);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Convert target IDs to platform names (API expects platform names, not IDs)
|
|
66
|
+
const platformNames: string[] = [];
|
|
67
|
+
for (const targetId of args.targets) {
|
|
68
|
+
const target = targetsMap.get(targetId);
|
|
69
|
+
if (!target) {
|
|
70
|
+
throw createError(ErrorCodes.TARGET_NOT_FOUND, `Target ${targetId} not found`);
|
|
71
|
+
}
|
|
72
|
+
platformNames.push(target.platform); // platform name (e.g., "twitter")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Validate that platforms keys match platform names if platforms are provided
|
|
76
|
+
if (args.platforms) {
|
|
77
|
+
const platformKeys = Object.keys(args.platforms);
|
|
78
|
+
const invalidPlatforms = platformKeys.filter(
|
|
79
|
+
(key) => !platformNames.includes(key)
|
|
80
|
+
);
|
|
81
|
+
if (invalidPlatforms.length > 0) {
|
|
82
|
+
throw createError(
|
|
83
|
+
ErrorCodes.VALIDATION_ERROR,
|
|
84
|
+
`Platform parameters specified for platforms not in targets: ${invalidPlatforms.join(", ")}. Available platforms: ${platformNames.join(", ")}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Generate idempotency key if not provided
|
|
90
|
+
const idempotencyKey = args.idempotency_key || generateIdempotencyKey(
|
|
91
|
+
args.content,
|
|
92
|
+
args.targets,
|
|
93
|
+
args.schedule
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Validate draft parameter is correctly passed
|
|
97
|
+
// Ensure draft is explicitly set (true, false, or undefined) and will be handled correctly
|
|
98
|
+
const draftValue = args.draft !== undefined ? args.draft : undefined;
|
|
99
|
+
|
|
100
|
+
// Create post
|
|
101
|
+
try {
|
|
102
|
+
const response = await client.createPost({
|
|
103
|
+
content: args.content,
|
|
104
|
+
profiles: platformNames, // API expects platform names, not profile IDs
|
|
105
|
+
schedule: args.schedule,
|
|
106
|
+
media: args.media,
|
|
107
|
+
idempotency_key: idempotencyKey,
|
|
108
|
+
draft: draftValue, // Explicitly pass draft value (true, false, or undefined)
|
|
109
|
+
platforms: args.platforms, // Platform-specific parameters
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Check if draft was requested but API ignored it
|
|
113
|
+
// Use strict boolean comparison to ensure we catch all cases
|
|
114
|
+
const wasDraftRequested = args.draft === true;
|
|
115
|
+
const isDraftInResponse = Boolean(response.draft) === true;
|
|
116
|
+
const wasProcessedImmediately = response.status === "processed" && wasDraftRequested;
|
|
117
|
+
|
|
118
|
+
// Draft is ignored if: it was requested AND (it's not in response OR post was processed immediately)
|
|
119
|
+
const draftIgnored = wasDraftRequested && (!isDraftInResponse || wasProcessedImmediately);
|
|
120
|
+
|
|
121
|
+
// Build response object
|
|
122
|
+
const responseData: any = {
|
|
123
|
+
job_id: response.id,
|
|
124
|
+
status: response.status,
|
|
125
|
+
draft: response.draft,
|
|
126
|
+
scheduled_at: response.scheduled_at,
|
|
127
|
+
created_at: response.created_at,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Always include warning field if draft was ignored
|
|
131
|
+
if (draftIgnored) {
|
|
132
|
+
responseData.warning = "Warning: Draft was requested but API returned draft: false. The post may have been processed immediately. This can happen if the API does not support drafts with media or other parameters.";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
content: [
|
|
137
|
+
{
|
|
138
|
+
type: "text",
|
|
139
|
+
text: JSON.stringify(responseData, null, 2),
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
} catch (error) {
|
|
144
|
+
logError(error as Error, "post.publish");
|
|
145
|
+
throw createError(
|
|
146
|
+
ErrorCodes.PUBLISH_FAILED,
|
|
147
|
+
`Failed to publish post: ${(error as Error).message}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function handlePostStatus(
|
|
153
|
+
client: PostProxyClient,
|
|
154
|
+
args: { job_id: string }
|
|
155
|
+
) {
|
|
156
|
+
if (!args.job_id) {
|
|
157
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "job_id is required");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const postDetails = await client.getPost(args.job_id);
|
|
162
|
+
|
|
163
|
+
// Parse platforms into per-platform format
|
|
164
|
+
const platforms: Array<{
|
|
165
|
+
platform: string;
|
|
166
|
+
status: "pending" | "processing" | "published" | "failed" | "deleted";
|
|
167
|
+
url?: string;
|
|
168
|
+
post_id?: string;
|
|
169
|
+
error?: string | null;
|
|
170
|
+
attempted_at: string | null;
|
|
171
|
+
insights?: any;
|
|
172
|
+
}> = [];
|
|
173
|
+
|
|
174
|
+
if (postDetails.platforms) {
|
|
175
|
+
for (const platform of postDetails.platforms) {
|
|
176
|
+
platforms.push({
|
|
177
|
+
platform: platform.platform,
|
|
178
|
+
status: platform.status,
|
|
179
|
+
url: platform.url,
|
|
180
|
+
post_id: platform.post_id,
|
|
181
|
+
error: platform.error || null,
|
|
182
|
+
attempted_at: platform.attempted_at,
|
|
183
|
+
insights: platform.insights,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Determine overall status from post status and platform statuses
|
|
189
|
+
let overallStatus: "pending" | "processing" | "complete" | "failed" | "draft" = "pending";
|
|
190
|
+
|
|
191
|
+
// Handle draft status first
|
|
192
|
+
if (postDetails.status === "draft" || postDetails.draft === true) {
|
|
193
|
+
overallStatus = "draft";
|
|
194
|
+
} else if (postDetails.status === "scheduled") {
|
|
195
|
+
overallStatus = "pending";
|
|
196
|
+
} else if (postDetails.status === "processing") {
|
|
197
|
+
overallStatus = "processing";
|
|
198
|
+
} else if (postDetails.status === "processed") {
|
|
199
|
+
if (platforms.length === 0) {
|
|
200
|
+
overallStatus = "pending";
|
|
201
|
+
} else {
|
|
202
|
+
const allPublished = platforms.every((p) => p.status === "published");
|
|
203
|
+
const allFailed = platforms.every((p) => p.status === "failed");
|
|
204
|
+
const anyPending = platforms.some((p) => p.status === "pending" || p.status === "processing");
|
|
205
|
+
|
|
206
|
+
if (anyPending) {
|
|
207
|
+
// Only if there are pending/processing platforms - this is truly processing
|
|
208
|
+
overallStatus = "processing";
|
|
209
|
+
} else if (allPublished) {
|
|
210
|
+
overallStatus = "complete";
|
|
211
|
+
} else if (allFailed) {
|
|
212
|
+
overallStatus = "failed";
|
|
213
|
+
} else {
|
|
214
|
+
// Mixed statuses (some published, some failed) - processing is complete
|
|
215
|
+
// Use "complete" since processing is finished, details are in platforms
|
|
216
|
+
overallStatus = "complete";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} else if (postDetails.status === "pending") {
|
|
220
|
+
overallStatus = "pending";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
content: [
|
|
225
|
+
{
|
|
226
|
+
type: "text",
|
|
227
|
+
text: JSON.stringify(
|
|
228
|
+
{
|
|
229
|
+
job_id: args.job_id,
|
|
230
|
+
overall_status: overallStatus,
|
|
231
|
+
draft: postDetails.draft || false,
|
|
232
|
+
status: postDetails.status,
|
|
233
|
+
platforms,
|
|
234
|
+
},
|
|
235
|
+
null,
|
|
236
|
+
2
|
|
237
|
+
),
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
} catch (error) {
|
|
242
|
+
logError(error as Error, "post.status");
|
|
243
|
+
throw createError(
|
|
244
|
+
ErrorCodes.API_ERROR,
|
|
245
|
+
`Failed to get post status: ${(error as Error).message}`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function handlePostPublishDraft(
|
|
251
|
+
client: PostProxyClient,
|
|
252
|
+
args: { job_id: string }
|
|
253
|
+
) {
|
|
254
|
+
if (!args.job_id) {
|
|
255
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "job_id is required");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
// First check if the post exists and is a draft
|
|
260
|
+
const postDetails = await client.getPost(args.job_id);
|
|
261
|
+
|
|
262
|
+
if (!postDetails.draft && postDetails.status !== "draft") {
|
|
263
|
+
throw createError(
|
|
264
|
+
ErrorCodes.VALIDATION_ERROR,
|
|
265
|
+
`Post ${args.job_id} is not a draft and cannot be published using this endpoint`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Publish the draft post
|
|
270
|
+
const publishedPost = await client.publishPost(args.job_id);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: "text",
|
|
276
|
+
text: JSON.stringify(
|
|
277
|
+
{
|
|
278
|
+
job_id: publishedPost.id,
|
|
279
|
+
status: publishedPost.status,
|
|
280
|
+
draft: publishedPost.draft,
|
|
281
|
+
scheduled_at: publishedPost.scheduled_at,
|
|
282
|
+
created_at: publishedPost.created_at,
|
|
283
|
+
message: "Draft post published successfully",
|
|
284
|
+
},
|
|
285
|
+
null,
|
|
286
|
+
2
|
|
287
|
+
),
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
};
|
|
291
|
+
} catch (error) {
|
|
292
|
+
logError(error as Error, "post.publish_draft");
|
|
293
|
+
|
|
294
|
+
// Re-throw validation errors as-is
|
|
295
|
+
if (error instanceof Error && "code" in error && error.code === ErrorCodes.VALIDATION_ERROR) {
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
throw createError(
|
|
300
|
+
ErrorCodes.API_ERROR,
|
|
301
|
+
`Failed to publish draft post: ${(error as Error).message}`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function handlePostDelete(
|
|
307
|
+
client: PostProxyClient,
|
|
308
|
+
args: { job_id: string }
|
|
309
|
+
) {
|
|
310
|
+
if (!args.job_id) {
|
|
311
|
+
throw createError(ErrorCodes.VALIDATION_ERROR, "job_id is required");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
await client.deletePost(args.job_id);
|
|
316
|
+
return {
|
|
317
|
+
content: [
|
|
318
|
+
{
|
|
319
|
+
type: "text",
|
|
320
|
+
text: JSON.stringify(
|
|
321
|
+
{
|
|
322
|
+
job_id: args.job_id,
|
|
323
|
+
deleted: true,
|
|
324
|
+
},
|
|
325
|
+
null,
|
|
326
|
+
2
|
|
327
|
+
),
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
};
|
|
331
|
+
} catch (error) {
|
|
332
|
+
logError(error as Error, "post.delete");
|
|
333
|
+
throw createError(
|
|
334
|
+
ErrorCodes.API_ERROR,
|
|
335
|
+
`Failed to delete post: ${(error as Error).message}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profiles tools: profiles.list
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostProxyClient } from "../api/client.js";
|
|
6
|
+
import { getApiKey } from "../auth/credentials.js";
|
|
7
|
+
import { createError, ErrorCodes } from "../utils/errors.js";
|
|
8
|
+
import { logError, logToolCall } from "../utils/logger.js";
|
|
9
|
+
|
|
10
|
+
export async function handleProfilesList(client: PostProxyClient) {
|
|
11
|
+
logToolCall("profiles.list", {});
|
|
12
|
+
|
|
13
|
+
// Check API key
|
|
14
|
+
const apiKey = getApiKey();
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
throw createError(ErrorCodes.AUTH_MISSING, "API key is not configured");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Get all profile groups
|
|
21
|
+
const profileGroups = await client.getProfileGroups();
|
|
22
|
+
|
|
23
|
+
// Get profiles for each group
|
|
24
|
+
const allTargets: Array<{
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
platform: string;
|
|
28
|
+
profile_group_id: string;
|
|
29
|
+
}> = [];
|
|
30
|
+
|
|
31
|
+
for (const group of profileGroups) {
|
|
32
|
+
try {
|
|
33
|
+
const profiles = await client.getProfiles(group.id);
|
|
34
|
+
for (const profile of profiles) {
|
|
35
|
+
allTargets.push({
|
|
36
|
+
id: profile.id, // Already a string
|
|
37
|
+
name: profile.name,
|
|
38
|
+
platform: profile.platform,
|
|
39
|
+
profile_group_id: profile.profile_group_id, // Already a string
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
logError(error as Error, `profiles.list (group ${group.id})`);
|
|
44
|
+
// Continue with other groups even if one fails
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: JSON.stringify(
|
|
53
|
+
{
|
|
54
|
+
targets: allTargets,
|
|
55
|
+
},
|
|
56
|
+
null,
|
|
57
|
+
2
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logError(error as Error, "profiles.list");
|
|
64
|
+
throw createError(
|
|
65
|
+
ErrorCodes.API_ERROR,
|
|
66
|
+
`Failed to retrieve profiles: ${(error as Error).message}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript types for PostProxy API responses and requests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ProfileGroup {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
profiles_count: number;
|
|
9
|
+
created_at?: string;
|
|
10
|
+
updated_at?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Profile {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
platform: string;
|
|
17
|
+
profile_group_id: string;
|
|
18
|
+
expires_at: string | null;
|
|
19
|
+
post_count: number;
|
|
20
|
+
username?: string;
|
|
21
|
+
avatar_url?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CreatePostParams {
|
|
25
|
+
content: string;
|
|
26
|
+
profile_group_id?: number; // Not used by API, kept for compatibility
|
|
27
|
+
profiles: string[]; // Platform names (e.g., ["twitter"]), not profile IDs
|
|
28
|
+
schedule?: string;
|
|
29
|
+
media?: string[];
|
|
30
|
+
idempotency_key?: string;
|
|
31
|
+
draft?: boolean; // If true, creates a draft post that won't publish automatically
|
|
32
|
+
platforms?: PlatformParams; // Platform-specific parameters
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CreatePostResponse {
|
|
36
|
+
id: string;
|
|
37
|
+
body?: string; // API returns "body" field
|
|
38
|
+
content?: string; // Some responses use "content"
|
|
39
|
+
status: "draft" | "pending" | "processing" | "processed" | "scheduled";
|
|
40
|
+
draft: boolean;
|
|
41
|
+
scheduled_at: string | null;
|
|
42
|
+
created_at: string;
|
|
43
|
+
platforms: PlatformOutcome[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface PlatformOutcome {
|
|
47
|
+
platform: string;
|
|
48
|
+
status: "pending" | "processing" | "published" | "failed" | "deleted";
|
|
49
|
+
params: Record<string, any>;
|
|
50
|
+
attempted_at: string | null;
|
|
51
|
+
insights?: {
|
|
52
|
+
impressions?: number;
|
|
53
|
+
on?: string;
|
|
54
|
+
[key: string]: any;
|
|
55
|
+
};
|
|
56
|
+
url?: string;
|
|
57
|
+
post_id?: string;
|
|
58
|
+
error?: string | null; // Error message from platform (replaces error_reason)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PostDetails {
|
|
62
|
+
id: string;
|
|
63
|
+
body?: string; // API returns "body" field
|
|
64
|
+
content?: string; // Some responses use "content"
|
|
65
|
+
status: "draft" | "pending" | "processing" | "processed" | "scheduled";
|
|
66
|
+
draft: boolean;
|
|
67
|
+
scheduled_at: string | null;
|
|
68
|
+
created_at: string;
|
|
69
|
+
updated_at?: string;
|
|
70
|
+
platforms: PlatformOutcome[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Post {
|
|
74
|
+
id: string;
|
|
75
|
+
body?: string; // API returns "body" field
|
|
76
|
+
content?: string; // Some responses use "content"
|
|
77
|
+
status: "draft" | "pending" | "processing" | "processed" | "scheduled";
|
|
78
|
+
draft: boolean;
|
|
79
|
+
scheduled_at: string | null;
|
|
80
|
+
created_at: string;
|
|
81
|
+
updated_at?: string;
|
|
82
|
+
platforms: PlatformOutcome[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Platform-specific parameters for Instagram
|
|
87
|
+
*/
|
|
88
|
+
export interface InstagramParams {
|
|
89
|
+
format?: "post" | "reel" | "story";
|
|
90
|
+
collaborators?: string[]; // Array of usernames (up to 10 for posts, 3 for reels)
|
|
91
|
+
first_comment?: string;
|
|
92
|
+
cover_url?: string; // For reels
|
|
93
|
+
audio_name?: string; // For reels
|
|
94
|
+
trial_strategy?: "MANUAL" | "SS_PERFORMANCE"; // For reels
|
|
95
|
+
thumb_offset?: string; // Thumbnail offset in milliseconds for reels
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Platform-specific parameters for YouTube
|
|
100
|
+
*/
|
|
101
|
+
export interface YouTubeParams {
|
|
102
|
+
title?: string; // Video title
|
|
103
|
+
privacy_status?: "public" | "unlisted" | "private"; // Video visibility
|
|
104
|
+
cover_url?: string; // Custom thumbnail URL
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Platform-specific parameters for TikTok
|
|
109
|
+
*/
|
|
110
|
+
export interface TikTokParams {
|
|
111
|
+
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
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Platform-specific parameters for Facebook
|
|
124
|
+
*/
|
|
125
|
+
export interface FacebookParams {
|
|
126
|
+
format?: "post" | "story";
|
|
127
|
+
first_comment?: string; // Comment to add after posting
|
|
128
|
+
page_id?: string; // Page ID when you have multiple pages
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Platform-specific parameters for LinkedIn
|
|
133
|
+
*/
|
|
134
|
+
export interface LinkedInParams {
|
|
135
|
+
organization_id?: string; // Post on behalf of an organization/company page
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Platform-specific parameters for Twitter/X
|
|
140
|
+
* Note: Twitter/X does not have platform-specific parameters
|
|
141
|
+
*/
|
|
142
|
+
export type TwitterParams = Record<string, never>;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Platform-specific parameters for Threads
|
|
146
|
+
* Note: Threads does not have platform-specific parameters
|
|
147
|
+
*/
|
|
148
|
+
export type ThreadsParams = Record<string, never>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Union type for all platform-specific parameters
|
|
152
|
+
*/
|
|
153
|
+
export interface PlatformParams {
|
|
154
|
+
instagram?: InstagramParams;
|
|
155
|
+
youtube?: YouTubeParams;
|
|
156
|
+
tiktok?: TikTokParams;
|
|
157
|
+
facebook?: FacebookParams;
|
|
158
|
+
linkedin?: LinkedInParams;
|
|
159
|
+
twitter?: TwitterParams;
|
|
160
|
+
threads?: ThreadsParams;
|
|
161
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error handling utilities for MCP server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class MCPError extends Error {
|
|
6
|
+
constructor(
|
|
7
|
+
public code: string,
|
|
8
|
+
message: string,
|
|
9
|
+
public details?: any
|
|
10
|
+
) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "MCPError";
|
|
13
|
+
Object.setPrototypeOf(this, MCPError.prototype);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ErrorCodes = {
|
|
18
|
+
AUTH_MISSING: "AUTH_MISSING",
|
|
19
|
+
AUTH_INVALID: "AUTH_INVALID",
|
|
20
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
21
|
+
TARGET_NOT_FOUND: "TARGET_NOT_FOUND",
|
|
22
|
+
PUBLISH_FAILED: "PUBLISH_FAILED",
|
|
23
|
+
PLATFORM_ERROR: "PLATFORM_ERROR",
|
|
24
|
+
API_ERROR: "API_ERROR",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
|
|
28
|
+
|
|
29
|
+
export function formatError(error: Error, code: ErrorCode, details?: any): MCPError {
|
|
30
|
+
if (error instanceof MCPError) {
|
|
31
|
+
return error;
|
|
32
|
+
}
|
|
33
|
+
return new MCPError(code, error.message || "An error occurred", details);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createError(code: ErrorCode, message: string, details?: any): MCPError {
|
|
37
|
+
return new MCPError(code, message, details);
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotency key generation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate an idempotency key from normalized post data
|
|
9
|
+
*/
|
|
10
|
+
export function generateIdempotencyKey(
|
|
11
|
+
content: string,
|
|
12
|
+
targets: string[],
|
|
13
|
+
schedule?: string
|
|
14
|
+
): string {
|
|
15
|
+
// Normalize input data
|
|
16
|
+
const normalizedContent = content.trim();
|
|
17
|
+
const normalizedTargets = [...targets].sort();
|
|
18
|
+
const normalizedSchedule = schedule || "";
|
|
19
|
+
|
|
20
|
+
// Create a JSON string from normalized data
|
|
21
|
+
const data = JSON.stringify({
|
|
22
|
+
content: normalizedContent,
|
|
23
|
+
targets: normalizedTargets,
|
|
24
|
+
schedule: normalizedSchedule,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Generate SHA256 hash
|
|
28
|
+
const hash = createHash("sha256").update(data).digest("hex");
|
|
29
|
+
|
|
30
|
+
return hash;
|
|
31
|
+
}
|