@post-ripple/mcp 0.2.0 → 0.3.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 +107 -4
- 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.3.0" });
|
|
49
49
|
function register(name, description, shape, transform) {
|
|
50
50
|
server.tool(name, description, shape, async (args) => {
|
|
51
51
|
try {
|
|
@@ -108,8 +108,68 @@ 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
|
+
prerenderedFileId: z
|
|
147
|
+
.string()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("A finished slide image (from get_image_upload_url or import_image_from_url) used as-is. JPEG at 1080x1350 recommended — other formats are re-encoded and cover-cropped to the canvas."),
|
|
150
|
+
backgroundFileId: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe("Background image for a template slide; text is rendered on top server-side."),
|
|
154
|
+
textFields: z
|
|
155
|
+
.array(z.object({
|
|
156
|
+
key: z
|
|
157
|
+
.string()
|
|
158
|
+
.describe("A template slot key (see list_slideshow_templates) or a 'custom-' prefixed key for a free text box"),
|
|
159
|
+
text: z.string(),
|
|
160
|
+
style: slideTextStyleSchema.optional(),
|
|
161
|
+
}))
|
|
162
|
+
.optional()
|
|
163
|
+
.describe("Template slides only"),
|
|
164
|
+
overlay: z
|
|
165
|
+
.object({
|
|
166
|
+
opacity: z.number().min(0).max(1),
|
|
167
|
+
direction: z.enum(["none", "bottom", "top", "full"]),
|
|
168
|
+
color: z.string().optional().describe("Hex, default #000000"),
|
|
169
|
+
})
|
|
170
|
+
.optional()
|
|
171
|
+
.describe("Darkening scrim over the background (template slides only); omit to use the template's default"),
|
|
172
|
+
});
|
|
113
173
|
// ---- read tools ----
|
|
114
174
|
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
175
|
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 });
|
|
@@ -121,6 +181,8 @@ register("list_videos", "List videos in the workspace library, newest first. Vid
|
|
|
121
181
|
register("list_video_groups", "List video groups (rotation playlists that auto-post on a schedule) with video/account counts and status.", { ...orgParam });
|
|
122
182
|
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
183
|
register("list_slideshows", "List photo-carousel slideshows with status ('draft' = still being composed, 'ready' = publishable).", { ...orgParam, limit: z.number().min(1).max(200).optional() });
|
|
184
|
+
register("list_slideshow_templates", "List the slideshow layout templates: canvas size, each template's text slots (keys, defaults), available fonts and text treatments, and the max slide count. Call before create_slideshow when composing template-based slides.", {});
|
|
185
|
+
register("get_slideshow", "Get one slideshow: per-slide render state and overall status, plus server-side render progress ({state, error}). Poll this after create_slideshow returns status 'rendering' — publish once status is 'ready'.", { ...orgParam, slideshowId: z.string() });
|
|
124
186
|
register("list_posts", "List posts (scheduled, publishing, published, failed, canceled), newest first. Scheduled posts include scheduledFor; published posts include platformPostId and publishedAt.", {
|
|
125
187
|
...orgParam,
|
|
126
188
|
status: postStatusEnum.optional(),
|
|
@@ -132,10 +194,18 @@ register("get_post_metrics", "Get engagement for one published post: latest snap
|
|
|
132
194
|
postId: z.string(),
|
|
133
195
|
limit: z.number().min(1).max(200).optional().describe("Max series points, default 50"),
|
|
134
196
|
});
|
|
135
|
-
register("get_account_metrics", "Get follower/profile growth history for
|
|
197
|
+
register("get_account_metrics", "Get follower/profile growth history (daily snapshots) for connected social accounts. Omit accountId to get every account in the organization at once; pass it to get a single account.", {
|
|
136
198
|
...orgParam,
|
|
137
|
-
accountId: z
|
|
138
|
-
|
|
199
|
+
accountId: z
|
|
200
|
+
.string()
|
|
201
|
+
.optional()
|
|
202
|
+
.describe("Omit for all accounts in the organization"),
|
|
203
|
+
limit: z
|
|
204
|
+
.number()
|
|
205
|
+
.min(1)
|
|
206
|
+
.max(200)
|
|
207
|
+
.optional()
|
|
208
|
+
.describe("Max series points per account, default 50"),
|
|
139
209
|
});
|
|
140
210
|
register("get_engagement_summary", "Get an org-wide engagement overview for the last N days: totals, per-platform breakdown, top 5 posts by views, and current follower counts per account. The best starting point for 'how is content performing?'", { ...orgParam, days: z.number().min(1).max(90).optional().describe("Window in days, default 30") });
|
|
141
211
|
// ---- content:write tools ----
|
|
@@ -170,6 +240,17 @@ register("update_group_schedule", "Attach a social account to a group or update
|
|
|
170
240
|
status: z.enum(["active", "paused"]).optional(),
|
|
171
241
|
postingWindows: z.array(postingWindowSchema).optional(),
|
|
172
242
|
});
|
|
243
|
+
register("get_image_upload_url", "Get a presigned upload URL for pushing a slide image into storage. PUT the raw image bytes to the returned uploadUrl with a Content-Type header, then use the returned fileId in create_slideshow. JPEG at 1080x1350 (4:5) strongly recommended.", { ...orgParam });
|
|
244
|
+
register("import_image_from_url", "Import an image into storage from a public URL (synchronous — the returned fileId is immediately usable in create_slideshow). Accepts JPEG/PNG/WebP up to 15MB; JPEG is recommended since other formats get re-encoded and cover-cropped to the 1080x1350 canvas.", { ...orgParam, url: z.string().url() });
|
|
245
|
+
register("create_slideshow", "Create a photo slideshow (1-10 slides). Each slide is EITHER {prerenderedFileId} — a finished image you composed — OR {backgroundFileId, textFields, overlay} rendered server-side using templateId's layout. Pre-rendered JPEG slides are ready instantly; template slides return status 'rendering' — poll get_slideshow until 'ready', then call publish_slideshow. Requires the slideshows plan feature.", {
|
|
246
|
+
...orgParam,
|
|
247
|
+
title: z.string().optional(),
|
|
248
|
+
templateId: z
|
|
249
|
+
.string()
|
|
250
|
+
.optional()
|
|
251
|
+
.describe("Layout template id (see list_slideshow_templates). Default 'bold-bottom'; use 'blank' for custom-only text."),
|
|
252
|
+
slides: z.array(slideSchema).min(1).max(10),
|
|
253
|
+
});
|
|
173
254
|
// ---- publish tools ----
|
|
174
255
|
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.", {
|
|
175
256
|
...orgParam,
|
|
@@ -195,6 +276,28 @@ register("reschedule_post", "Move a still-scheduled post to a new future time. F
|
|
|
195
276
|
...args,
|
|
196
277
|
scheduledFor: toEpochMs(args.scheduledFor, "scheduledFor"),
|
|
197
278
|
}));
|
|
279
|
+
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.", {
|
|
280
|
+
...orgParam,
|
|
281
|
+
slideshowId: z.string(),
|
|
282
|
+
targets: z
|
|
283
|
+
.array(z.object({
|
|
284
|
+
accountId: z.string(),
|
|
285
|
+
settings: tiktokSettingsSchema.optional(),
|
|
286
|
+
}))
|
|
287
|
+
.min(1)
|
|
288
|
+
.describe("Accounts to post to. settings applies to TikTok accounts only."),
|
|
289
|
+
caption: z.string().optional(),
|
|
290
|
+
scheduledFor: scheduledForSchema.optional(),
|
|
291
|
+
idempotencyKey: z
|
|
292
|
+
.string()
|
|
293
|
+
.optional()
|
|
294
|
+
.describe("Unique string for safe retries — strongly recommended"),
|
|
295
|
+
}, (args) => args.scheduledFor === undefined
|
|
296
|
+
? args
|
|
297
|
+
: {
|
|
298
|
+
...args,
|
|
299
|
+
scheduledFor: toEpochMs(args.scheduledFor, "scheduledFor"),
|
|
300
|
+
});
|
|
198
301
|
register("cancel_post", "Cancel a still-scheduled post and refund its credit. Fails once publishing has started.", { ...orgParam, postId: z.string() });
|
|
199
302
|
const transport = new StdioServerTransport();
|
|
200
303
|
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.3.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": {
|