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
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.37.0` exposes 86 tools.
|
|
126
126
|
|
|
127
127
|
Public setup tools work before `POSTERLY_API_KEY` exists:
|
|
128
128
|
|
|
@@ -190,6 +190,14 @@ 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
|
+
- `sync_inbox` (pull latest inbox data for one account; 30-second cooldown)
|
|
193
201
|
- `list_google_business_reviews`
|
|
194
202
|
- `get_google_business_review_link`
|
|
195
203
|
- `audit_google_business_profile`
|
|
@@ -267,6 +275,18 @@ Each API call counts as one request, so use `create_posts_batch` when you need t
|
|
|
267
275
|
|
|
268
276
|
Details: [poster.ly/dashboard/api](https://www.poster.ly/dashboard/api)
|
|
269
277
|
|
|
278
|
+
## Hosted MCP clients (Claude.ai, ChatGPT)
|
|
279
|
+
|
|
280
|
+
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:
|
|
281
|
+
|
|
282
|
+
- `POST /api/oauth/register`, send `{ client_name, redirect_uris, scope }`, receive a `client_id`.
|
|
283
|
+
- Standard authorization-code + PKCE at `/oauth/authorize`.
|
|
284
|
+
- 1-hour access tokens with 30-day rotating refresh tokens via `/api/oauth/token`.
|
|
285
|
+
|
|
286
|
+
Discovery document: [/.well-known/oauth-authorization-server](https://www.poster.ly/.well-known/oauth-authorization-server).
|
|
287
|
+
|
|
288
|
+
Local stdio still uses an API key in `POSTERLY_API_KEY`.
|
|
289
|
+
|
|
270
290
|
## Links
|
|
271
291
|
|
|
272
292
|
- 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,14 @@ 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 { syncInboxTool } from './tools/sync-inbox.js';
|
|
56
64
|
import { listActivityTool } from './tools/list-activity.js';
|
|
57
65
|
import { getUpdatesTool } from './tools/get-updates.js';
|
|
58
66
|
import { listWebhooksTool } from './tools/list-webhooks.js';
|
|
@@ -718,6 +726,78 @@ server.tool(deleteGoogleBusinessMediaTool.name, deleteGoogleBusinessMediaTool.de
|
|
|
718
726
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
719
727
|
}
|
|
720
728
|
});
|
|
729
|
+
server.tool(listConversationsTool.name, listConversationsTool.description, listConversationsTool.inputSchema.shape, getToolAnnotations(listConversationsTool.name), async (input) => {
|
|
730
|
+
try {
|
|
731
|
+
const text = await listConversationsTool.execute(client, input);
|
|
732
|
+
return { content: [{ type: 'text', text }] };
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
server.tool(getConversationTool.name, getConversationTool.description, getConversationTool.inputSchema.shape, getToolAnnotations(getConversationTool.name), async (input) => {
|
|
739
|
+
try {
|
|
740
|
+
const text = await getConversationTool.execute(client, input);
|
|
741
|
+
return { content: [{ type: 'text', text }] };
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
server.tool(sendMessageTool.name, sendMessageTool.description, sendMessageTool.inputSchema.shape, getToolAnnotations(sendMessageTool.name), async (input) => {
|
|
748
|
+
try {
|
|
749
|
+
const text = await sendMessageTool.execute(client, input);
|
|
750
|
+
return { content: [{ type: 'text', text }] };
|
|
751
|
+
}
|
|
752
|
+
catch (err) {
|
|
753
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
server.tool(listCommentsTool.name, listCommentsTool.description, listCommentsTool.inputSchema.shape, getToolAnnotations(listCommentsTool.name), async (input) => {
|
|
757
|
+
try {
|
|
758
|
+
const text = await listCommentsTool.execute(client, input);
|
|
759
|
+
return { content: [{ type: 'text', text }] };
|
|
760
|
+
}
|
|
761
|
+
catch (err) {
|
|
762
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
server.tool(getCommentTool.name, getCommentTool.description, getCommentTool.inputSchema.shape, getToolAnnotations(getCommentTool.name), async (input) => {
|
|
766
|
+
try {
|
|
767
|
+
const text = await getCommentTool.execute(client, input);
|
|
768
|
+
return { content: [{ type: 'text', text }] };
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
server.tool(replyToCommentTool.name, replyToCommentTool.description, replyToCommentTool.inputSchema.shape, getToolAnnotations(replyToCommentTool.name), async (input) => {
|
|
775
|
+
try {
|
|
776
|
+
const text = await replyToCommentTool.execute(client, input);
|
|
777
|
+
return { content: [{ type: 'text', text }] };
|
|
778
|
+
}
|
|
779
|
+
catch (err) {
|
|
780
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
server.tool(updateCommentTool.name, updateCommentTool.description, updateCommentTool.inputSchema.shape, getToolAnnotations(updateCommentTool.name), async (input) => {
|
|
784
|
+
try {
|
|
785
|
+
const text = await updateCommentTool.execute(client, input);
|
|
786
|
+
return { content: [{ type: 'text', text }] };
|
|
787
|
+
}
|
|
788
|
+
catch (err) {
|
|
789
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
server.tool(syncInboxTool.name, syncInboxTool.description, syncInboxTool.inputSchema.shape, getToolAnnotations(syncInboxTool.name), async (input) => {
|
|
793
|
+
try {
|
|
794
|
+
const text = await syncInboxTool.execute(client, input);
|
|
795
|
+
return { content: [{ type: 'text', text }] };
|
|
796
|
+
}
|
|
797
|
+
catch (err) {
|
|
798
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
799
|
+
}
|
|
800
|
+
});
|
|
721
801
|
server.tool(listActivityTool.name, listActivityTool.description, listActivityTool.inputSchema.shape, getToolAnnotations(listActivityTool.name), async (input) => {
|
|
722
802
|
try {
|
|
723
803
|
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,40 @@ 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
|
+
syncInbox(data: {
|
|
1315
|
+
account_id: number;
|
|
1316
|
+
sync_type?: 'all' | 'dm' | 'comments';
|
|
1317
|
+
workspace_id?: string;
|
|
1318
|
+
}): Promise<InboxSyncResponse>;
|
|
1246
1319
|
}
|
|
1247
1320
|
export declare function resolvePosterlyBaseUrl(baseUrl?: string): string;
|
|
1248
1321
|
type PosterlyBaseUrlInspection = {
|
package/dist/lib/api-client.js
CHANGED
|
@@ -553,6 +553,79 @@ 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 syncInbox(data) {
|
|
627
|
+
return this.request('POST', '/inbox/sync', data);
|
|
628
|
+
}
|
|
556
629
|
}
|
|
557
630
|
export function resolvePosterlyBaseUrl(baseUrl) {
|
|
558
631
|
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.37.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.37.0';
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PosterlyClient } from '../lib/api-client.js';
|
|
3
|
+
export declare const listConversationsTool: {
|
|
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", "starred", "archived"]>>;
|
|
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" | "starred" | "archived" | 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" | "starred" | "archived" | 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" | "starred" | "archived";
|
|
36
|
+
search?: string;
|
|
37
|
+
limit?: number;
|
|
38
|
+
offset?: number;
|
|
39
|
+
}): Promise<string>;
|
|
40
|
+
};
|