posterly-mcp-server 0.19.4 → 0.19.6

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 (33) hide show
  1. package/README.md +38 -18
  2. package/dist/index.js +52 -9
  3. package/dist/lib/api-client.d.ts +87 -0
  4. package/dist/lib/api-client.js +36 -1
  5. package/dist/tools/create-connect-session.d.ts +24 -0
  6. package/dist/tools/create-connect-session.js +38 -0
  7. package/dist/tools/create-webhook.d.ts +2 -2
  8. package/dist/tools/generate-image.d.ts +2 -2
  9. package/dist/tools/get-account-analytics.d.ts +7 -0
  10. package/dist/tools/get-account-analytics.js +121 -56
  11. package/dist/tools/get-agent-signup-info.d.ts +8 -0
  12. package/dist/tools/get-agent-signup-info.js +32 -0
  13. package/dist/tools/get-connect-session.d.ts +16 -0
  14. package/dist/tools/get-connect-session.js +26 -0
  15. package/dist/tools/get-post-analytics.d.ts +7 -0
  16. package/dist/tools/get-post-analytics.js +133 -54
  17. package/dist/tools/get-signup-session.d.ts +16 -0
  18. package/dist/tools/get-signup-session.js +65 -0
  19. package/dist/tools/get-video-job.d.ts +2 -2
  20. package/dist/tools/list-posts.d.ts +2 -2
  21. package/dist/tools/start-signup.d.ts +40 -0
  22. package/dist/tools/start-signup.js +83 -0
  23. package/dist/tools/update-webhook.d.ts +2 -2
  24. package/package.json +1 -1
  25. package/src/index.ts +77 -9
  26. package/src/lib/api-client.ts +142 -2
  27. package/src/tools/create-connect-session.ts +46 -0
  28. package/src/tools/get-account-analytics.ts +127 -64
  29. package/src/tools/get-agent-signup-info.ts +37 -0
  30. package/src/tools/get-connect-session.ts +34 -0
  31. package/src/tools/get-post-analytics.ts +139 -57
  32. package/src/tools/get-signup-session.ts +74 -0
  33. package/src/tools/start-signup.ts +104 -0
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient, PublicSignupTier } from '../lib/api-client.js';
3
+ export declare const startSignupTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{
7
+ email: z.ZodString;
8
+ name: z.ZodOptional<z.ZodString>;
9
+ tier: z.ZodDefault<z.ZodEnum<["starter", "pro", "power_user", "agency"]>>;
10
+ interval: z.ZodDefault<z.ZodEnum<["month"]>>;
11
+ api_addon: z.ZodDefault<z.ZodBoolean>;
12
+ return_path: z.ZodDefault<z.ZodString>;
13
+ source: z.ZodDefault<z.ZodString>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ email: string;
16
+ tier: "starter" | "pro" | "power_user" | "agency";
17
+ interval: "month";
18
+ api_addon: boolean;
19
+ return_path: string;
20
+ source: string;
21
+ name?: string | undefined;
22
+ }, {
23
+ email: string;
24
+ name?: string | undefined;
25
+ tier?: "starter" | "pro" | "power_user" | "agency" | undefined;
26
+ interval?: "month" | undefined;
27
+ api_addon?: boolean | undefined;
28
+ return_path?: string | undefined;
29
+ source?: string | undefined;
30
+ }>;
31
+ execute(client: PosterlyClient, input: {
32
+ email: string;
33
+ name?: string;
34
+ tier?: PublicSignupTier;
35
+ interval?: "month";
36
+ api_addon?: boolean;
37
+ return_path?: string;
38
+ source?: string;
39
+ }): Promise<string>;
40
+ };
@@ -0,0 +1,83 @@
1
+ import { z } from 'zod';
2
+ const DEFAULT_RETURN_PATH = '/dashboard/api?api_signup=success';
3
+ export const startSignupTool = {
4
+ name: 'start_signup',
5
+ description: 'Start the public Posterly paid signup flow for a user before Posterly API access exists. Returns a Posterly checkout handoff URL and signup poll URL. The user must handle payment and password setup in the browser.',
6
+ inputSchema: z.object({
7
+ email: z
8
+ .string()
9
+ .email()
10
+ .describe('User email address to sign up. Confirm this with the user before calling.'),
11
+ name: z
12
+ .string()
13
+ .optional()
14
+ .describe('Optional user or workspace name to pass into Posterly signup.'),
15
+ tier: z
16
+ .enum(['starter', 'pro', 'power_user', 'agency'])
17
+ .default('starter')
18
+ .describe('Posterly plan to start. Use starter unless the user explicitly chooses another plan.'),
19
+ interval: z
20
+ .enum(['month'])
21
+ .default('month')
22
+ .describe('Billing interval. Public agent signup currently supports monthly billing.'),
23
+ api_addon: z
24
+ .boolean()
25
+ .default(true)
26
+ .describe('Always true for AI agent setup because MCP/API access requires the API add-on.'),
27
+ return_path: z
28
+ .string()
29
+ .default(DEFAULT_RETURN_PATH)
30
+ .describe('Where Posterly should continue after checkout and password setup.'),
31
+ source: z
32
+ .string()
33
+ .default('posterly_mcp_pre_auth_signup')
34
+ .describe('Source label for analytics and support diagnostics.'),
35
+ }),
36
+ async execute(client, input) {
37
+ const response = await client.startSignup({
38
+ email: input.email,
39
+ name: input.name,
40
+ tier: input.tier ?? 'starter',
41
+ interval: input.interval ?? 'month',
42
+ api_addon: input.api_addon ?? true,
43
+ return_path: input.return_path ?? DEFAULT_RETURN_PATH,
44
+ source: input.source ?? 'posterly_mcp_pre_auth_signup',
45
+ });
46
+ return formatSignupStart(response);
47
+ },
48
+ };
49
+ function valueFromLinks(response, key) {
50
+ const links = response.signup_session?.links ?? response.links;
51
+ const value = links?.[key];
52
+ return typeof value === 'string' ? value : null;
53
+ }
54
+ function formatSignupStart(response) {
55
+ const session = response.signup_session;
56
+ const sessionId = session?.id || response.checkout_session_id;
57
+ const checkoutUrl = response.checkout_url || valueFromLinks(response, 'checkout_url');
58
+ const checkoutRedirectUrl = response.checkout_redirect_url || session?.checkout_redirect_url || valueFromLinks(response, 'checkout_redirect_url');
59
+ const pollUrl = response.poll_url || valueFromLinks(response, 'poll_url');
60
+ const completeUrl = response.complete_url || valueFromLinks(response, 'complete_url');
61
+ const status = session?.status || response.status || 'checkout_pending';
62
+ const message = session?.status_message || response.status_message;
63
+ const lines = [
64
+ 'Posterly signup started.',
65
+ sessionId ? `Signup session: ${sessionId}` : '',
66
+ `Status: ${status}`,
67
+ message ? `Message: ${message}` : '',
68
+ checkoutRedirectUrl ? `Checkout handoff URL: ${checkoutRedirectUrl}` : '',
69
+ checkoutUrl ? `Checkout URL: ${checkoutUrl}` : '',
70
+ pollUrl ? `Poll URL: ${pollUrl}` : '',
71
+ completeUrl ? `Completion URL: ${completeUrl}` : '',
72
+ '',
73
+ 'Next steps for the AI agent:',
74
+ '1. Send the Checkout handoff URL to the user so they can pay securely in their browser. Use the raw Stripe checkout URL only if you preserve the full URL including any fragment.',
75
+ '2. Call get_signup_session with the signup session ID until the status changes.',
76
+ '3. Tell the user when Posterly sends the password setup email.',
77
+ '4. If status becomes agent_access_required, ask the user to install or paste the Posterly MCP/API setup instructions into this trusted AI chat.',
78
+ '5. Do not ask for card details, the Posterly password, social passwords, or OAuth codes.',
79
+ '',
80
+ `Raw signup response:\n${JSON.stringify(response, null, 2)}`,
81
+ ];
82
+ return lines.filter(Boolean).join('\n');
83
+ }
@@ -16,16 +16,16 @@ export declare const updateWebhookTool: {
16
16
  webhook_id: string;
17
17
  workspace_id?: string | null | undefined;
18
18
  url?: string | undefined;
19
- events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced")[] | undefined;
20
19
  description?: string | null | undefined;
20
+ events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced")[] | undefined;
21
21
  is_active?: boolean | undefined;
22
22
  }, {
23
23
  confirm: true;
24
24
  webhook_id: string;
25
25
  workspace_id?: string | null | undefined;
26
26
  url?: string | undefined;
27
- events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced")[] | undefined;
28
27
  description?: string | null | undefined;
28
+ events?: ("webhook.test" | "post.created" | "post.updated" | "post.deleted" | "post.publishing" | "post.published" | "post.failed" | "account.disconnected" | "analytics.synced")[] | undefined;
29
29
  is_active?: boolean | undefined;
30
30
  }>;
31
31
  execute(client: PosterlyClient, input: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posterly-mcp-server",
3
- "version": "0.19.4",
3
+ "version": "0.19.6",
4
4
  "description": "MCP server for posterly — schedule social media posts from Claude Desktop",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.poster.ly/mcp",
package/src/index.ts CHANGED
@@ -3,6 +3,9 @@
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
5
  import { PosterlyClient } from './lib/api-client.js';
6
+ import { getAgentSignupInfoTool } from './tools/get-agent-signup-info.js';
7
+ import { getSignupSessionTool } from './tools/get-signup-session.js';
8
+ import { startSignupTool } from './tools/start-signup.js';
6
9
  import { listAccountsTool } from './tools/list-accounts.js';
7
10
  import { disconnectAccountTool } from './tools/disconnect-account.js';
8
11
  import { listBrandsTool } from './tools/list-brands.js';
@@ -48,6 +51,8 @@ import { getXPostingQuotaTool } from './tools/get-x-posting-quota.js';
48
51
  import { createSignedUploadTool } from './tools/create-signed-upload.js';
49
52
  import { uploadMediaFromUrlTool } from './tools/upload-media-from-url.js';
50
53
  import { getConnectLinkTool } from './tools/get-connect-link.js';
54
+ import { createConnectSessionTool } from './tools/create-connect-session.js';
55
+ import { getConnectSessionTool } from './tools/get-connect-session.js';
51
56
  import { listOAuthClientsTool } from './tools/list-oauth-clients.js';
52
57
  import { createOAuthClientTool } from './tools/create-oauth-client.js';
53
58
  import { updateOAuthClientTool } from './tools/update-oauth-client.js';
@@ -55,19 +60,54 @@ import { deleteOAuthClientTool } from './tools/delete-oauth-client.js';
55
60
 
56
61
  const server = new McpServer({
57
62
  name: 'posterly',
58
- version: '0.19.0',
63
+ version: '0.19.6',
59
64
  });
60
65
 
61
- let client: PosterlyClient;
62
-
63
- try {
64
- client = new PosterlyClient();
65
- } catch (err: any) {
66
- console.error(err.message);
67
- process.exit(1);
68
- }
66
+ const client = new PosterlyClient();
69
67
 
70
68
  // Register tools
69
+ server.tool(
70
+ getAgentSignupInfoTool.name,
71
+ getAgentSignupInfoTool.description,
72
+ getAgentSignupInfoTool.inputSchema.shape,
73
+ async () => {
74
+ try {
75
+ const text = await getAgentSignupInfoTool.execute(client);
76
+ return { content: [{ type: 'text' as const, text }] };
77
+ } catch (err: any) {
78
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
79
+ }
80
+ }
81
+ );
82
+
83
+ server.tool(
84
+ startSignupTool.name,
85
+ startSignupTool.description,
86
+ startSignupTool.inputSchema.shape,
87
+ async (input) => {
88
+ try {
89
+ const text = await startSignupTool.execute(client, input as any);
90
+ return { content: [{ type: 'text' as const, text }] };
91
+ } catch (err: any) {
92
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
93
+ }
94
+ }
95
+ );
96
+
97
+ server.tool(
98
+ getSignupSessionTool.name,
99
+ getSignupSessionTool.description,
100
+ getSignupSessionTool.inputSchema.shape,
101
+ async (input) => {
102
+ try {
103
+ const text = await getSignupSessionTool.execute(client, input as any);
104
+ return { content: [{ type: 'text' as const, text }] };
105
+ } catch (err: any) {
106
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
107
+ }
108
+ }
109
+ );
110
+
71
111
  server.tool(
72
112
  whoamiTool.name,
73
113
  whoamiTool.description,
@@ -124,6 +164,34 @@ server.tool(
124
164
  }
125
165
  );
126
166
 
167
+ server.tool(
168
+ createConnectSessionTool.name,
169
+ createConnectSessionTool.description,
170
+ createConnectSessionTool.inputSchema.shape,
171
+ async (input) => {
172
+ try {
173
+ const text = await createConnectSessionTool.execute(client, input as any);
174
+ return { content: [{ type: 'text' as const, text }] };
175
+ } catch (err: any) {
176
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
177
+ }
178
+ }
179
+ );
180
+
181
+ server.tool(
182
+ getConnectSessionTool.name,
183
+ getConnectSessionTool.description,
184
+ getConnectSessionTool.inputSchema.shape,
185
+ async (input) => {
186
+ try {
187
+ const text = await getConnectSessionTool.execute(client, input as any);
188
+ return { content: [{ type: 'text' as const, text }] };
189
+ } catch (err: any) {
190
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
191
+ }
192
+ }
193
+ );
194
+
127
195
  server.tool(
128
196
  listOAuthClientsTool.name,
129
197
  listOAuthClientsTool.description,
@@ -152,7 +152,7 @@ export interface AccountAnalyticsSummary {
152
152
  total_watch_minutes?: number | null;
153
153
  total_likes?: number | null;
154
154
  platform_metrics?: Record<string, number | null | undefined>;
155
- display_metrics?: Array<{ label: string; value: number | null | undefined }>;
155
+ display_metrics?: Array<{ key?: string; label: string; value: number | null | undefined; unit?: string }>;
156
156
  }
157
157
 
158
158
  export interface AccountAnalyticsSnapshot {
@@ -328,6 +328,35 @@ export interface ConnectOption {
328
328
  connected_accounts?: Account[];
329
329
  }
330
330
 
331
+ export interface ConnectSession {
332
+ id: string;
333
+ platform: string;
334
+ method: 'dashboard_oauth' | 'manual_credentials';
335
+ status:
336
+ | 'created'
337
+ | 'opened'
338
+ | 'awaiting_provider'
339
+ | 'awaiting_credentials'
340
+ | 'connected'
341
+ | 'failed'
342
+ | 'cancelled'
343
+ | 'expired';
344
+ status_message: string;
345
+ connect_url: string;
346
+ poll_url: string;
347
+ workspace_id?: string | null;
348
+ connected_account_ids: string[];
349
+ connected_accounts: Account[];
350
+ connected_count: number;
351
+ error_code?: string | null;
352
+ error_message?: string | null;
353
+ created_at: string;
354
+ opened_at?: string | null;
355
+ provider_redirected_at?: string | null;
356
+ completed_at?: string | null;
357
+ expires_at: string;
358
+ }
359
+
331
360
  export interface OAuthDeveloperClient {
332
361
  id: string;
333
362
  client_id: string;
@@ -341,6 +370,60 @@ export interface OAuthDeveloperClient {
341
370
  updated_at?: string;
342
371
  }
343
372
 
373
+ export type PublicSignupTier = 'starter' | 'pro' | 'power_user' | 'agency';
374
+
375
+ export interface PublicSignupPayload {
376
+ email: string;
377
+ name?: string;
378
+ tier?: PublicSignupTier;
379
+ interval?: 'month';
380
+ api_addon?: boolean;
381
+ return_path?: string;
382
+ source?: string;
383
+ }
384
+
385
+ export interface PublicSignupSession {
386
+ id: string;
387
+ status: string;
388
+ status_message?: string;
389
+ next_action?: {
390
+ actor?: string;
391
+ type?: string;
392
+ label?: string;
393
+ description?: string;
394
+ };
395
+ agent_next_steps?: string[];
396
+ checkout_redirect_url?: string | null;
397
+ checkout?: Record<string, unknown>;
398
+ account?: Record<string, unknown>;
399
+ billing?: Record<string, unknown>;
400
+ links?: Record<string, unknown>;
401
+ created_at?: string;
402
+ updated_at?: string;
403
+ expires_at?: string;
404
+ [key: string]: unknown;
405
+ }
406
+
407
+ export interface PublicSignupResponse {
408
+ signup_session?: PublicSignupSession;
409
+ checkout_session_id?: string;
410
+ checkout_url?: string | null;
411
+ checkout_redirect_url?: string | null;
412
+ poll_url?: string | null;
413
+ complete_url?: string | null;
414
+ dashboard_url?: string | null;
415
+ status?: string;
416
+ status_message?: string;
417
+ links?: Record<string, unknown>;
418
+ account?: Record<string, unknown>;
419
+ billing?: Record<string, unknown>;
420
+ [key: string]: unknown;
421
+ }
422
+
423
+ export interface PublicSignupSessionResponse {
424
+ signup_session: PublicSignupSession;
425
+ }
426
+
344
427
  export interface VideoJob {
345
428
  id: string;
346
429
  status: string;
@@ -387,9 +470,17 @@ export class PosterlyClient {
387
470
  constructor(apiKey?: string, baseUrl?: string) {
388
471
  this.apiKey = apiKey || process.env.POSTERLY_API_KEY || '';
389
472
  this.baseUrl = (baseUrl || process.env.POSTERLY_URL || 'https://www.poster.ly').replace(/\/$/, '');
473
+ }
390
474
 
475
+ hasApiKey(): boolean {
476
+ return Boolean(this.apiKey);
477
+ }
478
+
479
+ private requireApiKey(): void {
391
480
  if (!this.apiKey) {
392
- throw new Error('POSTERLY_API_KEY is required. Set it as an environment variable.');
481
+ throw new Error(
482
+ 'Posterly access is not installed yet. Use start_signup to begin paid setup, or set POSTERLY_API_KEY after signup.'
483
+ );
393
484
  }
394
485
  }
395
486
 
@@ -398,6 +489,8 @@ export class PosterlyClient {
398
489
  path: string,
399
490
  body?: unknown,
400
491
  ): Promise<T> {
492
+ this.requireApiKey();
493
+
401
494
  const url = `${this.baseUrl}/api/v1${path}`;
402
495
  const headers: Record<string, string> = {
403
496
  Authorization: `Bearer ${this.apiKey}`,
@@ -422,6 +515,38 @@ export class PosterlyClient {
422
515
  return res.json() as Promise<T>;
423
516
  }
424
517
 
518
+ private async publicRequest<T>(
519
+ method: string,
520
+ path: string,
521
+ body?: unknown,
522
+ ): Promise<T> {
523
+ const url = `${this.baseUrl}${path}`;
524
+ const headers: Record<string, string> = {};
525
+ const init: RequestInit = { method, headers };
526
+
527
+ if (body) {
528
+ headers['Content-Type'] = 'application/json';
529
+ init.body = JSON.stringify(body);
530
+ }
531
+
532
+ const res = await fetch(url, init);
533
+
534
+ if (!res.ok) {
535
+ const err = await res.json().catch(() => ({ error: res.statusText }));
536
+ throw new Error(err.error || `API error: ${res.status}`);
537
+ }
538
+
539
+ return res.json() as Promise<T>;
540
+ }
541
+
542
+ async startSignup(data: PublicSignupPayload): Promise<PublicSignupResponse> {
543
+ return this.publicRequest('POST', '/api/v1/signup', data);
544
+ }
545
+
546
+ async getSignupSession(sessionId: string): Promise<PublicSignupSessionResponse> {
547
+ return this.publicRequest('GET', `/api/v1/signup/sessions/${encodeURIComponent(sessionId)}`);
548
+ }
549
+
425
550
  async whoami(): Promise<Whoami> {
426
551
  return this.request<Whoami>('GET', '/whoami');
427
552
  }
@@ -453,6 +578,19 @@ export class PosterlyClient {
453
578
  return this.request('GET', `${path}${qs ? `?${qs}` : ''}`);
454
579
  }
455
580
 
581
+ async createConnectSession(data: {
582
+ platform: string;
583
+ workspace_id?: string;
584
+ auto_start?: boolean;
585
+ }): Promise<{ connect_session: ConnectSession; connect?: ConnectOption }> {
586
+ const { platform, ...body } = data;
587
+ return this.request('POST', `/connect/${encodeURIComponent(platform)}/sessions`, body);
588
+ }
589
+
590
+ async getConnectSession(sessionId: string): Promise<{ connect_session: ConnectSession }> {
591
+ return this.request('GET', `/connect/sessions/${encodeURIComponent(sessionId)}`);
592
+ }
593
+
456
594
  async listOAuthClients(): Promise<{ clients: OAuthDeveloperClient[] }> {
457
595
  return this.request('GET', '/oauth/clients');
458
596
  }
@@ -879,6 +1017,8 @@ export class PosterlyClient {
879
1017
  filename: string;
880
1018
  contentType?: string;
881
1019
  }): Promise<{ url: string; path: string }> {
1020
+ this.requireApiKey();
1021
+
882
1022
  let fileBuffer: Buffer;
883
1023
 
884
1024
  if (input.filePath) {
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ import type { ConnectSession, PosterlyClient } from '../lib/api-client.js';
3
+ import { CONNECT_INPUTS } from '../generated/platform-manifest.js';
4
+
5
+ export const createConnectSessionTool = {
6
+ name: 'create_connect_session',
7
+ description:
8
+ 'Create a short-lived Posterly dashboard handoff session for connecting a social account. Open connect_session.connect_url for the user, then poll get_connect_session to narrate progress.',
9
+ inputSchema: z.object({
10
+ platform: z
11
+ .enum(CONNECT_INPUTS)
12
+ .describe('Connection target such as instagram, meta, linkedin_page, twitter, google_business, telegram, or bluesky.'),
13
+ workspace_id: z
14
+ .string()
15
+ .optional()
16
+ .describe('Workspace to connect the account into. Workspace-scoped API keys ignore this.'),
17
+ auto_start: z
18
+ .boolean()
19
+ .optional()
20
+ .describe('When true, the dashboard starts the provider flow after the user opens the URL. Defaults to true.'),
21
+ }),
22
+
23
+ async execute(
24
+ client: PosterlyClient,
25
+ input: { platform: string; workspace_id?: string; auto_start?: boolean },
26
+ ) {
27
+ const result = await client.createConnectSession(input);
28
+ return formatConnectSession(result.connect_session, true);
29
+ },
30
+ };
31
+
32
+ function formatConnectSession(session: ConnectSession, includeRaw: boolean): string {
33
+ return [
34
+ `Posterly connect session: ${session.id}`,
35
+ `Platform: ${session.platform}`,
36
+ `Status: ${session.status}`,
37
+ `Message: ${session.status_message}`,
38
+ `Connection URL: ${session.connect_url}`,
39
+ `Poll URL: ${session.poll_url}`,
40
+ `Connected accounts: ${session.connected_count ?? session.connected_accounts?.length ?? 0}`,
41
+ `Expires: ${session.expires_at}`,
42
+ '',
43
+ 'Next step: Open the connection URL for the user, then call get_connect_session with this session ID until the status is terminal.',
44
+ includeRaw ? `\nRaw connect session:\n${JSON.stringify(session, null, 2)}` : '',
45
+ ].filter(Boolean).join('\n');
46
+ }