@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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +35 -10
  3. 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.1.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
- const body = transform ? transform(args) : args;
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 the connected Post Ripple workspace: organization name, plan, connected social accounts, active group count, scheduled post count, and this key's scopes. Call this first to orient yourself.", {});
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.1.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": {