posterly-mcp-server 0.44.0 → 0.45.1

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 (236) hide show
  1. package/README.md +310 -111
  2. package/dist/index.js +190 -894
  3. package/dist/lib/alias-transport.d.ts +84 -0
  4. package/dist/lib/alias-transport.js +193 -0
  5. package/dist/lib/api-client.d.ts +18 -0
  6. package/dist/lib/api-client.js +55 -0
  7. package/dist/lib/preview-token-core.d.ts +62 -0
  8. package/dist/lib/preview-token-core.js +131 -0
  9. package/dist/lib/preview-token.d.ts +23 -0
  10. package/dist/lib/preview-token.js +44 -0
  11. package/dist/lib/tool-registry.d.ts +246 -0
  12. package/dist/lib/tool-registry.js +394 -0
  13. package/dist/lib/tool-runtime.d.ts +68 -0
  14. package/dist/lib/tool-runtime.js +85 -0
  15. package/dist/lib/version.d.ts +1 -1
  16. package/dist/lib/version.js +1 -1
  17. package/dist/tools/audit-google-business-profile.js +1 -1
  18. package/dist/tools/connect-account.d.ts +4 -4
  19. package/dist/tools/connect-account.js +2 -2
  20. package/dist/tools/create-connect-session.d.ts +2 -2
  21. package/dist/tools/create-connect-session.js +2 -2
  22. package/dist/tools/create-post.d.ts +30 -7
  23. package/dist/tools/create-post.js +75 -9
  24. package/dist/tools/create-posts-batch.d.ts +4 -4
  25. package/dist/tools/create-posts-batch.js +14 -2
  26. package/dist/tools/create-webhook.d.ts +2 -2
  27. package/dist/tools/delete-post.d.ts +34 -10
  28. package/dist/tools/delete-post.js +52 -16
  29. package/dist/tools/generate-captions.d.ts +2 -2
  30. package/dist/tools/generate-image.d.ts +4 -4
  31. package/dist/tools/generate-image.js +2 -2
  32. package/dist/tools/generate-video.d.ts +6 -6
  33. package/dist/tools/generate-video.js +1 -1
  34. package/dist/tools/get-agent-signup-info.js +2 -2
  35. package/dist/tools/get-post.d.ts +16 -1
  36. package/dist/tools/get-post.js +35 -19
  37. package/dist/tools/list-accounts.d.ts +49 -3
  38. package/dist/tools/list-accounts.js +148 -33
  39. package/dist/tools/list-analytics.d.ts +111 -0
  40. package/dist/tools/list-analytics.js +393 -0
  41. package/dist/tools/list-brands.d.ts +38 -3
  42. package/dist/tools/list-brands.js +138 -20
  43. package/dist/tools/list-comments.d.ts +29 -9
  44. package/dist/tools/list-comments.js +75 -34
  45. package/dist/tools/list-conversations.d.ts +32 -9
  46. package/dist/tools/list-conversations.js +63 -27
  47. package/dist/tools/list-google-business-media.js +1 -1
  48. package/dist/tools/list-google-business-reviews.d.ts +32 -9
  49. package/dist/tools/list-google-business-reviews.js +77 -52
  50. package/dist/tools/list-jobs.d.ts +60 -0
  51. package/dist/tools/list-jobs.js +81 -0
  52. package/dist/tools/list-platforms.d.ts +62 -3
  53. package/dist/tools/list-platforms.js +118 -9
  54. package/dist/tools/list-posts.d.ts +2 -2
  55. package/dist/tools/manage-comment.d.ts +102 -0
  56. package/dist/tools/manage-comment.js +125 -0
  57. package/dist/tools/manage-conversation.d.ts +79 -0
  58. package/dist/tools/manage-conversation.js +75 -0
  59. package/dist/tools/manage-google-business-media.d.ts +103 -0
  60. package/dist/tools/manage-google-business-media.js +126 -0
  61. package/dist/tools/manage-google-business-review.d.ts +146 -0
  62. package/dist/tools/manage-google-business-review.js +115 -0
  63. package/dist/tools/manage-oauth-client.d.ts +121 -0
  64. package/dist/tools/manage-oauth-client.js +94 -0
  65. package/dist/tools/manage-webhook.d.ts +99 -0
  66. package/dist/tools/manage-webhook.js +87 -0
  67. package/dist/tools/start-signup.d.ts +2 -2
  68. package/dist/tools/submit-agent-feedback.d.ts +4 -4
  69. package/dist/tools/trigger-platform-helper.d.ts +2 -2
  70. package/dist/tools/update-post.d.ts +62 -11
  71. package/dist/tools/update-post.js +79 -26
  72. package/dist/tools/upload-media.d.ts +32 -9
  73. package/dist/tools/upload-media.js +54 -18
  74. package/dist/tools/validate-post.d.ts +4 -4
  75. package/dist/tools/validate-post.js +2 -2
  76. package/dist/tools/whoami.d.ts +15 -2
  77. package/dist/tools/whoami.js +221 -25
  78. package/package.json +5 -3
  79. package/scripts/test-alias-transport.mjs +368 -0
  80. package/scripts/test-client-header-sanitization.mjs +78 -0
  81. package/scripts/test-payload-builders.mjs +143 -20
  82. package/scripts/test-tool-annotations.mjs +38 -17
  83. package/server.json +9 -2
  84. package/src/index.ts +227 -1443
  85. package/src/lib/alias-transport.ts +233 -0
  86. package/src/lib/api-client.ts +65 -0
  87. package/src/lib/preview-token-core.ts +157 -0
  88. package/src/lib/preview-token.ts +54 -0
  89. package/src/lib/tool-registry.ts +538 -0
  90. package/src/lib/tool-runtime.ts +117 -0
  91. package/src/lib/version.ts +1 -1
  92. package/src/tools/audit-google-business-profile.ts +1 -1
  93. package/src/tools/connect-account.ts +2 -2
  94. package/src/tools/create-connect-session.ts +2 -2
  95. package/src/tools/create-post.ts +87 -12
  96. package/src/tools/create-posts-batch.ts +13 -2
  97. package/src/tools/delete-post.ts +60 -19
  98. package/src/tools/generate-image.ts +2 -2
  99. package/src/tools/generate-video.ts +1 -1
  100. package/src/tools/get-agent-signup-info.ts +2 -2
  101. package/src/tools/get-post.ts +35 -21
  102. package/src/tools/list-accounts.ts +187 -39
  103. package/src/tools/list-analytics.ts +444 -0
  104. package/src/tools/list-brands.ts +162 -26
  105. package/src/tools/list-comments.ts +86 -43
  106. package/src/tools/list-conversations.ts +81 -39
  107. package/src/tools/list-google-business-media.ts +1 -1
  108. package/src/tools/list-google-business-reviews.ts +93 -64
  109. package/src/tools/list-jobs.ts +91 -0
  110. package/src/tools/list-platforms.ts +135 -11
  111. package/src/tools/manage-comment.ts +142 -0
  112. package/src/tools/manage-conversation.ts +89 -0
  113. package/src/tools/manage-google-business-media.ts +147 -0
  114. package/src/tools/manage-google-business-review.ts +139 -0
  115. package/src/tools/manage-oauth-client.ts +116 -0
  116. package/src/tools/manage-webhook.ts +103 -0
  117. package/src/tools/update-post.ts +106 -39
  118. package/src/tools/upload-media.ts +76 -28
  119. package/src/tools/validate-post.ts +2 -1
  120. package/src/tools/whoami.ts +253 -30
  121. package/tool-annotations.json +76 -149
  122. package/tool-registry.json +2085 -0
  123. package/dist/tools/add-google-business-media.d.ts +0 -42
  124. package/dist/tools/add-google-business-media.js +0 -34
  125. package/dist/tools/create-oauth-client.d.ts +0 -40
  126. package/dist/tools/create-oauth-client.js +0 -20
  127. package/dist/tools/delete-comment.d.ts +0 -24
  128. package/dist/tools/delete-comment.js +0 -22
  129. package/dist/tools/delete-google-business-media.d.ts +0 -32
  130. package/dist/tools/delete-google-business-media.js +0 -17
  131. package/dist/tools/delete-google-business-review-reply.d.ts +0 -32
  132. package/dist/tools/delete-google-business-review-reply.js +0 -17
  133. package/dist/tools/delete-oauth-client.d.ts +0 -20
  134. package/dist/tools/delete-oauth-client.js +0 -13
  135. package/dist/tools/delete-post-group.d.ts +0 -20
  136. package/dist/tools/delete-post-group.js +0 -13
  137. package/dist/tools/delete-webhook.d.ts +0 -20
  138. package/dist/tools/delete-webhook.js +0 -13
  139. package/dist/tools/get-account-analytics.d.ts +0 -31
  140. package/dist/tools/get-account-analytics.js +0 -152
  141. package/dist/tools/get-brand-profile.d.ts +0 -16
  142. package/dist/tools/get-brand-profile.js +0 -73
  143. package/dist/tools/get-brand.d.ts +0 -16
  144. package/dist/tools/get-brand.js +0 -26
  145. package/dist/tools/get-comment.d.ts +0 -20
  146. package/dist/tools/get-comment.js +0 -36
  147. package/dist/tools/get-connect-link.d.ts +0 -28
  148. package/dist/tools/get-connect-link.js +0 -51
  149. package/dist/tools/get-connect-session.d.ts +0 -20
  150. package/dist/tools/get-connect-session.js +0 -54
  151. package/dist/tools/get-conversation.d.ts +0 -24
  152. package/dist/tools/get-conversation.js +0 -31
  153. package/dist/tools/get-google-business-review-link.d.ts +0 -24
  154. package/dist/tools/get-google-business-review-link.js +0 -19
  155. package/dist/tools/get-image-job.d.ts +0 -24
  156. package/dist/tools/get-image-job.js +0 -31
  157. package/dist/tools/get-learned-voice.d.ts +0 -16
  158. package/dist/tools/get-learned-voice.js +0 -22
  159. package/dist/tools/get-mcp-status.d.ts +0 -8
  160. package/dist/tools/get-mcp-status.js +0 -182
  161. package/dist/tools/get-performance-profile.d.ts +0 -16
  162. package/dist/tools/get-performance-profile.js +0 -39
  163. package/dist/tools/get-platform-schema.d.ts +0 -24
  164. package/dist/tools/get-platform-schema.js +0 -48
  165. package/dist/tools/get-post-analytics.d.ts +0 -39
  166. package/dist/tools/get-post-analytics.js +0 -222
  167. package/dist/tools/get-post-insights.d.ts +0 -28
  168. package/dist/tools/get-post-insights.js +0 -39
  169. package/dist/tools/get-post-missing.d.ts +0 -16
  170. package/dist/tools/get-post-missing.js +0 -12
  171. package/dist/tools/get-video-job.d.ts +0 -24
  172. package/dist/tools/get-video-job.js +0 -31
  173. package/dist/tools/list-brand-accounts.d.ts +0 -16
  174. package/dist/tools/list-brand-accounts.js +0 -24
  175. package/dist/tools/reply-google-business-review.d.ts +0 -36
  176. package/dist/tools/reply-google-business-review.js +0 -18
  177. package/dist/tools/reply-to-comment.d.ts +0 -32
  178. package/dist/tools/reply-to-comment.js +0 -17
  179. package/dist/tools/send-message.d.ts +0 -28
  180. package/dist/tools/send-message.js +0 -16
  181. package/dist/tools/suggest-google-business-review-reply.d.ts +0 -48
  182. package/dist/tools/suggest-google-business-review-reply.js +0 -27
  183. package/dist/tools/sync-inbox.d.ts +0 -24
  184. package/dist/tools/sync-inbox.js +0 -18
  185. package/dist/tools/test-webhook.d.ts +0 -20
  186. package/dist/tools/test-webhook.js +0 -14
  187. package/dist/tools/update-comment.d.ts +0 -28
  188. package/dist/tools/update-comment.js +0 -21
  189. package/dist/tools/update-oauth-client.d.ts +0 -44
  190. package/dist/tools/update-oauth-client.js +0 -21
  191. package/dist/tools/update-post-release-id.d.ts +0 -28
  192. package/dist/tools/update-post-release-id.js +0 -18
  193. package/dist/tools/update-post-status.d.ts +0 -28
  194. package/dist/tools/update-post-status.js +0 -23
  195. package/dist/tools/update-webhook.d.ts +0 -40
  196. package/dist/tools/update-webhook.js +0 -21
  197. package/dist/tools/upload-media-from-url.d.ts +0 -24
  198. package/dist/tools/upload-media-from-url.js +0 -26
  199. package/src/tools/add-google-business-media.ts +0 -50
  200. package/src/tools/create-oauth-client.ts +0 -36
  201. package/src/tools/delete-comment.ts +0 -29
  202. package/src/tools/delete-google-business-media.ts +0 -30
  203. package/src/tools/delete-google-business-review-reply.ts +0 -30
  204. package/src/tools/delete-oauth-client.ts +0 -23
  205. package/src/tools/delete-post-group.ts +0 -17
  206. package/src/tools/delete-webhook.ts +0 -17
  207. package/src/tools/get-account-analytics.ts +0 -171
  208. package/src/tools/get-brand-profile.ts +0 -86
  209. package/src/tools/get-brand.ts +0 -34
  210. package/src/tools/get-comment.ts +0 -47
  211. package/src/tools/get-connect-link.ts +0 -61
  212. package/src/tools/get-connect-session.ts +0 -63
  213. package/src/tools/get-conversation.ts +0 -44
  214. package/src/tools/get-google-business-review-link.ts +0 -30
  215. package/src/tools/get-image-job.ts +0 -36
  216. package/src/tools/get-learned-voice.ts +0 -31
  217. package/src/tools/get-mcp-status.ts +0 -212
  218. package/src/tools/get-performance-profile.ts +0 -47
  219. package/src/tools/get-platform-schema.ts +0 -56
  220. package/src/tools/get-post-analytics.ts +0 -243
  221. package/src/tools/get-post-insights.ts +0 -60
  222. package/src/tools/get-post-missing.ts +0 -16
  223. package/src/tools/get-video-job.ts +0 -36
  224. package/src/tools/list-brand-accounts.ts +0 -36
  225. package/src/tools/reply-google-business-review.ts +0 -32
  226. package/src/tools/reply-to-comment.ts +0 -30
  227. package/src/tools/send-message.ts +0 -28
  228. package/src/tools/suggest-google-business-review-reply.ts +0 -44
  229. package/src/tools/sync-inbox.ts +0 -28
  230. package/src/tools/test-webhook.ts +0 -18
  231. package/src/tools/update-comment.ts +0 -31
  232. package/src/tools/update-oauth-client.ts +0 -38
  233. package/src/tools/update-post-release-id.ts +0 -25
  234. package/src/tools/update-post-status.ts +0 -31
  235. package/src/tools/update-webhook.ts +0 -36
  236. package/src/tools/upload-media-from-url.ts +0 -33
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Per-call context and shared helpers for the npm stdio tools that merge
3
+ * several old tools (MCP tool consolidation, 0.45.0).
4
+ *
5
+ * - validateArgs(): the registry's per-mode rules (mcp-server/tool-registry.json)
6
+ * run before any request, same as the hosted endpoint
7
+ * - previewThenConfirm(): preview then confirm for public or destructive
8
+ * actions; alias calls (ctx.legacyConfirm) keep their old one-step confirm
9
+ * until the alias sunset date
10
+ *
11
+ * The context is built per call in index.ts from the transport's request map,
12
+ * never stored in the arguments or on shared state.
13
+ */
14
+ import { type ToolRegistry } from './tool-registry.js';
15
+ import type { StdioPreviewTokens } from './preview-token.js';
16
+ export interface ToolContext {
17
+ /** The name the client called: an alias or the canonical tool. */
18
+ calledName: string;
19
+ /** True for alias calls: confirm: true alone still runs preview actions until the sunset date. */
20
+ legacyConfirm: boolean;
21
+ registry: ToolRegistry;
22
+ previews: StdioPreviewTokens;
23
+ /** The tool sets listed in this process (POSTERLY_TOOLSETS); every set when all is true. */
24
+ toolsets?: {
25
+ enabled: string[];
26
+ all: boolean;
27
+ };
28
+ }
29
+ /**
30
+ * Validates a merged tool's args against the registry's per-mode rules and
31
+ * returns the normalised args and mode. Throws a plain error before any request.
32
+ */
33
+ export declare function validateArgs(ctx: ToolContext, tool: string, input: unknown): {
34
+ mode: string | null;
35
+ args: Record<string, any>;
36
+ };
37
+ /** A shallow copy of args without the given keys. */
38
+ export declare function omitKeys(args: Record<string, any>, keys: string[]): Record<string, any>;
39
+ export interface PreviewStep {
40
+ tool: string;
41
+ action: string;
42
+ /** Validated (normalised) args: the preview and the confirm call must hash identically. */
43
+ args: Record<string, any>;
44
+ /** Public writes: a preview_id can be confirmed once only. */
45
+ nonce: boolean;
46
+ /** The old tool's refusal when confirm is missing, for alias calls. */
47
+ legacyRefusal: string;
48
+ ctx: ToolContext;
49
+ /** What will change (may make one read request; must not throw). */
50
+ describe: () => Promise<string>;
51
+ run: () => Promise<string>;
52
+ }
53
+ /**
54
+ * Preview then confirm (docs/mcp-tool-design.md section 4). Canonical calls:
55
+ * no confirm returns a preview and a preview_id bound to this tool, action,
56
+ * and exact args; confirm needs that preview_id. Alias calls keep the old
57
+ * contract until the sunset: confirm: true alone runs the action.
58
+ */
59
+ export declare function previewThenConfirm(step: PreviewStep): Promise<string>;
60
+ /** Runs one read for a preview; a failed read (for example a 403 on a narrower key) falls back to echoing the request. */
61
+ export declare function readForPreview<T>(read: () => Promise<T>): Promise<{
62
+ ok: true;
63
+ data: T;
64
+ } | {
65
+ ok: false;
66
+ error: string;
67
+ }>;
68
+ export declare function readFailedNote(error: string): string;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Per-call context and shared helpers for the npm stdio tools that merge
3
+ * several old tools (MCP tool consolidation, 0.45.0).
4
+ *
5
+ * - validateArgs(): the registry's per-mode rules (mcp-server/tool-registry.json)
6
+ * run before any request, same as the hosted endpoint
7
+ * - previewThenConfirm(): preview then confirm for public or destructive
8
+ * actions; alias calls (ctx.legacyConfirm) keep their old one-step confirm
9
+ * until the alias sunset date
10
+ *
11
+ * The context is built per call in index.ts from the transport's request map,
12
+ * never stored in the arguments or on shared state.
13
+ */
14
+ import { canonicalArgs, validateMode } from './tool-registry.js';
15
+ import { previewFailureMessage } from './preview-token-core.js';
16
+ import { code, mdSection, mdTitle } from './format.js';
17
+ /** The local process serves one user; preview tokens are bound to this process's key anyway. */
18
+ const STDIO_PREVIEW_USER = 'stdio-local';
19
+ /**
20
+ * Validates a merged tool's args against the registry's per-mode rules and
21
+ * returns the normalised args and mode. Throws a plain error before any request.
22
+ */
23
+ export function validateArgs(ctx, tool, input) {
24
+ const validated = validateMode(ctx.registry, tool, input);
25
+ if (!validated.ok)
26
+ throw new Error(validated.error);
27
+ return { mode: validated.mode, args: validated.args };
28
+ }
29
+ /** A shallow copy of args without the given keys. */
30
+ export function omitKeys(args, keys) {
31
+ const out = {};
32
+ for (const [key, value] of Object.entries(args || {})) {
33
+ if (!keys.includes(key))
34
+ out[key] = value;
35
+ }
36
+ return out;
37
+ }
38
+ function formatPreview(body, previewId, tool) {
39
+ return [
40
+ mdTitle('Preview only: nothing has changed yet'),
41
+ body,
42
+ mdSection('Preview ID', `${code(previewId)} (expires in 10 minutes)`),
43
+ mdSection('Next step', `Show this preview to the user. Only after they explicitly approve, call ${tool} again with the same fields plus confirm: true and this preview_id.`),
44
+ ].filter(Boolean).join('\n\n');
45
+ }
46
+ /**
47
+ * Preview then confirm (docs/mcp-tool-design.md section 4). Canonical calls:
48
+ * no confirm returns a preview and a preview_id bound to this tool, action,
49
+ * and exact args; confirm needs that preview_id. Alias calls keep the old
50
+ * contract until the sunset: confirm: true alone runs the action.
51
+ */
52
+ export async function previewThenConfirm(step) {
53
+ const { ctx, args } = step;
54
+ if (ctx.legacyConfirm) {
55
+ if (args.confirm !== true)
56
+ throw new Error(step.legacyRefusal);
57
+ return step.run();
58
+ }
59
+ const binding = { userId: STDIO_PREVIEW_USER, tool: step.tool, action: step.action, args: canonicalArgs(args) };
60
+ if (args.confirm !== true) {
61
+ const previewId = ctx.previews.create(binding);
62
+ return formatPreview(await step.describe(), previewId, step.tool);
63
+ }
64
+ const token = args.preview_id;
65
+ if (typeof token !== 'string' || !token)
66
+ throw new Error(previewFailureMessage('missing'));
67
+ const verified = ctx.previews.verify(token, binding);
68
+ if (!verified.ok)
69
+ throw new Error(previewFailureMessage(verified.reason));
70
+ if (step.nonce && !ctx.previews.claimOnce(token, verified.exp))
71
+ throw new Error(previewFailureMessage('used'));
72
+ return step.run();
73
+ }
74
+ /** Runs one read for a preview; a failed read (for example a 403 on a narrower key) falls back to echoing the request. */
75
+ export async function readForPreview(read) {
76
+ try {
77
+ return { ok: true, data: await read() };
78
+ }
79
+ catch (error) {
80
+ return { ok: false, error: error?.message || String(error) };
81
+ }
82
+ }
83
+ export function readFailedNote(error) {
84
+ return `_posterly could not read the current item (${error}), so the preview shows the request as given._`;
85
+ }
@@ -1 +1 @@
1
- export declare const POSTERLY_MCP_VERSION = "0.44.0";
1
+ export declare const POSTERLY_MCP_VERSION = "0.45.1";
@@ -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.44.0';
8
+ export const POSTERLY_MCP_VERSION = '0.45.1';
@@ -5,7 +5,7 @@ export const auditGoogleBusinessProfileTool = {
5
5
  inputSchema: z.object({
6
6
  workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
7
7
  account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts.'),
8
- location_id: z.string().optional().describe('Google Business numeric location id (the location_id/platform_user_id from list_accounts, e.g. "197940849675145390"). NOT the ChIJ... Place ID returned by get_google_business_review_link. Prefer account_id.'),
8
+ location_id: z.string().optional().describe('Google Business numeric location id (the location_id/platform_user_id from list_accounts, e.g. "197940849675145390"). NOT the ChIJ... Place ID from list_google_business_reviews view=review_link. Prefer account_id.'),
9
9
  }),
10
10
  async execute(client, input) {
11
11
  const result = await client.auditGoogleBusinessProfile(input);
@@ -20,13 +20,13 @@ export declare const connectAccountTool: {
20
20
  workspace_id: z.ZodOptional<z.ZodString>;
21
21
  connect_session_id: z.ZodOptional<z.ZodString>;
22
22
  }, "strip", z.ZodTypeAny, {
23
- platform: "telegram" | "bluesky" | "discord" | "devto" | "hashnode" | "wordpress" | "lemmy";
23
+ platform: "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy";
24
24
  username?: string | undefined;
25
25
  workspace_id?: string | undefined;
26
26
  connect_session_id?: string | undefined;
27
- handle?: string | undefined;
28
27
  bot_token?: string | undefined;
29
28
  chat_id?: string | undefined;
29
+ handle?: string | undefined;
30
30
  app_password?: string | undefined;
31
31
  webhook_url?: string | undefined;
32
32
  site_url?: string | undefined;
@@ -35,13 +35,13 @@ export declare const connectAccountTool: {
35
35
  instance?: string | undefined;
36
36
  password?: string | undefined;
37
37
  }, {
38
- platform: "telegram" | "bluesky" | "discord" | "devto" | "hashnode" | "wordpress" | "lemmy";
38
+ platform: "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy";
39
39
  username?: string | undefined;
40
40
  workspace_id?: string | undefined;
41
41
  connect_session_id?: string | undefined;
42
- handle?: string | undefined;
43
42
  bot_token?: string | undefined;
44
43
  chat_id?: string | undefined;
44
+ handle?: string | undefined;
45
45
  app_password?: string | undefined;
46
46
  webhook_url?: string | undefined;
47
47
  site_url?: string | undefined;
@@ -26,7 +26,7 @@ const PLATFORM_CREDENTIAL_FIELDS = {
26
26
  };
27
27
  export const connectAccountTool = {
28
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.',
29
+ description: 'Connect a credential-based social account (telegram, bluesky, discord, wordpress, devto, hashnode, lemmy) directly, without a browser session. Call list_platforms with view=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
30
  inputSchema: z.object({
31
31
  platform: z
32
32
  .enum(CREDENTIAL_PLATFORMS)
@@ -56,7 +56,7 @@ export const connectAccountTool = {
56
56
  }
57
57
  const missing = allowedFields.filter((field) => !(field in credentials));
58
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.`;
59
+ return `Missing credential field(s) for ${input.platform}: ${missing.join(', ')}. Call list_platforms with view=connect_link and target=${input.platform} to see field descriptions.`;
60
60
  }
61
61
  const result = await client.connectAccountCredentials({
62
62
  platform: input.platform,
@@ -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" | "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";
12
+ platform: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "medium" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "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" | "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";
17
+ platform: "meta" | "instagram_direct" | "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "medium" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "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;
@@ -3,7 +3,7 @@ import { DASHBOARD_CONNECT_URL, formatConnectedAccounts, formatLocalDateTime } f
3
3
  import { CONNECT_INPUTS } from '../generated/platform-manifest.js';
4
4
  export const createConnectSessionTool = {
5
5
  name: 'create_connect_session',
6
- description: 'Create a short-lived posterly dashboard handoff session for connecting a social account. Send the secure connection URL to the user, then poll get_connect_session to narrate progress. Do not ask for social media passwords or OAuth codes; posterly handles those browser steps.',
6
+ description: 'Create a short-lived posterly dashboard handoff session for connecting a social account. Send the secure connection URL to the user, then poll list_accounts with view=connect_session to narrate progress. Do not ask for social media passwords or OAuth codes; posterly handles those browser steps.',
7
7
  inputSchema: z.object({
8
8
  platform: z
9
9
  .enum(CONNECT_INPUTS)
@@ -41,7 +41,7 @@ function formatConnectSession(session, includeRaw) {
41
41
  `Expires: ${formatLocalDateTime(session.expires_at)}`,
42
42
  `Dashboard connect page: ${DASHBOARD_CONNECT_URL}`,
43
43
  '',
44
- 'Next step for the AI agent: send the secure connection URL to the user, explain that posterly will handle the browser OAuth step, then poll get_connect_session until the account is connected or the session ends.',
44
+ 'Next step for the AI agent: send the secure connection URL to the user, explain that posterly will handle the browser OAuth step, then poll list_accounts with view=connect_session until the account is connected or the session ends.',
45
45
  includeRaw ? `\nRaw connect session:\n${JSON.stringify(session, null, 2)}` : '',
46
46
  ].filter(Boolean).join('\n');
47
47
  }
@@ -968,7 +968,7 @@ export declare const createPostPayloadInputSchema: z.ZodObject<{
968
968
  }, z.ZodTypeAny, "passthrough">>>;
969
969
  workspace_id: z.ZodOptional<z.ZodString>;
970
970
  }, "strip", z.ZodTypeAny, {
971
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
971
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
972
972
  username?: string | undefined;
973
973
  workspace_id?: string | undefined;
974
974
  account_id?: string | undefined;
@@ -1131,7 +1131,7 @@ export declare const createPostPayloadInputSchema: z.ZodObject<{
1131
1131
  ai_generated: z.ZodOptional<z.ZodBoolean>;
1132
1132
  }, z.ZodTypeAny, "passthrough"> | undefined;
1133
1133
  }, {
1134
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
1134
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
1135
1135
  username?: string | undefined;
1136
1136
  workspace_id?: string | undefined;
1137
1137
  account_id?: string | undefined;
@@ -1787,7 +1787,7 @@ export declare const createPostInputSchema: z.ZodObject<{
1787
1787
  confirm: z.ZodLiteral<true>;
1788
1788
  }, "strip", z.ZodTypeAny, {
1789
1789
  confirm: true;
1790
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
1790
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
1791
1791
  username?: string | undefined;
1792
1792
  workspace_id?: string | undefined;
1793
1793
  account_id?: string | undefined;
@@ -1951,7 +1951,7 @@ export declare const createPostInputSchema: z.ZodObject<{
1951
1951
  }, z.ZodTypeAny, "passthrough"> | undefined;
1952
1952
  }, {
1953
1953
  confirm: true;
1954
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
1954
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
1955
1955
  username?: string | undefined;
1956
1956
  workspace_id?: string | undefined;
1957
1957
  account_id?: string | undefined;
@@ -2116,7 +2116,30 @@ export declare const createPostInputSchema: z.ZodObject<{
2116
2116
  }>;
2117
2117
  export type CreatePostInput = z.infer<typeof createPostInputSchema>;
2118
2118
  export type CreatePostPayloadInput = z.infer<typeof createPostPayloadInputSchema>;
2119
- export declare function buildCreatePostPayload(input: CreatePostPayloadInput | CreatePostInput): Parameters<PosterlyClient['createPost']>[0];
2119
+ export type AccountPlatformLookup = {
2120
+ found: true;
2121
+ platform: string | null;
2122
+ } | {
2123
+ found: false;
2124
+ detail: string;
2125
+ };
2126
+ export type AccountPlatformResolver = (accountId: string, workspaceId?: string) => Promise<AccountPlatformLookup>;
2127
+ /**
2128
+ * Builds a resolver that answers "what platform is this account_id on?" via
2129
+ * client.listAccounts() (there is no GET /accounts/:id), for thread_posts
2130
+ * calls that gave account_id without platform. Caches one in-flight lookup
2131
+ * per workspace_id (including the unscoped list, keyed ''), so
2132
+ * create_posts_batch never issues more than one accounts request per
2133
+ * distinct workspace_id even though every item in the batch calls this
2134
+ * independently: the cache is populated synchronously (before the first
2135
+ * await) on the first call for a given workspace_id, so concurrent
2136
+ * Promise.all callers for the same workspace_id share the one in-flight
2137
+ * request. A failed lookup (non-2xx, missing accounts:read, network error)
2138
+ * is cached too, so it is reported once per workspace_id rather than
2139
+ * surfacing a fresh error per item.
2140
+ */
2141
+ export declare function createAccountPlatformResolver(client: PosterlyClient): AccountPlatformResolver;
2142
+ export declare function buildCreatePostPayload(input: CreatePostPayloadInput | CreatePostInput, resolvePlatform: AccountPlatformResolver): Promise<Parameters<PosterlyClient['createPost']>[0]>;
2120
2143
  export declare const createPostTool: {
2121
2144
  name: string;
2122
2145
  description: string;
@@ -2613,7 +2636,7 @@ export declare const createPostTool: {
2613
2636
  confirm: z.ZodLiteral<true>;
2614
2637
  }, "strip", z.ZodTypeAny, {
2615
2638
  confirm: true;
2616
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
2639
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
2617
2640
  username?: string | undefined;
2618
2641
  workspace_id?: string | undefined;
2619
2642
  account_id?: string | undefined;
@@ -2777,7 +2800,7 @@ export declare const createPostTool: {
2777
2800
  }, z.ZodTypeAny, "passthrough"> | undefined;
2778
2801
  }, {
2779
2802
  confirm: true;
2780
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
2803
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
2781
2804
  username?: string | undefined;
2782
2805
  workspace_id?: string | undefined;
2783
2806
  account_id?: string | undefined;
@@ -151,7 +151,7 @@ export const createPostPayloadInputSchema = z.object({
151
151
  .describe('For X or Threads only: array of 2+ strings, one per post in the thread. The first entry leads, the rest reply in order. When set, the platform must be twitter or threads.'),
152
152
  instagram_settings: instagramSettingsSchema
153
153
  .optional()
154
- .describe('Instagram-specific settings. If “tag” is ambiguous, distinguish caption @mentions (put @username in caption), native media tags (user_tags), and co-author invitations (collaborators). Native media tags work with eligible direct or Meta-linked accounts; collaborators require an eligible Meta-linked account. Call get_platform_schema(account_id) first.'),
154
+ .describe('Instagram-specific settings. If “tag” is ambiguous, distinguish caption @mentions (put @username in caption), native media tags (user_tags), and co-author invitations (collaborators). Native media tags work with eligible direct or Meta-linked accounts; collaborators require an eligible Meta-linked account. Call list_platforms with view=schema and account_id first.'),
155
155
  platform_settings: platformSettingsSchema
156
156
  .optional()
157
157
  .describe('Platform-specific settings for the selected account. Use this for non-Instagram settings and prefer it over raw metadata.'),
@@ -163,17 +163,83 @@ export const createPostPayloadInputSchema = z.object({
163
163
  export const createPostInputSchema = createPostPayloadInputSchema.extend({
164
164
  confirm: z.literal(true).describe('Must be true after explicit user confirmation of the post/account/workspace/schedule.'),
165
165
  });
166
- export function buildCreatePostPayload(input) {
166
+ /**
167
+ * Builds a resolver that answers "what platform is this account_id on?" via
168
+ * client.listAccounts() (there is no GET /accounts/:id), for thread_posts
169
+ * calls that gave account_id without platform. Caches one in-flight lookup
170
+ * per workspace_id (including the unscoped list, keyed ''), so
171
+ * create_posts_batch never issues more than one accounts request per
172
+ * distinct workspace_id even though every item in the batch calls this
173
+ * independently: the cache is populated synchronously (before the first
174
+ * await) on the first call for a given workspace_id, so concurrent
175
+ * Promise.all callers for the same workspace_id share the one in-flight
176
+ * request. A failed lookup (non-2xx, missing accounts:read, network error)
177
+ * is cached too, so it is reported once per workspace_id rather than
178
+ * surfacing a fresh error per item.
179
+ */
180
+ export function createAccountPlatformResolver(client) {
181
+ const cache = new Map();
182
+ const lookupForWorkspace = (workspaceKey) => {
183
+ let pending = cache.get(workspaceKey);
184
+ if (!pending) {
185
+ pending = (async () => {
186
+ try {
187
+ const accounts = await client.listAccounts(workspaceKey ? { workspace_id: workspaceKey } : undefined);
188
+ const platforms = new Map();
189
+ for (const account of accounts) {
190
+ if (account && typeof account.platform === 'string' && account.id !== undefined && account.id !== null) {
191
+ platforms.set(String(account.id), account.platform);
192
+ }
193
+ }
194
+ return { ok: true, platforms };
195
+ }
196
+ catch (error) {
197
+ return { ok: false, detail: error?.message || 'accounts lookup failed' };
198
+ }
199
+ })();
200
+ cache.set(workspaceKey, pending);
201
+ }
202
+ return pending;
203
+ };
204
+ return async (accountId, workspaceId) => {
205
+ const workspaceKey = workspaceId || '';
206
+ const lookup = await lookupForWorkspace(workspaceKey);
207
+ if (!lookup.ok)
208
+ return { found: false, detail: lookup.detail };
209
+ return { found: true, platform: lookup.platforms.get(String(accountId)) ?? null };
210
+ };
211
+ }
212
+ export async function buildCreatePostPayload(input, resolvePlatform) {
167
213
  const { confirm: _confirm, thread_posts, caption, post_type, instagram_settings, platform_settings, ...rest } = input;
168
214
  if (thread_posts && thread_posts.length > 0) {
169
215
  if (thread_posts.length < 2) {
170
216
  throw new Error('thread_posts must contain at least 2 entries');
171
217
  }
172
218
  const platformHint = (input.platform || '').toLowerCase();
173
- const isTwitter = platformHint === 'twitter' || platformHint === 'x';
174
- const isThreads = platformHint === 'threads';
175
- if (!isTwitter && !isThreads && !input.account_id) {
176
- throw new Error('thread_posts requires `platform` to be "twitter" or "threads", or pass `account_id` for an X/Threads account.');
219
+ let isTwitter = platformHint === 'twitter' || platformHint === 'x';
220
+ let isThreads = platformHint === 'threads';
221
+ if (!isTwitter && !isThreads) {
222
+ if (!input.account_id) {
223
+ throw new Error('thread_posts requires `platform` to be "twitter" or "threads", or pass `account_id` for an X/Threads account.');
224
+ }
225
+ // No platform hint: never guess X. Resolve the account's real platform
226
+ // first, so a Threads account_id builds a threads_thread, not an
227
+ // x_thread that the server then rejects.
228
+ const lookup = await resolvePlatform(input.account_id, input.workspace_id);
229
+ if (!lookup.found) {
230
+ throw new Error(`thread_posts could not resolve account_id's platform (${lookup.detail}). Pass \`platform\` explicitly.`);
231
+ }
232
+ else if (lookup.platform === 'threads') {
233
+ isThreads = true;
234
+ }
235
+ else if (lookup.platform === 'twitter') {
236
+ isTwitter = true;
237
+ }
238
+ else {
239
+ throw new Error(lookup.platform
240
+ ? `thread_posts requires an X/Twitter or Threads account; account_id resolved to platform "${lookup.platform}".`
241
+ : 'thread_posts requires `platform` to be "twitter" or "threads", or an `account_id` that exists in list_accounts. Pass `platform` explicitly.');
242
+ }
177
243
  }
178
244
  const arrayKey = isThreads ? 'threads_thread_posts' : 'x_thread_tweets';
179
245
  const totalKey = isThreads ? 'threads_thread_total' : 'x_thread_total';
@@ -218,16 +284,16 @@ export const createPostTool = {
218
284
  '4. If scheduling multiple posts in one turn, list every post first and confirm the batch as a whole, then prefer `create_posts_batch` so they are created in one API request.\n\n' +
219
285
  'AFTER CALLING: Tell the user the post was created, include the posterly dashboard link returned by the tool, and offer the next natural action. Do not narrate raw HTTP, curl, or API plumbing.\n\n' +
220
286
  'Provide either account_id OR username+platform to identify the account. If scheduled_at is omitted, the post publishes immediately. If workspace_id is omitted, the server resolves one from the social account, falling back to the caller\'s default (personal) workspace - pass workspace_id explicitly if the user has more than one workspace.\n\n' +
221
- 'MEDIA URLS: Posterly storage URLs are used as-is. Third-party HTTP(S) media URLs are copied into posterly storage before the post is saved, so API-created posts do not later fail on expired signed URLs. Short-lived signed URLs must still be live when this tool runs. You can also call `upload_media_from_url` first if you want to upload once and reuse the returned posterly URL across posts.\n\n' +
287
+ 'MEDIA URLS: Posterly storage URLs are used as-is. Third-party HTTP(S) media URLs are copied into posterly storage before the post is saved, so API-created posts do not later fail on expired signed URLs. Short-lived signed URLs must still be live when this tool runs. You can also call `upload_media` with `url` first if you want to upload once and reuse the returned posterly URL across posts.\n\n' +
222
288
  'THREADS: Pass `thread_posts` (an array of 2+ strings) to schedule a multi-post thread on X (Twitter) or Threads (Meta). The first entry is the lead post; the rest are published as replies in the same chain. X entries are capped at 280 characters each (4000 for verified, 25000 for organization accounts); Threads entries are capped at 500 characters each. When `thread_posts` is set, `caption` is ignored.\n\n' +
223
- 'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/brandPartner, LinkedIn document titles/mentions/polls/content_call_to_action_label (including BUY_NOW and SHOP_NOW), TikTok direct-post privacy/toggles/commercial disclosure, Pinterest board/title/link/product_tags, GBP event/offer/CTA/recurrence, X reply settings/polls/paid_partnership, Threads reply controls/text attachments/ghost posts/spoilers/reply approvals/GIPHY, Telegram polls/buttons/video cover/live photo, Slack markdown/blocks, Mastodon visibility/quote/poll (media+poll allowed), and Instagram feed/story/reel/carousel/native media tags/collaborators/first comments/trial Reels/is_ai_generated/audio_id. For Instagram, @username in caption is only a caption mention; `user_tags` creates native media tags and `collaborators` sends co-author invitations only when the selected Meta-linked account reports those capabilities. Call `get_platform_schema` with the account ID first. `instagram_settings` remains as a backwards-compatible alias.\n\n' +
289
+ 'PLATFORM SETTINGS: Pass `platform_settings` for composer-equivalent controls: Facebook story/reel/backgrounds, YouTube title/privacy/thumbnail/brandPartner, LinkedIn document titles/mentions/polls/content_call_to_action_label (including BUY_NOW and SHOP_NOW), TikTok direct-post privacy/toggles/commercial disclosure, Pinterest board/title/link/product_tags, GBP event/offer/CTA/recurrence, X reply settings/polls/paid_partnership, Threads reply controls/text attachments/ghost posts/spoilers/reply approvals/GIPHY, Telegram polls/buttons/video cover/live photo, Slack markdown/blocks, Mastodon visibility/quote/poll (media+poll allowed), and Instagram feed/story/reel/carousel/native media tags/collaborators/first comments/trial Reels/is_ai_generated/audio_id. For Instagram, @username in caption is only a caption mention; `user_tags` creates native media tags and `collaborators` sends co-author invitations only when the selected Meta-linked account reports those capabilities. Call `list_platforms` with `view: "schema"` and the account ID first. `instagram_settings` remains as a backwards-compatible alias.\n\n' +
224
290
  'TIKTOK MEDIA: TikTok supports a single video or a photo slideshow of 1 to 35 images, never multiple videos. Image media auto-detects as a slideshow, so you usually do not set post_type or media_type for photos and every image is posted. Pass `platform_settings.media_type: "PHOTO"` to force a slideshow explicitly.',
225
291
  inputSchema: createPostInputSchema,
226
292
  async execute(client, input) {
227
293
  if (input.confirm !== true) {
228
294
  throw new Error('Refusing to create post without confirm=true after explicit user confirmation.');
229
295
  }
230
- const payload = buildCreatePostPayload(input);
296
+ const payload = await buildCreatePostPayload(input, createAccountPlatformResolver(client));
231
297
  const { thread_posts, instagram_settings, platform_settings } = input;
232
298
  const result = await client.createPost(payload);
233
299
  const p = result.post;
@@ -495,7 +495,7 @@ export declare const createPostsBatchTool: {
495
495
  }, z.ZodTypeAny, "passthrough">>>;
496
496
  workspace_id: z.ZodOptional<z.ZodString>;
497
497
  }, "strip", z.ZodTypeAny, {
498
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
498
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
499
499
  username?: string | undefined;
500
500
  workspace_id?: string | undefined;
501
501
  account_id?: string | undefined;
@@ -658,7 +658,7 @@ export declare const createPostsBatchTool: {
658
658
  ai_generated: z.ZodOptional<z.ZodBoolean>;
659
659
  }, z.ZodTypeAny, "passthrough"> | undefined;
660
660
  }, {
661
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
661
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
662
662
  username?: string | undefined;
663
663
  workspace_id?: string | undefined;
664
664
  account_id?: string | undefined;
@@ -825,7 +825,7 @@ export declare const createPostsBatchTool: {
825
825
  }, "strip", z.ZodTypeAny, {
826
826
  confirm: true;
827
827
  posts: {
828
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
828
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
829
829
  username?: string | undefined;
830
830
  workspace_id?: string | undefined;
831
831
  account_id?: string | undefined;
@@ -991,7 +991,7 @@ export declare const createPostsBatchTool: {
991
991
  }, {
992
992
  confirm: true;
993
993
  posts: {
994
- platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "slack" | "mastodon" | "devto" | "hashnode" | "wordpress" | "lemmy" | "gmb" | "google-business" | "google_business_profile" | undefined;
994
+ platform?: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "threads" | "linkedin" | "youtube" | "pinterest" | "google_business" | "x" | "telegram" | "bluesky" | "discord" | "wordpress" | "devto" | "hashnode" | "lemmy" | "slack" | "mastodon" | "gmb" | "google-business" | "google_business_profile" | undefined;
995
995
  username?: string | undefined;
996
996
  workspace_id?: string | undefined;
997
997
  account_id?: string | undefined;
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { dashboardUrlForPostList, formatLocalDateTime } from '../lib/format.js';
3
- import { buildCreatePostPayload, createPostPayloadInputSchema } from './create-post.js';
3
+ import { buildCreatePostPayload, createAccountPlatformResolver, createPostPayloadInputSchema } from './create-post.js';
4
4
  export const createPostsBatchTool = {
5
5
  name: 'create_posts_batch',
6
6
  description: 'Create 1-25 scheduled or immediate social posts in one API request. This is a DESTRUCTIVE WRITE that creates content on the user\'s real social accounts - once scheduled_at passes, posts may go public and cannot be un-posted.\n\n' +
@@ -22,7 +22,19 @@ export const createPostsBatchTool = {
22
22
  if (input.confirm !== true) {
23
23
  throw new Error('Refusing to create batch posts without confirm=true after explicit user confirmation.');
24
24
  }
25
- const payloads = input.posts.map((post) => buildCreatePostPayload(post));
25
+ // One resolver shared across the whole batch: createAccountPlatformResolver
26
+ // caches its accounts lookup per workspace_id, so N items that all need
27
+ // thread_posts platform resolution for the same workspace still cost one
28
+ // listAccounts() call, not N.
29
+ const resolvePlatform = createAccountPlatformResolver(client);
30
+ const payloads = await Promise.all(input.posts.map(async (post, index) => {
31
+ try {
32
+ return await buildCreatePostPayload(post, resolvePlatform);
33
+ }
34
+ catch (error) {
35
+ throw new Error(`Item ${index}: ${error?.message || 'invalid post input'}`);
36
+ }
37
+ }));
26
38
  const result = await client.createPostsBatch({ posts: payloads });
27
39
  const createdPosts = result.posts.map((item) => item.post);
28
40
  const dashboardUrl = dashboardUrlForPostList(createdPosts);
@@ -15,15 +15,15 @@ export declare const createWebhookTool: {
15
15
  url: string;
16
16
  description?: string | undefined;
17
17
  workspace_id?: string | undefined;
18
- events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced" | "approval.requested" | "approval.approved" | "approval.changes_requested" | "approval.rejected" | "approval.commented")[] | undefined;
19
18
  is_active?: boolean | undefined;
19
+ events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced" | "approval.requested" | "approval.approved" | "approval.changes_requested" | "approval.rejected" | "approval.commented")[] | undefined;
20
20
  }, {
21
21
  confirm: true;
22
22
  url: string;
23
23
  description?: string | undefined;
24
24
  workspace_id?: string | undefined;
25
- events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced" | "approval.requested" | "approval.approved" | "approval.changes_requested" | "approval.rejected" | "approval.commented")[] | undefined;
26
25
  is_active?: boolean | undefined;
26
+ events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced" | "approval.requested" | "approval.approved" | "approval.changes_requested" | "approval.rejected" | "approval.commented")[] | undefined;
27
27
  }>;
28
28
  execute(client: PosterlyClient, input: {
29
29
  url: string;