posterly-mcp-server 0.48.2 → 0.48.3
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 -1
- package/dist/lib/api-client.d.ts +18 -0
- package/dist/lib/api-client.js +12 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/find-slot.d.ts +4 -0
- package/dist/tools/find-slot.js +39 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/lib/api-client.ts +29 -0
- package/src/lib/version.ts +1 -1
- package/src/tools/find-slot.ts +45 -2
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ Add the same server definition to your Cursor MCP settings:
|
|
|
123
123
|
|
|
124
124
|
## Available tools
|
|
125
125
|
|
|
126
|
-
`posterly-mcp-server@0.48.
|
|
126
|
+
`posterly-mcp-server@0.48.3` exposes 62 tools covering 95 actions. The hosted HTTP endpoint exposes the same catalog minus the three signup tools: 59 tools covering 92 actions.
|
|
127
127
|
|
|
128
128
|
Many tools pick what to do with one parameter: `view` (reads), `action` (writes), or `type`. A few pick their mode from which parameters you pass (for example `update_post` with `status` changes the status instead of editing). Every tool description names all of its views and actions.
|
|
129
129
|
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -555,6 +555,17 @@ export interface Slot {
|
|
|
555
555
|
time: string;
|
|
556
556
|
local_time: string;
|
|
557
557
|
}
|
|
558
|
+
export type SuggestedSlotBasis = 'yours' | 'platform' | 'default';
|
|
559
|
+
export interface SuggestedSlot extends Slot {
|
|
560
|
+
basis: SuggestedSlotBasis;
|
|
561
|
+
reason: string;
|
|
562
|
+
}
|
|
563
|
+
export interface SuggestedSlotsResponse {
|
|
564
|
+
timezone: string;
|
|
565
|
+
basis: SuggestedSlotBasis;
|
|
566
|
+
basis_label: string;
|
|
567
|
+
slots: SuggestedSlot[];
|
|
568
|
+
}
|
|
558
569
|
export interface Workspace {
|
|
559
570
|
id: string;
|
|
560
571
|
name: string;
|
|
@@ -1364,6 +1375,13 @@ export declare class PosterlyClient {
|
|
|
1364
1375
|
count?: number;
|
|
1365
1376
|
workspace_id?: string;
|
|
1366
1377
|
}): Promise<Slot[]>;
|
|
1378
|
+
/** Best times by engagement for the given accounts (GET /slots/suggested). */
|
|
1379
|
+
findSuggestedSlots(params: {
|
|
1380
|
+
account_ids: string[];
|
|
1381
|
+
timezone?: string;
|
|
1382
|
+
count?: number;
|
|
1383
|
+
workspace_id?: string;
|
|
1384
|
+
}): Promise<SuggestedSlotsResponse>;
|
|
1367
1385
|
getAccountAnalytics(params: {
|
|
1368
1386
|
account_id: number;
|
|
1369
1387
|
from?: string;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -452,6 +452,18 @@ export class PosterlyClient {
|
|
|
452
452
|
const data = await this.request('GET', `/slots/next${qs ? `?${qs}` : ''}`);
|
|
453
453
|
return data.slots;
|
|
454
454
|
}
|
|
455
|
+
/** Best times by engagement for the given accounts (GET /slots/suggested). */
|
|
456
|
+
async findSuggestedSlots(params) {
|
|
457
|
+
const searchParams = new URLSearchParams();
|
|
458
|
+
searchParams.set('account_ids', params.account_ids.join(','));
|
|
459
|
+
if (params.timezone)
|
|
460
|
+
searchParams.set('timezone', params.timezone);
|
|
461
|
+
if (params.count)
|
|
462
|
+
searchParams.set('count', String(params.count));
|
|
463
|
+
if (params.workspace_id)
|
|
464
|
+
searchParams.set('workspace_id', params.workspace_id);
|
|
465
|
+
return this.request('GET', `/slots/suggested?${searchParams.toString()}`);
|
|
466
|
+
}
|
|
455
467
|
async getAccountAnalytics(params) {
|
|
456
468
|
const searchParams = new URLSearchParams();
|
|
457
469
|
searchParams.set('account_id', String(params.account_id));
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const POSTERLY_MCP_VERSION = "0.48.
|
|
1
|
+
export declare const POSTERLY_MCP_VERSION = "0.48.3";
|
package/dist/lib/version.js
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// tool set (minus the intentional pre-auth signup tools that only this stdio
|
|
6
6
|
// package exposes). `npm run check:mcp-parity` enforces both the version match
|
|
7
7
|
// and the tool-list match, and runs in the pre-commit hook.
|
|
8
|
-
export const POSTERLY_MCP_VERSION = '0.48.
|
|
8
|
+
export const POSTERLY_MCP_VERSION = '0.48.3';
|
|
@@ -8,21 +8,25 @@ export declare const findSlotTool: {
|
|
|
8
8
|
timezone: z.ZodOptional<z.ZodString>;
|
|
9
9
|
count: z.ZodOptional<z.ZodNumber>;
|
|
10
10
|
workspace_id: z.ZodOptional<z.ZodString>;
|
|
11
|
+
strategy: z.ZodOptional<z.ZodEnum<["next_free", "best_engagement"]>>;
|
|
11
12
|
}, "strip", z.ZodTypeAny, {
|
|
12
13
|
workspace_id?: string | undefined;
|
|
13
14
|
account_ids?: string[] | undefined;
|
|
14
15
|
timezone?: string | undefined;
|
|
15
16
|
count?: number | undefined;
|
|
17
|
+
strategy?: "next_free" | "best_engagement" | undefined;
|
|
16
18
|
}, {
|
|
17
19
|
workspace_id?: string | undefined;
|
|
18
20
|
account_ids?: string[] | undefined;
|
|
19
21
|
timezone?: string | undefined;
|
|
20
22
|
count?: number | undefined;
|
|
23
|
+
strategy?: "next_free" | "best_engagement" | undefined;
|
|
21
24
|
}>;
|
|
22
25
|
execute(client: PosterlyClient, input: {
|
|
23
26
|
account_ids?: string[];
|
|
24
27
|
timezone?: string;
|
|
25
28
|
count?: number;
|
|
26
29
|
workspace_id?: string;
|
|
30
|
+
strategy?: "next_free" | "best_engagement";
|
|
27
31
|
}): Promise<string>;
|
|
28
32
|
};
|
package/dist/tools/find-slot.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { formatDate, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
|
|
3
|
+
const SUGGESTED_BASIS_LABELS = {
|
|
4
|
+
yours: 'Based on your posts',
|
|
5
|
+
platform: 'Popular on this platform',
|
|
6
|
+
default: 'Good default',
|
|
7
|
+
};
|
|
3
8
|
export const findSlotTool = {
|
|
4
9
|
name: 'find_available_slot',
|
|
5
|
-
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.',
|
|
10
|
+
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. strategy "best_engagement" (needs account_ids) ranks the next 7 days by when these accounts get the most engagement, with a reason per slot.',
|
|
6
11
|
inputSchema: z.object({
|
|
7
12
|
account_ids: z
|
|
8
13
|
.array(z.string())
|
|
@@ -17,13 +22,45 @@ export const findSlotTool = {
|
|
|
17
22
|
.min(1)
|
|
18
23
|
.max(10)
|
|
19
24
|
.optional()
|
|
20
|
-
.describe('Number of slots to return (
|
|
25
|
+
.describe('Number of slots to return (max 10). Default 5 for next_free, 3 for best_engagement.'),
|
|
21
26
|
workspace_id: z
|
|
22
27
|
.string()
|
|
23
28
|
.optional()
|
|
24
29
|
.describe('Scope slot-finding to a single workspace (get IDs via whoami).'),
|
|
30
|
+
strategy: z
|
|
31
|
+
.enum(['next_free', 'best_engagement'])
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('next_free (default): earliest free slots. best_engagement: best times by engagement (requires account_ids).'),
|
|
25
34
|
}),
|
|
26
35
|
async execute(client, input) {
|
|
36
|
+
if (input.strategy === 'best_engagement') {
|
|
37
|
+
const accountIds = (input.account_ids || []).filter(Boolean);
|
|
38
|
+
if (accountIds.length === 0) {
|
|
39
|
+
throw new Error('strategy "best_engagement" needs account_ids (get them from list_accounts).');
|
|
40
|
+
}
|
|
41
|
+
const result = await client.findSuggestedSlots({
|
|
42
|
+
account_ids: accountIds,
|
|
43
|
+
timezone: input.timezone,
|
|
44
|
+
count: input.count,
|
|
45
|
+
workspace_id: input.workspace_id,
|
|
46
|
+
});
|
|
47
|
+
const slots = Array.isArray(result.slots) ? result.slots : [];
|
|
48
|
+
if (slots.length === 0) {
|
|
49
|
+
return mdEmpty('suggested slots', 'No suggested slots are free in the next 7 days.');
|
|
50
|
+
}
|
|
51
|
+
const basis = result.basis_label || SUGGESTED_BASIS_LABELS[result.basis] || 'Good default';
|
|
52
|
+
return [
|
|
53
|
+
mdTitle(`Best times to post (${slots.length})`),
|
|
54
|
+
`Basis: ${basis}${result.timezone ? ` (${result.timezone})` : ''}`,
|
|
55
|
+
// local_time already carries the date in the requested timezone, so
|
|
56
|
+
// no separate Date column (a machine-local date could disagree).
|
|
57
|
+
mdTable(['Option', 'Local time', 'Why'], slots.map((slot, index) => [
|
|
58
|
+
index + 1,
|
|
59
|
+
slot.local_time,
|
|
60
|
+
slot.reason || 'n/a',
|
|
61
|
+
])),
|
|
62
|
+
].join('\n\n');
|
|
63
|
+
}
|
|
27
64
|
const slots = await client.findAvailableSlots(input);
|
|
28
65
|
if (slots.length === 0) {
|
|
29
66
|
return mdEmpty('available slots', 'No available posting slots were found in the next 14 days.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posterly-mcp-server",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.3",
|
|
4
4
|
"mcpName": "io.github.awpthorp/posterly",
|
|
5
5
|
"description": "MCP server for posterly: schedule and publish social media posts across 18 platforms from any MCP client (Claude, ChatGPT, Cursor, Windsurf, Cline, and more)",
|
|
6
6
|
"license": "MIT",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.awpthorp/posterly",
|
|
4
4
|
"title": "posterly",
|
|
5
5
|
"description": "Validate, schedule, publish, and analyze social content across 18 platforms with posterly.",
|
|
6
|
-
"version": "0.48.
|
|
6
|
+
"version": "0.48.3",
|
|
7
7
|
"websiteUrl": "https://www.poster.ly/mcp",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/awpthorp/posterly",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
{
|
|
15
15
|
"registryType": "npm",
|
|
16
16
|
"identifier": "posterly-mcp-server",
|
|
17
|
-
"version": "0.48.
|
|
17
|
+
"version": "0.48.3",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
},
|
package/src/lib/api-client.ts
CHANGED
|
@@ -606,6 +606,20 @@ export interface Slot {
|
|
|
606
606
|
local_time: string;
|
|
607
607
|
}
|
|
608
608
|
|
|
609
|
+
export type SuggestedSlotBasis = 'yours' | 'platform' | 'default';
|
|
610
|
+
|
|
611
|
+
export interface SuggestedSlot extends Slot {
|
|
612
|
+
basis: SuggestedSlotBasis;
|
|
613
|
+
reason: string;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export interface SuggestedSlotsResponse {
|
|
617
|
+
timezone: string;
|
|
618
|
+
basis: SuggestedSlotBasis;
|
|
619
|
+
basis_label: string;
|
|
620
|
+
slots: SuggestedSlot[];
|
|
621
|
+
}
|
|
622
|
+
|
|
609
623
|
export interface Workspace {
|
|
610
624
|
id: string;
|
|
611
625
|
name: string;
|
|
@@ -1788,6 +1802,21 @@ export class PosterlyClient {
|
|
|
1788
1802
|
return data.slots;
|
|
1789
1803
|
}
|
|
1790
1804
|
|
|
1805
|
+
/** Best times by engagement for the given accounts (GET /slots/suggested). */
|
|
1806
|
+
async findSuggestedSlots(params: {
|
|
1807
|
+
account_ids: string[];
|
|
1808
|
+
timezone?: string;
|
|
1809
|
+
count?: number;
|
|
1810
|
+
workspace_id?: string;
|
|
1811
|
+
}): Promise<SuggestedSlotsResponse> {
|
|
1812
|
+
const searchParams = new URLSearchParams();
|
|
1813
|
+
searchParams.set('account_ids', params.account_ids.join(','));
|
|
1814
|
+
if (params.timezone) searchParams.set('timezone', params.timezone);
|
|
1815
|
+
if (params.count) searchParams.set('count', String(params.count));
|
|
1816
|
+
if (params.workspace_id) searchParams.set('workspace_id', params.workspace_id);
|
|
1817
|
+
return this.request<SuggestedSlotsResponse>('GET', `/slots/suggested?${searchParams.toString()}`);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1791
1820
|
async getAccountAnalytics(params: {
|
|
1792
1821
|
account_id: number;
|
|
1793
1822
|
from?: string;
|
package/src/lib/version.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// tool set (minus the intentional pre-auth signup tools that only this stdio
|
|
6
6
|
// package exposes). `npm run check:mcp-parity` enforces both the version match
|
|
7
7
|
// and the tool-list match, and runs in the pre-commit hook.
|
|
8
|
-
export const POSTERLY_MCP_VERSION = '0.48.
|
|
8
|
+
export const POSTERLY_MCP_VERSION = '0.48.3';
|
package/src/tools/find-slot.ts
CHANGED
|
@@ -2,10 +2,16 @@ import { z } from 'zod';
|
|
|
2
2
|
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
3
|
import { formatDate, mdEmpty, mdTable, mdTitle } from '../lib/format.js';
|
|
4
4
|
|
|
5
|
+
const SUGGESTED_BASIS_LABELS: Record<string, string> = {
|
|
6
|
+
yours: 'Based on your posts',
|
|
7
|
+
platform: 'Popular on this platform',
|
|
8
|
+
default: 'Good default',
|
|
9
|
+
};
|
|
10
|
+
|
|
5
11
|
export const findSlotTool = {
|
|
6
12
|
name: 'find_available_slot',
|
|
7
13
|
description:
|
|
8
|
-
'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.',
|
|
14
|
+
'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. strategy "best_engagement" (needs account_ids) ranks the next 7 days by when these accounts get the most engagement, with a reason per slot.',
|
|
9
15
|
inputSchema: z.object({
|
|
10
16
|
account_ids: z
|
|
11
17
|
.array(z.string())
|
|
@@ -20,11 +26,15 @@ export const findSlotTool = {
|
|
|
20
26
|
.min(1)
|
|
21
27
|
.max(10)
|
|
22
28
|
.optional()
|
|
23
|
-
.describe('Number of slots to return (
|
|
29
|
+
.describe('Number of slots to return (max 10). Default 5 for next_free, 3 for best_engagement.'),
|
|
24
30
|
workspace_id: z
|
|
25
31
|
.string()
|
|
26
32
|
.optional()
|
|
27
33
|
.describe('Scope slot-finding to a single workspace (get IDs via whoami).'),
|
|
34
|
+
strategy: z
|
|
35
|
+
.enum(['next_free', 'best_engagement'])
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('next_free (default): earliest free slots. best_engagement: best times by engagement (requires account_ids).'),
|
|
28
38
|
}),
|
|
29
39
|
|
|
30
40
|
async execute(
|
|
@@ -34,8 +44,41 @@ export const findSlotTool = {
|
|
|
34
44
|
timezone?: string;
|
|
35
45
|
count?: number;
|
|
36
46
|
workspace_id?: string;
|
|
47
|
+
strategy?: 'next_free' | 'best_engagement';
|
|
37
48
|
}
|
|
38
49
|
) {
|
|
50
|
+
if (input.strategy === 'best_engagement') {
|
|
51
|
+
const accountIds = (input.account_ids || []).filter(Boolean);
|
|
52
|
+
if (accountIds.length === 0) {
|
|
53
|
+
throw new Error('strategy "best_engagement" needs account_ids (get them from list_accounts).');
|
|
54
|
+
}
|
|
55
|
+
const result = await client.findSuggestedSlots({
|
|
56
|
+
account_ids: accountIds,
|
|
57
|
+
timezone: input.timezone,
|
|
58
|
+
count: input.count,
|
|
59
|
+
workspace_id: input.workspace_id,
|
|
60
|
+
});
|
|
61
|
+
const slots = Array.isArray(result.slots) ? result.slots : [];
|
|
62
|
+
if (slots.length === 0) {
|
|
63
|
+
return mdEmpty('suggested slots', 'No suggested slots are free in the next 7 days.');
|
|
64
|
+
}
|
|
65
|
+
const basis = result.basis_label || SUGGESTED_BASIS_LABELS[result.basis] || 'Good default';
|
|
66
|
+
return [
|
|
67
|
+
mdTitle(`Best times to post (${slots.length})`),
|
|
68
|
+
`Basis: ${basis}${result.timezone ? ` (${result.timezone})` : ''}`,
|
|
69
|
+
// local_time already carries the date in the requested timezone, so
|
|
70
|
+
// no separate Date column (a machine-local date could disagree).
|
|
71
|
+
mdTable(
|
|
72
|
+
['Option', 'Local time', 'Why'],
|
|
73
|
+
slots.map((slot, index) => [
|
|
74
|
+
index + 1,
|
|
75
|
+
slot.local_time,
|
|
76
|
+
slot.reason || 'n/a',
|
|
77
|
+
]),
|
|
78
|
+
),
|
|
79
|
+
].join('\n\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
39
82
|
const slots = await client.findAvailableSlots(input);
|
|
40
83
|
|
|
41
84
|
if (slots.length === 0) {
|