posterly-mcp-server 0.3.1 → 0.4.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/index.js +11 -1
- package/dist/lib/api-client.d.ts +25 -0
- package/dist/lib/api-client.js +3 -0
- package/dist/tools/generate-image.d.ts +36 -0
- package/dist/tools/generate-image.js +70 -0
- package/package.json +1 -1
- package/src/index.ts +16 -1
- package/src/lib/api-client.ts +24 -0
- package/src/tools/generate-image.ts +86 -0
package/dist/index.js
CHANGED
|
@@ -11,9 +11,10 @@ import { getPostTool } from './tools/get-post.js';
|
|
|
11
11
|
import { updatePostTool } from './tools/update-post.js';
|
|
12
12
|
import { deletePostTool } from './tools/delete-post.js';
|
|
13
13
|
import { whoamiTool } from './tools/whoami.js';
|
|
14
|
+
import { generateImageTool } from './tools/generate-image.js';
|
|
14
15
|
const server = new McpServer({
|
|
15
16
|
name: 'posterly',
|
|
16
|
-
version: '0.
|
|
17
|
+
version: '0.4.0',
|
|
17
18
|
});
|
|
18
19
|
let client;
|
|
19
20
|
try {
|
|
@@ -78,6 +79,15 @@ server.tool(uploadMediaTool.name, uploadMediaTool.description, uploadMediaTool.i
|
|
|
78
79
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
79
80
|
}
|
|
80
81
|
});
|
|
82
|
+
server.tool(generateImageTool.name, generateImageTool.description, generateImageTool.inputSchema.shape, async (input) => {
|
|
83
|
+
try {
|
|
84
|
+
const text = await generateImageTool.execute(client, input);
|
|
85
|
+
return { content: [{ type: 'text', text }] };
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
89
|
+
}
|
|
90
|
+
});
|
|
81
91
|
server.tool(getPostTool.name, getPostTool.description, getPostTool.inputSchema.shape, async (input) => {
|
|
82
92
|
try {
|
|
83
93
|
const text = await getPostTool.execute(client, input);
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -99,6 +99,31 @@ export declare class PosterlyClient {
|
|
|
99
99
|
deleted: boolean;
|
|
100
100
|
id: number;
|
|
101
101
|
}>;
|
|
102
|
+
generateImage(data: {
|
|
103
|
+
prompt: string;
|
|
104
|
+
aspect_ratio?: string;
|
|
105
|
+
style?: string;
|
|
106
|
+
variations?: number;
|
|
107
|
+
model?: 'flash' | 'pro';
|
|
108
|
+
resolution?: '512' | '1K' | '2K' | '4K';
|
|
109
|
+
}): Promise<{
|
|
110
|
+
urls: string[];
|
|
111
|
+
images: Array<{
|
|
112
|
+
url: string;
|
|
113
|
+
filename: string;
|
|
114
|
+
path: string;
|
|
115
|
+
}>;
|
|
116
|
+
model: string;
|
|
117
|
+
credits_used: number;
|
|
118
|
+
warnings?: string[];
|
|
119
|
+
usage: {
|
|
120
|
+
used: number | null;
|
|
121
|
+
limit: number | null;
|
|
122
|
+
period: string | null;
|
|
123
|
+
tier: string | null;
|
|
124
|
+
billed_from: 'plan_quota' | 'credits';
|
|
125
|
+
};
|
|
126
|
+
}>;
|
|
102
127
|
findAvailableSlots(params?: {
|
|
103
128
|
account_ids?: string[];
|
|
104
129
|
timezone?: string;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -68,6 +68,9 @@ export class PosterlyClient {
|
|
|
68
68
|
async deletePost(id) {
|
|
69
69
|
return this.request('DELETE', `/posts/${id}`);
|
|
70
70
|
}
|
|
71
|
+
async generateImage(data) {
|
|
72
|
+
return this.request('POST', '/ai/generate-image', data);
|
|
73
|
+
}
|
|
71
74
|
async findAvailableSlots(params) {
|
|
72
75
|
const searchParams = new URLSearchParams();
|
|
73
76
|
if (params?.account_ids?.length)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const generateImageTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
prompt: z.ZodString;
|
|
8
|
+
aspect_ratio: z.ZodOptional<z.ZodEnum<["1:1", "9:16", "4:5", "3:4", "2:3", "1:4", "1:8", "21:9", "16:9", "4:3", "3:2", "4:1", "8:1", "5:4"]>>;
|
|
9
|
+
style: z.ZodOptional<z.ZodEnum<["photographic", "illustration", "minimal", "vibrant", "professional", "youtube_thumbnail", "reel_cover", "review_background"]>>;
|
|
10
|
+
variations: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
model: z.ZodOptional<z.ZodEnum<["flash", "pro"]>>;
|
|
12
|
+
resolution: z.ZodOptional<z.ZodEnum<["512", "1K", "2K", "4K"]>>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
prompt: string;
|
|
15
|
+
aspect_ratio?: "1:1" | "9:16" | "4:5" | "3:4" | "2:3" | "1:4" | "1:8" | "21:9" | "16:9" | "4:3" | "3:2" | "4:1" | "8:1" | "5:4" | undefined;
|
|
16
|
+
style?: "photographic" | "illustration" | "minimal" | "vibrant" | "professional" | "youtube_thumbnail" | "reel_cover" | "review_background" | undefined;
|
|
17
|
+
variations?: number | undefined;
|
|
18
|
+
model?: "flash" | "pro" | undefined;
|
|
19
|
+
resolution?: "512" | "1K" | "2K" | "4K" | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
prompt: string;
|
|
22
|
+
aspect_ratio?: "1:1" | "9:16" | "4:5" | "3:4" | "2:3" | "1:4" | "1:8" | "21:9" | "16:9" | "4:3" | "3:2" | "4:1" | "8:1" | "5:4" | undefined;
|
|
23
|
+
style?: "photographic" | "illustration" | "minimal" | "vibrant" | "professional" | "youtube_thumbnail" | "reel_cover" | "review_background" | undefined;
|
|
24
|
+
variations?: number | undefined;
|
|
25
|
+
model?: "flash" | "pro" | undefined;
|
|
26
|
+
resolution?: "512" | "1K" | "2K" | "4K" | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
execute(client: PosterlyClient, input: {
|
|
29
|
+
prompt: string;
|
|
30
|
+
aspect_ratio?: string;
|
|
31
|
+
style?: string;
|
|
32
|
+
variations?: number;
|
|
33
|
+
model?: "flash" | "pro";
|
|
34
|
+
resolution?: "512" | "1K" | "2K" | "4K";
|
|
35
|
+
}): Promise<string>;
|
|
36
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const generateImageTool = {
|
|
3
|
+
name: 'generate_image',
|
|
4
|
+
description: 'Generate an AI image via Posterly\'s Nano Banana (Gemini) integration. The image is saved to the user\'s media storage and the returned URL can be passed to `create_post` as `media_url`.\n\n' +
|
|
5
|
+
'COSTS CREDITS. Every call consumes part of the user\'s monthly AI image quota (or purchased credits once the quota is exhausted). Do NOT call speculatively — always describe the image you\'re about to generate (subject, style, aspect ratio) to the user and get confirmation before calling.\n\n' +
|
|
6
|
+
'If the user is over their plan limit and has no credits, this tool returns a 402 with upgrade info. Surface that message verbatim — do not retry.\n\n' +
|
|
7
|
+
'Common aspect ratios by platform:\n' +
|
|
8
|
+
' • Instagram feed / LinkedIn: 1:1 (square) or 4:5 (portrait)\n' +
|
|
9
|
+
' • Instagram Story/Reel, TikTok, YouTube Shorts: 9:16\n' +
|
|
10
|
+
' • YouTube thumbnail, Twitter cards, landscape feed: 16:9\n' +
|
|
11
|
+
' • Pinterest: 2:3',
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
prompt: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(5)
|
|
16
|
+
.max(4000)
|
|
17
|
+
.describe('Detailed image description. 5–4000 characters. Describe subject, scene, lighting, mood, and style.'),
|
|
18
|
+
aspect_ratio: z
|
|
19
|
+
.enum([
|
|
20
|
+
'1:1', '9:16', '4:5', '3:4', '2:3', '1:4', '1:8',
|
|
21
|
+
'21:9', '16:9', '4:3', '3:2', '4:1', '8:1', '5:4',
|
|
22
|
+
])
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Aspect ratio. Default 1:1. Pick based on target platform (see tool description).'),
|
|
25
|
+
style: z
|
|
26
|
+
.enum([
|
|
27
|
+
'photographic', 'illustration', 'minimal', 'vibrant', 'professional',
|
|
28
|
+
'youtube_thumbnail', 'reel_cover', 'review_background',
|
|
29
|
+
])
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('Preset style. Default photographic. Use youtube_thumbnail / reel_cover for platform-optimized covers; review_background for testimonial backgrounds.'),
|
|
32
|
+
variations: z
|
|
33
|
+
.number()
|
|
34
|
+
.int()
|
|
35
|
+
.min(1)
|
|
36
|
+
.max(4)
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('How many variations to generate. Default 1. Each variation costs credits separately — prefer 1 unless the user explicitly wants options.'),
|
|
39
|
+
model: z
|
|
40
|
+
.enum(['flash', 'pro'])
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('flash (default, 1 credit per 1K image) is fast and cost-effective. pro (2 credits per 1K) is higher quality for hero imagery. Start with flash unless quality is critical.'),
|
|
43
|
+
resolution: z
|
|
44
|
+
.enum(['512', '1K', '2K', '4K'])
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Output resolution. Default 1K. Higher resolutions cost more credits. 512 is flash-only.'),
|
|
47
|
+
}),
|
|
48
|
+
async execute(client, input) {
|
|
49
|
+
const result = await client.generateImage(input);
|
|
50
|
+
const lines = [];
|
|
51
|
+
lines.push(`Generated ${result.images.length} image${result.images.length === 1 ? '' : 's'} via ${result.model}.`);
|
|
52
|
+
lines.push('');
|
|
53
|
+
result.images.forEach((img, i) => {
|
|
54
|
+
lines.push(`${i + 1}. ${img.url}`);
|
|
55
|
+
});
|
|
56
|
+
lines.push('');
|
|
57
|
+
lines.push(`Billed from: ${result.usage.billed_from}${result.credits_used > 0 ? ` (${result.credits_used} credits)` : ''}`);
|
|
58
|
+
if (result.usage.limit != null) {
|
|
59
|
+
lines.push(`Plan usage: ${result.usage.used ?? 0}/${result.usage.limit} this ${result.usage.period || 'period'}${result.usage.tier ? ` (${result.usage.tier})` : ''}`);
|
|
60
|
+
}
|
|
61
|
+
if (result.warnings?.length) {
|
|
62
|
+
lines.push('');
|
|
63
|
+
lines.push('Warnings:');
|
|
64
|
+
result.warnings.forEach((w) => lines.push(`• ${w}`));
|
|
65
|
+
}
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push('Pass any of these URLs to create_post as media_url (or media_urls for a carousel).');
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
},
|
|
70
|
+
};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -12,10 +12,11 @@ import { getPostTool } from './tools/get-post.js';
|
|
|
12
12
|
import { updatePostTool } from './tools/update-post.js';
|
|
13
13
|
import { deletePostTool } from './tools/delete-post.js';
|
|
14
14
|
import { whoamiTool } from './tools/whoami.js';
|
|
15
|
+
import { generateImageTool } from './tools/generate-image.js';
|
|
15
16
|
|
|
16
17
|
const server = new McpServer({
|
|
17
18
|
name: 'posterly',
|
|
18
|
-
version: '0.
|
|
19
|
+
version: '0.4.0',
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
let client: PosterlyClient;
|
|
@@ -112,6 +113,20 @@ server.tool(
|
|
|
112
113
|
}
|
|
113
114
|
);
|
|
114
115
|
|
|
116
|
+
server.tool(
|
|
117
|
+
generateImageTool.name,
|
|
118
|
+
generateImageTool.description,
|
|
119
|
+
generateImageTool.inputSchema.shape,
|
|
120
|
+
async (input) => {
|
|
121
|
+
try {
|
|
122
|
+
const text = await generateImageTool.execute(client, input as any);
|
|
123
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
124
|
+
} catch (err: any) {
|
|
125
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
115
130
|
server.tool(
|
|
116
131
|
getPostTool.name,
|
|
117
132
|
getPostTool.description,
|
package/src/lib/api-client.ts
CHANGED
|
@@ -157,6 +157,30 @@ export class PosterlyClient {
|
|
|
157
157
|
return this.request('DELETE', `/posts/${id}`);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
async generateImage(data: {
|
|
161
|
+
prompt: string;
|
|
162
|
+
aspect_ratio?: string;
|
|
163
|
+
style?: string;
|
|
164
|
+
variations?: number;
|
|
165
|
+
model?: 'flash' | 'pro';
|
|
166
|
+
resolution?: '512' | '1K' | '2K' | '4K';
|
|
167
|
+
}): Promise<{
|
|
168
|
+
urls: string[];
|
|
169
|
+
images: Array<{ url: string; filename: string; path: string }>;
|
|
170
|
+
model: string;
|
|
171
|
+
credits_used: number;
|
|
172
|
+
warnings?: string[];
|
|
173
|
+
usage: {
|
|
174
|
+
used: number | null;
|
|
175
|
+
limit: number | null;
|
|
176
|
+
period: string | null;
|
|
177
|
+
tier: string | null;
|
|
178
|
+
billed_from: 'plan_quota' | 'credits';
|
|
179
|
+
};
|
|
180
|
+
}> {
|
|
181
|
+
return this.request('POST', '/ai/generate-image', data);
|
|
182
|
+
}
|
|
183
|
+
|
|
160
184
|
async findAvailableSlots(params?: {
|
|
161
185
|
account_ids?: string[];
|
|
162
186
|
timezone?: string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
|
|
4
|
+
export const generateImageTool = {
|
|
5
|
+
name: 'generate_image',
|
|
6
|
+
description:
|
|
7
|
+
'Generate an AI image via Posterly\'s Nano Banana (Gemini) integration. The image is saved to the user\'s media storage and the returned URL can be passed to `create_post` as `media_url`.\n\n' +
|
|
8
|
+
'COSTS CREDITS. Every call consumes part of the user\'s monthly AI image quota (or purchased credits once the quota is exhausted). Do NOT call speculatively — always describe the image you\'re about to generate (subject, style, aspect ratio) to the user and get confirmation before calling.\n\n' +
|
|
9
|
+
'If the user is over their plan limit and has no credits, this tool returns a 402 with upgrade info. Surface that message verbatim — do not retry.\n\n' +
|
|
10
|
+
'Common aspect ratios by platform:\n' +
|
|
11
|
+
' • Instagram feed / LinkedIn: 1:1 (square) or 4:5 (portrait)\n' +
|
|
12
|
+
' • Instagram Story/Reel, TikTok, YouTube Shorts: 9:16\n' +
|
|
13
|
+
' • YouTube thumbnail, Twitter cards, landscape feed: 16:9\n' +
|
|
14
|
+
' • Pinterest: 2:3',
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
prompt: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(5)
|
|
19
|
+
.max(4000)
|
|
20
|
+
.describe('Detailed image description. 5–4000 characters. Describe subject, scene, lighting, mood, and style.'),
|
|
21
|
+
aspect_ratio: z
|
|
22
|
+
.enum([
|
|
23
|
+
'1:1', '9:16', '4:5', '3:4', '2:3', '1:4', '1:8',
|
|
24
|
+
'21:9', '16:9', '4:3', '3:2', '4:1', '8:1', '5:4',
|
|
25
|
+
])
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Aspect ratio. Default 1:1. Pick based on target platform (see tool description).'),
|
|
28
|
+
style: z
|
|
29
|
+
.enum([
|
|
30
|
+
'photographic', 'illustration', 'minimal', 'vibrant', 'professional',
|
|
31
|
+
'youtube_thumbnail', 'reel_cover', 'review_background',
|
|
32
|
+
])
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Preset style. Default photographic. Use youtube_thumbnail / reel_cover for platform-optimized covers; review_background for testimonial backgrounds.'),
|
|
35
|
+
variations: z
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.min(1)
|
|
39
|
+
.max(4)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('How many variations to generate. Default 1. Each variation costs credits separately — prefer 1 unless the user explicitly wants options.'),
|
|
42
|
+
model: z
|
|
43
|
+
.enum(['flash', 'pro'])
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('flash (default, 1 credit per 1K image) is fast and cost-effective. pro (2 credits per 1K) is higher quality for hero imagery. Start with flash unless quality is critical.'),
|
|
46
|
+
resolution: z
|
|
47
|
+
.enum(['512', '1K', '2K', '4K'])
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Output resolution. Default 1K. Higher resolutions cost more credits. 512 is flash-only.'),
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
async execute(
|
|
53
|
+
client: PosterlyClient,
|
|
54
|
+
input: {
|
|
55
|
+
prompt: string;
|
|
56
|
+
aspect_ratio?: string;
|
|
57
|
+
style?: string;
|
|
58
|
+
variations?: number;
|
|
59
|
+
model?: 'flash' | 'pro';
|
|
60
|
+
resolution?: '512' | '1K' | '2K' | '4K';
|
|
61
|
+
}
|
|
62
|
+
) {
|
|
63
|
+
const result = await client.generateImage(input);
|
|
64
|
+
|
|
65
|
+
const lines: string[] = [];
|
|
66
|
+
lines.push(`Generated ${result.images.length} image${result.images.length === 1 ? '' : 's'} via ${result.model}.`);
|
|
67
|
+
lines.push('');
|
|
68
|
+
result.images.forEach((img, i) => {
|
|
69
|
+
lines.push(`${i + 1}. ${img.url}`);
|
|
70
|
+
});
|
|
71
|
+
lines.push('');
|
|
72
|
+
lines.push(`Billed from: ${result.usage.billed_from}${result.credits_used > 0 ? ` (${result.credits_used} credits)` : ''}`);
|
|
73
|
+
if (result.usage.limit != null) {
|
|
74
|
+
lines.push(`Plan usage: ${result.usage.used ?? 0}/${result.usage.limit} this ${result.usage.period || 'period'}${result.usage.tier ? ` (${result.usage.tier})` : ''}`);
|
|
75
|
+
}
|
|
76
|
+
if (result.warnings?.length) {
|
|
77
|
+
lines.push('');
|
|
78
|
+
lines.push('Warnings:');
|
|
79
|
+
result.warnings.forEach((w) => lines.push(`• ${w}`));
|
|
80
|
+
}
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push('Pass any of these URLs to create_post as media_url (or media_urls for a carousel).');
|
|
83
|
+
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
},
|
|
86
|
+
};
|