posterly-mcp-server 0.35.1 → 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.
Files changed (49) hide show
  1. package/README.md +21 -1
  2. package/SUBMISSIONS.md +73 -0
  3. package/dist/index.js +80 -0
  4. package/dist/lib/api-client.d.ts +74 -0
  5. package/dist/lib/api-client.js +75 -1
  6. package/dist/lib/version.d.ts +1 -1
  7. package/dist/lib/version.js +1 -1
  8. package/dist/tools/create-post.d.ts +576 -0
  9. package/dist/tools/create-post.js +28 -2
  10. package/dist/tools/create-posts-batch.d.ts +224 -0
  11. package/dist/tools/create-signed-upload.js +2 -1
  12. package/dist/tools/generate-image.d.ts +2 -2
  13. package/dist/tools/generate-video.d.ts +2 -2
  14. package/dist/tools/get-comment.d.ts +20 -0
  15. package/dist/tools/get-comment.js +36 -0
  16. package/dist/tools/get-conversation.d.ts +24 -0
  17. package/dist/tools/get-conversation.js +31 -0
  18. package/dist/tools/list-comments.d.ts +40 -0
  19. package/dist/tools/list-comments.js +43 -0
  20. package/dist/tools/list-conversations.d.ts +40 -0
  21. package/dist/tools/list-conversations.js +35 -0
  22. package/dist/tools/reply-to-comment.d.ts +32 -0
  23. package/dist/tools/reply-to-comment.js +17 -0
  24. package/dist/tools/send-message.d.ts +28 -0
  25. package/dist/tools/send-message.js +16 -0
  26. package/dist/tools/sync-inbox.d.ts +24 -0
  27. package/dist/tools/sync-inbox.js +18 -0
  28. package/dist/tools/update-comment.d.ts +28 -0
  29. package/dist/tools/update-comment.js +21 -0
  30. package/dist/tools/update-post.d.ts +256 -0
  31. package/dist/tools/update-post.js +26 -0
  32. package/dist/tools/validate-post.d.ts +320 -0
  33. package/package.json +1 -1
  34. package/server.json +2 -2
  35. package/src/index.ts +128 -0
  36. package/src/lib/api-client.ts +153 -2
  37. package/src/lib/version.ts +1 -1
  38. package/src/tools/create-post.ts +28 -2
  39. package/src/tools/create-signed-upload.ts +2 -1
  40. package/src/tools/get-comment.ts +47 -0
  41. package/src/tools/get-conversation.ts +44 -0
  42. package/src/tools/list-comments.ts +59 -0
  43. package/src/tools/list-conversations.ts +54 -0
  44. package/src/tools/reply-to-comment.ts +30 -0
  45. package/src/tools/send-message.ts +28 -0
  46. package/src/tools/sync-inbox.ts +28 -0
  47. package/src/tools/update-comment.ts +31 -0
  48. package/src/tools/update-post.ts +26 -0
  49. 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.35.1` exposes 78 tools.
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);
@@ -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';
@@ -1221,6 +1260,7 @@ export declare class PosterlyClient {
1221
1260
  token: string;
1222
1261
  path: string;
1223
1262
  public_url: string;
1263
+ headers?: Record<string, string>;
1224
1264
  }>;
1225
1265
  uploadMedia(input: {
1226
1266
  filePath?: string;
@@ -1242,6 +1282,40 @@ export declare class PosterlyClient {
1242
1282
  content_type: string;
1243
1283
  size: number;
1244
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>;
1245
1319
  }
1246
1320
  export declare function resolvePosterlyBaseUrl(baseUrl?: string): string;
1247
1321
  type PosterlyBaseUrlInspection = {
@@ -522,12 +522,13 @@ export class PosterlyClient {
522
522
  guessContentType(input.filename);
523
523
  const SIZE_THRESHOLD = 3 * 1024 * 1024; // 3MB
524
524
  if (fileBuffer.length > SIZE_THRESHOLD) {
525
- // Large file: use signed URL so the API can validate actual bytes before storage.
525
+ // Large file: PUT to the object-storage signed URL (not a Vercel relay).
526
526
  const signed = await this.getSignedUploadUrl(input.filename, contentType, fileBuffer.length);
527
527
  const uploadRes = await fetch(signed.upload_url, {
528
528
  method: 'PUT',
529
529
  headers: {
530
530
  'Content-Type': contentType,
531
+ ...(signed.headers || {}),
531
532
  },
532
533
  body: new Uint8Array(fileBuffer),
533
534
  });
@@ -552,6 +553,79 @@ export class PosterlyClient {
552
553
  content_type: input.contentType,
553
554
  });
554
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
+ }
555
629
  }
556
630
  export function resolvePosterlyBaseUrl(baseUrl) {
557
631
  const configured = (baseUrl ||
@@ -1 +1 @@
1
- export declare const POSTERLY_MCP_VERSION = "0.35.1";
1
+ export declare const POSTERLY_MCP_VERSION = "0.37.0";
@@ -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.35.1';
8
+ export const POSTERLY_MCP_VERSION = '0.37.0';