posterly-mcp-server 0.33.3 → 0.35.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 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.33.3` exposes 77 tools.
125
+ `posterly-mcp-server@0.35.0` exposes 78 tools.
126
126
 
127
127
  Public setup tools work before `POSTERLY_API_KEY` exists:
128
128
 
@@ -137,6 +137,7 @@ Authenticated tools require `POSTERLY_API_KEY`:
137
137
  - `list_accounts`
138
138
  - `disconnect_account` (disconnect a connected social account after explicit confirmation)
139
139
  - `get_connect_link`
140
+ - `connect_account` (connect a credential-based account: telegram, bluesky, discord, wordpress, devto, hashnode, lemmy; no browser session needed)
140
141
  - `create_connect_session` (create a guided browser handoff for connecting a social account)
141
142
  - `get_connect_session` (poll connection progress while the user approves OAuth or enters credentials)
142
143
  - `create_api_key` (create a new API key after explicit confirmation; scopes cannot exceed the calling dashboard key)
package/RELEASING.md ADDED
@@ -0,0 +1,103 @@
1
+ # Releasing the MCP surfaces
2
+
3
+ posterly has two MCP surfaces sharing one version namespace:
4
+
5
+ | Surface | Where it lives | How it reaches users |
6
+ | --- | --- | --- |
7
+ | Hosted HTTP | `lib/mcp/` → `POST /api/mcp` | **Automatically**, with the Vercel deploy of `main` |
8
+ | npm stdio | `mcp-server/src/` → `posterly-mcp-server` | **Only when someone runs `npm publish`** |
9
+
10
+ That asymmetry is the whole reason this file exists.
11
+
12
+ ## The failure this prevents
13
+
14
+ The docs tell users to install with `npx -y posterly-mcp-server@latest`, so most
15
+ MCP traffic is the npm package. The package is a thin client over `/api/v1/*`,
16
+ which makes it tempting to assume a server-side fix reaches everyone. It does
17
+ not. **The npm package does its own output formatting**, so anything the client
18
+ renders itself stays broken until a publish:
19
+
20
+ - tool output formatting (truncation, row caps, what columns are shown)
21
+ - input schemas (new parameters like `limit` / `offset`)
22
+ - tool descriptions and names
23
+
24
+ This is not hypothetical. In Aug 2026 the Google Business review truncation was
25
+ fixed server-side and merged. Hosted users got the fix on deploy. npm users kept
26
+ seeing reviews clipped to ~180 characters, capped at 25 rows, with no dates —
27
+ because that formatting lives in `mcp-server/src/tools/`. Only a publish fixed it.
28
+
29
+ ## Release order
30
+
31
+ ```
32
+ change an MCP surface -> bump the version -> merge -> deploy -> npm publish
33
+ ```
34
+
35
+ Bumping and publishing are separate steps with different failure modes:
36
+
37
+ - **Skipping the bump is unrecoverable.** npm versions are immutable. Tool
38
+ changes merged under a version already on npm can never be delivered under
39
+ that version. `npm run check:mcp-version-bump` fails the build to prevent it.
40
+ - **Skipping the publish is recoverable** — just publish. `npm run
41
+ check:mcp-published` tells you when one is outstanding.
42
+
43
+ ## 1. Bump the version
44
+
45
+ The version appears in six places and `npm run check:mcp-parity` enforces that
46
+ they all agree:
47
+
48
+ - `lib/mcp/version.ts` — hosted endpoint
49
+ - `mcp-server/src/lib/version.ts` — stdio package
50
+ - `mcp-server/package.json`
51
+ - `mcp-server/package-lock.json` (two entries)
52
+ - `mcp-server/server.json` (two entries)
53
+ - `mcp-server/README.md` — the `posterly-mcp-server@X.Y.Z exposes N tools` line
54
+
55
+ Then rebuild so the compiled output matches, since the parity check reads
56
+ `mcp-server/dist/lib/version.js` too:
57
+
58
+ ```bash
59
+ npm --prefix mcp-server run build
60
+ npm run check:mcp-parity
61
+ ```
62
+
63
+ Use a patch bump for tool fixes and additive schema changes; a minor bump for
64
+ new tools or a changed tool surface.
65
+
66
+ ## 2. Merge and deploy
67
+
68
+ Land the PR and let `main` deploy. The hosted endpoint is live at this point;
69
+ npm users are not.
70
+
71
+ ## 3. Publish
72
+
73
+ ```bash
74
+ cd mcp-server
75
+ npm whoami # must be an account that can publish posterly-mcp-server
76
+ npm publish # add --otp=<code> if 2FA prompts
77
+ ```
78
+
79
+ `prepack` rebuilds `dist/` and re-runs the parity check before packing, so a
80
+ stale build or a drifted version cannot ship. `dist/` is gitignored but is
81
+ included in the tarball — `prepack` is what puts it there.
82
+
83
+ Sanity-check the tarball first if you want:
84
+
85
+ ```bash
86
+ npm pack --dry-run
87
+ ```
88
+
89
+ ## 4. Verify
90
+
91
+ ```bash
92
+ npm view posterly-mcp-server version dist-tags
93
+ npm run check:mcp-published
94
+ ```
95
+
96
+ Both should report the version you just published. Users on `@latest` pick it
97
+ up on their next client restart; there is nothing for them to do.
98
+
99
+ ## If you published something broken
100
+
101
+ Do not `npm unpublish` and do not republish the same number — npm versions are
102
+ immutable and unpublishing breaks anyone who already resolved it. Cut the next
103
+ patch version and publish that.
package/dist/index.js CHANGED
@@ -67,6 +67,7 @@ import { getXPostingQuotaTool } from './tools/get-x-posting-quota.js';
67
67
  import { createSignedUploadTool } from './tools/create-signed-upload.js';
68
68
  import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
69
69
  import { getConnectLinkTool } from './tools/get-connect-link.js';
70
+ import { connectAccountTool } from './tools/connect-account.js';
70
71
  import { createConnectSessionTool } from './tools/create-connect-session.js';
71
72
  import { getConnectSessionTool } from './tools/get-connect-session.js';
72
73
  import { createApiKeyTool } from './tools/create-api-key.js';
@@ -159,6 +160,15 @@ server.tool(getConnectLinkTool.name, getConnectLinkTool.description, getConnectL
159
160
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
160
161
  }
161
162
  });
163
+ server.tool(connectAccountTool.name, connectAccountTool.description, connectAccountTool.inputSchema.shape, getToolAnnotations(connectAccountTool.name), async (input) => {
164
+ try {
165
+ const text = await connectAccountTool.execute(client, input);
166
+ return { content: [{ type: 'text', text }] };
167
+ }
168
+ catch (err) {
169
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
170
+ }
171
+ });
162
172
  server.tool(createConnectSessionTool.name, createConnectSessionTool.description, createConnectSessionTool.inputSchema.shape, getToolAnnotations(createConnectSessionTool.name), async (input) => {
163
173
  try {
164
174
  const text = await createConnectSessionTool.execute(client, input);
@@ -738,6 +738,16 @@ export interface ConnectSession {
738
738
  completed_at?: string | null;
739
739
  expires_at: string;
740
740
  }
741
+ export interface ConnectAccountCredentialsResponse {
742
+ connected: boolean;
743
+ account: {
744
+ id: number;
745
+ platform: string;
746
+ username: string;
747
+ [extra: string]: unknown;
748
+ };
749
+ workspace_id: string;
750
+ }
741
751
  export interface OAuthDeveloperClient {
742
752
  id: string;
743
753
  client_id: string;
@@ -802,6 +812,7 @@ export interface PublicSignupSessionResponse {
802
812
  export interface VideoJob {
803
813
  id: string;
804
814
  status: string;
815
+ provider: 'google' | 'xai';
805
816
  prompt: string;
806
817
  model: string;
807
818
  duration_seconds: number;
@@ -890,6 +901,12 @@ export declare class PosterlyClient {
890
901
  getConnectSession(sessionId: string): Promise<{
891
902
  connect_session: ConnectSession;
892
903
  }>;
904
+ connectAccountCredentials(data: {
905
+ platform: string;
906
+ credentials: Record<string, string>;
907
+ workspace_id?: string;
908
+ connect_session_id?: string;
909
+ }): Promise<ConnectAccountCredentialsResponse>;
893
910
  listOAuthClients(): Promise<{
894
911
  clients: OAuthDeveloperClient[];
895
912
  }>;
@@ -999,8 +1016,10 @@ export declare class PosterlyClient {
999
1016
  aspect_ratio?: string;
1000
1017
  style?: string;
1001
1018
  variations?: number;
1019
+ provider?: 'google' | 'xai';
1002
1020
  model?: 'lite' | 'flash' | 'pro';
1003
1021
  resolution?: '512' | '1K' | '2K' | '4K';
1022
+ quality?: 'low' | 'medium';
1004
1023
  }): Promise<{
1005
1024
  urls: string[];
1006
1025
  images: Array<{
@@ -1009,6 +1028,7 @@ export declare class PosterlyClient {
1009
1028
  path: string;
1010
1029
  }>;
1011
1030
  model: string;
1031
+ provider: 'google' | 'xai';
1012
1032
  credits_used: number;
1013
1033
  warnings?: string[];
1014
1034
  usage: {
@@ -1026,11 +1046,12 @@ export declare class PosterlyClient {
1026
1046
  params?: Record<string, unknown>;
1027
1047
  }): Promise<Record<string, any>>;
1028
1048
  generateVideo(data: {
1049
+ provider?: 'google' | 'xai';
1029
1050
  prompt: string;
1030
1051
  negative_prompt?: string;
1031
- aspect_ratio?: '16:9' | '9:16';
1032
- duration_seconds?: 4 | 6 | 8;
1033
- resolution?: '720p' | '1080p';
1052
+ aspect_ratio?: '1:1' | '16:9' | '9:16' | '4:3' | '3:4' | '3:2' | '2:3';
1053
+ duration_seconds?: number;
1054
+ resolution?: '480p' | '720p' | '1080p';
1034
1055
  model?: 'lite' | 'fast' | 'standard';
1035
1056
  image_url?: string;
1036
1057
  end_image_url?: string;
@@ -170,6 +170,15 @@ export class PosterlyClient {
170
170
  async getConnectSession(sessionId) {
171
171
  return this.request('GET', `/connect/sessions/${encodeURIComponent(sessionId)}`);
172
172
  }
173
+ async connectAccountCredentials(data) {
174
+ const { platform, credentials, workspace_id, connect_session_id } = data;
175
+ const body = { credentials };
176
+ if (workspace_id)
177
+ body.workspace_id = workspace_id;
178
+ if (connect_session_id)
179
+ body.connect_session_id = connect_session_id;
180
+ return this.request('POST', `/connect/${encodeURIComponent(platform)}/credentials`, body);
181
+ }
173
182
  async listOAuthClients() {
174
183
  return this.request('GET', '/oauth/clients');
175
184
  }
@@ -1 +1 @@
1
- export declare const POSTERLY_MCP_VERSION = "0.33.3";
1
+ export declare const POSTERLY_MCP_VERSION = "0.35.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.33.3';
8
+ export const POSTERLY_MCP_VERSION = '0.35.0';
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ declare const CREDENTIAL_PLATFORMS: readonly ["telegram", "bluesky", "discord", "wordpress", "devto", "hashnode", "lemmy"];
4
+ export declare const connectAccountTool: {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: z.ZodObject<{
8
+ platform: z.ZodEnum<["telegram", "bluesky", "discord", "wordpress", "devto", "hashnode", "lemmy"]>;
9
+ bot_token: z.ZodOptional<z.ZodString>;
10
+ chat_id: z.ZodOptional<z.ZodString>;
11
+ handle: z.ZodOptional<z.ZodString>;
12
+ app_password: z.ZodOptional<z.ZodString>;
13
+ webhook_url: z.ZodOptional<z.ZodString>;
14
+ site_url: z.ZodOptional<z.ZodString>;
15
+ username: z.ZodOptional<z.ZodString>;
16
+ password: z.ZodOptional<z.ZodString>;
17
+ api_key: z.ZodOptional<z.ZodString>;
18
+ pat: z.ZodOptional<z.ZodString>;
19
+ instance: z.ZodOptional<z.ZodString>;
20
+ workspace_id: z.ZodOptional<z.ZodString>;
21
+ connect_session_id: z.ZodOptional<z.ZodString>;
22
+ }, "strip", z.ZodTypeAny, {
23
+ platform: "telegram" | "bluesky" | "discord" | "devto" | "hashnode" | "wordpress" | "lemmy";
24
+ username?: string | undefined;
25
+ workspace_id?: string | undefined;
26
+ connect_session_id?: string | undefined;
27
+ handle?: string | undefined;
28
+ bot_token?: string | undefined;
29
+ chat_id?: string | undefined;
30
+ app_password?: string | undefined;
31
+ webhook_url?: string | undefined;
32
+ site_url?: string | undefined;
33
+ api_key?: string | undefined;
34
+ pat?: string | undefined;
35
+ instance?: string | undefined;
36
+ password?: string | undefined;
37
+ }, {
38
+ platform: "telegram" | "bluesky" | "discord" | "devto" | "hashnode" | "wordpress" | "lemmy";
39
+ username?: string | undefined;
40
+ workspace_id?: string | undefined;
41
+ connect_session_id?: string | undefined;
42
+ handle?: string | undefined;
43
+ bot_token?: string | undefined;
44
+ chat_id?: string | undefined;
45
+ app_password?: string | undefined;
46
+ webhook_url?: string | undefined;
47
+ site_url?: string | undefined;
48
+ api_key?: string | undefined;
49
+ pat?: string | undefined;
50
+ instance?: string | undefined;
51
+ password?: string | undefined;
52
+ }>;
53
+ execute(client: PosterlyClient, input: {
54
+ platform: (typeof CREDENTIAL_PLATFORMS)[number];
55
+ workspace_id?: string;
56
+ connect_session_id?: string;
57
+ } & Record<string, string | undefined>): Promise<string>;
58
+ };
59
+ export {};
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ const CREDENTIAL_PLATFORMS = [
3
+ 'telegram',
4
+ 'bluesky',
5
+ 'discord',
6
+ 'wordpress',
7
+ 'devto',
8
+ 'hashnode',
9
+ 'lemmy',
10
+ ];
11
+ /**
12
+ * Per-platform credential field whitelist. Duplicated in the hosted tool
13
+ * (lib/mcp/http-tools.ts) on purpose: the platform manifest does not carry
14
+ * credential fields, and the v1 endpoint rejects unknown keys, so each tool
15
+ * must whitelist before building the credentials object. The MCP parity and
16
+ * payload checks guard the duplication.
17
+ */
18
+ const PLATFORM_CREDENTIAL_FIELDS = {
19
+ telegram: ['bot_token', 'chat_id'],
20
+ bluesky: ['handle', 'app_password'],
21
+ discord: ['webhook_url'],
22
+ wordpress: ['site_url', 'username', 'app_password'],
23
+ devto: ['api_key'],
24
+ hashnode: ['pat'],
25
+ lemmy: ['instance', 'username', 'password'],
26
+ };
27
+ export const connectAccountTool = {
28
+ name: 'connect_account',
29
+ description: 'Connect a credential-based social account (telegram, bluesky, discord, wordpress, devto, hashnode, lemmy) directly, without a browser session. Call get_connect_link first to discover the exact credential fields the platform needs. Prefer scoped secrets: app passwords (Bluesky, WordPress), bot tokens (Telegram), webhook URLs (Discord), and API tokens (Dev.to, Hashnode) over primary passwords. Warn the user that any credential they share passes through this conversation. For OAuth platforms (Instagram, X, LinkedIn, ...) use create_connect_session instead.',
30
+ inputSchema: z.object({
31
+ platform: z
32
+ .enum(CREDENTIAL_PLATFORMS)
33
+ .describe('Credential-based connection target: telegram, bluesky, discord, wordpress, devto, hashnode, or lemmy.'),
34
+ bot_token: z.string().optional().describe('Telegram: bot token from BotFather.'),
35
+ chat_id: z.string().optional().describe('Telegram: target channel or group chat ID.'),
36
+ handle: z.string().optional().describe('Bluesky: handle, for example posterly.bsky.social.'),
37
+ app_password: z.string().optional().describe('Bluesky: app password from account settings. WordPress: application password from wp-admin Users, Profile, Application Passwords.'),
38
+ webhook_url: z.string().optional().describe('Discord: channel webhook URL from channel settings, Integrations, Webhooks.'),
39
+ site_url: z.string().optional().describe('WordPress: site URL, e.g. https://blog.example.com.'),
40
+ username: z.string().optional().describe('WordPress: username the application password belongs to. Lemmy: username or email.'),
41
+ password: z.string().optional().describe('Lemmy: account password (TOTP-enabled accounts are not supported).'),
42
+ api_key: z.string().optional().describe('Dev.to: API key from Settings, Extensions, DEV Community API Keys.'),
43
+ pat: z.string().optional().describe('Hashnode: personal access token from Account Settings, Developer.'),
44
+ instance: z.string().optional().describe('Lemmy: instance domain, for example lemmy.world.'),
45
+ workspace_id: z.string().optional().describe('Workspace to connect the account into. Workspace-scoped API keys ignore this.'),
46
+ connect_session_id: z.string().optional().describe('Optional connect session ID to mark connected or failed based on the outcome.'),
47
+ }),
48
+ async execute(client, input) {
49
+ const allowedFields = PLATFORM_CREDENTIAL_FIELDS[input.platform] || [];
50
+ const credentials = {};
51
+ for (const field of allowedFields) {
52
+ const value = input[field];
53
+ if (typeof value === 'string' && value.trim()) {
54
+ credentials[field] = value;
55
+ }
56
+ }
57
+ const missing = allowedFields.filter((field) => !(field in credentials));
58
+ if (missing.length > 0) {
59
+ return `Missing credential field(s) for ${input.platform}: ${missing.join(', ')}. Call get_connect_link with platform=${input.platform} to see field descriptions.`;
60
+ }
61
+ const result = await client.connectAccountCredentials({
62
+ platform: input.platform,
63
+ credentials,
64
+ workspace_id: input.workspace_id,
65
+ connect_session_id: input.connect_session_id,
66
+ });
67
+ const account = result.account || {};
68
+ const lines = [
69
+ `Connected ${input.platform} account: ${account.username || 'unknown'} (id ${account.id})`,
70
+ `Workspace: ${result.workspace_id}`,
71
+ 'Reconnecting the same account updates it in place.',
72
+ ];
73
+ return lines.join('\n');
74
+ },
75
+ };
@@ -9,12 +9,12 @@ export declare const createConnectSessionTool: {
9
9
  auto_start: z.ZodOptional<z.ZodBoolean>;
10
10
  debug: z.ZodOptional<z.ZodBoolean>;
11
11
  }, "strip", z.ZodTypeAny, {
12
- platform: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
12
+ platform: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "medium" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
13
13
  workspace_id?: string | undefined;
14
14
  auto_start?: boolean | undefined;
15
15
  debug?: boolean | undefined;
16
16
  }, {
17
- platform: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "medium" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
17
+ platform: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "medium" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "reddit" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
18
18
  workspace_id?: string | undefined;
19
19
  auto_start?: boolean | undefined;
20
20
  debug?: boolean | undefined;