@post-ripple/mcp 0.5.0 → 0.7.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 +71 -5
- package/package.json +1 -1
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.7.0" });
|
|
49
49
|
function register(name, description, shape, transform) {
|
|
50
50
|
server.tool(name, description, shape, async (args) => {
|
|
51
51
|
try {
|
|
@@ -184,21 +184,41 @@ register("list_videos", "List videos in the workspace library, newest first. Vid
|
|
|
184
184
|
});
|
|
185
185
|
register("list_video_groups", "List video groups (rotation playlists that auto-post on a schedule) with video/account counts and status.", { ...orgParam });
|
|
186
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() });
|
|
187
|
-
register("list_images", "List
|
|
187
|
+
register("list_images", "List or search the workspace's image library. Every image ever uploaded, imported, or generated lives here until deleted, and each is auto-captioned + tagged by AI on ingest — CHECK THIS BEFORE generating or re-uploading images; reusing an existing image is free and instant. Passing `text` runs hybrid SEMANTIC search (natural-language meaning + keyword), so 'cozy sunlit gym' finds matching photos even if those words aren't in the metadata — describe what you want. Without `text`, it lists newest-first and honors tags/source filters. Each result has an imageId (pass to create_slideshow slides), raw fileId, a signed preview url (~1h), and title/description/tags/aiCaption/prompt metadata.", {
|
|
188
188
|
...orgParam,
|
|
189
189
|
text: z
|
|
190
190
|
.string()
|
|
191
191
|
.optional()
|
|
192
|
-
.describe("
|
|
192
|
+
.describe("Natural-language / keyword query — runs hybrid semantic search over the image's caption, tags, description, and prompt. When set, tags/source filters are ignored."),
|
|
193
193
|
tags: z
|
|
194
194
|
.array(z.string())
|
|
195
195
|
.optional()
|
|
196
|
-
.describe("Only images carrying ALL of these tags"),
|
|
196
|
+
.describe("Only images carrying ALL of these tags (ignored when text is set)"),
|
|
197
197
|
source: z
|
|
198
198
|
.enum(["upload", "agent_upload", "agent_import", "generated"])
|
|
199
|
-
.optional()
|
|
199
|
+
.optional()
|
|
200
|
+
.describe("Filter by origin (ignored when text is set)"),
|
|
200
201
|
limit: z.number().min(1).max(200).optional().describe("Default 100, max 200"),
|
|
201
202
|
});
|
|
203
|
+
register("list_hooks", "List or search the shared HOOK VIDEO library — short vertical (9:16) clips (talking-head selfies, reactions, unboxings, B-roll) meant to open a short-form video. Each is auto-captioned + tagged by AI. Pass `text` for a keyword search over titles, tags, and AI captions (e.g. 'guy talking to camera', 'unboxing'); pass `tags` to require specific tags; omit both to list newest-first. Each result has a hookId, title, type ('ai' or 'human'), merged tags, aiCaption, and signed preview/video URLs (~1h). This library is shared platform-wide, not per-workspace.", {
|
|
204
|
+
text: z
|
|
205
|
+
.string()
|
|
206
|
+
.optional()
|
|
207
|
+
.describe("Keyword query over hook title, tags, description, and AI caption"),
|
|
208
|
+
tags: z
|
|
209
|
+
.array(z.string())
|
|
210
|
+
.optional()
|
|
211
|
+
.describe("Only hooks carrying ALL of these tags (human or AI tags)"),
|
|
212
|
+
type: z
|
|
213
|
+
.enum(["ai", "human"])
|
|
214
|
+
.optional()
|
|
215
|
+
.describe("Filter by origin: AI-generated or human-recorded"),
|
|
216
|
+
limit: z.number().min(1).max(200).optional().describe("Default 50, max 200"),
|
|
217
|
+
});
|
|
218
|
+
register("list_green_screens", "List the shared GREEN SCREEN clip library — talking/reacting people cut out on a green screen, composited over a background image by create_video (type 'green_screen_meme'). Each result has a greenScreenId, title, and signed preview/video URLs (~1h). Shared platform-wide, not per-workspace.", { limit: z.number().min(1).max(200).optional().describe("Default 50, max 200") });
|
|
219
|
+
register("list_backgrounds", "List the shared BACKGROUND image library used behind green screen memes (create_video type 'green_screen_meme'). Each result has a backgroundId, title, and a signed image URL (~1h). Shared platform-wide, not per-workspace.", { limit: z.number().min(1).max(200).optional().describe("Default 50, max 200") });
|
|
220
|
+
register("list_music", "List the platform MUSIC library — background tracks that can be laid over a hook+demo video (create_video type 'hook_demo', via musicId). Each result has a musicId, title, duration, and a signed audio URL (~1h). Shared platform-wide, not per-workspace.", { limit: z.number().min(1).max(200).optional().describe("Default 50, max 200") });
|
|
221
|
+
register("get_creation", "Get the render status of a video created with create_video. Poll until status is 'completed' — the result then includes resultVideoId (publish it with schedule_post or add it to a group) plus signed video/preview URLs. status 'failed' includes the error.", { ...orgParam, creationId: z.string().describe("The creationId returned by create_video") });
|
|
202
222
|
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
223
|
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
224
|
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() });
|
|
@@ -295,6 +315,52 @@ register("create_slideshow", "Create a photo slideshow (1-10 slides). Each slide
|
|
|
295
315
|
.describe("Layout template id (see list_slideshow_templates). Default 'bold-bottom'; use 'blank' for custom-only text."),
|
|
296
316
|
slides: z.array(slideSchema).min(1).max(10),
|
|
297
317
|
});
|
|
318
|
+
const captionStyleSchema = z
|
|
319
|
+
.object({
|
|
320
|
+
font: z
|
|
321
|
+
.enum(["classic", "impact", "serif", "typewriter", "handwriting"])
|
|
322
|
+
.optional(),
|
|
323
|
+
color: z.string().optional().describe("Text color, hex (default white)"),
|
|
324
|
+
treatment: z.enum(["none", "shadow", "outline", "highlight"]).optional(),
|
|
325
|
+
accentColor: z
|
|
326
|
+
.string()
|
|
327
|
+
.optional()
|
|
328
|
+
.describe("Highlight box / outline stroke color, hex"),
|
|
329
|
+
fontScale: z
|
|
330
|
+
.number()
|
|
331
|
+
.optional()
|
|
332
|
+
.describe("Multiplier on the base caption size (1 = default)"),
|
|
333
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
334
|
+
x: z
|
|
335
|
+
.number()
|
|
336
|
+
.min(0)
|
|
337
|
+
.max(1)
|
|
338
|
+
.optional()
|
|
339
|
+
.describe("Free caption position: normalized center anchor (overrides captionPosition)"),
|
|
340
|
+
y: z.number().min(0).max(1).optional(),
|
|
341
|
+
})
|
|
342
|
+
.describe("Caption styling; defaults to white TikTok-bold text with a black outline");
|
|
343
|
+
register("create_video", "Render a short-form (9:16) video from library ingredients. Two types: 'hook_demo' stitches a hook clip (list_hooks) in front of your demo/product video (a completed library video via demoVideoId, or a raw upload via demoFileId from get_video_upload_url), with optional background music (musicId from list_music). 'green_screen_meme' composites a green-screen clip (list_green_screens) over a background image (list_backgrounds). Both take an optional caption with styling. Rendering is asynchronous — poll get_creation with the returned creationId until 'completed', then publish resultVideoId with schedule_post. Uses one 'creations' credit from the plan.", {
|
|
344
|
+
...orgParam,
|
|
345
|
+
type: z.enum(["hook_demo", "green_screen_meme"]),
|
|
346
|
+
title: z.string().optional().describe("Library title for the finished video"),
|
|
347
|
+
caption: z.string().optional().describe("Text overlaid on the video (hook part only for hook_demo)"),
|
|
348
|
+
captionPosition: z.enum(["top", "middle", "bottom"]).optional(),
|
|
349
|
+
captionStyle: captionStyleSchema.optional(),
|
|
350
|
+
hookId: z.string().optional().describe("hook_demo: hook clip id from list_hooks (required)"),
|
|
351
|
+
demoVideoId: z
|
|
352
|
+
.string()
|
|
353
|
+
.optional()
|
|
354
|
+
.describe("hook_demo: a COMPLETED library video id (list_videos) to play after the hook. Exactly one of demoVideoId/demoFileId."),
|
|
355
|
+
demoFileId: z
|
|
356
|
+
.string()
|
|
357
|
+
.optional()
|
|
358
|
+
.describe("hook_demo: raw uploaded file key from get_video_upload_url (alternative to demoVideoId)"),
|
|
359
|
+
musicId: z.string().optional().describe("hook_demo: optional background track from list_music"),
|
|
360
|
+
soundId: z.string().optional().describe("hook_demo: optional org-uploaded sound id (alternative to musicId)"),
|
|
361
|
+
greenScreenId: z.string().optional().describe("green_screen_meme: clip id from list_green_screens (required)"),
|
|
362
|
+
backgroundId: z.string().optional().describe("green_screen_meme: image id from list_backgrounds (required)"),
|
|
363
|
+
});
|
|
298
364
|
// ---- publish tools ----
|
|
299
365
|
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.", {
|
|
300
366
|
...orgParam,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@post-ripple/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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": {
|