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
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# posterly MCP Server
|
|
2
|
+
|
|
3
|
+
Use Posterly from any MCP-compatible AI client.
|
|
4
|
+
|
|
5
|
+
This package gives Claude Desktop, Cursor, Windsurf, Cline, and other local MCP clients a `stdio` server that can:
|
|
6
|
+
|
|
7
|
+
- list connected social accounts
|
|
8
|
+
- resolve brands/clients into the right accounts
|
|
9
|
+
- schedule and manage posts
|
|
10
|
+
- upload media
|
|
11
|
+
- generate images
|
|
12
|
+
- read account and post analytics
|
|
13
|
+
|
|
14
|
+
Posterly also exposes the same toolset over HTTP at [poster.ly/mcp](https://www.poster.ly/mcp), but this npm package is the local `stdio` transport.
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- Node.js `20+`
|
|
19
|
+
- A Posterly account: [poster.ly/signup](https://www.poster.ly/signup)
|
|
20
|
+
- The Posterly API add-on enabled: [poster.ly/dashboard/api](https://www.poster.ly/dashboard/api)
|
|
21
|
+
- A Posterly API key
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
You can install globally:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g posterly-mcp-server
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or just use it via `npx` in your MCP config:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"posterly": {
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["-y", "posterly-mcp-server"],
|
|
39
|
+
"env": {
|
|
40
|
+
"POSTERLY_API_KEY": "pst_live_your_key_here"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick setup
|
|
48
|
+
|
|
49
|
+
1. Sign up at [poster.ly](https://www.poster.ly/signup)
|
|
50
|
+
2. Go to [Dashboard → API & MCP](https://www.poster.ly/dashboard/api)
|
|
51
|
+
3. Enable the API add-on
|
|
52
|
+
4. Generate an API key
|
|
53
|
+
5. Paste it into your MCP client config as `POSTERLY_API_KEY`
|
|
54
|
+
6. Restart your AI client
|
|
55
|
+
|
|
56
|
+
## Example configs
|
|
57
|
+
|
|
58
|
+
### Claude Desktop
|
|
59
|
+
|
|
60
|
+
Add this to your Claude Desktop MCP config:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"posterly": {
|
|
66
|
+
"command": "npx",
|
|
67
|
+
"args": ["-y", "posterly-mcp-server"],
|
|
68
|
+
"env": {
|
|
69
|
+
"POSTERLY_API_KEY": "pst_live_your_key_here"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Cursor
|
|
77
|
+
|
|
78
|
+
Add the same server definition to your Cursor MCP settings:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"posterly": {
|
|
84
|
+
"command": "npx",
|
|
85
|
+
"args": ["-y", "posterly-mcp-server"],
|
|
86
|
+
"env": {
|
|
87
|
+
"POSTERLY_API_KEY": "pst_live_your_key_here"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Available tools
|
|
95
|
+
|
|
96
|
+
`posterly-mcp-server@0.6.0` exposes 16 tools:
|
|
97
|
+
|
|
98
|
+
- `whoami`
|
|
99
|
+
- `list_accounts`
|
|
100
|
+
- `list_brands`
|
|
101
|
+
- `get_brand`
|
|
102
|
+
- `list_brand_accounts`
|
|
103
|
+
- `get_brand_profile`
|
|
104
|
+
- `list_posts`
|
|
105
|
+
- `get_post`
|
|
106
|
+
- `create_post`
|
|
107
|
+
- `update_post`
|
|
108
|
+
- `delete_post`
|
|
109
|
+
- `upload_media`
|
|
110
|
+
- `find_available_slot`
|
|
111
|
+
- `generate_image`
|
|
112
|
+
- `get_account_analytics`
|
|
113
|
+
- `get_post_analytics`
|
|
114
|
+
|
|
115
|
+
## What the brand tools are for
|
|
116
|
+
|
|
117
|
+
Posterly workspaces often have multiple connected accounts under one client or brand.
|
|
118
|
+
|
|
119
|
+
The brand tools let an assistant work at the same level a human does:
|
|
120
|
+
|
|
121
|
+
- `list_brands` lets the agent see clients/brands in the workspace
|
|
122
|
+
- `get_brand` returns summary info for one brand
|
|
123
|
+
- `list_brand_accounts` resolves a brand into the actual connected accounts
|
|
124
|
+
- `get_brand_profile` returns saved brand guidance like tone, audience, keywords, dos and don'ts, and visual notes
|
|
125
|
+
|
|
126
|
+
This makes prompts like:
|
|
127
|
+
|
|
128
|
+
- "How is Grassroots doing on Instagram?"
|
|
129
|
+
- "Write a post for the Posterly brand voice"
|
|
130
|
+
- "Schedule something for our Dubai dental client"
|
|
131
|
+
|
|
132
|
+
much more reliable than forcing the agent to guess from raw account handles alone.
|
|
133
|
+
|
|
134
|
+
## Example prompts
|
|
135
|
+
|
|
136
|
+
- `What Posterly accounts do I have connected?`
|
|
137
|
+
- `List my brands in Posterly`
|
|
138
|
+
- `Show me the brand profile for Grassroots`
|
|
139
|
+
- `Find the next 3 posting slots for my LinkedIn account`
|
|
140
|
+
- `Schedule a post for tomorrow at 9am for the Posterly Instagram account`
|
|
141
|
+
- `How did Grassroots perform on Instagram in the last 30 days?`
|
|
142
|
+
|
|
143
|
+
## Pricing
|
|
144
|
+
|
|
145
|
+
This package uses the Posterly API/MCP add-on:
|
|
146
|
+
|
|
147
|
+
- `$3/month` add-on
|
|
148
|
+
- `100 requests/hour` per API key
|
|
149
|
+
- works across all 11 supported platforms
|
|
150
|
+
|
|
151
|
+
Details: [poster.ly/dashboard/api](https://www.poster.ly/dashboard/api)
|
|
152
|
+
|
|
153
|
+
## Links
|
|
154
|
+
|
|
155
|
+
- Docs: [poster.ly/mcp](https://www.poster.ly/mcp)
|
|
156
|
+
- OpenClaw skill: [poster.ly/openclaw](https://www.poster.ly/openclaw)
|
|
157
|
+
- API add-on: [poster.ly/dashboard/api](https://www.poster.ly/dashboard/api)
|
|
158
|
+
- MCP server card: [/.well-known/mcp/server-card.json](https://www.poster.ly/.well-known/mcp/server-card.json)
|
|
159
|
+
- OAuth authorization server metadata: [/.well-known/oauth-authorization-server](https://www.poster.ly/.well-known/oauth-authorization-server)
|
|
160
|
+
- OAuth protected resource metadata: [/.well-known/oauth-protected-resource](https://www.poster.ly/.well-known/oauth-protected-resource)
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
From the `mcp-server` directory:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm install
|
|
168
|
+
npm run build
|
|
169
|
+
npm start
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The package reads:
|
|
173
|
+
|
|
174
|
+
- `POSTERLY_API_KEY`
|
|
175
|
+
- optional `POSTERLY_URL` if you need to point at a non-production environment
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { PosterlyClient } from './lib/api-client.js';
|
|
5
5
|
import { listAccountsTool } from './tools/list-accounts.js';
|
|
6
|
+
import { listBrandsTool } from './tools/list-brands.js';
|
|
7
|
+
import { getBrandTool } from './tools/get-brand.js';
|
|
8
|
+
import { listBrandAccountsTool } from './tools/list-brand-accounts.js';
|
|
9
|
+
import { getBrandProfileTool } from './tools/get-brand-profile.js';
|
|
6
10
|
import { createPostTool } from './tools/create-post.js';
|
|
7
11
|
import { findSlotTool } from './tools/find-slot.js';
|
|
8
12
|
import { listPostsTool } from './tools/list-posts.js';
|
|
@@ -12,9 +16,11 @@ import { updatePostTool } from './tools/update-post.js';
|
|
|
12
16
|
import { deletePostTool } from './tools/delete-post.js';
|
|
13
17
|
import { whoamiTool } from './tools/whoami.js';
|
|
14
18
|
import { generateImageTool } from './tools/generate-image.js';
|
|
19
|
+
import { getAccountAnalyticsTool } from './tools/get-account-analytics.js';
|
|
20
|
+
import { getPostAnalyticsTool } from './tools/get-post-analytics.js';
|
|
15
21
|
const server = new McpServer({
|
|
16
22
|
name: 'posterly',
|
|
17
|
-
version: '0.
|
|
23
|
+
version: '0.6.0',
|
|
18
24
|
});
|
|
19
25
|
let client;
|
|
20
26
|
try {
|
|
@@ -43,6 +49,42 @@ server.tool(listAccountsTool.name, listAccountsTool.description, listAccountsToo
|
|
|
43
49
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
44
50
|
}
|
|
45
51
|
});
|
|
52
|
+
server.tool(listBrandsTool.name, listBrandsTool.description, listBrandsTool.inputSchema.shape, async (input) => {
|
|
53
|
+
try {
|
|
54
|
+
const text = await listBrandsTool.execute(client, input);
|
|
55
|
+
return { content: [{ type: 'text', text }] };
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
server.tool(getBrandTool.name, getBrandTool.description, getBrandTool.inputSchema.shape, async (input) => {
|
|
62
|
+
try {
|
|
63
|
+
const text = await getBrandTool.execute(client, input);
|
|
64
|
+
return { content: [{ type: 'text', text }] };
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
server.tool(listBrandAccountsTool.name, listBrandAccountsTool.description, listBrandAccountsTool.inputSchema.shape, async (input) => {
|
|
71
|
+
try {
|
|
72
|
+
const text = await listBrandAccountsTool.execute(client, input);
|
|
73
|
+
return { content: [{ type: 'text', text }] };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
server.tool(getBrandProfileTool.name, getBrandProfileTool.description, getBrandProfileTool.inputSchema.shape, async (input) => {
|
|
80
|
+
try {
|
|
81
|
+
const text = await getBrandProfileTool.execute(client, input);
|
|
82
|
+
return { content: [{ type: 'text', text }] };
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
86
|
+
}
|
|
87
|
+
});
|
|
46
88
|
server.tool(createPostTool.name, createPostTool.description, createPostTool.inputSchema.shape, async (input) => {
|
|
47
89
|
try {
|
|
48
90
|
const text = await createPostTool.execute(client, input);
|
|
@@ -115,6 +157,24 @@ server.tool(deletePostTool.name, deletePostTool.description, deletePostTool.inpu
|
|
|
115
157
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
116
158
|
}
|
|
117
159
|
});
|
|
160
|
+
server.tool(getAccountAnalyticsTool.name, getAccountAnalyticsTool.description, getAccountAnalyticsTool.inputSchema.shape, async (input) => {
|
|
161
|
+
try {
|
|
162
|
+
const text = await getAccountAnalyticsTool.execute(client, input);
|
|
163
|
+
return { content: [{ type: 'text', text }] };
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
server.tool(getPostAnalyticsTool.name, getPostAnalyticsTool.description, getPostAnalyticsTool.inputSchema.shape, async (input) => {
|
|
170
|
+
try {
|
|
171
|
+
const text = await getPostAnalyticsTool.execute(client, input);
|
|
172
|
+
return { content: [{ type: 'text', text }] };
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
176
|
+
}
|
|
177
|
+
});
|
|
118
178
|
// Start the server
|
|
119
179
|
async function main() {
|
|
120
180
|
const transport = new StdioServerTransport();
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -6,6 +6,41 @@ export interface Account {
|
|
|
6
6
|
profile_picture_url?: string;
|
|
7
7
|
workspace_id?: string | null;
|
|
8
8
|
}
|
|
9
|
+
export interface Brand {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
workspace_id?: string | null;
|
|
13
|
+
workspace_client_id?: string | null;
|
|
14
|
+
legacy_brand_group_id?: string | null;
|
|
15
|
+
created_at?: string | null;
|
|
16
|
+
updated_at?: string | null;
|
|
17
|
+
source: 'canonical' | 'legacy';
|
|
18
|
+
account_count: number;
|
|
19
|
+
}
|
|
20
|
+
export interface BrandProfile {
|
|
21
|
+
id: string | null;
|
|
22
|
+
brand_group_id: string | null;
|
|
23
|
+
workspace_client_id: string | null;
|
|
24
|
+
brand_name: string;
|
|
25
|
+
tone_of_voice?: string | null;
|
|
26
|
+
audience?: string | null;
|
|
27
|
+
brand_values?: string | null;
|
|
28
|
+
do_donts?: unknown;
|
|
29
|
+
keywords?: unknown;
|
|
30
|
+
competitors?: unknown;
|
|
31
|
+
custom_instructions?: string | null;
|
|
32
|
+
visual_guidelines?: unknown;
|
|
33
|
+
logo_url?: string | null;
|
|
34
|
+
brand_story?: string | null;
|
|
35
|
+
example_posts?: unknown;
|
|
36
|
+
topics_to_cover?: unknown;
|
|
37
|
+
topics_to_avoid?: unknown;
|
|
38
|
+
voice_examples?: unknown;
|
|
39
|
+
last_context_refresh_at?: string | null;
|
|
40
|
+
created_at?: string | null;
|
|
41
|
+
updated_at?: string | null;
|
|
42
|
+
source: 'canonical' | 'legacy';
|
|
43
|
+
}
|
|
9
44
|
export interface Post {
|
|
10
45
|
id: number;
|
|
11
46
|
content: string;
|
|
@@ -43,6 +78,86 @@ export interface Whoami {
|
|
|
43
78
|
default_workspace: Workspace;
|
|
44
79
|
workspaces: Workspace[];
|
|
45
80
|
}
|
|
81
|
+
export interface AccountAnalyticsSummary {
|
|
82
|
+
current_followers: number;
|
|
83
|
+
followers_change: number;
|
|
84
|
+
total_reach: number | null;
|
|
85
|
+
total_views: number | null;
|
|
86
|
+
total_accounts_engaged: number | null;
|
|
87
|
+
total_follows_gained: number;
|
|
88
|
+
total_follows_lost: number;
|
|
89
|
+
engagement_rate: number;
|
|
90
|
+
engagement_rate_by_followers: number;
|
|
91
|
+
total_website_clicks: number | null;
|
|
92
|
+
total_call_clicks: number | null;
|
|
93
|
+
total_direction_requests: number | null;
|
|
94
|
+
total_conversations: number | null;
|
|
95
|
+
total_bookings: number | null;
|
|
96
|
+
}
|
|
97
|
+
export interface AccountAnalyticsSnapshot {
|
|
98
|
+
date: string;
|
|
99
|
+
followers: number | null;
|
|
100
|
+
following: number | null;
|
|
101
|
+
media_count: number | null;
|
|
102
|
+
reach: number | null;
|
|
103
|
+
views: number | null;
|
|
104
|
+
profile_views: number | null;
|
|
105
|
+
accounts_engaged: number | null;
|
|
106
|
+
follows_gained: number | null;
|
|
107
|
+
follows_lost: number | null;
|
|
108
|
+
website_clicks: number | null;
|
|
109
|
+
call_clicks: number | null;
|
|
110
|
+
direction_requests: number | null;
|
|
111
|
+
conversations: number | null;
|
|
112
|
+
bookings: number | null;
|
|
113
|
+
}
|
|
114
|
+
export interface AccountAnalyticsResponse {
|
|
115
|
+
account: {
|
|
116
|
+
id: number;
|
|
117
|
+
platform: string;
|
|
118
|
+
username: string;
|
|
119
|
+
};
|
|
120
|
+
range: {
|
|
121
|
+
from: string;
|
|
122
|
+
to: string;
|
|
123
|
+
};
|
|
124
|
+
summary: AccountAnalyticsSummary;
|
|
125
|
+
snapshots: AccountAnalyticsSnapshot[];
|
|
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
|
+
export interface PostAnalyticsResponse {
|
|
147
|
+
account: {
|
|
148
|
+
id: number;
|
|
149
|
+
platform: string;
|
|
150
|
+
username: string;
|
|
151
|
+
};
|
|
152
|
+
range: {
|
|
153
|
+
from: string;
|
|
154
|
+
to: string;
|
|
155
|
+
};
|
|
156
|
+
posts: PostAnalyticsRow[];
|
|
157
|
+
total: number;
|
|
158
|
+
limit: number;
|
|
159
|
+
offset: number;
|
|
160
|
+
}
|
|
46
161
|
export declare class PosterlyClient {
|
|
47
162
|
private baseUrl;
|
|
48
163
|
private apiKey;
|
|
@@ -52,9 +167,20 @@ export declare class PosterlyClient {
|
|
|
52
167
|
listAccounts(params?: {
|
|
53
168
|
workspace_id?: string;
|
|
54
169
|
}): Promise<Account[]>;
|
|
170
|
+
listBrands(params?: {
|
|
171
|
+
workspace_id?: string;
|
|
172
|
+
}): Promise<Brand[]>;
|
|
173
|
+
getBrand(id: string): Promise<{
|
|
174
|
+
brand: Brand;
|
|
175
|
+
}>;
|
|
176
|
+
listBrandAccounts(id: string): Promise<Account[]>;
|
|
177
|
+
getBrandProfile(id: string): Promise<{
|
|
178
|
+
brand_profile: BrandProfile;
|
|
179
|
+
}>;
|
|
55
180
|
listPosts(params?: {
|
|
56
181
|
status?: string;
|
|
57
182
|
platform?: string;
|
|
183
|
+
account_id?: string;
|
|
58
184
|
limit?: number;
|
|
59
185
|
offset?: number;
|
|
60
186
|
workspace_id?: string;
|
|
@@ -130,6 +256,18 @@ export declare class PosterlyClient {
|
|
|
130
256
|
count?: number;
|
|
131
257
|
workspace_id?: string;
|
|
132
258
|
}): Promise<Slot[]>;
|
|
259
|
+
getAccountAnalytics(params: {
|
|
260
|
+
account_id: number;
|
|
261
|
+
from?: string;
|
|
262
|
+
to?: string;
|
|
263
|
+
}): Promise<AccountAnalyticsResponse>;
|
|
264
|
+
getPostAnalytics(params: {
|
|
265
|
+
account_id: number;
|
|
266
|
+
from?: string;
|
|
267
|
+
to?: string;
|
|
268
|
+
limit?: number;
|
|
269
|
+
offset?: number;
|
|
270
|
+
}): Promise<PostAnalyticsResponse>;
|
|
133
271
|
getSignedUploadUrl(filename: string, contentType: string, size: number): Promise<{
|
|
134
272
|
upload_url: string;
|
|
135
273
|
token: string;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -41,12 +41,32 @@ export class PosterlyClient {
|
|
|
41
41
|
const data = await this.request('GET', `/accounts${qs ? `?${qs}` : ''}`);
|
|
42
42
|
return data.accounts;
|
|
43
43
|
}
|
|
44
|
+
async listBrands(params) {
|
|
45
|
+
const searchParams = new URLSearchParams();
|
|
46
|
+
if (params?.workspace_id)
|
|
47
|
+
searchParams.set('workspace_id', params.workspace_id);
|
|
48
|
+
const qs = searchParams.toString();
|
|
49
|
+
const data = await this.request('GET', `/brands${qs ? `?${qs}` : ''}`);
|
|
50
|
+
return data.brands;
|
|
51
|
+
}
|
|
52
|
+
async getBrand(id) {
|
|
53
|
+
return this.request('GET', `/brands/${encodeURIComponent(id)}`);
|
|
54
|
+
}
|
|
55
|
+
async listBrandAccounts(id) {
|
|
56
|
+
const data = await this.request('GET', `/brands/${encodeURIComponent(id)}/accounts`);
|
|
57
|
+
return data.accounts;
|
|
58
|
+
}
|
|
59
|
+
async getBrandProfile(id) {
|
|
60
|
+
return this.request('GET', `/brands/${encodeURIComponent(id)}/profile`);
|
|
61
|
+
}
|
|
44
62
|
async listPosts(params) {
|
|
45
63
|
const searchParams = new URLSearchParams();
|
|
46
64
|
if (params?.status)
|
|
47
65
|
searchParams.set('status', params.status);
|
|
48
66
|
if (params?.platform)
|
|
49
67
|
searchParams.set('platform', params.platform);
|
|
68
|
+
if (params?.account_id)
|
|
69
|
+
searchParams.set('account_id', params.account_id);
|
|
50
70
|
if (params?.limit)
|
|
51
71
|
searchParams.set('limit', String(params.limit));
|
|
52
72
|
if (params?.offset)
|
|
@@ -85,6 +105,28 @@ export class PosterlyClient {
|
|
|
85
105
|
const data = await this.request('GET', `/slots/next${qs ? `?${qs}` : ''}`);
|
|
86
106
|
return data.slots;
|
|
87
107
|
}
|
|
108
|
+
async getAccountAnalytics(params) {
|
|
109
|
+
const searchParams = new URLSearchParams();
|
|
110
|
+
searchParams.set('account_id', String(params.account_id));
|
|
111
|
+
if (params.from)
|
|
112
|
+
searchParams.set('from', params.from);
|
|
113
|
+
if (params.to)
|
|
114
|
+
searchParams.set('to', params.to);
|
|
115
|
+
return this.request('GET', `/analytics/accounts?${searchParams.toString()}`);
|
|
116
|
+
}
|
|
117
|
+
async getPostAnalytics(params) {
|
|
118
|
+
const searchParams = new URLSearchParams();
|
|
119
|
+
searchParams.set('account_id', String(params.account_id));
|
|
120
|
+
if (params.from)
|
|
121
|
+
searchParams.set('from', params.from);
|
|
122
|
+
if (params.to)
|
|
123
|
+
searchParams.set('to', params.to);
|
|
124
|
+
if (params.limit)
|
|
125
|
+
searchParams.set('limit', String(params.limit));
|
|
126
|
+
if (params.offset)
|
|
127
|
+
searchParams.set('offset', String(params.offset));
|
|
128
|
+
return this.request('GET', `/analytics/posts?${searchParams.toString()}`);
|
|
129
|
+
}
|
|
88
130
|
async getSignedUploadUrl(filename, contentType, size) {
|
|
89
131
|
return this.request('POST', '/media/signed-upload', {
|
|
90
132
|
filename,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const getAccountAnalyticsTool: {
|
|
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
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
account_id: number;
|
|
12
|
+
from?: string | undefined;
|
|
13
|
+
to?: string | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
account_id: number;
|
|
16
|
+
from?: string | undefined;
|
|
17
|
+
to?: string | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
execute(client: PosterlyClient, input: {
|
|
20
|
+
account_id: number;
|
|
21
|
+
from?: string;
|
|
22
|
+
to?: string;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const getAccountAnalyticsTool = {
|
|
3
|
+
name: 'get_account_analytics',
|
|
4
|
+
description: 'Get daily analytics snapshots and a period summary for a connected social account. Supports Instagram, LinkedIn, and Google Business Profile. Returns follower growth, reach, views, engagement rate, and platform-specific metrics (e.g. website clicks, direction requests for GBP).',
|
|
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
|
+
}),
|
|
18
|
+
async execute(client, input) {
|
|
19
|
+
const result = await client.getAccountAnalytics(input);
|
|
20
|
+
const { account, range, summary, snapshots } = result;
|
|
21
|
+
const lines = [
|
|
22
|
+
`Analytics for @${account.username} (${account.platform}, id ${account.id})`,
|
|
23
|
+
`Range: ${range.from} → ${range.to} (${snapshots.length} daily snapshots)`,
|
|
24
|
+
'',
|
|
25
|
+
'Summary:',
|
|
26
|
+
`• Followers: ${summary.current_followers.toLocaleString()} (${formatDelta(summary.followers_change)} in range)`,
|
|
27
|
+
`• Follows gained / lost: +${summary.total_follows_gained} / -${summary.total_follows_lost}`,
|
|
28
|
+
];
|
|
29
|
+
if (summary.total_reach !== null)
|
|
30
|
+
lines.push(`• Total reach: ${summary.total_reach.toLocaleString()}`);
|
|
31
|
+
if (summary.total_views !== null)
|
|
32
|
+
lines.push(`• Total views: ${summary.total_views.toLocaleString()}`);
|
|
33
|
+
if (summary.total_accounts_engaged !== null) {
|
|
34
|
+
lines.push(`• Total accounts engaged: ${summary.total_accounts_engaged.toLocaleString()}`);
|
|
35
|
+
}
|
|
36
|
+
lines.push(`• Engagement rate (by reach): ${summary.engagement_rate}%`, `• Engagement rate (by followers): ${summary.engagement_rate_by_followers}%`);
|
|
37
|
+
if (account.platform === 'google_business') {
|
|
38
|
+
if (summary.total_website_clicks !== null) {
|
|
39
|
+
lines.push(`• Website clicks: ${summary.total_website_clicks.toLocaleString()}`);
|
|
40
|
+
}
|
|
41
|
+
if (summary.total_call_clicks !== null) {
|
|
42
|
+
lines.push(`• Call clicks: ${summary.total_call_clicks.toLocaleString()}`);
|
|
43
|
+
}
|
|
44
|
+
if (summary.total_direction_requests !== null) {
|
|
45
|
+
lines.push(`• Direction requests: ${summary.total_direction_requests.toLocaleString()}`);
|
|
46
|
+
}
|
|
47
|
+
if (summary.total_conversations !== null) {
|
|
48
|
+
lines.push(`• Conversations: ${summary.total_conversations.toLocaleString()}`);
|
|
49
|
+
}
|
|
50
|
+
if (summary.total_bookings !== null) {
|
|
51
|
+
lines.push(`• Bookings: ${summary.total_bookings.toLocaleString()}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
function formatDelta(n) {
|
|
58
|
+
return n >= 0 ? `+${n.toLocaleString()}` : n.toLocaleString();
|
|
59
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const getBrandProfileTool: {
|
|
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,73 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const getBrandProfileTool = {
|
|
3
|
+
name: 'get_brand_profile',
|
|
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.',
|
|
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 result = await client.getBrandProfile(input.brand_id);
|
|
10
|
+
const profile = result.brand_profile;
|
|
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
|
+
},
|
|
46
|
+
};
|
|
47
|
+
function addStructuredLine(lines, label, value) {
|
|
48
|
+
const formatted = formatStructuredValue(value);
|
|
49
|
+
if (formatted) {
|
|
50
|
+
lines.push(`• ${label}: ${formatted}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function formatStructuredValue(value) {
|
|
54
|
+
if (value == null)
|
|
55
|
+
return null;
|
|
56
|
+
if (typeof value === 'string') {
|
|
57
|
+
const trimmed = value.trim();
|
|
58
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
if (value.length === 0)
|
|
62
|
+
return null;
|
|
63
|
+
return value
|
|
64
|
+
.map((item) => formatStructuredValue(item) || JSON.stringify(item))
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join(' | ');
|
|
67
|
+
}
|
|
68
|
+
if (typeof value === 'object') {
|
|
69
|
+
const json = JSON.stringify(value);
|
|
70
|
+
return json === '{}' ? null : json;
|
|
71
|
+
}
|
|
72
|
+
return String(value);
|
|
73
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const getBrandTool: {
|
|
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
|
+
};
|