posterly-mcp-server 0.19.2 → 0.19.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/index.js +49 -50
- package/dist/lib/api-client.d.ts +4 -0
- package/dist/tools/audit-google-business-profile.js +12 -15
- package/dist/tools/create-oauth-client.js +1 -7
- package/dist/tools/create-post.js +27 -20
- package/dist/tools/create-posts-batch.js +15 -23
- package/dist/tools/create-signed-upload.js +7 -10
- package/dist/tools/create-webhook.js +2 -7
- package/dist/tools/delete-google-business-review-reply.d.ts +1 -1
- package/dist/tools/delete-google-business-review-reply.js +1 -4
- package/dist/tools/delete-oauth-client.js +1 -4
- package/dist/tools/delete-post-group.js +1 -5
- package/dist/tools/delete-post.js +1 -4
- package/dist/tools/delete-webhook.js +1 -4
- package/dist/tools/disconnect-account.js +5 -7
- package/dist/tools/find-slot.js +3 -10
- package/dist/tools/generate-image.js +19 -17
- package/dist/tools/generate-video.js +9 -11
- package/dist/tools/get-account-analytics.js +61 -64
- package/dist/tools/get-brand-profile.js +40 -34
- package/dist/tools/get-brand.js +16 -14
- package/dist/tools/get-connect-link.js +7 -26
- package/dist/tools/get-google-business-review-link.js +6 -6
- package/dist/tools/get-platform-schema.js +16 -23
- package/dist/tools/get-post-analytics.js +50 -53
- package/dist/tools/get-post-missing.js +1 -5
- package/dist/tools/get-post.js +14 -16
- package/dist/tools/get-video-job.d.ts +2 -2
- package/dist/tools/get-video-job.js +11 -28
- package/dist/tools/get-video-options.js +15 -21
- package/dist/tools/get-x-posting-quota.js +9 -12
- package/dist/tools/list-accounts.js +4 -12
- package/dist/tools/list-activity.js +18 -13
- package/dist/tools/list-brand-accounts.js +3 -11
- package/dist/tools/list-brands.js +11 -12
- package/dist/tools/list-google-business-reviews.js +11 -13
- package/dist/tools/list-oauth-clients.js +4 -10
- package/dist/tools/list-platforms.js +5 -12
- package/dist/tools/list-posts.js +7 -12
- package/dist/tools/list-webhooks.js +11 -13
- package/dist/tools/reply-google-business-review.d.ts +1 -1
- package/dist/tools/reply-google-business-review.js +1 -4
- package/dist/tools/run-video-function.js +4 -4
- package/dist/tools/suggest-google-business-review-reply.js +4 -5
- package/dist/tools/test-webhook.js +1 -7
- package/dist/tools/trigger-platform-helper.js +1 -5
- package/dist/tools/update-oauth-client.js +1 -8
- package/dist/tools/update-post-release-id.js +1 -5
- package/dist/tools/update-post-status.d.ts +2 -2
- package/dist/tools/update-post-status.js +1 -7
- package/dist/tools/update-post.js +4 -10
- package/dist/tools/update-webhook.js +1 -7
- package/dist/tools/upload-media-from-url.js +9 -7
- package/dist/tools/upload-media.js +2 -6
- package/dist/tools/whoami.js +17 -18
- package/package.json +1 -1
- package/src/index.ts +49 -50
- package/src/lib/api-client.ts +1 -0
- package/src/tools/audit-google-business-profile.ts +12 -18
- package/src/tools/create-oauth-client.ts +2 -8
- package/src/tools/create-post.ts +28 -20
- package/src/tools/create-posts-batch.ts +18 -23
- package/src/tools/create-signed-upload.ts +7 -10
- package/src/tools/create-webhook.ts +3 -7
- package/src/tools/delete-google-business-review-reply.ts +1 -4
- package/src/tools/delete-oauth-client.ts +1 -4
- package/src/tools/delete-post-group.ts +1 -5
- package/src/tools/delete-post.ts +1 -4
- package/src/tools/delete-webhook.ts +2 -4
- package/src/tools/disconnect-account.ts +5 -7
- package/src/tools/find-slot.ts +5 -13
- package/src/tools/generate-image.ts +20 -20
- package/src/tools/generate-video.ts +9 -11
- package/src/tools/get-account-analytics.ts +67 -67
- package/src/tools/get-brand-profile.ts +38 -34
- package/src/tools/get-brand.ts +14 -14
- package/src/tools/get-connect-link.ts +7 -29
- package/src/tools/get-google-business-review-link.ts +6 -6
- package/src/tools/get-platform-schema.ts +16 -29
- package/src/tools/get-post-analytics.ts +51 -58
- package/src/tools/get-post-missing.ts +2 -5
- package/src/tools/get-post.ts +13 -16
- package/src/tools/get-video-job.ts +11 -31
- package/src/tools/get-video-options.ts +15 -27
- package/src/tools/get-x-posting-quota.ts +9 -12
- package/src/tools/list-accounts.ts +6 -15
- package/src/tools/list-activity.ts +20 -16
- package/src/tools/list-brand-accounts.ts +6 -14
- package/src/tools/list-brands.ts +13 -16
- package/src/tools/list-google-business-reviews.ts +12 -16
- package/src/tools/list-oauth-clients.ts +4 -13
- package/src/tools/list-platforms.ts +5 -15
- package/src/tools/list-posts.ts +9 -15
- package/src/tools/list-webhooks.ts +12 -16
- package/src/tools/reply-google-business-review.ts +1 -4
- package/src/tools/run-video-function.ts +4 -4
- package/src/tools/suggest-google-business-review-reply.ts +4 -5
- package/src/tools/test-webhook.ts +1 -7
- package/src/tools/trigger-platform-helper.ts +1 -5
- package/src/tools/update-oauth-client.ts +2 -9
- package/src/tools/update-post-release-id.ts +2 -5
- package/src/tools/update-post-status.ts +1 -7
- package/src/tools/update-post.ts +5 -10
- package/src/tools/update-webhook.ts +2 -7
- package/src/tools/upload-media-from-url.ts +9 -7
- package/src/tools/upload-media.ts +2 -6
- package/src/tools/whoami.ts +23 -21
- package/dist/lib/format.d.ts +0 -21
- package/dist/lib/format.js +0 -125
- package/src/lib/format.ts +0 -132
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { code, formatBytes, mdSuccess } from '../lib/format.js';
|
|
3
2
|
export const createSignedUploadTool = {
|
|
4
3
|
name: 'create_signed_upload',
|
|
5
4
|
description: 'Create a signed upload URL for a larger image or video. Upload the binary to upload_url; the API validates actual bytes before storage, then public_url can be used with create_post.',
|
|
@@ -10,14 +9,12 @@ export const createSignedUploadTool = {
|
|
|
10
9
|
}),
|
|
11
10
|
async execute(client, input) {
|
|
12
11
|
const signed = await client.getSignedUploadUrl(input.filename, input.content_type, input.size);
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
['Public URL after upload', signed.public_url],
|
|
21
|
-
]);
|
|
12
|
+
return [
|
|
13
|
+
'Signed upload created.',
|
|
14
|
+
`• Upload URL: ${signed.upload_url}`,
|
|
15
|
+
`• Token: ${signed.token}`,
|
|
16
|
+
`• Storage path: ${signed.path}`,
|
|
17
|
+
`• Public URL after upload: ${signed.public_url}`,
|
|
18
|
+
].join('\n');
|
|
22
19
|
},
|
|
23
20
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { webhookEventSchema } from './webhook-events.js';
|
|
3
|
-
import { code, mdSuccess } from '../lib/format.js';
|
|
4
3
|
export const createWebhookTool = {
|
|
5
4
|
name: 'create_webhook',
|
|
6
5
|
description: 'Create a webhook subscription for post/account/analytics events. WRITE WITH OUTBOUND SIDE EFFECTS: show the user the target URL, workspace, events, and active state, then get explicit confirmation before calling. The response includes the signing secret once.',
|
|
@@ -15,11 +14,7 @@ export const createWebhookTool = {
|
|
|
15
14
|
async execute(client, input) {
|
|
16
15
|
const { confirm: _confirm, ...payload } = input;
|
|
17
16
|
const result = await client.createWebhook(payload);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
['URL', result.webhook.url],
|
|
21
|
-
['Events', result.webhook.events.join(', ')],
|
|
22
|
-
['Signing secret', result.secret ? code(result.secret) : undefined],
|
|
23
|
-
], 'Store the signing secret now; Posterly only returns it once.');
|
|
17
|
+
const secret = result.secret ? `\nSigning secret: ${result.secret}` : '';
|
|
18
|
+
return `Webhook created: ${result.webhook.id}\nURL: ${result.webhook.url}\nEvents: ${result.webhook.events.join(', ')}${secret}`;
|
|
24
19
|
},
|
|
25
20
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { mdSuccess } from '../lib/format.js';
|
|
3
2
|
export const deleteGoogleBusinessReviewReplyTool = {
|
|
4
3
|
name: 'delete_google_business_review_reply',
|
|
5
4
|
description: 'Delete the owner reply from a Google Business Profile review. DESTRUCTIVE: confirm the review and location with the user, then pass confirm=true only after explicit confirmation.',
|
|
@@ -13,8 +12,6 @@ export const deleteGoogleBusinessReviewReplyTool = {
|
|
|
13
12
|
async execute(client, input) {
|
|
14
13
|
const { confirm: _confirm, ...payload } = input;
|
|
15
14
|
const result = await client.deleteGoogleBusinessReviewReply(payload);
|
|
16
|
-
return
|
|
17
|
-
['Message', result.message || 'Google Business review reply deleted successfully.'],
|
|
18
|
-
]);
|
|
15
|
+
return result.message || 'Google Business review reply deleted successfully.';
|
|
19
16
|
},
|
|
20
17
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { code, mdSuccess } from '../lib/format.js';
|
|
3
2
|
export const deleteOAuthClientTool = {
|
|
4
3
|
name: 'delete_oauth_client',
|
|
5
4
|
description: 'Delete a self-serve OAuth developer client. DESTRUCTIVE: prevents new authorizations for that client_id; existing access tokens remain revocable as API keys.',
|
|
@@ -9,8 +8,6 @@ export const deleteOAuthClientTool = {
|
|
|
9
8
|
}),
|
|
10
9
|
async execute(client, input) {
|
|
11
10
|
const result = await client.deleteOAuthClient(input.client_id);
|
|
12
|
-
return
|
|
13
|
-
['Client ID', code(result.client_id)],
|
|
14
|
-
]);
|
|
11
|
+
return `OAuth client deleted: ${result.client_id}`;
|
|
15
12
|
},
|
|
16
13
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { mdJson, mdTitle } from '../lib/format.js';
|
|
3
2
|
export const deletePostGroupTool = {
|
|
4
3
|
name: 'delete_post_group',
|
|
5
4
|
description: 'Delete every draft/scheduled/failed/paused post matching a caller-defined group_id, post_group_id, api_group_id, or release_id. DESTRUCTIVE: inspect the group first, list the affected posts, and pass confirm=true only after explicit confirmation.',
|
|
@@ -9,9 +8,6 @@ export const deletePostGroupTool = {
|
|
|
9
8
|
}),
|
|
10
9
|
async execute(client, input) {
|
|
11
10
|
const result = await client.deletePostGroup(input.group_id);
|
|
12
|
-
return
|
|
13
|
-
mdTitle('✅ Post group delete completed', `Group: ${input.group_id}`),
|
|
14
|
-
mdJson('Result', result),
|
|
15
|
-
].join('\n\n');
|
|
11
|
+
return JSON.stringify(result, null, 2);
|
|
16
12
|
},
|
|
17
13
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { code, mdSuccess } from '../lib/format.js';
|
|
3
2
|
export const deletePostTool = {
|
|
4
3
|
name: 'delete_post',
|
|
5
4
|
description: 'Delete a scheduled or draft post. DESTRUCTIVE and IRREVERSIBLE — the post and its caption cannot be recovered. Cannot delete published or currently publishing posts.\n\n' +
|
|
@@ -9,8 +8,6 @@ export const deletePostTool = {
|
|
|
9
8
|
}),
|
|
10
9
|
async execute(client, input) {
|
|
11
10
|
const result = await client.deletePost(input.post_id);
|
|
12
|
-
return
|
|
13
|
-
['Post ID', code(result.id)],
|
|
14
|
-
]);
|
|
11
|
+
return `Post #${result.id} deleted successfully.`;
|
|
15
12
|
},
|
|
16
13
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { code, mdSuccess } from '../lib/format.js';
|
|
3
2
|
export const deleteWebhookTool = {
|
|
4
3
|
name: 'delete_webhook',
|
|
5
4
|
description: 'Delete a webhook subscription. DESTRUCTIVE: list the webhook first, show the user its URL/events/workspace, and get explicit confirmation before calling.',
|
|
@@ -9,8 +8,6 @@ export const deleteWebhookTool = {
|
|
|
9
8
|
}),
|
|
10
9
|
async execute(client, input) {
|
|
11
10
|
const result = await client.deleteWebhook(input.webhook_id);
|
|
12
|
-
return
|
|
13
|
-
['Webhook ID', code(result.id)],
|
|
14
|
-
]);
|
|
11
|
+
return `Webhook deleted: ${result.id}`;
|
|
15
12
|
},
|
|
16
13
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { code, mdSuccess } from '../lib/format.js';
|
|
3
2
|
export const disconnectAccountTool = {
|
|
4
3
|
name: 'disconnect_account',
|
|
5
4
|
description: 'Disconnect a connected social account from posterly. DESTRUCTIVE and IRREVERSIBLE: removes the account connection, emits account.disconnected webhooks, and may transfer Instagram scheduled posts to a replacement account.\n\n' +
|
|
@@ -14,11 +13,10 @@ export const disconnectAccountTool = {
|
|
|
14
13
|
}
|
|
15
14
|
const result = await client.disconnectAccount(input.account_id);
|
|
16
15
|
const account = result.account || {};
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
]);
|
|
16
|
+
return [
|
|
17
|
+
`Disconnected ${account.platform || 'account'}${account.username ? ` @${account.username}` : ''} (ID: ${account.id || input.account_id}).`,
|
|
18
|
+
`Transferred posts: ${result.transferred_post_count ?? 0}.`,
|
|
19
|
+
`Stamped posts: ${result.stamped_post_count ?? 0}.`,
|
|
20
|
+
].join('\n');
|
|
23
21
|
},
|
|
24
22
|
};
|
package/dist/tools/find-slot.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { formatDate, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
|
|
3
2
|
export const findSlotTool = {
|
|
4
3
|
name: 'find_available_slot',
|
|
5
4
|
description: 'Find available time slots for posting. Respects a 1-hour gap between posts and preferred hours (8am–10pm in the given timezone). Returns up to 10 slots. IMPORTANT: pass a timezone explicitly — default is America/New_York and slots will be off if the user is elsewhere. Pass workspace_id to only avoid collisions with posts in that workspace.',
|
|
@@ -26,15 +25,9 @@ export const findSlotTool = {
|
|
|
26
25
|
async execute(client, input) {
|
|
27
26
|
const slots = await client.findAvailableSlots(input);
|
|
28
27
|
if (slots.length === 0) {
|
|
29
|
-
return
|
|
28
|
+
return 'No available slots found in the next 14 days.';
|
|
30
29
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
mdTable(['Option', 'Local time', 'Date'], slots.map((slot, index) => [
|
|
34
|
-
index + 1,
|
|
35
|
-
slot.local_time,
|
|
36
|
-
formatDate(slot.time),
|
|
37
|
-
])),
|
|
38
|
-
].join('\n\n');
|
|
30
|
+
const lines = slots.map((s, i) => `${i + 1}. ${s.local_time} — ${new Date(s.time).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' })}`);
|
|
31
|
+
return `Available posting slots:\n${lines.join('\n')}`;
|
|
39
32
|
},
|
|
40
33
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { mdBullets, mdKeyValue, mdSection, mdTable, mdTitle } from '../lib/format.js';
|
|
3
2
|
export const generateImageTool = {
|
|
4
3
|
name: 'generate_image',
|
|
5
4
|
description: 'Generate an AI image via Posterly\'s Nano Banana (Gemini) integration. The image is saved to the user\'s media storage and the returned URL can be passed to `create_post` as `media_url`.\n\n' +
|
|
@@ -48,21 +47,24 @@ export const generateImageTool = {
|
|
|
48
47
|
}),
|
|
49
48
|
async execute(client, input) {
|
|
50
49
|
const result = await client.generateImage(input);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
const lines = [];
|
|
51
|
+
lines.push(`Generated ${result.images.length} image${result.images.length === 1 ? '' : 's'} via ${result.model}.`);
|
|
52
|
+
lines.push('');
|
|
53
|
+
result.images.forEach((img, i) => {
|
|
54
|
+
lines.push(`${i + 1}. ${img.url}`);
|
|
55
|
+
});
|
|
56
|
+
lines.push('');
|
|
57
|
+
lines.push(`Billed from: ${result.usage.billed_from}${result.credits_used > 0 ? ` (${result.credits_used} credits)` : ''}`);
|
|
58
|
+
if (result.usage.limit != null) {
|
|
59
|
+
lines.push(`Plan usage: ${result.usage.used ?? 0}/${result.usage.limit} this ${result.usage.period || 'period'}${result.usage.tier ? ` (${result.usage.tier})` : ''}`);
|
|
60
|
+
}
|
|
61
|
+
if (result.warnings?.length) {
|
|
62
|
+
lines.push('');
|
|
63
|
+
lines.push('Warnings:');
|
|
64
|
+
result.warnings.forEach((w) => lines.push(`• ${w}`));
|
|
65
|
+
}
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push('Pass any of these URLs to create_post as media_url (or media_urls for a carousel).');
|
|
68
|
+
return lines.join('\n');
|
|
67
69
|
},
|
|
68
70
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { code, mdJson, mdKeyValue, mdTitle, statusLabel } from '../lib/format.js';
|
|
3
2
|
export const generateVideoTool = {
|
|
4
3
|
name: 'generate_video',
|
|
5
4
|
description: 'Queue a Veo AI video generation job. COSTS VEO CREDITS: confirm prompt, model, duration, resolution, aspect ratio, audio choice, and credit cost with the user before calling. Poll get_video_job for status and final video_url.',
|
|
@@ -22,15 +21,14 @@ export const generateVideoTool = {
|
|
|
22
21
|
async execute(client, input) {
|
|
23
22
|
const result = await client.generateVideo(input);
|
|
24
23
|
return [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
].filter(Boolean).join('\n\n');
|
|
24
|
+
'Video generation job queued',
|
|
25
|
+
`Job ID: ${result.job_id}`,
|
|
26
|
+
`Status: ${result.status}`,
|
|
27
|
+
`Credit cost: ${result.credit_cost} (${result.included_credit_cost || 0} included, ${result.purchased_credit_cost || 0} purchased)`,
|
|
28
|
+
result.message,
|
|
29
|
+
'',
|
|
30
|
+
'Raw response:',
|
|
31
|
+
JSON.stringify(result, null, 2),
|
|
32
|
+
].filter(Boolean).join('\n');
|
|
35
33
|
},
|
|
36
34
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { formatDelta, formatNumber, formatPercent, mdBullets, mdKeyValue, mdSection, mdTable, mdTitle } from '../lib/format.js';
|
|
3
2
|
export const getAccountAnalyticsTool = {
|
|
4
3
|
name: 'get_account_analytics',
|
|
5
|
-
description: 'Get daily analytics snapshots and a period summary for a connected social account. Supports Instagram, Facebook, LinkedIn, Google Business Profile, Pinterest, and YouTube.
|
|
4
|
+
description: 'Get daily analytics snapshots and a period summary for a connected social account. Supports Instagram, Facebook, LinkedIn, Google Business Profile, Pinterest, and YouTube. Uses API-provided display_metrics for platform-native dashboard labels such as Google Business Profile Views, Search Views, Maps Views, Customer Actions, and Posts.',
|
|
6
5
|
inputSchema: z.object({
|
|
7
6
|
account_id: z
|
|
8
7
|
.number()
|
|
@@ -19,72 +18,70 @@ export const getAccountAnalyticsTool = {
|
|
|
19
18
|
async execute(client, input) {
|
|
20
19
|
const result = await client.getAccountAnalytics(input);
|
|
21
20
|
const { account, range, summary, snapshots } = result;
|
|
22
|
-
const
|
|
23
|
-
?
|
|
24
|
-
:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
const accountLabel = account.platform === 'google_business'
|
|
22
|
+
? `${account.username} (Google Business, id ${account.id})`
|
|
23
|
+
: `@${account.username} (${account.platform}, id ${account.id})`;
|
|
24
|
+
const lines = [
|
|
25
|
+
`Analytics for ${accountLabel}`,
|
|
26
|
+
`Range: ${range.from} → ${range.to} (${snapshots.length} daily snapshots)`,
|
|
27
|
+
'',
|
|
28
|
+
'Summary:',
|
|
29
|
+
];
|
|
30
|
+
const nativeRows = getNativeMetricRows(account.platform, summary);
|
|
31
|
+
if (nativeRows.length > 0) {
|
|
32
|
+
for (const [label, value] of nativeRows) {
|
|
33
|
+
pushMetric(lines, label, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (account.platform === 'pinterest') {
|
|
37
|
+
pushGenericSummary(lines, summary);
|
|
38
|
+
pushMetric(lines, 'Outbound clicks', summary.total_website_clicks);
|
|
39
|
+
}
|
|
40
|
+
else if (account.platform === 'youtube') {
|
|
41
|
+
pushGenericSummary(lines, summary);
|
|
42
|
+
pushMetric(lines, 'Watch minutes', summary.total_watch_minutes);
|
|
43
|
+
pushMetric(lines, 'Likes', summary.total_likes);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
pushGenericSummary(lines, summary);
|
|
47
|
+
}
|
|
48
|
+
return lines.join('\n');
|
|
34
49
|
},
|
|
35
50
|
};
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
[
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
function getNativeMetricRows(platform, summary) {
|
|
52
|
+
// API-provided display_metrics is the contract for platform-native dashboards.
|
|
53
|
+
// Add future non-generic analytics platforms there so MCP output never guesses
|
|
54
|
+
// from internal storage field names.
|
|
55
|
+
const apiRows = (summary.display_metrics || [])
|
|
56
|
+
.filter((metric) => metric?.label)
|
|
57
|
+
.map((metric) => [metric.label, metric.value]);
|
|
58
|
+
if (apiRows.length > 0)
|
|
59
|
+
return apiRows;
|
|
60
|
+
if (platform === 'google_business') {
|
|
61
|
+
const metrics = summary.platform_metrics || {};
|
|
62
|
+
return [
|
|
63
|
+
['Profile Views', metrics.profile_views ?? summary.total_views],
|
|
64
|
+
['Search Views', metrics.search_views ?? summary.total_reach],
|
|
65
|
+
['Maps Views', metrics.maps_views ?? summary.total_profile_views],
|
|
66
|
+
['Customer Actions', metrics.customer_actions ?? summary.total_accounts_engaged],
|
|
67
|
+
['Posts', metrics.posts ?? summary.current_media_count],
|
|
68
|
+
];
|
|
53
69
|
}
|
|
54
|
-
return
|
|
70
|
+
return [];
|
|
55
71
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
return [
|
|
59
|
-
['Profile views', formatNumber(metrics.profile_views ?? summary.total_views)],
|
|
60
|
-
['Search views', formatNumber(metrics.search_views ?? summary.total_reach)],
|
|
61
|
-
['Maps views', formatNumber(metrics.maps_views ?? summary.total_profile_views)],
|
|
62
|
-
['Customer actions', formatNumber(metrics.customer_actions ?? summary.total_accounts_engaged)],
|
|
63
|
-
['Posts', formatNumber(metrics.posts ?? summary.current_media_count)],
|
|
64
|
-
['Engagement rate (actions / profile views)', formatPercent(summary.engagement_rate)],
|
|
65
|
-
['Website clicks', formatNumber(metrics.website_clicks ?? summary.total_website_clicks)],
|
|
66
|
-
['Call clicks', formatNumber(metrics.call_clicks ?? summary.total_call_clicks)],
|
|
67
|
-
['Direction requests', formatNumber(metrics.direction_requests ?? summary.total_direction_requests)],
|
|
68
|
-
['Conversations', formatNumber(metrics.conversations ?? summary.total_conversations)],
|
|
69
|
-
['Bookings', formatNumber(metrics.bookings ?? summary.total_bookings)],
|
|
70
|
-
];
|
|
72
|
+
function formatDelta(n) {
|
|
73
|
+
return n >= 0 ? `+${n.toLocaleString()}` : n.toLocaleString();
|
|
71
74
|
}
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (platform === 'google_business' && summary.total_website_clicks != null) {
|
|
84
|
-
insights.push(`${formatNumber(summary.total_website_clicks)} website clicks came from the profile.`);
|
|
85
|
-
}
|
|
86
|
-
if (platform === 'youtube' && summary.total_watch_minutes != null) {
|
|
87
|
-
insights.push(`${formatNumber(summary.total_watch_minutes)} total watch minutes were recorded.`);
|
|
75
|
+
function pushGenericSummary(lines, summary) {
|
|
76
|
+
lines.push(`• Followers: ${summary.current_followers.toLocaleString()} (${formatDelta(summary.followers_change)} in range)`, `• Follows gained / lost: +${summary.total_follows_gained} / -${summary.total_follows_lost}`);
|
|
77
|
+
pushMetric(lines, 'Total reach', summary.total_reach);
|
|
78
|
+
pushMetric(lines, 'Total views', summary.total_views);
|
|
79
|
+
pushMetric(lines, 'Profile views', summary.total_profile_views);
|
|
80
|
+
pushMetric(lines, 'Total accounts engaged', summary.total_accounts_engaged);
|
|
81
|
+
lines.push(`• Engagement rate (by reach): ${summary.engagement_rate}%`, `• Engagement rate (by followers): ${summary.engagement_rate_by_followers}%`);
|
|
82
|
+
}
|
|
83
|
+
function pushMetric(lines, label, value) {
|
|
84
|
+
if (value != null) {
|
|
85
|
+
lines.push(`• ${label}: ${value.toLocaleString()}`);
|
|
88
86
|
}
|
|
89
|
-
return insights.length > 0 ? insights : ['No standout movement detected in the returned period.'];
|
|
90
87
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { code, compactText, formatDateTime, mdKeyValue, mdSection, mdTitle } from '../lib/format.js';
|
|
3
2
|
export const getBrandProfileTool = {
|
|
4
3
|
name: 'get_brand_profile',
|
|
5
4
|
description: 'Get the extended brand profile for a brand/client. Returns voice/tone guidance, audience, keywords, dos and don’ts, visual notes, and other saved brand context.',
|
|
@@ -9,41 +8,48 @@ export const getBrandProfileTool = {
|
|
|
9
8
|
async execute(client, input) {
|
|
10
9
|
const result = await client.getBrandProfile(input.brand_id);
|
|
11
10
|
const profile = result.brand_profile;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
11
|
+
const lines = [
|
|
12
|
+
`Brand profile: ${profile.brand_name}`,
|
|
13
|
+
`• Source: ${profile.source}`,
|
|
14
|
+
];
|
|
15
|
+
if (profile.workspace_client_id)
|
|
16
|
+
lines.push(`• Workspace client ID: ${profile.workspace_client_id}`);
|
|
17
|
+
if (profile.brand_group_id)
|
|
18
|
+
lines.push(`• Legacy brand group ID: ${profile.brand_group_id}`);
|
|
19
|
+
if (profile.tone_of_voice)
|
|
20
|
+
lines.push(`• Tone of voice: ${profile.tone_of_voice}`);
|
|
21
|
+
if (profile.audience)
|
|
22
|
+
lines.push(`• Audience: ${profile.audience}`);
|
|
23
|
+
if (profile.brand_values)
|
|
24
|
+
lines.push(`• Brand values: ${profile.brand_values}`);
|
|
25
|
+
if (profile.custom_instructions)
|
|
26
|
+
lines.push(`• Custom instructions: ${profile.custom_instructions}`);
|
|
27
|
+
if (profile.logo_url)
|
|
28
|
+
lines.push(`• Logo URL: ${profile.logo_url}`);
|
|
29
|
+
addStructuredLine(lines, 'Keywords', profile.keywords);
|
|
30
|
+
addStructuredLine(lines, 'Competitors', profile.competitors);
|
|
31
|
+
addStructuredLine(lines, 'Do / Don’ts', profile.do_donts);
|
|
32
|
+
addStructuredLine(lines, 'Visual guidelines', profile.visual_guidelines);
|
|
33
|
+
addStructuredLine(lines, 'Brand story', profile.brand_story);
|
|
34
|
+
addStructuredLine(lines, 'Example posts', profile.example_posts);
|
|
35
|
+
addStructuredLine(lines, 'Topics to cover', profile.topics_to_cover);
|
|
36
|
+
addStructuredLine(lines, 'Topics to avoid', profile.topics_to_avoid);
|
|
37
|
+
addStructuredLine(lines, 'Voice examples', profile.voice_examples);
|
|
38
|
+
if (profile.last_context_refresh_at) {
|
|
39
|
+
lines.push(`• Last refreshed: ${profile.last_context_refresh_at}`);
|
|
40
|
+
}
|
|
41
|
+
if (lines.length === 2) {
|
|
42
|
+
lines.push('• No extended brand profile fields have been saved yet.');
|
|
43
|
+
}
|
|
44
|
+
return lines.join('\n');
|
|
45
45
|
},
|
|
46
46
|
};
|
|
47
|
+
function addStructuredLine(lines, label, value) {
|
|
48
|
+
const formatted = formatStructuredValue(value);
|
|
49
|
+
if (formatted) {
|
|
50
|
+
lines.push(`• ${label}: ${formatted}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
47
53
|
function formatStructuredValue(value) {
|
|
48
54
|
if (value == null)
|
|
49
55
|
return null;
|
package/dist/tools/get-brand.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { code, formatDateTime, mdKeyValue, mdTitle } from '../lib/format.js';
|
|
3
2
|
export const getBrandTool = {
|
|
4
3
|
name: 'get_brand',
|
|
5
4
|
description: 'Get one brand/client by ID. Returns its workspace, source, linked legacy brand group if present, and the number of social accounts assigned to it.',
|
|
@@ -9,18 +8,21 @@ export const getBrandTool = {
|
|
|
9
8
|
async execute(client, input) {
|
|
10
9
|
const result = await client.getBrand(input.brand_id);
|
|
11
10
|
const brand = result.brand;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
const lines = [
|
|
12
|
+
`Brand: ${brand.name}`,
|
|
13
|
+
`• ID: ${brand.id}`,
|
|
14
|
+
`• Source: ${brand.source}`,
|
|
15
|
+
`• Workspace: ${brand.workspace_id || 'N/A'}`,
|
|
16
|
+
`• Accounts assigned: ${brand.account_count ?? 0}`,
|
|
17
|
+
];
|
|
18
|
+
if (brand.workspace_client_id)
|
|
19
|
+
lines.push(`• Workspace client ID: ${brand.workspace_client_id}`);
|
|
20
|
+
if (brand.legacy_brand_group_id)
|
|
21
|
+
lines.push(`• Legacy brand group ID: ${brand.legacy_brand_group_id}`);
|
|
22
|
+
if (brand.created_at)
|
|
23
|
+
lines.push(`• Created: ${brand.created_at}`);
|
|
24
|
+
if (brand.updated_at)
|
|
25
|
+
lines.push(`• Updated: ${brand.updated_at}`);
|
|
26
|
+
return lines.join('\n');
|
|
25
27
|
},
|
|
26
28
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { CONNECT_INPUTS, PLANNED_PLATFORM_IDS } from '../generated/platform-manifest.js';
|
|
3
|
-
import { code, mdJson, mdKeyValue, mdSection, mdTable, mdTitle, statusLabel } from '../lib/format.js';
|
|
4
3
|
export const getConnectLinkTool = {
|
|
5
4
|
name: 'get_connect_link',
|
|
6
5
|
description: 'List dashboard handoff links/readiness for connecting social accounts, or get one platform connection URL. Use connection_url in a logged-in browser. Direct OAuth URLs are intentionally not exposed because provider callbacks rely on browser state.',
|
|
@@ -24,18 +23,8 @@ export const getConnectLinkTool = {
|
|
|
24
23
|
return formatConnectOption(result.connect, true);
|
|
25
24
|
}
|
|
26
25
|
const options = result.connect_options || [];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
mdTable(['Provider', 'Target', 'Status', 'Method', 'Connected', 'Readiness'], options.map((option) => [
|
|
30
|
-
option.label,
|
|
31
|
-
code(option.platform),
|
|
32
|
-
statusLabel(option.status),
|
|
33
|
-
option.method,
|
|
34
|
-
option.connected_count ?? option.connected_accounts?.length ?? 0,
|
|
35
|
-
option.missing_env?.length ? 'missing config' : 'configured',
|
|
36
|
-
])),
|
|
37
|
-
'**Tip:** Call this tool with a specific `platform` to get the direct dashboard handoff URL.',
|
|
38
|
-
].join('\n\n');
|
|
26
|
+
const lines = options.map((option) => formatConnectOption(option, false));
|
|
27
|
+
return `Posterly connection options (${options.length}):\n${lines.join('\n\n')}`;
|
|
39
28
|
},
|
|
40
29
|
};
|
|
41
30
|
function formatConnectOption(option, includeRaw) {
|
|
@@ -47,19 +36,11 @@ function formatConnectOption(option, includeRaw) {
|
|
|
47
36
|
? `; credential fields: ${option.credential_fields.map((field) => field.key).join(', ')}`
|
|
48
37
|
: '';
|
|
49
38
|
const scopes = option.scopes?.length ? `; scopes: ${option.scopes.join(', ')}` : '';
|
|
50
|
-
const raw = includeRaw ?
|
|
39
|
+
const raw = includeRaw ? `\n\nRaw connect option:\n${JSON.stringify(option, null, 2)}` : '';
|
|
51
40
|
return [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
['Method', option.method],
|
|
56
|
-
['Readiness', missing],
|
|
57
|
-
['Connected accounts', accounts],
|
|
58
|
-
['Connection URL', option.connection_url || 'not available'],
|
|
59
|
-
['Credential fields', fields.replace(/^; credential fields: /, '') || undefined],
|
|
60
|
-
['Scopes', scopes.replace(/^; scopes: /, '') || undefined],
|
|
61
|
-
]),
|
|
62
|
-
option.notes?.length ? mdSection('Notes', option.notes.map((note) => `- ${note}`).join('\n')) : '',
|
|
41
|
+
`${option.label} (${option.platform})`,
|
|
42
|
+
`Status: ${option.status}; method: ${option.method}; ${missing}; connected accounts: ${accounts}`,
|
|
43
|
+
`Connection URL: ${option.connection_url || 'not available'}${fields}${scopes}`,
|
|
63
44
|
raw,
|
|
64
|
-
].
|
|
45
|
+
].join('\n');
|
|
65
46
|
}
|