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,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials management - reading API key from environment variables
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BASE_URL = "https://api.postproxy.dev/api";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get API key from environment variables
|
|
9
|
+
* @returns API key or null if not found
|
|
10
|
+
*/
|
|
11
|
+
export function getApiKey(): string | null {
|
|
12
|
+
const apiKey = process.env.POSTPROXY_API_KEY;
|
|
13
|
+
return apiKey || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get base URL from environment variables or use default
|
|
18
|
+
* @returns Base URL for PostProxy API
|
|
19
|
+
*/
|
|
20
|
+
export function getBaseUrl(): string {
|
|
21
|
+
return process.env.POSTPROXY_BASE_URL || DEFAULT_BASE_URL;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate API key by making a test request (optional)
|
|
26
|
+
* This can be used to verify the key is valid before using it
|
|
27
|
+
*/
|
|
28
|
+
export async function validateApiKey(apiKey: string, baseUrl: string): Promise<boolean> {
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(`${baseUrl}/profile_groups/`, {
|
|
31
|
+
method: "GET",
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${apiKey}`,
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
},
|
|
36
|
+
signal: AbortSignal.timeout(5000), // 5 second timeout for validation
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return response.ok;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entry point for PostProxy MCP Server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import { getApiKey, getBaseUrl } from "./auth/credentials.js";
|
|
8
|
+
import { PostProxyClient } from "./api/client.js";
|
|
9
|
+
import { createMCPServer } from "./server.js";
|
|
10
|
+
import { log, logError } from "./utils/logger.js";
|
|
11
|
+
import { createError, ErrorCodes } from "./utils/errors.js";
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
// Check if setup command was called
|
|
15
|
+
if (process.argv[2] === "setup") {
|
|
16
|
+
const { setup } = await import("./setup.js");
|
|
17
|
+
await setup();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Read environment variables
|
|
23
|
+
const apiKey = getApiKey();
|
|
24
|
+
const baseUrl = getBaseUrl();
|
|
25
|
+
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
logError(
|
|
28
|
+
createError(
|
|
29
|
+
ErrorCodes.AUTH_MISSING,
|
|
30
|
+
"POSTPROXY_API_KEY environment variable is not set"
|
|
31
|
+
),
|
|
32
|
+
"startup"
|
|
33
|
+
);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Create API client
|
|
38
|
+
const client = new PostProxyClient(apiKey, baseUrl);
|
|
39
|
+
|
|
40
|
+
// Create MCP server
|
|
41
|
+
const server = await createMCPServer(client);
|
|
42
|
+
|
|
43
|
+
// Setup stdio transport
|
|
44
|
+
const transport = new StdioServerTransport();
|
|
45
|
+
await server.connect(transport);
|
|
46
|
+
|
|
47
|
+
log("PostProxy MCP Server started");
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logError(error as Error, "startup");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main().catch((error) => {
|
|
55
|
+
logError(error as Error, "main");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
});
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server setup and tool registration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import type { PostProxyClient } from "./api/client.js";
|
|
8
|
+
import { handleAuthStatus } from "./tools/auth.js";
|
|
9
|
+
import { handleProfilesList } from "./tools/profiles.js";
|
|
10
|
+
import {
|
|
11
|
+
handlePostPublish,
|
|
12
|
+
handlePostStatus,
|
|
13
|
+
handlePostDelete,
|
|
14
|
+
handlePostPublishDraft,
|
|
15
|
+
} from "./tools/post.js";
|
|
16
|
+
import { handleHistoryList } from "./tools/history.js";
|
|
17
|
+
import { createError, ErrorCodes } from "./utils/errors.js";
|
|
18
|
+
import { logToolCall } from "./utils/logger.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Tool definitions for the PostProxy MCP server.
|
|
22
|
+
* Exported for potential reuse in other contexts (e.g., Cloudflare Workers).
|
|
23
|
+
*/
|
|
24
|
+
export const TOOL_DEFINITIONS = [
|
|
25
|
+
{
|
|
26
|
+
name: "auth.status",
|
|
27
|
+
description: "Check authentication status, API configuration, and workspace information",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "profiles.list",
|
|
35
|
+
description: "List all available social media profiles (targets) for posting",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "post.publish",
|
|
43
|
+
description: "Publish a post to specified social media targets. Supports text content, media attachments, scheduling, drafts, and platform-specific customization via the 'platforms' parameter.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
content: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Post content text (caption/description)",
|
|
50
|
+
},
|
|
51
|
+
targets: {
|
|
52
|
+
type: "array",
|
|
53
|
+
items: { type: "string" },
|
|
54
|
+
description: "Array of target profile IDs to publish to",
|
|
55
|
+
},
|
|
56
|
+
schedule: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "Optional ISO 8601 scheduled time (e.g., '2024-12-31T23:59:59Z')",
|
|
59
|
+
},
|
|
60
|
+
media: {
|
|
61
|
+
type: "array",
|
|
62
|
+
items: { type: "string" },
|
|
63
|
+
description: "Optional array of media URLs or local file paths (images or videos). File paths can be absolute (/path/to/file.jpg), relative (./image.png), or use ~ for home directory (~/Pictures/photo.jpg)",
|
|
64
|
+
},
|
|
65
|
+
idempotency_key: {
|
|
66
|
+
type: "string",
|
|
67
|
+
description: "Optional idempotency key for request deduplication",
|
|
68
|
+
},
|
|
69
|
+
require_confirmation: {
|
|
70
|
+
type: "boolean",
|
|
71
|
+
description: "If true, return summary without publishing (dry run)",
|
|
72
|
+
},
|
|
73
|
+
draft: {
|
|
74
|
+
type: "boolean",
|
|
75
|
+
description: "If true, creates a draft post that won't publish automatically",
|
|
76
|
+
},
|
|
77
|
+
platforms: {
|
|
78
|
+
type: "object",
|
|
79
|
+
description: "Platform-specific parameters. Keys are platform names, values are parameter objects. Use this to add collaborators, set video titles, privacy settings, etc.",
|
|
80
|
+
properties: {
|
|
81
|
+
instagram: {
|
|
82
|
+
type: "object",
|
|
83
|
+
description: "Instagram: format (post|reel|story), collaborators (array of usernames), first_comment (string), cover_url (string), audio_name (string), trial_strategy (MANUAL|SS_PERFORMANCE), thumb_offset (string in ms)",
|
|
84
|
+
additionalProperties: true,
|
|
85
|
+
},
|
|
86
|
+
youtube: {
|
|
87
|
+
type: "object",
|
|
88
|
+
description: "YouTube: title (string), privacy_status (public|unlisted|private), cover_url (thumbnail URL)",
|
|
89
|
+
additionalProperties: true,
|
|
90
|
+
},
|
|
91
|
+
tiktok: {
|
|
92
|
+
type: "object",
|
|
93
|
+
description: "TikTok: privacy_status (PUBLIC_TO_EVERYONE|MUTUAL_FOLLOW_FRIENDS|FOLLOWER_OF_CREATOR|SELF_ONLY), photo_cover_index (integer), auto_add_music (bool), made_with_ai (bool), disable_comment (bool), disable_duet (bool), disable_stitch (bool), brand_content_toggle (bool), brand_organic_toggle (bool)",
|
|
94
|
+
additionalProperties: true,
|
|
95
|
+
},
|
|
96
|
+
facebook: {
|
|
97
|
+
type: "object",
|
|
98
|
+
description: "Facebook: format (post|story), first_comment (string), page_id (string)",
|
|
99
|
+
additionalProperties: true,
|
|
100
|
+
},
|
|
101
|
+
linkedin: {
|
|
102
|
+
type: "object",
|
|
103
|
+
description: "LinkedIn: organization_id (string for company pages)",
|
|
104
|
+
additionalProperties: true,
|
|
105
|
+
},
|
|
106
|
+
twitter: {
|
|
107
|
+
type: "object",
|
|
108
|
+
description: "Twitter/X: No platform-specific parameters available",
|
|
109
|
+
additionalProperties: true,
|
|
110
|
+
},
|
|
111
|
+
threads: {
|
|
112
|
+
type: "object",
|
|
113
|
+
description: "Threads: No platform-specific parameters available",
|
|
114
|
+
additionalProperties: true,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
additionalProperties: true,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ["content", "targets"],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "post.status",
|
|
125
|
+
description: "Get status of a published post by job ID",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
job_id: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Job ID from post.publish response",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
required: ["job_id"],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "post.publish_draft",
|
|
139
|
+
description: "Publish a draft post. Only posts with draft status can be published using this endpoint",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
job_id: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "Job ID of the draft post to publish",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: ["job_id"],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "post.delete",
|
|
153
|
+
description: "Delete a post by job ID",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
job_id: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "Job ID to delete",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
required: ["job_id"],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "history.list",
|
|
167
|
+
description: "List recent post jobs",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
limit: {
|
|
172
|
+
type: "number",
|
|
173
|
+
description: "Maximum number of jobs to return (default: 10)",
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
] as const;
|
|
179
|
+
|
|
180
|
+
export async function createMCPServer(client: PostProxyClient): Promise<Server> {
|
|
181
|
+
const server = new Server(
|
|
182
|
+
{
|
|
183
|
+
name: "postproxy-mcp",
|
|
184
|
+
version: "0.1.0",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
capabilities: {
|
|
188
|
+
tools: {},
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// List available tools
|
|
194
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
195
|
+
return {
|
|
196
|
+
tools: [...TOOL_DEFINITIONS],
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Handle tool calls
|
|
201
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
202
|
+
const { name, arguments: args } = request.params;
|
|
203
|
+
|
|
204
|
+
logToolCall(name, args);
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
switch (name) {
|
|
208
|
+
case "auth.status":
|
|
209
|
+
return await handleAuthStatus(client);
|
|
210
|
+
case "profiles.list":
|
|
211
|
+
return await handleProfilesList(client);
|
|
212
|
+
case "post.publish":
|
|
213
|
+
return await handlePostPublish(client, args as any);
|
|
214
|
+
case "post.status":
|
|
215
|
+
return await handlePostStatus(client, args as any);
|
|
216
|
+
case "post.publish_draft":
|
|
217
|
+
return await handlePostPublishDraft(client, args as any);
|
|
218
|
+
case "post.delete":
|
|
219
|
+
return await handlePostDelete(client, args as any);
|
|
220
|
+
case "history.list":
|
|
221
|
+
return await handleHistoryList(client, args as any);
|
|
222
|
+
default:
|
|
223
|
+
throw createError(ErrorCodes.API_ERROR, `Unknown tool: ${name}`);
|
|
224
|
+
}
|
|
225
|
+
} catch (error: any) {
|
|
226
|
+
if (error.code && error.message) {
|
|
227
|
+
// Already an MCPError
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
throw createError(ErrorCodes.API_ERROR, error.message || "Tool execution failed");
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return server;
|
|
235
|
+
}
|
package/src/setup-cli.ts
ADDED
package/src/setup.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Interactive setup script for PostProxy MCP Server
|
|
4
|
+
* Makes it easy for non-technical users to configure Claude Code integration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
8
|
+
import { join, dirname } from "path";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import * as readline from "readline";
|
|
11
|
+
|
|
12
|
+
interface ClaudeConfig {
|
|
13
|
+
mcpServers?: {
|
|
14
|
+
[key: string]: {
|
|
15
|
+
command: string;
|
|
16
|
+
env?: {
|
|
17
|
+
POSTPROXY_API_KEY?: string;
|
|
18
|
+
POSTPROXY_BASE_URL?: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getConfigPath(): string {
|
|
25
|
+
const platform = process.platform;
|
|
26
|
+
|
|
27
|
+
if (platform === "win32") {
|
|
28
|
+
return join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
|
|
29
|
+
} else {
|
|
30
|
+
return join(homedir(), ".config", "claude", "claude_desktop_config.json");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readConfig(): ClaudeConfig {
|
|
35
|
+
const configPath = getConfigPath();
|
|
36
|
+
|
|
37
|
+
if (!existsSync(configPath)) {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const content = readFileSync(configPath, "utf-8");
|
|
43
|
+
return JSON.parse(content);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`⚠️ Error reading config file: ${error}`);
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writeConfig(config: ClaudeConfig): void {
|
|
51
|
+
const configPath = getConfigPath();
|
|
52
|
+
const configDir = dirname(configPath);
|
|
53
|
+
|
|
54
|
+
// Create directory if it doesn't exist
|
|
55
|
+
if (!existsSync(configDir)) {
|
|
56
|
+
mkdirSync(configDir, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
61
|
+
console.log(`✅ Configuration saved to: ${configPath}`);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`❌ Error writing config file: ${error}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function question(rl: readline.Interface, query: string): Promise<string> {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
rl.question(query, resolve);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function questionWithDefault(rl: readline.Interface, query: string, defaultValue: string): Promise<string> {
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
rl.question(`${query} [${defaultValue}]: `, (answer) => {
|
|
77
|
+
resolve(answer.trim() || defaultValue);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function main() {
|
|
83
|
+
console.log("\n🚀 PostProxy MCP Server Setup");
|
|
84
|
+
console.log("=" .repeat(50));
|
|
85
|
+
console.log("This script will help you configure Claude Code to use PostProxy MCP Server.\n");
|
|
86
|
+
|
|
87
|
+
const rl = readline.createInterface({
|
|
88
|
+
input: process.stdin,
|
|
89
|
+
output: process.stdout,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Get API key
|
|
94
|
+
console.log("📝 Step 1: PostProxy API Configuration");
|
|
95
|
+
console.log("-" .repeat(50));
|
|
96
|
+
|
|
97
|
+
const apiKey = await question(
|
|
98
|
+
rl,
|
|
99
|
+
"Enter your PostProxy API key: "
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (!apiKey || apiKey.trim().length === 0) {
|
|
103
|
+
console.error("❌ API key is required!");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const baseUrl = await questionWithDefault(
|
|
108
|
+
rl,
|
|
109
|
+
"Enter PostProxy API base URL",
|
|
110
|
+
"https://api.postproxy.dev/api"
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Determine command path
|
|
114
|
+
console.log("\n📦 Step 2: Installation Path");
|
|
115
|
+
console.log("-" .repeat(50));
|
|
116
|
+
|
|
117
|
+
console.log("\nHow did you install PostProxy MCP Server?");
|
|
118
|
+
console.log("1. Installed globally via npm (npm install -g postproxy-mcp)");
|
|
119
|
+
console.log("2. Using local development build");
|
|
120
|
+
console.log("3. Custom path");
|
|
121
|
+
|
|
122
|
+
const installType = await question(rl, "\nSelect option (1-3): ");
|
|
123
|
+
|
|
124
|
+
let command: string;
|
|
125
|
+
|
|
126
|
+
if (installType === "1") {
|
|
127
|
+
command = "postproxy-mcp";
|
|
128
|
+
} else if (installType === "2") {
|
|
129
|
+
// Try to detect current location
|
|
130
|
+
const currentDir = process.cwd();
|
|
131
|
+
const possiblePath = join(currentDir, "dist", "index.js");
|
|
132
|
+
|
|
133
|
+
if (existsSync(possiblePath)) {
|
|
134
|
+
command = possiblePath;
|
|
135
|
+
console.log(`✅ Detected local build at: ${command}`);
|
|
136
|
+
} else {
|
|
137
|
+
const customPath = await question(
|
|
138
|
+
rl,
|
|
139
|
+
"Enter path to dist/index.js: "
|
|
140
|
+
);
|
|
141
|
+
command = customPath;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
command = await question(rl, "Enter full path to postproxy-mcp command: ");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Read existing config
|
|
148
|
+
const config = readConfig();
|
|
149
|
+
|
|
150
|
+
// Update config
|
|
151
|
+
if (!config.mcpServers) {
|
|
152
|
+
config.mcpServers = {};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
config.mcpServers.postproxy = {
|
|
156
|
+
command: command,
|
|
157
|
+
env: {
|
|
158
|
+
POSTPROXY_API_KEY: apiKey.trim(),
|
|
159
|
+
POSTPROXY_BASE_URL: baseUrl.trim(),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Write config
|
|
164
|
+
console.log("\n💾 Step 3: Saving Configuration");
|
|
165
|
+
console.log("-" .repeat(50));
|
|
166
|
+
|
|
167
|
+
writeConfig(config);
|
|
168
|
+
|
|
169
|
+
// Final instructions
|
|
170
|
+
console.log("\n✨ Setup Complete!");
|
|
171
|
+
console.log("=" .repeat(50));
|
|
172
|
+
console.log("\n📋 Next steps:");
|
|
173
|
+
console.log("1. Restart your Claude Code session");
|
|
174
|
+
console.log("2. Test the connection by asking Claude: 'Check my PostProxy authentication status'");
|
|
175
|
+
console.log("3. If configured correctly, Claude will automatically use PostProxy tools");
|
|
176
|
+
console.log("\n💡 Tip: You can run this setup again anytime with: postproxy-mcp setup");
|
|
177
|
+
console.log("");
|
|
178
|
+
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(`\n❌ Setup failed: ${error}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
} finally {
|
|
183
|
+
rl.close();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export { main as setup };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication tools: auth.status
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostProxyClient } from "../api/client.js";
|
|
6
|
+
import { getApiKey, getBaseUrl } 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 handleAuthStatus(client: PostProxyClient) {
|
|
11
|
+
logToolCall("auth.status", {});
|
|
12
|
+
|
|
13
|
+
const apiKey = getApiKey();
|
|
14
|
+
const baseUrl = getBaseUrl();
|
|
15
|
+
|
|
16
|
+
const result: {
|
|
17
|
+
authenticated: boolean;
|
|
18
|
+
base_url: string;
|
|
19
|
+
profile_groups_count?: number;
|
|
20
|
+
} = {
|
|
21
|
+
authenticated: apiKey !== null,
|
|
22
|
+
base_url: baseUrl,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// If authenticated, try to get profile groups count
|
|
26
|
+
if (apiKey !== null) {
|
|
27
|
+
try {
|
|
28
|
+
const profileGroups = await client.getProfileGroups();
|
|
29
|
+
result.profile_groups_count = profileGroups.length;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// If we can't get profile groups, just return without the count
|
|
32
|
+
// Don't fail the whole request
|
|
33
|
+
logError(error as Error, "auth.status");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: JSON.stringify(result, null, 2),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History tools: history.list
|
|
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
|
+
export async function handleHistoryList(
|
|
10
|
+
client: PostProxyClient,
|
|
11
|
+
args: { limit?: number }
|
|
12
|
+
) {
|
|
13
|
+
const limit = args.limit || 10;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const posts = await client.listPosts(limit);
|
|
17
|
+
|
|
18
|
+
const jobs = posts.map((post) => {
|
|
19
|
+
// Get content from either "body" or "content" field (API uses "body")
|
|
20
|
+
const content = post.body || post.content || "";
|
|
21
|
+
|
|
22
|
+
// Determine overall status from post status
|
|
23
|
+
let overallStatus = "unknown";
|
|
24
|
+
|
|
25
|
+
// Handle draft status first
|
|
26
|
+
if (post.status === "draft" || post.draft === true) {
|
|
27
|
+
overallStatus = "draft";
|
|
28
|
+
} else if (post.status === "scheduled") {
|
|
29
|
+
overallStatus = "pending";
|
|
30
|
+
} else if (post.status === "processing") {
|
|
31
|
+
overallStatus = "processing";
|
|
32
|
+
} else if (post.status === "processed") {
|
|
33
|
+
// Check platform statuses to determine overall status
|
|
34
|
+
if (post.platforms && post.platforms.length > 0) {
|
|
35
|
+
const allPublished = post.platforms.every((p) => p.status === "published");
|
|
36
|
+
const allFailed = post.platforms.every((p) => p.status === "failed");
|
|
37
|
+
const anyPending = post.platforms.some((p) => p.status === "pending" || p.status === "processing");
|
|
38
|
+
|
|
39
|
+
if (anyPending) {
|
|
40
|
+
// Only if there are pending/processing platforms - this is truly processing
|
|
41
|
+
overallStatus = "processing";
|
|
42
|
+
} else if (allPublished) {
|
|
43
|
+
overallStatus = "complete";
|
|
44
|
+
} else if (allFailed) {
|
|
45
|
+
overallStatus = "failed";
|
|
46
|
+
} else {
|
|
47
|
+
// Mixed statuses (some published, some failed) - processing is complete
|
|
48
|
+
// Use "complete" since processing is finished, details are in platforms
|
|
49
|
+
overallStatus = "complete";
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
overallStatus = "pending";
|
|
53
|
+
}
|
|
54
|
+
} else if (post.status === "pending") {
|
|
55
|
+
overallStatus = "pending";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
job_id: post.id,
|
|
60
|
+
content_preview: content.substring(0, 100) + (content.length > 100 ? "..." : ""),
|
|
61
|
+
created_at: post.created_at,
|
|
62
|
+
overall_status: overallStatus,
|
|
63
|
+
draft: post.draft || false,
|
|
64
|
+
platforms_count: post.platforms?.length || 0,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: JSON.stringify(
|
|
73
|
+
{
|
|
74
|
+
jobs,
|
|
75
|
+
},
|
|
76
|
+
null,
|
|
77
|
+
2
|
|
78
|
+
),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logError(error as Error, "history.list");
|
|
84
|
+
throw createError(
|
|
85
|
+
ErrorCodes.API_ERROR,
|
|
86
|
+
`Failed to list history: ${(error as Error).message}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|