@jclvsh/dropspace 2.33.0 → 2.34.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 +85 -50
- package/dist/cli.js +2 -0
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.js +50 -0
- package/dist/commands/connections.js +2 -4
- package/dist/commands/launches.js +88 -0
- package/dist/server.js +4 -0
- package/dist/tool-policy.d.ts +5 -4
- package/dist/tool-policy.js +24 -3
- package/dist/tools/account.d.ts +3 -0
- package/dist/tools/account.js +40 -0
- package/dist/tools/comments.d.ts +5 -0
- package/dist/tools/comments.js +79 -0
- package/dist/tools/connections.d.ts +2 -0
- package/dist/tools/connections.js +36 -1
- package/dist/tools/launches.js +21 -0
- package/dist/tools/media.js +1 -1
- package/dist/types.d.ts +16 -0
- package/package.json +1 -1
- package/skills/dropspace/SKILL.md +6 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jclvsh/dropspace
|
|
2
2
|
|
|
3
|
-
MCP server and CLI for the [Dropspace](https://www.dropspace.dev) API. Lets AI agents (Claude Code, Cursor) and humans manage launches, personas, media generation, boosts, autopilot, recommendations,
|
|
3
|
+
MCP server and CLI for the [Dropspace](https://www.dropspace.dev) API. Lets AI agents (Claude Code, Cursor) and humans manage launches, comments, personas, media generation, boosts, autopilot, recommendations, subreddit discovery, platform connections, profile and billing, API keys, and webhooks. The full tool list, by category, is in the tables below and at https://www.dropspace.dev/docs#mcp-server.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -44,23 +44,25 @@ dropspace launches --help
|
|
|
44
44
|
|
|
45
45
|
### CLI Commands
|
|
46
46
|
|
|
47
|
-
| Group | Commands
|
|
48
|
-
| ---------------------- |
|
|
49
|
-
| launches | list, create, get, update, delete, publish, retry, generate, retry-content, analytics, batch-analytics, status, delete-post, delete-posts |
|
|
50
|
-
| personas | list, create, get, update, delete, analyze
|
|
51
|
-
| media | generate, job, library list, library add, library describe, library delete
|
|
52
|
-
| connections | list
|
|
53
|
-
| boosts | create, list, get, activate, cancel, performance
|
|
54
|
-
| reddit | suggest
|
|
55
|
-
| credits | balance, purchase
|
|
56
|
-
| keys | me, list, create, rename, revoke, rotate
|
|
57
|
-
| webhooks | list, create, get, update, delete, test, rotate-secret, deliveries
|
|
58
|
-
| status | dropspace official account connections
|
|
59
|
-
| usage | plan, limits, and billing period
|
|
60
|
-
| changelog | machine-readable release history
|
|
61
|
-
| audience | follower counts + growth per connected account
|
|
62
|
-
| autopilot | status, configure, review, approve, reject, run, runs
|
|
63
|
-
| recommendations (recs) | list, apply, dismiss
|
|
47
|
+
| Group | Commands |
|
|
48
|
+
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
49
|
+
| launches | list, create, get, update, delete, publish, retry, generate, retry-content, analytics, batch-analytics, status, destinations, delete-post, delete-posts, comments list, comments reply, comments hide, comments delete |
|
|
50
|
+
| personas | list, create, get, update, delete, analyze |
|
|
51
|
+
| media | generate, job, library list, library add, library describe, library delete |
|
|
52
|
+
| connections | list, disconnect, profile |
|
|
53
|
+
| boosts | create, list, get, activate, cancel, performance |
|
|
54
|
+
| reddit | suggest |
|
|
55
|
+
| credits | balance, purchase |
|
|
56
|
+
| keys | me, list, create, rename, revoke, rotate |
|
|
57
|
+
| webhooks | list, create, get, update, delete, test, rotate-secret, deliveries |
|
|
58
|
+
| status | dropspace official account connections |
|
|
59
|
+
| usage | plan, limits, and billing period |
|
|
60
|
+
| changelog | machine-readable release history |
|
|
61
|
+
| audience | follower counts + growth per connected account |
|
|
62
|
+
| autopilot | status, configure, review, approve, reject, run, runs |
|
|
63
|
+
| recommendations (recs) | list, apply, dismiss |
|
|
64
|
+
| profile | get, update |
|
|
65
|
+
| billing | portal |
|
|
64
66
|
|
|
65
67
|
## MCP Server Configuration
|
|
66
68
|
|
|
@@ -119,7 +121,7 @@ https://api.dropspace.dev/mcp
|
|
|
119
121
|
|
|
120
122
|
- Transport: MCP streamable HTTP, `POST` only, stateless, JSON responses.
|
|
121
123
|
- Auth: `Authorization: Bearer ds_live_...` (the same API key; its scopes apply).
|
|
122
|
-
- Tools: a subset of this package. API key, webhook, boost, credit, and
|
|
124
|
+
- Tools: a subset of this package. API key, webhook, boost, credit, reddit, media library, comment, destination, disconnect, profile, and billing tools are only available through the npm package (stdio) for now.
|
|
123
125
|
- Every tool is annotated by class: `read` (`readOnlyHint: true`), `write`, or `sensitive` (`destructiveHint: true`: it publishes, schedules, or deletes on a connected platform, or deletes a launch or persona with no restore). The class of each hosted tool is listed at https://www.dropspace.dev/docs#mcp-hosted, and the source of truth is `src/tool-policy.ts`.
|
|
124
126
|
- `create_launch` sends a content-derived `Idempotency-Key`, so a retry after a timeout returns the launch already created instead of creating a second one (the result says so).
|
|
125
127
|
- Each MCP request counts once against the key's rate limit, and each tool call counts once more when it reaches the REST API.
|
|
@@ -140,20 +142,21 @@ Other agents that read skill files can use the same file from `node_modules/@jcl
|
|
|
140
142
|
|
|
141
143
|
### Launches
|
|
142
144
|
|
|
143
|
-
| Tool | Description
|
|
144
|
-
| ---------------------------- |
|
|
145
|
-
| `list_launches` | List your launches with pagination
|
|
146
|
-
| `create_launch` | Create a new launch with content, media, and account selection
|
|
147
|
-
| `get_launch` | Get a launch by ID with posting status
|
|
148
|
-
| `update_launch` | Update a launch's schedule, status, content, or media
|
|
149
|
-
| `delete_launch` | Delete a launch
|
|
150
|
-
| `publish_launch` | Publish a launch to all configured platforms
|
|
151
|
-
| `retry_launch` | Retry failed platforms for a launch
|
|
152
|
-
| `generate_content` | Generate AI content and video scripts for a launch
|
|
153
|
-
| `retry_launch_content` | Retry AI content generation for failed platforms
|
|
154
|
-
| `get_launch_analytics` | Get publishing analytics with per-post engagement metrics
|
|
155
|
-
| `get_batch_launch_analytics` | Get analytics for multiple launches in one call
|
|
156
|
-
| `get_launch_status` | Get detailed posting status with per-platform logs
|
|
145
|
+
| Tool | Description |
|
|
146
|
+
| ---------------------------- | ------------------------------------------------------------------ |
|
|
147
|
+
| `list_launches` | List your launches with pagination |
|
|
148
|
+
| `create_launch` | Create a new launch with content, media, and account selection |
|
|
149
|
+
| `get_launch` | Get a launch by ID with posting status |
|
|
150
|
+
| `update_launch` | Update a launch's schedule, status, content, or media |
|
|
151
|
+
| `delete_launch` | Delete a launch |
|
|
152
|
+
| `publish_launch` | Publish a launch to all configured platforms |
|
|
153
|
+
| `retry_launch` | Retry failed platforms for a launch |
|
|
154
|
+
| `generate_content` | Generate AI content and video scripts for a launch |
|
|
155
|
+
| `retry_launch_content` | Retry AI content generation for failed platforms |
|
|
156
|
+
| `get_launch_analytics` | Get publishing analytics with per-post engagement metrics |
|
|
157
|
+
| `get_batch_launch_analytics` | Get analytics for multiple launches in one call |
|
|
158
|
+
| `get_launch_status` | Get detailed posting status with per-platform logs |
|
|
159
|
+
| `get_launch_destinations` | One row per destination, naming each reddit subreddit (stdio only) |
|
|
157
160
|
|
|
158
161
|
#### `create_launch` Parameters
|
|
159
162
|
|
|
@@ -252,9 +255,30 @@ Other agents that read skill files can use the same file from `node_modules/@jcl
|
|
|
252
255
|
|
|
253
256
|
### Connections
|
|
254
257
|
|
|
255
|
-
| Tool
|
|
256
|
-
|
|
|
257
|
-
| `list_connections`
|
|
258
|
+
| Tool | Description |
|
|
259
|
+
| --------------------------- | ---------------------------------------------------------------------------------- |
|
|
260
|
+
| `list_connections` | List your connected social media accounts |
|
|
261
|
+
| `disconnect_connection` | Disconnect a platform, or one account on it with `account_id` (stdio only) |
|
|
262
|
+
| `update_connection_profile` | Edit the connected account's profile on the platform (youtube channel description) |
|
|
263
|
+
|
|
264
|
+
### Comments
|
|
265
|
+
|
|
266
|
+
| Tool | Description |
|
|
267
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------- |
|
|
268
|
+
| `list_launch_comments` | Comments on a launch's published posts, newest first, per post with a support status (stdio only) |
|
|
269
|
+
| `reply_to_comment` | Reply as the publishing account: youtube and instagram (send scope, stdio only) |
|
|
270
|
+
| `hide_comment` | Hide or unhide a comment: instagram (send scope, stdio only) |
|
|
271
|
+
| `delete_comment` | Permanently delete a comment: instagram, media owner only (send scope, stdio only) |
|
|
272
|
+
|
|
273
|
+
CLI: `dropspace launches comments list|reply|hide|delete`.
|
|
274
|
+
|
|
275
|
+
### Reddit
|
|
276
|
+
|
|
277
|
+
| Tool | Description |
|
|
278
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
279
|
+
| `suggest_subreddits` | Subreddits matched to a product brief, with promo policy and a standing badge per community (stdio only) |
|
|
280
|
+
|
|
281
|
+
Reddit engagement (drafted comments in other communities) is paused: its tools and CLI commands are not served.
|
|
258
282
|
|
|
259
283
|
### Boosts
|
|
260
284
|
|
|
@@ -371,6 +395,16 @@ Counts come from each platform's official API and are cached for 15 minutes. You
|
|
|
371
395
|
|
|
372
396
|
Autopilot is available on pro and premium plans. `configure_autopilot` takes `media_source` (`ai`, `library`, `mixed`) to post the user's own photos and videos, and `product_image_url` for AI slides built from the real product.
|
|
373
397
|
|
|
398
|
+
### Account
|
|
399
|
+
|
|
400
|
+
| Tool | Description |
|
|
401
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
402
|
+
| `get_profile` | Get your profile (stdio only) |
|
|
403
|
+
| `update_profile` | Update your `full_name` and/or `avatar_url` (stdio only) |
|
|
404
|
+
| `create_billing_portal_session` | A URL to manage your subscription; pre-authenticated for subscribers, so don't share it (stdio only) |
|
|
405
|
+
|
|
406
|
+
CLI: `dropspace profile get|update`, `dropspace billing portal`.
|
|
407
|
+
|
|
374
408
|
### Recommendations
|
|
375
409
|
|
|
376
410
|
| Tool | Description |
|
|
@@ -387,23 +421,24 @@ Generate an API key at [www.dropspace.dev/settings](https://www.dropspace.dev/se
|
|
|
387
421
|
|
|
388
422
|
API keys support scoped permissions:
|
|
389
423
|
|
|
390
|
-
| Scope | Access
|
|
391
|
-
| ---------- |
|
|
392
|
-
| `read` | List and read launches, personas, connections, dropspace status
|
|
393
|
-
| `write` | Create and
|
|
394
|
-
| `
|
|
395
|
-
| `
|
|
396
|
-
| `
|
|
397
|
-
|
|
424
|
+
| Scope | Access |
|
|
425
|
+
| ---------- | ---------------------------------------------------------------------------- |
|
|
426
|
+
| `read` | List and read launches, personas, connections, dropspace status |
|
|
427
|
+
| `write` | Create, update, and soft-delete launches and personas |
|
|
428
|
+
| `generate` | Generate AI content and media, retry content generation |
|
|
429
|
+
| `send` | Publish and retry launches, reply to and moderate comments, deliver webhooks |
|
|
430
|
+
| `admin` | Manage API keys and webhooks |
|
|
431
|
+
|
|
432
|
+
Keys created without a scopes list get `read` and `write`. The legacy `publish` and `delete` scopes were retired on 07/20/2026 (use `send` and `write`).
|
|
398
433
|
|
|
399
434
|
### Recommended Scopes by Use Case
|
|
400
435
|
|
|
401
|
-
| Use Case | Recommended Scopes
|
|
402
|
-
| ---------------- |
|
|
403
|
-
| AI agents / MCP | `read`, `write`, `generate`
|
|
404
|
-
| CI/CD publishing | `read`, `
|
|
405
|
-
| Monitoring | `read`
|
|
406
|
-
| Full management | `read`, `write`, `
|
|
436
|
+
| Use Case | Recommended Scopes |
|
|
437
|
+
| ---------------- | -------------------------------------------- |
|
|
438
|
+
| AI agents / MCP | `read`, `write`, `generate` |
|
|
439
|
+
| CI/CD publishing | `read`, `send` |
|
|
440
|
+
| Monitoring | `read` |
|
|
441
|
+
| Full management | `read`, `write`, `generate`, `send`, `admin` |
|
|
407
442
|
|
|
408
443
|
## Webhook Events
|
|
409
444
|
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { registerRedditCommands } from "./commands/reddit.js";
|
|
|
18
18
|
import { registerAutopilotCommands } from "./commands/autopilot.js";
|
|
19
19
|
import { registerRecommendationCommands } from "./commands/recommendations.js";
|
|
20
20
|
import { registerAudienceCommands } from "./commands/audience.js";
|
|
21
|
+
import { registerAccountCommands } from "./commands/account.js";
|
|
21
22
|
const program = new Command()
|
|
22
23
|
.name("dropspace")
|
|
23
24
|
.description("dropspace CLI - manage launches, personas, boosts, and more")
|
|
@@ -40,6 +41,7 @@ try {
|
|
|
40
41
|
registerAutopilotCommands(program, client);
|
|
41
42
|
registerRecommendationCommands(program, client);
|
|
42
43
|
registerRedditCommands(program, client);
|
|
44
|
+
registerAccountCommands(program, client);
|
|
43
45
|
await program.parseAsync(process.argv);
|
|
44
46
|
}
|
|
45
47
|
catch (error) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { formatOutput, handleError, getFormat } from "../cli-utils.js";
|
|
2
|
+
export function registerAccountCommands(program, client) {
|
|
3
|
+
const profile = program.command("profile").description("your dropspace profile");
|
|
4
|
+
profile
|
|
5
|
+
.command("get")
|
|
6
|
+
.description("get your profile")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
try {
|
|
9
|
+
const result = await client.get("/profile");
|
|
10
|
+
formatOutput(result, getFormat(program.opts()));
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
handleError(error);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
profile
|
|
17
|
+
.command("update")
|
|
18
|
+
.description("update your name and/or avatar")
|
|
19
|
+
.option("-n, --name <name>", "display name (1-120 characters)")
|
|
20
|
+
.option("--avatar <url>", "public URL of the avatar image")
|
|
21
|
+
.action(async (opts) => {
|
|
22
|
+
try {
|
|
23
|
+
const body = {};
|
|
24
|
+
if (opts.name)
|
|
25
|
+
body.full_name = opts.name;
|
|
26
|
+
if (opts.avatar)
|
|
27
|
+
body.avatar_url = opts.avatar;
|
|
28
|
+
if (Object.keys(body).length === 0)
|
|
29
|
+
throw new Error("provide --name or --avatar");
|
|
30
|
+
const result = await client.patch("/profile", body);
|
|
31
|
+
formatOutput(result, getFormat(program.opts()));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
handleError(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const billing = program.command("billing").description("manage your subscription");
|
|
38
|
+
billing
|
|
39
|
+
.command("portal")
|
|
40
|
+
.description("get a URL to manage your subscription (pre-authenticated for subscribers; don't share it)")
|
|
41
|
+
.action(async () => {
|
|
42
|
+
try {
|
|
43
|
+
const result = await client.post("/billing/portal");
|
|
44
|
+
formatOutput(result, getFormat(program.opts()));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
handleError(error);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatOutput, handleError, getFormat } from "../cli-utils.js";
|
|
2
|
+
import { connectionPath } from "../tools/connections.js";
|
|
2
3
|
export function registerConnectionCommands(program, client) {
|
|
3
4
|
const connections = program.command("connections").alias("c").description("manage platform connections");
|
|
4
5
|
connections
|
|
@@ -26,10 +27,7 @@ export function registerConnectionCommands(program, client) {
|
|
|
26
27
|
.option("--account-id <id>", "disconnect only this account (its entity_id from `connections list`) instead of the whole platform")
|
|
27
28
|
.action(async (platform, opts) => {
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
-
? `/connections/${platform}?account_id=${encodeURIComponent(opts.accountId)}`
|
|
31
|
-
: `/connections/${platform}`;
|
|
32
|
-
await client.delete(path);
|
|
30
|
+
await client.delete(connectionPath(platform, opts.accountId));
|
|
33
31
|
formatOutput({ message: `${platform} disconnected` }, getFormat(program.opts()));
|
|
34
32
|
}
|
|
35
33
|
catch (error) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatOutput, handleError, getFormat, parseList } from "../cli-utils.js";
|
|
2
|
+
import { commentPath } from "../tools/comments.js";
|
|
2
3
|
export function registerLaunchCommands(program, client) {
|
|
3
4
|
const launches = program.command("launches").alias("l").description("manage launches");
|
|
4
5
|
launches
|
|
@@ -258,6 +259,93 @@ export function registerLaunchCommands(program, client) {
|
|
|
258
259
|
handleError(error);
|
|
259
260
|
}
|
|
260
261
|
});
|
|
262
|
+
launches
|
|
263
|
+
.command("destinations <id>")
|
|
264
|
+
.description("one row per destination (names the subreddit for each reddit community)")
|
|
265
|
+
.action(async (id) => {
|
|
266
|
+
try {
|
|
267
|
+
const result = await client.get(`/launches/${id}/destinations`);
|
|
268
|
+
formatOutput(result, getFormat(program.opts()));
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
handleError(error);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
const comments = launches.command("comments").description("read and moderate comments on a launch's posts");
|
|
275
|
+
comments
|
|
276
|
+
.command("list <launch-id>")
|
|
277
|
+
.description("list comments per published post, newest first")
|
|
278
|
+
.option("--post <post-id>", "one post's comments only (the platform post id; required with --cursor)")
|
|
279
|
+
.option("--platform <platform>", "only posts on this platform")
|
|
280
|
+
.option("--cursor <cursor>", "next_cursor from a previous page (needs --post)")
|
|
281
|
+
.option("-l, --limit <n>", "comments per post, 1-100 (default 25)")
|
|
282
|
+
.action(async (launchId, opts) => {
|
|
283
|
+
try {
|
|
284
|
+
const params = {};
|
|
285
|
+
if (opts.post)
|
|
286
|
+
params.post_id = opts.post;
|
|
287
|
+
if (opts.platform)
|
|
288
|
+
params.platform = opts.platform;
|
|
289
|
+
if (opts.cursor)
|
|
290
|
+
params.cursor = opts.cursor;
|
|
291
|
+
if (opts.limit)
|
|
292
|
+
params.limit = opts.limit;
|
|
293
|
+
const result = await client.get(`/launches/${launchId}/comments`, params);
|
|
294
|
+
formatOutput(result, getFormat(program.opts()));
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
handleError(error);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
comments
|
|
301
|
+
.command("reply <launch-id> <comment-id>")
|
|
302
|
+
.description("reply to a comment as the account that published the post (youtube, instagram)")
|
|
303
|
+
.requiredOption("--post <post-id>", "the platform post id the comment lives on")
|
|
304
|
+
.requiredOption("-m, --message <text>", "the reply (1-2,200 characters)")
|
|
305
|
+
.action(async (launchId, commentId, opts) => {
|
|
306
|
+
try {
|
|
307
|
+
const result = await client.post(`/launches/${launchId}/comments/replies`, {
|
|
308
|
+
post_id: opts.post,
|
|
309
|
+
comment_id: commentId,
|
|
310
|
+
message: opts.message,
|
|
311
|
+
});
|
|
312
|
+
formatOutput(result, getFormat(program.opts()));
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
handleError(error);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
comments
|
|
319
|
+
.command("hide <launch-id> <comment-id>")
|
|
320
|
+
.description("hide a comment from everyone but its author (instagram); --unhide reverses it")
|
|
321
|
+
.requiredOption("--post <post-id>", "the platform post id the comment lives on")
|
|
322
|
+
.option("--unhide", "unhide the comment instead")
|
|
323
|
+
.action(async (launchId, commentId, opts) => {
|
|
324
|
+
try {
|
|
325
|
+
const result = await client.post(`/launches/${launchId}/comments/hide`, {
|
|
326
|
+
post_id: opts.post,
|
|
327
|
+
comment_id: commentId,
|
|
328
|
+
hidden: !opts.unhide,
|
|
329
|
+
});
|
|
330
|
+
formatOutput(result, getFormat(program.opts()));
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
handleError(error);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
comments
|
|
337
|
+
.command("delete <launch-id> <comment-id>")
|
|
338
|
+
.description("permanently delete a comment (instagram, media owner only)")
|
|
339
|
+
.requiredOption("--post <post-id>", "the platform post id the comment lives on")
|
|
340
|
+
.action(async (launchId, commentId, opts) => {
|
|
341
|
+
try {
|
|
342
|
+
await client.delete(commentPath(launchId, commentId, opts.post));
|
|
343
|
+
formatOutput({ comment_id: commentId, deleted: true }, getFormat(program.opts()));
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
handleError(error);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
261
349
|
launches
|
|
262
350
|
.command("delete-post <launch-id> <posting-log-id>")
|
|
263
351
|
.description("delete a single published post from its platform")
|
package/dist/server.js
CHANGED
|
@@ -15,6 +15,8 @@ import { registerAutopilotTools } from "./tools/autopilot.js";
|
|
|
15
15
|
import { registerRecommendationTools } from "./tools/recommendations.js";
|
|
16
16
|
import { registerRedditTools } from "./tools/reddit.js";
|
|
17
17
|
import { registerAudienceTools } from "./tools/audience.js";
|
|
18
|
+
import { registerCommentTools } from "./tools/comments.js";
|
|
19
|
+
import { registerAccountTools } from "./tools/account.js";
|
|
18
20
|
/** Drops tools whose feature is paused before the policy registrar sees them (see PAUSED_TOOLS). */
|
|
19
21
|
function skipPausedTools(registrar) {
|
|
20
22
|
return {
|
|
@@ -42,5 +44,7 @@ export function createDropspaceServer({ client, version, profile }) {
|
|
|
42
44
|
registerRecommendationTools(registrar, client);
|
|
43
45
|
registerRedditTools(registrar, client);
|
|
44
46
|
registerAudienceTools(registrar, client);
|
|
47
|
+
registerCommentTools(registrar, client);
|
|
48
|
+
registerAccountTools(registrar, client);
|
|
45
49
|
return server;
|
|
46
50
|
}
|
package/dist/tool-policy.d.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* hosted: whether the hosted endpoint (https://api.dropspace.dev/mcp) serves the tool.
|
|
7
7
|
* The stdio server serves every tool. Left off hosted: credits and boosts (payments and
|
|
8
|
-
* ad spend), API keys and webhooks (developer plumbing and credentials),
|
|
9
|
-
* engagement
|
|
8
|
+
* ad spend), API keys and webhooks (developer plumbing and credentials), reddit
|
|
9
|
+
* engagement, and the tools added after the 10/13/2026 Meta submission (media library,
|
|
10
|
+
* comments, destinations, disconnect, account) until that review settles.
|
|
10
11
|
*
|
|
11
12
|
* category: the documentation grouping. Every tool count and category list in the docs
|
|
12
13
|
* page, llms.txt, and llms-full.txt is computed from this table.
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
*/
|
|
17
18
|
import { type PolicyTable, type ToolClass, type ToolPolicy, type ToolProfile } from "./policy.js";
|
|
18
19
|
export type { ToolClass, ToolPolicy, ToolProfile };
|
|
19
|
-
export declare const TOOL_CATEGORIES: readonly ["launches", "posts", "personas", "media", "connections", "boosts", "credits", "API keys", "webhooks", "dropspace", "usage", "autopilot", "recommendations", "reddit engagement", "audience"];
|
|
20
|
+
export declare const TOOL_CATEGORIES: readonly ["launches", "posts", "comments", "personas", "media", "connections", "boosts", "credits", "API keys", "webhooks", "dropspace", "usage", "account", "autopilot", "recommendations", "reddit engagement", "audience"];
|
|
20
21
|
export type ToolCategory = (typeof TOOL_CATEGORIES)[number];
|
|
21
22
|
/**
|
|
22
23
|
* Reddit engagement is paused (dropspace's reddit_engagement_disabled switch, 10/15/2026): the API
|
|
@@ -28,7 +29,7 @@ export declare const REDDIT_ENGAGEMENT_PAUSED = true;
|
|
|
28
29
|
export declare const PAUSED_TOOLS: ReadonlySet<string>;
|
|
29
30
|
/** The tools the package serves - every docs count and category list reads this. */
|
|
30
31
|
export declare const TOOL_POLICY: PolicyTable<ToolCategory>;
|
|
31
|
-
export declare const toolPolicy: (name: string) => ToolPolicy<"launches" | "personas" | "connections" | "webhooks" | "usage" | "credits" | "media" | "boosts" | "
|
|
32
|
+
export declare const toolPolicy: (name: string) => ToolPolicy<"audience" | "launches" | "comments" | "personas" | "connections" | "webhooks" | "usage" | "credits" | "media" | "boosts" | "posts" | "API keys" | "dropspace" | "account" | "autopilot" | "recommendations" | "reddit engagement">;
|
|
32
33
|
export declare const isToolEnabled: (name: string, profile: ToolProfile) => boolean;
|
|
33
34
|
export declare const toolAnnotations: (name: string) => {
|
|
34
35
|
readOnlyHint: boolean;
|
package/dist/tool-policy.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* hosted: whether the hosted endpoint (https://api.dropspace.dev/mcp) serves the tool.
|
|
7
7
|
* The stdio server serves every tool. Left off hosted: credits and boosts (payments and
|
|
8
|
-
* ad spend), API keys and webhooks (developer plumbing and credentials),
|
|
9
|
-
* engagement
|
|
8
|
+
* ad spend), API keys and webhooks (developer plumbing and credentials), reddit
|
|
9
|
+
* engagement, and the tools added after the 10/13/2026 Meta submission (media library,
|
|
10
|
+
* comments, destinations, disconnect, account) until that review settles.
|
|
10
11
|
*
|
|
11
12
|
* category: the documentation grouping. Every tool count and category list in the docs
|
|
12
13
|
* page, llms.txt, and llms-full.txt is computed from this table.
|
|
@@ -18,6 +19,7 @@ import { isToolEnabledIn, toolAnnotationsFor, toolPolicyFor, toolsInCategoryOf,
|
|
|
18
19
|
export const TOOL_CATEGORIES = [
|
|
19
20
|
"launches",
|
|
20
21
|
"posts",
|
|
22
|
+
"comments",
|
|
21
23
|
"personas",
|
|
22
24
|
"media",
|
|
23
25
|
"connections",
|
|
@@ -27,6 +29,7 @@ export const TOOL_CATEGORIES = [
|
|
|
27
29
|
"webhooks",
|
|
28
30
|
"dropspace",
|
|
29
31
|
"usage",
|
|
32
|
+
"account",
|
|
30
33
|
"autopilot",
|
|
31
34
|
"recommendations",
|
|
32
35
|
"reddit engagement",
|
|
@@ -58,9 +61,19 @@ const FULL_TOOL_POLICY = {
|
|
|
58
61
|
retry_launch_content: hosted("launches", "write"),
|
|
59
62
|
publish_launch: hosted("launches", "sensitive"),
|
|
60
63
|
retry_launch: hosted("launches", "sensitive"),
|
|
64
|
+
// stdio only for now: the hosted set is pinned to what was submitted to Meta on 10/13/2026
|
|
65
|
+
// (__tests__/server.test.ts), the same hold as the media library tools below.
|
|
66
|
+
get_launch_destinations: localOnly("launches", "read"),
|
|
61
67
|
// posts
|
|
62
68
|
delete_post: hosted("posts", "sensitive"),
|
|
63
69
|
delete_all_posts: hosted("posts", "sensitive"),
|
|
70
|
+
// comments - stdio only for now, the same Meta-review hold as the media library tools below.
|
|
71
|
+
list_launch_comments: localOnly("comments", "read"),
|
|
72
|
+
// reply and hide act on the platform as the account that published the post
|
|
73
|
+
reply_to_comment: localOnly("comments", "sensitive"),
|
|
74
|
+
hide_comment: localOnly("comments", "sensitive"),
|
|
75
|
+
// deletes on the platform with no restore
|
|
76
|
+
delete_comment: localOnly("comments", "sensitive"),
|
|
64
77
|
// personas
|
|
65
78
|
list_personas: hosted("personas", "read"),
|
|
66
79
|
get_persona: hosted("personas", "read"),
|
|
@@ -75,7 +88,7 @@ const FULL_TOOL_POLICY = {
|
|
|
75
88
|
generate_media: hosted("media", "write"),
|
|
76
89
|
get_media_job: hosted("media", "read"),
|
|
77
90
|
// stdio only for now: the hosted set is pinned to what was submitted to Meta on 10/13/2026
|
|
78
|
-
// (__tests__/
|
|
91
|
+
// (__tests__/server.test.ts). Move these to hosted() once that review settles.
|
|
79
92
|
list_media_library: localOnly("media", "read"),
|
|
80
93
|
add_to_media_library: localOnly("media", "write"),
|
|
81
94
|
update_media_library_item: localOnly("media", "write"),
|
|
@@ -83,6 +96,9 @@ const FULL_TOOL_POLICY = {
|
|
|
83
96
|
delete_media_library_item: localOnly("media", "write"),
|
|
84
97
|
// connections
|
|
85
98
|
list_connections: hosted("connections", "read"),
|
|
99
|
+
// deletes the stored token (a credential) with no restore short of reconnecting.
|
|
100
|
+
// stdio only for now, the same Meta-review hold as the media library tools above.
|
|
101
|
+
disconnect_connection: localOnly("connections", "sensitive"),
|
|
86
102
|
// edits the profile ON the platform (youtube channel description)
|
|
87
103
|
update_connection_profile: hosted("connections", "sensitive"),
|
|
88
104
|
// autopilot
|
|
@@ -105,6 +121,11 @@ const FULL_TOOL_POLICY = {
|
|
|
105
121
|
get_usage: hosted("usage", "read"),
|
|
106
122
|
get_changelog: hosted("usage", "read"),
|
|
107
123
|
get_dropspace_status: hosted("dropspace", "read"),
|
|
124
|
+
// account - stdio only for now, the same Meta-review hold as the media library tools above.
|
|
125
|
+
get_profile: localOnly("account", "read"),
|
|
126
|
+
update_profile: localOnly("account", "write"),
|
|
127
|
+
// returns a pre-authenticated stripe billing portal url (credential-like)
|
|
128
|
+
create_billing_portal_session: localOnly("account", "sensitive"),
|
|
108
129
|
// reddit engagement
|
|
109
130
|
suggest_subreddits: localOnly("reddit engagement", "read"),
|
|
110
131
|
get_engagement_config: localOnly("reddit engagement", "read"),
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const text = (value) => ({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
|
|
3
|
+
const fail = (error) => ({
|
|
4
|
+
content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
5
|
+
isError: true,
|
|
6
|
+
});
|
|
7
|
+
export function registerAccountTools(server, client) {
|
|
8
|
+
server.tool("get_profile", "get the authenticated user's profile: email, full_name, avatar_url, role, and whether onboarding is complete.", {}, async () => {
|
|
9
|
+
try {
|
|
10
|
+
return text(await client.get("/profile"));
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return fail(error);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
server.tool("update_profile", "update the authenticated user's full_name and/or avatar_url (pass at least one). requires the write scope.", {
|
|
17
|
+
full_name: z.string().min(1).max(120).optional().describe("display name (1-120 characters)"),
|
|
18
|
+
avatar_url: z.string().url().optional().describe("public URL of the avatar image"),
|
|
19
|
+
}, async ({ full_name, avatar_url }) => {
|
|
20
|
+
try {
|
|
21
|
+
const body = {};
|
|
22
|
+
if (full_name !== undefined)
|
|
23
|
+
body.full_name = full_name;
|
|
24
|
+
if (avatar_url !== undefined)
|
|
25
|
+
body.avatar_url = avatar_url;
|
|
26
|
+
return text(await client.patch("/profile", body));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
return fail(error);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
server.tool("create_billing_portal_session", "get a URL where the user manages their subscription. a subscriber gets a pre-authenticated stripe customer portal session, so treat the url like a credential: open it, never share it. a user with no subscription gets the web billing page, where they sign in to upgrade.", {}, async () => {
|
|
33
|
+
try {
|
|
34
|
+
return text(await client.post("/billing/portal"));
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return fail(error);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { DropspaceClient } from "../client.js";
|
|
3
|
+
/** DELETE path for one comment on one of a launch's posts (shared by the MCP tool and the CLI). */
|
|
4
|
+
export declare function commentPath(launchId: string, commentId: string, postId: string): string;
|
|
5
|
+
export declare function registerCommentTools(server: McpServer, client: DropspaceClient): void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const text = (value) => ({ content: [{ type: "text", text: JSON.stringify(value, null, 2) }] });
|
|
3
|
+
const fail = (error) => ({
|
|
4
|
+
content: [{ type: "text", text: `error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
5
|
+
isError: true,
|
|
6
|
+
});
|
|
7
|
+
/** DELETE path for one comment on one of a launch's posts (shared by the MCP tool and the CLI). */
|
|
8
|
+
export function commentPath(launchId, commentId, postId) {
|
|
9
|
+
return `/launches/${launchId}/comments/${encodeURIComponent(commentId)}?post_id=${encodeURIComponent(postId)}`;
|
|
10
|
+
}
|
|
11
|
+
export function registerCommentTools(server, client) {
|
|
12
|
+
server.tool("list_launch_comments", "list comments on a launch's published posts, newest first, grouped per post. each post carries a status: ok (comments returned), unsupported (the platform's api does not expose comment content), comments_disabled, post_deleted, or unavailable (transient - retry later); detail explains every non-ok status. to page through one post, pass post_id plus the next_cursor from the previous response. comment ids feed reply_to_comment, hide_comment, and delete_comment.", {
|
|
13
|
+
launch_id: z.string().uuid().describe("launch ID"),
|
|
14
|
+
post_id: z
|
|
15
|
+
.string()
|
|
16
|
+
.min(1)
|
|
17
|
+
.max(200)
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("read one post's comments only (the platform post id; required with cursor)"),
|
|
20
|
+
platform: z.string().min(1).max(50).optional().describe("only posts on this platform"),
|
|
21
|
+
cursor: z.string().min(1).max(500).optional().describe("next_cursor from a previous page (needs post_id)"),
|
|
22
|
+
limit: z.number().int().min(1).max(100).optional().describe("comments per post, 1-100 (default 25)"),
|
|
23
|
+
}, async ({ launch_id, post_id, platform, cursor, limit }) => {
|
|
24
|
+
try {
|
|
25
|
+
const params = {};
|
|
26
|
+
if (post_id)
|
|
27
|
+
params.post_id = post_id;
|
|
28
|
+
if (platform)
|
|
29
|
+
params.platform = platform;
|
|
30
|
+
if (cursor)
|
|
31
|
+
params.cursor = cursor;
|
|
32
|
+
if (limit !== undefined)
|
|
33
|
+
params.limit = String(limit);
|
|
34
|
+
return text(await client.get(`/launches/${launch_id}/comments`, params));
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return fail(error);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
server.tool("reply_to_comment", "reply to a comment on one of a launch's published posts, as the account that published it. youtube and instagram only; other platforms, and instagram connections made before 08/25/2026 (reconnect instagram once to fix), return an error with the reason. requires the send scope.", {
|
|
41
|
+
launch_id: z.string().uuid().describe("launch ID"),
|
|
42
|
+
post_id: z.string().min(1).max(200).describe("the platform post id (from list_launch_comments)"),
|
|
43
|
+
comment_id: z.string().min(1).max(200).describe("the comment to reply to (from list_launch_comments)"),
|
|
44
|
+
message: z.string().min(1).max(2200).describe("the reply text (1-2,200 characters)"),
|
|
45
|
+
}, async ({ launch_id, post_id, comment_id, message }) => {
|
|
46
|
+
try {
|
|
47
|
+
return text(await client.post(`/launches/${launch_id}/comments/replies`, { post_id, comment_id, message }));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return fail(error);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
server.tool("hide_comment", "hide or unhide a comment on one of a launch's published posts, as the account that published it. a hidden comment stays visible to its author and disappears for everyone else; hidden: false reverses it. instagram only (not stories); other platforms return an error with the reason. requires the send scope.", {
|
|
54
|
+
launch_id: z.string().uuid().describe("launch ID"),
|
|
55
|
+
post_id: z.string().min(1).max(200).describe("the platform post id (from list_launch_comments)"),
|
|
56
|
+
comment_id: z.string().min(1).max(200).describe("the comment to hide or unhide"),
|
|
57
|
+
hidden: z.boolean().describe("true hides the comment, false unhides it"),
|
|
58
|
+
}, async ({ launch_id, post_id, comment_id, hidden }) => {
|
|
59
|
+
try {
|
|
60
|
+
return text(await client.post(`/launches/${launch_id}/comments/hide`, { post_id, comment_id, hidden }));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return fail(error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
server.tool("delete_comment", "permanently delete a comment on one of a launch's published posts. instagram only, and instagram lets only the account that published the post delete; other platforms, stories, and instagram connections made before 08/25/2026 return an error with the reason. this cannot be undone. requires the send scope.", {
|
|
67
|
+
launch_id: z.string().uuid().describe("launch ID"),
|
|
68
|
+
post_id: z.string().min(1).max(200).describe("the platform post id the comment lives on"),
|
|
69
|
+
comment_id: z.string().min(1).max(200).describe("the comment to delete"),
|
|
70
|
+
}, async ({ launch_id, post_id, comment_id }) => {
|
|
71
|
+
try {
|
|
72
|
+
await client.delete(commentPath(launch_id, comment_id, post_id));
|
|
73
|
+
return text({ comment_id, deleted: true });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return fail(error);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { DropspaceClient } from "../client.js";
|
|
3
|
+
/** DELETE path for a platform, or for one account on it (shared by the MCP tool and the CLI). */
|
|
4
|
+
export declare function connectionPath(platform: string, accountId?: string): string;
|
|
3
5
|
export declare function registerConnectionTools(server: McpServer, client: DropspaceClient): void;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
/** DELETE path for a platform, or for one account on it (shared by the MCP tool and the CLI). */
|
|
3
|
+
export function connectionPath(platform, accountId) {
|
|
4
|
+
const base = `/connections/${encodeURIComponent(platform)}`;
|
|
5
|
+
return accountId ? `${base}?account_id=${encodeURIComponent(accountId)}` : base;
|
|
6
|
+
}
|
|
2
7
|
export function registerConnectionTools(server, client) {
|
|
3
|
-
server.tool("list_connections", 'list your connected social media accounts. each row has a "platform" and an "entity_id" (null for your primary account on that platform, set for additional accounts). to target a specific account in create_launch when you have more than one on instagram / tiktok / youtube / twitter / reddit, pass "{platform}:{entity_id}" as a user_platform_accounts key (the bare "{platform}" key targets your primary). the same entity_id is what
|
|
8
|
+
server.tool("list_connections", 'list your connected social media accounts. each row has a "platform" and an "entity_id" (null for your primary account on that platform, set for additional accounts). to target a specific account in create_launch when you have more than one on instagram / tiktok / youtube / twitter / reddit, pass "{platform}:{entity_id}" as a user_platform_accounts key (the bare "{platform}" key targets your primary). the same entity_id is what disconnect_connection and retry_launch take as account_id', {
|
|
4
9
|
page: z.number().int().positive().optional().describe("page number (default: 1)"),
|
|
5
10
|
page_size: z.number().int().min(1).max(100).optional().describe("items per page (default: 50, max: 100)"),
|
|
6
11
|
}, async ({ page, page_size }) => {
|
|
@@ -27,6 +32,36 @@ export function registerConnectionTools(server, client) {
|
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
34
|
});
|
|
35
|
+
server.tool("disconnect_connection", "disconnect a connected platform: every account on it, or only the one whose entity_id you pass as account_id (from list_connections). the stored token is deleted, so launches cannot post through that account until it is reconnected in the web app. requires the write scope.", {
|
|
36
|
+
platform: z.string().describe('platform to disconnect (e.g. "instagram")'),
|
|
37
|
+
account_id: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("disconnect only this account (its entity_id from list_connections) instead of the whole platform"),
|
|
41
|
+
}, async ({ platform, account_id }) => {
|
|
42
|
+
try {
|
|
43
|
+
await client.delete(connectionPath(platform, account_id));
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: JSON.stringify({ platform, account_id: account_id ?? null, disconnected: true }, null, 2),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `error: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
30
65
|
server.tool("update_connection_profile", "edit the connected account's profile ON the platform. youtube only today: sets the channel description (max 1,000 characters; an empty string clears it) via channels.update. instagram's api can read a profile but not write it, facebook page \"about\" needs a permission dropspace does not hold, and tiktok / x / linkedin expose no profile write - those return an error with the reason. requires the write scope.", {
|
|
31
66
|
platform: z.string().describe('platform whose profile to edit (currently only "youtube")'),
|
|
32
67
|
description: z.string().max(1000).describe("the new profile / channel description (max 1,000 characters)"),
|
package/dist/tools/launches.js
CHANGED
|
@@ -466,6 +466,27 @@ export function registerLaunchTools(server, client) {
|
|
|
466
466
|
};
|
|
467
467
|
}
|
|
468
468
|
});
|
|
469
|
+
server.tool("get_launch_destinations", "list one row per destination of a launch: platform, account_type, and subreddit for each reddit community, with its status, post_url, and error. get_launch_status reports every reddit subreddit as the same platform; this names each one, so you can see which community failed and retry just that one with retry_launch + subreddit.", {
|
|
470
|
+
id: z.string().uuid().describe("launch ID"),
|
|
471
|
+
}, async ({ id }) => {
|
|
472
|
+
try {
|
|
473
|
+
const result = await client.get(`/launches/${id}/destinations`);
|
|
474
|
+
return {
|
|
475
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
return {
|
|
480
|
+
content: [
|
|
481
|
+
{
|
|
482
|
+
type: "text",
|
|
483
|
+
text: `error: ${error instanceof Error ? error.message : String(error)}`,
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
isError: true,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
});
|
|
469
490
|
server.tool("retry_launch_content", "retry AI content generation for failed platforms (requires generate scope)", {
|
|
470
491
|
id: z.string().uuid().describe("launch ID"),
|
|
471
492
|
platforms: z
|
package/dist/tools/media.js
CHANGED
|
@@ -105,7 +105,7 @@ export function registerMediaTools(server, client) {
|
|
|
105
105
|
return fail(error);
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
|
-
server.tool("add_to_media_library", "add photos or videos to the user's media library by public URL (up to 20 at once; a URL already in the library is skipped). Give each a description of what it shows - autopilot chooses media by it;
|
|
108
|
+
server.tool("add_to_media_library", "add photos or videos to the user's media library by public URL (up to 20 at once; a URL already in the library is skipped). Give each a description of what it shows - autopilot chooses media by it; items without one are captioned automatically on autopilot's next run: images from the image, videos from their thumbnail_url. give a video a description or a thumbnail_url, or autopilot cannot choose it.", {
|
|
109
109
|
items: z
|
|
110
110
|
.array(z.object({
|
|
111
111
|
url: z.string().url().describe("public http(s) URL of the image or video"),
|
package/dist/types.d.ts
CHANGED
|
@@ -153,6 +153,12 @@ export interface Persona {
|
|
|
153
153
|
instagram_samples?: unknown[];
|
|
154
154
|
tiktok_samples?: unknown[];
|
|
155
155
|
linkedin_samples?: unknown[];
|
|
156
|
+
brand_kit?: {
|
|
157
|
+
text_color?: string;
|
|
158
|
+
outline_color?: string;
|
|
159
|
+
logo_url?: string;
|
|
160
|
+
logo_position?: string;
|
|
161
|
+
};
|
|
156
162
|
created_at: string;
|
|
157
163
|
updated_at: string;
|
|
158
164
|
}
|
|
@@ -335,8 +341,13 @@ export interface Boost {
|
|
|
335
341
|
impressions: number | null;
|
|
336
342
|
clicks: number | null;
|
|
337
343
|
reach: number | null;
|
|
344
|
+
link_clicks?: number | null;
|
|
338
345
|
total_spend_cents: number | null;
|
|
339
346
|
failure_reason: string | null;
|
|
347
|
+
stop_rules?: {
|
|
348
|
+
max_cpc_cents?: number;
|
|
349
|
+
};
|
|
350
|
+
stop_reason?: string | null;
|
|
340
351
|
created_at: string;
|
|
341
352
|
updated_at: string;
|
|
342
353
|
activated_at: string | null;
|
|
@@ -357,10 +368,15 @@ export interface AutopilotConfig {
|
|
|
357
368
|
run_hour: number;
|
|
358
369
|
timezone: string;
|
|
359
370
|
product_url: string | null;
|
|
371
|
+
product_image_url?: string | null;
|
|
360
372
|
product_brief: string | null;
|
|
361
373
|
content_pillars: string[] | null;
|
|
374
|
+
directives?: string[] | null;
|
|
362
375
|
min_queue: number;
|
|
363
376
|
max_posts_per_day: number;
|
|
377
|
+
track_links?: boolean;
|
|
378
|
+
reuse_media?: boolean;
|
|
379
|
+
media_source?: "ai" | "library" | "mixed";
|
|
364
380
|
paused: boolean;
|
|
365
381
|
pause_reason: string | null;
|
|
366
382
|
created_at: string;
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ Dropspace turns one brief into per-platform posts and publishes them on the user
|
|
|
10
10
|
## Setup
|
|
11
11
|
|
|
12
12
|
- MCP over stdio: `npx -y -p @jclvsh/dropspace dropspace-mcp` with `DROPSPACE_API_KEY=ds_live_...` in the environment. Every tool is available.
|
|
13
|
-
- Hosted MCP: `https://api.dropspace.dev/mcp` (streamable HTTP, POST, `Authorization: Bearer ds_live_...`). Serves a subset: no credits, boosts, API keys, webhooks, or
|
|
13
|
+
- Hosted MCP: `https://api.dropspace.dev/mcp` (streamable HTTP, POST, `Authorization: Bearer ds_live_...`). Serves a subset: no credits, boosts, API keys, webhooks, reddit, media library, comment, destination, disconnect, profile, or billing tools.
|
|
14
14
|
- CLI: `npx -y @jclvsh/dropspace <command>` (the same key). `dropspace --help` lists commands.
|
|
15
15
|
- No key? The user creates one at https://www.dropspace.dev/settings/api. A key's scopes limit what it can do (`read`, `write`, `generate`, `send`, `admin`); a 403 names the missing scope.
|
|
16
16
|
|
|
@@ -24,20 +24,22 @@ Dropspace turns one brief into per-platform posts and publishes them on the user
|
|
|
24
24
|
|
|
25
25
|
1. `create_launch` with `product_url` and/or `product_description` and `platforms`. Dropspace reads the product page and writes copy per platform.
|
|
26
26
|
- Use `platform_contents` or `custom_content` when the user already wrote the copy.
|
|
27
|
+
- Reddit: `platform_contents.reddit.metadata.subreddits` (up to 10, no `r/`) posts once per subreddit; `metadata.first_comment` (up to 1,500 characters) is posted as a comment under each reddit post once it is live. `suggest_subreddits` finds communities that fit the product.
|
|
27
28
|
- `track_links: true` posts the product URL as a tracked redirect on X, LinkedIn, Facebook, Reddit, and YouTube; clicks then appear in analytics. Tell the user their posts will show a dropspace.dev link.
|
|
28
29
|
- `save_to_library: true` adds the launch's uploaded photos and videos to the user's media library, where autopilot can post them.
|
|
29
30
|
- `scheduled_date` (ISO 8601, at least 15 minutes out) schedules it; without one, the launch waits for `publish_launch`.
|
|
30
31
|
2. Review: `get_launch` shows the generated copy per platform. Show it to the user before publishing anything they have not seen. Edit with `update_launch`; regenerate one platform with `retry_launch_content`.
|
|
31
32
|
3. Media (optional): `generate_media` with `type: "image"` and `use_product_image: true` makes an image of the real product from its page; `get_media_job` polls it. Videos are 8-second clips (`type: "video"` or `"script_video"`).
|
|
32
|
-
4. `publish_launch`, then `get_launch_status` until it settles. Failed platforms: `retry_launch`.
|
|
33
|
+
4. `publish_launch`, then `get_launch_status` until it settles. Failed platforms: `retry_launch`. A launch posted to several subreddits reports them all as `reddit` there; `get_launch_destinations` names each subreddit, and `retry_launch` with `subreddit` retries just that one.
|
|
33
34
|
5. Results: `get_launch_analytics` (or `get_batch_launch_analytics` for many). `metrics.tracked_clicks` is clicks on the tracked link.
|
|
35
|
+
6. Comments (stdio server and CLI only): `list_launch_comments` reads comments per post, newest first. `reply_to_comment` (youtube, instagram), `hide_comment` and `delete_comment` (instagram) act on the platform as the user's account and need the `send` scope; show the user the reply or the comment before you send, hide, or delete. A deleted comment cannot be restored.
|
|
34
36
|
|
|
35
37
|
## Ongoing
|
|
36
38
|
|
|
37
39
|
- Recommendations: `list_recommendations` after posts have run a day or two; `apply_recommendation` turns one into a draft launch, an autopilot post, or a paused boost. Nothing publishes or spends on apply.
|
|
38
40
|
- Autopilot (pro and premium plans): `get_autopilot`, `configure_autopilot`, `run_autopilot_now`. In review mode it drafts posts for approval: `list_autopilot_review`, `approve_autopilot_draft`, `reject_autopilot_draft`.
|
|
39
41
|
- `media_source`: `ai` (generated slides and video), `library` (only the user's own media), or `mixed` (library first). `product_image_url` makes AI slides show the real product.
|
|
40
|
-
- Media library (stdio server and CLI only): `list_media_library`, `add_to_media_library`, `update_media_library_item`, `delete_media_library_item`. Autopilot chooses items by their description, so describe each one
|
|
42
|
+
- Media library (stdio server and CLI only): `list_media_library`, `add_to_media_library`, `update_media_library_item`, `delete_media_library_item`. Autopilot chooses items by their description, so describe each one. Items without one are captioned on autopilot's next run (images from the image, videos from their `thumbnail_url`), so a video needs a description or a thumbnail.
|
|
41
43
|
- Brand kit: `update_persona` with `brand_kit` (text and outline colors, a corner logo) styles the image slides autopilot posts as that persona.
|
|
42
44
|
- Audience: `get_audience` for follower counts on Instagram, TikTok, and YouTube.
|
|
43
45
|
- Webhooks: `create_webhook` with a Slack incoming webhook URL (`https://hooks.slack.com/services/...`) posts events to that channel as readable messages.
|
|
@@ -46,7 +48,7 @@ Dropspace turns one brief into per-platform posts and publishes them on the user
|
|
|
46
48
|
|
|
47
49
|
These cost the user money. Get an explicit yes for the amount before calling them:
|
|
48
50
|
|
|
49
|
-
- `activate_boost` starts real ad spend on the user's Meta ad account. `create_boost` makes it paused; activation is the spend. Offer `max_cpc_cents` so the boost pauses itself if clicks get expensive; `objective: "traffic"` needs a Facebook link post.
|
|
51
|
+
- `activate_boost` starts real ad spend on the user's Meta ad account. `create_boost` makes it paused; activation is the spend. Offer `max_cpc_cents` so the boost pauses itself if clicks get expensive; `objective: "traffic"` needs a Facebook link post. A boost reports `link_clicks` (clicks through to the link, for traffic boosts), and `stop_reason` says why a stop rule paused it (null while it runs).
|
|
50
52
|
- `purchase_credit_pack`.
|
|
51
53
|
|
|
52
54
|
## Errors that need the user
|