posterly-mcp-server 0.36.0 → 0.37.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 +21 -1
- package/SUBMISSIONS.md +73 -0
- package/dist/index.js +80 -0
- package/dist/lib/api-client.d.ts +73 -0
- package/dist/lib/api-client.js +73 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/get-comment.d.ts +20 -0
- package/dist/tools/get-comment.js +36 -0
- package/dist/tools/get-conversation.d.ts +24 -0
- package/dist/tools/get-conversation.js +31 -0
- package/dist/tools/list-comments.d.ts +40 -0
- package/dist/tools/list-comments.js +43 -0
- package/dist/tools/list-conversations.d.ts +40 -0
- package/dist/tools/list-conversations.js +35 -0
- package/dist/tools/reply-to-comment.d.ts +32 -0
- package/dist/tools/reply-to-comment.js +17 -0
- package/dist/tools/send-message.d.ts +28 -0
- package/dist/tools/send-message.js +16 -0
- package/dist/tools/sync-inbox.d.ts +24 -0
- package/dist/tools/sync-inbox.js +18 -0
- package/dist/tools/update-comment.d.ts +28 -0
- package/dist/tools/update-comment.js +21 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/index.ts +128 -0
- package/src/lib/api-client.ts +144 -0
- package/src/lib/version.ts +1 -1
- package/src/tools/get-comment.ts +47 -0
- package/src/tools/get-conversation.ts +44 -0
- package/src/tools/list-comments.ts +59 -0
- package/src/tools/list-conversations.ts +54 -0
- package/src/tools/reply-to-comment.ts +30 -0
- package/src/tools/send-message.ts +28 -0
- package/src/tools/sync-inbox.ts +28 -0
- package/src/tools/update-comment.ts +31 -0
- package/tool-annotations.json +21 -4
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { truncateText } from '../lib/format.js';
|
|
3
|
+
const INBOX_PLATFORMS = ['instagram', 'facebook', 'threads'];
|
|
4
|
+
export const listConversationsTool = {
|
|
5
|
+
name: 'list_conversations',
|
|
6
|
+
description: 'List social inbox DM conversations for Instagram and Facebook Page inboxes. Filter by account, platform, unread/starred/archived, or search. Requires a Pro plan or higher plus posts:read. Threads has no DMs.',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
|
|
9
|
+
account_id: z.number().int().positive().optional().describe('Social account ID from list_accounts.'),
|
|
10
|
+
platform: z.enum(INBOX_PLATFORMS).optional().describe('instagram or facebook. Threads has no DMs.'),
|
|
11
|
+
filter: z.enum(['all', 'unread', 'starred', 'archived']).optional().describe('Conversation filter. Defaults to all (excludes archived).'),
|
|
12
|
+
search: z.string().optional().describe('Search participant username or last message preview.'),
|
|
13
|
+
limit: z.number().int().min(1).max(100).optional().describe('Number of conversations to return. Default 50, max 100.'),
|
|
14
|
+
offset: z.number().int().min(0).optional().describe('Skip this many conversations before returning results.'),
|
|
15
|
+
}),
|
|
16
|
+
async execute(client, input) {
|
|
17
|
+
const result = await client.listConversations(input);
|
|
18
|
+
if (result.conversations.length === 0) {
|
|
19
|
+
return 'No inbox conversations found matching your filters.';
|
|
20
|
+
}
|
|
21
|
+
const lines = [`Inbox conversations (showing ${result.conversations.length} of ${result.total}):`];
|
|
22
|
+
for (const conversation of result.conversations) {
|
|
23
|
+
const username = typeof conversation.participant_username === 'string' ? `@${conversation.participant_username}` : 'unknown';
|
|
24
|
+
const platform = typeof conversation.platform === 'string' ? conversation.platform : 'unknown';
|
|
25
|
+
const unread = conversation.is_read === false ? 'unread' : 'read';
|
|
26
|
+
const preview = truncateText(typeof conversation.last_message_preview === 'string' ? conversation.last_message_preview : '', 80);
|
|
27
|
+
lines.push(`- ${String(conversation.id)} (${platform}, ${username}, ${unread}): ${preview}`);
|
|
28
|
+
}
|
|
29
|
+
if (result.hasMore) {
|
|
30
|
+
const nextOffset = (input.offset ?? 0) + result.conversations.length;
|
|
31
|
+
lines.push('', `More conversations available. Call again with offset: ${nextOffset}.`);
|
|
32
|
+
}
|
|
33
|
+
return lines.join('\n');
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const replyToCommentTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
comment_id: z.ZodString;
|
|
8
|
+
content: z.ZodString;
|
|
9
|
+
type: z.ZodOptional<z.ZodEnum<["public", "private"]>>;
|
|
10
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
11
|
+
confirm: z.ZodLiteral<true>;
|
|
12
|
+
}, "strip", z.ZodTypeAny, {
|
|
13
|
+
content: string;
|
|
14
|
+
confirm: true;
|
|
15
|
+
comment_id: string;
|
|
16
|
+
workspace_id?: string | undefined;
|
|
17
|
+
type?: "public" | "private" | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
content: string;
|
|
20
|
+
confirm: true;
|
|
21
|
+
comment_id: string;
|
|
22
|
+
workspace_id?: string | undefined;
|
|
23
|
+
type?: "public" | "private" | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
execute(client: PosterlyClient, input: {
|
|
26
|
+
comment_id: string;
|
|
27
|
+
content: string;
|
|
28
|
+
type?: "public" | "private";
|
|
29
|
+
workspace_id?: string;
|
|
30
|
+
confirm: true;
|
|
31
|
+
}): Promise<string>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const replyToCommentTool = {
|
|
3
|
+
name: 'reply_to_comment',
|
|
4
|
+
description: 'Reply to an Instagram, Facebook Page, or Threads inbox comment. WRITE: posts to the social network. Facebook has no private comment replies. Threads has no DMs, so only public replies. Instagram supports public or private. Requires a Pro plan or higher plus posts:write. Pass confirm=true only after explicit user confirmation.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
comment_id: z.string().min(1).describe('Comment ID from list_comments.'),
|
|
7
|
+
content: z.string().min(1).describe('Reply text to post.'),
|
|
8
|
+
type: z.enum(['public', 'private']).optional().describe('public (default) or private. Private is Instagram only.'),
|
|
9
|
+
workspace_id: z.string().optional().describe('Workspace ID from whoami.'),
|
|
10
|
+
confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
|
|
11
|
+
}),
|
|
12
|
+
async execute(client, input) {
|
|
13
|
+
const { confirm: _confirm, ...payload } = input;
|
|
14
|
+
const result = await client.replyToComment(payload);
|
|
15
|
+
return `Comment reply sent (${result.reply_type}, reply_id ${result.reply_id}).`;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const sendMessageTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
conversation_id: z.ZodString;
|
|
8
|
+
content: z.ZodString;
|
|
9
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
10
|
+
confirm: z.ZodLiteral<true>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
content: string;
|
|
13
|
+
confirm: true;
|
|
14
|
+
conversation_id: string;
|
|
15
|
+
workspace_id?: string | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
content: string;
|
|
18
|
+
confirm: true;
|
|
19
|
+
conversation_id: string;
|
|
20
|
+
workspace_id?: string | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
execute(client: PosterlyClient, input: {
|
|
23
|
+
conversation_id: string;
|
|
24
|
+
content: string;
|
|
25
|
+
workspace_id?: string;
|
|
26
|
+
confirm: true;
|
|
27
|
+
}): Promise<string>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const sendMessageTool = {
|
|
3
|
+
name: 'send_message',
|
|
4
|
+
description: 'Send a DM reply in an Instagram or Facebook Page inbox conversation. WRITE: posts to the social network. Honor the 24-hour messaging window. Threads has no DMs. Requires a Pro plan or higher plus posts:write. Pass confirm=true only after explicit user confirmation.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
conversation_id: z.string().min(1).describe('Conversation ID from list_conversations.'),
|
|
7
|
+
content: z.string().min(1).describe('Message text to send.'),
|
|
8
|
+
workspace_id: z.string().optional().describe('Workspace ID from whoami.'),
|
|
9
|
+
confirm: z.literal(true).describe('Must be true after explicit user confirmation.'),
|
|
10
|
+
}),
|
|
11
|
+
async execute(client, input) {
|
|
12
|
+
const { confirm: _confirm, ...payload } = input;
|
|
13
|
+
const result = await client.sendMessage(payload);
|
|
14
|
+
return `Message sent (message_id ${result.message_id}).`;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const syncInboxTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
account_id: z.ZodNumber;
|
|
8
|
+
sync_type: z.ZodOptional<z.ZodEnum<["all", "dm", "comments"]>>;
|
|
9
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
account_id: number;
|
|
12
|
+
workspace_id?: string | undefined;
|
|
13
|
+
sync_type?: "all" | "dm" | "comments" | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
account_id: number;
|
|
16
|
+
workspace_id?: string | undefined;
|
|
17
|
+
sync_type?: "all" | "dm" | "comments" | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
execute(client: PosterlyClient, input: {
|
|
20
|
+
account_id: number;
|
|
21
|
+
sync_type?: "all" | "dm" | "comments";
|
|
22
|
+
workspace_id?: string;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const syncInboxTool = {
|
|
3
|
+
name: 'sync_inbox',
|
|
4
|
+
description: 'Pull the latest Instagram, Facebook Page, or Threads inbox data for one connected account. Honors a 30-second cooldown. Threads syncs replies only (no DMs). Requires a Pro plan or higher plus posts:write.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
account_id: z.number().int().positive().describe('Social account ID from list_accounts.'),
|
|
7
|
+
sync_type: z.enum(['all', 'dm', 'comments']).optional().describe('What to sync. Defaults to all. Threads is comments-only.'),
|
|
8
|
+
workspace_id: z.string().optional().describe('Workspace ID from whoami.'),
|
|
9
|
+
}),
|
|
10
|
+
async execute(client, input) {
|
|
11
|
+
const result = await client.syncInbox(input);
|
|
12
|
+
const parts = ['Inbox sync completed.'];
|
|
13
|
+
const keys = Object.keys(result.results || {});
|
|
14
|
+
if (keys.length > 0)
|
|
15
|
+
parts.push(`Synced: ${keys.join(', ')}.`);
|
|
16
|
+
return parts.join(' ');
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const updateCommentTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
comment_id: z.ZodString;
|
|
8
|
+
is_hidden: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
is_read: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
comment_id: string;
|
|
13
|
+
workspace_id?: string | undefined;
|
|
14
|
+
is_hidden?: boolean | undefined;
|
|
15
|
+
is_read?: boolean | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
comment_id: string;
|
|
18
|
+
workspace_id?: string | undefined;
|
|
19
|
+
is_hidden?: boolean | undefined;
|
|
20
|
+
is_read?: boolean | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
execute(client: PosterlyClient, input: {
|
|
23
|
+
comment_id: string;
|
|
24
|
+
is_hidden?: boolean;
|
|
25
|
+
is_read?: boolean;
|
|
26
|
+
workspace_id?: string;
|
|
27
|
+
}): Promise<string>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const updateCommentTool = {
|
|
3
|
+
name: 'update_comment',
|
|
4
|
+
description: 'Update an inbox comment: hide/unhide it on the platform, or mark it read/unread. Hide is reversible and does not delete the comment. Requires a Pro plan or higher plus posts:write.',
|
|
5
|
+
inputSchema: z.object({
|
|
6
|
+
comment_id: z.string().min(1).describe('Comment ID from list_comments.'),
|
|
7
|
+
is_hidden: z.boolean().optional().describe('Hide or unhide the comment on Instagram, Facebook, or Threads.'),
|
|
8
|
+
is_read: z.boolean().optional().describe('Mark the comment read or unread in posterly.'),
|
|
9
|
+
workspace_id: z.string().optional().describe('Workspace ID from whoami.'),
|
|
10
|
+
}),
|
|
11
|
+
async execute(client, input) {
|
|
12
|
+
const result = await client.updateComment(input);
|
|
13
|
+
const comment = result.comment;
|
|
14
|
+
const parts = [`Comment ${String(comment.id)} updated.`];
|
|
15
|
+
if (typeof comment.is_hidden === 'boolean')
|
|
16
|
+
parts.push(`Hidden: ${comment.is_hidden ? 'yes' : 'no'}.`);
|
|
17
|
+
if (typeof comment.is_read === 'boolean')
|
|
18
|
+
parts.push(`Read: ${comment.is_read ? 'yes' : 'no'}.`);
|
|
19
|
+
return parts.join(' ');
|
|
20
|
+
},
|
|
21
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "posterly-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
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.
|
|
6
|
+
"version": "0.37.0",
|
|
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.
|
|
17
|
+
"version": "0.37.0",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
},
|
package/src/index.ts
CHANGED
|
@@ -54,6 +54,14 @@ import { deleteGoogleBusinessReviewReplyTool } from './tools/delete-google-busin
|
|
|
54
54
|
import { listGoogleBusinessMediaTool } from './tools/list-google-business-media.js';
|
|
55
55
|
import { addGoogleBusinessMediaTool } from './tools/add-google-business-media.js';
|
|
56
56
|
import { deleteGoogleBusinessMediaTool } from './tools/delete-google-business-media.js';
|
|
57
|
+
import { listConversationsTool } from './tools/list-conversations.js';
|
|
58
|
+
import { getConversationTool } from './tools/get-conversation.js';
|
|
59
|
+
import { sendMessageTool } from './tools/send-message.js';
|
|
60
|
+
import { listCommentsTool } from './tools/list-comments.js';
|
|
61
|
+
import { getCommentTool } from './tools/get-comment.js';
|
|
62
|
+
import { replyToCommentTool } from './tools/reply-to-comment.js';
|
|
63
|
+
import { updateCommentTool } from './tools/update-comment.js';
|
|
64
|
+
import { syncInboxTool } from './tools/sync-inbox.js';
|
|
57
65
|
import { listActivityTool } from './tools/list-activity.js';
|
|
58
66
|
import { getUpdatesTool } from './tools/get-updates.js';
|
|
59
67
|
import { listWebhooksTool } from './tools/list-webhooks.js';
|
|
@@ -1142,6 +1150,126 @@ server.tool(
|
|
|
1142
1150
|
}
|
|
1143
1151
|
);
|
|
1144
1152
|
|
|
1153
|
+
server.tool(
|
|
1154
|
+
listConversationsTool.name,
|
|
1155
|
+
listConversationsTool.description,
|
|
1156
|
+
listConversationsTool.inputSchema.shape,
|
|
1157
|
+
getToolAnnotations(listConversationsTool.name),
|
|
1158
|
+
async (input) => {
|
|
1159
|
+
try {
|
|
1160
|
+
const text = await listConversationsTool.execute(client, input as any);
|
|
1161
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1162
|
+
} catch (err: any) {
|
|
1163
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
server.tool(
|
|
1169
|
+
getConversationTool.name,
|
|
1170
|
+
getConversationTool.description,
|
|
1171
|
+
getConversationTool.inputSchema.shape,
|
|
1172
|
+
getToolAnnotations(getConversationTool.name),
|
|
1173
|
+
async (input) => {
|
|
1174
|
+
try {
|
|
1175
|
+
const text = await getConversationTool.execute(client, input as any);
|
|
1176
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1177
|
+
} catch (err: any) {
|
|
1178
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
);
|
|
1182
|
+
|
|
1183
|
+
server.tool(
|
|
1184
|
+
sendMessageTool.name,
|
|
1185
|
+
sendMessageTool.description,
|
|
1186
|
+
sendMessageTool.inputSchema.shape,
|
|
1187
|
+
getToolAnnotations(sendMessageTool.name),
|
|
1188
|
+
async (input) => {
|
|
1189
|
+
try {
|
|
1190
|
+
const text = await sendMessageTool.execute(client, input as any);
|
|
1191
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1192
|
+
} catch (err: any) {
|
|
1193
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
);
|
|
1197
|
+
|
|
1198
|
+
server.tool(
|
|
1199
|
+
listCommentsTool.name,
|
|
1200
|
+
listCommentsTool.description,
|
|
1201
|
+
listCommentsTool.inputSchema.shape,
|
|
1202
|
+
getToolAnnotations(listCommentsTool.name),
|
|
1203
|
+
async (input) => {
|
|
1204
|
+
try {
|
|
1205
|
+
const text = await listCommentsTool.execute(client, input as any);
|
|
1206
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1207
|
+
} catch (err: any) {
|
|
1208
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
server.tool(
|
|
1214
|
+
getCommentTool.name,
|
|
1215
|
+
getCommentTool.description,
|
|
1216
|
+
getCommentTool.inputSchema.shape,
|
|
1217
|
+
getToolAnnotations(getCommentTool.name),
|
|
1218
|
+
async (input) => {
|
|
1219
|
+
try {
|
|
1220
|
+
const text = await getCommentTool.execute(client, input as any);
|
|
1221
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1222
|
+
} catch (err: any) {
|
|
1223
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
);
|
|
1227
|
+
|
|
1228
|
+
server.tool(
|
|
1229
|
+
replyToCommentTool.name,
|
|
1230
|
+
replyToCommentTool.description,
|
|
1231
|
+
replyToCommentTool.inputSchema.shape,
|
|
1232
|
+
getToolAnnotations(replyToCommentTool.name),
|
|
1233
|
+
async (input) => {
|
|
1234
|
+
try {
|
|
1235
|
+
const text = await replyToCommentTool.execute(client, input as any);
|
|
1236
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1237
|
+
} catch (err: any) {
|
|
1238
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
);
|
|
1242
|
+
|
|
1243
|
+
server.tool(
|
|
1244
|
+
updateCommentTool.name,
|
|
1245
|
+
updateCommentTool.description,
|
|
1246
|
+
updateCommentTool.inputSchema.shape,
|
|
1247
|
+
getToolAnnotations(updateCommentTool.name),
|
|
1248
|
+
async (input) => {
|
|
1249
|
+
try {
|
|
1250
|
+
const text = await updateCommentTool.execute(client, input as any);
|
|
1251
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1252
|
+
} catch (err: any) {
|
|
1253
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
);
|
|
1257
|
+
|
|
1258
|
+
server.tool(
|
|
1259
|
+
syncInboxTool.name,
|
|
1260
|
+
syncInboxTool.description,
|
|
1261
|
+
syncInboxTool.inputSchema.shape,
|
|
1262
|
+
getToolAnnotations(syncInboxTool.name),
|
|
1263
|
+
async (input) => {
|
|
1264
|
+
try {
|
|
1265
|
+
const text = await syncInboxTool.execute(client, input as any);
|
|
1266
|
+
return { content: [{ type: 'text' as const, text }] };
|
|
1267
|
+
} catch (err: any) {
|
|
1268
|
+
return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
);
|
|
1272
|
+
|
|
1145
1273
|
server.tool(
|
|
1146
1274
|
listActivityTool.name,
|
|
1147
1275
|
listActivityTool.description,
|
package/src/lib/api-client.ts
CHANGED
|
@@ -629,6 +629,53 @@ export interface GoogleBusinessReviewLinkResponse {
|
|
|
629
629
|
account_id: number;
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
+
export interface InboxListParams {
|
|
633
|
+
workspace_id?: string;
|
|
634
|
+
account_id?: number | number[];
|
|
635
|
+
platform?: string | string[];
|
|
636
|
+
filter?: string;
|
|
637
|
+
search?: string;
|
|
638
|
+
limit?: number;
|
|
639
|
+
offset?: number;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export interface InboxConversationsResponse {
|
|
643
|
+
conversations: Array<Record<string, unknown>>;
|
|
644
|
+
total: number;
|
|
645
|
+
hasMore: boolean;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export interface InboxConversationResponse {
|
|
649
|
+
conversation: Record<string, unknown>;
|
|
650
|
+
messages: Array<Record<string, unknown>>;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export interface InboxCommentsResponse {
|
|
654
|
+
comments: Array<Record<string, unknown>>;
|
|
655
|
+
total: number;
|
|
656
|
+
hasMore: boolean;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
export interface InboxCommentResponse {
|
|
660
|
+
comment: Record<string, unknown>;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export interface InboxSendMessageResponse {
|
|
664
|
+
success: true;
|
|
665
|
+
message_id: string;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export interface InboxCommentReplyResponse {
|
|
669
|
+
success: true;
|
|
670
|
+
reply_type: string;
|
|
671
|
+
reply_id: string;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export interface InboxSyncResponse {
|
|
675
|
+
success: true;
|
|
676
|
+
results: Record<string, unknown>;
|
|
677
|
+
}
|
|
678
|
+
|
|
632
679
|
export interface ActivityItem {
|
|
633
680
|
id: string;
|
|
634
681
|
type: 'activity' | 'publish';
|
|
@@ -1725,6 +1772,103 @@ export class PosterlyClient {
|
|
|
1725
1772
|
content_type: input.contentType,
|
|
1726
1773
|
});
|
|
1727
1774
|
}
|
|
1775
|
+
|
|
1776
|
+
private appendInboxListParams(searchParams: URLSearchParams, params?: InboxListParams) {
|
|
1777
|
+
if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
|
|
1778
|
+
const accountIds = params?.account_id == null ? [] : Array.isArray(params.account_id) ? params.account_id : [params.account_id];
|
|
1779
|
+
for (const accountId of accountIds) searchParams.append('account_id', String(accountId));
|
|
1780
|
+
const platforms = params?.platform == null ? [] : Array.isArray(params.platform) ? params.platform : [params.platform];
|
|
1781
|
+
for (const platform of platforms) searchParams.append('platform', platform);
|
|
1782
|
+
if (params?.filter) searchParams.set('filter', params.filter);
|
|
1783
|
+
if (params?.search) searchParams.set('search', params.search);
|
|
1784
|
+
if (params?.limit) searchParams.set('limit', String(params.limit));
|
|
1785
|
+
if (params?.offset != null) searchParams.set('offset', String(params.offset));
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
async listConversations(params?: InboxListParams): Promise<InboxConversationsResponse> {
|
|
1789
|
+
const searchParams = new URLSearchParams();
|
|
1790
|
+
this.appendInboxListParams(searchParams, params);
|
|
1791
|
+
const qs = searchParams.toString();
|
|
1792
|
+
return this.request('GET', `/inbox/conversations${qs ? `?${qs}` : ''}`);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
async getConversation(params: {
|
|
1796
|
+
conversation_id: string;
|
|
1797
|
+
workspace_id?: string;
|
|
1798
|
+
limit?: number;
|
|
1799
|
+
}): Promise<InboxConversationResponse> {
|
|
1800
|
+
const searchParams = new URLSearchParams();
|
|
1801
|
+
if (params.workspace_id) searchParams.set('workspace_id', params.workspace_id);
|
|
1802
|
+
if (params.limit) searchParams.set('limit', String(params.limit));
|
|
1803
|
+
const qs = searchParams.toString();
|
|
1804
|
+
return this.request(
|
|
1805
|
+
'GET',
|
|
1806
|
+
`/inbox/conversations/${encodeURIComponent(params.conversation_id)}${qs ? `?${qs}` : ''}`,
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
async sendMessage(data: {
|
|
1811
|
+
conversation_id: string;
|
|
1812
|
+
content: string;
|
|
1813
|
+
workspace_id?: string;
|
|
1814
|
+
}): Promise<InboxSendMessageResponse> {
|
|
1815
|
+
const body: Record<string, unknown> = { content: data.content };
|
|
1816
|
+
if (data.workspace_id) body.workspace_id = data.workspace_id;
|
|
1817
|
+
return this.request('POST', `/inbox/conversations/${encodeURIComponent(data.conversation_id)}/reply`, body);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
async listComments(params?: InboxListParams): Promise<InboxCommentsResponse> {
|
|
1821
|
+
const searchParams = new URLSearchParams();
|
|
1822
|
+
this.appendInboxListParams(searchParams, params);
|
|
1823
|
+
const qs = searchParams.toString();
|
|
1824
|
+
return this.request('GET', `/inbox/comments${qs ? `?${qs}` : ''}`);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
async getComment(params: {
|
|
1828
|
+
comment_id: string;
|
|
1829
|
+
workspace_id?: string;
|
|
1830
|
+
}): Promise<InboxCommentResponse> {
|
|
1831
|
+
const searchParams = new URLSearchParams();
|
|
1832
|
+
if (params.workspace_id) searchParams.set('workspace_id', params.workspace_id);
|
|
1833
|
+
const qs = searchParams.toString();
|
|
1834
|
+
return this.request(
|
|
1835
|
+
'GET',
|
|
1836
|
+
`/inbox/comments/${encodeURIComponent(params.comment_id)}${qs ? `?${qs}` : ''}`,
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
async replyToComment(data: {
|
|
1841
|
+
comment_id: string;
|
|
1842
|
+
content: string;
|
|
1843
|
+
type?: 'public' | 'private';
|
|
1844
|
+
workspace_id?: string;
|
|
1845
|
+
}): Promise<InboxCommentReplyResponse> {
|
|
1846
|
+
const body: Record<string, unknown> = { content: data.content };
|
|
1847
|
+
if (data.type) body.type = data.type;
|
|
1848
|
+
if (data.workspace_id) body.workspace_id = data.workspace_id;
|
|
1849
|
+
return this.request('POST', `/inbox/comments/${encodeURIComponent(data.comment_id)}/reply`, body);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
async updateComment(data: {
|
|
1853
|
+
comment_id: string;
|
|
1854
|
+
is_hidden?: boolean;
|
|
1855
|
+
is_read?: boolean;
|
|
1856
|
+
workspace_id?: string;
|
|
1857
|
+
}): Promise<InboxCommentResponse> {
|
|
1858
|
+
const body: Record<string, unknown> = {};
|
|
1859
|
+
if (typeof data.is_hidden === 'boolean') body.is_hidden = data.is_hidden;
|
|
1860
|
+
if (typeof data.is_read === 'boolean') body.is_read = data.is_read;
|
|
1861
|
+
if (data.workspace_id) body.workspace_id = data.workspace_id;
|
|
1862
|
+
return this.request('PATCH', `/inbox/comments/${encodeURIComponent(data.comment_id)}`, body);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
async syncInbox(data: {
|
|
1866
|
+
account_id: number;
|
|
1867
|
+
sync_type?: 'all' | 'dm' | 'comments';
|
|
1868
|
+
workspace_id?: string;
|
|
1869
|
+
}): Promise<InboxSyncResponse> {
|
|
1870
|
+
return this.request('POST', '/inbox/sync', data);
|
|
1871
|
+
}
|
|
1728
1872
|
}
|
|
1729
1873
|
|
|
1730
1874
|
export function resolvePosterlyBaseUrl(baseUrl?: string): 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.
|
|
8
|
+
export const POSTERLY_MCP_VERSION = '0.37.0';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { formatLocalDateTime } from '../lib/format.js';
|
|
4
|
+
|
|
5
|
+
export const getCommentTool = {
|
|
6
|
+
name: 'get_comment',
|
|
7
|
+
description:
|
|
8
|
+
'Get one social inbox comment and its replies. Instagram, Facebook Page, and Threads comments. Requires a Pro plan or higher plus posts:read.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
comment_id: z.string().min(1).describe('Comment ID from list_comments.'),
|
|
11
|
+
workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
|
|
12
|
+
}),
|
|
13
|
+
|
|
14
|
+
async execute(
|
|
15
|
+
client: PosterlyClient,
|
|
16
|
+
input: {
|
|
17
|
+
comment_id: string;
|
|
18
|
+
workspace_id?: string;
|
|
19
|
+
},
|
|
20
|
+
) {
|
|
21
|
+
const result = await client.getComment(input);
|
|
22
|
+
const comment = result.comment;
|
|
23
|
+
const author = typeof comment.author_username === 'string' ? `@${comment.author_username}` : 'unknown';
|
|
24
|
+
const lines = [
|
|
25
|
+
`Comment ${String(comment.id)}`,
|
|
26
|
+
`• Platform: ${typeof comment.platform === 'string' ? comment.platform : 'unknown'}`,
|
|
27
|
+
`• Author: ${author}`,
|
|
28
|
+
`• Hidden: ${comment.is_hidden ? 'yes' : 'no'}`,
|
|
29
|
+
`• Read: ${comment.is_read === false ? 'no' : 'yes'}`,
|
|
30
|
+
`• Posted: ${formatLocalDateTime(typeof comment.posted_at === 'string' ? comment.posted_at : null)}`,
|
|
31
|
+
`• Content: ${typeof comment.content === 'string' ? comment.content : '(empty)'}`,
|
|
32
|
+
];
|
|
33
|
+
if (typeof comment.media_permalink === 'string') lines.push(`• Media: ${comment.media_permalink}`);
|
|
34
|
+
|
|
35
|
+
const replies = Array.isArray(comment.replies) ? comment.replies : [];
|
|
36
|
+
if (replies.length > 0) {
|
|
37
|
+
lines.push('', `Replies (${replies.length}):`);
|
|
38
|
+
for (const reply of replies) {
|
|
39
|
+
const row = reply as Record<string, unknown>;
|
|
40
|
+
const replyAuthor = typeof row.author_username === 'string' ? `@${row.author_username}` : 'unknown';
|
|
41
|
+
lines.push(`- ${String(row.id)} ${replyAuthor}: ${typeof row.content === 'string' ? row.content : '(empty)'}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return lines.join('\n');
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
import { formatLocalDateTime, truncateText } from '../lib/format.js';
|
|
4
|
+
|
|
5
|
+
export const getConversationTool = {
|
|
6
|
+
name: 'get_conversation',
|
|
7
|
+
description:
|
|
8
|
+
'Get one social inbox DM conversation and its messages. Instagram and Facebook Page DMs only. Requires a Pro plan or higher plus posts:read.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
conversation_id: z.string().min(1).describe('Conversation ID from list_conversations.'),
|
|
11
|
+
workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
|
|
12
|
+
limit: z.number().int().min(1).max(100).optional().describe('Maximum messages to return. Default 50, max 100.'),
|
|
13
|
+
}),
|
|
14
|
+
|
|
15
|
+
async execute(
|
|
16
|
+
client: PosterlyClient,
|
|
17
|
+
input: {
|
|
18
|
+
conversation_id: string;
|
|
19
|
+
workspace_id?: string;
|
|
20
|
+
limit?: number;
|
|
21
|
+
},
|
|
22
|
+
) {
|
|
23
|
+
const result = await client.getConversation(input);
|
|
24
|
+
const conversation = result.conversation;
|
|
25
|
+
const username = typeof conversation.participant_username === 'string' ? `@${conversation.participant_username}` : 'unknown';
|
|
26
|
+
const lines = [
|
|
27
|
+
`Conversation ${String(conversation.id)}`,
|
|
28
|
+
`• Platform: ${typeof conversation.platform === 'string' ? conversation.platform : 'unknown'}`,
|
|
29
|
+
`• Participant: ${username}`,
|
|
30
|
+
`• Read: ${conversation.is_read === false ? 'no' : 'yes'}`,
|
|
31
|
+
`• Messages: ${result.messages.length}`,
|
|
32
|
+
'',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const message of result.messages) {
|
|
36
|
+
const who = message.sender_is_business ? 'you' : (typeof message.sender_username === 'string' ? message.sender_username : 'them');
|
|
37
|
+
const when = formatLocalDateTime(typeof message.sent_at === 'string' ? message.sent_at : null);
|
|
38
|
+
const text = truncateText(typeof message.content === 'string' ? message.content : '', 240);
|
|
39
|
+
lines.push(`- [${when}] ${who}: ${text}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
},
|
|
44
|
+
};
|