posterly-mcp-server 0.20.4 → 0.20.7
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 +2 -2
- package/dist/index.js +11 -10
- package/dist/lib/api-client.d.ts +29 -0
- package/dist/lib/api-client.js +3 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/ask-support.d.ts +0 -33
- package/dist/tools/ask-support.js +0 -6
- package/dist/tools/create-api-key.d.ts +26 -0
- package/dist/tools/create-api-key.js +35 -0
- package/dist/tools/create-post.js +3 -2
- package/dist/tools/update-post.js +2 -2
- package/dist/tools/upload-media-from-url.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +16 -15
- package/src/lib/api-client.ts +41 -0
- package/src/lib/version.ts +1 -1
- package/src/tools/ask-support.ts +0 -8
- package/src/tools/create-api-key.ts +41 -0
- package/src/tools/create-post.ts +3 -2
- package/src/tools/update-post.ts +2 -2
- package/src/tools/upload-media-from-url.ts +1 -1
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ Add the same server definition to your Cursor MCP settings:
|
|
|
108
108
|
|
|
109
109
|
## Available tools
|
|
110
110
|
|
|
111
|
-
`posterly-mcp-server@0.20.
|
|
111
|
+
`posterly-mcp-server@0.20.7` exposes 58 tools.
|
|
112
112
|
|
|
113
113
|
Public setup tools work before `POSTERLY_API_KEY` exists:
|
|
114
114
|
|
|
@@ -125,6 +125,7 @@ Authenticated tools require `POSTERLY_API_KEY`:
|
|
|
125
125
|
- `get_connect_link`
|
|
126
126
|
- `create_connect_session` (create a guided browser handoff for connecting a social account)
|
|
127
127
|
- `get_connect_session` (poll connection progress while the user approves OAuth or enters credentials)
|
|
128
|
+
- `create_api_key` (create a new API key after explicit confirmation; scopes cannot exceed the calling dashboard key)
|
|
128
129
|
- `list_oauth_clients`
|
|
129
130
|
- `create_oauth_client` (create a public PKCE client after explicit confirmation)
|
|
130
131
|
- `update_oauth_client` (update redirect URIs/scopes after explicit confirmation)
|
|
@@ -140,7 +141,6 @@ Authenticated tools require `POSTERLY_API_KEY`:
|
|
|
140
141
|
- `get_post`
|
|
141
142
|
- `get_post_missing`
|
|
142
143
|
- `ask_support` (authenticated docs-backed support with read-only account/post diagnostics; human tickets require explicit confirmation)
|
|
143
|
-
- `get_support` (alias for `ask_support` for agents that look for get-style support tools)
|
|
144
144
|
- `create_post` (supports `thread_posts: string[]` for X / Threads reply chains, plus `platform_settings` for platform-specific composer controls)
|
|
145
145
|
- `create_posts_batch` (create up to 25 confirmed posts in one API request)
|
|
146
146
|
- `update_post` (also accepts `platform_settings`)
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import { listPostsTool } from './tools/list-posts.js';
|
|
|
20
20
|
import { uploadMediaTool } from './tools/upload-media.js';
|
|
21
21
|
import { getPostTool } from './tools/get-post.js';
|
|
22
22
|
import { getPostMissingTool } from './tools/get-post-missing.js';
|
|
23
|
-
import { askSupportTool
|
|
23
|
+
import { askSupportTool } from './tools/ask-support.js';
|
|
24
24
|
import { updatePostTool } from './tools/update-post.js';
|
|
25
25
|
import { updatePostStatusTool } from './tools/update-post-status.js';
|
|
26
26
|
import { updatePostReleaseIdTool } from './tools/update-post-release-id.js';
|
|
@@ -56,6 +56,7 @@ import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
|
|
|
56
56
|
import { getConnectLinkTool } from './tools/get-connect-link.js';
|
|
57
57
|
import { createConnectSessionTool } from './tools/create-connect-session.js';
|
|
58
58
|
import { getConnectSessionTool } from './tools/get-connect-session.js';
|
|
59
|
+
import { createApiKeyTool } from './tools/create-api-key.js';
|
|
59
60
|
import { listOAuthClientsTool } from './tools/list-oauth-clients.js';
|
|
60
61
|
import { createOAuthClientTool } from './tools/create-oauth-client.js';
|
|
61
62
|
import { updateOAuthClientTool } from './tools/update-oauth-client.js';
|
|
@@ -156,6 +157,15 @@ server.tool(getConnectSessionTool.name, getConnectSessionTool.description, getCo
|
|
|
156
157
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
157
158
|
}
|
|
158
159
|
});
|
|
160
|
+
server.tool(createApiKeyTool.name, createApiKeyTool.description, createApiKeyTool.inputSchema.shape, async (input) => {
|
|
161
|
+
try {
|
|
162
|
+
const text = await createApiKeyTool.execute(client, input);
|
|
163
|
+
return { content: [{ type: 'text', text }] };
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
159
169
|
server.tool(listOAuthClientsTool.name, listOAuthClientsTool.description, listOAuthClientsTool.inputSchema.shape, async () => {
|
|
160
170
|
try {
|
|
161
171
|
const text = await listOAuthClientsTool.execute(client);
|
|
@@ -399,15 +409,6 @@ server.tool(askSupportTool.name, askSupportTool.description, askSupportTool.inpu
|
|
|
399
409
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
400
410
|
}
|
|
401
411
|
});
|
|
402
|
-
server.tool(getSupportTool.name, getSupportTool.description, getSupportTool.inputSchema.shape, async (input) => {
|
|
403
|
-
try {
|
|
404
|
-
const text = await getSupportTool.execute(client, input);
|
|
405
|
-
return { content: [{ type: 'text', text }] };
|
|
406
|
-
}
|
|
407
|
-
catch (err) {
|
|
408
|
-
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
412
|
server.tool(updatePostTool.name, updatePostTool.description, updatePostTool.inputSchema.shape, async (input) => {
|
|
412
413
|
try {
|
|
413
414
|
const text = await updatePostTool.execute(client, input);
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -159,6 +159,34 @@ export type AskSupportPayload = {
|
|
|
159
159
|
request_human?: boolean;
|
|
160
160
|
confirm_escalation?: boolean;
|
|
161
161
|
};
|
|
162
|
+
export type ApiKeyScope = 'accounts:read' | 'accounts:write' | 'posts:read' | 'posts:write' | 'media:write' | 'analytics:read';
|
|
163
|
+
export type CreateApiKeyPayload = {
|
|
164
|
+
name?: string;
|
|
165
|
+
scopes?: ApiKeyScope[];
|
|
166
|
+
workspace_id?: string;
|
|
167
|
+
expires_in_days?: number;
|
|
168
|
+
confirm: true;
|
|
169
|
+
};
|
|
170
|
+
export type CreateApiKeyResponse = {
|
|
171
|
+
key: string;
|
|
172
|
+
api_key: {
|
|
173
|
+
id: string;
|
|
174
|
+
name: string;
|
|
175
|
+
key_prefix: string;
|
|
176
|
+
scopes: ApiKeyScope[];
|
|
177
|
+
workspace_id?: string | null;
|
|
178
|
+
expires_at?: string | null;
|
|
179
|
+
created_at: string;
|
|
180
|
+
};
|
|
181
|
+
warning?: string;
|
|
182
|
+
limits?: {
|
|
183
|
+
tier?: string;
|
|
184
|
+
max_user_keys?: number;
|
|
185
|
+
active_user_keys?: number;
|
|
186
|
+
remaining_user_keys?: number;
|
|
187
|
+
uses_grandfathered_keys?: boolean;
|
|
188
|
+
};
|
|
189
|
+
};
|
|
162
190
|
export type AskSupportResponse = {
|
|
163
191
|
success: boolean;
|
|
164
192
|
workspace_id: string;
|
|
@@ -552,6 +580,7 @@ export declare class PosterlyClient {
|
|
|
552
580
|
startSignup(data: PublicSignupPayload): Promise<PublicSignupResponse>;
|
|
553
581
|
getSignupSession(sessionId: string): Promise<PublicSignupSessionResponse>;
|
|
554
582
|
whoami(): Promise<Whoami>;
|
|
583
|
+
createApiKey(data: CreateApiKeyPayload): Promise<CreateApiKeyResponse>;
|
|
555
584
|
listAccounts(params?: {
|
|
556
585
|
workspace_id?: string;
|
|
557
586
|
}): Promise<Account[]>;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -113,6 +113,9 @@ export class PosterlyClient {
|
|
|
113
113
|
async whoami() {
|
|
114
114
|
return this.request('GET', '/whoami');
|
|
115
115
|
}
|
|
116
|
+
async createApiKey(data) {
|
|
117
|
+
return this.request('POST', '/api-keys', data);
|
|
118
|
+
}
|
|
116
119
|
async listAccounts(params) {
|
|
117
120
|
const searchParams = new URLSearchParams();
|
|
118
121
|
if (params?.workspace_id)
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const POSTERLY_MCP_VERSION = "0.20.
|
|
1
|
+
export declare const POSTERLY_MCP_VERSION = "0.20.7";
|
package/dist/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const POSTERLY_MCP_VERSION = '0.20.
|
|
1
|
+
export const POSTERLY_MCP_VERSION = '0.20.7';
|
|
@@ -34,37 +34,4 @@ export declare const askSupportTool: {
|
|
|
34
34
|
}>;
|
|
35
35
|
execute: typeof executeSupport;
|
|
36
36
|
};
|
|
37
|
-
export declare const getSupportTool: {
|
|
38
|
-
name: string;
|
|
39
|
-
description: string;
|
|
40
|
-
inputSchema: z.ZodObject<{
|
|
41
|
-
question: z.ZodString;
|
|
42
|
-
workspace_id: z.ZodOptional<z.ZodString>;
|
|
43
|
-
conversation_id: z.ZodOptional<z.ZodString>;
|
|
44
|
-
page_url: z.ZodOptional<z.ZodString>;
|
|
45
|
-
post_id: z.ZodOptional<z.ZodNumber>;
|
|
46
|
-
referenced_post_ids: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
47
|
-
request_human: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
48
|
-
confirm_escalation: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
49
|
-
}, "strip", z.ZodTypeAny, {
|
|
50
|
-
question: string;
|
|
51
|
-
workspace_id?: string | undefined;
|
|
52
|
-
post_id?: number | undefined;
|
|
53
|
-
conversation_id?: string | undefined;
|
|
54
|
-
page_url?: string | undefined;
|
|
55
|
-
referenced_post_ids?: number[] | undefined;
|
|
56
|
-
request_human?: boolean | undefined;
|
|
57
|
-
confirm_escalation?: boolean | undefined;
|
|
58
|
-
}, {
|
|
59
|
-
question: string;
|
|
60
|
-
workspace_id?: string | undefined;
|
|
61
|
-
post_id?: number | undefined;
|
|
62
|
-
conversation_id?: string | undefined;
|
|
63
|
-
page_url?: string | undefined;
|
|
64
|
-
referenced_post_ids?: number[] | undefined;
|
|
65
|
-
request_human?: boolean | undefined;
|
|
66
|
-
confirm_escalation?: boolean | undefined;
|
|
67
|
-
}>;
|
|
68
|
-
execute: typeof executeSupport;
|
|
69
|
-
};
|
|
70
37
|
export {};
|
|
@@ -46,9 +46,3 @@ export const askSupportTool = {
|
|
|
46
46
|
inputSchema: supportInputSchema,
|
|
47
47
|
execute: executeSupport,
|
|
48
48
|
};
|
|
49
|
-
export const getSupportTool = {
|
|
50
|
-
name: 'get_support',
|
|
51
|
-
description: 'Alias for ask_support. Ask Posterly Support AI an authenticated question using Posterly docs plus read-only account/post diagnostics. Human tickets require request_human=true and confirm_escalation=true after explicit user confirmation.',
|
|
52
|
-
inputSchema: supportInputSchema,
|
|
53
|
-
execute: executeSupport,
|
|
54
|
-
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { CreateApiKeyPayload, PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const createApiKeyTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
name: z.ZodOptional<z.ZodString>;
|
|
8
|
+
scopes: z.ZodOptional<z.ZodArray<z.ZodEnum<["accounts:read", "accounts:write", "posts:read", "posts:write", "media:write", "analytics:read"]>, "many">>;
|
|
9
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
10
|
+
expires_in_days: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
confirm: z.ZodLiteral<true>;
|
|
12
|
+
}, "strip", z.ZodTypeAny, {
|
|
13
|
+
confirm: true;
|
|
14
|
+
workspace_id?: string | undefined;
|
|
15
|
+
name?: string | undefined;
|
|
16
|
+
scopes?: ("accounts:read" | "accounts:write" | "posts:read" | "posts:write" | "media:write" | "analytics:read")[] | undefined;
|
|
17
|
+
expires_in_days?: number | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
confirm: true;
|
|
20
|
+
workspace_id?: string | undefined;
|
|
21
|
+
name?: string | undefined;
|
|
22
|
+
scopes?: ("accounts:read" | "accounts:write" | "posts:read" | "posts:write" | "media:write" | "analytics:read")[] | undefined;
|
|
23
|
+
expires_in_days?: number | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
execute(client: PosterlyClient, input: CreateApiKeyPayload): Promise<string>;
|
|
26
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { code, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
|
|
3
|
+
const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read']);
|
|
4
|
+
function formatCreatedApiKey(result) {
|
|
5
|
+
const key = result.api_key;
|
|
6
|
+
return [
|
|
7
|
+
mdTitle('API key created'),
|
|
8
|
+
`Full key (shown once): ${code(result.key)}`,
|
|
9
|
+
mdSection('Details', mdKeyValue([
|
|
10
|
+
['Name', key.name],
|
|
11
|
+
['Key ID', code(key.id)],
|
|
12
|
+
['Prefix', code(key.key_prefix)],
|
|
13
|
+
['Scopes', key.scopes.join(', ')],
|
|
14
|
+
['Workspace', key.workspace_id ? code(key.workspace_id) : 'all accessible workspaces'],
|
|
15
|
+
['Expires', key.expires_at || 'no expiry'],
|
|
16
|
+
['Remaining key slots', result.limits?.remaining_user_keys ?? 'unknown'],
|
|
17
|
+
])),
|
|
18
|
+
mdSection('Warning', result.warning || 'Store this securely. It will not be shown again.'),
|
|
19
|
+
].filter(Boolean).join('\n\n');
|
|
20
|
+
}
|
|
21
|
+
export const createApiKeyTool = {
|
|
22
|
+
name: 'create_api_key',
|
|
23
|
+
description: 'Create a new Posterly API key for the authenticated user. SECRET-CREATING WRITE: only use after explicit user confirmation. The new key can only request scopes already present on the calling dashboard-created API key; OAuth and managed assistant tokens cannot mint keys.',
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
name: z.string().trim().min(1).max(120).optional().describe('Human-readable key name.'),
|
|
26
|
+
scopes: z.array(scopeSchema).min(1).max(6).optional().describe('Scopes for the new key. Omit to copy the calling key scopes. Cannot exceed the calling key scopes.'),
|
|
27
|
+
workspace_id: z.string().trim().min(1).optional().describe('Optional workspace restriction. Workspace-scoped calling keys cannot create keys outside their workspace.'),
|
|
28
|
+
expires_in_days: z.number().int().min(1).max(365).optional().describe('Optional expiry from now, up to 365 days. Omit for no expiry.'),
|
|
29
|
+
confirm: z.literal(true).describe('Must be true after the user explicitly confirms that a new secret API key should be created.'),
|
|
30
|
+
}),
|
|
31
|
+
async execute(client, input) {
|
|
32
|
+
const result = await client.createApiKey(input);
|
|
33
|
+
return formatCreatedApiKey(result);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -82,11 +82,11 @@ export const createPostPayloadInputSchema = z.object({
|
|
|
82
82
|
.string()
|
|
83
83
|
.optional()
|
|
84
84
|
.describe('ISO 8601 datetime for scheduling (e.g. 2026-03-05T09:00:00Z). Omit for immediate publish.'),
|
|
85
|
-
media_url: z.string().optional().describe('URL
|
|
85
|
+
media_url: z.string().optional().describe('Media URL to attach. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved. Short-lived signed URLs must still be live when this tool runs. For threads, attaches to the lead post only.'),
|
|
86
86
|
media_urls: z
|
|
87
87
|
.array(z.string())
|
|
88
88
|
.optional()
|
|
89
|
-
.describe('Multiple media URLs for carousel or multi-image posts. For TikTok, 2 or more images auto-create a photo slideshow (up to 35 images) and every image is posted.'),
|
|
89
|
+
.describe('Multiple media URLs for carousel or multi-image posts. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved. For TikTok, 2 or more images auto-create a photo slideshow (up to 35 images) and every image is posted.'),
|
|
90
90
|
post_type: z
|
|
91
91
|
.string()
|
|
92
92
|
.optional()
|
|
@@ -156,6 +156,7 @@ export const createPostTool = {
|
|
|
156
156
|
'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' +
|
|
157
157
|
'AFTER CALLING: Tell the user the post was created, include the Posterly dashboard link returned by the tool, and offer the next natural action. Do not narrate raw HTTP, curl, or API plumbing.\n\n' +
|
|
158
158
|
'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' +
|
|
159
|
+
'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' +
|
|
159
160
|
'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' +
|
|
160
161
|
'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.\n\n' +
|
|
161
162
|
'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.',
|
|
@@ -67,8 +67,8 @@ export const updatePostTool = {
|
|
|
67
67
|
.string()
|
|
68
68
|
.optional()
|
|
69
69
|
.describe('New scheduled time (ISO 8601, e.g. 2026-03-10T09:00:00Z)'),
|
|
70
|
-
media_url: z.string().optional().describe('New media URL'),
|
|
71
|
-
media_urls: z.array(z.string()).optional().describe('New media URLs for carousel'),
|
|
70
|
+
media_url: z.string().optional().describe('New media URL. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved. Short-lived signed URLs must still be live when this tool runs.'),
|
|
71
|
+
media_urls: z.array(z.string()).optional().describe('New media URLs for carousel. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved.'),
|
|
72
72
|
post_type: z
|
|
73
73
|
.string()
|
|
74
74
|
.optional()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export const uploadMediaFromUrlTool = {
|
|
3
3
|
name: 'upload_media_from_url',
|
|
4
|
-
description: 'Fetch an image or video from a public HTTPS URL into
|
|
4
|
+
description: 'Fetch an image or video from a public HTTPS URL into Posterly storage. Returns a URL that can be used with create_post. You can also pass public HTTP(S) media URLs directly to create_post/update_post; use this tool when you want to upload once and reuse the returned Posterly URL.',
|
|
5
5
|
inputSchema: z.object({
|
|
6
6
|
url: z.string().url().describe('Public HTTPS URL for the image or video. Local/private network URLs are blocked.'),
|
|
7
7
|
filename: z.string().optional().describe('Optional filename override.'),
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { listPostsTool } from './tools/list-posts.js';
|
|
|
21
21
|
import { uploadMediaTool } from './tools/upload-media.js';
|
|
22
22
|
import { getPostTool } from './tools/get-post.js';
|
|
23
23
|
import { getPostMissingTool } from './tools/get-post-missing.js';
|
|
24
|
-
import { askSupportTool
|
|
24
|
+
import { askSupportTool } from './tools/ask-support.js';
|
|
25
25
|
import { updatePostTool } from './tools/update-post.js';
|
|
26
26
|
import { updatePostStatusTool } from './tools/update-post-status.js';
|
|
27
27
|
import { updatePostReleaseIdTool } from './tools/update-post-release-id.js';
|
|
@@ -57,6 +57,7 @@ import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
|
|
|
57
57
|
import { getConnectLinkTool } from './tools/get-connect-link.js';
|
|
58
58
|
import { createConnectSessionTool } from './tools/create-connect-session.js';
|
|
59
59
|
import { getConnectSessionTool } from './tools/get-connect-session.js';
|
|
60
|
+
import { createApiKeyTool } from './tools/create-api-key.js';
|
|
60
61
|
import { listOAuthClientsTool } from './tools/list-oauth-clients.js';
|
|
61
62
|
import { createOAuthClientTool } from './tools/create-oauth-client.js';
|
|
62
63
|
import { updateOAuthClientTool } from './tools/update-oauth-client.js';
|
|
@@ -210,6 +211,20 @@ server.tool(
|
|
|
210
211
|
}
|
|
211
212
|
);
|
|
212
213
|
|
|
214
|
+
server.tool(
|
|
215
|
+
createApiKeyTool.name,
|
|
216
|
+
createApiKeyTool.description,
|
|
217
|
+
createApiKeyTool.inputSchema.shape,
|
|
218
|
+
async (input) => {
|
|
219
|
+
try {
|
|
220
|
+
const text = await createApiKeyTool.execute(client, input as any);
|
|
221
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
222
|
+
} catch (err: any) {
|
|
223
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
|
|
213
228
|
server.tool(
|
|
214
229
|
listOAuthClientsTool.name,
|
|
215
230
|
listOAuthClientsTool.description,
|
|
@@ -588,20 +603,6 @@ server.tool(
|
|
|
588
603
|
}
|
|
589
604
|
);
|
|
590
605
|
|
|
591
|
-
server.tool(
|
|
592
|
-
getSupportTool.name,
|
|
593
|
-
getSupportTool.description,
|
|
594
|
-
getSupportTool.inputSchema.shape,
|
|
595
|
-
async (input) => {
|
|
596
|
-
try {
|
|
597
|
-
const text = await getSupportTool.execute(client, input as any);
|
|
598
|
-
return { content: [{ type: 'text' as const, text }] };
|
|
599
|
-
} catch (err: any) {
|
|
600
|
-
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
);
|
|
604
|
-
|
|
605
606
|
server.tool(
|
|
606
607
|
updatePostTool.name,
|
|
607
608
|
updatePostTool.description,
|
package/src/lib/api-client.ts
CHANGED
|
@@ -163,6 +163,43 @@ export type AskSupportPayload = {
|
|
|
163
163
|
confirm_escalation?: boolean;
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
+
export type ApiKeyScope =
|
|
167
|
+
| 'accounts:read'
|
|
168
|
+
| 'accounts:write'
|
|
169
|
+
| 'posts:read'
|
|
170
|
+
| 'posts:write'
|
|
171
|
+
| 'media:write'
|
|
172
|
+
| 'analytics:read';
|
|
173
|
+
|
|
174
|
+
export type CreateApiKeyPayload = {
|
|
175
|
+
name?: string;
|
|
176
|
+
scopes?: ApiKeyScope[];
|
|
177
|
+
workspace_id?: string;
|
|
178
|
+
expires_in_days?: number;
|
|
179
|
+
confirm: true;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export type CreateApiKeyResponse = {
|
|
183
|
+
key: string;
|
|
184
|
+
api_key: {
|
|
185
|
+
id: string;
|
|
186
|
+
name: string;
|
|
187
|
+
key_prefix: string;
|
|
188
|
+
scopes: ApiKeyScope[];
|
|
189
|
+
workspace_id?: string | null;
|
|
190
|
+
expires_at?: string | null;
|
|
191
|
+
created_at: string;
|
|
192
|
+
};
|
|
193
|
+
warning?: string;
|
|
194
|
+
limits?: {
|
|
195
|
+
tier?: string;
|
|
196
|
+
max_user_keys?: number;
|
|
197
|
+
active_user_keys?: number;
|
|
198
|
+
remaining_user_keys?: number;
|
|
199
|
+
uses_grandfathered_keys?: boolean;
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
166
203
|
export type AskSupportResponse = {
|
|
167
204
|
success: boolean;
|
|
168
205
|
workspace_id: string;
|
|
@@ -693,6 +730,10 @@ export class PosterlyClient {
|
|
|
693
730
|
return this.request<Whoami>('GET', '/whoami');
|
|
694
731
|
}
|
|
695
732
|
|
|
733
|
+
async createApiKey(data: CreateApiKeyPayload): Promise<CreateApiKeyResponse> {
|
|
734
|
+
return this.request('POST', '/api-keys', data);
|
|
735
|
+
}
|
|
736
|
+
|
|
696
737
|
async listAccounts(params?: { workspace_id?: string }): Promise<Account[]> {
|
|
697
738
|
const searchParams = new URLSearchParams();
|
|
698
739
|
if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
|
package/src/lib/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const POSTERLY_MCP_VERSION = '0.20.
|
|
1
|
+
export const POSTERLY_MCP_VERSION = '0.20.7';
|
package/src/tools/ask-support.ts
CHANGED
|
@@ -53,11 +53,3 @@ export const askSupportTool = {
|
|
|
53
53
|
inputSchema: supportInputSchema,
|
|
54
54
|
execute: executeSupport,
|
|
55
55
|
};
|
|
56
|
-
|
|
57
|
-
export const getSupportTool = {
|
|
58
|
-
name: 'get_support',
|
|
59
|
-
description:
|
|
60
|
-
'Alias for ask_support. Ask Posterly Support AI an authenticated question using Posterly docs plus read-only account/post diagnostics. Human tickets require request_human=true and confirm_escalation=true after explicit user confirmation.',
|
|
61
|
-
inputSchema: supportInputSchema,
|
|
62
|
-
execute: executeSupport,
|
|
63
|
-
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { CreateApiKeyPayload, CreateApiKeyResponse, PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { code, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
|
|
4
|
+
|
|
5
|
+
const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read']);
|
|
6
|
+
|
|
7
|
+
function formatCreatedApiKey(result: CreateApiKeyResponse): string {
|
|
8
|
+
const key = result.api_key;
|
|
9
|
+
return [
|
|
10
|
+
mdTitle('API key created'),
|
|
11
|
+
`Full key (shown once): ${code(result.key)}`,
|
|
12
|
+
mdSection('Details', mdKeyValue([
|
|
13
|
+
['Name', key.name],
|
|
14
|
+
['Key ID', code(key.id)],
|
|
15
|
+
['Prefix', code(key.key_prefix)],
|
|
16
|
+
['Scopes', key.scopes.join(', ')],
|
|
17
|
+
['Workspace', key.workspace_id ? code(key.workspace_id) : 'all accessible workspaces'],
|
|
18
|
+
['Expires', key.expires_at || 'no expiry'],
|
|
19
|
+
['Remaining key slots', result.limits?.remaining_user_keys ?? 'unknown'],
|
|
20
|
+
])),
|
|
21
|
+
mdSection('Warning', result.warning || 'Store this securely. It will not be shown again.'),
|
|
22
|
+
].filter(Boolean).join('\n\n');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const createApiKeyTool = {
|
|
26
|
+
name: 'create_api_key',
|
|
27
|
+
description:
|
|
28
|
+
'Create a new Posterly API key for the authenticated user. SECRET-CREATING WRITE: only use after explicit user confirmation. The new key can only request scopes already present on the calling dashboard-created API key; OAuth and managed assistant tokens cannot mint keys.',
|
|
29
|
+
inputSchema: z.object({
|
|
30
|
+
name: z.string().trim().min(1).max(120).optional().describe('Human-readable key name.'),
|
|
31
|
+
scopes: z.array(scopeSchema).min(1).max(6).optional().describe('Scopes for the new key. Omit to copy the calling key scopes. Cannot exceed the calling key scopes.'),
|
|
32
|
+
workspace_id: z.string().trim().min(1).optional().describe('Optional workspace restriction. Workspace-scoped calling keys cannot create keys outside their workspace.'),
|
|
33
|
+
expires_in_days: z.number().int().min(1).max(365).optional().describe('Optional expiry from now, up to 365 days. Omit for no expiry.'),
|
|
34
|
+
confirm: z.literal(true).describe('Must be true after the user explicitly confirms that a new secret API key should be created.'),
|
|
35
|
+
}),
|
|
36
|
+
|
|
37
|
+
async execute(client: PosterlyClient, input: CreateApiKeyPayload) {
|
|
38
|
+
const result = await client.createApiKey(input);
|
|
39
|
+
return formatCreatedApiKey(result);
|
|
40
|
+
},
|
|
41
|
+
};
|
package/src/tools/create-post.ts
CHANGED
|
@@ -86,11 +86,11 @@ export const createPostPayloadInputSchema = z.object({
|
|
|
86
86
|
.string()
|
|
87
87
|
.optional()
|
|
88
88
|
.describe('ISO 8601 datetime for scheduling (e.g. 2026-03-05T09:00:00Z). Omit for immediate publish.'),
|
|
89
|
-
media_url: z.string().optional().describe('URL
|
|
89
|
+
media_url: z.string().optional().describe('Media URL to attach. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved. Short-lived signed URLs must still be live when this tool runs. For threads, attaches to the lead post only.'),
|
|
90
90
|
media_urls: z
|
|
91
91
|
.array(z.string())
|
|
92
92
|
.optional()
|
|
93
|
-
.describe('Multiple media URLs for carousel or multi-image posts. For TikTok, 2 or more images auto-create a photo slideshow (up to 35 images) and every image is posted.'),
|
|
93
|
+
.describe('Multiple media URLs for carousel or multi-image posts. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved. For TikTok, 2 or more images auto-create a photo slideshow (up to 35 images) and every image is posted.'),
|
|
94
94
|
post_type: z
|
|
95
95
|
.string()
|
|
96
96
|
.optional()
|
|
@@ -172,6 +172,7 @@ export const createPostTool = {
|
|
|
172
172
|
'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' +
|
|
173
173
|
'AFTER CALLING: Tell the user the post was created, include the Posterly dashboard link returned by the tool, and offer the next natural action. Do not narrate raw HTTP, curl, or API plumbing.\n\n' +
|
|
174
174
|
'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' +
|
|
175
|
+
'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' +
|
|
175
176
|
'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' +
|
|
176
177
|
'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.\n\n' +
|
|
177
178
|
'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.',
|
package/src/tools/update-post.ts
CHANGED
|
@@ -72,8 +72,8 @@ export const updatePostTool = {
|
|
|
72
72
|
.string()
|
|
73
73
|
.optional()
|
|
74
74
|
.describe('New scheduled time (ISO 8601, e.g. 2026-03-10T09:00:00Z)'),
|
|
75
|
-
media_url: z.string().optional().describe('New media URL'),
|
|
76
|
-
media_urls: z.array(z.string()).optional().describe('New media URLs for carousel'),
|
|
75
|
+
media_url: z.string().optional().describe('New media URL. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved. Short-lived signed URLs must still be live when this tool runs.'),
|
|
76
|
+
media_urls: z.array(z.string()).optional().describe('New media URLs for carousel. Posterly storage URLs are used as-is; third-party HTTP(S) URLs are copied into Posterly storage before the post is saved.'),
|
|
77
77
|
post_type: z
|
|
78
78
|
.string()
|
|
79
79
|
.optional()
|
|
@@ -4,7 +4,7 @@ import type { PosterlyClient } from '../lib/api-client.js';
|
|
|
4
4
|
export const uploadMediaFromUrlTool = {
|
|
5
5
|
name: 'upload_media_from_url',
|
|
6
6
|
description:
|
|
7
|
-
'Fetch an image or video from a public HTTPS URL into
|
|
7
|
+
'Fetch an image or video from a public HTTPS URL into Posterly storage. Returns a URL that can be used with create_post. You can also pass public HTTP(S) media URLs directly to create_post/update_post; use this tool when you want to upload once and reuse the returned Posterly URL.',
|
|
8
8
|
inputSchema: z.object({
|
|
9
9
|
url: z.string().url().describe('Public HTTPS URL for the image or video. Local/private network URLs are blocked.'),
|
|
10
10
|
filename: z.string().optional().describe('Optional filename override.'),
|