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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +635 -0
  3. package/dist/api/client.d.ts +71 -0
  4. package/dist/api/client.d.ts.map +1 -0
  5. package/dist/api/client.js +432 -0
  6. package/dist/api/client.js.map +1 -0
  7. package/dist/auth/credentials.d.ts +19 -0
  8. package/dist/auth/credentials.d.ts.map +1 -0
  9. package/dist/auth/credentials.js +40 -0
  10. package/dist/auth/credentials.js.map +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +44 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/server.d.ts +162 -0
  16. package/dist/server.d.ts.map +1 -0
  17. package/dist/server.js +220 -0
  18. package/dist/server.js.map +1 -0
  19. package/dist/setup-cli.d.ts +6 -0
  20. package/dist/setup-cli.d.ts.map +1 -0
  21. package/dist/setup-cli.js +10 -0
  22. package/dist/setup-cli.js.map +1 -0
  23. package/dist/setup.d.ts +8 -0
  24. package/dist/setup.d.ts.map +1 -0
  25. package/dist/setup.js +143 -0
  26. package/dist/setup.js.map +1 -0
  27. package/dist/tools/accounts.d.ts +11 -0
  28. package/dist/tools/accounts.d.ts.map +1 -0
  29. package/dist/tools/accounts.js +53 -0
  30. package/dist/tools/accounts.js.map +1 -0
  31. package/dist/tools/auth.d.ts +11 -0
  32. package/dist/tools/auth.d.ts.map +1 -0
  33. package/dist/tools/auth.js +35 -0
  34. package/dist/tools/auth.js.map +1 -0
  35. package/dist/tools/history.d.ts +13 -0
  36. package/dist/tools/history.d.ts.map +1 -0
  37. package/dist/tools/history.js +79 -0
  38. package/dist/tools/history.js.map +1 -0
  39. package/dist/tools/post.d.ts +44 -0
  40. package/dist/tools/post.d.ts.map +1 -0
  41. package/dist/tools/post.js +251 -0
  42. package/dist/tools/post.js.map +1 -0
  43. package/dist/tools/profiles.d.ts +11 -0
  44. package/dist/tools/profiles.d.ts.map +1 -0
  45. package/dist/tools/profiles.js +52 -0
  46. package/dist/tools/profiles.js.map +1 -0
  47. package/dist/types/index.d.ts +147 -0
  48. package/dist/types/index.d.ts.map +1 -0
  49. package/dist/types/index.js +5 -0
  50. package/dist/types/index.js.map +1 -0
  51. package/dist/utils/errors.d.ts +21 -0
  52. package/dist/utils/errors.d.ts.map +1 -0
  53. package/dist/utils/errors.js +33 -0
  54. package/dist/utils/errors.js.map +1 -0
  55. package/dist/utils/idempotency.d.ts +8 -0
  56. package/dist/utils/idempotency.d.ts.map +1 -0
  57. package/dist/utils/idempotency.js +23 -0
  58. package/dist/utils/idempotency.js.map +1 -0
  59. package/dist/utils/logger.d.ts +20 -0
  60. package/dist/utils/logger.d.ts.map +1 -0
  61. package/dist/utils/logger.js +68 -0
  62. package/dist/utils/logger.js.map +1 -0
  63. package/dist/utils/validation.d.ts +555 -0
  64. package/dist/utils/validation.d.ts.map +1 -0
  65. package/dist/utils/validation.js +145 -0
  66. package/dist/utils/validation.js.map +1 -0
  67. package/package.json +39 -0
  68. package/src/api/client.ts +497 -0
  69. package/src/auth/credentials.ts +43 -0
  70. package/src/index.ts +57 -0
  71. package/src/server.ts +235 -0
  72. package/src/setup-cli.ts +11 -0
  73. package/src/setup.ts +187 -0
  74. package/src/tools/auth.ts +45 -0
  75. package/src/tools/history.ts +89 -0
  76. package/src/tools/post.ts +338 -0
  77. package/src/tools/profiles.ts +69 -0
  78. package/src/types/index.ts +161 -0
  79. package/src/utils/errors.ts +38 -0
  80. package/src/utils/idempotency.ts +31 -0
  81. package/src/utils/logger.ts +75 -0
  82. package/src/utils/validation.ts +171 -0
  83. package/tsconfig.json +19 -0
  84. package/worker/index.ts +901 -0
  85. 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
+ }