@jclvsh/dropspace 2.0.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 (40) hide show
  1. package/README.md +273 -0
  2. package/dist/cli-utils.d.ts +4 -0
  3. package/dist/cli-utils.js +31 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +31 -0
  6. package/dist/client.d.ts +11 -0
  7. package/dist/client.js +110 -0
  8. package/dist/commands/connections.d.ts +3 -0
  9. package/dist/commands/connections.js +23 -0
  10. package/dist/commands/dropspace.d.ts +3 -0
  11. package/dist/commands/dropspace.js +15 -0
  12. package/dist/commands/keys.d.ts +3 -0
  13. package/dist/commands/keys.js +70 -0
  14. package/dist/commands/launches.d.ts +3 -0
  15. package/dist/commands/launches.js +263 -0
  16. package/dist/commands/personas.d.ts +3 -0
  17. package/dist/commands/personas.js +116 -0
  18. package/dist/commands/usage.d.ts +3 -0
  19. package/dist/commands/usage.js +15 -0
  20. package/dist/commands/webhooks.d.ts +3 -0
  21. package/dist/commands/webhooks.js +108 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +25 -0
  24. package/dist/tools/connections.d.ts +3 -0
  25. package/dist/tools/connections.js +30 -0
  26. package/dist/tools/dropspace.d.ts +3 -0
  27. package/dist/tools/dropspace.js +21 -0
  28. package/dist/tools/keys.d.ts +3 -0
  29. package/dist/tools/keys.js +107 -0
  30. package/dist/tools/launches.d.ts +3 -0
  31. package/dist/tools/launches.js +492 -0
  32. package/dist/tools/personas.d.ts +3 -0
  33. package/dist/tools/personas.js +152 -0
  34. package/dist/tools/usage.d.ts +3 -0
  35. package/dist/tools/usage.js +21 -0
  36. package/dist/tools/webhooks.d.ts +3 -0
  37. package/dist/tools/webhooks.js +183 -0
  38. package/dist/types.d.ts +257 -0
  39. package/dist/types.js +1 -0
  40. package/package.json +33 -0
@@ -0,0 +1,107 @@
1
+ import { z } from "zod";
2
+ const SCOPES = ["read", "write", "delete", "publish", "generate", "admin"];
3
+ export function registerKeyTools(server, client) {
4
+ server.tool("get_current_api_key", "get the current API key's info — name, scopes, and prefix (no scope required)", {}, async () => {
5
+ try {
6
+ const result = await client.get("/keys/me");
7
+ return {
8
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
9
+ };
10
+ }
11
+ catch (error) {
12
+ return {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
17
+ },
18
+ ],
19
+ isError: true,
20
+ };
21
+ }
22
+ });
23
+ server.tool("list_api_keys", "list your API keys (requires admin scope)", {}, async () => {
24
+ try {
25
+ const result = await client.get("/keys");
26
+ return {
27
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
28
+ };
29
+ }
30
+ catch (error) {
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
36
+ },
37
+ ],
38
+ isError: true,
39
+ };
40
+ }
41
+ });
42
+ server.tool("create_api_key", "create a new API key — the raw key is returned once, store it securely (requires admin scope)", {
43
+ name: z.string().min(1).max(100).describe("key name (1-100 characters)"),
44
+ scopes: z.array(z.enum(SCOPES)).optional().describe("permission scopes (defaults to all scopes)"),
45
+ }, async (args) => {
46
+ try {
47
+ const result = await client.post("/keys", args);
48
+ return {
49
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
50
+ };
51
+ }
52
+ catch (error) {
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
58
+ },
59
+ ],
60
+ isError: true,
61
+ };
62
+ }
63
+ });
64
+ server.tool("rename_api_key", "rename an API key (requires admin scope)", {
65
+ id: z.string().uuid().describe("API key ID"),
66
+ name: z.string().min(1).max(100).describe("new key name (1-100 characters)"),
67
+ }, async ({ id, name }) => {
68
+ try {
69
+ const result = await client.patch(`/keys/${id}`, { name });
70
+ return {
71
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
72
+ };
73
+ }
74
+ catch (error) {
75
+ return {
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
80
+ },
81
+ ],
82
+ isError: true,
83
+ };
84
+ }
85
+ });
86
+ server.tool("revoke_api_key", "permanently revoke an API key (requires admin scope)", {
87
+ id: z.string().uuid().describe("API key ID"),
88
+ }, async ({ id }) => {
89
+ try {
90
+ await client.delete(`/keys/${id}`);
91
+ return {
92
+ content: [{ type: "text", text: "API key revoked successfully" }],
93
+ };
94
+ }
95
+ catch (error) {
96
+ return {
97
+ content: [
98
+ {
99
+ type: "text",
100
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
101
+ },
102
+ ],
103
+ isError: true,
104
+ };
105
+ }
106
+ });
107
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { DropspaceClient } from "../client.js";
3
+ export declare function registerLaunchTools(server: McpServer, client: DropspaceClient): void;
@@ -0,0 +1,492 @@
1
+ import { z } from "zod";
2
+ // 11 enum values but only 10 distinct platforms — linkedin-community is a LinkedIn variant, not a separate platform.
3
+ // auto-post (7): facebook, linkedin, twitter, reddit, instagram, tiktok, youtube
4
+ // guided (3): substack, producthunt, hackernews
5
+ // .max(10) on platform arrays reflects the 10 real platforms.
6
+ const PLATFORMS = [
7
+ "facebook",
8
+ "linkedin",
9
+ "linkedin-community",
10
+ "twitter",
11
+ "reddit",
12
+ "instagram",
13
+ "tiktok",
14
+ "youtube",
15
+ "substack",
16
+ "producthunt",
17
+ "hackernews",
18
+ ];
19
+ const AUTO_POST_PLATFORMS = ["facebook", "linkedin", "twitter", "reddit", "instagram", "tiktok", "youtube"];
20
+ const VIDEO_GENERATION_PLATFORMS = ["instagram", "tiktok"];
21
+ const inlineMediaItemSchema = z.discriminatedUnion("source", [
22
+ z.object({
23
+ source: z.literal("url"),
24
+ url: z.string().url().describe("media URL"),
25
+ filename: z.string().max(255).optional().describe("filename"),
26
+ }),
27
+ z.object({
28
+ source: z.literal("base64"),
29
+ data: z.string().min(1).describe("base64-encoded data"),
30
+ filename: z.string().min(1).max(255).describe("filename (required for base64)"),
31
+ mime_type: z
32
+ .enum(["image/jpeg", "image/png", "image/webp", "image/gif", "video/mp4", "video/quicktime"])
33
+ .describe("MIME type (required for base64)"),
34
+ }),
35
+ ]);
36
+ const mediaAssetSchema = z.object({
37
+ id: z.string().min(1).describe("asset ID"),
38
+ url: z.string().url().describe("asset URL"),
39
+ type: z.enum(["image", "video"]).describe("asset type"),
40
+ filename: z.string().min(1).describe("filename"),
41
+ size: z.number().nonnegative().describe("file size in bytes"),
42
+ mime_type: z.string().min(1).describe("MIME type"),
43
+ width: z.number().positive().nullable().optional().describe("width in pixels"),
44
+ height: z.number().positive().nullable().optional().describe("height in pixels"),
45
+ thumbnail_url: z.string().url().nullable().optional().describe("thumbnail URL"),
46
+ generation_id: z.string().optional().describe("AI generation ID"),
47
+ });
48
+ export function registerLaunchTools(server, client) {
49
+ server.tool("list_launches", "list your launches with pagination", {
50
+ page: z.number().int().positive().optional().describe("page number (default: 1)"),
51
+ page_size: z.number().int().min(1).max(100).optional().describe("items per page (default: 50, max: 100)"),
52
+ }, async ({ page, page_size }) => {
53
+ try {
54
+ const params = {};
55
+ if (page !== undefined)
56
+ params.page = String(page);
57
+ if (page_size !== undefined)
58
+ params.page_size = String(page_size);
59
+ const result = await client.get("/launches", params);
60
+ return {
61
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
62
+ };
63
+ }
64
+ catch (error) {
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
70
+ },
71
+ ],
72
+ isError: true,
73
+ };
74
+ }
75
+ });
76
+ server.tool("create_launch", "create a new launch for publishing to social platforms", {
77
+ title: z.string().describe("launch title"),
78
+ product_description: z
79
+ .string()
80
+ .optional()
81
+ .describe("product description - required unless custom_content or platform_contents is provided"),
82
+ platforms: z
83
+ .array(z.enum(PLATFORMS))
84
+ .min(1)
85
+ .max(10)
86
+ .optional()
87
+ .describe("target platforms - required unless dropspace_platforms or user_platform_accounts is provided (inferred from their union)"),
88
+ product_url: z.string().url().optional().describe("product URL"),
89
+ scheduled_date: z
90
+ .string()
91
+ .optional()
92
+ .describe("ISO 8601 datetime to schedule (must be at least 15 minutes in the future)"),
93
+ persona_id: z.string().uuid().optional().describe("persona ID for tone/style"),
94
+ dropspace_platforms: z
95
+ .array(z.enum(PLATFORMS))
96
+ .optional()
97
+ .describe("platforms to post via official dropspace accounts"),
98
+ media_mode: z.enum(["images", "video"]).optional().describe("media mode for the launch"),
99
+ user_platform_accounts: z
100
+ .record(z.string(), z.string())
101
+ .optional()
102
+ .describe('map of platform key to token_id (UUID). most platforms use simple keys: "twitter", "reddit", "instagram", "tiktok", "youtube". LinkedIn uses "linkedin:personal" or "linkedin:organization:<org_id>". Facebook uses "facebook:page:<page_id>". multiple keys allowed'),
103
+ platform_contents: z
104
+ .record(z.string(), z.record(z.string(), z.unknown()))
105
+ .optional()
106
+ .describe('per-platform content — each value needs "content" (string). Reddit also requires "title" (max 300). Twitter supports "thread" (string[], max 6, each ≤280 or ≤25,000 for X Premium) instead of "content". TikTok supports "tiktok_settings": { "privacy_level": "PUBLIC_TO_EVERYONE"|"FOLLOWER_OF_CREATOR"|"MUTUAL_FOLLOW_FRIENDS"|"SELF_ONLY" (required before publish), "allow_comments"?, "allow_duet"?, "allow_stitch"?, "is_commercial"?, "is_your_brand"?, "is_branded_content"?, "auto_add_music"? (booleans) }. YouTube requires video and supports "title" (max 100 chars). character limits: Twitter 280/tweet (25,000 for X Premium), LinkedIn 3000, Instagram 2200, Reddit 3000, Facebook 3000, TikTok 4000, YouTube 5000 (description). X Premium status is auto-detected from the connected account. mutually exclusive with custom_content'),
107
+ custom_content: z
108
+ .union([z.string().min(1), z.array(z.string()).min(1).max(6)])
109
+ .optional()
110
+ .describe("single content string for all platforms, or array of tweet strings for twitter thread mode (each ≤280 chars or ≤25,000 for X Premium, mutually exclusive with platform_contents)"),
111
+ custom_content_reddit_title: z
112
+ .string()
113
+ .min(1)
114
+ .max(300)
115
+ .optional()
116
+ .describe("reddit title (required when custom_content is used with reddit platform)"),
117
+ media: z
118
+ .array(inlineMediaItemSchema)
119
+ .min(1)
120
+ .max(10)
121
+ .optional()
122
+ .describe("inline media (URL or base64, 1-10 items, mutually exclusive with media_assets)"),
123
+ media_assets: z
124
+ .array(mediaAssetSchema)
125
+ .optional()
126
+ .describe("pre-uploaded media asset references (mutually exclusive with media)"),
127
+ media_attach_platforms: z
128
+ .array(z.enum(AUTO_POST_PLATFORMS))
129
+ .optional()
130
+ .describe("platforms to attach media to"),
131
+ // Disabled: AI video generation is currently disabled in production
132
+ // generate_ai_videos: z
133
+ // .array(z.enum(VIDEO_GENERATION_PLATFORMS))
134
+ // .optional()
135
+ // .describe("platforms to auto-generate AI videos for"),
136
+ publish: z
137
+ .boolean()
138
+ .optional()
139
+ .describe("immediately publish after creation (returns 202). mutually exclusive with scheduled_date"),
140
+ wait: z
141
+ .boolean()
142
+ .optional()
143
+ .describe("wait for publishing to complete and return post URLs inline (returns 200). requires publish: true"),
144
+ }, async (args) => {
145
+ try {
146
+ const result = await client.post("/launches", args);
147
+ return {
148
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
149
+ };
150
+ }
151
+ catch (error) {
152
+ return {
153
+ content: [
154
+ {
155
+ type: "text",
156
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
157
+ },
158
+ ],
159
+ isError: true,
160
+ };
161
+ }
162
+ });
163
+ server.tool("get_launch", "get a launch by ID with posting status per platform", {
164
+ id: z.string().uuid().describe("launch ID"),
165
+ }, async ({ id }) => {
166
+ try {
167
+ const result = await client.get(`/launches/${id}`);
168
+ return {
169
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
170
+ };
171
+ }
172
+ catch (error) {
173
+ return {
174
+ content: [
175
+ {
176
+ type: "text",
177
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
178
+ },
179
+ ],
180
+ isError: true,
181
+ };
182
+ }
183
+ });
184
+ server.tool("update_launch", "update a launch (cannot update running/completed/partial launches)", {
185
+ id: z.string().uuid().describe("launch ID"),
186
+ scheduled_date: z.string().nullable().optional().describe("ISO 8601 datetime or null to clear"),
187
+ status: z
188
+ .enum(["draft", "manual", "trigger", "scheduled", "cancelled"])
189
+ .optional()
190
+ .describe("launch status"),
191
+ platforms: z
192
+ .array(z.enum(PLATFORMS))
193
+ .min(1)
194
+ .max(10)
195
+ .optional()
196
+ .describe("target platforms for this launch"),
197
+ name: z.string().max(200).optional().describe("launch name/title"),
198
+ product_description: z.string().max(10000).optional().describe("product description"),
199
+ product_url: z.string().url().or(z.literal("")).optional().describe("product URL (empty string to clear)"),
200
+ persona_id: z
201
+ .string()
202
+ .uuid()
203
+ .nullable()
204
+ .optional()
205
+ .describe("persona ID for content generation, null to clear"),
206
+ platform_contents: z
207
+ .record(z.string(), z.record(z.string(), z.unknown()))
208
+ .optional()
209
+ .describe('per-platform content update (deep-merged with existing). each value can include "content" (string). Twitter supports "thread" (string[]) instead. Reddit "title" is optional on update. TikTok "tiktok_settings" is deep-merged — you can update individual fields (e.g. just "privacy_level") without overwriting the rest. see create_launch for full field details'),
210
+ dropspace_platforms: z.array(z.enum(PLATFORMS)).optional().describe("official dropspace account platforms"),
211
+ user_platform_accounts: z
212
+ .record(z.string(), z.string())
213
+ .optional()
214
+ .describe('map of platform key to token_id (UUID). most platforms use simple keys: "twitter", "reddit", "instagram", "tiktok", "youtube". LinkedIn uses "linkedin:personal" or "linkedin:organization:<org_id>". Facebook uses "facebook:page:<page_id>". multiple keys allowed'),
215
+ media: z
216
+ .array(inlineMediaItemSchema)
217
+ .min(1)
218
+ .max(10)
219
+ .optional()
220
+ .describe("inline media (URL or base64, 1-10 items, mutually exclusive with media_assets)"),
221
+ media_assets: z
222
+ .array(mediaAssetSchema)
223
+ .optional()
224
+ .describe("pre-uploaded media asset references (mutually exclusive with media)"),
225
+ media_attach_platforms: z
226
+ .array(z.enum(AUTO_POST_PLATFORMS))
227
+ .optional()
228
+ .describe("platforms to attach media to"),
229
+ media_mode: z.enum(["images", "video"]).optional().describe("media mode"),
230
+ }, async ({ id, ...body }) => {
231
+ try {
232
+ const result = await client.patch(`/launches/${id}`, body);
233
+ return {
234
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
235
+ };
236
+ }
237
+ catch (error) {
238
+ return {
239
+ content: [
240
+ {
241
+ type: "text",
242
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
243
+ },
244
+ ],
245
+ isError: true,
246
+ };
247
+ }
248
+ });
249
+ server.tool("delete_launch", "delete a launch (cannot delete running launches)", {
250
+ id: z.string().uuid().describe("launch ID"),
251
+ }, async ({ id }) => {
252
+ try {
253
+ await client.delete(`/launches/${id}`);
254
+ return {
255
+ content: [{ type: "text", text: "launch deleted successfully" }],
256
+ };
257
+ }
258
+ catch (error) {
259
+ return {
260
+ content: [
261
+ {
262
+ type: "text",
263
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
264
+ },
265
+ ],
266
+ isError: true,
267
+ };
268
+ }
269
+ });
270
+ server.tool("publish_launch", "publish a launch to all configured platforms (queued async)", {
271
+ id: z.string().uuid().describe("launch ID"),
272
+ }, async ({ id }) => {
273
+ try {
274
+ const result = await client.post(`/launches/${id}/publish`);
275
+ return {
276
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
277
+ };
278
+ }
279
+ catch (error) {
280
+ return {
281
+ content: [
282
+ {
283
+ type: "text",
284
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
285
+ },
286
+ ],
287
+ isError: true,
288
+ };
289
+ }
290
+ });
291
+ server.tool("retry_launch", "retry failed platforms for a launch", {
292
+ id: z.string().uuid().describe("launch ID"),
293
+ }, async ({ id }) => {
294
+ try {
295
+ const result = await client.post(`/launches/${id}/retry`);
296
+ return {
297
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
298
+ };
299
+ }
300
+ catch (error) {
301
+ return {
302
+ content: [
303
+ {
304
+ type: "text",
305
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
306
+ },
307
+ ],
308
+ isError: true,
309
+ };
310
+ }
311
+ });
312
+ server.tool("generate_content", "generate AI content for a launch's platforms using Claude", {
313
+ id: z.string().uuid().describe("launch ID"),
314
+ platforms: z
315
+ .array(z.enum(PLATFORMS))
316
+ .min(1)
317
+ .max(10)
318
+ .optional()
319
+ .describe("specific platforms to generate for (defaults to all launch platforms)"),
320
+ // Disabled: AI video generation is currently disabled in production
321
+ // generate_video_scripts: z
322
+ // .array(z.enum(VIDEO_GENERATION_PLATFORMS))
323
+ // .optional()
324
+ // .describe("platforms to generate video scripts for"),
325
+ }, async ({ id, platforms }) => {
326
+ try {
327
+ const body = {};
328
+ if (platforms)
329
+ body.platforms = platforms;
330
+ // Disabled: AI video generation is currently disabled in production
331
+ // if (generate_video_scripts) body.generate_video_scripts = generate_video_scripts;
332
+ const result = await client.post(`/launches/${id}/generate-content`, body);
333
+ return {
334
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
335
+ };
336
+ }
337
+ catch (error) {
338
+ return {
339
+ content: [
340
+ {
341
+ type: "text",
342
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
343
+ },
344
+ ],
345
+ isError: true,
346
+ };
347
+ }
348
+ });
349
+ server.tool("get_launch_analytics", "get publishing analytics with per-post engagement metrics (likes, views, shares, etc.)", {
350
+ id: z.string().uuid().describe("launch ID"),
351
+ }, async ({ id }) => {
352
+ try {
353
+ const result = await client.get(`/launches/${id}/analytics`);
354
+ return {
355
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
356
+ };
357
+ }
358
+ catch (error) {
359
+ return {
360
+ content: [
361
+ {
362
+ type: "text",
363
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
364
+ },
365
+ ],
366
+ isError: true,
367
+ };
368
+ }
369
+ });
370
+ server.tool("get_batch_launch_analytics", "get cached analytics for multiple launches in one request (no live refresh, max 100 IDs)", {
371
+ ids: z.array(z.string().uuid()).min(1).max(100).describe("launch IDs to fetch analytics for"),
372
+ }, async ({ ids }) => {
373
+ try {
374
+ const result = await client.get("/launches/analytics", {
375
+ ids: ids.join(","),
376
+ });
377
+ return {
378
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
379
+ };
380
+ }
381
+ catch (error) {
382
+ return {
383
+ content: [
384
+ {
385
+ type: "text",
386
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
387
+ },
388
+ ],
389
+ isError: true,
390
+ };
391
+ }
392
+ });
393
+ server.tool("get_launch_status", "get detailed posting status with per-platform logs", {
394
+ id: z.string().uuid().describe("launch ID"),
395
+ }, async ({ id }) => {
396
+ try {
397
+ const result = await client.get(`/launches/${id}/status`);
398
+ return {
399
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
400
+ };
401
+ }
402
+ catch (error) {
403
+ return {
404
+ content: [
405
+ {
406
+ type: "text",
407
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
408
+ },
409
+ ],
410
+ isError: true,
411
+ };
412
+ }
413
+ });
414
+ server.tool("retry_launch_content", "retry AI content generation for failed platforms (requires generate scope)", {
415
+ id: z.string().uuid().describe("launch ID"),
416
+ platforms: z
417
+ .array(z.enum(PLATFORMS))
418
+ .min(1)
419
+ .optional()
420
+ .describe("specific platforms to retry (defaults to all failed platforms)"),
421
+ }, async ({ id, platforms }) => {
422
+ try {
423
+ const body = {};
424
+ if (platforms)
425
+ body.platforms = platforms;
426
+ const result = await client.post(`/launches/${id}/retry-content`, body);
427
+ return {
428
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
429
+ };
430
+ }
431
+ catch (error) {
432
+ return {
433
+ content: [
434
+ {
435
+ type: "text",
436
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
437
+ },
438
+ ],
439
+ isError: true,
440
+ };
441
+ }
442
+ });
443
+ // ============================================================================
444
+ // Delete Post
445
+ // ============================================================================
446
+ server.tool("delete_post", "Delete a single published post from its platform. Only works for Twitter, Facebook, LinkedIn, Reddit, and YouTube. Instagram and TikTok do not support API deletion.", {
447
+ launch_id: z.string().uuid().describe("launch ID"),
448
+ posting_log_id: z.string().uuid().describe("posting log ID (from launch status)"),
449
+ }, async ({ launch_id, posting_log_id }) => {
450
+ try {
451
+ const result = await client.delete(`/launches/${launch_id}/posts/${posting_log_id}`);
452
+ return {
453
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
454
+ };
455
+ }
456
+ catch (error) {
457
+ return {
458
+ content: [
459
+ {
460
+ type: "text",
461
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
462
+ },
463
+ ],
464
+ isError: true,
465
+ };
466
+ }
467
+ });
468
+ // ============================================================================
469
+ // Delete All Posts
470
+ // ============================================================================
471
+ server.tool("delete_all_posts", "Delete all published posts for a launch from their platforms. Only deletes from Twitter, Facebook, LinkedIn, Reddit, and YouTube. Instagram and TikTok posts are skipped (require manual deletion).", {
472
+ launch_id: z.string().uuid().describe("launch ID"),
473
+ }, async ({ launch_id }) => {
474
+ try {
475
+ const result = await client.delete(`/launches/${launch_id}/posts`);
476
+ return {
477
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
478
+ };
479
+ }
480
+ catch (error) {
481
+ return {
482
+ content: [
483
+ {
484
+ type: "text",
485
+ text: `error: ${error instanceof Error ? error.message : String(error)}`,
486
+ },
487
+ ],
488
+ isError: true,
489
+ };
490
+ }
491
+ });
492
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { DropspaceClient } from "../client.js";
3
+ export declare function registerPersonaTools(server: McpServer, client: DropspaceClient): void;