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,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation schemas using Zod
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
/**
|
|
6
|
+
* Schema for ISO 8601 date strings
|
|
7
|
+
*/
|
|
8
|
+
export const ISO8601DateSchema = z.string().refine((date) => {
|
|
9
|
+
const d = new Date(date);
|
|
10
|
+
return !isNaN(d.getTime()) && date.includes("T");
|
|
11
|
+
}, {
|
|
12
|
+
message: "Must be a valid ISO 8601 date string (e.g., 2024-01-01T12:00:00Z)",
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Schema for URL strings
|
|
16
|
+
*/
|
|
17
|
+
export const URLSchema = z.string().url({
|
|
18
|
+
message: "Must be a valid URL",
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Check if a string looks like a file path
|
|
22
|
+
*/
|
|
23
|
+
export function isFilePath(value) {
|
|
24
|
+
// Absolute paths, relative paths, or home directory paths
|
|
25
|
+
return (value.startsWith("/") ||
|
|
26
|
+
value.startsWith("./") ||
|
|
27
|
+
value.startsWith("../") ||
|
|
28
|
+
value.startsWith("~/") ||
|
|
29
|
+
// Windows absolute paths
|
|
30
|
+
/^[A-Za-z]:[\\/]/.test(value));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Schema for media items (URLs or file paths)
|
|
34
|
+
*/
|
|
35
|
+
export const MediaItemSchema = z.string().refine((value) => {
|
|
36
|
+
// Allow file paths
|
|
37
|
+
if (isFilePath(value)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
// Or valid URLs
|
|
41
|
+
try {
|
|
42
|
+
new URL(value);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}, {
|
|
49
|
+
message: "Must be a valid URL or file path",
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Platform-specific validation schemas
|
|
53
|
+
*/
|
|
54
|
+
// Instagram parameters validation
|
|
55
|
+
export const InstagramParamsSchema = z.object({
|
|
56
|
+
format: z.enum(["post", "reel", "story"], {
|
|
57
|
+
errorMap: () => ({ message: "Instagram format must be 'post', 'reel', or 'story'" }),
|
|
58
|
+
}).optional(),
|
|
59
|
+
collaborators: z.array(z.string()).max(10, {
|
|
60
|
+
message: "Instagram allows up to 10 collaborators for posts, 3 for reels",
|
|
61
|
+
}).optional(),
|
|
62
|
+
first_comment: z.string().optional(),
|
|
63
|
+
cover_url: URLSchema.optional(),
|
|
64
|
+
audio_name: z.string().optional(),
|
|
65
|
+
trial_strategy: z.enum(["MANUAL", "SS_PERFORMANCE"], {
|
|
66
|
+
errorMap: () => ({ message: "Instagram trial_strategy must be 'MANUAL' or 'SS_PERFORMANCE'" }),
|
|
67
|
+
}).optional(),
|
|
68
|
+
thumb_offset: z.string().optional(),
|
|
69
|
+
}).strict();
|
|
70
|
+
// YouTube parameters validation
|
|
71
|
+
export const YouTubeParamsSchema = z.object({
|
|
72
|
+
title: z.string().optional(),
|
|
73
|
+
privacy_status: z.enum(["public", "unlisted", "private"], {
|
|
74
|
+
errorMap: () => ({ message: "YouTube privacy_status must be 'public', 'unlisted', or 'private'" }),
|
|
75
|
+
}).optional(),
|
|
76
|
+
cover_url: URLSchema.optional(),
|
|
77
|
+
}).strict();
|
|
78
|
+
// TikTok parameters validation
|
|
79
|
+
export const TikTokParamsSchema = z.object({
|
|
80
|
+
privacy_status: z.enum([
|
|
81
|
+
"PUBLIC_TO_EVERYONE",
|
|
82
|
+
"MUTUAL_FOLLOW_FRIENDS",
|
|
83
|
+
"FOLLOWER_OF_CREATOR",
|
|
84
|
+
"SELF_ONLY"
|
|
85
|
+
], {
|
|
86
|
+
errorMap: () => ({ message: "TikTok privacy_status must be one of: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY" }),
|
|
87
|
+
}).optional(),
|
|
88
|
+
photo_cover_index: z.number().int().nonnegative({
|
|
89
|
+
message: "TikTok photo_cover_index must be a non-negative integer",
|
|
90
|
+
}).optional(),
|
|
91
|
+
auto_add_music: z.boolean().optional(),
|
|
92
|
+
made_with_ai: z.boolean().optional(),
|
|
93
|
+
disable_comment: z.boolean().optional(),
|
|
94
|
+
disable_duet: z.boolean().optional(),
|
|
95
|
+
disable_stitch: z.boolean().optional(),
|
|
96
|
+
brand_content_toggle: z.boolean().optional(),
|
|
97
|
+
brand_organic_toggle: z.boolean().optional(),
|
|
98
|
+
}).strict();
|
|
99
|
+
// Facebook parameters validation
|
|
100
|
+
export const FacebookParamsSchema = z.object({
|
|
101
|
+
format: z.enum(["post", "story"], {
|
|
102
|
+
errorMap: () => ({ message: "Facebook format must be 'post' or 'story'" }),
|
|
103
|
+
}).optional(),
|
|
104
|
+
first_comment: z.string().optional(),
|
|
105
|
+
page_id: z.string().optional(),
|
|
106
|
+
}).strict();
|
|
107
|
+
// LinkedIn parameters validation
|
|
108
|
+
export const LinkedInParamsSchema = z.object({
|
|
109
|
+
organization_id: z.string().optional(),
|
|
110
|
+
}).strict();
|
|
111
|
+
// Twitter/X and Threads don't have platform-specific parameters
|
|
112
|
+
export const TwitterParamsSchema = z.object({}).strict();
|
|
113
|
+
export const ThreadsParamsSchema = z.object({}).strict();
|
|
114
|
+
// Combined platform parameters schema
|
|
115
|
+
export const PlatformParamsSchema = z.object({
|
|
116
|
+
instagram: InstagramParamsSchema.optional(),
|
|
117
|
+
youtube: YouTubeParamsSchema.optional(),
|
|
118
|
+
tiktok: TikTokParamsSchema.optional(),
|
|
119
|
+
facebook: FacebookParamsSchema.optional(),
|
|
120
|
+
linkedin: LinkedInParamsSchema.optional(),
|
|
121
|
+
twitter: TwitterParamsSchema.optional(),
|
|
122
|
+
threads: ThreadsParamsSchema.optional(),
|
|
123
|
+
}).strict().optional();
|
|
124
|
+
/**
|
|
125
|
+
* Schema for post.publish parameters
|
|
126
|
+
*/
|
|
127
|
+
export const PostPublishSchema = z.object({
|
|
128
|
+
content: z.string().min(1, "Content cannot be empty"),
|
|
129
|
+
targets: z.array(z.string()).min(1, "At least one target is required"),
|
|
130
|
+
schedule: ISO8601DateSchema.optional(),
|
|
131
|
+
media: z.array(MediaItemSchema).optional(),
|
|
132
|
+
idempotency_key: z.string().optional(),
|
|
133
|
+
require_confirmation: z.boolean().optional(),
|
|
134
|
+
draft: z.boolean().optional(),
|
|
135
|
+
platforms: PlatformParamsSchema,
|
|
136
|
+
});
|
|
137
|
+
/**
|
|
138
|
+
* Validate that a schedule date is in the future
|
|
139
|
+
*/
|
|
140
|
+
export function validateScheduleInFuture(schedule) {
|
|
141
|
+
const scheduleDate = new Date(schedule);
|
|
142
|
+
const now = new Date();
|
|
143
|
+
return scheduleDate > now;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postproxy-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for PostProxy API integration with Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"postproxy-mcp": "./dist/index.js",
|
|
9
|
+
"postproxy-mcp-setup": "./dist/setup-cli.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"dev:worker": "wrangler dev",
|
|
15
|
+
"deploy": "wrangler deploy",
|
|
16
|
+
"prepare": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mcp",
|
|
20
|
+
"postproxy",
|
|
21
|
+
"claude",
|
|
22
|
+
"social-media"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
27
|
+
"workers-mcp": "0.1.0-3",
|
|
28
|
+
"zod": "^3.22.4"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@cloudflare/workers-types": "^4.20250124.0",
|
|
32
|
+
"@types/node": "^20.10.0",
|
|
33
|
+
"typescript": "^5.3.3",
|
|
34
|
+
"wrangler": "^4.61.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostProxy API HTTP client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { basename } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import type {
|
|
9
|
+
ProfileGroup,
|
|
10
|
+
Profile,
|
|
11
|
+
CreatePostParams,
|
|
12
|
+
CreatePostResponse,
|
|
13
|
+
PostDetails,
|
|
14
|
+
Post,
|
|
15
|
+
} from "../types/index.js";
|
|
16
|
+
import { createError, ErrorCodes, formatError, type ErrorCode } from "../utils/errors.js";
|
|
17
|
+
import { log, logError } from "../utils/logger.js";
|
|
18
|
+
import { isFilePath } from "../utils/validation.js";
|
|
19
|
+
|
|
20
|
+
export class PostProxyClient {
|
|
21
|
+
private apiKey: string;
|
|
22
|
+
private baseUrl: string;
|
|
23
|
+
|
|
24
|
+
constructor(apiKey: string, baseUrl: string) {
|
|
25
|
+
this.apiKey = apiKey;
|
|
26
|
+
this.baseUrl = baseUrl.replace(/\/$/, ""); // Remove trailing slash
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract array from API response
|
|
31
|
+
* API returns either:
|
|
32
|
+
* - Direct array: [...]
|
|
33
|
+
* - Object with data field: {"data": [...]}
|
|
34
|
+
* - Paginated response: {"total": N, "data": [...]}
|
|
35
|
+
*/
|
|
36
|
+
private extractArray<T>(response: any): T[] {
|
|
37
|
+
if (Array.isArray(response)) {
|
|
38
|
+
return response;
|
|
39
|
+
}
|
|
40
|
+
if (response && typeof response === "object" && Array.isArray(response.data)) {
|
|
41
|
+
return response.data;
|
|
42
|
+
}
|
|
43
|
+
// Return empty array if response is not in expected format
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Expand ~ to home directory in file paths
|
|
49
|
+
*/
|
|
50
|
+
private expandPath(filePath: string): string {
|
|
51
|
+
if (filePath.startsWith("~/")) {
|
|
52
|
+
return filePath.replace("~", homedir());
|
|
53
|
+
}
|
|
54
|
+
return filePath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get MIME type based on file extension
|
|
59
|
+
*/
|
|
60
|
+
private getMimeType(filePath: string): string {
|
|
61
|
+
const ext = filePath.toLowerCase().split(".").pop();
|
|
62
|
+
const mimeTypes: Record<string, string> = {
|
|
63
|
+
jpg: "image/jpeg",
|
|
64
|
+
jpeg: "image/jpeg",
|
|
65
|
+
png: "image/png",
|
|
66
|
+
gif: "image/gif",
|
|
67
|
+
webp: "image/webp",
|
|
68
|
+
mp4: "video/mp4",
|
|
69
|
+
mov: "video/quicktime",
|
|
70
|
+
avi: "video/x-msvideo",
|
|
71
|
+
webm: "video/webm",
|
|
72
|
+
};
|
|
73
|
+
return mimeTypes[ext || ""] || "application/octet-stream";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if any media items are file paths (vs URLs)
|
|
78
|
+
*/
|
|
79
|
+
private hasFilePaths(media: string[]): boolean {
|
|
80
|
+
return media.some((item) => isFilePath(item));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create post using multipart/form-data (for file uploads)
|
|
85
|
+
* Uses form field names with brackets: post[body], profiles[], media[]
|
|
86
|
+
*/
|
|
87
|
+
private async createPostWithFiles(
|
|
88
|
+
params: CreatePostParams,
|
|
89
|
+
extraHeaders: Record<string, string>
|
|
90
|
+
): Promise<CreatePostResponse> {
|
|
91
|
+
const url = `${this.baseUrl}/posts`;
|
|
92
|
+
const formData = new FormData();
|
|
93
|
+
|
|
94
|
+
// Add post body
|
|
95
|
+
formData.append("post[body]", params.content);
|
|
96
|
+
|
|
97
|
+
// Add scheduled_at if provided
|
|
98
|
+
if (params.schedule) {
|
|
99
|
+
formData.append("post[scheduled_at]", params.schedule);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Add draft if provided
|
|
103
|
+
if (params.draft !== undefined) {
|
|
104
|
+
formData.append("post[draft]", String(params.draft));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add profiles (platform names)
|
|
108
|
+
for (const profile of params.profiles) {
|
|
109
|
+
formData.append("profiles[]", profile);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Add media files and URLs
|
|
113
|
+
if (params.media && params.media.length > 0) {
|
|
114
|
+
for (const mediaItem of params.media) {
|
|
115
|
+
if (isFilePath(mediaItem)) {
|
|
116
|
+
// It's a file path - read and upload the file
|
|
117
|
+
const expandedPath = this.expandPath(mediaItem);
|
|
118
|
+
try {
|
|
119
|
+
const fileContent = readFileSync(expandedPath);
|
|
120
|
+
const fileName = basename(expandedPath);
|
|
121
|
+
const mimeType = this.getMimeType(expandedPath);
|
|
122
|
+
const blob = new Blob([fileContent], { type: mimeType });
|
|
123
|
+
formData.append("media[]", blob, fileName);
|
|
124
|
+
if (process.env.POSTPROXY_MCP_DEBUG === "1") {
|
|
125
|
+
log(`Adding file to upload: ${fileName} (${mimeType}, ${fileContent.length} bytes)`);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw createError(
|
|
129
|
+
ErrorCodes.VALIDATION_ERROR,
|
|
130
|
+
`Failed to read file: ${mediaItem} - ${(error as Error).message}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// It's a URL - pass it as-is
|
|
135
|
+
formData.append("media[]", mediaItem);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Add platform-specific parameters as JSON
|
|
141
|
+
if (params.platforms && Object.keys(params.platforms).length > 0) {
|
|
142
|
+
formData.append("platforms", JSON.stringify(params.platforms));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Build headers (no Content-Type - fetch will set it with boundary for multipart)
|
|
146
|
+
const headers: Record<string, string> = {
|
|
147
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
148
|
+
...extraHeaders,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
if (process.env.POSTPROXY_MCP_DEBUG === "1") {
|
|
152
|
+
log(`Creating post with file upload (multipart/form-data)`);
|
|
153
|
+
log(`Profiles: ${params.profiles.join(", ")}`);
|
|
154
|
+
log(`Media count: ${params.media?.length || 0}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch(url, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers,
|
|
161
|
+
body: formData,
|
|
162
|
+
signal: AbortSignal.timeout(60000), // 60 second timeout for file uploads
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const requestId = response.headers.get("x-request-id");
|
|
166
|
+
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
let errorMessage = `API request failed with status ${response.status}`;
|
|
169
|
+
let errorDetails: any = { status: response.status, requestId };
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const errorBody = await response.json();
|
|
173
|
+
if (Array.isArray(errorBody.errors)) {
|
|
174
|
+
errorMessage = errorBody.errors.join("; ");
|
|
175
|
+
errorDetails = { ...errorDetails, errors: errorBody.errors };
|
|
176
|
+
} else if (errorBody.message) {
|
|
177
|
+
errorMessage = errorBody.message;
|
|
178
|
+
errorDetails = { ...errorDetails, ...errorBody };
|
|
179
|
+
} else if (errorBody.error) {
|
|
180
|
+
errorMessage = typeof errorBody.error === "string"
|
|
181
|
+
? errorBody.error
|
|
182
|
+
: errorBody.message || errorMessage;
|
|
183
|
+
errorDetails = { ...errorDetails, ...errorBody };
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
errorMessage = response.statusText || errorMessage;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let errorCode: ErrorCode = ErrorCodes.API_ERROR;
|
|
190
|
+
if (response.status === 401) {
|
|
191
|
+
errorCode = ErrorCodes.AUTH_INVALID;
|
|
192
|
+
} else if (response.status === 404) {
|
|
193
|
+
errorCode = ErrorCodes.TARGET_NOT_FOUND;
|
|
194
|
+
} else if (response.status >= 400 && response.status < 500) {
|
|
195
|
+
errorCode = ErrorCodes.VALIDATION_ERROR;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
logError(createError(errorCode, errorMessage, errorDetails), `API POST /posts (multipart)`);
|
|
199
|
+
throw createError(errorCode, errorMessage, errorDetails);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const jsonResponse = await response.json();
|
|
203
|
+
if (process.env.POSTPROXY_MCP_DEBUG === "1") {
|
|
204
|
+
log(`Response POST /posts (multipart)`, JSON.stringify(jsonResponse, null, 2));
|
|
205
|
+
}
|
|
206
|
+
return jsonResponse as CreatePostResponse;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
209
|
+
throw createError(
|
|
210
|
+
ErrorCodes.API_ERROR,
|
|
211
|
+
"Request timeout - API did not respond within 60 seconds"
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (error instanceof Error && "code" in error) {
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
logError(error as Error, `API POST /posts (multipart)`);
|
|
218
|
+
throw formatError(error as Error, ErrorCodes.API_ERROR, { method: "POST", path: "/posts" });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Make an HTTP request to the PostProxy API
|
|
224
|
+
*/
|
|
225
|
+
private async request<T>(
|
|
226
|
+
method: string,
|
|
227
|
+
path: string,
|
|
228
|
+
body?: any,
|
|
229
|
+
extraHeaders?: Record<string, string>
|
|
230
|
+
): Promise<T> {
|
|
231
|
+
const url = `${this.baseUrl}${path}`;
|
|
232
|
+
const headers: Record<string, string> = {
|
|
233
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
234
|
+
"Content-Type": "application/json",
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Merge extra headers if provided
|
|
238
|
+
if (extraHeaders) {
|
|
239
|
+
Object.assign(headers, extraHeaders);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const options: RequestInit = {
|
|
243
|
+
method,
|
|
244
|
+
headers,
|
|
245
|
+
signal: AbortSignal.timeout(30000), // 30 second timeout
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
249
|
+
options.body = JSON.stringify(body);
|
|
250
|
+
// Log request payload in debug mode
|
|
251
|
+
if (process.env.POSTPROXY_MCP_DEBUG === "1") {
|
|
252
|
+
log(`Request ${method} ${path}`, JSON.stringify(body, null, 2));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const response = await fetch(url, options);
|
|
258
|
+
const requestId = response.headers.get("x-request-id");
|
|
259
|
+
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
let errorMessage = `API request failed with status ${response.status}`;
|
|
262
|
+
let errorDetails: any = { status: response.status, requestId };
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const errorBody = await response.json();
|
|
266
|
+
|
|
267
|
+
// Handle different error response formats
|
|
268
|
+
if (Array.isArray(errorBody.errors)) {
|
|
269
|
+
// 422 validation errors: {"errors": ["...", "..."]}
|
|
270
|
+
errorMessage = errorBody.errors.join("; ");
|
|
271
|
+
errorDetails = { ...errorDetails, errors: errorBody.errors };
|
|
272
|
+
} else if (errorBody.message) {
|
|
273
|
+
// 400 errors: {"status":400,"error":"Bad Request","message":"..."}
|
|
274
|
+
errorMessage = errorBody.message;
|
|
275
|
+
errorDetails = { ...errorDetails, ...errorBody };
|
|
276
|
+
} else if (errorBody.error) {
|
|
277
|
+
// Some errors use "error" field
|
|
278
|
+
errorMessage = typeof errorBody.error === "string"
|
|
279
|
+
? errorBody.error
|
|
280
|
+
: errorBody.message || errorMessage;
|
|
281
|
+
errorDetails = { ...errorDetails, ...errorBody };
|
|
282
|
+
} else {
|
|
283
|
+
// Fallback: use any available message field
|
|
284
|
+
errorMessage = errorBody.message || errorBody.error || errorMessage;
|
|
285
|
+
errorDetails = { ...errorDetails, ...errorBody };
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
// If response is not JSON, use status text
|
|
289
|
+
errorMessage = response.statusText || errorMessage;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Map HTTP status codes to error codes
|
|
293
|
+
let errorCode: ErrorCode = ErrorCodes.API_ERROR;
|
|
294
|
+
if (response.status === 401) {
|
|
295
|
+
errorCode = ErrorCodes.AUTH_INVALID;
|
|
296
|
+
} else if (response.status === 404) {
|
|
297
|
+
errorCode = ErrorCodes.TARGET_NOT_FOUND;
|
|
298
|
+
} else if (response.status >= 400 && response.status < 500) {
|
|
299
|
+
errorCode = ErrorCodes.VALIDATION_ERROR;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
logError(createError(errorCode, errorMessage, errorDetails), `API ${method} ${path}`);
|
|
303
|
+
throw createError(errorCode, errorMessage, errorDetails);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Handle empty responses
|
|
307
|
+
const contentType = response.headers.get("content-type");
|
|
308
|
+
if (contentType && contentType.includes("application/json")) {
|
|
309
|
+
const jsonResponse = await response.json();
|
|
310
|
+
// Log response in debug mode
|
|
311
|
+
if (process.env.POSTPROXY_MCP_DEBUG === "1") {
|
|
312
|
+
log(`Response ${method} ${path}`, JSON.stringify(jsonResponse, null, 2));
|
|
313
|
+
}
|
|
314
|
+
return jsonResponse;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {} as T;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
320
|
+
throw createError(
|
|
321
|
+
ErrorCodes.API_ERROR,
|
|
322
|
+
"Request timeout - API did not respond within 30 seconds"
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (error instanceof Error && "code" in error && error.code === "AUTH_INVALID") {
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
logError(error as Error, `API ${method} ${path}`);
|
|
331
|
+
throw formatError(error as Error, ErrorCodes.API_ERROR, { method, path });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get all profile groups
|
|
337
|
+
*/
|
|
338
|
+
async getProfileGroups(): Promise<ProfileGroup[]> {
|
|
339
|
+
const response = await this.request<any>("GET", "/profile_groups/");
|
|
340
|
+
return this.extractArray<ProfileGroup>(response);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Get profiles, optionally filtered by group ID
|
|
345
|
+
*/
|
|
346
|
+
async getProfiles(groupId?: string | number): Promise<Profile[]> {
|
|
347
|
+
const path = groupId
|
|
348
|
+
? `/profiles?group_id=${groupId}`
|
|
349
|
+
: "/profiles";
|
|
350
|
+
const response = await this.request<any>("GET", path);
|
|
351
|
+
return this.extractArray<Profile>(response);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Create a new post
|
|
356
|
+
* API expects: { post: { body, scheduled_at, draft }, profiles: [...], media: [...], platforms: {...} }
|
|
357
|
+
* Note: draft parameter must be inside the post object, not at the top level
|
|
358
|
+
* If media contains file paths, uses multipart/form-data for file upload
|
|
359
|
+
*/
|
|
360
|
+
async createPost(params: CreatePostParams): Promise<CreatePostResponse> {
|
|
361
|
+
// Check if we need to use multipart/form-data for file uploads
|
|
362
|
+
if (params.media && params.media.length > 0 && this.hasFilePaths(params.media)) {
|
|
363
|
+
const extraHeaders: Record<string, string> = {};
|
|
364
|
+
if (params.idempotency_key) {
|
|
365
|
+
extraHeaders["Idempotency-Key"] = params.idempotency_key;
|
|
366
|
+
}
|
|
367
|
+
return this.createPostWithFiles(params, extraHeaders);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Transform to API format (JSON request for URLs only)
|
|
371
|
+
const apiPayload: any = {
|
|
372
|
+
post: {
|
|
373
|
+
body: params.content,
|
|
374
|
+
},
|
|
375
|
+
profiles: params.profiles, // Array of platform names (e.g., ["twitter"])
|
|
376
|
+
media: params.media || [], // Always include media field, even if empty
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
if (params.schedule) {
|
|
380
|
+
apiPayload.post.scheduled_at = params.schedule;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Draft parameter must be inside the post object, not at the top level
|
|
384
|
+
if (params.draft !== undefined) {
|
|
385
|
+
apiPayload.post.draft = params.draft;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Platform-specific parameters
|
|
389
|
+
// Only include platforms if it's a non-empty object with at least one platform
|
|
390
|
+
// Supports all platform parameter types: strings, numbers, booleans, arrays (e.g., collaborators)
|
|
391
|
+
// Empty platform objects (e.g., {"linkedin": {}}) are also supported
|
|
392
|
+
if (
|
|
393
|
+
params.platforms &&
|
|
394
|
+
typeof params.platforms === "object" &&
|
|
395
|
+
!Array.isArray(params.platforms) &&
|
|
396
|
+
Object.keys(params.platforms).length > 0
|
|
397
|
+
) {
|
|
398
|
+
// Validate structure: each key should be a platform name (string), value should be an object
|
|
399
|
+
// Note: We only validate the top-level structure. Detailed validation happens in validation.ts
|
|
400
|
+
// Platform parameter objects can contain: strings, numbers, booleans, arrays (e.g., collaborators), or be empty {}
|
|
401
|
+
const isValidPlatforms = Object.entries(params.platforms).every(
|
|
402
|
+
([key, value]) =>
|
|
403
|
+
typeof key === "string" &&
|
|
404
|
+
typeof value === "object" &&
|
|
405
|
+
value !== null &&
|
|
406
|
+
!Array.isArray(value)
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
if (isValidPlatforms) {
|
|
410
|
+
apiPayload.platforms = params.platforms;
|
|
411
|
+
} else {
|
|
412
|
+
log("WARNING: Invalid platforms structure, skipping platform parameters");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Log payload in debug mode to troubleshoot draft issues
|
|
417
|
+
if (process.env.POSTPROXY_MCP_DEBUG === "1") {
|
|
418
|
+
log("Creating post with payload:", JSON.stringify(apiPayload, null, 2));
|
|
419
|
+
log(`Draft parameter: requested=${params.draft}, sending=${apiPayload.draft}`);
|
|
420
|
+
if (params.media && params.media.length > 0) {
|
|
421
|
+
log(`Post includes ${params.media.length} media file(s)`);
|
|
422
|
+
}
|
|
423
|
+
if (params.platforms) {
|
|
424
|
+
log(`Platform parameters received: ${JSON.stringify(params.platforms, null, 2)}`);
|
|
425
|
+
log(`Platform parameter keys: ${Object.keys(params.platforms).join(", ")}`);
|
|
426
|
+
if (apiPayload.platforms) {
|
|
427
|
+
log(`Platform parameters sent to API: ${JSON.stringify(apiPayload.platforms, null, 2)}`);
|
|
428
|
+
} else {
|
|
429
|
+
log("WARNING: Platform parameters were provided but not included in API payload (invalid structure or empty)");
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
log("No platform parameters provided");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Add idempotency key as header if provided
|
|
437
|
+
const extraHeaders: Record<string, string> = {};
|
|
438
|
+
if (params.idempotency_key) {
|
|
439
|
+
extraHeaders["Idempotency-Key"] = params.idempotency_key;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const response = await this.request<CreatePostResponse>("POST", "/posts", apiPayload, extraHeaders);
|
|
443
|
+
|
|
444
|
+
// Log response in debug mode, especially draft status
|
|
445
|
+
if (process.env.POSTPROXY_MCP_DEBUG === "1") {
|
|
446
|
+
log(`Post created: id=${response.id}, status=${response.status}, draft=${response.draft}`);
|
|
447
|
+
if (params.draft === true && response.draft === false) {
|
|
448
|
+
log("WARNING: Draft was requested but API returned draft=false. API may have ignored the draft parameter.");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return response;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Get post details by ID
|
|
457
|
+
*/
|
|
458
|
+
async getPost(postId: string): Promise<PostDetails> {
|
|
459
|
+
return this.request<PostDetails>("GET", `/posts/${postId}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* List posts with optional pagination
|
|
464
|
+
*/
|
|
465
|
+
async listPosts(limit?: number, page?: number, perPage?: number): Promise<Post[]> {
|
|
466
|
+
const params = new URLSearchParams();
|
|
467
|
+
// Map limit to per_page (API expects per_page, not limit)
|
|
468
|
+
if (limit !== undefined) {
|
|
469
|
+
params.append("per_page", String(limit));
|
|
470
|
+
}
|
|
471
|
+
if (page !== undefined) {
|
|
472
|
+
params.append("page", String(page));
|
|
473
|
+
}
|
|
474
|
+
if (perPage !== undefined) {
|
|
475
|
+
params.append("per_page", String(perPage));
|
|
476
|
+
}
|
|
477
|
+
const queryString = params.toString();
|
|
478
|
+
const path = queryString ? `/posts?${queryString}` : "/posts";
|
|
479
|
+
const response = await this.request<any>("GET", path);
|
|
480
|
+
return this.extractArray<Post>(response);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Delete a post by ID
|
|
485
|
+
*/
|
|
486
|
+
async deletePost(postId: string): Promise<void> {
|
|
487
|
+
await this.request<void>("DELETE", `/posts/${postId}`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Publish a draft post
|
|
492
|
+
* Only posts with status "draft" can be published using this endpoint
|
|
493
|
+
*/
|
|
494
|
+
async publishPost(postId: string): Promise<PostDetails> {
|
|
495
|
+
return this.request<PostDetails>("POST", `/posts/${postId}/publish`);
|
|
496
|
+
}
|
|
497
|
+
}
|