@post-ripple/mcp 0.1.0 → 0.2.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 +2 -0
- package/dist/index.js +35 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ MCP server that lets AI agents (Claude Code, Codex, opencode, and any other MCP
|
|
|
10
10
|
- `publish` — schedule/cancel posts to real social accounts (off by default; enable deliberately)
|
|
11
11
|
2. Give your agent the key as the `POSTRIPPLE_API_KEY` environment variable. That's the only configuration needed.
|
|
12
12
|
|
|
13
|
+
Keys are personal: one key works in **every organization you belong to** (unless you restricted it at creation). Agents discover your organizations via `whoami` and pass `organizationId` per call — or set the optional `POSTRIPPLE_ORG` environment variable to pin a default organization (handy for pinning one brand per repo).
|
|
14
|
+
|
|
13
15
|
### Claude Code
|
|
14
16
|
|
|
15
17
|
```sh
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,9 @@ const DEFAULT_API_URL = "https://api.postripple.app";
|
|
|
6
6
|
const API_KEY = process.env.POSTRIPPLE_API_KEY;
|
|
7
7
|
// Override only for development against a non-production deployment.
|
|
8
8
|
const API_URL = (process.env.POSTRIPPLE_API_URL ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
9
|
+
// Optional default organization for keys that span several. A per-call
|
|
10
|
+
// organizationId argument always wins over this.
|
|
11
|
+
const DEFAULT_ORG = process.env.POSTRIPPLE_ORG;
|
|
9
12
|
if (!API_KEY) {
|
|
10
13
|
console.error("POSTRIPPLE_API_KEY is not set. Create a key in Post Ripple under Settings → API Keys (Pro plan and up) and set it in the MCP server env.");
|
|
11
14
|
process.exit(1);
|
|
@@ -42,11 +45,14 @@ function toEpochMs(value, field) {
|
|
|
42
45
|
}
|
|
43
46
|
return parsed;
|
|
44
47
|
}
|
|
45
|
-
const server = new McpServer({ name: "postripple", version: "0.
|
|
48
|
+
const server = new McpServer({ name: "postripple", version: "0.2.0" });
|
|
46
49
|
function register(name, description, shape, transform) {
|
|
47
50
|
server.tool(name, description, shape, async (args) => {
|
|
48
51
|
try {
|
|
49
|
-
|
|
52
|
+
let body = transform ? transform(args) : args;
|
|
53
|
+
if (DEFAULT_ORG && body.organizationId === undefined) {
|
|
54
|
+
body = { ...body, organizationId: DEFAULT_ORG };
|
|
55
|
+
}
|
|
50
56
|
const data = await callApi(name, body);
|
|
51
57
|
return {
|
|
52
58
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
@@ -61,6 +67,14 @@ function register(name, description, shape, transform) {
|
|
|
61
67
|
}
|
|
62
68
|
});
|
|
63
69
|
}
|
|
70
|
+
// Keys are personal and can span several organizations. Every org-scoped
|
|
71
|
+
// tool accepts this; whoami lists the available organizations.
|
|
72
|
+
const orgParam = {
|
|
73
|
+
organizationId: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Target organization id (see whoami's organizations list). Optional when the key has access to a single organization or POSTRIPPLE_ORG is set."),
|
|
77
|
+
};
|
|
64
78
|
const platformEnum = z.enum(["tiktok", "instagram", "youtube", "other"]);
|
|
65
79
|
const postStatusEnum = z.enum([
|
|
66
80
|
"scheduled",
|
|
@@ -97,50 +111,59 @@ const tiktokSettingsSchema = z
|
|
|
97
111
|
})
|
|
98
112
|
.describe("TikTok compliance settings. If omitted, the post publishes as SELF_ONLY (private).");
|
|
99
113
|
// ---- read tools ----
|
|
100
|
-
register("whoami", "Get
|
|
101
|
-
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.", {});
|
|
114
|
+
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
|
+
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 });
|
|
102
116
|
register("list_videos", "List videos in the workspace library, newest first. Videos with status 'completed' are publishable; 'pending'/'downloading' are still being imported.", {
|
|
117
|
+
...orgParam,
|
|
103
118
|
status: videoStatusEnum.optional(),
|
|
104
119
|
limit: z.number().min(1).max(200).optional().describe("Default 50, max 200"),
|
|
105
120
|
});
|
|
106
|
-
register("list_video_groups", "List video groups (rotation playlists that auto-post on a schedule) with video/account counts and status.", {});
|
|
107
|
-
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).", { groupId: z.string() });
|
|
108
|
-
register("list_slideshows", "List photo-carousel slideshows with status ('draft' = still being composed, 'ready' = publishable).", { limit: z.number().min(1).max(200).optional() });
|
|
121
|
+
register("list_video_groups", "List video groups (rotation playlists that auto-post on a schedule) with video/account counts and status.", { ...orgParam });
|
|
122
|
+
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("list_slideshows", "List photo-carousel slideshows with status ('draft' = still being composed, 'ready' = publishable).", { ...orgParam, limit: z.number().min(1).max(200).optional() });
|
|
109
124
|
register("list_posts", "List posts (scheduled, publishing, published, failed, canceled), newest first. Scheduled posts include scheduledFor; published posts include platformPostId and publishedAt.", {
|
|
125
|
+
...orgParam,
|
|
110
126
|
status: postStatusEnum.optional(),
|
|
111
127
|
platform: platformEnum.optional(),
|
|
112
128
|
limit: z.number().min(1).max(200).optional(),
|
|
113
129
|
});
|
|
114
130
|
register("get_post_metrics", "Get engagement for one published post: latest snapshot (views/likes/comments/shares) plus the time series of scraped metrics.", {
|
|
131
|
+
...orgParam,
|
|
115
132
|
postId: z.string(),
|
|
116
133
|
limit: z.number().min(1).max(200).optional().describe("Max series points, default 50"),
|
|
117
134
|
});
|
|
118
135
|
register("get_account_metrics", "Get follower/profile growth history for one connected social account (daily snapshots).", {
|
|
136
|
+
...orgParam,
|
|
119
137
|
accountId: z.string(),
|
|
120
138
|
limit: z.number().min(1).max(200).optional(),
|
|
121
139
|
});
|
|
122
|
-
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?'", { days: z.number().min(1).max(90).optional().describe("Window in days, default 30") });
|
|
140
|
+
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") });
|
|
123
141
|
// ---- content:write tools ----
|
|
124
142
|
register("import_video_from_url", "Import a video into the library from a public URL (e.g. a TikTok/Instagram/YouTube link or a direct file URL). Download happens asynchronously — poll list_videos until its status is 'completed' before scheduling it.", {
|
|
143
|
+
...orgParam,
|
|
125
144
|
url: z.string().url(),
|
|
126
145
|
platform: platformEnum
|
|
127
146
|
.optional()
|
|
128
147
|
.describe("Source platform of the URL; helps the downloader. Defaults to 'other'."),
|
|
129
148
|
});
|
|
130
|
-
register("get_video_upload_url", "Get a presigned upload URL for pushing a local video file into the library. PUT the raw video bytes to the returned uploadUrl, then call register_uploaded_video with the returned fileId.", {});
|
|
149
|
+
register("get_video_upload_url", "Get a presigned upload URL for pushing a local video file into the library. PUT the raw video bytes to the returned uploadUrl, then call register_uploaded_video with the returned fileId.", { ...orgParam });
|
|
131
150
|
register("register_uploaded_video", "Register a video after uploading its bytes to a presigned URL from get_video_upload_url. The video becomes immediately publishable.", {
|
|
151
|
+
...orgParam,
|
|
132
152
|
fileId: z.string().describe("The fileId returned by get_video_upload_url"),
|
|
133
153
|
title: z.string(),
|
|
134
154
|
});
|
|
135
155
|
register("create_video_group", "Create a video group (a shuffle rotation playlist). Attach accounts with update_group_schedule and add videos with add_videos_to_group to start auto-posting. Uses one 'groups' credit from the plan.", {
|
|
156
|
+
...orgParam,
|
|
136
157
|
name: z.string(),
|
|
137
158
|
description: z.string().optional(),
|
|
138
159
|
});
|
|
139
160
|
register("add_videos_to_group", "Add up to 100 library videos to a group's rotation. Returns which were added and which were skipped (already present / not found).", {
|
|
161
|
+
...orgParam,
|
|
140
162
|
groupId: z.string(),
|
|
141
163
|
videoIds: z.array(z.string()).min(1).max(100),
|
|
142
164
|
});
|
|
143
165
|
register("update_group_schedule", "Attach a social account to a group or update its posting rules: posts per day (1-24), active/paused, and optional posting time windows (hours in the org's timezone). First call for an account must include postsPerDay.", {
|
|
166
|
+
...orgParam,
|
|
144
167
|
groupId: z.string(),
|
|
145
168
|
accountId: z.string(),
|
|
146
169
|
postsPerDay: z.number().min(1).max(24).optional(),
|
|
@@ -149,6 +172,7 @@ register("update_group_schedule", "Attach a social account to a group or update
|
|
|
149
172
|
});
|
|
150
173
|
// ---- publish tools ----
|
|
151
174
|
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
|
+
...orgParam,
|
|
152
176
|
videoId: z.string(),
|
|
153
177
|
accountId: z.string(),
|
|
154
178
|
scheduledFor: scheduledForSchema,
|
|
@@ -164,13 +188,14 @@ register("schedule_post", "Schedule a completed library video to publish on a co
|
|
|
164
188
|
scheduledFor: toEpochMs(args.scheduledFor, "scheduledFor"),
|
|
165
189
|
}));
|
|
166
190
|
register("reschedule_post", "Move a still-scheduled post to a new future time. Fails once publishing has started.", {
|
|
191
|
+
...orgParam,
|
|
167
192
|
postId: z.string(),
|
|
168
193
|
scheduledFor: scheduledForSchema,
|
|
169
194
|
}, (args) => ({
|
|
170
195
|
...args,
|
|
171
196
|
scheduledFor: toEpochMs(args.scheduledFor, "scheduledFor"),
|
|
172
197
|
}));
|
|
173
|
-
register("cancel_post", "Cancel a still-scheduled post and refund its credit. Fails once publishing has started.", { postId: z.string() });
|
|
198
|
+
register("cancel_post", "Cancel a still-scheduled post and refund its credit. Fails once publishing has started.", { ...orgParam, postId: z.string() });
|
|
174
199
|
const transport = new StdioServerTransport();
|
|
175
200
|
await server.connect(transport);
|
|
176
201
|
console.error(`Post Ripple MCP server connected (${API_URL})`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@post-ripple/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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": {
|