posterly-mcp-server 0.8.2 → 0.19.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 +38 -3
- package/dist/generated/platform-manifest.d.ts +392 -0
- package/dist/generated/platform-manifest.js +695 -0
- package/dist/index.js +331 -1
- package/dist/lib/api-client.d.ts +391 -24
- package/dist/lib/api-client.js +176 -0
- package/dist/tools/audit-google-business-profile.d.ts +24 -0
- package/dist/tools/audit-google-business-profile.js +27 -0
- package/dist/tools/create-oauth-client.d.ts +40 -0
- package/dist/tools/create-oauth-client.js +20 -0
- package/dist/tools/create-post.d.ts +399 -20
- package/dist/tools/create-post.js +81 -78
- package/dist/tools/create-posts-batch.d.ts +567 -0
- package/dist/tools/create-posts-batch.js +38 -0
- package/dist/tools/create-signed-upload.d.ts +24 -0
- package/dist/tools/create-signed-upload.js +20 -0
- package/dist/tools/create-webhook.d.ts +36 -0
- package/dist/tools/create-webhook.js +20 -0
- package/dist/tools/delete-google-business-review-reply.d.ts +32 -0
- package/dist/tools/delete-google-business-review-reply.js +17 -0
- package/dist/tools/delete-oauth-client.d.ts +20 -0
- package/dist/tools/delete-oauth-client.js +13 -0
- package/dist/tools/delete-post-group.d.ts +20 -0
- package/dist/tools/delete-post-group.js +13 -0
- package/dist/tools/delete-webhook.d.ts +20 -0
- package/dist/tools/delete-webhook.js +13 -0
- package/dist/tools/disconnect-account.d.ts +20 -0
- package/dist/tools/disconnect-account.js +22 -0
- package/dist/tools/generate-image.d.ts +4 -4
- package/dist/tools/generate-video.d.ts +59 -0
- package/dist/tools/generate-video.js +34 -0
- package/dist/tools/get-connect-link.d.ts +24 -0
- package/dist/tools/get-connect-link.js +46 -0
- package/dist/tools/get-google-business-review-link.d.ts +24 -0
- package/dist/tools/get-google-business-review-link.js +19 -0
- package/dist/tools/get-platform-schema.d.ts +24 -0
- package/dist/tools/get-platform-schema.js +44 -0
- package/dist/tools/get-post-missing.d.ts +16 -0
- package/dist/tools/get-post-missing.js +12 -0
- package/dist/tools/get-video-job.d.ts +24 -0
- package/dist/tools/get-video-job.js +31 -0
- package/dist/tools/get-video-options.d.ts +8 -0
- package/dist/tools/get-video-options.js +26 -0
- package/dist/tools/get-x-posting-quota.d.ts +16 -0
- package/dist/tools/get-x-posting-quota.js +21 -0
- package/dist/tools/list-activity.d.ts +32 -0
- package/dist/tools/list-activity.js +35 -0
- package/dist/tools/list-google-business-reviews.d.ts +32 -0
- package/dist/tools/list-google-business-reviews.js +28 -0
- package/dist/tools/list-oauth-clients.d.ts +8 -0
- package/dist/tools/list-oauth-clients.js +15 -0
- package/dist/tools/list-platforms.d.ts +16 -0
- package/dist/tools/list-platforms.js +20 -0
- package/dist/tools/list-posts.d.ts +3 -3
- package/dist/tools/list-posts.js +3 -2
- package/dist/tools/list-webhooks.d.ts +16 -0
- package/dist/tools/list-webhooks.js +24 -0
- package/dist/tools/reply-google-business-review.d.ts +36 -0
- package/dist/tools/reply-google-business-review.js +18 -0
- package/dist/tools/run-video-function.d.ts +24 -0
- package/dist/tools/run-video-function.js +25 -0
- package/dist/tools/suggest-google-business-review-reply.d.ts +48 -0
- package/dist/tools/suggest-google-business-review-reply.js +27 -0
- package/dist/tools/test-webhook.d.ts +20 -0
- package/dist/tools/test-webhook.js +14 -0
- package/dist/tools/trigger-platform-helper.d.ts +28 -0
- package/dist/tools/trigger-platform-helper.js +22 -0
- package/dist/tools/update-oauth-client.d.ts +44 -0
- package/dist/tools/update-oauth-client.js +21 -0
- package/dist/tools/update-post-release-id.d.ts +28 -0
- package/dist/tools/update-post-release-id.js +18 -0
- package/dist/tools/update-post-status.d.ts +28 -0
- package/dist/tools/update-post-status.js +23 -0
- package/dist/tools/update-post.js +1 -1
- package/dist/tools/update-webhook.d.ts +40 -0
- package/dist/tools/update-webhook.js +21 -0
- package/dist/tools/upload-media-from-url.d.ts +24 -0
- package/dist/tools/upload-media-from-url.js +26 -0
- package/dist/tools/webhook-events.d.ts +3 -0
- package/dist/tools/webhook-events.js +13 -0
- package/package.json +1 -1
- package/src/generated/platform-manifest.ts +695 -0
- package/src/index.ts +496 -1
- package/src/lib/api-client.ts +497 -19
- package/src/tools/audit-google-business-profile.ts +38 -0
- package/src/tools/create-oauth-client.ts +36 -0
- package/src/tools/create-post.ts +91 -94
- package/src/tools/create-posts-batch.ts +48 -0
- package/src/tools/create-signed-upload.ts +27 -0
- package/src/tools/create-webhook.ts +35 -0
- package/src/tools/delete-google-business-review-reply.ts +30 -0
- package/src/tools/delete-oauth-client.ts +23 -0
- package/src/tools/delete-post-group.ts +17 -0
- package/src/tools/delete-webhook.ts +18 -0
- package/src/tools/disconnect-account.ts +30 -0
- package/src/tools/generate-video.ts +38 -0
- package/src/tools/get-connect-link.ts +56 -0
- package/src/tools/get-google-business-review-link.ts +30 -0
- package/src/tools/get-platform-schema.ts +52 -0
- package/src/tools/get-post-missing.ts +17 -0
- package/src/tools/get-video-job.ts +36 -0
- package/src/tools/get-video-options.ts +30 -0
- package/src/tools/get-x-posting-quota.ts +25 -0
- package/src/tools/list-activity.ts +51 -0
- package/src/tools/list-google-business-reviews.ts +43 -0
- package/src/tools/list-oauth-clients.ts +18 -0
- package/src/tools/list-platforms.ts +25 -0
- package/src/tools/list-posts.ts +3 -2
- package/src/tools/list-webhooks.ts +30 -0
- package/src/tools/reply-google-business-review.ts +32 -0
- package/src/tools/run-video-function.ts +32 -0
- package/src/tools/suggest-google-business-review-reply.ts +44 -0
- package/src/tools/test-webhook.ts +18 -0
- package/src/tools/trigger-platform-helper.ts +34 -0
- package/src/tools/update-oauth-client.ts +38 -0
- package/src/tools/update-post-release-id.ts +26 -0
- package/src/tools/update-post-status.ts +31 -0
- package/src/tools/update-post.ts +1 -1
- package/src/tools/update-webhook.ts +37 -0
- package/src/tools/upload-media-from-url.ts +33 -0
- package/src/tools/webhook-events.ts +16 -0
package/src/tools/create-post.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { SUPPORTED_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
|
|
3
4
|
|
|
4
|
-
const instagramSettingsSchema = z.object({
|
|
5
|
+
export const instagramSettingsSchema = z.object({
|
|
5
6
|
__type: z.enum(['instagram', 'instagram-standalone']).optional(),
|
|
6
7
|
post_type: z.enum(['post', 'feed', 'story', 'reel', 'carousel']).optional(),
|
|
7
8
|
is_trial_reel: z.boolean().optional(),
|
|
@@ -18,7 +19,7 @@ const instagramSettingsSchema = z.object({
|
|
|
18
19
|
reel_thumb_offset: z.number().nonnegative().optional(),
|
|
19
20
|
});
|
|
20
21
|
|
|
21
|
-
const platformSettingsSchema = z.object({
|
|
22
|
+
export const platformSettingsSchema = z.object({
|
|
22
23
|
__type: z.string().optional(),
|
|
23
24
|
post_type: z.string().optional(),
|
|
24
25
|
content_type: z.string().optional(),
|
|
@@ -71,6 +72,88 @@ const platformSettingsSchema = z.object({
|
|
|
71
72
|
langs: z.array(z.string()).optional(),
|
|
72
73
|
}).passthrough();
|
|
73
74
|
|
|
75
|
+
export const createPostInputSchema = z.object({
|
|
76
|
+
account_id: z.string().optional().describe('Social account ID (from list_accounts)'),
|
|
77
|
+
username: z.string().optional().describe('Account username (alternative to account_id)'),
|
|
78
|
+
platform: z
|
|
79
|
+
.enum(SUPPORTED_PLATFORM_INPUTS)
|
|
80
|
+
.optional()
|
|
81
|
+
.describe(`Platform name (required with username). Supported: ${SUPPORTED_PLATFORM_IDS.join(', ')}. Aliases such as x, gmb, and instagram-standalone are accepted.`),
|
|
82
|
+
caption: z.string().optional().describe('The post caption/text content. Ignored when `thread_posts` is provided.'),
|
|
83
|
+
scheduled_at: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('ISO 8601 datetime for scheduling (e.g. 2026-03-05T09:00:00Z). Omit for immediate publish.'),
|
|
87
|
+
media_url: z.string().optional().describe('URL of media to attach (image or video). For threads, attaches to the lead post only.'),
|
|
88
|
+
media_urls: z
|
|
89
|
+
.array(z.string())
|
|
90
|
+
.optional()
|
|
91
|
+
.describe('Multiple media URLs for carousel posts'),
|
|
92
|
+
post_type: z
|
|
93
|
+
.string()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe('Post type: text, image, video, carousel, reel, story. Auto-set to x_thread/threads_thread when `thread_posts` is provided.'),
|
|
96
|
+
thread_posts: z
|
|
97
|
+
.array(z.string())
|
|
98
|
+
.optional()
|
|
99
|
+
.describe('For X or Threads only: array of 2+ strings, one per post in the thread. The first entry leads, the rest reply in order. When set, the platform must be twitter or threads.'),
|
|
100
|
+
instagram_settings: instagramSettingsSchema
|
|
101
|
+
.optional()
|
|
102
|
+
.describe('Instagram-specific settings: post_type (post/feed/story/reel/carousel), collaborators, user_tags, first_comment, media_alt_texts, is_trial_reel, graduation_strategy (defaults to MANUAL), reel_cover_url, reel_thumb_offset.'),
|
|
103
|
+
platform_settings: platformSettingsSchema
|
|
104
|
+
.optional()
|
|
105
|
+
.describe('Platform-specific settings for the selected account. Use this for non-Instagram settings and prefer it over raw metadata.'),
|
|
106
|
+
workspace_id: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe('Workspace ID to assign the post to (from whoami). If omitted, uses the account\'s workspace or the caller\'s default workspace.'),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export type CreatePostInput = z.infer<typeof createPostInputSchema>;
|
|
113
|
+
|
|
114
|
+
export function buildCreatePostPayload(input: CreatePostInput): Parameters<PosterlyClient['createPost']>[0] {
|
|
115
|
+
const { thread_posts, caption, post_type, instagram_settings, platform_settings, ...rest } = input;
|
|
116
|
+
|
|
117
|
+
if (thread_posts && thread_posts.length > 0) {
|
|
118
|
+
if (thread_posts.length < 2) {
|
|
119
|
+
throw new Error('thread_posts must contain at least 2 entries');
|
|
120
|
+
}
|
|
121
|
+
const platformHint = (input.platform || '').toLowerCase();
|
|
122
|
+
const isTwitter = platformHint === 'twitter' || platformHint === 'x';
|
|
123
|
+
const isThreads = platformHint === 'threads';
|
|
124
|
+
if (!isTwitter && !isThreads && !input.account_id) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
'thread_posts requires `platform` to be "twitter" or "threads", or pass `account_id` for an X/Threads account.',
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const arrayKey = isThreads ? 'threads_thread_posts' : 'x_thread_tweets';
|
|
130
|
+
const totalKey = isThreads ? 'threads_thread_total' : 'x_thread_total';
|
|
131
|
+
return {
|
|
132
|
+
...rest,
|
|
133
|
+
platform: isTwitter ? 'twitter' : isThreads ? 'threads' : input.platform,
|
|
134
|
+
caption: thread_posts[0],
|
|
135
|
+
post_type: isThreads ? 'threads_thread' : 'x_thread',
|
|
136
|
+
metadata: {
|
|
137
|
+
[arrayKey]: thread_posts,
|
|
138
|
+
[totalKey]: thread_posts.length,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!caption) {
|
|
144
|
+
throw new Error('caption is required (or pass `thread_posts` for an X/Threads thread)');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
...rest,
|
|
149
|
+
caption,
|
|
150
|
+
post_type,
|
|
151
|
+
...(platform_settings || instagram_settings
|
|
152
|
+
? { settings: { ...(instagram_settings ? { __type: 'instagram', ...instagram_settings } : {}), ...(platform_settings || {}) } }
|
|
153
|
+
: {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
74
157
|
export const createPostTool = {
|
|
75
158
|
name: 'create_post',
|
|
76
159
|
description:
|
|
@@ -79,104 +162,18 @@ export const createPostTool = {
|
|
|
79
162
|
'1. Call `whoami` at the start of any new session to confirm which workspace and user you are acting for.\n' +
|
|
80
163
|
'2. Show the user a preview containing ALL of: account(s) and platform(s), final caption text, scheduled time (in the user\'s timezone), media attached (if any), and workspace name.\n' +
|
|
81
164
|
'3. Get explicit confirmation from the user (e.g. "post it", "yes schedule that", "looks good") BEFORE calling this tool. Do NOT infer consent from earlier instructions like "post about X every Monday" — confirm each individual post or the entire batch.\n' +
|
|
82
|
-
'4. If scheduling multiple posts in one turn, list every post first and confirm the batch as a whole
|
|
165
|
+
'4. If scheduling multiple posts in one turn, list every post first and confirm the batch as a whole, then prefer `create_posts_batch` so they are created in one API request.\n\n' +
|
|
83
166
|
'Provide either account_id OR username+platform to identify the account. If scheduled_at is omitted, the post publishes immediately. If workspace_id is omitted, the server resolves one from the social account, falling back to the caller\'s default (personal) workspace — pass workspace_id explicitly if the user has more than one workspace.\n\n' +
|
|
84
167
|
'THREADS: Pass `thread_posts` (an array of 2+ strings) to schedule a multi-post thread on X (Twitter) or Threads (Meta). The first entry is the lead post; the rest are published as replies in the same chain. X entries are capped at 280 characters each (4000 for verified, 25000 for organization accounts); Threads entries are capped at 500 characters each. When `thread_posts` is set, `caption` is ignored.\n\n' +
|
|
85
|
-
'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/playlist, LinkedIn document titles/mentions/video thumbnails, TikTok direct-post privacy/toggles/commercial disclosure, Pinterest
|
|
86
|
-
inputSchema:
|
|
87
|
-
account_id: z.string().optional().describe('Social account ID (from list_accounts)'),
|
|
88
|
-
username: z.string().optional().describe('Account username (alternative to account_id)'),
|
|
89
|
-
platform: z
|
|
90
|
-
.string()
|
|
91
|
-
.optional()
|
|
92
|
-
.describe('Platform name (required with username): instagram, twitter, linkedin, facebook, tiktok, threads, youtube, pinterest, google_business'),
|
|
93
|
-
caption: z.string().optional().describe('The post caption/text content. Ignored when `thread_posts` is provided.'),
|
|
94
|
-
scheduled_at: z
|
|
95
|
-
.string()
|
|
96
|
-
.optional()
|
|
97
|
-
.describe('ISO 8601 datetime for scheduling (e.g. 2026-03-05T09:00:00Z). Omit for immediate publish.'),
|
|
98
|
-
media_url: z.string().optional().describe('URL of media to attach (image or video). For threads, attaches to the lead post only.'),
|
|
99
|
-
media_urls: z
|
|
100
|
-
.array(z.string())
|
|
101
|
-
.optional()
|
|
102
|
-
.describe('Multiple media URLs for carousel posts'),
|
|
103
|
-
post_type: z
|
|
104
|
-
.string()
|
|
105
|
-
.optional()
|
|
106
|
-
.describe('Post type: text, image, video, carousel, reel, story. Auto-set to x_thread/threads_thread when `thread_posts` is provided.'),
|
|
107
|
-
thread_posts: z
|
|
108
|
-
.array(z.string())
|
|
109
|
-
.optional()
|
|
110
|
-
.describe('For X or Threads only: array of 2+ strings, one per post in the thread. The first entry leads, the rest reply in order. When set, the platform must be twitter or threads.'),
|
|
111
|
-
instagram_settings: instagramSettingsSchema
|
|
112
|
-
.optional()
|
|
113
|
-
.describe('Instagram-specific settings: post_type (post/feed/story/reel/carousel), collaborators, user_tags, first_comment, media_alt_texts, is_trial_reel, graduation_strategy, reel_cover_url, reel_thumb_offset.'),
|
|
114
|
-
platform_settings: platformSettingsSchema
|
|
115
|
-
.optional()
|
|
116
|
-
.describe('Platform-specific settings for the selected account. Use this for non-Instagram settings and prefer it over raw metadata.'),
|
|
117
|
-
workspace_id: z
|
|
118
|
-
.string()
|
|
119
|
-
.optional()
|
|
120
|
-
.describe('Workspace ID to assign the post to (from whoami). If omitted, uses the account\'s workspace or the caller\'s default workspace.'),
|
|
121
|
-
}),
|
|
168
|
+
'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/playlist, LinkedIn document titles/mentions/video thumbnails, TikTok direct-post privacy/toggles/commercial disclosure, Pinterest post_type (image/video/carousel), board (from pinterest.boards helper), title, link, and cover_image_url (video Pins), GBP event/offer/CTA, X reply settings/polls, Threads reply controls/text attachments, Bluesky languages/alt text, and Instagram feed/story/reel/carousel/collaborators/first comments/trial Reels. `instagram_settings` remains as a backwards-compatible alias.',
|
|
169
|
+
inputSchema: createPostInputSchema,
|
|
122
170
|
|
|
123
171
|
async execute(
|
|
124
172
|
client: PosterlyClient,
|
|
125
|
-
input:
|
|
126
|
-
account_id?: string;
|
|
127
|
-
username?: string;
|
|
128
|
-
platform?: string;
|
|
129
|
-
caption?: string;
|
|
130
|
-
scheduled_at?: string;
|
|
131
|
-
media_url?: string;
|
|
132
|
-
media_urls?: string[];
|
|
133
|
-
post_type?: string;
|
|
134
|
-
thread_posts?: string[];
|
|
135
|
-
instagram_settings?: z.infer<typeof instagramSettingsSchema>;
|
|
136
|
-
platform_settings?: z.infer<typeof platformSettingsSchema>;
|
|
137
|
-
workspace_id?: string;
|
|
138
|
-
}
|
|
173
|
+
input: CreatePostInput,
|
|
139
174
|
) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (thread_posts && thread_posts.length > 0) {
|
|
144
|
-
if (thread_posts.length < 2) {
|
|
145
|
-
throw new Error('thread_posts must contain at least 2 entries');
|
|
146
|
-
}
|
|
147
|
-
const platformHint = (input.platform || '').toLowerCase();
|
|
148
|
-
const isTwitter = platformHint === 'twitter' || platformHint === 'x';
|
|
149
|
-
const isThreads = platformHint === 'threads';
|
|
150
|
-
if (!isTwitter && !isThreads && !input.account_id) {
|
|
151
|
-
throw new Error(
|
|
152
|
-
'thread_posts requires `platform` to be "twitter" or "threads", or pass `account_id` for an X/Threads account.',
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
const arrayKey = isThreads ? 'threads_thread_posts' : 'x_thread_tweets';
|
|
156
|
-
const totalKey = isThreads ? 'threads_thread_total' : 'x_thread_total';
|
|
157
|
-
payload = {
|
|
158
|
-
...rest,
|
|
159
|
-
platform: isTwitter ? 'twitter' : isThreads ? 'threads' : input.platform,
|
|
160
|
-
caption: thread_posts[0],
|
|
161
|
-
post_type: isThreads ? 'threads_thread' : 'x_thread',
|
|
162
|
-
metadata: {
|
|
163
|
-
[arrayKey]: thread_posts,
|
|
164
|
-
[totalKey]: thread_posts.length,
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
} else {
|
|
168
|
-
if (!caption) {
|
|
169
|
-
throw new Error('caption is required (or pass `thread_posts` for an X/Threads thread)');
|
|
170
|
-
}
|
|
171
|
-
payload = {
|
|
172
|
-
...rest,
|
|
173
|
-
caption,
|
|
174
|
-
post_type,
|
|
175
|
-
...(platform_settings || instagram_settings
|
|
176
|
-
? { settings: { ...(instagram_settings ? { __type: 'instagram', ...instagram_settings } : {}), ...(platform_settings || {}) } }
|
|
177
|
-
: {}),
|
|
178
|
-
};
|
|
179
|
-
}
|
|
175
|
+
const payload = buildCreatePostPayload(input);
|
|
176
|
+
const { thread_posts, instagram_settings, platform_settings } = input;
|
|
180
177
|
|
|
181
178
|
const result = await client.createPost(payload);
|
|
182
179
|
const p = result.post as Record<string, any>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { buildCreatePostPayload, createPostInputSchema, type CreatePostInput } from './create-post.js';
|
|
4
|
+
|
|
5
|
+
export const createPostsBatchTool = {
|
|
6
|
+
name: 'create_posts_batch',
|
|
7
|
+
description:
|
|
8
|
+
'Create 1-25 scheduled or immediate social posts in one API request. This is a DESTRUCTIVE WRITE that creates content on the user\'s real social accounts — once scheduled_at passes, posts may go public and cannot be un-posted.\n\n' +
|
|
9
|
+
'REQUIRED BEFORE CALLING:\n' +
|
|
10
|
+
'1. Call `whoami` at the start of any new session to confirm which workspace and user you are acting for.\n' +
|
|
11
|
+
'2. Show the user a preview of EVERY post: account/platform, final caption or thread text, scheduled time in their timezone, media, platform settings, and workspace.\n' +
|
|
12
|
+
'3. Get explicit confirmation for the whole batch before calling. Do not infer consent from an earlier content plan.\n\n' +
|
|
13
|
+
'Each item accepts the same fields as `create_post`, including `thread_posts`, `platform_settings`, `instagram_settings`, media URLs, and workspace_id. The endpoint may partially succeed; failed items are returned with their index.',
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
posts: z
|
|
16
|
+
.array(createPostInputSchema)
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(25)
|
|
19
|
+
.describe('Posts to create. Each item uses the same schema as create_post.'),
|
|
20
|
+
}),
|
|
21
|
+
|
|
22
|
+
async execute(client: PosterlyClient, input: { posts: CreatePostInput[] }) {
|
|
23
|
+
const payloads = input.posts.map((post) => buildCreatePostPayload(post));
|
|
24
|
+
const result = await client.createPostsBatch({ posts: payloads });
|
|
25
|
+
|
|
26
|
+
const lines = [
|
|
27
|
+
'Batch create completed.',
|
|
28
|
+
`• Total: ${result.total}`,
|
|
29
|
+
`• Created: ${result.created}`,
|
|
30
|
+
`• Replayed: ${result.replayed}`,
|
|
31
|
+
`• Failed: ${result.failed}`,
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const item of result.posts.slice(0, 25)) {
|
|
35
|
+
const post = item.post as Record<string, any>;
|
|
36
|
+
const when = post.scheduled_at ? new Date(post.scheduled_at).toLocaleString() : 'now';
|
|
37
|
+
lines.push(
|
|
38
|
+
`• [${item.index}] ID ${post.id} ${post.platform || 'unknown'} ${post.post_type || 'post'} ${post.status || 'created'} at ${when}${item.replayed ? ' (replayed)' : ''}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const error of result.errors.slice(0, 25)) {
|
|
43
|
+
lines.push(`• [${error.index}] ERROR ${error.status}: ${error.error}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return lines.join('\n');
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const createSignedUploadTool = {
|
|
5
|
+
name: 'create_signed_upload',
|
|
6
|
+
description:
|
|
7
|
+
'Create a signed direct-upload URL for a larger image or video. Upload the binary to upload_url, then use public_url with create_post.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
filename: z.string().describe('Filename with extension, e.g. launch-video.mp4'),
|
|
10
|
+
content_type: z.string().describe('MIME type, e.g. video/mp4'),
|
|
11
|
+
size: z.number().int().positive().describe('File size in bytes'),
|
|
12
|
+
}),
|
|
13
|
+
|
|
14
|
+
async execute(
|
|
15
|
+
client: PosterlyClient,
|
|
16
|
+
input: { filename: string; content_type: string; size: number },
|
|
17
|
+
) {
|
|
18
|
+
const signed = await client.getSignedUploadUrl(input.filename, input.content_type, input.size);
|
|
19
|
+
return [
|
|
20
|
+
'Signed upload created.',
|
|
21
|
+
`• Upload URL: ${signed.upload_url}`,
|
|
22
|
+
`• Token: ${signed.token}`,
|
|
23
|
+
`• Storage path: ${signed.path}`,
|
|
24
|
+
`• Public URL after upload: ${signed.public_url}`,
|
|
25
|
+
].join('\n');
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { webhookEventSchema } from './webhook-events.js';
|
|
4
|
+
|
|
5
|
+
export const createWebhookTool = {
|
|
6
|
+
name: 'create_webhook',
|
|
7
|
+
description:
|
|
8
|
+
'Create a webhook subscription for post/account/analytics events. WRITE WITH OUTBOUND SIDE EFFECTS: show the user the target URL, workspace, events, and active state, then get explicit confirmation before calling. The response includes the signing secret once.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
url: z.string().url().describe('HTTPS webhook endpoint URL. Localhost HTTP is allowed by the API for development.'),
|
|
11
|
+
events: z.array(webhookEventSchema).optional().describe('Events to deliver. Defaults to core post lifecycle events.'),
|
|
12
|
+
workspace_id: z.string().optional().describe('Optional workspace filter. Omit for all workspaces owned by the API key user.'),
|
|
13
|
+
description: z.string().max(240).optional(),
|
|
14
|
+
is_active: z.boolean().optional().describe('Defaults to true.'),
|
|
15
|
+
confirm: z.literal(true).describe('Must be true after the user explicitly confirms webhook creation.'),
|
|
16
|
+
}),
|
|
17
|
+
|
|
18
|
+
async execute(
|
|
19
|
+
client: PosterlyClient,
|
|
20
|
+
input: {
|
|
21
|
+
url: string;
|
|
22
|
+
events?: string[];
|
|
23
|
+
workspace_id?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
is_active?: boolean;
|
|
26
|
+
confirm: true;
|
|
27
|
+
},
|
|
28
|
+
) {
|
|
29
|
+
const { confirm: _confirm, ...payload } = input;
|
|
30
|
+
const result = await client.createWebhook(payload);
|
|
31
|
+
const secret = result.secret ? `\nSigning secret: ${result.secret}` : '';
|
|
32
|
+
return `Webhook created: ${result.webhook.id}\nURL: ${result.webhook.url}\nEvents: ${result.webhook.events.join(', ')}${secret}`;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const deleteGoogleBusinessReviewReplyTool = {
|
|
5
|
+
name: 'delete_google_business_review_reply',
|
|
6
|
+
description:
|
|
7
|
+
'Delete the owner reply from a Google Business Profile review. DESTRUCTIVE: confirm the review and location with the user, then pass confirm=true only after explicit confirmation.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
|
|
10
|
+
account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts.'),
|
|
11
|
+
location_id: z.string().optional().describe('Google Business location/platform_user_id.'),
|
|
12
|
+
review_name: z.string().min(1).describe('Full Google review resource name.'),
|
|
13
|
+
confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
|
|
14
|
+
}),
|
|
15
|
+
|
|
16
|
+
async execute(
|
|
17
|
+
client: PosterlyClient,
|
|
18
|
+
input: {
|
|
19
|
+
workspace_id?: string;
|
|
20
|
+
account_id?: number;
|
|
21
|
+
location_id?: string;
|
|
22
|
+
review_name: string;
|
|
23
|
+
confirm: true;
|
|
24
|
+
},
|
|
25
|
+
) {
|
|
26
|
+
const { confirm: _confirm, ...payload } = input;
|
|
27
|
+
const result = await client.deleteGoogleBusinessReviewReply(payload);
|
|
28
|
+
return result.message || 'Google Business review reply deleted successfully.';
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const deleteOAuthClientTool = {
|
|
5
|
+
name: 'delete_oauth_client',
|
|
6
|
+
description:
|
|
7
|
+
'Delete a self-serve OAuth developer client. DESTRUCTIVE: prevents new authorizations for that client_id; existing access tokens remain revocable as API keys.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
client_id: z.string().min(1),
|
|
10
|
+
confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
|
|
11
|
+
}),
|
|
12
|
+
|
|
13
|
+
async execute(
|
|
14
|
+
client: PosterlyClient,
|
|
15
|
+
input: {
|
|
16
|
+
client_id: string;
|
|
17
|
+
confirm: true;
|
|
18
|
+
},
|
|
19
|
+
) {
|
|
20
|
+
const result = await client.deleteOAuthClient(input.client_id);
|
|
21
|
+
return `OAuth client deleted: ${result.client_id}`;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const deletePostGroupTool = {
|
|
5
|
+
name: 'delete_post_group',
|
|
6
|
+
description:
|
|
7
|
+
'Delete every draft/scheduled/failed/paused post matching a caller-defined group_id, post_group_id, api_group_id, or release_id. DESTRUCTIVE: inspect the group first, list the affected posts, and pass confirm=true only after explicit confirmation.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
group_id: z.string().min(1).max(160).describe('Group/release ID to delete.'),
|
|
10
|
+
confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
|
|
11
|
+
}),
|
|
12
|
+
|
|
13
|
+
async execute(client: PosterlyClient, input: { group_id: string; confirm: true }) {
|
|
14
|
+
const result = await client.deletePostGroup(input.group_id);
|
|
15
|
+
return JSON.stringify(result, null, 2);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const deleteWebhookTool = {
|
|
5
|
+
name: 'delete_webhook',
|
|
6
|
+
description:
|
|
7
|
+
'Delete a webhook subscription. DESTRUCTIVE: list the webhook first, show the user its URL/events/workspace, and get explicit confirmation before calling.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
webhook_id: z.string().describe('Webhook ID from list_webhooks.'),
|
|
10
|
+
confirm: z.literal(true).describe('Must be true after the user explicitly confirms webhook deletion.'),
|
|
11
|
+
}),
|
|
12
|
+
|
|
13
|
+
async execute(client: PosterlyClient, input: { webhook_id: string; confirm: true }) {
|
|
14
|
+
const result = await client.deleteWebhook(input.webhook_id);
|
|
15
|
+
return `Webhook deleted: ${result.id}`;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const disconnectAccountTool = {
|
|
5
|
+
name: 'disconnect_account',
|
|
6
|
+
description:
|
|
7
|
+
'Disconnect a connected social account from posterly. DESTRUCTIVE and IRREVERSIBLE: removes the account connection, emits account.disconnected webhooks, and may transfer Instagram scheduled posts to a replacement account.\n\n' +
|
|
8
|
+
'REQUIRED BEFORE CALLING: Call `list_accounts` first, show the user the exact platform, username, account ID, and workspace that will be disconnected, then get explicit confirmation. Pass confirm=true only after that confirmation.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
account_id: z.string().describe('Connected social account ID from list_accounts.'),
|
|
11
|
+
confirm: z.boolean().describe('Must be true after explicit user confirmation.'),
|
|
12
|
+
}),
|
|
13
|
+
|
|
14
|
+
async execute(
|
|
15
|
+
client: PosterlyClient,
|
|
16
|
+
input: { account_id: string; confirm: boolean },
|
|
17
|
+
) {
|
|
18
|
+
if (input.confirm !== true) {
|
|
19
|
+
throw new Error('Refusing to disconnect account without confirm=true after explicit user confirmation.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await client.disconnectAccount(input.account_id);
|
|
23
|
+
const account = result.account || {};
|
|
24
|
+
return [
|
|
25
|
+
`Disconnected ${account.platform || 'account'}${account.username ? ` @${account.username}` : ''} (ID: ${account.id || input.account_id}).`,
|
|
26
|
+
`Transferred posts: ${result.transferred_post_count ?? 0}.`,
|
|
27
|
+
`Stamped posts: ${result.stamped_post_count ?? 0}.`,
|
|
28
|
+
].join('\n');
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const generateVideoTool = {
|
|
5
|
+
name: 'generate_video',
|
|
6
|
+
description:
|
|
7
|
+
'Queue a Veo AI video generation job. COSTS VEO CREDITS: confirm prompt, model, duration, resolution, aspect ratio, audio choice, and credit cost with the user before calling. Poll get_video_job for status and final video_url.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
prompt: z.string().min(5).max(1024),
|
|
10
|
+
negative_prompt: z.string().max(500).optional(),
|
|
11
|
+
aspect_ratio: z.enum(['16:9', '9:16']).default('16:9').optional(),
|
|
12
|
+
duration_seconds: z.union([z.literal(4), z.literal(6), z.literal(8)]).default(8).optional(),
|
|
13
|
+
resolution: z.enum(['720p', '1080p']).default('720p').optional(),
|
|
14
|
+
model: z.enum(['fast', 'standard']).default('fast').optional(),
|
|
15
|
+
image_url: z.string().url().optional(),
|
|
16
|
+
end_image_url: z.string().url().optional(),
|
|
17
|
+
reference_images: z.array(z.object({
|
|
18
|
+
url: z.string().url(),
|
|
19
|
+
type: z.enum(['character', 'object', 'scene']),
|
|
20
|
+
})).max(3).optional(),
|
|
21
|
+
generate_audio: z.boolean().default(true).optional(),
|
|
22
|
+
source_video_url: z.string().url().optional(),
|
|
23
|
+
}),
|
|
24
|
+
|
|
25
|
+
async execute(client: PosterlyClient, input: any) {
|
|
26
|
+
const result = await client.generateVideo(input);
|
|
27
|
+
return [
|
|
28
|
+
'Video generation job queued',
|
|
29
|
+
`Job ID: ${result.job_id}`,
|
|
30
|
+
`Status: ${result.status}`,
|
|
31
|
+
`Credit cost: ${result.credit_cost} (${result.included_credit_cost || 0} included, ${result.purchased_credit_cost || 0} purchased)`,
|
|
32
|
+
result.message,
|
|
33
|
+
'',
|
|
34
|
+
'Raw response:',
|
|
35
|
+
JSON.stringify(result, null, 2),
|
|
36
|
+
].filter(Boolean).join('\n');
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ConnectOption, PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { CONNECT_INPUTS, PLANNED_PLATFORM_IDS } from '../generated/platform-manifest.js';
|
|
4
|
+
|
|
5
|
+
export const getConnectLinkTool = {
|
|
6
|
+
name: 'get_connect_link',
|
|
7
|
+
description:
|
|
8
|
+
'List dashboard handoff links/readiness for connecting social accounts, or get one platform connection URL. Use connection_url in a logged-in browser. Direct OAuth URLs are intentionally not exposed because provider callbacks rely on browser state.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
platform: z
|
|
11
|
+
.enum(CONNECT_INPUTS)
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Connection target such as instagram, meta, linkedin_page, twitter, x, google_business, telegram, reddit, or wordpress. Omit to list live connection targets.'),
|
|
14
|
+
workspace_id: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Filter connected account counts to a workspace.'),
|
|
18
|
+
include_planned: z
|
|
19
|
+
.boolean()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe(`Include planned provider expansion targets from the Postiz parity audit: ${PLANNED_PLATFORM_IDS.join(', ')}.`),
|
|
22
|
+
}),
|
|
23
|
+
|
|
24
|
+
async execute(
|
|
25
|
+
client: PosterlyClient,
|
|
26
|
+
input: { platform?: string; workspace_id?: string; include_planned?: boolean },
|
|
27
|
+
) {
|
|
28
|
+
const result = await client.getConnectLink(input);
|
|
29
|
+
if (result.connect) {
|
|
30
|
+
return formatConnectOption(result.connect, true);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const options = result.connect_options || [];
|
|
34
|
+
const lines = options.map((option) => formatConnectOption(option, false));
|
|
35
|
+
return `Posterly connection options (${options.length}):\n${lines.join('\n\n')}`;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function formatConnectOption(option: ConnectOption, includeRaw: boolean): string {
|
|
40
|
+
const missing = option.missing_env?.length
|
|
41
|
+
? `missing env: ${option.missing_env.map((group) => group.join(' or ')).join(', ')}`
|
|
42
|
+
: 'configured';
|
|
43
|
+
const accounts = option.connected_count ?? option.connected_accounts?.length ?? 0;
|
|
44
|
+
const fields = option.credential_fields?.length
|
|
45
|
+
? `; credential fields: ${option.credential_fields.map((field: any) => field.key).join(', ')}`
|
|
46
|
+
: '';
|
|
47
|
+
const scopes = option.scopes?.length ? `; scopes: ${option.scopes.join(', ')}` : '';
|
|
48
|
+
const raw = includeRaw ? `\n\nRaw connect option:\n${JSON.stringify(option, null, 2)}` : '';
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
`${option.label} (${option.platform})`,
|
|
52
|
+
`Status: ${option.status}; method: ${option.method}; ${missing}; connected accounts: ${accounts}`,
|
|
53
|
+
`Connection URL: ${option.connection_url || 'not available'}${fields}${scopes}`,
|
|
54
|
+
raw,
|
|
55
|
+
].join('\n');
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const getGoogleBusinessReviewLinkTool = {
|
|
5
|
+
name: 'get_google_business_review_link',
|
|
6
|
+
description:
|
|
7
|
+
'Get the direct public Google review link for a connected Google Business Profile location. Requires account_id or location_id.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
|
|
10
|
+
account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts.'),
|
|
11
|
+
location_id: z.string().optional().describe('Google Business location/platform_user_id.'),
|
|
12
|
+
}),
|
|
13
|
+
|
|
14
|
+
async execute(
|
|
15
|
+
client: PosterlyClient,
|
|
16
|
+
input: {
|
|
17
|
+
workspace_id?: string;
|
|
18
|
+
account_id?: number;
|
|
19
|
+
location_id?: string;
|
|
20
|
+
},
|
|
21
|
+
) {
|
|
22
|
+
const result = await client.getGoogleBusinessReviewLink(input);
|
|
23
|
+
return [
|
|
24
|
+
`Review link for ${result.businessName}:`,
|
|
25
|
+
result.reviewLink,
|
|
26
|
+
`Place ID: ${result.placeId}`,
|
|
27
|
+
`Account ID: ${result.account_id}`,
|
|
28
|
+
].join('\n');
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { ALL_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
|
|
4
|
+
|
|
5
|
+
export const getPlatformSchemaTool = {
|
|
6
|
+
name: 'get_platform_schema',
|
|
7
|
+
description:
|
|
8
|
+
'Get the platform schema for a specific connected account or platform. Call this before creating posts with platform_settings so you know allowed post types, media limits, helper tools, and field names.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
account_id: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Connected account ID from list_accounts. Preferred because it returns account context.'),
|
|
14
|
+
platform: z
|
|
15
|
+
.enum(ALL_PLATFORM_INPUTS)
|
|
16
|
+
.optional()
|
|
17
|
+
.describe(`Platform ID such as ${SUPPORTED_PLATFORM_IDS.join(', ')}. Set include_planned to inspect planned providers.`),
|
|
18
|
+
include_planned: z.boolean().optional(),
|
|
19
|
+
}),
|
|
20
|
+
|
|
21
|
+
async execute(
|
|
22
|
+
client: PosterlyClient,
|
|
23
|
+
input: { account_id?: string; platform?: string; include_planned?: boolean },
|
|
24
|
+
) {
|
|
25
|
+
const result = await client.getPlatformSchema(input);
|
|
26
|
+
const schema = result.schema || result.platform || result.platforms?.[0];
|
|
27
|
+
if (!schema) return JSON.stringify(result, null, 2);
|
|
28
|
+
|
|
29
|
+
const fields = schema.settings_schema?.fields || [];
|
|
30
|
+
const helpers = schema.helper_tools || [];
|
|
31
|
+
const accountLine = result.account
|
|
32
|
+
? `Account: ${(result.account.username as string) || result.account.id} (${result.account.platform})\n`
|
|
33
|
+
: '';
|
|
34
|
+
|
|
35
|
+
return [
|
|
36
|
+
`${schema.label} (${schema.id}) schema`,
|
|
37
|
+
accountLine.trim(),
|
|
38
|
+
`Status: ${schema.status}`,
|
|
39
|
+
`Post types: ${schema.post_types.join(', ') || 'n/a'}`,
|
|
40
|
+
`Content limit: ${schema.content_limit ?? 'n/a'}`,
|
|
41
|
+
`Media: ${JSON.stringify(schema.media)}`,
|
|
42
|
+
`Analytics: ${schema.analytics_supported ? 'yes' : 'no'}`,
|
|
43
|
+
fields.length
|
|
44
|
+
? `Settings:\n${fields.map((field: any) => `• ${field.key}${field.required ? ' (required)' : ''}: ${field.description}`).join('\n')}`
|
|
45
|
+
: 'Settings: none',
|
|
46
|
+
helpers.length
|
|
47
|
+
? `Helpers:\n${helpers.map((helper: any) => `• ${helper.name}: ${helper.description}`).join('\n')}`
|
|
48
|
+
: 'Helpers: none',
|
|
49
|
+
`Example settings: ${JSON.stringify(schema.settings_schema?.example || {})}`,
|
|
50
|
+
].filter(Boolean).join('\n');
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const getPostMissingTool = {
|
|
5
|
+
name: 'get_post_missing',
|
|
6
|
+
description:
|
|
7
|
+
'Inspect whether a post is missing required content, media, account, platform settings, or metadata before it can publish. Use this to repair failed or imported posts.',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
post_id: z.number().int().positive().describe('The post ID to inspect.'),
|
|
10
|
+
}),
|
|
11
|
+
|
|
12
|
+
async execute(client: PosterlyClient, input: { post_id: number }) {
|
|
13
|
+
const result = await client.getPostMissing(input.post_id);
|
|
14
|
+
return JSON.stringify(result, null, 2);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|