posterly-mcp-server 0.19.1 → 0.19.2

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 (111) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +50 -49
  3. package/dist/lib/api-client.js +1 -1
  4. package/dist/lib/format.d.ts +21 -0
  5. package/dist/lib/format.js +125 -0
  6. package/dist/tools/audit-google-business-profile.js +15 -12
  7. package/dist/tools/create-oauth-client.js +7 -1
  8. package/dist/tools/create-post.js +20 -27
  9. package/dist/tools/create-posts-batch.js +23 -15
  10. package/dist/tools/create-signed-upload.js +11 -8
  11. package/dist/tools/create-webhook.js +7 -2
  12. package/dist/tools/delete-google-business-review-reply.d.ts +1 -1
  13. package/dist/tools/delete-google-business-review-reply.js +4 -1
  14. package/dist/tools/delete-oauth-client.js +4 -1
  15. package/dist/tools/delete-post-group.js +5 -1
  16. package/dist/tools/delete-post.js +4 -1
  17. package/dist/tools/delete-webhook.js +4 -1
  18. package/dist/tools/disconnect-account.js +7 -5
  19. package/dist/tools/find-slot.js +10 -3
  20. package/dist/tools/generate-image.js +17 -19
  21. package/dist/tools/generate-video.js +11 -9
  22. package/dist/tools/get-account-analytics.js +64 -45
  23. package/dist/tools/get-brand-profile.js +34 -40
  24. package/dist/tools/get-brand.js +14 -16
  25. package/dist/tools/get-connect-link.js +26 -7
  26. package/dist/tools/get-google-business-review-link.js +6 -6
  27. package/dist/tools/get-platform-schema.js +23 -16
  28. package/dist/tools/get-post-analytics.js +53 -50
  29. package/dist/tools/get-post-missing.js +5 -1
  30. package/dist/tools/get-post.js +16 -14
  31. package/dist/tools/get-video-job.d.ts +2 -2
  32. package/dist/tools/get-video-job.js +28 -11
  33. package/dist/tools/get-video-options.js +21 -15
  34. package/dist/tools/get-x-posting-quota.js +12 -9
  35. package/dist/tools/list-accounts.js +12 -4
  36. package/dist/tools/list-activity.js +13 -18
  37. package/dist/tools/list-brand-accounts.js +11 -3
  38. package/dist/tools/list-brands.js +12 -11
  39. package/dist/tools/list-google-business-reviews.js +13 -11
  40. package/dist/tools/list-oauth-clients.js +10 -4
  41. package/dist/tools/list-platforms.js +12 -5
  42. package/dist/tools/list-posts.js +12 -7
  43. package/dist/tools/list-webhooks.js +13 -11
  44. package/dist/tools/reply-google-business-review.d.ts +1 -1
  45. package/dist/tools/reply-google-business-review.js +4 -1
  46. package/dist/tools/run-video-function.js +4 -4
  47. package/dist/tools/suggest-google-business-review-reply.js +5 -4
  48. package/dist/tools/test-webhook.js +7 -1
  49. package/dist/tools/trigger-platform-helper.js +5 -1
  50. package/dist/tools/update-oauth-client.js +8 -1
  51. package/dist/tools/update-post-release-id.js +5 -1
  52. package/dist/tools/update-post-status.d.ts +2 -2
  53. package/dist/tools/update-post-status.js +7 -1
  54. package/dist/tools/update-post.js +10 -4
  55. package/dist/tools/update-webhook.js +7 -1
  56. package/dist/tools/upload-media-from-url.js +7 -9
  57. package/dist/tools/upload-media.js +6 -2
  58. package/dist/tools/whoami.js +18 -17
  59. package/package.json +1 -1
  60. package/src/index.ts +50 -49
  61. package/src/lib/api-client.ts +1 -1
  62. package/src/lib/format.ts +132 -0
  63. package/src/tools/audit-google-business-profile.ts +18 -12
  64. package/src/tools/create-oauth-client.ts +8 -2
  65. package/src/tools/create-post.ts +20 -28
  66. package/src/tools/create-posts-batch.ts +23 -18
  67. package/src/tools/create-signed-upload.ts +11 -8
  68. package/src/tools/create-webhook.ts +7 -3
  69. package/src/tools/delete-google-business-review-reply.ts +4 -1
  70. package/src/tools/delete-oauth-client.ts +4 -1
  71. package/src/tools/delete-post-group.ts +5 -1
  72. package/src/tools/delete-post.ts +4 -1
  73. package/src/tools/delete-webhook.ts +4 -2
  74. package/src/tools/disconnect-account.ts +7 -5
  75. package/src/tools/find-slot.ts +13 -5
  76. package/src/tools/generate-image.ts +20 -20
  77. package/src/tools/generate-video.ts +11 -9
  78. package/src/tools/get-account-analytics.ts +70 -49
  79. package/src/tools/get-brand-profile.ts +34 -38
  80. package/src/tools/get-brand.ts +14 -14
  81. package/src/tools/get-connect-link.ts +29 -7
  82. package/src/tools/get-google-business-review-link.ts +6 -6
  83. package/src/tools/get-platform-schema.ts +29 -16
  84. package/src/tools/get-post-analytics.ts +58 -51
  85. package/src/tools/get-post-missing.ts +5 -2
  86. package/src/tools/get-post.ts +16 -13
  87. package/src/tools/get-video-job.ts +31 -11
  88. package/src/tools/get-video-options.ts +27 -15
  89. package/src/tools/get-x-posting-quota.ts +12 -9
  90. package/src/tools/list-accounts.ts +15 -6
  91. package/src/tools/list-activity.ts +16 -20
  92. package/src/tools/list-brand-accounts.ts +14 -6
  93. package/src/tools/list-brands.ts +16 -13
  94. package/src/tools/list-google-business-reviews.ts +16 -12
  95. package/src/tools/list-oauth-clients.ts +13 -4
  96. package/src/tools/list-platforms.ts +15 -5
  97. package/src/tools/list-posts.ts +15 -9
  98. package/src/tools/list-webhooks.ts +16 -12
  99. package/src/tools/reply-google-business-review.ts +4 -1
  100. package/src/tools/run-video-function.ts +4 -4
  101. package/src/tools/suggest-google-business-review-reply.ts +5 -4
  102. package/src/tools/test-webhook.ts +7 -1
  103. package/src/tools/trigger-platform-helper.ts +5 -1
  104. package/src/tools/update-oauth-client.ts +9 -2
  105. package/src/tools/update-post-release-id.ts +5 -2
  106. package/src/tools/update-post-status.ts +7 -1
  107. package/src/tools/update-post.ts +10 -5
  108. package/src/tools/update-webhook.ts +7 -2
  109. package/src/tools/upload-media-from-url.ts +7 -9
  110. package/src/tools/upload-media.ts +6 -2
  111. package/src/tools/whoami.ts +21 -23
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { code, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
2
3
  export const listBrandsTool = {
3
4
  name: 'list_brands',
4
5
  description: 'List brands/clients the caller can access. Returns each brand ID, name, workspace ID, source, and how many social accounts are currently assigned to it.',
@@ -12,18 +13,18 @@ export const listBrandsTool = {
12
13
  const brands = await client.listBrands({ workspace_id: input.workspace_id });
13
14
  if (brands.length === 0) {
14
15
  return input.workspace_id
15
- ? 'No brands found in this workspace.'
16
- : 'No brands found. Create brands in the posterly dashboard first.';
16
+ ? mdEmpty('brands', 'This workspace does not have any brands or clients yet.')
17
+ : mdEmpty('brands', 'No brands or clients were found for this API key.', 'Create a brand in the Posterly dashboard, then run this again.');
17
18
  }
18
- const lines = brands.map((brand) => {
19
- const suffix = [
20
- `ID: ${brand.id}`,
21
- brand.workspace_id ? `ws: ${brand.workspace_id}` : null,
22
- `accounts: ${brand.account_count}`,
19
+ return [
20
+ mdTitle(`Brands (${brands.length})`),
21
+ mdTable(['Brand', 'Brand ID', 'Accounts', 'Source', 'Workspace'], brands.map((brand) => [
22
+ brand.name,
23
+ code(brand.id),
24
+ brand.account_count,
23
25
  brand.source,
24
- ].filter(Boolean).join(', ');
25
- return `• ${brand.name} (${suffix})`;
26
- });
27
- return `Brands (${brands.length}):\n${lines.join('\n')}`;
26
+ brand.workspace_id ? code(brand.workspace_id) : 'default',
27
+ ])),
28
+ ].join('\n\n');
28
29
  },
29
30
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { compactText, formatDateTime, mdEmpty, mdTable, mdTitle, statusLabel } from '../lib/format.js';
2
3
  export const listGoogleBusinessReviewsTool = {
3
4
  name: 'list_google_business_reviews',
4
5
  description: 'List Google Business Profile reviews for one location/account or every accessible GBP location. Supports rating and unanswered-only filters.',
@@ -12,17 +13,18 @@ export const listGoogleBusinessReviewsTool = {
12
13
  async execute(client, input) {
13
14
  const result = await client.listGoogleBusinessReviews(input);
14
15
  if (result.reviews.length === 0) {
15
- return 'No Google Business reviews found matching your filters.';
16
+ return mdEmpty('Google Business reviews', 'No reviews matched those filters.');
16
17
  }
17
- const lines = [`Google Business reviews (${result.reviews.length} of ${result.total}):`];
18
- for (const review of result.reviews.slice(0, 25)) {
19
- const name = review.reviewer?.displayName || 'Anonymous';
20
- const rating = review.starRating ? `${review.starRating} stars` : 'unrated';
21
- const location = review.businessName || review.locationName || review.locationId || 'location';
22
- const text = review.comment ? ` - ${review.comment.replace(/\s+/g, ' ').slice(0, 180)}` : '';
23
- const replied = review.reviewReply ? ' replied' : ' unanswered';
24
- lines.push(`- ${name} (${rating}, ${location},${replied})${text}`);
25
- }
26
- return lines.join('\n');
18
+ return [
19
+ mdTitle(`Google Business reviews (${result.reviews.length} of ${result.total})`),
20
+ mdTable(['Reviewer', 'Rating', 'Location', 'Reply', 'Updated', 'Comment'], result.reviews.slice(0, 25).map((review) => [
21
+ review.reviewer?.displayName || 'Anonymous',
22
+ review.starRating ? `${review.starRating} stars` : 'unrated',
23
+ review.businessName || review.locationName || review.locationId || 'location',
24
+ statusLabel(review.reviewReply ? 'replied' : 'unanswered'),
25
+ formatDateTime(review.updateTime || review.createTime),
26
+ compactText(review.comment, 140) || '(rating only)',
27
+ ])),
28
+ ].join('\n\n');
27
29
  },
28
30
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { code, mdEmpty, mdTable, mdTitle, statusLabel } from '../lib/format.js';
2
3
  export const listOAuthClientsTool = {
3
4
  name: 'list_oauth_clients',
4
5
  description: 'List self-serve OAuth developer clients owned by the user. These are public PKCE clients for third-party app integrations.',
@@ -6,10 +7,15 @@ export const listOAuthClientsTool = {
6
7
  async execute(client) {
7
8
  const result = await client.listOAuthClients();
8
9
  if (result.clients.length === 0)
9
- return 'No OAuth developer clients found.';
10
+ return mdEmpty('OAuth developer clients', 'No OAuth developer clients were found.');
10
11
  return [
11
- `OAuth developer clients (${result.clients.length}):`,
12
- ...result.clients.map((item) => `- ${item.client_name} (${item.client_id}) scopes=${item.default_scopes.join(', ')} active=${item.is_active}`),
13
- ].join('\n');
12
+ mdTitle(`OAuth developer clients (${result.clients.length})`),
13
+ mdTable(['Client', 'Client ID', 'Scopes', 'State'], result.clients.map((item) => [
14
+ item.client_name,
15
+ code(item.client_id),
16
+ item.default_scopes.join(', '),
17
+ statusLabel(item.is_active ? 'active' : 'inactive'),
18
+ ])),
19
+ ].join('\n\n');
14
20
  },
15
21
  };
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { PLANNED_PLATFORM_IDS } from '../generated/platform-manifest.js';
3
+ import { code, mdTable, mdTitle, statusLabel } from '../lib/format.js';
3
4
  export const listPlatformsTool = {
4
5
  name: 'list_platforms',
5
6
  description: 'List posterly platform integrations, posting capabilities, content/media limits, settings schemas, helper tools, and analytics support. Use include_planned to show future integration possibilities from the Postiz parity audit.',
@@ -11,10 +12,16 @@ export const listPlatformsTool = {
11
12
  }),
12
13
  async execute(client, input) {
13
14
  const result = await client.listPlatforms(input);
14
- const lines = result.platforms.map((platform) => {
15
- const helperCount = platform.helper_tools?.length || 0;
16
- return `• ${platform.label} (${platform.id}) ${platform.status}; post types: ${platform.post_types.join(', ') || 'n/a'}; helpers: ${helperCount}; analytics: ${platform.analytics_supported ? 'yes' : 'no'}`;
17
- });
18
- return `Posterly platforms (${result.platforms.length}):\n${lines.join('\n')}`;
15
+ return [
16
+ mdTitle(`Posterly platforms (${result.platforms.length})`),
17
+ mdTable(['Platform', 'ID', 'Status', 'Post types', 'Helpers', 'Analytics'], result.platforms.map((platform) => [
18
+ platform.label,
19
+ code(platform.id),
20
+ statusLabel(platform.status),
21
+ platform.post_types.join(', ') || 'n/a',
22
+ platform.helper_tools?.length || 0,
23
+ platform.analytics_supported,
24
+ ])),
25
+ ].join('\n\n');
19
26
  },
20
27
  };
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { SUPPORTED_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
3
+ import { code, compactText, formatDateTime, mdEmpty, mdTable, mdTitle, statusLabel } from '../lib/format.js';
3
4
  export const listPostsTool = {
4
5
  name: 'list_posts',
5
6
  description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, account_id, or workspace_id. If workspace_id is omitted, posts across every workspace the caller is a member of are returned.',
@@ -33,13 +34,17 @@ export const listPostsTool = {
33
34
  limit: input.limit || 20,
34
35
  });
35
36
  if (posts.length === 0) {
36
- return 'No posts found matching your criteria.';
37
+ return mdEmpty('posts', 'No posts matched those filters.');
37
38
  }
38
- const lines = posts.map((p) => {
39
- const date = new Date(p.scheduled_at).toLocaleString();
40
- const caption = p.content.length > 60 ? p.content.slice(0, 60) + '…' : p.content;
41
- return `• [${p.status}] #${p.id} — ${caption} (${date})`;
42
- });
43
- return `Posts (${posts.length} of ${total}):\n${lines.join('\n')}`;
39
+ return [
40
+ mdTitle(`Posts (${posts.length} of ${total})`),
41
+ mdTable(['Status', 'Post ID', 'Caption', 'Scheduled', 'Type'], posts.map((post) => [
42
+ statusLabel(post.status),
43
+ code(post.id),
44
+ compactText(post.content, 80) || '(empty)',
45
+ formatDateTime(post.scheduled_at),
46
+ post.post_type || 'post',
47
+ ])),
48
+ ].join('\n\n');
44
49
  },
45
50
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { code, compactText, formatDateTime, mdEmpty, mdTable, mdTitle, statusLabel } from '../lib/format.js';
2
3
  export const listWebhooksTool = {
3
4
  name: 'list_webhooks',
4
5
  description: 'List API webhook subscriptions, their event filters, active state, and most recent delivery status.',
@@ -8,17 +9,18 @@ export const listWebhooksTool = {
8
9
  async execute(client, input) {
9
10
  const { webhooks } = await client.listWebhooks(input);
10
11
  if (webhooks.length === 0) {
11
- return 'No webhooks found.';
12
+ return mdEmpty('webhooks', 'No webhook subscriptions were found.');
12
13
  }
13
- const lines = [`Webhooks (${webhooks.length}):`];
14
- for (const webhook of webhooks) {
15
- const active = webhook.is_active ? 'active' : 'inactive';
16
- const events = webhook.events?.length ? webhook.events.join(', ') : 'default events';
17
- const last = webhook.last_delivery_at
18
- ? ` last ${webhook.last_delivery_status || 'n/a'} at ${new Date(webhook.last_delivery_at).toLocaleString()}`
19
- : '';
20
- lines.push(`- ${webhook.id} [${active}] ${webhook.url} (${events})${last}`);
21
- }
22
- return lines.join('\n');
14
+ return [
15
+ mdTitle(`Webhooks (${webhooks.length})`),
16
+ mdTable(['Webhook ID', 'State', 'URL', 'Events', 'Last delivery', 'Last error'], webhooks.map((webhook) => [
17
+ code(webhook.id),
18
+ statusLabel(webhook.is_active ? 'active' : 'inactive'),
19
+ webhook.url,
20
+ webhook.events?.length ? webhook.events.join(', ') : 'default events',
21
+ webhook.last_delivery_at ? `${webhook.last_delivery_status || 'n/a'} at ${formatDateTime(webhook.last_delivery_at)}` : 'n/a',
22
+ compactText(webhook.last_error, 80) || 'n/a',
23
+ ])),
24
+ ].join('\n\n');
23
25
  },
24
26
  };
@@ -32,5 +32,5 @@ export declare const replyGoogleBusinessReviewTool: {
32
32
  review_name: string;
33
33
  comment: string;
34
34
  confirm: true;
35
- }): Promise<any>;
35
+ }): Promise<string>;
36
36
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { mdSuccess } from '../lib/format.js';
2
3
  export const replyGoogleBusinessReviewTool = {
3
4
  name: 'reply_google_business_review',
4
5
  description: 'Post or update the owner reply on a Google Business Profile review. WRITE: show the review, location, and exact reply text to the user, then pass confirm=true only after explicit confirmation.',
@@ -13,6 +14,8 @@ export const replyGoogleBusinessReviewTool = {
13
14
  async execute(client, input) {
14
15
  const { confirm: _confirm, ...payload } = input;
15
16
  const result = await client.replyGoogleBusinessReview(payload);
16
- return result.message || 'Google Business review reply posted successfully.';
17
+ return mdSuccess('Google Business review reply posted', [
18
+ ['Message', result.message || 'Google Business review reply posted successfully.'],
19
+ ]);
17
20
  },
18
21
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { code, mdJson, mdTitle } from '../lib/format.js';
2
3
  export const runVideoFunctionTool = {
3
4
  name: 'run_video_function',
4
5
  description: 'Run a read-only Veo helper function before generation: list_input_modes, estimate_cost, or validate_request. This does not generate video or spend credits.',
@@ -17,9 +18,8 @@ export const runVideoFunctionTool = {
17
18
  async execute(client, input) {
18
19
  const result = await client.runVideoFunction(input);
19
20
  return [
20
- `Video function ${input.functionName} completed`,
21
- '',
22
- JSON.stringify(result, null, 2),
23
- ].join('\n');
21
+ mdTitle(`Video function ${code(input.functionName)} completed`),
22
+ mdJson('Result', result),
23
+ ].join('\n\n');
24
24
  },
25
25
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { mdEmpty, mdQuote, mdSection, mdTitle } from '../lib/format.js';
2
3
  export const suggestGoogleBusinessReviewReplyTool = {
3
4
  name: 'suggest_google_business_review_reply',
4
5
  description: 'Generate short, brand-aware AI reply suggestions for a Google review. Uses the AI Caption Assist allowance; does not post the reply.',
@@ -17,11 +18,11 @@ export const suggestGoogleBusinessReviewReplyTool = {
17
18
  const result = await client.suggestGoogleBusinessReviewReply(input);
18
19
  const suggestions = Array.isArray(result.suggestions) ? result.suggestions : [];
19
20
  if (suggestions.length === 0) {
20
- return 'No reply suggestions were generated.';
21
+ return mdEmpty('reply suggestions', 'No Google Business reply suggestions were generated.');
21
22
  }
22
23
  return [
23
- `Suggested replies (${result.tone || 'professional'}, ${result.rating || input.star_rating} stars):`,
24
- ...suggestions.map((text, index) => `${index + 1}. ${text}`),
25
- ].join('\n');
24
+ mdTitle(`Suggested replies`, `${result.tone || 'professional'} tone · ${result.rating || input.star_rating} stars`),
25
+ ...suggestions.map((text, index) => mdSection(`Option ${index + 1}`, mdQuote(text))),
26
+ ].join('\n\n');
26
27
  },
27
28
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { mdJson, mdSuccess } from '../lib/format.js';
2
3
  export const testWebhookTool = {
3
4
  name: 'test_webhook',
4
5
  description: 'Send a signed webhook.test delivery to a webhook URL. OUTBOUND SIDE EFFECT: get explicit confirmation before sending a test request.',
@@ -9,6 +10,11 @@ export const testWebhookTool = {
9
10
  async execute(client, input) {
10
11
  const result = await client.testWebhook(input.webhook_id);
11
12
  const delivery = result.delivery || {};
12
- return `Webhook test sent.\nDelivery: ${JSON.stringify(delivery, null, 2)}`;
13
+ return [
14
+ mdSuccess('Webhook test sent', [
15
+ ['Webhook ID', input.webhook_id],
16
+ ]),
17
+ mdJson('Delivery', delivery),
18
+ ].join('\n\n');
13
19
  },
14
20
  };
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { PLATFORM_HELPER_NAMES, SUPPORTED_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
3
+ import { code, mdJson, mdTitle } from '../lib/format.js';
3
4
  export const triggerPlatformHelperTool = {
4
5
  name: 'trigger_platform_helper',
5
6
  description: `Run account-specific platform helper discovery. Available helpers: ${PLATFORM_HELPER_NAMES.join(', ')}.`,
@@ -17,6 +18,9 @@ export const triggerPlatformHelperTool = {
17
18
  }),
18
19
  async execute(client, input) {
19
20
  const result = await client.triggerPlatformHelper(input);
20
- return JSON.stringify(result, null, 2);
21
+ return [
22
+ mdTitle(`Platform helper ${code(input.helper)} completed`),
23
+ mdJson('Result', result),
24
+ ].join('\n\n');
21
25
  },
22
26
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { code, mdSuccess, statusLabel } from '../lib/format.js';
2
3
  const scopeSchema = z.enum(['accounts:read', 'accounts:write', 'posts:read', 'posts:write', 'media:write', 'analytics:read']);
3
4
  export const updateOAuthClientTool = {
4
5
  name: 'update_oauth_client',
@@ -16,6 +17,12 @@ export const updateOAuthClientTool = {
16
17
  async execute(client, input) {
17
18
  const { client_id, confirm: _confirm, ...payload } = input;
18
19
  const result = await client.updateOAuthClient(client_id, payload);
19
- return `OAuth client updated: ${result.client.client_name} (${result.client.client_id}) active=${result.client.is_active}`;
20
+ return mdSuccess('OAuth client updated', [
21
+ ['Client', result.client.client_name],
22
+ ['Client ID', code(result.client.client_id)],
23
+ ['State', statusLabel(result.client.is_active ? 'active' : 'inactive')],
24
+ ['Redirect URIs', result.client.allowed_redirect_uris.join(', ')],
25
+ ['Default scopes', result.client.default_scopes.join(', ')],
26
+ ]);
20
27
  },
21
28
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { code, mdJson, mdTitle } from '../lib/format.js';
2
3
  export const updatePostReleaseIdTool = {
3
4
  name: 'update_post_release_id',
4
5
  description: 'Set or repair an external release_id and optional group_id on a post. WRITE: call get_post first, show the target post and release/group IDs, then pass confirm=true only after explicit confirmation.',
@@ -13,6 +14,9 @@ export const updatePostReleaseIdTool = {
13
14
  release_id: input.release_id,
14
15
  group_id: input.group_id,
15
16
  });
16
- return JSON.stringify(result, null, 2);
17
+ return [
18
+ mdTitle(`✅ Release ID updated for post ${code(input.post_id)}`),
19
+ mdJson('Result', result),
20
+ ].join('\n\n');
17
21
  },
18
22
  };
@@ -9,12 +9,12 @@ export declare const updatePostStatusTool: {
9
9
  scheduled_at: z.ZodOptional<z.ZodString>;
10
10
  confirm: z.ZodBoolean;
11
11
  }, "strip", z.ZodTypeAny, {
12
- status: "scheduled" | "paused" | "draft";
12
+ status: "scheduled" | "draft" | "paused";
13
13
  post_id: number;
14
14
  confirm: boolean;
15
15
  scheduled_at?: string | undefined;
16
16
  }, {
17
- status: "scheduled" | "paused" | "draft";
17
+ status: "scheduled" | "draft" | "paused";
18
18
  post_id: number;
19
19
  confirm: boolean;
20
20
  scheduled_at?: string | undefined;
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { code, mdSuccess, statusLabel } from '../lib/format.js';
2
3
  export const updatePostStatusTool = {
3
4
  name: 'update_post_status',
4
5
  description: 'Pause, resume/schedule, or move a post back to draft. DESTRUCTIVE WRITE: changes whether content will publish.\n\n' +
@@ -18,6 +19,11 @@ export const updatePostStatusTool = {
18
19
  scheduled_at: input.scheduled_at,
19
20
  });
20
21
  const transition = result.transition || {};
21
- return `Post #${input.post_id} status changed from ${transition.old_status || 'unknown'} to ${transition.new_status || input.status}.`;
22
+ return mdSuccess('Post status updated', [
23
+ ['Post ID', code(input.post_id)],
24
+ ['From', statusLabel(transition.old_status || 'unknown')],
25
+ ['To', statusLabel(transition.new_status || input.status)],
26
+ ['Scheduled', input.scheduled_at || undefined],
27
+ ]);
22
28
  },
23
29
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { code, formatDateTime, mdKeyValue, mdQuote, mdSection, mdTitle, statusLabel } from '../lib/format.js';
2
3
  const instagramSettingsSchema = z.object({
3
4
  __type: z.enum(['instagram', 'instagram-standalone']).optional(),
4
5
  post_type: z.enum(['post', 'feed', 'story', 'reel', 'carousel']).optional(),
@@ -89,9 +90,14 @@ export const updatePostTool = {
89
90
  };
90
91
  const result = await client.updatePost(post_id, payload);
91
92
  const p = result.post;
92
- const when = p.scheduled_at
93
- ? new Date(p.scheduled_at).toLocaleString()
94
- : 'N/A';
95
- return `Post #${p.id} updated successfully!\n• Status: ${p.status}\n• Caption: ${p.content || '(empty)'}\n• Scheduled: ${when}`;
93
+ return [
94
+ mdTitle(`✅ Post ${code(p.id)} updated`),
95
+ mdKeyValue([
96
+ ['Status', statusLabel(p.status)],
97
+ ['Scheduled', formatDateTime(p.scheduled_at)],
98
+ ['Type', p.post_type || 'unknown'],
99
+ ]),
100
+ mdSection('Caption', p.content ? mdQuote(p.content) : '_(empty)_'),
101
+ ].join('\n\n');
96
102
  },
97
103
  };
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { webhookEventSchema } from './webhook-events.js';
3
+ import { code, mdSuccess, statusLabel } from '../lib/format.js';
3
4
  export const updateWebhookTool = {
4
5
  name: 'update_webhook',
5
6
  description: 'Update a webhook URL, events, workspace, description, or active state. WRITE WITH OUTBOUND SIDE EFFECTS: list the current webhook first and get explicit confirmation before calling.',
@@ -16,6 +17,11 @@ export const updateWebhookTool = {
16
17
  const { webhook_id, confirm: _confirm, ...payload } = input;
17
18
  const result = await client.updateWebhook(webhook_id, payload);
18
19
  const active = result.webhook.is_active ? 'active' : 'inactive';
19
- return `Webhook updated: ${result.webhook.id}\nURL: ${result.webhook.url}\nState: ${active}\nEvents: ${result.webhook.events.join(', ')}`;
20
+ return mdSuccess('Webhook updated', [
21
+ ['Webhook ID', code(result.webhook.id)],
22
+ ['URL', result.webhook.url],
23
+ ['State', statusLabel(active)],
24
+ ['Events', result.webhook.events.join(', ')],
25
+ ]);
20
26
  },
21
27
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { formatBytes, mdSuccess } from '../lib/format.js';
2
3
  export const uploadMediaFromUrlTool = {
3
4
  name: 'upload_media_from_url',
4
5
  description: 'Fetch an image or video from a public HTTPS URL into posterly storage. Returns a URL that can be used with create_post.',
@@ -13,14 +14,11 @@ export const uploadMediaFromUrlTool = {
13
14
  filename: input.filename,
14
15
  contentType: input.content_type,
15
16
  });
16
- return [
17
- 'Remote media uploaded successfully.',
18
- `• URL: ${result.url}`,
19
- `• Source URL: ${result.source_url}`,
20
- `• Content type: ${result.content_type}`,
21
- `• Size: ${result.size} bytes`,
22
- '',
23
- 'Use this URL as media_url when creating a post.',
24
- ].join('\n');
17
+ return mdSuccess('Remote media uploaded', [
18
+ ['URL', result.url],
19
+ ['Source URL', result.source_url],
20
+ ['Content type', result.content_type],
21
+ ['Size', formatBytes(result.size)],
22
+ ], 'Use this URL as `media_url` when creating a post.');
25
23
  },
26
24
  };
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { mdError, mdSuccess } from '../lib/format.js';
2
3
  export const uploadMediaTool = {
3
4
  name: 'upload_media',
4
5
  description: 'Upload an image or video file to posterly storage. Returns a URL that can be used with create_post. Supports JPEG, PNG, GIF, WebP, MP4, MOV, WebM. Images up to 10MB, videos up to 50MB.\n\n' +
@@ -23,7 +24,7 @@ export const uploadMediaTool = {
23
24
  }),
24
25
  async execute(client, input) {
25
26
  if (!input.file_path && !input.base64_data) {
26
- return 'Error: Provide either file_path or base64_data.';
27
+ return mdError('Provide either `file_path` or `base64_data`.');
27
28
  }
28
29
  const result = await client.uploadMedia({
29
30
  filePath: input.file_path,
@@ -31,6 +32,9 @@ export const uploadMediaTool = {
31
32
  filename: input.filename,
32
33
  contentType: input.content_type,
33
34
  });
34
- return `Media uploaded successfully!\n• URL: ${result.url}\n\nUse this URL as media_url when creating a post.`;
35
+ return mdSuccess('Media uploaded', [
36
+ ['URL', result.url],
37
+ ['Storage path', result.path],
38
+ ], 'Use this URL as `media_url` when creating a post.');
35
39
  },
36
40
  };
@@ -1,26 +1,27 @@
1
1
  import { z } from 'zod';
2
+ import { code, mdKeyValue, mdTable, mdTitle } from '../lib/format.js';
2
3
  export const whoamiTool = {
3
4
  name: 'whoami',
4
5
  description: 'Return the authenticated user, API key scopes, the default (personal) workspace, and every workspace the caller can post in. ALWAYS call this at the start of a session before creating, listing, or scheduling posts — posts created without an explicit workspace_id land in the default workspace shown here, and confirming with the user first prevents posts from appearing in the wrong workspace.',
5
6
  inputSchema: z.object({}),
6
7
  async execute(client) {
7
8
  const info = await client.whoami();
8
- const lines = [];
9
- lines.push(`User: ${info.user.email || info.user.id}`);
10
- lines.push(`API scopes: ${info.api_key.scopes.join(', ') || 'none'}`);
11
- lines.push('');
12
- lines.push(`Default workspace: ${info.default_workspace.name} (${info.default_workspace.id})` +
13
- (info.default_workspace.timezone ? ` — tz: ${info.default_workspace.timezone}` : ''));
14
- lines.push('');
15
- lines.push(`All workspaces (${info.workspaces.length}):`);
16
- for (const ws of info.workspaces) {
17
- const marker = ws.id === info.default_workspace.id ? ' [default]' : '';
18
- const personal = ws.is_personal ? ' [personal]' : '';
19
- const tz = ws.timezone ? ` — tz: ${ws.timezone}` : '';
20
- lines.push(`• ${ws.name} — ${ws.id} (role: ${ws.role || 'member'})${personal}${marker}${tz}`);
21
- }
22
- lines.push('');
23
- lines.push('Tip: pass workspace_id to create_post / list_posts / list_accounts / find_available_slot to scope actions to a specific workspace. Omit it to use the default.');
24
- return lines.join('\n');
9
+ return [
10
+ mdTitle('Posterly identity'),
11
+ mdKeyValue([
12
+ ['User', info.user.email || code(info.user.id)],
13
+ ['API scopes', info.api_key.scopes.join(', ') || 'none'],
14
+ ['Default workspace', `${info.default_workspace.name} (${code(info.default_workspace.id)})`],
15
+ ['Default timezone', info.default_workspace.timezone || 'n/a'],
16
+ ]),
17
+ mdTable(['Workspace', 'Workspace ID', 'Role', 'Timezone', 'Default'], info.workspaces.map((workspace) => [
18
+ `${workspace.name}${workspace.is_personal ? ' (personal)' : ''}`,
19
+ code(workspace.id),
20
+ workspace.role || 'member',
21
+ workspace.timezone || 'n/a',
22
+ workspace.id === info.default_workspace.id,
23
+ ])),
24
+ '**Tip:** Pass `workspace_id` to `create_post`, `list_posts`, `list_accounts`, and `find_available_slot` when you want the assistant to stay inside one workspace.',
25
+ ].join('\n\n');
25
26
  },
26
27
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posterly-mcp-server",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "MCP server for posterly — schedule social media posts from Claude Desktop",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.poster.ly/mcp",