posterly-mcp-server 0.36.0 → 0.38.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 +22 -1
- package/SUBMISSIONS.md +73 -0
- package/dist/index.js +90 -0
- package/dist/lib/api-client.d.ts +76 -0
- package/dist/lib/api-client.js +80 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/tools/delete-comment.d.ts +24 -0
- package/dist/tools/delete-comment.js +22 -0
- 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 +144 -0
- package/src/lib/api-client.ts +151 -0
- package/src/lib/version.ts +1 -1
- package/src/tools/delete-comment.ts +29 -0
- 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 +25 -4
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ Add the same server definition to your Cursor MCP settings:
|
|
|
122
122
|
|
|
123
123
|
## Available tools
|
|
124
124
|
|
|
125
|
-
`posterly-mcp-server@0.
|
|
125
|
+
`posterly-mcp-server@0.38.0` exposes 87 tools.
|
|
126
126
|
|
|
127
127
|
Public setup tools work before `POSTERLY_API_KEY` exists:
|
|
128
128
|
|
|
@@ -190,6 +190,15 @@ Authenticated tools require `POSTERLY_API_KEY`:
|
|
|
190
190
|
- `get_post_insights` (list per-post feedback-loop insights: performance tier, diagnosis, next action, metrics, baseline)
|
|
191
191
|
- `list_post_suggestions` (list evidence-based weekly post drafts in the account's learned voice, each with a rationale)
|
|
192
192
|
- `dismiss_suggestion` (dismiss a post suggestion after confirmation; never touches one already scheduled)
|
|
193
|
+
- `list_conversations` (Instagram and Facebook Page inbox DMs; Threads has no DMs)
|
|
194
|
+
- `get_conversation`
|
|
195
|
+
- `send_message` (DM reply after explicit confirmation; honors Meta 24-hour windows)
|
|
196
|
+
- `list_comments` (Instagram, Facebook Page, and Threads inbox comments)
|
|
197
|
+
- `get_comment`
|
|
198
|
+
- `reply_to_comment` (comment reply after explicit confirmation)
|
|
199
|
+
- `update_comment` (hide/unhide or mark read; hide is reversible)
|
|
200
|
+
- `delete_comment` (delete an Instagram or Facebook comment after confirmation; Threads replies cannot be deleted)
|
|
201
|
+
- `sync_inbox` (pull latest inbox data for one account; 30-second cooldown)
|
|
193
202
|
- `list_google_business_reviews`
|
|
194
203
|
- `get_google_business_review_link`
|
|
195
204
|
- `audit_google_business_profile`
|
|
@@ -267,6 +276,18 @@ Each API call counts as one request, so use `create_posts_batch` when you need t
|
|
|
267
276
|
|
|
268
277
|
Details: [poster.ly/dashboard/api](https://www.poster.ly/dashboard/api)
|
|
269
278
|
|
|
279
|
+
## Hosted MCP clients (Claude.ai, ChatGPT)
|
|
280
|
+
|
|
281
|
+
Browser-hosted MCP hosts use the HTTP endpoint at [poster.ly/mcp](https://www.poster.ly/mcp) with OAuth instead of an API key. The HTTP endpoint supports RFC 7591 Dynamic Client Registration, so a host can self-register and complete a full connect flow without manual allowlisting:
|
|
282
|
+
|
|
283
|
+
- `POST /api/oauth/register`, send `{ client_name, redirect_uris, scope }`, receive a `client_id`.
|
|
284
|
+
- Standard authorization-code + PKCE at `/oauth/authorize`.
|
|
285
|
+
- 1-hour access tokens with 30-day rotating refresh tokens via `/api/oauth/token`.
|
|
286
|
+
|
|
287
|
+
Discovery document: [/.well-known/oauth-authorization-server](https://www.poster.ly/.well-known/oauth-authorization-server).
|
|
288
|
+
|
|
289
|
+
Local stdio still uses an API key in `POSTERLY_API_KEY`.
|
|
290
|
+
|
|
270
291
|
## Links
|
|
271
292
|
|
|
272
293
|
- Docs: [poster.ly/mcp](https://www.poster.ly/mcp)
|
package/SUBMISSIONS.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# MCP connector submissions
|
|
2
|
+
|
|
3
|
+
Notes for listing the hosted MCP server as a one-click connector. The pipework
|
|
4
|
+
(hosted HTTP MCP + stdio MCP) already works with API keys. Dynamic Client
|
|
5
|
+
Registration and refresh-token rotation are what Claude.ai / ChatGPT-style hosts
|
|
6
|
+
need for a connect flow without a human-allowlisted `client_id`.
|
|
7
|
+
|
|
8
|
+
Do not publish these notes as user-facing marketing copy. They are for the
|
|
9
|
+
person who applies the production migration and files the directory forms.
|
|
10
|
+
|
|
11
|
+
## Preconditions
|
|
12
|
+
|
|
13
|
+
1. Apply the DCR / refresh-token migration
|
|
14
|
+
(`supabase/migrations/20260906140000_oauth_dynamic_registration_and_refresh.sql`)
|
|
15
|
+
**before or with** the production deploy, not after. Shipping the app first
|
|
16
|
+
leaves `oauth_refresh_tokens` missing: the token endpoint then falls back to
|
|
17
|
+
a 365-day access-only grant for dynamic clients, and DCR verification that
|
|
18
|
+
expects refresh tokens will fail. Do not apply the SQL from an agent session
|
|
19
|
+
that can write straight to prod.
|
|
20
|
+
2. Confirm `NEXT_PUBLIC_SITE_URL` is `https://www.poster.ly` in the production
|
|
21
|
+
deploy so discovery documents advertise the right issuer.
|
|
22
|
+
3. Create a reviewer test account with the API add-on enabled, plus a short
|
|
23
|
+
setup note they can follow.
|
|
24
|
+
4. Confirm discovery before submitting:
|
|
25
|
+
- `GET /.well-known/oauth-authorization-server` includes
|
|
26
|
+
`registration_endpoint` and `refresh_token` in `grant_types_supported`
|
|
27
|
+
- `POST /api/oauth/register` returns `201` with a `dyn_` `client_id`
|
|
28
|
+
- consent shows the self-registered warning, callback host, and `client_id`
|
|
29
|
+
for that client
|
|
30
|
+
- refresh-token reuse within 30 seconds returns `invalid_grant` without
|
|
31
|
+
revoking the chain; reuse older than 30 seconds revokes the chain
|
|
32
|
+
- `/api/mcp` returns `403` `origin_not_allowed` for an untrusted Origin
|
|
33
|
+
- `tools/list` includes annotations on every tool
|
|
34
|
+
|
|
35
|
+
## Anthropic Connectors Directory
|
|
36
|
+
|
|
37
|
+
Submit the remote MCP server at https://clau.de/mcp-directory-submission.
|
|
38
|
+
|
|
39
|
+
Fallback contact: mcp-review@anthropic.com.
|
|
40
|
+
|
|
41
|
+
Use:
|
|
42
|
+
|
|
43
|
+
- Server URL: `https://www.poster.ly/api/mcp`
|
|
44
|
+
- Server card: `https://www.poster.ly/.well-known/mcp/server-card.json`
|
|
45
|
+
- Docs: `https://www.poster.ly/mcp` and `https://www.poster.ly/agents`
|
|
46
|
+
(public pages only; do not submit auth-gated `https://www.poster.ly/docs/...`
|
|
47
|
+
URLs)
|
|
48
|
+
- Auth: OAuth 2.1 + PKCE, RFC 7591 Dynamic Client Registration, rotating
|
|
49
|
+
refresh tokens (1-hour access, 30-day refresh for DCR clients)
|
|
50
|
+
- Platforms: all 18 production platforms
|
|
51
|
+
|
|
52
|
+
Do not claim Autopilot ships. Do not claim the connector is already listed.
|
|
53
|
+
|
|
54
|
+
Optional after deploy: bump and publish `posterly-mcp-server` if you want the
|
|
55
|
+
stdio README changes on npm. Hosted `/api/mcp` picks up this repo deploy
|
|
56
|
+
without an npm publish.
|
|
57
|
+
|
|
58
|
+
## Other hosted hosts
|
|
59
|
+
|
|
60
|
+
ChatGPT-style hosts follow the same DCR + authorize + token flow. HTTPS
|
|
61
|
+
callbacks such as `https://chatgpt.com/oauth/callback` are already accepted by
|
|
62
|
+
the redirect URI validator. Extra browser origins can be added with
|
|
63
|
+
`MCP_TRUSTED_ORIGINS` (comma-separated hostnames or full URLs).
|
|
64
|
+
|
|
65
|
+
The ChatGPT plugin / app directory is a separate OpenAI form. File it after
|
|
66
|
+
this OAuth DCR deploy is live; it is not covered by the Anthropic connector
|
|
67
|
+
submission.
|
|
68
|
+
|
|
69
|
+
## Registry metadata
|
|
70
|
+
|
|
71
|
+
`server.json` is the official MCP Registry metadata for the stdio package
|
|
72
|
+
(`io.github.awpthorp/posterly`). Publishing that package is a separate release
|
|
73
|
+
step. See `RELEASING.md`.
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,15 @@ import { deleteGoogleBusinessReviewReplyTool } from './tools/delete-google-busin
|
|
|
53
53
|
import { listGoogleBusinessMediaTool } from './tools/list-google-business-media.js';
|
|
54
54
|
import { addGoogleBusinessMediaTool } from './tools/add-google-business-media.js';
|
|
55
55
|
import { deleteGoogleBusinessMediaTool } from './tools/delete-google-business-media.js';
|
|
56
|
+
import { listConversationsTool } from './tools/list-conversations.js';
|
|
57
|
+
import { getConversationTool } from './tools/get-conversation.js';
|
|
58
|
+
import { sendMessageTool } from './tools/send-message.js';
|
|
59
|
+
import { listCommentsTool } from './tools/list-comments.js';
|
|
60
|
+
import { getCommentTool } from './tools/get-comment.js';
|
|
61
|
+
import { replyToCommentTool } from './tools/reply-to-comment.js';
|
|
62
|
+
import { updateCommentTool } from './tools/update-comment.js';
|
|
63
|
+
import { deleteCommentTool } from './tools/delete-comment.js';
|
|
64
|
+
import { syncInboxTool } from './tools/sync-inbox.js';
|
|
56
65
|
import { listActivityTool } from './tools/list-activity.js';
|
|
57
66
|
import { getUpdatesTool } from './tools/get-updates.js';
|
|
58
67
|
import { listWebhooksTool } from './tools/list-webhooks.js';
|
|
@@ -718,6 +727,87 @@ server.tool(deleteGoogleBusinessMediaTool.name, deleteGoogleBusinessMediaTool.de
|
|
|
718
727
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
719
728
|
}
|
|
720
729
|
});
|
|
730
|
+
server.tool(listConversationsTool.name, listConversationsTool.description, listConversationsTool.inputSchema.shape, getToolAnnotations(listConversationsTool.name), async (input) => {
|
|
731
|
+
try {
|
|
732
|
+
const text = await listConversationsTool.execute(client, input);
|
|
733
|
+
return { content: [{ type: 'text', text }] };
|
|
734
|
+
}
|
|
735
|
+
catch (err) {
|
|
736
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
server.tool(getConversationTool.name, getConversationTool.description, getConversationTool.inputSchema.shape, getToolAnnotations(getConversationTool.name), async (input) => {
|
|
740
|
+
try {
|
|
741
|
+
const text = await getConversationTool.execute(client, input);
|
|
742
|
+
return { content: [{ type: 'text', text }] };
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
server.tool(sendMessageTool.name, sendMessageTool.description, sendMessageTool.inputSchema.shape, getToolAnnotations(sendMessageTool.name), async (input) => {
|
|
749
|
+
try {
|
|
750
|
+
const text = await sendMessageTool.execute(client, input);
|
|
751
|
+
return { content: [{ type: 'text', text }] };
|
|
752
|
+
}
|
|
753
|
+
catch (err) {
|
|
754
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
server.tool(listCommentsTool.name, listCommentsTool.description, listCommentsTool.inputSchema.shape, getToolAnnotations(listCommentsTool.name), async (input) => {
|
|
758
|
+
try {
|
|
759
|
+
const text = await listCommentsTool.execute(client, input);
|
|
760
|
+
return { content: [{ type: 'text', text }] };
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
server.tool(getCommentTool.name, getCommentTool.description, getCommentTool.inputSchema.shape, getToolAnnotations(getCommentTool.name), async (input) => {
|
|
767
|
+
try {
|
|
768
|
+
const text = await getCommentTool.execute(client, input);
|
|
769
|
+
return { content: [{ type: 'text', text }] };
|
|
770
|
+
}
|
|
771
|
+
catch (err) {
|
|
772
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
server.tool(replyToCommentTool.name, replyToCommentTool.description, replyToCommentTool.inputSchema.shape, getToolAnnotations(replyToCommentTool.name), async (input) => {
|
|
776
|
+
try {
|
|
777
|
+
const text = await replyToCommentTool.execute(client, input);
|
|
778
|
+
return { content: [{ type: 'text', text }] };
|
|
779
|
+
}
|
|
780
|
+
catch (err) {
|
|
781
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
server.tool(updateCommentTool.name, updateCommentTool.description, updateCommentTool.inputSchema.shape, getToolAnnotations(updateCommentTool.name), async (input) => {
|
|
785
|
+
try {
|
|
786
|
+
const text = await updateCommentTool.execute(client, input);
|
|
787
|
+
return { content: [{ type: 'text', text }] };
|
|
788
|
+
}
|
|
789
|
+
catch (err) {
|
|
790
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
server.tool(deleteCommentTool.name, deleteCommentTool.description, deleteCommentTool.inputSchema.shape, getToolAnnotations(deleteCommentTool.name), async (input) => {
|
|
794
|
+
try {
|
|
795
|
+
const text = await deleteCommentTool.execute(client, input);
|
|
796
|
+
return { content: [{ type: 'text', text }] };
|
|
797
|
+
}
|
|
798
|
+
catch (err) {
|
|
799
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
server.tool(syncInboxTool.name, syncInboxTool.description, syncInboxTool.inputSchema.shape, getToolAnnotations(syncInboxTool.name), async (input) => {
|
|
803
|
+
try {
|
|
804
|
+
const text = await syncInboxTool.execute(client, input);
|
|
805
|
+
return { content: [{ type: 'text', text }] };
|
|
806
|
+
}
|
|
807
|
+
catch (err) {
|
|
808
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
809
|
+
}
|
|
810
|
+
});
|
|
721
811
|
server.tool(listActivityTool.name, listActivityTool.description, listActivityTool.inputSchema.shape, getToolAnnotations(listActivityTool.name), async (input) => {
|
|
722
812
|
try {
|
|
723
813
|
const text = await listActivityTool.execute(client, input);
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -624,6 +624,45 @@ export interface GoogleBusinessReviewLinkResponse {
|
|
|
624
624
|
locationId: string | null;
|
|
625
625
|
account_id: number;
|
|
626
626
|
}
|
|
627
|
+
export interface InboxListParams {
|
|
628
|
+
workspace_id?: string;
|
|
629
|
+
account_id?: number | number[];
|
|
630
|
+
platform?: string | string[];
|
|
631
|
+
filter?: string;
|
|
632
|
+
search?: string;
|
|
633
|
+
limit?: number;
|
|
634
|
+
offset?: number;
|
|
635
|
+
}
|
|
636
|
+
export interface InboxConversationsResponse {
|
|
637
|
+
conversations: Array<Record<string, unknown>>;
|
|
638
|
+
total: number;
|
|
639
|
+
hasMore: boolean;
|
|
640
|
+
}
|
|
641
|
+
export interface InboxConversationResponse {
|
|
642
|
+
conversation: Record<string, unknown>;
|
|
643
|
+
messages: Array<Record<string, unknown>>;
|
|
644
|
+
}
|
|
645
|
+
export interface InboxCommentsResponse {
|
|
646
|
+
comments: Array<Record<string, unknown>>;
|
|
647
|
+
total: number;
|
|
648
|
+
hasMore: boolean;
|
|
649
|
+
}
|
|
650
|
+
export interface InboxCommentResponse {
|
|
651
|
+
comment: Record<string, unknown>;
|
|
652
|
+
}
|
|
653
|
+
export interface InboxSendMessageResponse {
|
|
654
|
+
success: true;
|
|
655
|
+
message_id: string;
|
|
656
|
+
}
|
|
657
|
+
export interface InboxCommentReplyResponse {
|
|
658
|
+
success: true;
|
|
659
|
+
reply_type: string;
|
|
660
|
+
reply_id: string;
|
|
661
|
+
}
|
|
662
|
+
export interface InboxSyncResponse {
|
|
663
|
+
success: true;
|
|
664
|
+
results: Record<string, unknown>;
|
|
665
|
+
}
|
|
627
666
|
export interface ActivityItem {
|
|
628
667
|
id: string;
|
|
629
668
|
type: 'activity' | 'publish';
|
|
@@ -1243,6 +1282,43 @@ export declare class PosterlyClient {
|
|
|
1243
1282
|
content_type: string;
|
|
1244
1283
|
size: number;
|
|
1245
1284
|
}>;
|
|
1285
|
+
private appendInboxListParams;
|
|
1286
|
+
listConversations(params?: InboxListParams): Promise<InboxConversationsResponse>;
|
|
1287
|
+
getConversation(params: {
|
|
1288
|
+
conversation_id: string;
|
|
1289
|
+
workspace_id?: string;
|
|
1290
|
+
limit?: number;
|
|
1291
|
+
}): Promise<InboxConversationResponse>;
|
|
1292
|
+
sendMessage(data: {
|
|
1293
|
+
conversation_id: string;
|
|
1294
|
+
content: string;
|
|
1295
|
+
workspace_id?: string;
|
|
1296
|
+
}): Promise<InboxSendMessageResponse>;
|
|
1297
|
+
listComments(params?: InboxListParams): Promise<InboxCommentsResponse>;
|
|
1298
|
+
getComment(params: {
|
|
1299
|
+
comment_id: string;
|
|
1300
|
+
workspace_id?: string;
|
|
1301
|
+
}): Promise<InboxCommentResponse>;
|
|
1302
|
+
replyToComment(data: {
|
|
1303
|
+
comment_id: string;
|
|
1304
|
+
content: string;
|
|
1305
|
+
type?: 'public' | 'private';
|
|
1306
|
+
workspace_id?: string;
|
|
1307
|
+
}): Promise<InboxCommentReplyResponse>;
|
|
1308
|
+
updateComment(data: {
|
|
1309
|
+
comment_id: string;
|
|
1310
|
+
is_hidden?: boolean;
|
|
1311
|
+
is_read?: boolean;
|
|
1312
|
+
workspace_id?: string;
|
|
1313
|
+
}): Promise<InboxCommentResponse>;
|
|
1314
|
+
deleteComment(id: string, workspaceId?: string): Promise<{
|
|
1315
|
+
success: true;
|
|
1316
|
+
}>;
|
|
1317
|
+
syncInbox(data: {
|
|
1318
|
+
account_id: number;
|
|
1319
|
+
sync_type?: 'all' | 'dm' | 'comments';
|
|
1320
|
+
workspace_id?: string;
|
|
1321
|
+
}): Promise<InboxSyncResponse>;
|
|
1246
1322
|
}
|
|
1247
1323
|
export declare function resolvePosterlyBaseUrl(baseUrl?: string): string;
|
|
1248
1324
|
type PosterlyBaseUrlInspection = {
|
package/dist/lib/api-client.js
CHANGED
|
@@ -553,6 +553,86 @@ export class PosterlyClient {
|
|
|
553
553
|
content_type: input.contentType,
|
|
554
554
|
});
|
|
555
555
|
}
|
|
556
|
+
appendInboxListParams(searchParams, params) {
|
|
557
|
+
if (params?.workspace_id)
|
|
558
|
+
searchParams.set('workspace_id', params.workspace_id);
|
|
559
|
+
const accountIds = params?.account_id == null ? [] : Array.isArray(params.account_id) ? params.account_id : [params.account_id];
|
|
560
|
+
for (const accountId of accountIds)
|
|
561
|
+
searchParams.append('account_id', String(accountId));
|
|
562
|
+
const platforms = params?.platform == null ? [] : Array.isArray(params.platform) ? params.platform : [params.platform];
|
|
563
|
+
for (const platform of platforms)
|
|
564
|
+
searchParams.append('platform', platform);
|
|
565
|
+
if (params?.filter)
|
|
566
|
+
searchParams.set('filter', params.filter);
|
|
567
|
+
if (params?.search)
|
|
568
|
+
searchParams.set('search', params.search);
|
|
569
|
+
if (params?.limit)
|
|
570
|
+
searchParams.set('limit', String(params.limit));
|
|
571
|
+
if (params?.offset != null)
|
|
572
|
+
searchParams.set('offset', String(params.offset));
|
|
573
|
+
}
|
|
574
|
+
async listConversations(params) {
|
|
575
|
+
const searchParams = new URLSearchParams();
|
|
576
|
+
this.appendInboxListParams(searchParams, params);
|
|
577
|
+
const qs = searchParams.toString();
|
|
578
|
+
return this.request('GET', `/inbox/conversations${qs ? `?${qs}` : ''}`);
|
|
579
|
+
}
|
|
580
|
+
async getConversation(params) {
|
|
581
|
+
const searchParams = new URLSearchParams();
|
|
582
|
+
if (params.workspace_id)
|
|
583
|
+
searchParams.set('workspace_id', params.workspace_id);
|
|
584
|
+
if (params.limit)
|
|
585
|
+
searchParams.set('limit', String(params.limit));
|
|
586
|
+
const qs = searchParams.toString();
|
|
587
|
+
return this.request('GET', `/inbox/conversations/${encodeURIComponent(params.conversation_id)}${qs ? `?${qs}` : ''}`);
|
|
588
|
+
}
|
|
589
|
+
async sendMessage(data) {
|
|
590
|
+
const body = { content: data.content };
|
|
591
|
+
if (data.workspace_id)
|
|
592
|
+
body.workspace_id = data.workspace_id;
|
|
593
|
+
return this.request('POST', `/inbox/conversations/${encodeURIComponent(data.conversation_id)}/reply`, body);
|
|
594
|
+
}
|
|
595
|
+
async listComments(params) {
|
|
596
|
+
const searchParams = new URLSearchParams();
|
|
597
|
+
this.appendInboxListParams(searchParams, params);
|
|
598
|
+
const qs = searchParams.toString();
|
|
599
|
+
return this.request('GET', `/inbox/comments${qs ? `?${qs}` : ''}`);
|
|
600
|
+
}
|
|
601
|
+
async getComment(params) {
|
|
602
|
+
const searchParams = new URLSearchParams();
|
|
603
|
+
if (params.workspace_id)
|
|
604
|
+
searchParams.set('workspace_id', params.workspace_id);
|
|
605
|
+
const qs = searchParams.toString();
|
|
606
|
+
return this.request('GET', `/inbox/comments/${encodeURIComponent(params.comment_id)}${qs ? `?${qs}` : ''}`);
|
|
607
|
+
}
|
|
608
|
+
async replyToComment(data) {
|
|
609
|
+
const body = { content: data.content };
|
|
610
|
+
if (data.type)
|
|
611
|
+
body.type = data.type;
|
|
612
|
+
if (data.workspace_id)
|
|
613
|
+
body.workspace_id = data.workspace_id;
|
|
614
|
+
return this.request('POST', `/inbox/comments/${encodeURIComponent(data.comment_id)}/reply`, body);
|
|
615
|
+
}
|
|
616
|
+
async updateComment(data) {
|
|
617
|
+
const body = {};
|
|
618
|
+
if (typeof data.is_hidden === 'boolean')
|
|
619
|
+
body.is_hidden = data.is_hidden;
|
|
620
|
+
if (typeof data.is_read === 'boolean')
|
|
621
|
+
body.is_read = data.is_read;
|
|
622
|
+
if (data.workspace_id)
|
|
623
|
+
body.workspace_id = data.workspace_id;
|
|
624
|
+
return this.request('PATCH', `/inbox/comments/${encodeURIComponent(data.comment_id)}`, body);
|
|
625
|
+
}
|
|
626
|
+
async deleteComment(id, workspaceId) {
|
|
627
|
+
const searchParams = new URLSearchParams();
|
|
628
|
+
if (workspaceId)
|
|
629
|
+
searchParams.set('workspace_id', workspaceId);
|
|
630
|
+
const qs = searchParams.toString();
|
|
631
|
+
return this.request('DELETE', `/inbox/comments/${encodeURIComponent(id)}${qs ? `?${qs}` : ''}`);
|
|
632
|
+
}
|
|
633
|
+
async syncInbox(data) {
|
|
634
|
+
return this.request('POST', '/inbox/sync', data);
|
|
635
|
+
}
|
|
556
636
|
}
|
|
557
637
|
export function resolvePosterlyBaseUrl(baseUrl) {
|
|
558
638
|
const configured = (baseUrl ||
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const POSTERLY_MCP_VERSION = "0.
|
|
1
|
+
export declare const POSTERLY_MCP_VERSION = "0.38.0";
|
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.
|
|
8
|
+
export const POSTERLY_MCP_VERSION = '0.38.0';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const deleteCommentTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
comment_id: z.ZodString;
|
|
8
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
9
|
+
confirm: z.ZodLiteral<true>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
confirm: true;
|
|
12
|
+
comment_id: string;
|
|
13
|
+
workspace_id?: string | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
confirm: true;
|
|
16
|
+
comment_id: string;
|
|
17
|
+
workspace_id?: string | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
execute(client: PosterlyClient, input: {
|
|
20
|
+
comment_id: string;
|
|
21
|
+
workspace_id?: string;
|
|
22
|
+
confirm: true;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const deleteCommentTool = {
|
|
3
|
+
name: 'delete_comment',
|
|
4
|
+
description: 'Delete an Instagram or Facebook Page inbox comment. DESTRUCTIVE and IRREVERSIBLE. Threads replies cannot be deleted; hide them with update_comment instead. Requires a Pro plan or higher, posts:write, and admin workspace access. Fetch with get_comment first and 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 or get_comment.'),
|
|
7
|
+
workspace_id: z.string().optional().describe('Workspace ID from whoami.'),
|
|
8
|
+
confirm: z.literal(true).describe('Must be true after explicit user confirmation of the comment deletion.'),
|
|
9
|
+
}),
|
|
10
|
+
async execute(client, input) {
|
|
11
|
+
if (input.confirm !== true) {
|
|
12
|
+
throw new Error('Refusing to delete comment without confirm=true after explicit user confirmation.');
|
|
13
|
+
}
|
|
14
|
+
await client.deleteComment(input.comment_id, input.workspace_id);
|
|
15
|
+
return [
|
|
16
|
+
`Comment ${input.comment_id} deleted successfully.`,
|
|
17
|
+
'It has been deleted from the posterly inbox. On Instagram or Facebook it is also removed on the network when the platform call succeeds.',
|
|
18
|
+
'',
|
|
19
|
+
'Next step for the AI agent: tell the user the comment is gone. Threads replies cannot be deleted; hide them with update_comment instead.',
|
|
20
|
+
].join('\n');
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const getCommentTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
comment_id: z.ZodString;
|
|
8
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
comment_id: string;
|
|
11
|
+
workspace_id?: string | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
comment_id: string;
|
|
14
|
+
workspace_id?: string | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
execute(client: PosterlyClient, input: {
|
|
17
|
+
comment_id: string;
|
|
18
|
+
workspace_id?: string;
|
|
19
|
+
}): Promise<string>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatLocalDateTime } from '../lib/format.js';
|
|
3
|
+
export const getCommentTool = {
|
|
4
|
+
name: 'get_comment',
|
|
5
|
+
description: 'Get one social inbox comment and its replies. Instagram, Facebook Page, and Threads comments. Requires a Pro plan or higher plus posts:read.',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
comment_id: z.string().min(1).describe('Comment ID from list_comments.'),
|
|
8
|
+
workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
|
|
9
|
+
}),
|
|
10
|
+
async execute(client, input) {
|
|
11
|
+
const result = await client.getComment(input);
|
|
12
|
+
const comment = result.comment;
|
|
13
|
+
const author = typeof comment.author_username === 'string' ? `@${comment.author_username}` : 'unknown';
|
|
14
|
+
const lines = [
|
|
15
|
+
`Comment ${String(comment.id)}`,
|
|
16
|
+
`• Platform: ${typeof comment.platform === 'string' ? comment.platform : 'unknown'}`,
|
|
17
|
+
`• Author: ${author}`,
|
|
18
|
+
`• Hidden: ${comment.is_hidden ? 'yes' : 'no'}`,
|
|
19
|
+
`• Read: ${comment.is_read === false ? 'no' : 'yes'}`,
|
|
20
|
+
`• Posted: ${formatLocalDateTime(typeof comment.posted_at === 'string' ? comment.posted_at : null)}`,
|
|
21
|
+
`• Content: ${typeof comment.content === 'string' ? comment.content : '(empty)'}`,
|
|
22
|
+
];
|
|
23
|
+
if (typeof comment.media_permalink === 'string')
|
|
24
|
+
lines.push(`• Media: ${comment.media_permalink}`);
|
|
25
|
+
const replies = Array.isArray(comment.replies) ? comment.replies : [];
|
|
26
|
+
if (replies.length > 0) {
|
|
27
|
+
lines.push('', `Replies (${replies.length}):`);
|
|
28
|
+
for (const reply of replies) {
|
|
29
|
+
const row = reply;
|
|
30
|
+
const replyAuthor = typeof row.author_username === 'string' ? `@${row.author_username}` : 'unknown';
|
|
31
|
+
lines.push(`- ${String(row.id)} ${replyAuthor}: ${typeof row.content === 'string' ? row.content : '(empty)'}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const getConversationTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
conversation_id: z.ZodString;
|
|
8
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
9
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
conversation_id: string;
|
|
12
|
+
workspace_id?: string | undefined;
|
|
13
|
+
limit?: number | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
conversation_id: string;
|
|
16
|
+
workspace_id?: string | undefined;
|
|
17
|
+
limit?: number | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
execute(client: PosterlyClient, input: {
|
|
20
|
+
conversation_id: string;
|
|
21
|
+
workspace_id?: string;
|
|
22
|
+
limit?: number;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatLocalDateTime, truncateText } from '../lib/format.js';
|
|
3
|
+
export const getConversationTool = {
|
|
4
|
+
name: 'get_conversation',
|
|
5
|
+
description: 'Get one social inbox DM conversation and its messages. Instagram and Facebook Page DMs only. Requires a Pro plan or higher plus posts:read.',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
conversation_id: z.string().min(1).describe('Conversation ID from list_conversations.'),
|
|
8
|
+
workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
|
|
9
|
+
limit: z.number().int().min(1).max(100).optional().describe('Maximum messages to return. Default 50, max 100.'),
|
|
10
|
+
}),
|
|
11
|
+
async execute(client, input) {
|
|
12
|
+
const result = await client.getConversation(input);
|
|
13
|
+
const conversation = result.conversation;
|
|
14
|
+
const username = typeof conversation.participant_username === 'string' ? `@${conversation.participant_username}` : 'unknown';
|
|
15
|
+
const lines = [
|
|
16
|
+
`Conversation ${String(conversation.id)}`,
|
|
17
|
+
`• Platform: ${typeof conversation.platform === 'string' ? conversation.platform : 'unknown'}`,
|
|
18
|
+
`• Participant: ${username}`,
|
|
19
|
+
`• Read: ${conversation.is_read === false ? 'no' : 'yes'}`,
|
|
20
|
+
`• Messages: ${result.messages.length}`,
|
|
21
|
+
'',
|
|
22
|
+
];
|
|
23
|
+
for (const message of result.messages) {
|
|
24
|
+
const who = message.sender_is_business ? 'you' : (typeof message.sender_username === 'string' ? message.sender_username : 'them');
|
|
25
|
+
const when = formatLocalDateTime(typeof message.sent_at === 'string' ? message.sent_at : null);
|
|
26
|
+
const text = truncateText(typeof message.content === 'string' ? message.content : '', 240);
|
|
27
|
+
lines.push(`- [${when}] ${who}: ${text}`);
|
|
28
|
+
}
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const listCommentsTool: {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: z.ZodObject<{
|
|
7
|
+
workspace_id: z.ZodOptional<z.ZodString>;
|
|
8
|
+
account_id: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
platform: z.ZodOptional<z.ZodEnum<["instagram", "facebook", "threads"]>>;
|
|
10
|
+
filter: z.ZodOptional<z.ZodEnum<["all", "unread", "flagged"]>>;
|
|
11
|
+
search: z.ZodOptional<z.ZodString>;
|
|
12
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
platform?: "instagram" | "facebook" | "threads" | undefined;
|
|
16
|
+
workspace_id?: string | undefined;
|
|
17
|
+
account_id?: number | undefined;
|
|
18
|
+
limit?: number | undefined;
|
|
19
|
+
offset?: number | undefined;
|
|
20
|
+
filter?: "all" | "unread" | "flagged" | undefined;
|
|
21
|
+
search?: string | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
platform?: "instagram" | "facebook" | "threads" | undefined;
|
|
24
|
+
workspace_id?: string | undefined;
|
|
25
|
+
account_id?: number | undefined;
|
|
26
|
+
limit?: number | undefined;
|
|
27
|
+
offset?: number | undefined;
|
|
28
|
+
filter?: "all" | "unread" | "flagged" | undefined;
|
|
29
|
+
search?: string | undefined;
|
|
30
|
+
}>;
|
|
31
|
+
execute(client: PosterlyClient, input: {
|
|
32
|
+
workspace_id?: string;
|
|
33
|
+
account_id?: number;
|
|
34
|
+
platform?: "instagram" | "facebook" | "threads";
|
|
35
|
+
filter?: "all" | "unread" | "flagged";
|
|
36
|
+
search?: string;
|
|
37
|
+
limit?: number;
|
|
38
|
+
offset?: number;
|
|
39
|
+
}): Promise<string>;
|
|
40
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { truncateText } from '../lib/format.js';
|
|
3
|
+
const INBOX_PLATFORMS = ['instagram', 'facebook', 'threads'];
|
|
4
|
+
export const listCommentsTool = {
|
|
5
|
+
name: 'list_comments',
|
|
6
|
+
description: 'List social inbox comments and replies for Instagram, Facebook Pages, and Threads. Filter by account, platform, unread/flagged, or search. Requires a Pro plan or higher plus posts:read.',
|
|
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, facebook, or threads.'),
|
|
11
|
+
filter: z.enum(['all', 'unread', 'flagged']).optional().describe('Comment filter. Defaults to all.'),
|
|
12
|
+
search: z.string().optional().describe('Search comment content or author username.'),
|
|
13
|
+
limit: z.number().int().min(1).max(100).optional().describe('Number of comments to return. Default 50, max 100.'),
|
|
14
|
+
offset: z.number().int().min(0).optional().describe('Skip this many comments before returning results.'),
|
|
15
|
+
}),
|
|
16
|
+
async execute(client, input) {
|
|
17
|
+
const result = await client.listComments(input);
|
|
18
|
+
if (result.comments.length === 0) {
|
|
19
|
+
return 'No inbox comments found matching your filters.';
|
|
20
|
+
}
|
|
21
|
+
const lines = [`Inbox comments (showing ${result.comments.length} of ${result.total}):`];
|
|
22
|
+
for (const comment of result.comments) {
|
|
23
|
+
const author = typeof comment.author_username === 'string' ? `@${comment.author_username}` : 'unknown';
|
|
24
|
+
const platform = typeof comment.platform === 'string' ? comment.platform : 'unknown';
|
|
25
|
+
const flags = [];
|
|
26
|
+
if (comment.is_read === false)
|
|
27
|
+
flags.push('unread');
|
|
28
|
+
if (comment.is_hidden)
|
|
29
|
+
flags.push('hidden');
|
|
30
|
+
if (comment.is_flagged)
|
|
31
|
+
flags.push('flagged');
|
|
32
|
+
const flagLabel = flags.length > 0 ? `, ${flags.join(', ')}` : '';
|
|
33
|
+
const text = truncateText(typeof comment.content === 'string' ? comment.content : '', 80);
|
|
34
|
+
const replies = Array.isArray(comment.replies) ? comment.replies.length : 0;
|
|
35
|
+
lines.push(`- ${String(comment.id)} (${platform}, ${author}${flagLabel}${replies ? `, ${replies} replies` : ''}): ${text}`);
|
|
36
|
+
}
|
|
37
|
+
if (result.hasMore) {
|
|
38
|
+
const nextOffset = (input.offset ?? 0) + result.comments.length;
|
|
39
|
+
lines.push('', `More comments available. Call again with offset: ${nextOffset}.`);
|
|
40
|
+
}
|
|
41
|
+
return lines.join('\n');
|
|
42
|
+
},
|
|
43
|
+
};
|