posterly-mcp-server 0.26.0 → 0.27.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/README.md +17 -2
- package/dist/index.js +20 -0
- package/dist/lib/api-client.d.ts +39 -0
- package/dist/lib/api-client.js +6 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/create-post.d.ts +36 -36
- package/dist/tools/create-post.js +7 -7
- package/dist/tools/create-posts-batch.d.ts +14 -14
- package/dist/tools/reply-google-business-review.d.ts +2 -2
- package/dist/tools/submit-agent-feedback.d.ts +108 -0
- package/dist/tools/submit-agent-feedback.js +32 -0
- package/dist/tools/trigger-platform-helper.js +1 -1
- package/dist/tools/update-post.d.ts +16 -16
- package/dist/tools/update-post.js +7 -7
- package/dist/tools/validate-post.d.ts +1080 -0
- package/dist/tools/validate-post.js +28 -0
- package/package.json +3 -2
- package/server.json +38 -0
- package/src/index.ts +30 -0
- package/src/lib/api-client.ts +43 -0
- package/src/lib/version.ts +1 -1
- package/src/tools/create-post.ts +7 -7
- package/src/tools/submit-agent-feedback.ts +47 -0
- package/src/tools/trigger-platform-helper.ts +1 -1
- package/src/tools/update-post.ts +7 -7
- package/src/tools/validate-post.ts +41 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { buildCreatePostPayload, createPostPayloadInputSchema, } from './create-post.js';
|
|
2
|
+
export const validatePostInputSchema = createPostPayloadInputSchema;
|
|
3
|
+
export const validatePostTool = {
|
|
4
|
+
name: 'validate_post',
|
|
5
|
+
description: 'Validate and normalize one post without creating it. This does not create records, copy remote media, consume post-item/storage quota, reserve X quota, emit webhooks, or enqueue publication. Third-party media may return remote_media_not_materialized because live create performs storage copy and final byte validation. Use this before showing the final preview and asking for confirmation to call create_post.',
|
|
6
|
+
inputSchema: validatePostInputSchema,
|
|
7
|
+
async execute(client, input) {
|
|
8
|
+
const payload = buildCreatePostPayload(input);
|
|
9
|
+
const result = await client.validatePost(payload);
|
|
10
|
+
const lines = [
|
|
11
|
+
'Post validation passed.',
|
|
12
|
+
`• Platform: ${result.normalized.platform}`,
|
|
13
|
+
`• Account ID: ${result.normalized.account_id}`,
|
|
14
|
+
`• Type: ${result.normalized.post_type}`,
|
|
15
|
+
`• Scheduled: ${result.normalized.scheduled_at}`,
|
|
16
|
+
`• Workspace: ${result.normalized.workspace_id || 'personal/default'}`,
|
|
17
|
+
`• Would create: ${result.would_create.status}`,
|
|
18
|
+
];
|
|
19
|
+
if (result.warnings.length > 0) {
|
|
20
|
+
lines.push('• Warnings:');
|
|
21
|
+
for (const warning of result.warnings) {
|
|
22
|
+
lines.push(` - ${warning.code}: ${warning.message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
lines.push('Show the final preview and obtain explicit confirmation before calling create_post.');
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
},
|
|
28
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posterly-mcp-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.27.0",
|
|
4
|
+
"mcpName": "io.github.awpthorp/posterly",
|
|
5
|
+
"description": "MCP server for posterly: schedule and publish social media posts across 18 platforms from any MCP client (Claude, ChatGPT, Cursor, Windsurf, Cline, and more)",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"homepage": "https://www.poster.ly/mcp",
|
|
7
8
|
"repository": {
|
package/server.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.awpthorp/posterly",
|
|
4
|
+
"title": "posterly",
|
|
5
|
+
"description": "Validate, schedule, publish, and analyze social content across 18 platforms with posterly.",
|
|
6
|
+
"version": "0.27.0",
|
|
7
|
+
"websiteUrl": "https://www.poster.ly/mcp",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/awpthorp/posterly",
|
|
10
|
+
"source": "github",
|
|
11
|
+
"subfolder": "mcp-server"
|
|
12
|
+
},
|
|
13
|
+
"packages": [
|
|
14
|
+
{
|
|
15
|
+
"registryType": "npm",
|
|
16
|
+
"identifier": "posterly-mcp-server",
|
|
17
|
+
"version": "0.27.0",
|
|
18
|
+
"transport": {
|
|
19
|
+
"type": "stdio"
|
|
20
|
+
},
|
|
21
|
+
"environmentVariables": [
|
|
22
|
+
{
|
|
23
|
+
"name": "POSTERLY_API_KEY",
|
|
24
|
+
"description": "posterly API key. Optional for public signup tools; required for authenticated tools.",
|
|
25
|
+
"format": "string",
|
|
26
|
+
"isRequired": false,
|
|
27
|
+
"isSecret": true
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"remotes": [
|
|
33
|
+
{
|
|
34
|
+
"type": "streamable-http",
|
|
35
|
+
"url": "https://www.poster.ly/api/mcp"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,8 @@ import { getPostInsightsTool } from './tools/get-post-insights.js';
|
|
|
20
20
|
import { listPostSuggestionsTool } from './tools/list-post-suggestions.js';
|
|
21
21
|
import { dismissSuggestionTool } from './tools/dismiss-suggestion.js';
|
|
22
22
|
import { createPostTool } from './tools/create-post.js';
|
|
23
|
+
import { validatePostTool } from './tools/validate-post.js';
|
|
24
|
+
import { submitAgentFeedbackTool } from './tools/submit-agent-feedback.js';
|
|
23
25
|
import { createPostsBatchTool } from './tools/create-posts-batch.js';
|
|
24
26
|
import { findSlotTool } from './tools/find-slot.js';
|
|
25
27
|
import { listPostsTool } from './tools/list-posts.js';
|
|
@@ -548,6 +550,34 @@ server.tool(
|
|
|
548
550
|
}
|
|
549
551
|
);
|
|
550
552
|
|
|
553
|
+
server.tool(
|
|
554
|
+
submitAgentFeedbackTool.name,
|
|
555
|
+
submitAgentFeedbackTool.description,
|
|
556
|
+
submitAgentFeedbackTool.inputSchema.shape,
|
|
557
|
+
async (input) => {
|
|
558
|
+
try {
|
|
559
|
+
const text = await submitAgentFeedbackTool.execute(client, input as any);
|
|
560
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
561
|
+
} catch (err: any) {
|
|
562
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
server.tool(
|
|
568
|
+
validatePostTool.name,
|
|
569
|
+
validatePostTool.description,
|
|
570
|
+
validatePostTool.inputSchema.shape,
|
|
571
|
+
async (input) => {
|
|
572
|
+
try {
|
|
573
|
+
const text = await validatePostTool.execute(client, input as any);
|
|
574
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
575
|
+
} catch (err: any) {
|
|
576
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
);
|
|
580
|
+
|
|
551
581
|
server.tool(
|
|
552
582
|
createPostTool.name,
|
|
553
583
|
createPostTool.description,
|
package/src/lib/api-client.ts
CHANGED
|
@@ -188,6 +188,41 @@ export type CreatePostResponse = {
|
|
|
188
188
|
workspace: { id: string; name: string; is_personal: boolean; resolved_from: 'explicit' | 'account' | 'personal' };
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
+
export type ValidatePostResponse = {
|
|
192
|
+
dry_run: true;
|
|
193
|
+
valid: true;
|
|
194
|
+
normalized: {
|
|
195
|
+
account_id: number;
|
|
196
|
+
platform: string;
|
|
197
|
+
post_type: string;
|
|
198
|
+
scheduled_at: string;
|
|
199
|
+
workspace_id: string | null;
|
|
200
|
+
};
|
|
201
|
+
warnings: Array<{ code: string; message: string }>;
|
|
202
|
+
would_create: { status: string };
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export type AgentFeedbackPayload = {
|
|
206
|
+
source: 'mcp' | 'cli' | 'rest' | 'skill';
|
|
207
|
+
tool: string;
|
|
208
|
+
outcome: 'success' | 'error' | 'abandoned';
|
|
209
|
+
error_code?: string;
|
|
210
|
+
client?: string;
|
|
211
|
+
comment?: string;
|
|
212
|
+
context?: {
|
|
213
|
+
request_id?: string;
|
|
214
|
+
http_status?: number;
|
|
215
|
+
latency_ms?: number;
|
|
216
|
+
attempt?: number;
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export type AgentFeedbackResponse = {
|
|
221
|
+
accepted: true;
|
|
222
|
+
feedback_id: string;
|
|
223
|
+
request_id: string;
|
|
224
|
+
};
|
|
225
|
+
|
|
191
226
|
export type BatchCreatePostsResponse = {
|
|
192
227
|
posts: Array<CreatePostResponse & { index: number; replayed: boolean }>;
|
|
193
228
|
errors: Array<{ index: number; status: number; error: string; validation_errors?: unknown }>;
|
|
@@ -1105,6 +1140,14 @@ export class PosterlyClient {
|
|
|
1105
1140
|
return this.request('POST', '/posts', data);
|
|
1106
1141
|
}
|
|
1107
1142
|
|
|
1143
|
+
async validatePost(data: CreatePostPayload): Promise<ValidatePostResponse> {
|
|
1144
|
+
return this.request('POST', '/posts', { ...data, dry_run: true });
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
async submitAgentFeedback(data: AgentFeedbackPayload): Promise<AgentFeedbackResponse> {
|
|
1148
|
+
return this.request('POST', '/agent-feedback', data);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1108
1151
|
async createPostsBatch(data: { posts: CreatePostPayload[] }): Promise<BatchCreatePostsResponse> {
|
|
1109
1152
|
return this.request('POST', '/posts/batch', data);
|
|
1110
1153
|
}
|
package/src/lib/version.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// tool set (minus the intentional pre-auth signup tools that only this stdio
|
|
6
6
|
// package exposes). `npm run check:mcp-parity` enforces both the version match
|
|
7
7
|
// and the tool-list match, and runs in the pre-commit hook.
|
|
8
|
-
export const POSTERLY_MCP_VERSION = '0.
|
|
8
|
+
export const POSTERLY_MCP_VERSION = '0.27.0';
|
package/src/tools/create-post.ts
CHANGED
|
@@ -15,7 +15,7 @@ export const instagramSettingsSchema = z.object({
|
|
|
15
15
|
.array(z.union([z.string(), z.object({ username: z.string().optional(), label: z.string().optional(), handle: z.string().optional() })]))
|
|
16
16
|
.optional(),
|
|
17
17
|
first_comment: z.string().optional(),
|
|
18
|
-
media_alt_texts: z.record(z.string()).optional(),
|
|
18
|
+
media_alt_texts: z.record(z.string(), z.string()).optional(),
|
|
19
19
|
reel_cover_url: z.string().optional(),
|
|
20
20
|
reel_thumb_offset: z.number().nonnegative().optional(),
|
|
21
21
|
});
|
|
@@ -24,7 +24,7 @@ export const platformSettingsSchema = z.object({
|
|
|
24
24
|
__type: z.string().optional(),
|
|
25
25
|
post_type: z.string().optional(),
|
|
26
26
|
content_type: z.string().optional(),
|
|
27
|
-
media_alt_texts: z.record(z.string()).optional(),
|
|
27
|
+
media_alt_texts: z.record(z.string(), z.string()).optional(),
|
|
28
28
|
reel_cover_url: z.string().optional(),
|
|
29
29
|
reel_thumb_offset: z.number().nonnegative().optional(),
|
|
30
30
|
// X
|
|
@@ -40,13 +40,13 @@ export const platformSettingsSchema = z.object({
|
|
|
40
40
|
reply_control: z.enum(['everyone', 'accounts_you_follow', 'mentioned_only', 'followers_only', 'parent_post_author_only']).optional(),
|
|
41
41
|
topic_tag: z.string().optional(), // Threads single discovery topic tag
|
|
42
42
|
share_to_instagram: z.boolean().optional(), // Threads: also share the published post to the linked Instagram account as a Story
|
|
43
|
-
text_attachment: z.record(z.unknown()).optional(),
|
|
43
|
+
text_attachment: z.record(z.string(), z.unknown()).optional(),
|
|
44
44
|
// Facebook
|
|
45
45
|
text_format_preset_id: z.string().optional(),
|
|
46
46
|
// LinkedIn
|
|
47
47
|
document_title: z.string().optional(),
|
|
48
48
|
document_filename: z.string().optional(),
|
|
49
|
-
mentions: z.array(z.record(z.unknown())).optional(),
|
|
49
|
+
mentions: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
50
50
|
thumbnail_url: z.string().optional(),
|
|
51
51
|
thumbnail_method: z.string().optional(),
|
|
52
52
|
// TikTok
|
|
@@ -73,9 +73,9 @@ export const platformSettingsSchema = z.object({
|
|
|
73
73
|
video_cover_url: z.string().optional(),
|
|
74
74
|
cover_image_method: z.enum(['upload', 'frame', 'api']).optional(),
|
|
75
75
|
// Google Business
|
|
76
|
-
event: z.record(z.unknown()).optional(),
|
|
77
|
-
offer: z.record(z.unknown()).optional(),
|
|
78
|
-
cta: z.record(z.unknown()).optional(),
|
|
76
|
+
event: z.record(z.string(), z.unknown()).optional(),
|
|
77
|
+
offer: z.record(z.string(), z.unknown()).optional(),
|
|
78
|
+
cta: z.record(z.string(), z.unknown()).optional(),
|
|
79
79
|
// YouTube
|
|
80
80
|
notify_subscribers: z.boolean().optional(),
|
|
81
81
|
add_shorts_hashtag: z.boolean().optional(),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
const tokenSchema = z.string().regex(/^[a-z0-9][a-z0-9_.:-]{0,63}$/);
|
|
5
|
+
const printableSchema = (maxLength: number) =>
|
|
6
|
+
z.string().trim().min(1).max(maxLength).refine(
|
|
7
|
+
(value) => !/[\u0000-\u001f\u007f]/.test(value),
|
|
8
|
+
'Control characters are not allowed',
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export const submitAgentFeedbackInputSchema = z.object({
|
|
12
|
+
source: z.enum(['mcp', 'cli', 'rest', 'skill']),
|
|
13
|
+
tool: tokenSchema,
|
|
14
|
+
outcome: z.enum(['success', 'error', 'abandoned']),
|
|
15
|
+
error_code: tokenSchema.optional(),
|
|
16
|
+
client: printableSchema(80).optional(),
|
|
17
|
+
comment: printableSchema(1000)
|
|
18
|
+
.refine(
|
|
19
|
+
(value) => !/(?:pst_(?:live|test)_|sk-[a-z0-9_-]{12,})/i.test(value),
|
|
20
|
+
'Comments must not contain secrets',
|
|
21
|
+
)
|
|
22
|
+
.optional(),
|
|
23
|
+
context: z.object({
|
|
24
|
+
request_id: z.string().regex(/^[a-zA-Z0-9_.:-]{8,80}$/).optional(),
|
|
25
|
+
http_status: z.number().int().min(100).max(599).optional(),
|
|
26
|
+
latency_ms: z.number().int().min(0).max(300000).optional(),
|
|
27
|
+
attempt: z.number().int().min(1).max(20).optional(),
|
|
28
|
+
}).strict().optional(),
|
|
29
|
+
}).strict();
|
|
30
|
+
|
|
31
|
+
export type SubmitAgentFeedbackInput = z.infer<typeof submitAgentFeedbackInputSchema>;
|
|
32
|
+
|
|
33
|
+
export const submitAgentFeedbackTool = {
|
|
34
|
+
name: 'submit_agent_feedback',
|
|
35
|
+
description:
|
|
36
|
+
'Submit bounded private operational telemetry after a concrete posterly workflow outcome. This does not alter product content but writes a private feedback event. Never include API keys, prompts, captions, media URLs, personal data, or fabricated outcomes.',
|
|
37
|
+
inputSchema: submitAgentFeedbackInputSchema,
|
|
38
|
+
|
|
39
|
+
async execute(client: PosterlyClient, input: SubmitAgentFeedbackInput) {
|
|
40
|
+
const result = await client.submitAgentFeedback(input);
|
|
41
|
+
return [
|
|
42
|
+
'Agent feedback accepted.',
|
|
43
|
+
`• Feedback ID: ${result.feedback_id}`,
|
|
44
|
+
`• Request ID: ${result.request_id}`,
|
|
45
|
+
].join('\n');
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -16,7 +16,7 @@ export const triggerPlatformHelperTool = {
|
|
|
16
16
|
.enum(SUPPORTED_PLATFORM_INPUTS)
|
|
17
17
|
.optional()
|
|
18
18
|
.describe(`Platform hint when no account_id is supplied. Supported: ${SUPPORTED_PLATFORM_IDS.join(', ')}.`),
|
|
19
|
-
params: z.record(z.unknown()).optional(),
|
|
19
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
20
20
|
}),
|
|
21
21
|
|
|
22
22
|
async execute(
|
package/src/tools/update-post.ts
CHANGED
|
@@ -14,7 +14,7 @@ const instagramSettingsSchema = z.object({
|
|
|
14
14
|
.array(z.union([z.string(), z.object({ username: z.string().optional(), label: z.string().optional(), handle: z.string().optional() })]))
|
|
15
15
|
.optional(),
|
|
16
16
|
first_comment: z.string().optional(),
|
|
17
|
-
media_alt_texts: z.record(z.string()).optional(),
|
|
17
|
+
media_alt_texts: z.record(z.string(), z.string()).optional(),
|
|
18
18
|
reel_cover_url: z.string().optional(),
|
|
19
19
|
reel_thumb_offset: z.number().nonnegative().optional(),
|
|
20
20
|
});
|
|
@@ -23,7 +23,7 @@ const platformSettingsSchema = z.object({
|
|
|
23
23
|
__type: z.string().optional(),
|
|
24
24
|
post_type: z.string().optional(),
|
|
25
25
|
content_type: z.string().optional(),
|
|
26
|
-
media_alt_texts: z.record(z.string()).optional(),
|
|
26
|
+
media_alt_texts: z.record(z.string(), z.string()).optional(),
|
|
27
27
|
reel_cover_url: z.string().optional(),
|
|
28
28
|
reel_thumb_offset: z.number().nonnegative().optional(),
|
|
29
29
|
reply_settings: z.enum(['everyone', 'following', 'mentionedUsers', 'subscribers', 'verified']).optional(),
|
|
@@ -31,11 +31,11 @@ const platformSettingsSchema = z.object({
|
|
|
31
31
|
reply_control: z.enum(['everyone', 'accounts_you_follow', 'mentioned_only', 'followers_only', 'parent_post_author_only']).optional(),
|
|
32
32
|
topic_tag: z.string().optional(),
|
|
33
33
|
share_to_instagram: z.boolean().optional(), // Threads: also share the published post to the linked Instagram account as a Story
|
|
34
|
-
text_attachment: z.record(z.unknown()).optional(),
|
|
34
|
+
text_attachment: z.record(z.string(), z.unknown()).optional(),
|
|
35
35
|
text_format_preset_id: z.string().optional(),
|
|
36
36
|
document_title: z.string().optional(),
|
|
37
37
|
document_filename: z.string().optional(),
|
|
38
|
-
mentions: z.array(z.record(z.unknown())).optional(),
|
|
38
|
+
mentions: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
39
39
|
thumbnail_url: z.string().optional(),
|
|
40
40
|
thumbnail_method: z.string().optional(),
|
|
41
41
|
privacy_level: z.string().optional(),
|
|
@@ -57,9 +57,9 @@ const platformSettingsSchema = z.object({
|
|
|
57
57
|
cover_image_url: z.string().optional(),
|
|
58
58
|
video_cover_url: z.string().optional(),
|
|
59
59
|
cover_image_method: z.enum(['upload', 'frame', 'api']).optional(),
|
|
60
|
-
event: z.record(z.unknown()).optional(),
|
|
61
|
-
offer: z.record(z.unknown()).optional(),
|
|
62
|
-
cta: z.record(z.unknown()).optional(),
|
|
60
|
+
event: z.record(z.string(), z.unknown()).optional(),
|
|
61
|
+
offer: z.record(z.string(), z.unknown()).optional(),
|
|
62
|
+
cta: z.record(z.string(), z.unknown()).optional(),
|
|
63
63
|
notify_subscribers: z.boolean().optional(),
|
|
64
64
|
add_shorts_hashtag: z.boolean().optional(),
|
|
65
65
|
buttons: z.array(z.object({ text: z.string(), url: z.string() })).optional(),
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
2
|
+
import {
|
|
3
|
+
buildCreatePostPayload,
|
|
4
|
+
createPostPayloadInputSchema,
|
|
5
|
+
} from './create-post.js';
|
|
6
|
+
|
|
7
|
+
export const validatePostInputSchema = createPostPayloadInputSchema;
|
|
8
|
+
|
|
9
|
+
export type ValidatePostInput = Parameters<typeof buildCreatePostPayload>[0];
|
|
10
|
+
|
|
11
|
+
export const validatePostTool = {
|
|
12
|
+
name: 'validate_post',
|
|
13
|
+
description:
|
|
14
|
+
'Validate and normalize one post without creating it. This does not create records, copy remote media, consume post-item/storage quota, reserve X quota, emit webhooks, or enqueue publication. Third-party media may return remote_media_not_materialized because live create performs storage copy and final byte validation. Use this before showing the final preview and asking for confirmation to call create_post.',
|
|
15
|
+
inputSchema: validatePostInputSchema,
|
|
16
|
+
|
|
17
|
+
async execute(client: PosterlyClient, input: ValidatePostInput) {
|
|
18
|
+
const payload = buildCreatePostPayload(input);
|
|
19
|
+
const result = await client.validatePost(payload);
|
|
20
|
+
|
|
21
|
+
const lines = [
|
|
22
|
+
'Post validation passed.',
|
|
23
|
+
`• Platform: ${result.normalized.platform}`,
|
|
24
|
+
`• Account ID: ${result.normalized.account_id}`,
|
|
25
|
+
`• Type: ${result.normalized.post_type}`,
|
|
26
|
+
`• Scheduled: ${result.normalized.scheduled_at}`,
|
|
27
|
+
`• Workspace: ${result.normalized.workspace_id || 'personal/default'}`,
|
|
28
|
+
`• Would create: ${result.would_create.status}`,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
if (result.warnings.length > 0) {
|
|
32
|
+
lines.push('• Warnings:');
|
|
33
|
+
for (const warning of result.warnings) {
|
|
34
|
+
lines.push(` - ${warning.code}: ${warning.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
lines.push('Show the final preview and obtain explicit confirmation before calling create_post.');
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
},
|
|
41
|
+
};
|