posterly-mcp-server 0.32.0 → 0.33.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/dist/lib/api-client.d.ts +9 -0
- package/dist/tools/create-connect-session.d.ts +2 -2
- package/dist/tools/create-post.js +8 -5
- package/dist/tools/get-connect-link.d.ts +2 -2
- package/dist/tools/get-platform-schema.js +5 -1
- package/dist/tools/list-accounts.js +18 -3
- package/dist/tools/start-signup.js +1 -1
- package/dist/tools/update-post.js +6 -3
- package/package.json +1 -1
- package/scripts/test-tool-annotations.mjs +12 -1
- package/src/lib/api-client.ts +5 -0
- package/src/tools/create-post.ts +8 -5
- package/src/tools/get-platform-schema.ts +5 -1
- package/src/tools/list-accounts.ts +19 -3
- package/src/tools/start-signup.ts +1 -1
- package/src/tools/update-post.ts +6 -3
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -5,6 +5,15 @@ export interface Account {
|
|
|
5
5
|
account_type?: string;
|
|
6
6
|
profile_picture_url?: string;
|
|
7
7
|
workspace_id?: string | null;
|
|
8
|
+
connection_mode?: 'meta' | 'instagram_direct' | null;
|
|
9
|
+
instagram_tagging?: {
|
|
10
|
+
media_user_tags?: {
|
|
11
|
+
supported?: boolean;
|
|
12
|
+
};
|
|
13
|
+
collaborators?: {
|
|
14
|
+
supported?: boolean;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
8
17
|
}
|
|
9
18
|
export interface Brand {
|
|
10
19
|
id: string;
|
|
@@ -9,12 +9,12 @@ export declare const createConnectSessionTool: {
|
|
|
9
9
|
auto_start: z.ZodOptional<z.ZodBoolean>;
|
|
10
10
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
11
11
|
}, "strip", z.ZodTypeAny, {
|
|
12
|
-
platform: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "
|
|
12
|
+
platform: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
|
|
13
13
|
workspace_id?: string | undefined;
|
|
14
14
|
auto_start?: boolean | undefined;
|
|
15
15
|
debug?: boolean | undefined;
|
|
16
16
|
}, {
|
|
17
|
-
platform: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "
|
|
17
|
+
platform: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
|
|
18
18
|
workspace_id?: string | undefined;
|
|
19
19
|
auto_start?: boolean | undefined;
|
|
20
20
|
debug?: boolean | undefined;
|
|
@@ -8,10 +8,13 @@ export const instagramSettingsSchema = z.object({
|
|
|
8
8
|
graduation_strategy: z.enum(['MANUAL', 'SS_PERFORMANCE']).optional(),
|
|
9
9
|
collaborators: z
|
|
10
10
|
.array(z.union([z.string(), z.object({ label: z.string().optional(), username: z.string().optional(), handle: z.string().optional() })]))
|
|
11
|
-
.
|
|
11
|
+
.max(3)
|
|
12
|
+
.optional()
|
|
13
|
+
.describe('Native Instagram co-author invitations. Meta-linked accounts only; feed images, Reels, and carousels; not Stories.'),
|
|
12
14
|
user_tags: z
|
|
13
15
|
.array(z.union([z.string(), z.object({ username: z.string().optional(), label: z.string().optional(), handle: z.string().optional() })]))
|
|
14
|
-
.optional()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Native clickable media tags, not caption @mentions. Requires an eligible Meta-linked Instagram connection with publishing permission; feed images, Reels/video, and Stories. Tags apply to each carousel item and the first Story in a Story Series.'),
|
|
15
18
|
first_comment: z.string().optional(),
|
|
16
19
|
media_alt_texts: z.record(z.string(), z.string()).optional(),
|
|
17
20
|
reel_cover_url: z.string().optional(),
|
|
@@ -94,7 +97,7 @@ export const createPostPayloadInputSchema = z.object({
|
|
|
94
97
|
.enum(SUPPORTED_PLATFORM_INPUTS)
|
|
95
98
|
.optional()
|
|
96
99
|
.describe(`Platform name (required with username). Supported: ${SUPPORTED_PLATFORM_IDS.join(', ')}. Aliases such as x, gmb, and instagram-standalone are accepted.`),
|
|
97
|
-
caption: z.string().optional().describe('The post caption/text content. Ignored when `thread_posts` is provided.'),
|
|
100
|
+
caption: z.string().optional().describe('The post caption/text content. On Instagram, @username creates a caption mention only; it is not a native media tag or collaborator invitation. Ignored when `thread_posts` is provided.'),
|
|
98
101
|
scheduled_at: z
|
|
99
102
|
.string()
|
|
100
103
|
.optional()
|
|
@@ -114,7 +117,7 @@ export const createPostPayloadInputSchema = z.object({
|
|
|
114
117
|
.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.'),
|
|
115
118
|
instagram_settings: instagramSettingsSchema
|
|
116
119
|
.optional()
|
|
117
|
-
.describe('Instagram-specific settings
|
|
120
|
+
.describe('Instagram-specific settings. If “tag” is ambiguous, distinguish caption @mentions (put @username in caption), native media tags (user_tags), and co-author invitations (collaborators). Native media tags and collaborators require an eligible Meta-linked connection. Call get_platform_schema(account_id) first.'),
|
|
118
121
|
platform_settings: platformSettingsSchema
|
|
119
122
|
.optional()
|
|
120
123
|
.describe('Platform-specific settings for the selected account. Use this for non-Instagram settings and prefer it over raw metadata.'),
|
|
@@ -183,7 +186,7 @@ export const createPostTool = {
|
|
|
183
186
|
'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' +
|
|
184
187
|
'MEDIA URLS: Posterly storage URLs are used as-is. Third-party HTTP(S) media URLs are copied into Posterly storage before the post is saved, so API-created posts do not later fail on expired signed URLs. Short-lived signed URLs must still be live when this tool runs. You can also call `upload_media_from_url` first if you want to upload once and reuse the returned Posterly URL across posts.\n\n' +
|
|
185
188
|
'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' +
|
|
186
|
-
'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/playlist, LinkedIn document titles/mentions/polls (poll.question, poll.options 2-4, poll.duration ONE_DAY|THREE_DAYS|SEVEN_DAYS|FOURTEEN_DAYS; text-only)/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.\n\n' +
|
|
189
|
+
'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/playlist, LinkedIn document titles/mentions/polls (poll.question, poll.options 2-4, poll.duration ONE_DAY|THREE_DAYS|SEVEN_DAYS|FOURTEEN_DAYS; text-only)/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/native media tags/collaborators/first comments/trial Reels. For Instagram, @username in caption is only a caption mention; `user_tags` creates native media tags and `collaborators` sends co-author invitations only when the selected Meta-linked account reports those capabilities. Call `get_platform_schema` with the account ID first. `instagram_settings` remains as a backwards-compatible alias.\n\n' +
|
|
187
190
|
'TIKTOK MEDIA: TikTok supports a single video or a photo slideshow of 1 to 35 images, never multiple videos. Image media auto-detects as a slideshow, so you usually do not set post_type or media_type for photos and every image is posted. Pass `platform_settings.media_type: "PHOTO"` to force a slideshow explicitly.',
|
|
188
191
|
inputSchema: createPostInputSchema,
|
|
189
192
|
async execute(client, input) {
|
|
@@ -11,12 +11,12 @@ export declare const getConnectLinkTool: {
|
|
|
11
11
|
}, "strip", z.ZodTypeAny, {
|
|
12
12
|
workspace_id?: string | undefined;
|
|
13
13
|
include_planned?: boolean | undefined;
|
|
14
|
-
platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "
|
|
14
|
+
platform?: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter" | undefined;
|
|
15
15
|
debug?: boolean | undefined;
|
|
16
16
|
}, {
|
|
17
17
|
workspace_id?: string | undefined;
|
|
18
18
|
include_planned?: boolean | undefined;
|
|
19
|
-
platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "
|
|
19
|
+
platform?: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter" | undefined;
|
|
20
20
|
debug?: boolean | undefined;
|
|
21
21
|
}>;
|
|
22
22
|
execute(client: PosterlyClient, input: {
|
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { ALL_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
|
|
3
3
|
export const getPlatformSchemaTool = {
|
|
4
4
|
name: 'get_platform_schema',
|
|
5
|
-
description: 'Get the platform schema for a specific connected account or platform.
|
|
5
|
+
description: 'Get the platform schema for a specific connected account or platform. For Instagram, pass account_id before native tags/collaborators so connection-specific capabilities are returned.',
|
|
6
6
|
inputSchema: z.object({
|
|
7
7
|
account_id: z
|
|
8
8
|
.string()
|
|
@@ -24,9 +24,13 @@ export const getPlatformSchemaTool = {
|
|
|
24
24
|
const accountLine = result.account
|
|
25
25
|
? `Account: ${result.account.username || result.account.id} (${result.account.platform})\n`
|
|
26
26
|
: '';
|
|
27
|
+
const instagramTaggingLine = result.account?.instagram_tagging
|
|
28
|
+
? `Instagram tagging capabilities: ${JSON.stringify(result.account.instagram_tagging)}`
|
|
29
|
+
: '';
|
|
27
30
|
return [
|
|
28
31
|
`${schema.label} (${schema.id}) schema`,
|
|
29
32
|
accountLine.trim(),
|
|
33
|
+
instagramTaggingLine,
|
|
30
34
|
`Status: ${schema.status}`,
|
|
31
35
|
`Post types: ${schema.post_types.join(', ') || 'n/a'}`,
|
|
32
36
|
`Content limit: ${schema.content_limit ?? 'n/a'}`,
|
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { code, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
|
|
3
3
|
export const listAccountsTool = {
|
|
4
4
|
name: 'list_accounts',
|
|
5
|
-
description: 'List connected social media accounts.
|
|
5
|
+
description: 'List connected social media accounts. Instagram rows identify direct-login versus Meta-linked connections and whether native media tags/collaborators are available. Check this before using Instagram user_tags or collaborators.',
|
|
6
6
|
inputSchema: z.object({
|
|
7
7
|
workspace_id: z
|
|
8
8
|
.string()
|
|
@@ -16,14 +16,29 @@ export const listAccountsTool = {
|
|
|
16
16
|
? mdEmpty('connected accounts', 'This workspace does not have any social accounts connected yet.')
|
|
17
17
|
: mdEmpty('connected accounts', 'Posterly does not see any connected social accounts yet.', 'Connect accounts in the Posterly dashboard, then run this again.');
|
|
18
18
|
}
|
|
19
|
+
const hasInstagram = accounts.some((account) => account.platform === 'instagram');
|
|
19
20
|
return [
|
|
20
21
|
mdTitle(`Connected accounts (${accounts.length})`),
|
|
21
|
-
mdTable(
|
|
22
|
+
mdTable(hasInstagram
|
|
23
|
+
? ['Platform', 'Account', 'Account ID', 'Workspace', 'Connection', 'Native tags', 'Collaborators']
|
|
24
|
+
: ['Platform', 'Account', 'Account ID', 'Workspace'], accounts.map((account) => [
|
|
22
25
|
account.platform,
|
|
23
26
|
`@${account.username}`,
|
|
24
27
|
code(account.id),
|
|
25
28
|
account.workspace_id ? code(account.workspace_id) : 'default',
|
|
29
|
+
...(hasInstagram
|
|
30
|
+
? account.platform === 'instagram'
|
|
31
|
+
? [
|
|
32
|
+
account.connection_mode === 'meta' ? 'Meta-linked' : account.connection_mode === 'instagram_direct' ? 'Direct login' : 'unknown',
|
|
33
|
+
account.instagram_tagging?.media_user_tags?.supported ? 'yes' : 'no',
|
|
34
|
+
account.instagram_tagging?.collaborators?.supported ? 'yes' : 'no',
|
|
35
|
+
]
|
|
36
|
+
: ['—', '—', '—']
|
|
37
|
+
: []),
|
|
26
38
|
])),
|
|
27
|
-
|
|
39
|
+
hasInstagram
|
|
40
|
+
? '_Instagram caption @mentions use @username in caption text. Native media tags and collaborator invitations require an eligible Meta-linked account with the required publishing permissions._'
|
|
41
|
+
: '',
|
|
42
|
+
].filter(Boolean).join('\n\n');
|
|
28
43
|
},
|
|
29
44
|
};
|
|
@@ -75,7 +75,7 @@ function formatSignupStart(response, includeRaw) {
|
|
|
75
75
|
completeUrl ? `Completion URL: ${completeUrl}` : '',
|
|
76
76
|
'',
|
|
77
77
|
'Next steps for the AI agent:',
|
|
78
|
-
'1. Send the Checkout handoff URL to the user so they can pay securely in their browser. Use the raw Stripe checkout URL only if you preserve the full URL including any fragment.',
|
|
78
|
+
'1. Send the Checkout handoff URL to the user so they can pay securely in their browser. Use the raw Stripe checkout URL only if you preserve the full URL including any fragment. posterly does not email this link, so do not tell the user to check their inbox at this stage.',
|
|
79
79
|
'2. Call get_signup_session with the signup session ID until the status changes.',
|
|
80
80
|
'3. Tell the user when Posterly sends the password setup email.',
|
|
81
81
|
'4. If status becomes agent_access_required, ask the user to install or paste the Posterly MCP/API setup instructions into this trusted AI chat.',
|
|
@@ -7,10 +7,13 @@ const instagramSettingsSchema = z.object({
|
|
|
7
7
|
graduation_strategy: z.enum(['MANUAL', 'SS_PERFORMANCE']).optional(),
|
|
8
8
|
collaborators: z
|
|
9
9
|
.array(z.union([z.string(), z.object({ label: z.string().optional(), username: z.string().optional(), handle: z.string().optional() })]))
|
|
10
|
-
.
|
|
10
|
+
.max(3)
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('Native Instagram co-author invitations. Meta-linked accounts only; feed images, Reels, and carousels; not Stories.'),
|
|
11
13
|
user_tags: z
|
|
12
14
|
.array(z.union([z.string(), z.object({ username: z.string().optional(), label: z.string().optional(), handle: z.string().optional() })]))
|
|
13
|
-
.optional()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Native clickable media tags, not caption @mentions. Requires an eligible Meta-linked Instagram connection with publishing permission; feed images, Reels/video, and Stories. Tags apply to each carousel item and the first Story in a Story Series.'),
|
|
14
17
|
first_comment: z.string().optional(),
|
|
15
18
|
media_alt_texts: z.record(z.string(), z.string()).optional(),
|
|
16
19
|
reel_cover_url: z.string().optional(),
|
|
@@ -93,7 +96,7 @@ export const updatePostTool = {
|
|
|
93
96
|
reel_thumb_offset: z.number().nonnegative().optional().describe('Frame offset in milliseconds to use as the reel cover.'),
|
|
94
97
|
instagram_settings: instagramSettingsSchema
|
|
95
98
|
.optional()
|
|
96
|
-
.describe('Instagram-specific updates
|
|
99
|
+
.describe('Instagram-specific updates. Distinguish caption @mentions, native media user_tags, and collaborators. Native media tags and collaborators require an eligible Meta-linked connection. Call get_platform_schema(account_id) first.'),
|
|
97
100
|
platform_settings: platformSettingsSchema
|
|
98
101
|
.optional()
|
|
99
102
|
.describe('Platform-specific settings to merge into the post. Prefer this over raw metadata.'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posterly-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"mcpName": "io.github.awpthorp/posterly",
|
|
5
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)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
2
5
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
6
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
4
7
|
|
|
8
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const annotations = JSON.parse(readFileSync(resolve(root, 'tool-annotations.json'), 'utf8'));
|
|
10
|
+
const expectedToolCount = annotations.knownTools.length;
|
|
11
|
+
|
|
5
12
|
const transport = new StdioClientTransport({
|
|
6
13
|
command: process.execPath,
|
|
7
14
|
args: ['dist/index.js'],
|
|
@@ -12,7 +19,11 @@ try {
|
|
|
12
19
|
await client.connect(transport);
|
|
13
20
|
const { tools } = await client.listTools();
|
|
14
21
|
|
|
15
|
-
assert.equal(
|
|
22
|
+
assert.equal(
|
|
23
|
+
tools.length,
|
|
24
|
+
expectedToolCount,
|
|
25
|
+
`stdio tool count ${tools.length} must match tool-annotations.json knownTools (${expectedToolCount})`,
|
|
26
|
+
);
|
|
16
27
|
for (const tool of tools) {
|
|
17
28
|
assert(tool.annotations, `${tool.name} is missing annotations`);
|
|
18
29
|
assert(tool.annotations.title, `${tool.name} is missing an annotation title`);
|
package/src/lib/api-client.ts
CHANGED
|
@@ -8,6 +8,11 @@ export interface Account {
|
|
|
8
8
|
account_type?: string;
|
|
9
9
|
profile_picture_url?: string;
|
|
10
10
|
workspace_id?: string | null;
|
|
11
|
+
connection_mode?: 'meta' | 'instagram_direct' | null;
|
|
12
|
+
instagram_tagging?: {
|
|
13
|
+
media_user_tags?: { supported?: boolean };
|
|
14
|
+
collaborators?: { supported?: boolean };
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export interface Brand {
|
package/src/tools/create-post.ts
CHANGED
|
@@ -10,10 +10,13 @@ export const instagramSettingsSchema = z.object({
|
|
|
10
10
|
graduation_strategy: z.enum(['MANUAL', 'SS_PERFORMANCE']).optional(),
|
|
11
11
|
collaborators: z
|
|
12
12
|
.array(z.union([z.string(), z.object({ label: z.string().optional(), username: z.string().optional(), handle: z.string().optional() })]))
|
|
13
|
-
.
|
|
13
|
+
.max(3)
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Native Instagram co-author invitations. Meta-linked accounts only; feed images, Reels, and carousels; not Stories.'),
|
|
14
16
|
user_tags: z
|
|
15
17
|
.array(z.union([z.string(), z.object({ username: z.string().optional(), label: z.string().optional(), handle: z.string().optional() })]))
|
|
16
|
-
.optional()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Native clickable media tags, not caption @mentions. Requires an eligible Meta-linked Instagram connection with publishing permission; feed images, Reels/video, and Stories. Tags apply to each carousel item and the first Story in a Story Series.'),
|
|
17
20
|
first_comment: z.string().optional(),
|
|
18
21
|
media_alt_texts: z.record(z.string(), z.string()).optional(),
|
|
19
22
|
reel_cover_url: z.string().optional(),
|
|
@@ -98,7 +101,7 @@ export const createPostPayloadInputSchema = z.object({
|
|
|
98
101
|
.enum(SUPPORTED_PLATFORM_INPUTS)
|
|
99
102
|
.optional()
|
|
100
103
|
.describe(`Platform name (required with username). Supported: ${SUPPORTED_PLATFORM_IDS.join(', ')}. Aliases such as x, gmb, and instagram-standalone are accepted.`),
|
|
101
|
-
caption: z.string().optional().describe('The post caption/text content. Ignored when `thread_posts` is provided.'),
|
|
104
|
+
caption: z.string().optional().describe('The post caption/text content. On Instagram, @username creates a caption mention only; it is not a native media tag or collaborator invitation. Ignored when `thread_posts` is provided.'),
|
|
102
105
|
scheduled_at: z
|
|
103
106
|
.string()
|
|
104
107
|
.optional()
|
|
@@ -118,7 +121,7 @@ export const createPostPayloadInputSchema = z.object({
|
|
|
118
121
|
.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.'),
|
|
119
122
|
instagram_settings: instagramSettingsSchema
|
|
120
123
|
.optional()
|
|
121
|
-
|
|
124
|
+
.describe('Instagram-specific settings. If “tag” is ambiguous, distinguish caption @mentions (put @username in caption), native media tags (user_tags), and co-author invitations (collaborators). Native media tags and collaborators require an eligible Meta-linked connection. Call get_platform_schema(account_id) first.'),
|
|
122
125
|
platform_settings: platformSettingsSchema
|
|
123
126
|
.optional()
|
|
124
127
|
.describe('Platform-specific settings for the selected account. Use this for non-Instagram settings and prefer it over raw metadata.'),
|
|
@@ -201,7 +204,7 @@ export const createPostTool = {
|
|
|
201
204
|
'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' +
|
|
202
205
|
'MEDIA URLS: Posterly storage URLs are used as-is. Third-party HTTP(S) media URLs are copied into Posterly storage before the post is saved, so API-created posts do not later fail on expired signed URLs. Short-lived signed URLs must still be live when this tool runs. You can also call `upload_media_from_url` first if you want to upload once and reuse the returned Posterly URL across posts.\n\n' +
|
|
203
206
|
'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' +
|
|
204
|
-
'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/playlist, LinkedIn document titles/mentions/polls (poll.question, poll.options 2-4, poll.duration ONE_DAY|THREE_DAYS|SEVEN_DAYS|FOURTEEN_DAYS; text-only)/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.\n\n' +
|
|
207
|
+
'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/playlist, LinkedIn document titles/mentions/polls (poll.question, poll.options 2-4, poll.duration ONE_DAY|THREE_DAYS|SEVEN_DAYS|FOURTEEN_DAYS; text-only)/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/native media tags/collaborators/first comments/trial Reels. For Instagram, @username in caption is only a caption mention; `user_tags` creates native media tags and `collaborators` sends co-author invitations only when the selected Meta-linked account reports those capabilities. Call `get_platform_schema` with the account ID first. `instagram_settings` remains as a backwards-compatible alias.\n\n' +
|
|
205
208
|
'TIKTOK MEDIA: TikTok supports a single video or a photo slideshow of 1 to 35 images, never multiple videos. Image media auto-detects as a slideshow, so you usually do not set post_type or media_type for photos and every image is posted. Pass `platform_settings.media_type: "PHOTO"` to force a slideshow explicitly.',
|
|
206
209
|
inputSchema: createPostInputSchema,
|
|
207
210
|
|
|
@@ -5,7 +5,7 @@ import { ALL_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platfo
|
|
|
5
5
|
export const getPlatformSchemaTool = {
|
|
6
6
|
name: 'get_platform_schema',
|
|
7
7
|
description:
|
|
8
|
-
'Get the platform schema for a specific connected account or platform.
|
|
8
|
+
'Get the platform schema for a specific connected account or platform. For Instagram, pass account_id before native tags/collaborators so connection-specific capabilities are returned.',
|
|
9
9
|
inputSchema: z.object({
|
|
10
10
|
account_id: z
|
|
11
11
|
.string()
|
|
@@ -31,10 +31,14 @@ export const getPlatformSchemaTool = {
|
|
|
31
31
|
const accountLine = result.account
|
|
32
32
|
? `Account: ${(result.account.username as string) || result.account.id} (${result.account.platform})\n`
|
|
33
33
|
: '';
|
|
34
|
+
const instagramTaggingLine = result.account?.instagram_tagging
|
|
35
|
+
? `Instagram tagging capabilities: ${JSON.stringify(result.account.instagram_tagging)}`
|
|
36
|
+
: '';
|
|
34
37
|
|
|
35
38
|
return [
|
|
36
39
|
`${schema.label} (${schema.id}) schema`,
|
|
37
40
|
accountLine.trim(),
|
|
41
|
+
instagramTaggingLine,
|
|
38
42
|
`Status: ${schema.status}`,
|
|
39
43
|
`Post types: ${schema.post_types.join(', ') || 'n/a'}`,
|
|
40
44
|
`Content limit: ${schema.content_limit ?? 'n/a'}`,
|
|
@@ -5,7 +5,7 @@ import { code, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
|
|
|
5
5
|
export const listAccountsTool = {
|
|
6
6
|
name: 'list_accounts',
|
|
7
7
|
description:
|
|
8
|
-
'List connected social media accounts.
|
|
8
|
+
'List connected social media accounts. Instagram rows identify direct-login versus Meta-linked connections and whether native media tags/collaborators are available. Check this before using Instagram user_tags or collaborators.',
|
|
9
9
|
inputSchema: z.object({
|
|
10
10
|
workspace_id: z
|
|
11
11
|
.string()
|
|
@@ -22,17 +22,33 @@ export const listAccountsTool = {
|
|
|
22
22
|
: mdEmpty('connected accounts', 'Posterly does not see any connected social accounts yet.', 'Connect accounts in the Posterly dashboard, then run this again.');
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const hasInstagram = accounts.some((account) => account.platform === 'instagram');
|
|
26
|
+
|
|
25
27
|
return [
|
|
26
28
|
mdTitle(`Connected accounts (${accounts.length})`),
|
|
27
29
|
mdTable(
|
|
28
|
-
|
|
30
|
+
hasInstagram
|
|
31
|
+
? ['Platform', 'Account', 'Account ID', 'Workspace', 'Connection', 'Native tags', 'Collaborators']
|
|
32
|
+
: ['Platform', 'Account', 'Account ID', 'Workspace'],
|
|
29
33
|
accounts.map((account) => [
|
|
30
34
|
account.platform,
|
|
31
35
|
`@${account.username}`,
|
|
32
36
|
code(account.id),
|
|
33
37
|
account.workspace_id ? code(account.workspace_id) : 'default',
|
|
38
|
+
...(hasInstagram
|
|
39
|
+
? account.platform === 'instagram'
|
|
40
|
+
? [
|
|
41
|
+
account.connection_mode === 'meta' ? 'Meta-linked' : account.connection_mode === 'instagram_direct' ? 'Direct login' : 'unknown',
|
|
42
|
+
account.instagram_tagging?.media_user_tags?.supported ? 'yes' : 'no',
|
|
43
|
+
account.instagram_tagging?.collaborators?.supported ? 'yes' : 'no',
|
|
44
|
+
]
|
|
45
|
+
: ['—', '—', '—']
|
|
46
|
+
: []),
|
|
34
47
|
]),
|
|
35
48
|
),
|
|
36
|
-
|
|
49
|
+
hasInstagram
|
|
50
|
+
? '_Instagram caption @mentions use @username in caption text. Native media tags and collaborator invitations require an eligible Meta-linked account with the required publishing permissions._'
|
|
51
|
+
: '',
|
|
52
|
+
].filter(Boolean).join('\n\n');
|
|
37
53
|
},
|
|
38
54
|
};
|
|
@@ -96,7 +96,7 @@ function formatSignupStart(response: PublicSignupResponse, includeRaw: boolean):
|
|
|
96
96
|
completeUrl ? `Completion URL: ${completeUrl}` : '',
|
|
97
97
|
'',
|
|
98
98
|
'Next steps for the AI agent:',
|
|
99
|
-
'1. Send the Checkout handoff URL to the user so they can pay securely in their browser. Use the raw Stripe checkout URL only if you preserve the full URL including any fragment.',
|
|
99
|
+
'1. Send the Checkout handoff URL to the user so they can pay securely in their browser. Use the raw Stripe checkout URL only if you preserve the full URL including any fragment. posterly does not email this link, so do not tell the user to check their inbox at this stage.',
|
|
100
100
|
'2. Call get_signup_session with the signup session ID until the status changes.',
|
|
101
101
|
'3. Tell the user when Posterly sends the password setup email.',
|
|
102
102
|
'4. If status becomes agent_access_required, ask the user to install or paste the Posterly MCP/API setup instructions into this trusted AI chat.',
|
package/src/tools/update-post.ts
CHANGED
|
@@ -9,10 +9,13 @@ const instagramSettingsSchema = z.object({
|
|
|
9
9
|
graduation_strategy: z.enum(['MANUAL', 'SS_PERFORMANCE']).optional(),
|
|
10
10
|
collaborators: z
|
|
11
11
|
.array(z.union([z.string(), z.object({ label: z.string().optional(), username: z.string().optional(), handle: z.string().optional() })]))
|
|
12
|
-
.
|
|
12
|
+
.max(3)
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('Native Instagram co-author invitations. Meta-linked accounts only; feed images, Reels, and carousels; not Stories.'),
|
|
13
15
|
user_tags: z
|
|
14
16
|
.array(z.union([z.string(), z.object({ username: z.string().optional(), label: z.string().optional(), handle: z.string().optional() })]))
|
|
15
|
-
.optional()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Native clickable media tags, not caption @mentions. Requires an eligible Meta-linked Instagram connection with publishing permission; feed images, Reels/video, and Stories. Tags apply to each carousel item and the first Story in a Story Series.'),
|
|
16
19
|
first_comment: z.string().optional(),
|
|
17
20
|
media_alt_texts: z.record(z.string(), z.string()).optional(),
|
|
18
21
|
reel_cover_url: z.string().optional(),
|
|
@@ -98,7 +101,7 @@ export const updatePostTool = {
|
|
|
98
101
|
reel_thumb_offset: z.number().nonnegative().optional().describe('Frame offset in milliseconds to use as the reel cover.'),
|
|
99
102
|
instagram_settings: instagramSettingsSchema
|
|
100
103
|
.optional()
|
|
101
|
-
.describe('Instagram-specific updates
|
|
104
|
+
.describe('Instagram-specific updates. Distinguish caption @mentions, native media user_tags, and collaborators. Native media tags and collaborators require an eligible Meta-linked connection. Call get_platform_schema(account_id) first.'),
|
|
102
105
|
platform_settings: platformSettingsSchema
|
|
103
106
|
.optional()
|
|
104
107
|
.describe('Platform-specific settings to merge into the post. Prefer this over raw metadata.'),
|