posterly-mcp-server 0.44.0 → 0.45.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 (155) hide show
  1. package/README.md +310 -111
  2. package/dist/index.js +190 -894
  3. package/dist/lib/alias-transport.d.ts +84 -0
  4. package/dist/lib/alias-transport.js +193 -0
  5. package/dist/lib/api-client.d.ts +18 -0
  6. package/dist/lib/api-client.js +55 -0
  7. package/dist/lib/preview-token-core.d.ts +62 -0
  8. package/dist/lib/preview-token-core.js +131 -0
  9. package/dist/lib/preview-token.d.ts +23 -0
  10. package/dist/lib/preview-token.js +44 -0
  11. package/dist/lib/tool-registry.d.ts +246 -0
  12. package/dist/lib/tool-registry.js +394 -0
  13. package/dist/lib/tool-runtime.d.ts +68 -0
  14. package/dist/lib/tool-runtime.js +85 -0
  15. package/dist/lib/version.d.ts +1 -1
  16. package/dist/lib/version.js +1 -1
  17. package/dist/tools/audit-google-business-profile.js +1 -1
  18. package/dist/tools/connect-account.d.ts +4 -4
  19. package/dist/tools/connect-account.js +2 -2
  20. package/dist/tools/create-connect-session.d.ts +2 -2
  21. package/dist/tools/create-connect-session.js +2 -2
  22. package/dist/tools/create-post.d.ts +6 -6
  23. package/dist/tools/create-post.js +3 -3
  24. package/dist/tools/create-posts-batch.d.ts +4 -4
  25. package/dist/tools/create-webhook.d.ts +2 -2
  26. package/dist/tools/delete-post.d.ts +34 -10
  27. package/dist/tools/delete-post.js +52 -16
  28. package/dist/tools/generate-captions.d.ts +2 -2
  29. package/dist/tools/generate-image.d.ts +4 -4
  30. package/dist/tools/generate-image.js +2 -2
  31. package/dist/tools/generate-video.d.ts +6 -6
  32. package/dist/tools/generate-video.js +1 -1
  33. package/dist/tools/get-agent-signup-info.js +2 -2
  34. package/dist/tools/get-post.d.ts +16 -1
  35. package/dist/tools/get-post.js +35 -19
  36. package/dist/tools/list-accounts.d.ts +49 -3
  37. package/dist/tools/list-accounts.js +148 -33
  38. package/dist/tools/list-analytics.d.ts +111 -0
  39. package/dist/tools/list-analytics.js +393 -0
  40. package/dist/tools/list-brands.d.ts +38 -3
  41. package/dist/tools/list-brands.js +138 -20
  42. package/dist/tools/list-comments.d.ts +29 -9
  43. package/dist/tools/list-comments.js +75 -34
  44. package/dist/tools/list-conversations.d.ts +32 -9
  45. package/dist/tools/list-conversations.js +63 -27
  46. package/dist/tools/list-google-business-media.js +1 -1
  47. package/dist/tools/list-google-business-reviews.d.ts +32 -9
  48. package/dist/tools/list-google-business-reviews.js +77 -52
  49. package/dist/tools/list-jobs.d.ts +60 -0
  50. package/dist/tools/list-jobs.js +81 -0
  51. package/dist/tools/list-platforms.d.ts +62 -3
  52. package/dist/tools/list-platforms.js +118 -9
  53. package/dist/tools/list-posts.d.ts +2 -2
  54. package/dist/tools/manage-comment.d.ts +102 -0
  55. package/dist/tools/manage-comment.js +125 -0
  56. package/dist/tools/manage-conversation.d.ts +79 -0
  57. package/dist/tools/manage-conversation.js +75 -0
  58. package/dist/tools/manage-google-business-media.d.ts +103 -0
  59. package/dist/tools/manage-google-business-media.js +126 -0
  60. package/dist/tools/manage-google-business-review.d.ts +146 -0
  61. package/dist/tools/manage-google-business-review.js +115 -0
  62. package/dist/tools/manage-oauth-client.d.ts +121 -0
  63. package/dist/tools/manage-oauth-client.js +94 -0
  64. package/dist/tools/manage-webhook.d.ts +99 -0
  65. package/dist/tools/manage-webhook.js +87 -0
  66. package/dist/tools/start-signup.d.ts +2 -2
  67. package/dist/tools/submit-agent-feedback.d.ts +4 -4
  68. package/dist/tools/trigger-platform-helper.d.ts +2 -2
  69. package/dist/tools/update-post.d.ts +62 -11
  70. package/dist/tools/update-post.js +79 -26
  71. package/dist/tools/upload-media.d.ts +32 -9
  72. package/dist/tools/upload-media.js +54 -18
  73. package/dist/tools/validate-post.d.ts +4 -4
  74. package/dist/tools/whoami.d.ts +15 -2
  75. package/dist/tools/whoami.js +221 -25
  76. package/package.json +5 -3
  77. package/scripts/test-alias-transport.mjs +368 -0
  78. package/scripts/test-client-header-sanitization.mjs +78 -0
  79. package/scripts/test-tool-annotations.mjs +38 -17
  80. package/server.json +9 -2
  81. package/src/index.ts +227 -1443
  82. package/src/lib/alias-transport.ts +233 -0
  83. package/src/lib/api-client.ts +65 -0
  84. package/src/lib/preview-token-core.ts +157 -0
  85. package/src/lib/preview-token.ts +54 -0
  86. package/src/lib/tool-registry.ts +538 -0
  87. package/src/lib/tool-runtime.ts +117 -0
  88. package/src/lib/version.ts +1 -1
  89. package/src/tools/audit-google-business-profile.ts +1 -1
  90. package/src/tools/connect-account.ts +2 -2
  91. package/src/tools/create-connect-session.ts +2 -2
  92. package/src/tools/create-post.ts +3 -3
  93. package/src/tools/delete-post.ts +60 -19
  94. package/src/tools/generate-image.ts +2 -2
  95. package/src/tools/generate-video.ts +1 -1
  96. package/src/tools/get-agent-signup-info.ts +2 -2
  97. package/src/tools/get-post.ts +35 -21
  98. package/src/tools/list-accounts.ts +187 -39
  99. package/src/tools/list-analytics.ts +444 -0
  100. package/src/tools/list-brands.ts +162 -26
  101. package/src/tools/list-comments.ts +86 -43
  102. package/src/tools/list-conversations.ts +81 -39
  103. package/src/tools/list-google-business-media.ts +1 -1
  104. package/src/tools/list-google-business-reviews.ts +93 -64
  105. package/src/tools/list-jobs.ts +91 -0
  106. package/src/tools/list-platforms.ts +135 -11
  107. package/src/tools/manage-comment.ts +142 -0
  108. package/src/tools/manage-conversation.ts +89 -0
  109. package/src/tools/manage-google-business-media.ts +147 -0
  110. package/src/tools/manage-google-business-review.ts +139 -0
  111. package/src/tools/manage-oauth-client.ts +116 -0
  112. package/src/tools/manage-webhook.ts +103 -0
  113. package/src/tools/update-post.ts +106 -39
  114. package/src/tools/upload-media.ts +76 -28
  115. package/src/tools/whoami.ts +253 -30
  116. package/tool-annotations.json +76 -149
  117. package/tool-registry.json +2085 -0
  118. package/src/tools/add-google-business-media.ts +0 -50
  119. package/src/tools/create-oauth-client.ts +0 -36
  120. package/src/tools/delete-comment.ts +0 -29
  121. package/src/tools/delete-google-business-media.ts +0 -30
  122. package/src/tools/delete-google-business-review-reply.ts +0 -30
  123. package/src/tools/delete-oauth-client.ts +0 -23
  124. package/src/tools/delete-post-group.ts +0 -17
  125. package/src/tools/delete-webhook.ts +0 -17
  126. package/src/tools/get-account-analytics.ts +0 -171
  127. package/src/tools/get-brand-profile.ts +0 -86
  128. package/src/tools/get-brand.ts +0 -34
  129. package/src/tools/get-comment.ts +0 -47
  130. package/src/tools/get-connect-link.ts +0 -61
  131. package/src/tools/get-connect-session.ts +0 -63
  132. package/src/tools/get-conversation.ts +0 -44
  133. package/src/tools/get-google-business-review-link.ts +0 -30
  134. package/src/tools/get-image-job.ts +0 -36
  135. package/src/tools/get-learned-voice.ts +0 -31
  136. package/src/tools/get-mcp-status.ts +0 -212
  137. package/src/tools/get-performance-profile.ts +0 -47
  138. package/src/tools/get-platform-schema.ts +0 -56
  139. package/src/tools/get-post-analytics.ts +0 -243
  140. package/src/tools/get-post-insights.ts +0 -60
  141. package/src/tools/get-post-missing.ts +0 -16
  142. package/src/tools/get-video-job.ts +0 -36
  143. package/src/tools/list-brand-accounts.ts +0 -36
  144. package/src/tools/reply-google-business-review.ts +0 -32
  145. package/src/tools/reply-to-comment.ts +0 -30
  146. package/src/tools/send-message.ts +0 -28
  147. package/src/tools/suggest-google-business-review-reply.ts +0 -44
  148. package/src/tools/sync-inbox.ts +0 -28
  149. package/src/tools/test-webhook.ts +0 -18
  150. package/src/tools/update-comment.ts +0 -31
  151. package/src/tools/update-oauth-client.ts +0 -38
  152. package/src/tools/update-post-release-id.ts +0 -25
  153. package/src/tools/update-post-status.ts +0 -31
  154. package/src/tools/update-webhook.ts +0 -36
  155. package/src/tools/upload-media-from-url.ts +0 -33
@@ -0,0 +1,79 @@
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 ManageConversationInput = {
5
+ action: 'send' | 'sync';
6
+ conversation_id?: string;
7
+ content?: string;
8
+ account_id?: number;
9
+ sync_type?: 'all' | 'dm' | 'comments';
10
+ workspace_id?: string;
11
+ confirm?: boolean;
12
+ preview_id?: string;
13
+ };
14
+ export declare const manageConversationTool: {
15
+ name: string;
16
+ description: string;
17
+ inputSchema: z.ZodObject<{
18
+ action: z.ZodEnum<["send", "sync"]>;
19
+ conversation_id: z.ZodOptional<z.ZodString>;
20
+ content: z.ZodOptional<z.ZodString>;
21
+ account_id: z.ZodOptional<z.ZodNumber>;
22
+ sync_type: z.ZodOptional<z.ZodEnum<["all", "dm", "comments"]>>;
23
+ workspace_id: z.ZodOptional<z.ZodString>;
24
+ confirm: z.ZodOptional<z.ZodBoolean>;
25
+ preview_id: z.ZodOptional<z.ZodString>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ action: "send" | "sync";
28
+ confirm?: boolean | undefined;
29
+ preview_id?: string | undefined;
30
+ workspace_id?: string | undefined;
31
+ account_id?: number | undefined;
32
+ content?: string | undefined;
33
+ conversation_id?: string | undefined;
34
+ sync_type?: "all" | "dm" | "comments" | undefined;
35
+ }, {
36
+ action: "send" | "sync";
37
+ confirm?: boolean | undefined;
38
+ preview_id?: string | undefined;
39
+ workspace_id?: string | undefined;
40
+ account_id?: number | undefined;
41
+ content?: string | undefined;
42
+ conversation_id?: string | undefined;
43
+ sync_type?: "all" | "dm" | "comments" | undefined;
44
+ }>;
45
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
46
+ legacySchemas: {
47
+ send_message: z.ZodObject<{
48
+ conversation_id: z.ZodString;
49
+ content: z.ZodString;
50
+ workspace_id: z.ZodOptional<z.ZodString>;
51
+ confirm: z.ZodLiteral<true>;
52
+ }, "strip", z.ZodTypeAny, {
53
+ confirm: true;
54
+ content: string;
55
+ conversation_id: string;
56
+ workspace_id?: string | undefined;
57
+ }, {
58
+ confirm: true;
59
+ content: string;
60
+ conversation_id: string;
61
+ workspace_id?: string | undefined;
62
+ }>;
63
+ sync_inbox: z.ZodObject<{
64
+ account_id: z.ZodNumber;
65
+ sync_type: z.ZodOptional<z.ZodEnum<["all", "dm", "comments"]>>;
66
+ workspace_id: z.ZodOptional<z.ZodString>;
67
+ }, "strip", z.ZodTypeAny, {
68
+ account_id: number;
69
+ workspace_id?: string | undefined;
70
+ sync_type?: "all" | "dm" | "comments" | undefined;
71
+ }, {
72
+ account_id: number;
73
+ workspace_id?: string | undefined;
74
+ sync_type?: "all" | "dm" | "comments" | undefined;
75
+ }>;
76
+ };
77
+ execute(client: PosterlyClient, input: ManageConversationInput, ctx: ToolContext): Promise<string>;
78
+ };
79
+ export {};
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ import { code, mdKeyValue, mdQuote, mdSection } from '../lib/format.js';
3
+ import { omitKeys, previewThenConfirm, readFailedNote, readForPreview, validateArgs } from '../lib/tool-runtime.js';
4
+ export const manageConversationTool = {
5
+ name: 'manage_conversation',
6
+ description: 'Act on the social inbox (Pro plan or higher plus posts:write). action=send (conversation_id, content): send a DM reply in an Instagram or Facebook Page conversation, honoring the 24-hour messaging window (Threads has no DMs); preview then confirm: call without confirm to get a preview and preview_id, show it, then call again with confirm=true and that preview_id. action=sync (account_id): pull the latest Instagram, Facebook Page, Threads, or LinkedIn inbox data for one account; 30-second cooldown (60 seconds for LinkedIn); Threads and LinkedIn sync comments only.',
7
+ inputSchema: z.object({
8
+ action: z.enum(['send', 'sync']).describe('send or sync.'),
9
+ conversation_id: z.string().min(1).optional().describe('send: conversation ID from list_conversations.'),
10
+ content: z.string().min(1).optional().describe('send: message text to send.'),
11
+ account_id: z.number().int().positive().optional().describe('sync: social account ID from list_accounts.'),
12
+ sync_type: z.enum(['all', 'dm', 'comments']).optional().describe('sync: what to sync. Defaults to all. Threads and LinkedIn are comments-only.'),
13
+ workspace_id: z.string().optional().describe('Optional. Omit to search every workspace you belong to; pass a workspace ID from whoami to limit to one.'),
14
+ confirm: z.boolean().optional().describe('send: leave out to get a preview; true only with the preview_id after the user approved it.'),
15
+ preview_id: z.string().optional().describe('send: the preview_id from the preview call. Required with confirm: true.'),
16
+ }),
17
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
18
+ legacySchemas: {
19
+ send_message: z.object({
20
+ conversation_id: z.string().min(1),
21
+ content: z.string().min(1),
22
+ workspace_id: z.string().optional(),
23
+ confirm: z.literal(true),
24
+ }),
25
+ sync_inbox: z.object({
26
+ account_id: z.number().int().positive(),
27
+ sync_type: z.enum(['all', 'dm', 'comments']).optional(),
28
+ workspace_id: z.string().optional(),
29
+ }),
30
+ },
31
+ async execute(client, input, ctx) {
32
+ const { mode, args } = validateArgs(ctx, 'manage_conversation', input);
33
+ if (mode === 'sync')
34
+ return syncInbox(client, args);
35
+ return previewThenConfirm({
36
+ tool: 'manage_conversation',
37
+ action: 'send',
38
+ args,
39
+ nonce: true,
40
+ legacyRefusal: 'Refusing to send an inbox message without confirm=true after explicit user confirmation.',
41
+ ctx,
42
+ describe: async () => {
43
+ const read = await readForPreview(() => client.getConversation({ conversation_id: args.conversation_id, workspace_id: args.workspace_id, limit: 3 }));
44
+ const header = read.ok
45
+ ? mdSection('Conversation', mdKeyValue([
46
+ ['Conversation ID', code(String(read.data.conversation.id))],
47
+ ['Platform', typeof read.data.conversation.platform === 'string' ? read.data.conversation.platform : 'unknown'],
48
+ ['Participant', typeof read.data.conversation.participant_username === 'string' ? `@${read.data.conversation.participant_username}` : 'unknown'],
49
+ ]))
50
+ : [readFailedNote(read.error), mdKeyValue([['Conversation ID', code(args.conversation_id)]])].join('\n\n');
51
+ return [
52
+ header,
53
+ mdSection('Message to send', mdQuote(String(args.content ?? ''))),
54
+ 'This sends a DM as your account. Instagram and Facebook only allow replies within 24 hours of the person\'s last message.',
55
+ ].join('\n\n');
56
+ },
57
+ run: async () => {
58
+ const result = await client.sendMessage({
59
+ conversation_id: args.conversation_id,
60
+ content: args.content,
61
+ workspace_id: args.workspace_id,
62
+ });
63
+ return `Message sent (message_id ${result.message_id}).`;
64
+ },
65
+ });
66
+ },
67
+ };
68
+ async function syncInbox(client, input) {
69
+ const result = await client.syncInbox(omitKeys(input, ['action', 'confirm', 'preview_id']));
70
+ const parts = ['Inbox sync completed.'];
71
+ const keys = Object.keys(result.results || {});
72
+ if (keys.length > 0)
73
+ parts.push(`Synced: ${keys.join(', ')}.`);
74
+ return parts.join(' ');
75
+ }
@@ -0,0 +1,103 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ import { type ToolContext } from '../lib/tool-runtime.js';
4
+ declare const CATEGORIES: readonly ["COVER", "PROFILE", "LOGO", "EXTERIOR", "INTERIOR", "PRODUCT", "AT_WORK", "FOOD_AND_DRINK", "MENU", "COMMON_AREA", "ROOMS", "TEAMS", "ADDITIONAL"];
5
+ type ManageMediaInput = {
6
+ action: 'add' | 'delete';
7
+ workspace_id?: string;
8
+ account_id?: number;
9
+ location_id?: string;
10
+ source_url?: string;
11
+ category?: (typeof CATEGORIES)[number];
12
+ media_format?: 'PHOTO' | 'VIDEO';
13
+ media_name?: string;
14
+ confirm?: boolean;
15
+ preview_id?: string;
16
+ };
17
+ export declare const manageGoogleBusinessMediaTool: {
18
+ name: string;
19
+ description: string;
20
+ inputSchema: z.ZodObject<{
21
+ action: z.ZodEnum<["add", "delete"]>;
22
+ workspace_id: z.ZodOptional<z.ZodString>;
23
+ account_id: z.ZodOptional<z.ZodNumber>;
24
+ location_id: z.ZodOptional<z.ZodString>;
25
+ source_url: z.ZodOptional<z.ZodString>;
26
+ category: z.ZodOptional<z.ZodEnum<["COVER", "PROFILE", "LOGO", "EXTERIOR", "INTERIOR", "PRODUCT", "AT_WORK", "FOOD_AND_DRINK", "MENU", "COMMON_AREA", "ROOMS", "TEAMS", "ADDITIONAL"]>>;
27
+ media_format: z.ZodOptional<z.ZodEnum<["PHOTO", "VIDEO"]>>;
28
+ media_name: z.ZodOptional<z.ZodString>;
29
+ confirm: z.ZodOptional<z.ZodBoolean>;
30
+ preview_id: z.ZodOptional<z.ZodString>;
31
+ }, "strip", z.ZodTypeAny, {
32
+ action: "delete" | "add";
33
+ confirm?: boolean | undefined;
34
+ preview_id?: string | undefined;
35
+ workspace_id?: string | undefined;
36
+ account_id?: number | undefined;
37
+ location_id?: string | undefined;
38
+ category?: "COVER" | "PROFILE" | "LOGO" | "EXTERIOR" | "INTERIOR" | "PRODUCT" | "AT_WORK" | "FOOD_AND_DRINK" | "MENU" | "COMMON_AREA" | "ROOMS" | "TEAMS" | "ADDITIONAL" | undefined;
39
+ source_url?: string | undefined;
40
+ media_format?: "PHOTO" | "VIDEO" | undefined;
41
+ media_name?: string | undefined;
42
+ }, {
43
+ action: "delete" | "add";
44
+ confirm?: boolean | undefined;
45
+ preview_id?: string | undefined;
46
+ workspace_id?: string | undefined;
47
+ account_id?: number | undefined;
48
+ location_id?: string | undefined;
49
+ category?: "COVER" | "PROFILE" | "LOGO" | "EXTERIOR" | "INTERIOR" | "PRODUCT" | "AT_WORK" | "FOOD_AND_DRINK" | "MENU" | "COMMON_AREA" | "ROOMS" | "TEAMS" | "ADDITIONAL" | undefined;
50
+ source_url?: string | undefined;
51
+ media_format?: "PHOTO" | "VIDEO" | undefined;
52
+ media_name?: string | undefined;
53
+ }>;
54
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
55
+ legacySchemas: {
56
+ add_google_business_media: z.ZodObject<{
57
+ source_url: z.ZodString;
58
+ category: z.ZodEnum<["COVER", "PROFILE", "LOGO", "EXTERIOR", "INTERIOR", "PRODUCT", "AT_WORK", "FOOD_AND_DRINK", "MENU", "COMMON_AREA", "ROOMS", "TEAMS", "ADDITIONAL"]>;
59
+ media_format: z.ZodOptional<z.ZodEnum<["PHOTO", "VIDEO"]>>;
60
+ confirm: z.ZodLiteral<true>;
61
+ workspace_id: z.ZodOptional<z.ZodString>;
62
+ account_id: z.ZodOptional<z.ZodNumber>;
63
+ location_id: z.ZodOptional<z.ZodString>;
64
+ }, "strip", z.ZodTypeAny, {
65
+ confirm: true;
66
+ category: "COVER" | "PROFILE" | "LOGO" | "EXTERIOR" | "INTERIOR" | "PRODUCT" | "AT_WORK" | "FOOD_AND_DRINK" | "MENU" | "COMMON_AREA" | "ROOMS" | "TEAMS" | "ADDITIONAL";
67
+ source_url: string;
68
+ workspace_id?: string | undefined;
69
+ account_id?: number | undefined;
70
+ location_id?: string | undefined;
71
+ media_format?: "PHOTO" | "VIDEO" | undefined;
72
+ }, {
73
+ confirm: true;
74
+ category: "COVER" | "PROFILE" | "LOGO" | "EXTERIOR" | "INTERIOR" | "PRODUCT" | "AT_WORK" | "FOOD_AND_DRINK" | "MENU" | "COMMON_AREA" | "ROOMS" | "TEAMS" | "ADDITIONAL";
75
+ source_url: string;
76
+ workspace_id?: string | undefined;
77
+ account_id?: number | undefined;
78
+ location_id?: string | undefined;
79
+ media_format?: "PHOTO" | "VIDEO" | undefined;
80
+ }>;
81
+ delete_google_business_media: z.ZodObject<{
82
+ media_name: z.ZodString;
83
+ confirm: z.ZodLiteral<true>;
84
+ workspace_id: z.ZodOptional<z.ZodString>;
85
+ account_id: z.ZodOptional<z.ZodNumber>;
86
+ location_id: z.ZodOptional<z.ZodString>;
87
+ }, "strip", z.ZodTypeAny, {
88
+ confirm: true;
89
+ media_name: string;
90
+ workspace_id?: string | undefined;
91
+ account_id?: number | undefined;
92
+ location_id?: string | undefined;
93
+ }, {
94
+ confirm: true;
95
+ media_name: string;
96
+ workspace_id?: string | undefined;
97
+ account_id?: number | undefined;
98
+ location_id?: string | undefined;
99
+ }>;
100
+ };
101
+ execute(client: PosterlyClient, input: ManageMediaInput, ctx: ToolContext): Promise<string>;
102
+ };
103
+ export {};
@@ -0,0 +1,126 @@
1
+ import { z } from 'zod';
2
+ import { code, mdKeyValue, mdSection } from '../lib/format.js';
3
+ import { omitKeys, previewThenConfirm, readFailedNote, readForPreview, validateArgs } from '../lib/tool-runtime.js';
4
+ const CATEGORIES = [
5
+ 'COVER',
6
+ 'PROFILE',
7
+ 'LOGO',
8
+ 'EXTERIOR',
9
+ 'INTERIOR',
10
+ 'PRODUCT',
11
+ 'AT_WORK',
12
+ 'FOOD_AND_DRINK',
13
+ 'MENU',
14
+ 'COMMON_AREA',
15
+ 'ROOMS',
16
+ 'TEAMS',
17
+ 'ADDITIONAL',
18
+ ];
19
+ const LOCATION_ID_DESCRIPTION = 'Google Business numeric location id (the location_id/platform_user_id from list_accounts, e.g. "197940849675145390"). NOT the ChIJ... Place ID from list_google_business_reviews view=review_link. Prefer account_id.';
20
+ const LEGACY_LOCATION = {
21
+ workspace_id: z.string().optional(),
22
+ account_id: z.number().int().positive().optional(),
23
+ location_id: z.string().optional(),
24
+ };
25
+ function payloadOf(input) {
26
+ return omitKeys(input, ['action', 'confirm', 'preview_id']);
27
+ }
28
+ function locationRows(input) {
29
+ return [
30
+ ['Account ID', input.account_id !== undefined ? code(input.account_id) : undefined],
31
+ ['Location ID', input.location_id ? code(input.location_id) : undefined],
32
+ ['Workspace', input.workspace_id ? code(input.workspace_id) : undefined],
33
+ ];
34
+ }
35
+ export const manageGoogleBusinessMediaTool = {
36
+ name: 'manage_google_business_media',
37
+ description: 'Change a Google Business Profile gallery (the photos and videos shown on Maps and Search). action=add (source_url, category): add a photo or video from a public https URL (upload it with upload_media first); COVER and PROFILE are single-slot and replace the existing one. action=delete (media_name from list_google_business_media): permanently remove an item from the public profile. Both use preview then confirm: call without confirm to get a preview and preview_id, show it, then call again with confirm=true and that preview_id.',
38
+ inputSchema: z.object({
39
+ action: z.enum(['add', 'delete']).describe('add or delete.'),
40
+ workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
41
+ account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts.'),
42
+ location_id: z.string().optional().describe(LOCATION_ID_DESCRIPTION),
43
+ source_url: z.string().url().optional().describe('add: public https URL of the photo or video (from upload_media).'),
44
+ category: z.enum(CATEGORIES).optional().describe('add: which gallery category to add the media to.'),
45
+ media_format: z.enum(['PHOTO', 'VIDEO']).optional().describe('add: defaults to PHOTO. Use VIDEO for videos (Google caps profile videos at 30s / 75MB).'),
46
+ media_name: z.string().min(1).optional().describe('delete: full media resource name from list_google_business_media (accounts/.../locations/.../media/...).'),
47
+ confirm: z.boolean().optional().describe('Leave out to get a preview; true only with the preview_id after the user approved it.'),
48
+ preview_id: z.string().optional().describe('The preview_id from the preview call. Required with confirm: true.'),
49
+ }),
50
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
51
+ legacySchemas: {
52
+ add_google_business_media: z.object({
53
+ ...LEGACY_LOCATION,
54
+ source_url: z.string().url(),
55
+ category: z.enum(CATEGORIES),
56
+ media_format: z.enum(['PHOTO', 'VIDEO']).optional(),
57
+ confirm: z.literal(true),
58
+ }),
59
+ delete_google_business_media: z.object({
60
+ ...LEGACY_LOCATION,
61
+ media_name: z.string().min(1),
62
+ confirm: z.literal(true),
63
+ }),
64
+ },
65
+ async execute(client, input, ctx) {
66
+ const { mode, args } = validateArgs(ctx, 'manage_google_business_media', input);
67
+ const call = args;
68
+ if (mode === 'delete') {
69
+ return previewThenConfirm({
70
+ tool: 'manage_google_business_media',
71
+ action: 'delete',
72
+ args,
73
+ nonce: false,
74
+ legacyRefusal: 'Refusing to delete Google Business media without confirm=true after explicit user confirmation.',
75
+ ctx,
76
+ describe: async () => {
77
+ const read = await readForPreview(() => client.listGoogleBusinessMedia({
78
+ workspace_id: call.workspace_id,
79
+ account_id: call.account_id,
80
+ location_id: call.location_id,
81
+ }));
82
+ const item = read.ok ? (read.data.media || []).find((media) => media?.name === call.media_name) : null;
83
+ const details = item
84
+ ? mdSection('Media', mdKeyValue([
85
+ ['Resource name', code(String(item.name))],
86
+ ['Type', item.mediaFormat === 'VIDEO' ? 'video' : 'photo'],
87
+ ['Category', item.category || 'uncategorised'],
88
+ ...locationRows(call),
89
+ ]))
90
+ : [
91
+ read.ok ? '_This item was not found in the current gallery listing, so the preview shows the request as given._' : readFailedNote(read.error),
92
+ mdKeyValue([['Resource name', code(String(call.media_name))], ...locationRows(call)]),
93
+ ].join('\n\n');
94
+ return [details, 'This permanently removes the item from the public Google Business profile.'].join('\n\n');
95
+ },
96
+ run: async () => {
97
+ const result = await client.deleteGoogleBusinessMedia(payloadOf(call));
98
+ return result.message || 'Media removed from the Google Business profile.';
99
+ },
100
+ });
101
+ }
102
+ return previewThenConfirm({
103
+ tool: 'manage_google_business_media',
104
+ action: 'add',
105
+ args,
106
+ nonce: true,
107
+ legacyRefusal: 'Refusing to add Google Business media without confirm=true after explicit user confirmation.',
108
+ ctx,
109
+ describe: async () => [
110
+ mdSection('Media to add', mdKeyValue([
111
+ ['Source URL', call.source_url],
112
+ ['Category', call.category],
113
+ ['Format', call.media_format || 'PHOTO'],
114
+ ...locationRows(call),
115
+ ])),
116
+ call.category === 'COVER' || call.category === 'PROFILE'
117
+ ? `This adds the item publicly to the Google Business profile and replaces the current ${String(call.category).toLowerCase()} image.`
118
+ : 'This adds the item publicly to the Google Business profile gallery on Maps and Search.',
119
+ ].join('\n\n'),
120
+ run: async () => {
121
+ const result = await client.addGoogleBusinessMedia(payloadOf(call));
122
+ return result.message || 'Media added to the Google Business profile.';
123
+ },
124
+ });
125
+ },
126
+ };
@@ -0,0 +1,146 @@
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 ManageReviewInput = {
5
+ action: 'suggest_reply' | 'reply' | 'delete_reply';
6
+ workspace_id?: string;
7
+ account_id?: number;
8
+ location_id?: string;
9
+ review_name?: string;
10
+ comment?: string;
11
+ review_text?: string;
12
+ star_rating?: number;
13
+ reviewer_name?: string;
14
+ business_name?: string;
15
+ tone?: 'professional' | 'friendly' | 'apologetic';
16
+ count?: number;
17
+ confirm?: boolean;
18
+ preview_id?: string;
19
+ };
20
+ export declare const manageGoogleBusinessReviewTool: {
21
+ name: string;
22
+ description: string;
23
+ inputSchema: z.ZodObject<{
24
+ action: z.ZodEnum<["suggest_reply", "reply", "delete_reply"]>;
25
+ workspace_id: z.ZodOptional<z.ZodString>;
26
+ account_id: z.ZodOptional<z.ZodNumber>;
27
+ location_id: z.ZodOptional<z.ZodString>;
28
+ review_name: z.ZodOptional<z.ZodString>;
29
+ comment: z.ZodOptional<z.ZodString>;
30
+ review_text: z.ZodOptional<z.ZodString>;
31
+ star_rating: z.ZodOptional<z.ZodNumber>;
32
+ reviewer_name: z.ZodOptional<z.ZodString>;
33
+ business_name: z.ZodOptional<z.ZodString>;
34
+ tone: z.ZodOptional<z.ZodEnum<["professional", "friendly", "apologetic"]>>;
35
+ count: z.ZodOptional<z.ZodNumber>;
36
+ confirm: z.ZodOptional<z.ZodBoolean>;
37
+ preview_id: z.ZodOptional<z.ZodString>;
38
+ }, "strip", z.ZodTypeAny, {
39
+ action: "suggest_reply" | "reply" | "delete_reply";
40
+ confirm?: boolean | undefined;
41
+ preview_id?: string | undefined;
42
+ workspace_id?: string | undefined;
43
+ account_id?: number | undefined;
44
+ count?: number | undefined;
45
+ location_id?: string | undefined;
46
+ comment?: string | undefined;
47
+ tone?: "professional" | "friendly" | "apologetic" | undefined;
48
+ review_name?: string | undefined;
49
+ review_text?: string | undefined;
50
+ star_rating?: number | undefined;
51
+ reviewer_name?: string | undefined;
52
+ business_name?: string | undefined;
53
+ }, {
54
+ action: "suggest_reply" | "reply" | "delete_reply";
55
+ confirm?: boolean | undefined;
56
+ preview_id?: string | undefined;
57
+ workspace_id?: string | undefined;
58
+ account_id?: number | undefined;
59
+ count?: number | undefined;
60
+ location_id?: string | undefined;
61
+ comment?: string | undefined;
62
+ tone?: "professional" | "friendly" | "apologetic" | undefined;
63
+ review_name?: string | undefined;
64
+ review_text?: string | undefined;
65
+ star_rating?: number | undefined;
66
+ reviewer_name?: string | undefined;
67
+ business_name?: string | undefined;
68
+ }>;
69
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
70
+ legacySchemas: {
71
+ suggest_google_business_review_reply: z.ZodObject<{
72
+ review_text: z.ZodOptional<z.ZodString>;
73
+ star_rating: z.ZodNumber;
74
+ reviewer_name: z.ZodOptional<z.ZodString>;
75
+ business_name: z.ZodOptional<z.ZodString>;
76
+ tone: z.ZodOptional<z.ZodEnum<["professional", "friendly", "apologetic"]>>;
77
+ count: z.ZodOptional<z.ZodNumber>;
78
+ workspace_id: z.ZodOptional<z.ZodString>;
79
+ account_id: z.ZodOptional<z.ZodNumber>;
80
+ location_id: z.ZodOptional<z.ZodString>;
81
+ }, "strip", z.ZodTypeAny, {
82
+ star_rating: number;
83
+ workspace_id?: string | undefined;
84
+ account_id?: number | undefined;
85
+ count?: number | undefined;
86
+ location_id?: string | undefined;
87
+ tone?: "professional" | "friendly" | "apologetic" | undefined;
88
+ review_text?: string | undefined;
89
+ reviewer_name?: string | undefined;
90
+ business_name?: string | undefined;
91
+ }, {
92
+ star_rating: number;
93
+ workspace_id?: string | undefined;
94
+ account_id?: number | undefined;
95
+ count?: number | undefined;
96
+ location_id?: string | undefined;
97
+ tone?: "professional" | "friendly" | "apologetic" | undefined;
98
+ review_text?: string | undefined;
99
+ reviewer_name?: string | undefined;
100
+ business_name?: string | undefined;
101
+ }>;
102
+ reply_google_business_review: z.ZodObject<{
103
+ review_name: z.ZodString;
104
+ comment: z.ZodString;
105
+ confirm: z.ZodLiteral<true>;
106
+ workspace_id: z.ZodOptional<z.ZodString>;
107
+ account_id: z.ZodOptional<z.ZodNumber>;
108
+ location_id: z.ZodOptional<z.ZodString>;
109
+ }, "strip", z.ZodTypeAny, {
110
+ confirm: true;
111
+ comment: string;
112
+ review_name: string;
113
+ workspace_id?: string | undefined;
114
+ account_id?: number | undefined;
115
+ location_id?: string | undefined;
116
+ }, {
117
+ confirm: true;
118
+ comment: string;
119
+ review_name: string;
120
+ workspace_id?: string | undefined;
121
+ account_id?: number | undefined;
122
+ location_id?: string | undefined;
123
+ }>;
124
+ delete_google_business_review_reply: z.ZodObject<{
125
+ review_name: z.ZodString;
126
+ confirm: z.ZodLiteral<true>;
127
+ workspace_id: z.ZodOptional<z.ZodString>;
128
+ account_id: z.ZodOptional<z.ZodNumber>;
129
+ location_id: z.ZodOptional<z.ZodString>;
130
+ }, "strip", z.ZodTypeAny, {
131
+ confirm: true;
132
+ review_name: string;
133
+ workspace_id?: string | undefined;
134
+ account_id?: number | undefined;
135
+ location_id?: string | undefined;
136
+ }, {
137
+ confirm: true;
138
+ review_name: string;
139
+ workspace_id?: string | undefined;
140
+ account_id?: number | undefined;
141
+ location_id?: string | undefined;
142
+ }>;
143
+ };
144
+ execute(client: PosterlyClient, input: ManageReviewInput, ctx: ToolContext): Promise<string>;
145
+ };
146
+ export {};
@@ -0,0 +1,115 @@
1
+ import { z } from 'zod';
2
+ import { code, mdKeyValue, mdQuote, mdSection } from '../lib/format.js';
3
+ import { omitKeys, previewThenConfirm, validateArgs } from '../lib/tool-runtime.js';
4
+ const LOCATION_ID_DESCRIPTION = 'Google Business numeric location id (the location_id/platform_user_id from list_accounts, e.g. "197940849675145390"). NOT the ChIJ... Place ID from list_google_business_reviews view=review_link. Prefer account_id.';
5
+ const LEGACY_LOCATION = {
6
+ workspace_id: z.string().optional(),
7
+ account_id: z.number().int().positive().optional(),
8
+ location_id: z.string().optional(),
9
+ };
10
+ /** Everything the review endpoints accept, without the selector and confirm fields. */
11
+ function payloadOf(input) {
12
+ return omitKeys(input, ['action', 'confirm', 'preview_id']);
13
+ }
14
+ function locationRows(input) {
15
+ return [
16
+ ['Account ID', input.account_id !== undefined ? code(input.account_id) : undefined],
17
+ ['Location ID', input.location_id ? code(input.location_id) : undefined],
18
+ ['Workspace', input.workspace_id ? code(input.workspace_id) : undefined],
19
+ ];
20
+ }
21
+ export const manageGoogleBusinessReviewTool = {
22
+ name: 'manage_google_business_review',
23
+ description: 'Act on Google Business Profile reviews. action=suggest_reply (star_rating, optional review_text): short, brand-aware AI reply suggestions; uses the AI Caption Assist allowance and never posts. action=reply (review_name, comment): post or update the owner reply. action=delete_reply (review_name): delete the owner reply. reply and delete_reply use preview then confirm: call without confirm to get a preview and preview_id (it repeats the review name, location, and exact reply text; show the reviewer, rating, and review text from list_google_business_reviews alongside it), then call again with confirm=true and that preview_id.',
24
+ inputSchema: z.object({
25
+ action: z.enum(['suggest_reply', 'reply', 'delete_reply']).describe('suggest_reply, reply, or delete_reply.'),
26
+ workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
27
+ account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts (optional brand voice context for suggest_reply).'),
28
+ location_id: z.string().optional().describe(LOCATION_ID_DESCRIPTION),
29
+ review_name: z.string().min(1).optional().describe('reply and delete_reply: full Google review resource name.'),
30
+ comment: z.string().min(1).max(4096).optional().describe('reply: exact owner reply text to post.'),
31
+ review_text: z.string().optional().describe('suggest_reply: review text. Leave empty for rating-only reviews.'),
32
+ star_rating: z.number().min(1).max(5).optional().describe('suggest_reply: review rating from 1 to 5.'),
33
+ reviewer_name: z.string().optional(),
34
+ business_name: z.string().optional(),
35
+ tone: z.enum(['professional', 'friendly', 'apologetic']).optional(),
36
+ count: z.number().int().min(1).max(5).optional(),
37
+ confirm: z.boolean().optional().describe('reply and delete_reply: leave out to get a preview; true only with the preview_id after the user approved it.'),
38
+ preview_id: z.string().optional().describe('reply and delete_reply: the preview_id from the preview call. Required with confirm: true.'),
39
+ }),
40
+ /** The old tools' exact input schemas, for alias calls (see alias-transport.ts). */
41
+ legacySchemas: {
42
+ suggest_google_business_review_reply: z.object({
43
+ ...LEGACY_LOCATION,
44
+ review_text: z.string().optional(),
45
+ star_rating: z.number().min(1).max(5),
46
+ reviewer_name: z.string().optional(),
47
+ business_name: z.string().optional(),
48
+ tone: z.enum(['professional', 'friendly', 'apologetic']).optional(),
49
+ count: z.number().int().min(1).max(5).optional(),
50
+ }),
51
+ reply_google_business_review: z.object({
52
+ ...LEGACY_LOCATION,
53
+ review_name: z.string().min(1),
54
+ comment: z.string().min(1).max(4096),
55
+ confirm: z.literal(true),
56
+ }),
57
+ delete_google_business_review_reply: z.object({
58
+ ...LEGACY_LOCATION,
59
+ review_name: z.string().min(1),
60
+ confirm: z.literal(true),
61
+ }),
62
+ },
63
+ async execute(client, input, ctx) {
64
+ const { mode, args } = validateArgs(ctx, 'manage_google_business_review', input);
65
+ const call = args;
66
+ if (mode === 'suggest_reply')
67
+ return suggestReply(client, call);
68
+ if (mode === 'delete_reply') {
69
+ return previewThenConfirm({
70
+ tool: 'manage_google_business_review',
71
+ action: 'delete_reply',
72
+ args,
73
+ nonce: false,
74
+ legacyRefusal: 'Refusing to delete a Google review reply without confirm=true after explicit user confirmation.',
75
+ ctx,
76
+ describe: async () => [
77
+ mdSection('Review', mdKeyValue([['Review', code(String(call.review_name))], ...locationRows(call)])),
78
+ 'This deletes the owner reply from this review on Google. The review itself stays.',
79
+ ].join('\n\n'),
80
+ run: async () => {
81
+ const result = await client.deleteGoogleBusinessReviewReply(payloadOf(call));
82
+ return result.message || 'Google Business review reply deleted successfully.';
83
+ },
84
+ });
85
+ }
86
+ return previewThenConfirm({
87
+ tool: 'manage_google_business_review',
88
+ action: 'reply',
89
+ args,
90
+ nonce: true,
91
+ legacyRefusal: 'Refusing to post a Google review reply without confirm=true after explicit user confirmation.',
92
+ ctx,
93
+ describe: async () => [
94
+ mdSection('Review', mdKeyValue([['Review', code(String(call.review_name))], ...locationRows(call)])),
95
+ mdSection('Owner reply to post', mdQuote(String(call.comment ?? ''))),
96
+ 'This posts publicly on Google as the business owner and replaces any existing owner reply on this review.',
97
+ ].join('\n\n'),
98
+ run: async () => {
99
+ const result = await client.replyGoogleBusinessReview(payloadOf(call));
100
+ return result.message || 'Google Business review reply posted successfully.';
101
+ },
102
+ });
103
+ },
104
+ };
105
+ async function suggestReply(client, input) {
106
+ const result = await client.suggestGoogleBusinessReviewReply(payloadOf(input));
107
+ const suggestions = Array.isArray(result.suggestions) ? result.suggestions : [];
108
+ if (suggestions.length === 0) {
109
+ return 'No reply suggestions were generated.';
110
+ }
111
+ return [
112
+ `Suggested replies (${result.tone || 'professional'}, ${result.rating || input.star_rating} stars):`,
113
+ ...suggestions.map((text, index) => `${index + 1}. ${text}`),
114
+ ].join('\n');
115
+ }