posterly-mcp-server 0.48.4 → 0.49.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 (58) hide show
  1. package/README.md +18 -3
  2. package/dist/index.js +4 -0
  3. package/dist/lib/api-client.d.ts +149 -12
  4. package/dist/lib/api-client.js +59 -16
  5. package/dist/lib/format.d.ts +39 -2
  6. package/dist/lib/format.js +158 -0
  7. package/dist/lib/version.d.ts +1 -1
  8. package/dist/lib/version.js +1 -1
  9. package/dist/tools/audit-google-business-profile.d.ts +2 -2
  10. package/dist/tools/connect-account.d.ts +2 -2
  11. package/dist/tools/create-post.d.ts +12 -12
  12. package/dist/tools/create-posts-batch.d.ts +8 -8
  13. package/dist/tools/create-webhook.d.ts +2 -2
  14. package/dist/tools/disconnect-account.d.ts +2 -2
  15. package/dist/tools/find-slot.d.ts +2 -2
  16. package/dist/tools/get-updates.d.ts +4 -0
  17. package/dist/tools/get-updates.js +13 -1
  18. package/dist/tools/list-accounts.d.ts +2 -2
  19. package/dist/tools/list-activity.d.ts +4 -0
  20. package/dist/tools/list-activity.js +16 -3
  21. package/dist/tools/list-analytics.d.ts +2 -2
  22. package/dist/tools/list-brands.d.ts +2 -2
  23. package/dist/tools/list-comments.d.ts +2 -2
  24. package/dist/tools/list-conversations.d.ts +2 -2
  25. package/dist/tools/list-google-business-media.d.ts +2 -2
  26. package/dist/tools/list-google-business-reviews.d.ts +4 -4
  27. package/dist/tools/list-media.d.ts +4 -0
  28. package/dist/tools/list-media.js +22 -1
  29. package/dist/tools/list-platforms.d.ts +4 -4
  30. package/dist/tools/list-post-suggestions.d.ts +4 -0
  31. package/dist/tools/list-post-suggestions.js +16 -4
  32. package/dist/tools/list-posts.d.ts +35 -17
  33. package/dist/tools/list-posts.js +63 -33
  34. package/dist/tools/list-publishing-pauses.d.ts +19 -0
  35. package/dist/tools/list-publishing-pauses.js +81 -0
  36. package/dist/tools/manage-comment.d.ts +2 -2
  37. package/dist/tools/manage-conversation.d.ts +4 -4
  38. package/dist/tools/manage-google-business-media.d.ts +8 -8
  39. package/dist/tools/manage-google-business-review.d.ts +10 -10
  40. package/dist/tools/manage-publishing-pause.d.ts +35 -0
  41. package/dist/tools/manage-publishing-pause.js +47 -0
  42. package/dist/tools/manage-webhook.d.ts +4 -4
  43. package/dist/tools/validate-post.d.ts +8 -8
  44. package/package.json +1 -1
  45. package/server.json +2 -2
  46. package/src/index.ts +4 -0
  47. package/src/lib/api-client.ts +172 -17
  48. package/src/lib/format.ts +201 -1
  49. package/src/lib/version.ts +1 -1
  50. package/src/tools/get-updates.ts +15 -1
  51. package/src/tools/list-activity.ts +18 -3
  52. package/src/tools/list-media.ts +23 -2
  53. package/src/tools/list-post-suggestions.ts +17 -4
  54. package/src/tools/list-posts.ts +74 -45
  55. package/src/tools/list-publishing-pauses.ts +98 -0
  56. package/src/tools/manage-publishing-pause.ts +62 -0
  57. package/tool-annotations.json +5 -0
  58. package/tool-registry.json +173 -6
@@ -1,8 +1,8 @@
1
1
  import { z } from 'zod';
2
- import { mdTitle, mdSection, mdKeyValue, mdEmpty, truncateText } from '../lib/format.js';
2
+ import { formatPagingNote, mdTitle, mdSection, mdKeyValue, mdEmpty, truncateText } from '../lib/format.js';
3
3
  export const listPostSuggestionsTool = {
4
4
  name: 'list_post_suggestions',
5
- description: "List proactive post suggestions: evidence-based weekly drafts posterly writes in each account's learned voice, each with a rationale tying it back to what has performed well for that account. Filter by account_id and status (pending, scheduled, dismissed; default pending). Read-only; requires a Pro plan or higher. Use dismiss_suggestion to hide one.",
5
+ description: "List proactive post suggestions: evidence-based weekly drafts posterly writes in each account's learned voice, each with a rationale tying it back to what has performed well for that account. Filter by account_id and status (pending, scheduled, dismissed; default pending). The output says whether every matching suggestion is shown; page with offset until it does. Read-only; requires a Pro plan or higher. Use dismiss_suggestion to hide one.",
6
6
  inputSchema: z.object({
7
7
  account_id: z
8
8
  .union([z.string(), z.number()])
@@ -13,9 +13,11 @@ export const listPostSuggestionsTool = {
13
13
  .optional()
14
14
  .describe('Suggestion status to list (default pending).'),
15
15
  limit: z.number().int().min(1).max(100).optional().describe('Max suggestions to return (default 20).'),
16
+ offset: z.number().int().min(0).optional().describe('How many suggestions to skip. Use the offset the previous page gives to get the next page.'),
16
17
  }),
17
18
  async execute(client, input) {
18
- const { suggestions } = await client.listPostSuggestions(input);
19
+ const page = await client.listPostSuggestions(input);
20
+ const { suggestions } = page;
19
21
  if (!suggestions.length) {
20
22
  return mdEmpty('post suggestions', 'No proactive post suggestions match these filters. posterly generates fresh drafts weekly for accounts with a learned voice.');
21
23
  }
@@ -31,6 +33,16 @@ export const listPostSuggestionsTool = {
31
33
  ['Rationale', suggestion.rationale ? truncateText(suggestion.rationale, 240) : undefined],
32
34
  ]));
33
35
  });
34
- return [mdTitle(`Post suggestions (${suggestions.length})`), ...blocks].join('\n\n');
36
+ const note = formatPagingNote({
37
+ noun: 'suggestions',
38
+ tool: 'list_post_suggestions',
39
+ offset: page.offset ?? input.offset ?? 0,
40
+ count: suggestions.length,
41
+ total: page.total,
42
+ // An API older than has_more: a full page may mean more exist.
43
+ hasMore: page.has_more ?? suggestions.length >= (input.limit || 20),
44
+ nextOffset: page.next_offset,
45
+ });
46
+ return [mdTitle(`Post suggestions (${suggestions.length})`), ...blocks, note].join('\n\n');
35
47
  },
36
48
  };
@@ -1,44 +1,62 @@
1
1
  import { z } from 'zod';
2
- import type { PosterlyClient } from '../lib/api-client.js';
2
+ import type { ListPostsParams, PosterlyClient } from '../lib/api-client.js';
3
+ import { type ToolContext } from '../lib/tool-runtime.js';
4
+ type ListPostsInput = ListPostsParams & {
5
+ view?: 'list' | 'counts';
6
+ limit?: number;
7
+ offset?: number;
8
+ order?: 'asc' | 'desc';
9
+ };
10
+ export declare const LIST_POSTS_DESCRIPTION: string;
3
11
  export declare const listPostsTool: {
4
12
  name: string;
5
13
  description: string;
6
14
  inputSchema: z.ZodObject<{
15
+ view: z.ZodOptional<z.ZodEnum<["list", "counts"]>>;
7
16
  status: z.ZodOptional<z.ZodString>;
8
17
  platform: z.ZodOptional<z.ZodEnum<["bluesky", "devto", "discord", "facebook", "gmb", "google-business", "google_business", "google_business_profile", "hashnode", "instagram", "instagram-standalone", "lemmy", "linkedin", "mastodon", "pinterest", "slack", "telegram", "threads", "tiktok", "twitter", "wordpress", "x", "youtube"]>>;
9
18
  account_id: z.ZodOptional<z.ZodString>;
19
+ scheduled_from: z.ZodOptional<z.ZodString>;
20
+ scheduled_to: z.ZodOptional<z.ZodString>;
21
+ timezone: z.ZodOptional<z.ZodString>;
22
+ order: z.ZodOptional<z.ZodEnum<["asc", "desc"]>>;
10
23
  limit: z.ZodOptional<z.ZodNumber>;
24
+ offset: z.ZodOptional<z.ZodNumber>;
11
25
  workspace_id: z.ZodOptional<z.ZodString>;
12
26
  approval_status: z.ZodOptional<z.ZodEnum<["none", "pending_client", "approved", "changes_requested", "rejected"]>>;
13
27
  approval_waiting_on: z.ZodOptional<z.ZodEnum<["client", "team"]>>;
14
28
  brand_id: z.ZodOptional<z.ZodString>;
15
29
  }, "strip", z.ZodTypeAny, {
16
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
17
30
  status?: string | undefined;
18
- workspace_id?: string | undefined;
31
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
19
32
  account_id?: string | undefined;
20
- limit?: number | undefined;
33
+ workspace_id?: string | undefined;
21
34
  approval_status?: "none" | "pending_client" | "approved" | "changes_requested" | "rejected" | undefined;
22
35
  approval_waiting_on?: "client" | "team" | undefined;
23
36
  brand_id?: string | undefined;
37
+ scheduled_from?: string | undefined;
38
+ scheduled_to?: string | undefined;
39
+ timezone?: string | undefined;
40
+ limit?: number | undefined;
41
+ offset?: number | undefined;
42
+ order?: "asc" | "desc" | undefined;
43
+ view?: "list" | "counts" | undefined;
24
44
  }, {
25
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
26
45
  status?: string | undefined;
27
- workspace_id?: string | undefined;
46
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
28
47
  account_id?: string | undefined;
29
- limit?: number | undefined;
48
+ workspace_id?: string | undefined;
30
49
  approval_status?: "none" | "pending_client" | "approved" | "changes_requested" | "rejected" | undefined;
31
50
  approval_waiting_on?: "client" | "team" | undefined;
32
51
  brand_id?: string | undefined;
52
+ scheduled_from?: string | undefined;
53
+ scheduled_to?: string | undefined;
54
+ timezone?: string | undefined;
55
+ limit?: number | undefined;
56
+ offset?: number | undefined;
57
+ order?: "asc" | "desc" | undefined;
58
+ view?: "list" | "counts" | undefined;
33
59
  }>;
34
- execute(client: PosterlyClient, input: {
35
- status?: string;
36
- platform?: string;
37
- account_id?: string;
38
- limit?: number;
39
- workspace_id?: string;
40
- approval_status?: string;
41
- approval_waiting_on?: "client" | "team";
42
- brand_id?: string;
43
- }): Promise<string>;
60
+ execute(client: PosterlyClient, input: ListPostsInput, ctx: ToolContext): Promise<string>;
44
61
  };
62
+ export {};
@@ -1,10 +1,20 @@
1
1
  import { z } from 'zod';
2
- import { dashboardUrlForPostList, formatLocalDateTime, truncateText } from '../lib/format.js';
2
+ import { dashboardUrlForPostList, formatPostCounts, formatPostListPage } from '../lib/format.js';
3
+ import { validateArgs } from '../lib/tool-runtime.js';
3
4
  import { SUPPORTED_PLATFORM_INPUTS, SUPPORTED_PLATFORM_IDS } from '../generated/platform-manifest.js';
5
+ export const LIST_POSTS_DESCRIPTION = 'Posts in your workspaces, teammates\' too, per your role there (brand-locked seats only see their assigned brands). If workspace_id is omitted, this searches every workspace you belong to. ' +
6
+ 'view=list (default): posts filtered by status (scheduled, published, failed, draft), platform, account_id, workspace_id, brand_id, approval_status, approval_waiting_on, and a scheduled date range: scheduled_from (inclusive) and scheduled_to (a plain date includes that whole day), read in timezone (IANA, default UTC; also the zone times are shown in). Returns limit posts (default 20, max 200) starting at offset; with a date range the order defaults to oldest first (order=asc|desc). ' +
7
+ 'The output always says whether every matching post was shown. If it says more are available, call again with the offset it gives and keep paging until all are shown before drawing any conclusion about totals, gaps, or how busy a client is. ' +
8
+ 'view=counts (scheduled_from and scheduled_to required, up to 366 days): exact totals per day, status, account, and brand for the same filters (limit, offset, and order are ignored); use it for "how many" or "is this month full" questions. ' +
9
+ 'approval_waiting_on=client is the same queue as approval_status=pending_client. Each post includes approval.pending_client_approval.';
4
10
  export const listPostsTool = {
5
11
  name: 'list_posts',
6
- description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, account_id, workspace_id, approval_status, approval_waiting_on, or brand_id. approval_waiting_on=client is the same queue as approval_status=pending_client. Each post includes approval.pending_client_approval. Returns posts in your workspaces, teammates\' too, per your role there (brand-locked seats only see their assigned brands). If workspace_id is omitted, this searches every workspace you belong to.',
12
+ description: LIST_POSTS_DESCRIPTION,
7
13
  inputSchema: z.object({
14
+ view: z
15
+ .enum(['list', 'counts'])
16
+ .optional()
17
+ .describe('list (default): the posts themselves, one page at a time. counts: exact totals per day, status, account, and brand (needs scheduled_from and scheduled_to).'),
8
18
  status: z
9
19
  .string()
10
20
  .optional()
@@ -17,12 +27,34 @@ export const listPostsTool = {
17
27
  .string()
18
28
  .optional()
19
29
  .describe('Filter to a specific social account ID (from list_accounts).'),
30
+ scheduled_from: z
31
+ .string()
32
+ .optional()
33
+ .describe('Start of the scheduled range, inclusive: a date (2026-10-01) or an ISO date-time. Plain dates and times without an offset are read in timezone.'),
34
+ scheduled_to: z
35
+ .string()
36
+ .optional()
37
+ .describe('End of the scheduled range: a plain date (2026-10-31) includes that whole day; an ISO date-time is exclusive.'),
38
+ timezone: z
39
+ .string()
40
+ .optional()
41
+ .describe('IANA time zone such as Europe/London for reading plain dates and showing times. Default UTC.'),
42
+ order: z
43
+ .enum(['asc', 'desc'])
44
+ .optional()
45
+ .describe('asc (oldest first) or desc (newest first). Default asc with a date range, otherwise desc. Ignored by view=counts.'),
20
46
  limit: z
21
47
  .number()
22
48
  .min(1)
23
- .max(100)
49
+ .max(200)
24
50
  .optional()
25
- .describe('Number of posts to return (default 20, max 100)'),
51
+ .describe('Posts per page (default 20, max 200). Ignored by view=counts.'),
52
+ offset: z
53
+ .number()
54
+ .int()
55
+ .min(0)
56
+ .optional()
57
+ .describe('How many matching posts to skip. Use the offset the previous page gives to get the next page. Ignored by view=counts.'),
26
58
  workspace_id: z
27
59
  .string()
28
60
  .optional()
@@ -40,36 +72,34 @@ export const listPostsTool = {
40
72
  .optional()
41
73
  .describe('Filter to posts assigned to a specific brand/client (the id from list_brands).'),
42
74
  }),
43
- async execute(client, input) {
44
- const { posts, total } = await client.listPosts({
45
- ...input,
46
- limit: input.limit || 20,
47
- });
48
- if (posts.length === 0) {
49
- return 'No posts found matching your criteria.\n\nNext step for the AI agent: tell the user their posterly queue is clear for that filter, then ask what they want to schedule next.';
75
+ async execute(client, input, ctx) {
76
+ const { mode, args } = validateArgs(ctx, 'list_posts', input);
77
+ const filters = postFilters(args);
78
+ const timeZone = filters.timezone || 'UTC';
79
+ if (mode === 'counts') {
80
+ return formatPostCounts(await client.countPosts(filters));
50
81
  }
51
- const lines = posts.map((p) => {
52
- const date = formatLocalDateTime(p.scheduled_at);
53
- const caption = truncateText(p.content, 60);
54
- const approvalBits = [];
55
- if (p.approval && p.approval.status !== 'none') {
56
- approvalBits.push(`approval: ${p.approval.status}`);
57
- if (p.approval.waiting_on)
58
- approvalBits.push(`waiting on: ${p.approval.waiting_on}`);
59
- }
60
- if (p.brand_id)
61
- approvalBits.push(`brand: ${p.brand_id}`);
62
- if (p.content_plan_id)
63
- approvalBits.push(`plan: ${p.content_plan_id}`);
64
- const approvalSuffix = approvalBits.length > 0 ? ` [${approvalBits.join(', ')}]` : '';
65
- return `• [${p.status}] #${p.id}: ${caption} (${date})${approvalSuffix}`;
82
+ const hasRange = Boolean(filters.scheduled_from || filters.scheduled_to);
83
+ const page = await client.listPosts({
84
+ ...filters,
85
+ limit: args.limit || 20,
86
+ offset: args.offset,
87
+ order: args.order ?? (hasRange ? 'asc' : undefined),
66
88
  });
67
- return [
68
- `Posts (${posts.length} of ${total}):`,
69
- ...lines,
70
- '',
71
- `View in posterly: ${dashboardUrlForPostList(posts)}`,
72
- 'Next step for the AI agent: summarize the queue in plain language and use the dashboard link above if the user wants to inspect it in posterly.',
73
- ].join('\n');
89
+ return formatPostListPage(page, { timeZone, dashboardUrl: dashboardUrlForPostList(page.posts || []) });
74
90
  },
75
91
  };
92
+ function postFilters(input) {
93
+ return {
94
+ status: input.status,
95
+ platform: input.platform,
96
+ account_id: input.account_id,
97
+ workspace_id: input.workspace_id,
98
+ approval_status: input.approval_status,
99
+ approval_waiting_on: input.approval_waiting_on,
100
+ brand_id: input.brand_id,
101
+ scheduled_from: input.scheduled_from,
102
+ scheduled_to: input.scheduled_to,
103
+ timezone: input.timezone,
104
+ };
105
+ }
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient, PublishingPauseChangeResponse, PublishingPauseState, PublishMissedResponse } from '../lib/api-client.js';
3
+ export declare function formatPublishingPauseState(data: PublishingPauseState): string;
4
+ export declare function formatPublishingPauseChange(data: PublishingPauseChangeResponse): string;
5
+ export declare function formatPublishMissed(data: PublishMissedResponse): string;
6
+ export declare const listPublishingPausesTool: {
7
+ name: string;
8
+ description: string;
9
+ inputSchema: z.ZodObject<{
10
+ workspace_id: z.ZodOptional<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ workspace_id?: string | undefined;
13
+ }, {
14
+ workspace_id?: string | undefined;
15
+ }>;
16
+ execute(client: PosterlyClient, input: {
17
+ workspace_id?: string;
18
+ }): Promise<string>;
19
+ };
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod';
2
+ // Plain-text output shared by list_publishing_pauses and
3
+ // manage_publishing_pause. The hosted endpoint (lib/mcp/http-tools.ts) renders
4
+ // the same text from the same API responses; scripts/test-publishing-pause-api.ts
5
+ // checks the two surfaces match.
6
+ function yesNo(value) {
7
+ return value ? 'yes' : 'no';
8
+ }
9
+ function idList(ids) {
10
+ return Array.isArray(ids) && ids.length > 0 ? ids.join(', ') : 'none';
11
+ }
12
+ function scopeText(data) {
13
+ if (data.scope === 'brand')
14
+ return `brand ${data.brand_name ? `${data.brand_name} ` : ''}(${data.brand_id})`;
15
+ return `the workspace ${data.workspace_name ? `${data.workspace_name} ` : ''}(${data.workspace_id})`;
16
+ }
17
+ export function formatPublishingPauseState(data) {
18
+ const workspace = data.workspace;
19
+ const brands = Array.isArray(data.brands) ? data.brands : [];
20
+ const viewer = data.viewer;
21
+ return [
22
+ `Publishing pause for ${workspace.name || 'this workspace'} (${workspace.id})`,
23
+ `Workspace: ${workspace.paused ? `paused since ${workspace.paused_at}` : 'publishing normally'}`,
24
+ brands.length > 0
25
+ ? ['Paused brands:', ...brands.map((brand) => `- ${brand.name || 'Unnamed brand'} (${brand.id}), paused since ${brand.paused_at}`)].join('\n')
26
+ : 'Paused brands: none',
27
+ `Your role: ${viewer.role}. Pause the workspace: ${yesNo(viewer.can_pause_workspace)}. Pause a brand: ${yesNo(viewer.can_pause_brand)}. Resume and publish missed posts: ${yesNo(viewer.can_resume)}.`,
28
+ 'While paused, nothing in that scope publishes (scheduled posts are held, Post now is refused). Pause and resume with manage_publishing_pause.',
29
+ ].join('\n');
30
+ }
31
+ export function formatPublishingPauseChange(data) {
32
+ if (data.requires_confirmation) {
33
+ return [
34
+ 'Preview only: nothing has changed yet.',
35
+ data.error || '',
36
+ `Scope: ${scopeText(data)}`,
37
+ `Scheduled posts to hold: ${typeof data.scheduled_posts_to_hold === 'number' ? data.scheduled_posts_to_hold : 'unknown'}`,
38
+ `Already paused: ${yesNo(data.already_paused)}`,
39
+ 'Next step: show this to the user. Only after they agree, call manage_publishing_pause again with action=pause, the same fields, and confirm=true.',
40
+ ].filter(Boolean).join('\n');
41
+ }
42
+ if (data.action === 'pause') {
43
+ const failed = data.failed_post_ids || [];
44
+ return [
45
+ `Publishing paused for ${scopeText(data)}.`,
46
+ `Held posts: ${data.held_count ?? 0}`,
47
+ failed.length > 0 ? `Posts that could not be marked as held (the publish guard still holds them): ${idList(failed)}` : '',
48
+ data.message || '',
49
+ ].filter(Boolean).join('\n');
50
+ }
51
+ const missed = data.missed_post_ids || [];
52
+ const failed = data.failed_post_ids || [];
53
+ return [
54
+ `Publishing resumed for ${scopeText(data)}.`,
55
+ `Scheduled again: ${data.resumed_count ?? 0}`,
56
+ `Missed their time while paused (still paused): ${missed.length}${missed.length > 0 ? ` [post IDs: ${idList(missed)}]` : ''}`,
57
+ `Still held by another pause: ${data.still_held_count ?? 0}`,
58
+ (data.disconnected_count ?? 0) > 0 ? `Still paused because the account is disconnected: ${data.disconnected_count}` : '',
59
+ failed.length > 0 ? `Could not be released (try resume again): ${idList(failed)}` : '',
60
+ missed.length > 0
61
+ ? 'Next step: missed posts never publish on their own. Ask the user whether to publish them now (action=publish_missed with these post_ids and confirm=true) or reschedule them.'
62
+ : '',
63
+ ].filter(Boolean).join('\n');
64
+ }
65
+ export function formatPublishMissed(data) {
66
+ return [
67
+ `Missed posts: ${data.started} publishing now, ${data.skipped} skipped.`,
68
+ data.message || '',
69
+ ].filter(Boolean).join('\n');
70
+ }
71
+ export const listPublishingPausesTool = {
72
+ name: 'list_publishing_pauses',
73
+ description: 'Is publishing paused? Shows whether the workspace is paused, which brands are paused (only brands you can see), and whether you may pause, resume, or publish missed posts. Read-only. Change it with manage_publishing_pause.',
74
+ inputSchema: z.object({
75
+ workspace_id: z.string().optional().describe('Workspace ID from whoami. Defaults to your default workspace.'),
76
+ }),
77
+ async execute(client, input) {
78
+ const state = await client.getPublishingPause(input.workspace_id ? { workspace_id: input.workspace_id } : undefined);
79
+ return formatPublishingPauseState(state);
80
+ },
81
+ };
@@ -28,9 +28,9 @@ export declare const manageCommentTool: {
28
28
  }, "strip", z.ZodTypeAny, {
29
29
  action: "update" | "delete" | "reply";
30
30
  comment_id: string;
31
+ workspace_id?: string | undefined;
31
32
  confirm?: boolean | undefined;
32
33
  preview_id?: string | undefined;
33
- workspace_id?: string | undefined;
34
34
  type?: "public" | "private" | undefined;
35
35
  content?: string | undefined;
36
36
  is_hidden?: boolean | undefined;
@@ -38,9 +38,9 @@ export declare const manageCommentTool: {
38
38
  }, {
39
39
  action: "update" | "delete" | "reply";
40
40
  comment_id: string;
41
+ workspace_id?: string | undefined;
41
42
  confirm?: boolean | undefined;
42
43
  preview_id?: string | undefined;
43
- workspace_id?: string | undefined;
44
44
  type?: "public" | "private" | undefined;
45
45
  content?: string | undefined;
46
46
  is_hidden?: boolean | undefined;
@@ -25,19 +25,19 @@ export declare const manageConversationTool: {
25
25
  preview_id: z.ZodOptional<z.ZodString>;
26
26
  }, "strip", z.ZodTypeAny, {
27
27
  action: "send" | "sync";
28
+ account_id?: number | undefined;
29
+ workspace_id?: string | undefined;
28
30
  confirm?: boolean | undefined;
29
31
  preview_id?: string | undefined;
30
- workspace_id?: string | undefined;
31
- account_id?: number | undefined;
32
32
  content?: string | undefined;
33
33
  conversation_id?: string | undefined;
34
34
  sync_type?: "all" | "dm" | "comments" | undefined;
35
35
  }, {
36
36
  action: "send" | "sync";
37
+ account_id?: number | undefined;
38
+ workspace_id?: string | undefined;
37
39
  confirm?: boolean | undefined;
38
40
  preview_id?: string | undefined;
39
- workspace_id?: string | undefined;
40
- account_id?: number | undefined;
41
41
  content?: string | undefined;
42
42
  conversation_id?: string | undefined;
43
43
  sync_type?: "all" | "dm" | "comments" | undefined;
@@ -30,10 +30,10 @@ export declare const manageGoogleBusinessMediaTool: {
30
30
  preview_id: z.ZodOptional<z.ZodString>;
31
31
  }, "strip", z.ZodTypeAny, {
32
32
  action: "delete" | "add";
33
+ account_id?: number | undefined;
34
+ workspace_id?: string | undefined;
33
35
  confirm?: boolean | undefined;
34
36
  preview_id?: string | undefined;
35
- workspace_id?: string | undefined;
36
- account_id?: number | undefined;
37
37
  location_id?: string | undefined;
38
38
  category?: "COVER" | "PROFILE" | "LOGO" | "EXTERIOR" | "INTERIOR" | "PRODUCT" | "AT_WORK" | "FOOD_AND_DRINK" | "MENU" | "COMMON_AREA" | "ROOMS" | "TEAMS" | "ADDITIONAL" | undefined;
39
39
  source_url?: string | undefined;
@@ -41,10 +41,10 @@ export declare const manageGoogleBusinessMediaTool: {
41
41
  media_name?: string | undefined;
42
42
  }, {
43
43
  action: "delete" | "add";
44
+ account_id?: number | undefined;
45
+ workspace_id?: string | undefined;
44
46
  confirm?: boolean | undefined;
45
47
  preview_id?: string | undefined;
46
- workspace_id?: string | undefined;
47
- account_id?: number | undefined;
48
48
  location_id?: string | undefined;
49
49
  category?: "COVER" | "PROFILE" | "LOGO" | "EXTERIOR" | "INTERIOR" | "PRODUCT" | "AT_WORK" | "FOOD_AND_DRINK" | "MENU" | "COMMON_AREA" | "ROOMS" | "TEAMS" | "ADDITIONAL" | undefined;
50
50
  source_url?: string | undefined;
@@ -65,16 +65,16 @@ export declare const manageGoogleBusinessMediaTool: {
65
65
  confirm: true;
66
66
  category: "COVER" | "PROFILE" | "LOGO" | "EXTERIOR" | "INTERIOR" | "PRODUCT" | "AT_WORK" | "FOOD_AND_DRINK" | "MENU" | "COMMON_AREA" | "ROOMS" | "TEAMS" | "ADDITIONAL";
67
67
  source_url: string;
68
- workspace_id?: string | undefined;
69
68
  account_id?: number | undefined;
69
+ workspace_id?: string | undefined;
70
70
  location_id?: string | undefined;
71
71
  media_format?: "PHOTO" | "VIDEO" | undefined;
72
72
  }, {
73
73
  confirm: true;
74
74
  category: "COVER" | "PROFILE" | "LOGO" | "EXTERIOR" | "INTERIOR" | "PRODUCT" | "AT_WORK" | "FOOD_AND_DRINK" | "MENU" | "COMMON_AREA" | "ROOMS" | "TEAMS" | "ADDITIONAL";
75
75
  source_url: string;
76
- workspace_id?: string | undefined;
77
76
  account_id?: number | undefined;
77
+ workspace_id?: string | undefined;
78
78
  location_id?: string | undefined;
79
79
  media_format?: "PHOTO" | "VIDEO" | undefined;
80
80
  }>;
@@ -87,14 +87,14 @@ export declare const manageGoogleBusinessMediaTool: {
87
87
  }, "strip", z.ZodTypeAny, {
88
88
  confirm: true;
89
89
  media_name: string;
90
- workspace_id?: string | undefined;
91
90
  account_id?: number | undefined;
91
+ workspace_id?: string | undefined;
92
92
  location_id?: string | undefined;
93
93
  }, {
94
94
  confirm: true;
95
95
  media_name: string;
96
- workspace_id?: string | undefined;
97
96
  account_id?: number | undefined;
97
+ workspace_id?: string | undefined;
98
98
  location_id?: string | undefined;
99
99
  }>;
100
100
  };
@@ -37,10 +37,10 @@ export declare const manageGoogleBusinessReviewTool: {
37
37
  preview_id: z.ZodOptional<z.ZodString>;
38
38
  }, "strip", z.ZodTypeAny, {
39
39
  action: "suggest_reply" | "reply" | "delete_reply";
40
+ account_id?: number | undefined;
41
+ workspace_id?: string | undefined;
40
42
  confirm?: boolean | undefined;
41
43
  preview_id?: string | undefined;
42
- workspace_id?: string | undefined;
43
- account_id?: number | undefined;
44
44
  count?: number | undefined;
45
45
  location_id?: string | undefined;
46
46
  comment?: string | undefined;
@@ -52,10 +52,10 @@ export declare const manageGoogleBusinessReviewTool: {
52
52
  business_name?: string | undefined;
53
53
  }, {
54
54
  action: "suggest_reply" | "reply" | "delete_reply";
55
+ account_id?: number | undefined;
56
+ workspace_id?: string | undefined;
55
57
  confirm?: boolean | undefined;
56
58
  preview_id?: string | undefined;
57
- workspace_id?: string | undefined;
58
- account_id?: number | undefined;
59
59
  count?: number | undefined;
60
60
  location_id?: string | undefined;
61
61
  comment?: string | undefined;
@@ -80,8 +80,8 @@ export declare const manageGoogleBusinessReviewTool: {
80
80
  location_id: z.ZodOptional<z.ZodString>;
81
81
  }, "strip", z.ZodTypeAny, {
82
82
  star_rating: number;
83
- workspace_id?: string | undefined;
84
83
  account_id?: number | undefined;
84
+ workspace_id?: string | undefined;
85
85
  count?: number | undefined;
86
86
  location_id?: string | undefined;
87
87
  tone?: "professional" | "friendly" | "apologetic" | undefined;
@@ -90,8 +90,8 @@ export declare const manageGoogleBusinessReviewTool: {
90
90
  business_name?: string | undefined;
91
91
  }, {
92
92
  star_rating: number;
93
- workspace_id?: string | undefined;
94
93
  account_id?: number | undefined;
94
+ workspace_id?: string | undefined;
95
95
  count?: number | undefined;
96
96
  location_id?: string | undefined;
97
97
  tone?: "professional" | "friendly" | "apologetic" | undefined;
@@ -110,15 +110,15 @@ export declare const manageGoogleBusinessReviewTool: {
110
110
  confirm: true;
111
111
  comment: string;
112
112
  review_name: string;
113
- workspace_id?: string | undefined;
114
113
  account_id?: number | undefined;
114
+ workspace_id?: string | undefined;
115
115
  location_id?: string | undefined;
116
116
  }, {
117
117
  confirm: true;
118
118
  comment: string;
119
119
  review_name: string;
120
- workspace_id?: string | undefined;
121
120
  account_id?: number | undefined;
121
+ workspace_id?: string | undefined;
122
122
  location_id?: string | undefined;
123
123
  }>;
124
124
  delete_google_business_review_reply: z.ZodObject<{
@@ -130,14 +130,14 @@ export declare const manageGoogleBusinessReviewTool: {
130
130
  }, "strip", z.ZodTypeAny, {
131
131
  confirm: true;
132
132
  review_name: string;
133
- workspace_id?: string | undefined;
134
133
  account_id?: number | undefined;
134
+ workspace_id?: string | undefined;
135
135
  location_id?: string | undefined;
136
136
  }, {
137
137
  confirm: true;
138
138
  review_name: string;
139
- workspace_id?: string | undefined;
140
139
  account_id?: number | undefined;
140
+ workspace_id?: string | undefined;
141
141
  location_id?: string | undefined;
142
142
  }>;
143
143
  };
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ import { type ToolContext } from '../lib/tool-runtime.js';
4
+ type ManagePublishingPauseInput = {
5
+ action: 'pause' | 'resume' | 'publish_missed';
6
+ workspace_id?: string;
7
+ brand_id?: string;
8
+ post_ids?: number[];
9
+ confirm?: boolean;
10
+ };
11
+ export declare const managePublishingPauseTool: {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: z.ZodObject<{
15
+ action: z.ZodEnum<["pause", "resume", "publish_missed"]>;
16
+ workspace_id: z.ZodOptional<z.ZodString>;
17
+ brand_id: z.ZodOptional<z.ZodString>;
18
+ post_ids: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
19
+ confirm: z.ZodOptional<z.ZodBoolean>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ action: "pause" | "resume" | "publish_missed";
22
+ workspace_id?: string | undefined;
23
+ brand_id?: string | undefined;
24
+ confirm?: boolean | undefined;
25
+ post_ids?: number[] | undefined;
26
+ }, {
27
+ action: "pause" | "resume" | "publish_missed";
28
+ workspace_id?: string | undefined;
29
+ brand_id?: string | undefined;
30
+ confirm?: boolean | undefined;
31
+ post_ids?: number[] | undefined;
32
+ }>;
33
+ execute(client: PosterlyClient, input: ManagePublishingPauseInput, ctx: ToolContext): Promise<string>;
34
+ };
35
+ export {};
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ import { validateArgs } from '../lib/tool-runtime.js';
3
+ import { formatPublishingPauseChange, formatPublishMissed } from './list-publishing-pauses.js';
4
+ /** Same wording as EMPTY_BRAND_ID_MESSAGE in lib/publish/publishing-pause-service.ts. */
5
+ const EMPTY_BRAND_ID_MESSAGE = 'brand_id is empty; omit it to target the whole workspace.';
6
+ export const managePublishingPauseTool = {
7
+ name: 'manage_publishing_pause',
8
+ description: 'Pause or resume publishing for a whole workspace or one brand (check it first with list_publishing_pauses). Only act when the user asks; never pause on your own initiative. action=pause (optional brand_id; leave it out for the whole workspace): holds every scheduled post in that scope and blocks Post now until an admin resumes. Call it first without confirm to get a preview (scope and how many scheduled posts it holds), show it to the user, and only after they agree call again with confirm=true. Publisher or higher; a seat limited to some brands can only pause those brands. action=resume (optional brand_id; admin or higher): future posts are scheduled again; posts whose time passed while paused stay paused and are listed as missed. Missed posts never publish on their own. action=publish_missed (post_ids from the resume, confirm=true after the user agrees; admin or higher): publishes those missed posts now.',
9
+ inputSchema: z.object({
10
+ action: z.enum(['pause', 'resume', 'publish_missed']).describe('pause, resume, or publish_missed.'),
11
+ workspace_id: z.string().optional().describe('Workspace ID from whoami. Defaults to your default workspace.'),
12
+ brand_id: z.string().optional().describe('pause and resume: a brand ID from list_brands. Leave it out (never empty) for the whole workspace.'),
13
+ post_ids: z.array(z.number().int().positive()).max(500).optional().describe('publish_missed: the missed post IDs a resume returned.'),
14
+ confirm: z.boolean().optional().describe('pause: leave out to get a preview; true only after the user agreed. publish_missed: true after the user agreed. resume: not needed.'),
15
+ }),
16
+ async execute(client, input, ctx) {
17
+ // A present but empty brand_id is refused, never read as the whole
18
+ // workspace (same rule and wording as the API and the hosted endpoint).
19
+ if ((input?.action === 'pause' || input?.action === 'resume') && input && Object.prototype.hasOwnProperty.call(input, 'brand_id')) {
20
+ const value = input.brand_id;
21
+ if (value === null || (typeof value === 'string' && value.trim() === ''))
22
+ throw new Error(EMPTY_BRAND_ID_MESSAGE);
23
+ }
24
+ const { mode, args } = validateArgs(ctx, 'manage_publishing_pause', input);
25
+ const call = args;
26
+ const workspace = call.workspace_id ? { workspace_id: call.workspace_id } : {};
27
+ if (mode === 'publish_missed') {
28
+ if (call.confirm !== true) {
29
+ throw new Error('Refusing to publish missed posts without confirm=true after explicit user confirmation.');
30
+ }
31
+ const result = await client.publishMissedPausedPosts({ ...workspace, post_ids: call.post_ids || [], confirm: true });
32
+ return formatPublishMissed(result);
33
+ }
34
+ const brand = call.brand_id ? { brand_id: call.brand_id } : {};
35
+ if (mode === 'pause') {
36
+ const result = await client.changePublishingPause({
37
+ action: 'pause',
38
+ ...workspace,
39
+ ...brand,
40
+ ...(call.confirm === true ? { confirm: true } : {}),
41
+ });
42
+ return formatPublishingPauseChange(result);
43
+ }
44
+ const result = await client.changePublishingPause({ action: 'resume', ...workspace, ...brand });
45
+ return formatPublishingPauseChange(result);
46
+ },
47
+ };