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.
- package/README.md +38 -18
- package/dist/index.js +52 -9
- package/dist/lib/api-client.d.ts +85 -0
- package/dist/lib/api-client.js +36 -1
- package/dist/lib/format.d.ts +11 -0
- package/dist/lib/format.js +51 -0
- package/dist/tools/create-connect-session.d.ts +28 -0
- package/dist/tools/create-connect-session.js +47 -0
- package/dist/tools/create-post.d.ts +39 -39
- package/dist/tools/create-post.js +8 -4
- package/dist/tools/create-posts-batch.d.ts +29 -29
- package/dist/tools/create-posts-batch.js +7 -1
- package/dist/tools/create-webhook.d.ts +2 -2
- package/dist/tools/delete-post.js +8 -1
- package/dist/tools/generate-image.d.ts +2 -2
- package/dist/tools/get-agent-signup-info.d.ts +8 -0
- package/dist/tools/get-agent-signup-info.js +37 -0
- package/dist/tools/get-connect-link.d.ts +4 -0
- package/dist/tools/get-connect-link.js +7 -2
- package/dist/tools/get-connect-session.d.ts +20 -0
- package/dist/tools/get-connect-session.js +54 -0
- package/dist/tools/get-post.js +3 -1
- package/dist/tools/get-signup-session.d.ts +20 -0
- package/dist/tools/get-signup-session.js +68 -0
- package/dist/tools/get-video-job.d.ts +2 -2
- package/dist/tools/list-posts.d.ts +2 -2
- package/dist/tools/list-posts.js +11 -4
- package/dist/tools/start-signup.d.ts +44 -0
- package/dist/tools/start-signup.js +86 -0
- package/dist/tools/update-post.d.ts +20 -20
- package/dist/tools/update-webhook.d.ts +2 -2
- package/package.json +1 -1
- package/src/index.ts +77 -9
- package/src/lib/api-client.ts +141 -1
- package/src/lib/format.ts +56 -0
- package/src/tools/create-connect-session.ts +55 -0
- package/src/tools/create-post.ts +11 -4
- package/src/tools/create-posts-batch.ts +11 -1
- package/src/tools/delete-post.ts +8 -1
- package/src/tools/get-agent-signup-info.ts +42 -0
- package/src/tools/get-connect-link.ts +8 -3
- package/src/tools/get-connect-session.ts +63 -0
- package/src/tools/get-post.ts +3 -1
- package/src/tools/get-signup-session.ts +77 -0
- package/src/tools/list-posts.ts +11 -5
- 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
|
-
-
|
|
20
|
-
-
|
|
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
|
-
|
|
26
|
+
Recommended: use it via `npx` in your MCP config so your client runs the current server without a global install.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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.
|
|
50
|
-
2.
|
|
51
|
-
3.
|
|
52
|
-
4.
|
|
53
|
-
5.
|
|
54
|
-
6.
|
|
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.
|
|
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.
|
|
61
|
+
version: '0.19.7',
|
|
57
62
|
});
|
|
58
|
-
|
|
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);
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -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
|
}>;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -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('
|
|
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
|
+
}
|