posterly-mcp-server 0.19.5 → 0.19.7

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 (46) hide show
  1. package/README.md +38 -18
  2. package/dist/index.js +52 -9
  3. package/dist/lib/api-client.d.ts +85 -0
  4. package/dist/lib/api-client.js +36 -1
  5. package/dist/lib/format.d.ts +11 -0
  6. package/dist/lib/format.js +51 -0
  7. package/dist/tools/create-connect-session.d.ts +28 -0
  8. package/dist/tools/create-connect-session.js +47 -0
  9. package/dist/tools/create-post.d.ts +39 -39
  10. package/dist/tools/create-post.js +8 -4
  11. package/dist/tools/create-posts-batch.d.ts +29 -29
  12. package/dist/tools/create-posts-batch.js +7 -1
  13. package/dist/tools/create-webhook.d.ts +2 -2
  14. package/dist/tools/delete-post.js +8 -1
  15. package/dist/tools/generate-image.d.ts +2 -2
  16. package/dist/tools/get-agent-signup-info.d.ts +8 -0
  17. package/dist/tools/get-agent-signup-info.js +37 -0
  18. package/dist/tools/get-connect-link.d.ts +4 -0
  19. package/dist/tools/get-connect-link.js +7 -2
  20. package/dist/tools/get-connect-session.d.ts +20 -0
  21. package/dist/tools/get-connect-session.js +54 -0
  22. package/dist/tools/get-post.js +3 -1
  23. package/dist/tools/get-signup-session.d.ts +20 -0
  24. package/dist/tools/get-signup-session.js +68 -0
  25. package/dist/tools/get-video-job.d.ts +2 -2
  26. package/dist/tools/list-posts.d.ts +2 -2
  27. package/dist/tools/list-posts.js +11 -4
  28. package/dist/tools/start-signup.d.ts +44 -0
  29. package/dist/tools/start-signup.js +86 -0
  30. package/dist/tools/update-post.d.ts +20 -20
  31. package/dist/tools/update-webhook.d.ts +2 -2
  32. package/package.json +1 -1
  33. package/src/index.ts +77 -9
  34. package/src/lib/api-client.ts +141 -1
  35. package/src/lib/format.ts +56 -0
  36. package/src/tools/create-connect-session.ts +55 -0
  37. package/src/tools/create-post.ts +11 -4
  38. package/src/tools/create-posts-batch.ts +11 -1
  39. package/src/tools/delete-post.ts +8 -1
  40. package/src/tools/get-agent-signup-info.ts +42 -0
  41. package/src/tools/get-connect-link.ts +8 -3
  42. package/src/tools/get-connect-session.ts +63 -0
  43. package/src/tools/get-post.ts +3 -1
  44. package/src/tools/get-signup-session.ts +77 -0
  45. package/src/tools/list-posts.ts +11 -5
  46. package/src/tools/start-signup.ts +108 -0
package/README.md CHANGED
@@ -4,6 +4,8 @@ Use Posterly from any MCP-compatible AI client.
4
4
 
5
5
  This package gives Claude Desktop, Cursor, Windsurf, Cline, and other local MCP clients a `stdio` server that can:
6
6
 
7
+ - start paid Posterly signup before an API key exists
8
+ - poll signup progress while the user completes checkout and password setup
7
9
  - list connected social accounts
8
10
  - resolve brands/clients into the right accounts
9
11
  - schedule and manage posts
@@ -16,26 +18,34 @@ Posterly also exposes the same toolset over HTTP at [poster.ly/mcp](https://www.
16
18
  ## Requirements
17
19
 
18
20
  - Node.js `20+`
19
- - A Posterly account: [poster.ly/signup](https://www.poster.ly/signup)
20
- - The Posterly API add-on enabled: [poster.ly/dashboard/api](https://www.poster.ly/dashboard/api)
21
- - A Posterly API key
21
+ - No API key is required for the public signup tools: `get_agent_signup_info`, `start_signup`, and `get_signup_session`
22
+ - A Posterly account, API add-on, and API key are required for authenticated tools like `whoami`, `list_accounts`, `create_connect_session`, and `create_post`
22
23
 
23
24
  ## Install
24
25
 
25
- You can install globally:
26
+ Recommended: use it via `npx` in your MCP config so your client runs the current server without a global install.
26
27
 
27
- ```bash
28
- npm install -g posterly-mcp-server
28
+ To let an AI agent start signup before a Posterly API key exists, install the server without `POSTERLY_API_KEY`:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "posterly": {
34
+ "command": "npx",
35
+ "args": ["-y", "posterly-mcp-server@latest"]
36
+ }
37
+ }
38
+ }
29
39
  ```
30
40
 
31
- Or just use it via `npx` in your MCP config:
41
+ After paid signup is complete and Posterly shows an API key, add `POSTERLY_API_KEY` to unlock the authenticated tools:
32
42
 
33
43
  ```json
34
44
  {
35
45
  "mcpServers": {
36
46
  "posterly": {
37
47
  "command": "npx",
38
- "args": ["-y", "posterly-mcp-server"],
48
+ "args": ["-y", "posterly-mcp-server@latest"],
39
49
  "env": {
40
50
  "POSTERLY_API_KEY": "pst_live_your_key_here"
41
51
  }
@@ -46,12 +56,12 @@ Or just use it via `npx` in your MCP config:
46
56
 
47
57
  ## Quick setup
48
58
 
49
- 1. Sign up at [poster.ly](https://www.poster.ly/signup)
50
- 2. Go to [Dashboard API & MCP](https://www.poster.ly/dashboard/api)
51
- 3. Enable the API add-on
52
- 4. Generate an API key
53
- 5. Paste it into your MCP client config as `POSTERLY_API_KEY`
54
- 6. Restart your AI client
59
+ 1. Add the Posterly MCP server to your AI client.
60
+ 2. If you do not have Posterly yet, ask the AI to call `start_signup`.
61
+ 3. Pay in Stripe Checkout and set your Posterly password in the browser.
62
+ 4. When Posterly shows your API key, add it as `POSTERLY_API_KEY`.
63
+ 5. Restart your AI client.
64
+ 6. Ask the AI to call `whoami`, then continue by connecting your first social account.
55
65
 
56
66
  ## Example configs
57
67
 
@@ -64,7 +74,7 @@ Add this to your Claude Desktop MCP config:
64
74
  "mcpServers": {
65
75
  "posterly": {
66
76
  "command": "npx",
67
- "args": ["-y", "posterly-mcp-server"],
77
+ "args": ["-y", "posterly-mcp-server@latest"],
68
78
  "env": {
69
79
  "POSTERLY_API_KEY": "pst_live_your_key_here"
70
80
  }
@@ -82,7 +92,7 @@ Add the same server definition to your Cursor MCP settings:
82
92
  "mcpServers": {
83
93
  "posterly": {
84
94
  "command": "npx",
85
- "args": ["-y", "posterly-mcp-server"],
95
+ "args": ["-y", "posterly-mcp-server@latest"],
86
96
  "env": {
87
97
  "POSTERLY_API_KEY": "pst_live_your_key_here"
88
98
  }
@@ -93,12 +103,22 @@ Add the same server definition to your Cursor MCP settings:
93
103
 
94
104
  ## Available tools
95
105
 
96
- `posterly-mcp-server@0.19.5` exposes 49 tools:
106
+ `posterly-mcp-server@0.19.7` exposes 54 tools.
107
+
108
+ Public signup tools work before `POSTERLY_API_KEY` exists:
109
+
110
+ - `get_agent_signup_info`
111
+ - `start_signup` (start paid signup and return a Posterly checkout handoff URL)
112
+ - `get_signup_session` (poll checkout, payment, password, and agent-access status)
113
+
114
+ Authenticated tools require `POSTERLY_API_KEY`:
97
115
 
98
116
  - `whoami`
99
117
  - `list_accounts`
100
118
  - `disconnect_account` (disconnect a connected social account after explicit confirmation)
101
119
  - `get_connect_link`
120
+ - `create_connect_session` (create a guided browser handoff for connecting a social account)
121
+ - `get_connect_session` (poll connection progress while the user approves OAuth or enters credentials)
102
122
  - `list_oauth_clients`
103
123
  - `create_oauth_client` (create a public PKCE client after explicit confirmation)
104
124
  - `update_oauth_client` (update redirect URIs/scopes after explicit confirmation)
@@ -214,5 +234,5 @@ npm start
214
234
 
215
235
  The package reads:
216
236
 
217
- - `POSTERLY_API_KEY`
237
+ - optional `POSTERLY_API_KEY` for authenticated Posterly tools
218
238
  - optional `POSTERLY_URL` if you need to point at a non-production environment
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { PosterlyClient } from './lib/api-client.js';
5
+ import { getAgentSignupInfoTool } from './tools/get-agent-signup-info.js';
6
+ import { getSignupSessionTool } from './tools/get-signup-session.js';
7
+ import { startSignupTool } from './tools/start-signup.js';
5
8
  import { listAccountsTool } from './tools/list-accounts.js';
6
9
  import { disconnectAccountTool } from './tools/disconnect-account.js';
7
10
  import { listBrandsTool } from './tools/list-brands.js';
@@ -47,23 +50,45 @@ import { getXPostingQuotaTool } from './tools/get-x-posting-quota.js';
47
50
  import { createSignedUploadTool } from './tools/create-signed-upload.js';
48
51
  import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
49
52
  import { getConnectLinkTool } from './tools/get-connect-link.js';
53
+ import { createConnectSessionTool } from './tools/create-connect-session.js';
54
+ import { getConnectSessionTool } from './tools/get-connect-session.js';
50
55
  import { listOAuthClientsTool } from './tools/list-oauth-clients.js';
51
56
  import { createOAuthClientTool } from './tools/create-oauth-client.js';
52
57
  import { updateOAuthClientTool } from './tools/update-oauth-client.js';
53
58
  import { deleteOAuthClientTool } from './tools/delete-oauth-client.js';
54
59
  const server = new McpServer({
55
60
  name: 'posterly',
56
- version: '0.19.0',
61
+ version: '0.19.7',
57
62
  });
58
- let client;
59
- try {
60
- client = new PosterlyClient();
61
- }
62
- catch (err) {
63
- console.error(err.message);
64
- process.exit(1);
65
- }
63
+ const client = new PosterlyClient();
66
64
  // Register tools
65
+ server.tool(getAgentSignupInfoTool.name, getAgentSignupInfoTool.description, getAgentSignupInfoTool.inputSchema.shape, async () => {
66
+ try {
67
+ const text = await getAgentSignupInfoTool.execute(client);
68
+ return { content: [{ type: 'text', text }] };
69
+ }
70
+ catch (err) {
71
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
72
+ }
73
+ });
74
+ server.tool(startSignupTool.name, startSignupTool.description, startSignupTool.inputSchema.shape, async (input) => {
75
+ try {
76
+ const text = await startSignupTool.execute(client, input);
77
+ return { content: [{ type: 'text', text }] };
78
+ }
79
+ catch (err) {
80
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
81
+ }
82
+ });
83
+ server.tool(getSignupSessionTool.name, getSignupSessionTool.description, getSignupSessionTool.inputSchema.shape, async (input) => {
84
+ try {
85
+ const text = await getSignupSessionTool.execute(client, input);
86
+ return { content: [{ type: 'text', text }] };
87
+ }
88
+ catch (err) {
89
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
90
+ }
91
+ });
67
92
  server.tool(whoamiTool.name, whoamiTool.description, whoamiTool.inputSchema.shape, async () => {
68
93
  try {
69
94
  const text = await whoamiTool.execute(client);
@@ -100,6 +125,24 @@ server.tool(getConnectLinkTool.name, getConnectLinkTool.description, getConnectL
100
125
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
101
126
  }
102
127
  });
128
+ server.tool(createConnectSessionTool.name, createConnectSessionTool.description, createConnectSessionTool.inputSchema.shape, async (input) => {
129
+ try {
130
+ const text = await createConnectSessionTool.execute(client, input);
131
+ return { content: [{ type: 'text', text }] };
132
+ }
133
+ catch (err) {
134
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
135
+ }
136
+ });
137
+ server.tool(getConnectSessionTool.name, getConnectSessionTool.description, getConnectSessionTool.inputSchema.shape, async (input) => {
138
+ try {
139
+ const text = await getConnectSessionTool.execute(client, input);
140
+ return { content: [{ type: 'text', text }] };
141
+ }
142
+ catch (err) {
143
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
144
+ }
145
+ });
103
146
  server.tool(listOAuthClientsTool.name, listOAuthClientsTool.description, listOAuthClientsTool.inputSchema.shape, async () => {
104
147
  try {
105
148
  const text = await listOAuthClientsTool.execute(client);
@@ -348,6 +348,26 @@ export interface ConnectOption {
348
348
  connected_count?: number;
349
349
  connected_accounts?: Account[];
350
350
  }
351
+ export interface ConnectSession {
352
+ id: string;
353
+ platform: string;
354
+ method: 'dashboard_oauth' | 'manual_credentials';
355
+ status: 'created' | 'opened' | 'awaiting_provider' | 'awaiting_credentials' | 'connected' | 'failed' | 'cancelled' | 'expired';
356
+ status_message: string;
357
+ connect_url: string;
358
+ poll_url: string;
359
+ workspace_id?: string | null;
360
+ connected_account_ids: string[];
361
+ connected_accounts: Account[];
362
+ connected_count: number;
363
+ error_code?: string | null;
364
+ error_message?: string | null;
365
+ created_at: string;
366
+ opened_at?: string | null;
367
+ provider_redirected_at?: string | null;
368
+ completed_at?: string | null;
369
+ expires_at: string;
370
+ }
351
371
  export interface OAuthDeveloperClient {
352
372
  id: string;
353
373
  client_id: string;
@@ -360,6 +380,55 @@ export interface OAuthDeveloperClient {
360
380
  created_at?: string;
361
381
  updated_at?: string;
362
382
  }
383
+ export type PublicSignupTier = 'starter' | 'pro' | 'power_user' | 'agency';
384
+ export interface PublicSignupPayload {
385
+ email: string;
386
+ name?: string;
387
+ tier?: PublicSignupTier;
388
+ interval?: 'month';
389
+ api_addon?: boolean;
390
+ return_path?: string;
391
+ source?: string;
392
+ }
393
+ export interface PublicSignupSession {
394
+ id: string;
395
+ status: string;
396
+ status_message?: string;
397
+ next_action?: {
398
+ actor?: string;
399
+ type?: string;
400
+ label?: string;
401
+ description?: string;
402
+ };
403
+ agent_next_steps?: string[];
404
+ checkout_redirect_url?: string | null;
405
+ checkout?: Record<string, unknown>;
406
+ account?: Record<string, unknown>;
407
+ billing?: Record<string, unknown>;
408
+ links?: Record<string, unknown>;
409
+ created_at?: string;
410
+ updated_at?: string;
411
+ expires_at?: string;
412
+ [key: string]: unknown;
413
+ }
414
+ export interface PublicSignupResponse {
415
+ signup_session?: PublicSignupSession;
416
+ checkout_session_id?: string;
417
+ checkout_url?: string | null;
418
+ checkout_redirect_url?: string | null;
419
+ poll_url?: string | null;
420
+ complete_url?: string | null;
421
+ dashboard_url?: string | null;
422
+ status?: string;
423
+ status_message?: string;
424
+ links?: Record<string, unknown>;
425
+ account?: Record<string, unknown>;
426
+ billing?: Record<string, unknown>;
427
+ [key: string]: unknown;
428
+ }
429
+ export interface PublicSignupSessionResponse {
430
+ signup_session: PublicSignupSession;
431
+ }
363
432
  export interface VideoJob {
364
433
  id: string;
365
434
  status: string;
@@ -401,7 +470,12 @@ export declare class PosterlyClient {
401
470
  private baseUrl;
402
471
  private apiKey;
403
472
  constructor(apiKey?: string, baseUrl?: string);
473
+ hasApiKey(): boolean;
474
+ private requireApiKey;
404
475
  private request;
476
+ private publicRequest;
477
+ startSignup(data: PublicSignupPayload): Promise<PublicSignupResponse>;
478
+ getSignupSession(sessionId: string): Promise<PublicSignupSessionResponse>;
405
479
  whoami(): Promise<Whoami>;
406
480
  listAccounts(params?: {
407
481
  workspace_id?: string;
@@ -415,6 +489,17 @@ export declare class PosterlyClient {
415
489
  connect?: ConnectOption;
416
490
  connect_options?: ConnectOption[];
417
491
  }>;
492
+ createConnectSession(data: {
493
+ platform: string;
494
+ workspace_id?: string;
495
+ auto_start?: boolean;
496
+ }): Promise<{
497
+ connect_session: ConnectSession;
498
+ connect?: ConnectOption;
499
+ }>;
500
+ getConnectSession(sessionId: string): Promise<{
501
+ connect_session: ConnectSession;
502
+ }>;
418
503
  listOAuthClients(): Promise<{
419
504
  clients: OAuthDeveloperClient[];
420
505
  }>;
@@ -6,11 +6,17 @@ export class PosterlyClient {
6
6
  constructor(apiKey, baseUrl) {
7
7
  this.apiKey = apiKey || process.env.POSTERLY_API_KEY || '';
8
8
  this.baseUrl = (baseUrl || process.env.POSTERLY_URL || 'https://www.poster.ly').replace(/\/$/, '');
9
+ }
10
+ hasApiKey() {
11
+ return Boolean(this.apiKey);
12
+ }
13
+ requireApiKey() {
9
14
  if (!this.apiKey) {
10
- throw new Error('POSTERLY_API_KEY is required. Set it as an environment variable.');
15
+ throw new Error('Posterly access is not installed yet. Use start_signup to begin paid setup, or set POSTERLY_API_KEY after signup.');
11
16
  }
12
17
  }
13
18
  async request(method, path, body) {
19
+ this.requireApiKey();
14
20
  const url = `${this.baseUrl}/api/v1${path}`;
15
21
  const headers = {
16
22
  Authorization: `Bearer ${this.apiKey}`,
@@ -30,6 +36,27 @@ export class PosterlyClient {
30
36
  }
31
37
  return res.json();
32
38
  }
39
+ async publicRequest(method, path, body) {
40
+ const url = `${this.baseUrl}${path}`;
41
+ const headers = {};
42
+ const init = { method, headers };
43
+ if (body) {
44
+ headers['Content-Type'] = 'application/json';
45
+ init.body = JSON.stringify(body);
46
+ }
47
+ const res = await fetch(url, init);
48
+ if (!res.ok) {
49
+ const err = await res.json().catch(() => ({ error: res.statusText }));
50
+ throw new Error(err.error || `API error: ${res.status}`);
51
+ }
52
+ return res.json();
53
+ }
54
+ async startSignup(data) {
55
+ return this.publicRequest('POST', '/api/v1/signup', data);
56
+ }
57
+ async getSignupSession(sessionId) {
58
+ return this.publicRequest('GET', `/api/v1/signup/sessions/${encodeURIComponent(sessionId)}`);
59
+ }
33
60
  async whoami() {
34
61
  return this.request('GET', '/whoami');
35
62
  }
@@ -56,6 +83,13 @@ export class PosterlyClient {
56
83
  : '/connect';
57
84
  return this.request('GET', `${path}${qs ? `?${qs}` : ''}`);
58
85
  }
86
+ async createConnectSession(data) {
87
+ const { platform, ...body } = data;
88
+ return this.request('POST', `/connect/${encodeURIComponent(platform)}/sessions`, body);
89
+ }
90
+ async getConnectSession(sessionId) {
91
+ return this.request('GET', `/connect/sessions/${encodeURIComponent(sessionId)}`);
92
+ }
59
93
  async listOAuthClients() {
60
94
  return this.request('GET', '/oauth/clients');
61
95
  }
@@ -304,6 +338,7 @@ export class PosterlyClient {
304
338
  });
305
339
  }
306
340
  async uploadMedia(input) {
341
+ this.requireApiKey();
307
342
  let fileBuffer;
308
343
  if (input.filePath) {
309
344
  fileBuffer = await readFile(resolve(input.filePath));
@@ -0,0 +1,11 @@
1
+ import type { Account, Post } from './api-client.js';
2
+ export declare const DASHBOARD_CALENDAR_URL: string;
3
+ export declare const DASHBOARD_TABLE_URL: string;
4
+ export declare const DASHBOARD_CONNECT_URL: string;
5
+ export declare function formatLocalDateTime(value?: string | null): string;
6
+ export declare function dashboardUrlForPost(post: Pick<Post, 'id' | 'scheduled_at'>): string;
7
+ export declare function dashboardUrlForPosts(): string;
8
+ export declare function dashboardUrlForPostList(posts: Array<Pick<Post, 'scheduled_at'>>): string;
9
+ export declare function truncateText(value: string | null | undefined, max?: number): string;
10
+ export declare function formatAccountLabel(account: Account): string;
11
+ export declare function formatConnectedAccounts(accounts?: Account[]): string;
@@ -0,0 +1,51 @@
1
+ const POSTERLY_APP_URL = (process.env.POSTERLY_URL || 'https://www.poster.ly').replace(/\/$/, '');
2
+ export const DASHBOARD_CALENDAR_URL = `${POSTERLY_APP_URL}/dashboard/calendar`;
3
+ export const DASHBOARD_TABLE_URL = `${POSTERLY_APP_URL}/dashboard/table`;
4
+ export const DASHBOARD_CONNECT_URL = `${POSTERLY_APP_URL}/dashboard/connect`;
5
+ export function formatLocalDateTime(value) {
6
+ if (!value)
7
+ return 'N/A';
8
+ const date = new Date(value);
9
+ if (Number.isNaN(date.getTime()))
10
+ return value;
11
+ return date.toLocaleString();
12
+ }
13
+ export function dashboardUrlForPost(post) {
14
+ const baseUrl = isInCurrentMonth(post.scheduled_at) ? DASHBOARD_CALENDAR_URL : DASHBOARD_TABLE_URL;
15
+ const url = new URL(baseUrl);
16
+ url.searchParams.set('post_id', String(post.id));
17
+ return url.toString();
18
+ }
19
+ export function dashboardUrlForPosts() {
20
+ return DASHBOARD_TABLE_URL;
21
+ }
22
+ export function dashboardUrlForPostList(posts) {
23
+ if (posts.length > 0 && posts.every((post) => isInCurrentMonth(post.scheduled_at))) {
24
+ return DASHBOARD_CALENDAR_URL;
25
+ }
26
+ return DASHBOARD_TABLE_URL;
27
+ }
28
+ export function truncateText(value, max = 80) {
29
+ const text = value?.trim() || '';
30
+ if (!text)
31
+ return '(empty)';
32
+ return text.length > max ? `${text.slice(0, max - 1)}...` : text;
33
+ }
34
+ export function formatAccountLabel(account) {
35
+ const username = account.username ? `@${account.username}` : `account ${account.id}`;
36
+ return `${account.platform} ${username}`;
37
+ }
38
+ export function formatConnectedAccounts(accounts) {
39
+ if (!accounts?.length)
40
+ return 'none yet';
41
+ return accounts.map(formatAccountLabel).join(', ');
42
+ }
43
+ function isInCurrentMonth(value) {
44
+ if (!value)
45
+ return false;
46
+ const date = new Date(value);
47
+ if (Number.isNaN(date.getTime()))
48
+ return false;
49
+ const now = new Date();
50
+ return date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth();
51
+ }
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ export declare const createConnectSessionTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{
7
+ platform: z.ZodEnum<["bluesky", "devto", "discord", "facebook", "facebook_instagram", "facebook_pages", "gmb", "google-business", "google_business", "google_business_profile", "hashnode", "instagram", "instagram-standalone", "instagram_direct", "linkedin", "linkedin-company", "linkedin-page", "linkedin_company", "linkedin_page", "linkedin_personal", "mastodon", "medium", "meta", "meta_business", "pinterest", "reddit", "skool", "slack", "telegram", "threads", "tiktok", "twitter", "whop", "wordpress", "x", "x_twitter", "youtube"]>;
8
+ workspace_id: z.ZodOptional<z.ZodString>;
9
+ auto_start: z.ZodOptional<z.ZodBoolean>;
10
+ debug: z.ZodOptional<z.ZodBoolean>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ platform: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "reddit" | "wordpress" | "mastodon" | "medium" | "devto" | "hashnode" | "discord" | "slack" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "x" | "meta" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "instagram_direct" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
13
+ workspace_id?: string | undefined;
14
+ auto_start?: boolean | undefined;
15
+ debug?: boolean | undefined;
16
+ }, {
17
+ platform: "instagram" | "instagram-standalone" | "facebook" | "tiktok" | "twitter" | "linkedin" | "youtube" | "pinterest" | "threads" | "google_business" | "telegram" | "bluesky" | "reddit" | "wordpress" | "mastodon" | "medium" | "devto" | "hashnode" | "discord" | "slack" | "skool" | "whop" | "gmb" | "google-business" | "google_business_profile" | "x" | "meta" | "linkedin_page" | "facebook_instagram" | "facebook_pages" | "instagram_direct" | "linkedin-company" | "linkedin-page" | "linkedin_company" | "linkedin_personal" | "meta_business" | "x_twitter";
18
+ workspace_id?: string | undefined;
19
+ auto_start?: boolean | undefined;
20
+ debug?: boolean | undefined;
21
+ }>;
22
+ execute(client: PosterlyClient, input: {
23
+ platform: string;
24
+ workspace_id?: string;
25
+ auto_start?: boolean;
26
+ debug?: boolean;
27
+ }): Promise<string>;
28
+ };
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ import { DASHBOARD_CONNECT_URL, formatConnectedAccounts, formatLocalDateTime } from '../lib/format.js';
3
+ import { CONNECT_INPUTS } from '../generated/platform-manifest.js';
4
+ export const createConnectSessionTool = {
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.',
7
+ inputSchema: z.object({
8
+ platform: z
9
+ .enum(CONNECT_INPUTS)
10
+ .describe('Connection target such as instagram, meta, linkedin_page, twitter, google_business, telegram, or bluesky.'),
11
+ workspace_id: z
12
+ .string()
13
+ .optional()
14
+ .describe('Workspace to connect the account into. Workspace-scoped API keys ignore this.'),
15
+ auto_start: z
16
+ .boolean()
17
+ .optional()
18
+ .describe('When true, the dashboard starts the provider flow after the user opens the URL. Defaults to true.'),
19
+ debug: z
20
+ .boolean()
21
+ .optional()
22
+ .describe('When true, include the raw connect session JSON for debugging. Keep false for normal user-facing agent flows.'),
23
+ }),
24
+ async execute(client, input) {
25
+ const { debug, ...payload } = input;
26
+ const result = await client.createConnectSession(payload);
27
+ return formatConnectSession(result.connect_session, Boolean(debug));
28
+ },
29
+ };
30
+ function formatConnectSession(session, includeRaw) {
31
+ const connected = session.connected_count ?? session.connected_accounts?.length ?? 0;
32
+ return [
33
+ `Posterly connect session created: ${session.id}`,
34
+ `Platform: ${session.platform}`,
35
+ `Status: ${session.status}`,
36
+ `Message: ${session.status_message}`,
37
+ `Secure connection URL: ${session.connect_url}`,
38
+ `Poll URL: ${session.poll_url}`,
39
+ `Connected accounts: ${connected}`,
40
+ `Accounts: ${formatConnectedAccounts(session.connected_accounts)}`,
41
+ `Expires: ${formatLocalDateTime(session.expires_at)}`,
42
+ `Dashboard connect page: ${DASHBOARD_CONNECT_URL}`,
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.',
45
+ includeRaw ? `\nRaw connect session:\n${JSON.stringify(session, null, 2)}` : '',
46
+ ].filter(Boolean).join('\n');
47
+ }