@post-ripple/mcp 0.2.1 → 0.5.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 +9 -1
- package/dist/index.js +141 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,8 @@ env = { POSTRIPPLE_API_KEY = "pr_..." }
|
|
|
58
58
|
| `list_videos` | read | Video library (status, source) |
|
|
59
59
|
| `list_video_groups` / `get_video_group` | read | Rotation groups and their posting rules |
|
|
60
60
|
| `list_slideshows` | read | Photo-carousel slideshows |
|
|
61
|
+
| `list_slideshow_templates` | read | Slideshow layout templates, fonts, and text treatments |
|
|
62
|
+
| `get_slideshow` | read | One slideshow with per-slide render state and progress |
|
|
61
63
|
| `list_posts` | read | Posts with status, schedule, errors |
|
|
62
64
|
| `get_post_metrics` | read | Per-post engagement time series |
|
|
63
65
|
| `get_account_metrics` | read | Follower growth history |
|
|
@@ -67,13 +69,19 @@ env = { POSTRIPPLE_API_KEY = "pr_..." }
|
|
|
67
69
|
| `create_video_group` | content:write | Create a rotation group |
|
|
68
70
|
| `add_videos_to_group` | content:write | Fill a group's rotation |
|
|
69
71
|
| `update_group_schedule` | content:write | Attach accounts / set posts-per-day and time windows |
|
|
72
|
+
| `get_image_upload_url` | content:write | Upload a slide image (JPEG 1080×1350 recommended) |
|
|
73
|
+
| `import_image_from_url` | content:write | Import a slide image from a URL (synchronous) |
|
|
74
|
+
| `create_slideshow` | content:write | Create a slideshow from pre-rendered images and/or template slides (server-side rendered) |
|
|
70
75
|
| `schedule_post` | publish | Queue a video to publish at a future time (idempotent via `idempotencyKey`) |
|
|
76
|
+
| `publish_slideshow` | publish | Publish or schedule a slideshow to Instagram/TikTok (supports TikTok drafts-inbox mode) |
|
|
71
77
|
| `reschedule_post` | publish | Move a scheduled post |
|
|
72
78
|
| `cancel_post` | publish | Cancel a scheduled post (refunds the credit) |
|
|
73
79
|
|
|
74
80
|
Notes for agents:
|
|
75
81
|
|
|
76
|
-
- `schedule_post`
|
|
82
|
+
- `schedule_post` and `publish_slideshow` publish to **real** social accounts. Always pass `idempotencyKey`, and pass `tiktokSettings` if the post should be public on TikTok (the default is private/`SELF_ONLY`).
|
|
83
|
+
- The typical slideshow flow: `import_image_from_url` (or `get_image_upload_url` + PUT) per image → `create_slideshow` → poll `get_slideshow` until `ready` (template slides render server-side) → `publish_slideshow`. Pre-rendered JPEG slideshows are ready instantly.
|
|
84
|
+
- TikTok `settings.postMode: "inbox"` sends the photo post to the account's TikTok app drafts inbox instead of publishing directly — useful when a human finishes posts from a real device.
|
|
77
85
|
- Imports and publishes are asynchronous. Poll `list_videos` / `list_posts` to observe progress.
|
|
78
86
|
- Every call is audit-logged and attributed to the member who created the key. Keys are org-bound; usage requires the Pro plan or higher.
|
|
79
87
|
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ function toEpochMs(value, field) {
|
|
|
45
45
|
}
|
|
46
46
|
return parsed;
|
|
47
47
|
}
|
|
48
|
-
const server = new McpServer({ name: "postripple", version: "0.
|
|
48
|
+
const server = new McpServer({ name: "postripple", version: "0.5.0" });
|
|
49
49
|
function register(name, description, shape, transform) {
|
|
50
50
|
server.tool(name, description, shape, async (args) => {
|
|
51
51
|
try {
|
|
@@ -108,8 +108,72 @@ const tiktokSettingsSchema = z
|
|
|
108
108
|
disableStitch: z.boolean(),
|
|
109
109
|
brandOrganicToggle: z.boolean(),
|
|
110
110
|
brandContentToggle: z.boolean(),
|
|
111
|
+
autoAddMusic: z
|
|
112
|
+
.boolean()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe("Photo (slideshow) posts only: let TikTok auto-pick background music. Defaults to true."),
|
|
115
|
+
postMode: z
|
|
116
|
+
.enum(["direct", "inbox"])
|
|
117
|
+
.optional()
|
|
118
|
+
.describe("Photo (slideshow) posts only. 'direct' (default) publishes immediately; 'inbox' sends a draft to the account's TikTok app inbox — the creator finishes it in the app, and privacy/comment/brand fields are ignored by TikTok in that mode."),
|
|
111
119
|
})
|
|
112
120
|
.describe("TikTok compliance settings. If omitted, the post publishes as SELF_ONLY (private).");
|
|
121
|
+
/** One slide for create_slideshow: EITHER a finished image (prerenderedFileId)
|
|
122
|
+
* OR a background + text fields rendered server-side from the template. */
|
|
123
|
+
const slideTextStyleSchema = z
|
|
124
|
+
.object({
|
|
125
|
+
font: z
|
|
126
|
+
.enum(["classic", "impact", "serif", "typewriter", "handwriting"])
|
|
127
|
+
.optional(),
|
|
128
|
+
color: z.string().optional().describe("Text color (hex)"),
|
|
129
|
+
treatment: z
|
|
130
|
+
.enum(["none", "shadow", "outline", "highlight"])
|
|
131
|
+
.optional()
|
|
132
|
+
.describe("'highlight' = solid box behind each line (classic TikTok caption)"),
|
|
133
|
+
accentColor: z
|
|
134
|
+
.string()
|
|
135
|
+
.optional()
|
|
136
|
+
.describe("Box color for 'highlight' / stroke color for 'outline' (hex)"),
|
|
137
|
+
fontScale: z.number().min(0.5).max(2.5).optional(),
|
|
138
|
+
x: z.number().min(0).max(1).optional().describe("Anchor x (0..1 of canvas width)"),
|
|
139
|
+
y: z.number().min(0).max(1).optional().describe("Top y (0..1 of canvas height)"),
|
|
140
|
+
maxWidth: z.number().min(0.2).max(1).optional().describe("Text width (fraction of canvas width)"),
|
|
141
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
142
|
+
rotation: z.number().min(-45).max(45).optional().describe("Degrees clockwise"),
|
|
143
|
+
})
|
|
144
|
+
.describe("Optional per-field style overrides; anything omitted uses the template slot's defaults");
|
|
145
|
+
const slideSchema = z.object({
|
|
146
|
+
imageId: z
|
|
147
|
+
.string()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("A library image (from list_images / register_uploaded_image / import_image_from_url) used as the slide background; text is rendered on top server-side. Provide exactly one of imageId, prerenderedFileId, or backgroundFileId per slide."),
|
|
150
|
+
prerenderedFileId: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe("A finished slide image (a fileId from get_image_upload_url, import_image_from_url, or list_images) used as-is. JPEG at 1080x1350 recommended — other formats are re-encoded and cover-cropped to the canvas."),
|
|
154
|
+
backgroundFileId: z
|
|
155
|
+
.string()
|
|
156
|
+
.optional()
|
|
157
|
+
.describe("Background image (raw fileId) for a template slide; text is rendered on top server-side. Prefer imageId when the image is in the library."),
|
|
158
|
+
textFields: z
|
|
159
|
+
.array(z.object({
|
|
160
|
+
key: z
|
|
161
|
+
.string()
|
|
162
|
+
.describe("A template slot key (see list_slideshow_templates) or a 'custom-' prefixed key for a free text box"),
|
|
163
|
+
text: z.string(),
|
|
164
|
+
style: slideTextStyleSchema.optional(),
|
|
165
|
+
}))
|
|
166
|
+
.optional()
|
|
167
|
+
.describe("Template slides only"),
|
|
168
|
+
overlay: z
|
|
169
|
+
.object({
|
|
170
|
+
opacity: z.number().min(0).max(1),
|
|
171
|
+
direction: z.enum(["none", "bottom", "top", "full"]),
|
|
172
|
+
color: z.string().optional().describe("Hex, default #000000"),
|
|
173
|
+
})
|
|
174
|
+
.optional()
|
|
175
|
+
.describe("Darkening scrim over the background (template slides only); omit to use the template's default"),
|
|
176
|
+
});
|
|
113
177
|
// ---- read tools ----
|
|
114
178
|
register("whoami", "Get who this key acts as and which organizations it can work in, plus the key's scopes. When an organization is selected (single-org key, organizationId argument, or POSTRIPPLE_ORG), also returns that workspace's overview: connected accounts, active groups, scheduled posts. Call this first to orient yourself.", { ...orgParam });
|
|
115
179
|
register("list_social_accounts", "List the workspace's connected social accounts (TikTok / Instagram / YouTube) with connection health: status (active/expired/revoked/error), token expiry, and last error. Use the returned accountId for scheduling and metrics tools.", { ...orgParam });
|
|
@@ -120,7 +184,24 @@ register("list_videos", "List videos in the workspace library, newest first. Vid
|
|
|
120
184
|
});
|
|
121
185
|
register("list_video_groups", "List video groups (rotation playlists that auto-post on a schedule) with video/account counts and status.", { ...orgParam });
|
|
122
186
|
register("get_video_group", "Get one video group in detail: its videos and the per-account posting rules (posts per day, posting windows, next scheduled post).", { ...orgParam, groupId: z.string() });
|
|
123
|
-
register("
|
|
187
|
+
register("list_images", "List/search the workspace's image library, newest first (relevance order when text is passed). Every image ever uploaded, imported, or generated lives here until deleted — CHECK THIS BEFORE generating or re-uploading images; reusing an existing image is free and instant. Each result has an imageId (pass to create_slideshow slides), its raw fileId, a signed preview url (~1h), and its title/description/tags/prompt metadata.", {
|
|
188
|
+
...orgParam,
|
|
189
|
+
text: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("Free-text search over title, description, tags, and generation prompt"),
|
|
193
|
+
tags: z
|
|
194
|
+
.array(z.string())
|
|
195
|
+
.optional()
|
|
196
|
+
.describe("Only images carrying ALL of these tags"),
|
|
197
|
+
source: z
|
|
198
|
+
.enum(["upload", "agent_upload", "agent_import", "generated"])
|
|
199
|
+
.optional(),
|
|
200
|
+
limit: z.number().min(1).max(200).optional().describe("Default 100, max 200"),
|
|
201
|
+
});
|
|
202
|
+
register("list_slideshows", "List photo-carousel slideshows with status ('draft' = still being composed, 'ready' = publishable) and a signed coverUrl thumbnail (first rendered slide, or a background for drafts).", { ...orgParam, limit: z.number().min(1).max(200).optional() });
|
|
203
|
+
register("list_slideshow_templates", "List the slideshow layout templates: canvas size, each template's text slots with exact geometry (x/y anchor, maxWidth, fontSize, lineHeight — all normalized to the canvas) and defaults (font, color, treatment), the template's default overlay scrim, available fonts and text treatments, and the max slide count. Call before create_slideshow when composing template-based slides — use the slot geometry rather than inventing your own placement.", {});
|
|
204
|
+
register("get_slideshow", "Get one slideshow in full detail: per-slide render state, each text field's text AND its style overrides (font, color, treatment, fontScale, x/y position, maxWidth, align, rotation — null style means the template slot defaults apply), the slide's overlay scrim, and signed image URLs (renderedUrl = the finished slide, backgroundUrl = the photo behind it; both expire in ~1h). Use it to study how existing slides are actually styled before composing new ones, or poll it after create_slideshow returns status 'rendering' — publish once status is 'ready'.", { ...orgParam, slideshowId: z.string() });
|
|
124
205
|
register("list_posts", "List posts (scheduled, publishing, published, failed, canceled), newest first. Scheduled posts include scheduledFor; published posts include platformPostId and publishedAt.", {
|
|
125
206
|
...orgParam,
|
|
126
207
|
status: postStatusEnum.optional(),
|
|
@@ -178,6 +259,42 @@ register("update_group_schedule", "Attach a social account to a group or update
|
|
|
178
259
|
status: z.enum(["active", "paused"]).optional(),
|
|
179
260
|
postingWindows: z.array(postingWindowSchema).optional(),
|
|
180
261
|
});
|
|
262
|
+
register("get_image_upload_url", "Get a presigned upload URL for pushing an image into the workspace library. PUT the raw image bytes to the returned uploadUrl with a Content-Type header, then call register_uploaded_image with the returned fileId (plus a title/tags so the image is findable later). JPEG at 1080x1350 (4:5) strongly recommended.", { ...orgParam });
|
|
263
|
+
register("register_uploaded_image", "Register an image after uploading its bytes to a presigned URL from get_image_upload_url. This saves it to the workspace image library — it persists until deleted and can be reused in any slideshow (via list_images). Give it a descriptive title and tags so it's easy to find again.", {
|
|
264
|
+
...orgParam,
|
|
265
|
+
fileId: z.string().describe("The fileId returned by get_image_upload_url"),
|
|
266
|
+
title: z.string().optional().describe("Descriptive title — strongly recommended for later discovery"),
|
|
267
|
+
description: z.string().optional(),
|
|
268
|
+
tags: z.array(z.string()).optional().describe("Lowercase keywords for filtering in list_images"),
|
|
269
|
+
});
|
|
270
|
+
register("import_image_from_url", "Import an image into the workspace library from a public URL (synchronous — the returned imageId/fileId are immediately usable in create_slideshow, and the image persists in the library for reuse via list_images). Accepts JPEG/PNG/WebP up to 15MB; JPEG is recommended since other formats get re-encoded and cover-cropped to the 1080x1350 canvas. Give it a title/tags so it's findable later.", {
|
|
271
|
+
...orgParam,
|
|
272
|
+
url: z.string().url(),
|
|
273
|
+
title: z.string().optional().describe("Descriptive title — strongly recommended for later discovery"),
|
|
274
|
+
description: z.string().optional(),
|
|
275
|
+
tags: z.array(z.string()).optional().describe("Lowercase keywords for filtering in list_images"),
|
|
276
|
+
});
|
|
277
|
+
register("update_image", "Update a library image's title, description, or tags (e.g. to make older images easier to rediscover via list_images).", {
|
|
278
|
+
...orgParam,
|
|
279
|
+
imageId: z.string(),
|
|
280
|
+
title: z.string().optional(),
|
|
281
|
+
description: z.string().optional(),
|
|
282
|
+
tags: z.array(z.string()).optional().describe("Replaces the existing tag list"),
|
|
283
|
+
});
|
|
284
|
+
register("delete_image", "Delete a library image and its stored file. If the image is still used as a slide background or character thumbnail, the call returns the references instead of deleting; retry with force: true to delete anyway (those draft slides lose their background). Images that ARE a slideshow's final rendered content can't be deleted at all — delete the slideshow first.", {
|
|
285
|
+
...orgParam,
|
|
286
|
+
imageId: z.string(),
|
|
287
|
+
force: z.boolean().optional(),
|
|
288
|
+
});
|
|
289
|
+
register("create_slideshow", "Create a photo slideshow (1-10 slides). Each slide is EXACTLY ONE OF: {imageId} — a library image (see list_images) as the background, optionally with textFields/overlay; {prerenderedFileId} — a finished image you composed, used as-is; or {backgroundFileId, textFields, overlay} rendered server-side using templateId's layout. Reuse library images via list_images before generating or uploading new ones. Pre-rendered JPEG slides are ready instantly; other slides return status 'rendering' — poll get_slideshow until 'ready', then call publish_slideshow. Requires the slideshows plan feature.", {
|
|
290
|
+
...orgParam,
|
|
291
|
+
title: z.string().optional(),
|
|
292
|
+
templateId: z
|
|
293
|
+
.string()
|
|
294
|
+
.optional()
|
|
295
|
+
.describe("Layout template id (see list_slideshow_templates). Default 'bold-bottom'; use 'blank' for custom-only text."),
|
|
296
|
+
slides: z.array(slideSchema).min(1).max(10),
|
|
297
|
+
});
|
|
181
298
|
// ---- publish tools ----
|
|
182
299
|
register("schedule_post", "Schedule a completed library video to publish on a connected account at a future time. This posts to a REAL social account when the time arrives. The platform is derived from the account. Uses one 'posts' credit (refunded if canceled). ALWAYS pass idempotencyKey (any unique string) so retries can't double-post. For TikTok, pass tiktokSettings to control privacy — omitting it publishes as private (SELF_ONLY). For YouTube, pass title.", {
|
|
183
300
|
...orgParam,
|
|
@@ -203,6 +320,28 @@ register("reschedule_post", "Move a still-scheduled post to a new future time. F
|
|
|
203
320
|
...args,
|
|
204
321
|
scheduledFor: toEpochMs(args.scheduledFor, "scheduledFor"),
|
|
205
322
|
}));
|
|
323
|
+
register("publish_slideshow", "Publish or schedule a READY slideshow to one or more Instagram/TikTok accounts as a native carousel / photo post. This posts to REAL social accounts. Omit scheduledFor to publish immediately; pass it to schedule (uses one 'posts' credit per account, refunded if canceled). ALWAYS pass idempotencyKey so retries can't double-post. TikTok targets without settings publish as SELF_ONLY (private); pass settings with postMode 'inbox' to send a draft to the account's TikTok app inbox instead.", {
|
|
324
|
+
...orgParam,
|
|
325
|
+
slideshowId: z.string(),
|
|
326
|
+
targets: z
|
|
327
|
+
.array(z.object({
|
|
328
|
+
accountId: z.string(),
|
|
329
|
+
settings: tiktokSettingsSchema.optional(),
|
|
330
|
+
}))
|
|
331
|
+
.min(1)
|
|
332
|
+
.describe("Accounts to post to. settings applies to TikTok accounts only."),
|
|
333
|
+
caption: z.string().optional(),
|
|
334
|
+
scheduledFor: scheduledForSchema.optional(),
|
|
335
|
+
idempotencyKey: z
|
|
336
|
+
.string()
|
|
337
|
+
.optional()
|
|
338
|
+
.describe("Unique string for safe retries — strongly recommended"),
|
|
339
|
+
}, (args) => args.scheduledFor === undefined
|
|
340
|
+
? args
|
|
341
|
+
: {
|
|
342
|
+
...args,
|
|
343
|
+
scheduledFor: toEpochMs(args.scheduledFor, "scheduledFor"),
|
|
344
|
+
});
|
|
206
345
|
register("cancel_post", "Cancel a still-scheduled post and refund its credit. Fails once publishing has started.", { ...orgParam, postId: z.string() });
|
|
207
346
|
const transport = new StdioServerTransport();
|
|
208
347
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@post-ripple/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MCP server that lets AI agents (Claude Code, Codex, opencode) manage a Post Ripple workspace: content, scheduling, publishing, and analytics.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|