posterly-mcp-server 0.4.0 → 0.6.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.
- package/README.md +175 -0
- package/dist/index.js +61 -1
- package/dist/lib/api-client.d.ts +138 -0
- package/dist/lib/api-client.js +42 -0
- package/dist/tools/get-account-analytics.d.ts +24 -0
- package/dist/tools/get-account-analytics.js +59 -0
- package/dist/tools/get-brand-profile.d.ts +16 -0
- package/dist/tools/get-brand-profile.js +73 -0
- package/dist/tools/get-brand.d.ts +16 -0
- package/dist/tools/get-brand.js +28 -0
- package/dist/tools/get-post-analytics.d.ts +32 -0
- package/dist/tools/get-post-analytics.js +65 -0
- package/dist/tools/list-brand-accounts.d.ts +16 -0
- package/dist/tools/list-brand-accounts.js +16 -0
- package/dist/tools/list-brands.d.ts +16 -0
- package/dist/tools/list-brands.js +29 -0
- package/dist/tools/list-posts.d.ts +4 -0
- package/dist/tools/list-posts.js +8 -4
- package/package.json +20 -1
- package/src/index.ts +91 -1
- package/src/lib/api-client.ts +159 -0
- package/src/tools/get-account-analytics.ts +72 -0
- package/src/tools/get-brand-profile.ts +79 -0
- package/src/tools/get-brand.ts +34 -0
- package/src/tools/get-post-analytics.ts +78 -0
- package/src/tools/list-brand-accounts.ts +28 -0
- package/src/tools/list-brands.ts +36 -0
- package/src/tools/list-posts.ts +15 -5
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const getBrandTool = {
|
|
3
|
+
name: 'get_brand',
|
|
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.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
brand_id: z.string().describe('The brand ID to look up (from list_brands).'),
|
|
7
|
+
}),
|
|
8
|
+
async execute(client, input) {
|
|
9
|
+
const result = await client.getBrand(input.brand_id);
|
|
10
|
+
const brand = result.brand;
|
|
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');
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const getPostAnalyticsTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
account_id: z.ZodNumber;
|
|
8
|
+
from: z.ZodOptional<z.ZodString>;
|
|
9
|
+
to: z.ZodOptional<z.ZodString>;
|
|
10
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
}, "strip", z.ZodTypeAny, {
|
|
13
|
+
account_id: number;
|
|
14
|
+
limit?: number | undefined;
|
|
15
|
+
offset?: number | undefined;
|
|
16
|
+
from?: string | undefined;
|
|
17
|
+
to?: string | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
account_id: number;
|
|
20
|
+
limit?: number | undefined;
|
|
21
|
+
offset?: number | undefined;
|
|
22
|
+
from?: string | undefined;
|
|
23
|
+
to?: string | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
execute(client: PosterlyClient, input: {
|
|
26
|
+
account_id: number;
|
|
27
|
+
from?: string;
|
|
28
|
+
to?: string;
|
|
29
|
+
limit?: number;
|
|
30
|
+
offset?: number;
|
|
31
|
+
}): Promise<string>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const getPostAnalyticsTool = {
|
|
3
|
+
name: 'get_post_analytics',
|
|
4
|
+
description: 'Get per-post engagement metrics (likes, comments, reach, impressions, saves, shares, plays) for a connected social account. Supports Instagram, LinkedIn, and Google Business Profile. Returns the most recent posts first.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
account_id: z
|
|
7
|
+
.number()
|
|
8
|
+
.describe('The social account ID (from list_accounts)'),
|
|
9
|
+
from: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('Start date (ISO date, e.g. 2026-03-19). Defaults to 30 days ago.'),
|
|
13
|
+
to: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('End date (ISO date). Defaults to today.'),
|
|
17
|
+
limit: z
|
|
18
|
+
.number()
|
|
19
|
+
.min(1)
|
|
20
|
+
.max(200)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Number of posts to return (default 50, max 200)'),
|
|
23
|
+
offset: z.number().min(0).optional().describe('Pagination offset'),
|
|
24
|
+
}),
|
|
25
|
+
async execute(client, input) {
|
|
26
|
+
const result = await client.getPostAnalytics(input);
|
|
27
|
+
const { account, range, posts, total } = result;
|
|
28
|
+
if (posts.length === 0) {
|
|
29
|
+
return `No analytics found for @${account.username} (${account.platform}) between ${range.from} and ${range.to}.`;
|
|
30
|
+
}
|
|
31
|
+
const lines = [
|
|
32
|
+
`Post analytics for @${account.username} (${account.platform})`,
|
|
33
|
+
`Range: ${range.from} → ${range.to} • Showing ${posts.length} of ${total}`,
|
|
34
|
+
'',
|
|
35
|
+
];
|
|
36
|
+
for (const p of posts) {
|
|
37
|
+
const postedAt = p.posted_at
|
|
38
|
+
? new Date(p.posted_at).toLocaleString()
|
|
39
|
+
: 'unknown date';
|
|
40
|
+
const caption = p.caption_snippet
|
|
41
|
+
? p.caption_snippet.length > 60
|
|
42
|
+
? `${p.caption_snippet.slice(0, 60)}…`
|
|
43
|
+
: p.caption_snippet
|
|
44
|
+
: '(no caption)';
|
|
45
|
+
const metrics = [
|
|
46
|
+
`${p.likes} likes`,
|
|
47
|
+
`${p.comments} comments`,
|
|
48
|
+
`${p.reach.toLocaleString()} reach`,
|
|
49
|
+
];
|
|
50
|
+
if (p.impressions)
|
|
51
|
+
metrics.push(`${p.impressions.toLocaleString()} impressions`);
|
|
52
|
+
if (p.saved)
|
|
53
|
+
metrics.push(`${p.saved} saved`);
|
|
54
|
+
if (p.shares)
|
|
55
|
+
metrics.push(`${p.shares} shares`);
|
|
56
|
+
if (p.plays)
|
|
57
|
+
metrics.push(`${p.plays.toLocaleString()} plays`);
|
|
58
|
+
lines.push(`• [${postedAt}] ${caption}`);
|
|
59
|
+
lines.push(` ${metrics.join(' • ')}`);
|
|
60
|
+
if (p.permalink)
|
|
61
|
+
lines.push(` ${p.permalink}`);
|
|
62
|
+
}
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const listBrandAccountsTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
brand_id: z.ZodString;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
brand_id: string;
|
|
10
|
+
}, {
|
|
11
|
+
brand_id: string;
|
|
12
|
+
}>;
|
|
13
|
+
execute(client: PosterlyClient, input: {
|
|
14
|
+
brand_id: string;
|
|
15
|
+
}): Promise<string>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const listBrandAccountsTool = {
|
|
3
|
+
name: 'list_brand_accounts',
|
|
4
|
+
description: 'List the connected social accounts assigned to a brand/client. Use this when a user refers to a brand name rather than a raw account handle.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
brand_id: z.string().describe('The brand ID to inspect (from list_brands).'),
|
|
7
|
+
}),
|
|
8
|
+
async execute(client, input) {
|
|
9
|
+
const accounts = await client.listBrandAccounts(input.brand_id);
|
|
10
|
+
if (accounts.length === 0) {
|
|
11
|
+
return 'No social accounts are currently assigned to this brand.';
|
|
12
|
+
}
|
|
13
|
+
const lines = accounts.map((account) => `• ${account.platform} — @${account.username} (ID: ${account.id}${account.workspace_id ? `, ws: ${account.workspace_id}` : ''})`);
|
|
14
|
+
return `Brand accounts (${accounts.length}):\n${lines.join('\n')}`;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const listBrandsTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
workspace_id?: string | undefined;
|
|
10
|
+
}, {
|
|
11
|
+
workspace_id?: string | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
execute(client: PosterlyClient, input: {
|
|
14
|
+
workspace_id?: string;
|
|
15
|
+
}): Promise<string>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const listBrandsTool = {
|
|
3
|
+
name: 'list_brands',
|
|
4
|
+
description: 'List brands/clients the caller can access. Returns each brand ID, name, workspace ID, source, and how many social accounts are currently assigned to it.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
workspace_id: z
|
|
7
|
+
.string()
|
|
8
|
+
.optional()
|
|
9
|
+
.describe('Filter to brands in a specific workspace (get IDs via whoami).'),
|
|
10
|
+
}),
|
|
11
|
+
async execute(client, input) {
|
|
12
|
+
const brands = await client.listBrands({ workspace_id: input.workspace_id });
|
|
13
|
+
if (brands.length === 0) {
|
|
14
|
+
return input.workspace_id
|
|
15
|
+
? 'No brands found in this workspace.'
|
|
16
|
+
: 'No brands found. Create brands in the posterly dashboard first.';
|
|
17
|
+
}
|
|
18
|
+
const lines = brands.map((brand) => {
|
|
19
|
+
const suffix = [
|
|
20
|
+
`ID: ${brand.id}`,
|
|
21
|
+
brand.workspace_id ? `ws: ${brand.workspace_id}` : null,
|
|
22
|
+
`accounts: ${brand.account_count}`,
|
|
23
|
+
brand.source,
|
|
24
|
+
].filter(Boolean).join(', ');
|
|
25
|
+
return `• ${brand.name} (${suffix})`;
|
|
26
|
+
});
|
|
27
|
+
return `Brands (${brands.length}):\n${lines.join('\n')}`;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -6,22 +6,26 @@ export declare const listPostsTool: {
|
|
|
6
6
|
inputSchema: z.ZodObject<{
|
|
7
7
|
status: z.ZodOptional<z.ZodString>;
|
|
8
8
|
platform: z.ZodOptional<z.ZodString>;
|
|
9
|
+
account_id: z.ZodOptional<z.ZodString>;
|
|
9
10
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
10
11
|
workspace_id: z.ZodOptional<z.ZodString>;
|
|
11
12
|
}, "strip", z.ZodTypeAny, {
|
|
12
13
|
workspace_id?: string | undefined;
|
|
13
14
|
status?: string | undefined;
|
|
14
15
|
platform?: string | undefined;
|
|
16
|
+
account_id?: string | undefined;
|
|
15
17
|
limit?: number | undefined;
|
|
16
18
|
}, {
|
|
17
19
|
workspace_id?: string | undefined;
|
|
18
20
|
status?: string | undefined;
|
|
19
21
|
platform?: string | undefined;
|
|
22
|
+
account_id?: string | undefined;
|
|
20
23
|
limit?: number | undefined;
|
|
21
24
|
}>;
|
|
22
25
|
execute(client: PosterlyClient, input: {
|
|
23
26
|
status?: string;
|
|
24
27
|
platform?: string;
|
|
28
|
+
account_id?: string;
|
|
25
29
|
limit?: number;
|
|
26
30
|
workspace_id?: string;
|
|
27
31
|
}): Promise<string>;
|
package/dist/tools/list-posts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export const listPostsTool = {
|
|
3
3
|
name: 'list_posts',
|
|
4
|
-
description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, or workspace_id. If workspace_id is omitted, posts across every workspace the caller is a member of are returned.',
|
|
4
|
+
description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, account_id, or workspace_id. If workspace_id is omitted, posts across every workspace the caller is a member of are returned.',
|
|
5
5
|
inputSchema: z.object({
|
|
6
6
|
status: z
|
|
7
7
|
.string()
|
|
@@ -11,12 +11,16 @@ export const listPostsTool = {
|
|
|
11
11
|
.string()
|
|
12
12
|
.optional()
|
|
13
13
|
.describe('Filter by platform: instagram, twitter, linkedin, etc.'),
|
|
14
|
+
account_id: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Filter to a specific social account ID (from list_accounts).'),
|
|
14
18
|
limit: z
|
|
15
19
|
.number()
|
|
16
20
|
.min(1)
|
|
17
|
-
.max(
|
|
21
|
+
.max(100)
|
|
18
22
|
.optional()
|
|
19
|
-
.describe('Number of posts to return (default
|
|
23
|
+
.describe('Number of posts to return (default 20, max 100)'),
|
|
20
24
|
workspace_id: z
|
|
21
25
|
.string()
|
|
22
26
|
.optional()
|
|
@@ -25,7 +29,7 @@ export const listPostsTool = {
|
|
|
25
29
|
async execute(client, input) {
|
|
26
30
|
const { posts, total } = await client.listPosts({
|
|
27
31
|
...input,
|
|
28
|
-
limit: input.limit ||
|
|
32
|
+
limit: input.limit || 20,
|
|
29
33
|
});
|
|
30
34
|
if (posts.length === 0) {
|
|
31
35
|
return 'No posts found matching your criteria.';
|
package/package.json
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posterly-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "MCP server for posterly — schedule social media posts from Claude Desktop",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"homepage": "https://www.poster.ly/mcp",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/awpthorp/posterly.git",
|
|
10
|
+
"directory": "mcp-server"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/awpthorp/posterly/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"posterly",
|
|
19
|
+
"social-media",
|
|
20
|
+
"scheduling",
|
|
21
|
+
"claude",
|
|
22
|
+
"cursor",
|
|
23
|
+
"chatgpt"
|
|
24
|
+
],
|
|
6
25
|
"type": "module",
|
|
7
26
|
"bin": {
|
|
8
27
|
"posterly-mcp": "./dist/index.js"
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
4
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
5
|
import { PosterlyClient } from './lib/api-client.js';
|
|
6
6
|
import { listAccountsTool } from './tools/list-accounts.js';
|
|
7
|
+
import { listBrandsTool } from './tools/list-brands.js';
|
|
8
|
+
import { getBrandTool } from './tools/get-brand.js';
|
|
9
|
+
import { listBrandAccountsTool } from './tools/list-brand-accounts.js';
|
|
10
|
+
import { getBrandProfileTool } from './tools/get-brand-profile.js';
|
|
7
11
|
import { createPostTool } from './tools/create-post.js';
|
|
8
12
|
import { findSlotTool } from './tools/find-slot.js';
|
|
9
13
|
import { listPostsTool } from './tools/list-posts.js';
|
|
@@ -13,10 +17,12 @@ import { updatePostTool } from './tools/update-post.js';
|
|
|
13
17
|
import { deletePostTool } from './tools/delete-post.js';
|
|
14
18
|
import { whoamiTool } from './tools/whoami.js';
|
|
15
19
|
import { generateImageTool } from './tools/generate-image.js';
|
|
20
|
+
import { getAccountAnalyticsTool } from './tools/get-account-analytics.js';
|
|
21
|
+
import { getPostAnalyticsTool } from './tools/get-post-analytics.js';
|
|
16
22
|
|
|
17
23
|
const server = new McpServer({
|
|
18
24
|
name: 'posterly',
|
|
19
|
-
version: '0.
|
|
25
|
+
version: '0.6.0',
|
|
20
26
|
});
|
|
21
27
|
|
|
22
28
|
let client: PosterlyClient;
|
|
@@ -57,6 +63,62 @@ server.tool(
|
|
|
57
63
|
}
|
|
58
64
|
);
|
|
59
65
|
|
|
66
|
+
server.tool(
|
|
67
|
+
listBrandsTool.name,
|
|
68
|
+
listBrandsTool.description,
|
|
69
|
+
listBrandsTool.inputSchema.shape,
|
|
70
|
+
async (input) => {
|
|
71
|
+
try {
|
|
72
|
+
const text = await listBrandsTool.execute(client, input as any);
|
|
73
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
74
|
+
} catch (err: any) {
|
|
75
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
server.tool(
|
|
81
|
+
getBrandTool.name,
|
|
82
|
+
getBrandTool.description,
|
|
83
|
+
getBrandTool.inputSchema.shape,
|
|
84
|
+
async (input) => {
|
|
85
|
+
try {
|
|
86
|
+
const text = await getBrandTool.execute(client, input as any);
|
|
87
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
88
|
+
} catch (err: any) {
|
|
89
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
server.tool(
|
|
95
|
+
listBrandAccountsTool.name,
|
|
96
|
+
listBrandAccountsTool.description,
|
|
97
|
+
listBrandAccountsTool.inputSchema.shape,
|
|
98
|
+
async (input) => {
|
|
99
|
+
try {
|
|
100
|
+
const text = await listBrandAccountsTool.execute(client, input as any);
|
|
101
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
102
|
+
} catch (err: any) {
|
|
103
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
server.tool(
|
|
109
|
+
getBrandProfileTool.name,
|
|
110
|
+
getBrandProfileTool.description,
|
|
111
|
+
getBrandProfileTool.inputSchema.shape,
|
|
112
|
+
async (input) => {
|
|
113
|
+
try {
|
|
114
|
+
const text = await getBrandProfileTool.execute(client, input as any);
|
|
115
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
116
|
+
} catch (err: any) {
|
|
117
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
60
122
|
server.tool(
|
|
61
123
|
createPostTool.name,
|
|
62
124
|
createPostTool.description,
|
|
@@ -169,6 +231,34 @@ server.tool(
|
|
|
169
231
|
}
|
|
170
232
|
);
|
|
171
233
|
|
|
234
|
+
server.tool(
|
|
235
|
+
getAccountAnalyticsTool.name,
|
|
236
|
+
getAccountAnalyticsTool.description,
|
|
237
|
+
getAccountAnalyticsTool.inputSchema.shape,
|
|
238
|
+
async (input) => {
|
|
239
|
+
try {
|
|
240
|
+
const text = await getAccountAnalyticsTool.execute(client, input as any);
|
|
241
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
242
|
+
} catch (err: any) {
|
|
243
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
server.tool(
|
|
249
|
+
getPostAnalyticsTool.name,
|
|
250
|
+
getPostAnalyticsTool.description,
|
|
251
|
+
getPostAnalyticsTool.inputSchema.shape,
|
|
252
|
+
async (input) => {
|
|
253
|
+
try {
|
|
254
|
+
const text = await getPostAnalyticsTool.execute(client, input as any);
|
|
255
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
256
|
+
} catch (err: any) {
|
|
257
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
|
|
172
262
|
// Start the server
|
|
173
263
|
async function main() {
|
|
174
264
|
const transport = new StdioServerTransport();
|
package/src/lib/api-client.ts
CHANGED
|
@@ -10,6 +10,43 @@ export interface Account {
|
|
|
10
10
|
workspace_id?: string | null;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface Brand {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
workspace_id?: string | null;
|
|
17
|
+
workspace_client_id?: string | null;
|
|
18
|
+
legacy_brand_group_id?: string | null;
|
|
19
|
+
created_at?: string | null;
|
|
20
|
+
updated_at?: string | null;
|
|
21
|
+
source: 'canonical' | 'legacy';
|
|
22
|
+
account_count: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface BrandProfile {
|
|
26
|
+
id: string | null;
|
|
27
|
+
brand_group_id: string | null;
|
|
28
|
+
workspace_client_id: string | null;
|
|
29
|
+
brand_name: string;
|
|
30
|
+
tone_of_voice?: string | null;
|
|
31
|
+
audience?: string | null;
|
|
32
|
+
brand_values?: string | null;
|
|
33
|
+
do_donts?: unknown;
|
|
34
|
+
keywords?: unknown;
|
|
35
|
+
competitors?: unknown;
|
|
36
|
+
custom_instructions?: string | null;
|
|
37
|
+
visual_guidelines?: unknown;
|
|
38
|
+
logo_url?: string | null;
|
|
39
|
+
brand_story?: string | null;
|
|
40
|
+
example_posts?: unknown;
|
|
41
|
+
topics_to_cover?: unknown;
|
|
42
|
+
topics_to_avoid?: unknown;
|
|
43
|
+
voice_examples?: unknown;
|
|
44
|
+
last_context_refresh_at?: string | null;
|
|
45
|
+
created_at?: string | null;
|
|
46
|
+
updated_at?: string | null;
|
|
47
|
+
source: 'canonical' | 'legacy';
|
|
48
|
+
}
|
|
49
|
+
|
|
13
50
|
export interface Post {
|
|
14
51
|
id: number;
|
|
15
52
|
content: string;
|
|
@@ -45,6 +82,77 @@ export interface Whoami {
|
|
|
45
82
|
workspaces: Workspace[];
|
|
46
83
|
}
|
|
47
84
|
|
|
85
|
+
export interface AccountAnalyticsSummary {
|
|
86
|
+
current_followers: number;
|
|
87
|
+
followers_change: number;
|
|
88
|
+
total_reach: number | null;
|
|
89
|
+
total_views: number | null;
|
|
90
|
+
total_accounts_engaged: number | null;
|
|
91
|
+
total_follows_gained: number;
|
|
92
|
+
total_follows_lost: number;
|
|
93
|
+
engagement_rate: number;
|
|
94
|
+
engagement_rate_by_followers: number;
|
|
95
|
+
total_website_clicks: number | null;
|
|
96
|
+
total_call_clicks: number | null;
|
|
97
|
+
total_direction_requests: number | null;
|
|
98
|
+
total_conversations: number | null;
|
|
99
|
+
total_bookings: number | null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface AccountAnalyticsSnapshot {
|
|
103
|
+
date: string;
|
|
104
|
+
followers: number | null;
|
|
105
|
+
following: number | null;
|
|
106
|
+
media_count: number | null;
|
|
107
|
+
reach: number | null;
|
|
108
|
+
views: number | null;
|
|
109
|
+
profile_views: number | null;
|
|
110
|
+
accounts_engaged: number | null;
|
|
111
|
+
follows_gained: number | null;
|
|
112
|
+
follows_lost: number | null;
|
|
113
|
+
website_clicks: number | null;
|
|
114
|
+
call_clicks: number | null;
|
|
115
|
+
direction_requests: number | null;
|
|
116
|
+
conversations: number | null;
|
|
117
|
+
bookings: number | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface AccountAnalyticsResponse {
|
|
121
|
+
account: { id: number; platform: string; username: string };
|
|
122
|
+
range: { from: string; to: string };
|
|
123
|
+
summary: AccountAnalyticsSummary;
|
|
124
|
+
snapshots: AccountAnalyticsSnapshot[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface PostAnalyticsRow {
|
|
128
|
+
id: number | null;
|
|
129
|
+
platform_media_id: string;
|
|
130
|
+
platform: string;
|
|
131
|
+
posted_at: string | null;
|
|
132
|
+
likes: number;
|
|
133
|
+
comments: number;
|
|
134
|
+
impressions: number;
|
|
135
|
+
reach: number;
|
|
136
|
+
saved: number;
|
|
137
|
+
shares: number;
|
|
138
|
+
plays: number;
|
|
139
|
+
total_interactions: number;
|
|
140
|
+
media_type: string | null;
|
|
141
|
+
media_url: string | null;
|
|
142
|
+
permalink: string | null;
|
|
143
|
+
caption_snippet: string | null;
|
|
144
|
+
synced_at: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface PostAnalyticsResponse {
|
|
148
|
+
account: { id: number; platform: string; username: string };
|
|
149
|
+
range: { from: string; to: string };
|
|
150
|
+
posts: PostAnalyticsRow[];
|
|
151
|
+
total: number;
|
|
152
|
+
limit: number;
|
|
153
|
+
offset: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
48
156
|
export class PosterlyClient {
|
|
49
157
|
private baseUrl: string;
|
|
50
158
|
private apiKey: string;
|
|
@@ -99,9 +207,31 @@ export class PosterlyClient {
|
|
|
99
207
|
return data.accounts;
|
|
100
208
|
}
|
|
101
209
|
|
|
210
|
+
async listBrands(params?: { workspace_id?: string }): Promise<Brand[]> {
|
|
211
|
+
const searchParams = new URLSearchParams();
|
|
212
|
+
if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
|
|
213
|
+
const qs = searchParams.toString();
|
|
214
|
+
const data = await this.request<{ brands: Brand[] }>('GET', `/brands${qs ? `?${qs}` : ''}`);
|
|
215
|
+
return data.brands;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async getBrand(id: string): Promise<{ brand: Brand }> {
|
|
219
|
+
return this.request('GET', `/brands/${encodeURIComponent(id)}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async listBrandAccounts(id: string): Promise<Account[]> {
|
|
223
|
+
const data = await this.request<{ accounts: Account[] }>('GET', `/brands/${encodeURIComponent(id)}/accounts`);
|
|
224
|
+
return data.accounts;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async getBrandProfile(id: string): Promise<{ brand_profile: BrandProfile }> {
|
|
228
|
+
return this.request('GET', `/brands/${encodeURIComponent(id)}/profile`);
|
|
229
|
+
}
|
|
230
|
+
|
|
102
231
|
async listPosts(params?: {
|
|
103
232
|
status?: string;
|
|
104
233
|
platform?: string;
|
|
234
|
+
account_id?: string;
|
|
105
235
|
limit?: number;
|
|
106
236
|
offset?: number;
|
|
107
237
|
workspace_id?: string;
|
|
@@ -109,6 +239,7 @@ export class PosterlyClient {
|
|
|
109
239
|
const searchParams = new URLSearchParams();
|
|
110
240
|
if (params?.status) searchParams.set('status', params.status);
|
|
111
241
|
if (params?.platform) searchParams.set('platform', params.platform);
|
|
242
|
+
if (params?.account_id) searchParams.set('account_id', params.account_id);
|
|
112
243
|
if (params?.limit) searchParams.set('limit', String(params.limit));
|
|
113
244
|
if (params?.offset) searchParams.set('offset', String(params.offset));
|
|
114
245
|
if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
|
|
@@ -198,6 +329,34 @@ export class PosterlyClient {
|
|
|
198
329
|
return data.slots;
|
|
199
330
|
}
|
|
200
331
|
|
|
332
|
+
async getAccountAnalytics(params: {
|
|
333
|
+
account_id: number;
|
|
334
|
+
from?: string;
|
|
335
|
+
to?: string;
|
|
336
|
+
}): Promise<AccountAnalyticsResponse> {
|
|
337
|
+
const searchParams = new URLSearchParams();
|
|
338
|
+
searchParams.set('account_id', String(params.account_id));
|
|
339
|
+
if (params.from) searchParams.set('from', params.from);
|
|
340
|
+
if (params.to) searchParams.set('to', params.to);
|
|
341
|
+
return this.request('GET', `/analytics/accounts?${searchParams.toString()}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async getPostAnalytics(params: {
|
|
345
|
+
account_id: number;
|
|
346
|
+
from?: string;
|
|
347
|
+
to?: string;
|
|
348
|
+
limit?: number;
|
|
349
|
+
offset?: number;
|
|
350
|
+
}): Promise<PostAnalyticsResponse> {
|
|
351
|
+
const searchParams = new URLSearchParams();
|
|
352
|
+
searchParams.set('account_id', String(params.account_id));
|
|
353
|
+
if (params.from) searchParams.set('from', params.from);
|
|
354
|
+
if (params.to) searchParams.set('to', params.to);
|
|
355
|
+
if (params.limit) searchParams.set('limit', String(params.limit));
|
|
356
|
+
if (params.offset) searchParams.set('offset', String(params.offset));
|
|
357
|
+
return this.request('GET', `/analytics/posts?${searchParams.toString()}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
201
360
|
async getSignedUploadUrl(
|
|
202
361
|
filename: string,
|
|
203
362
|
contentType: string,
|