posterly-mcp-server 0.45.1 → 0.45.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -122,7 +122,7 @@ Add the same server definition to your Cursor MCP settings:
122
122
 
123
123
  ## Available tools
124
124
 
125
- `posterly-mcp-server@0.45.1` exposes 62 tools covering 94 actions. The hosted HTTP endpoint exposes the same catalog minus the three signup tools: 59 tools covering 91 actions.
125
+ `posterly-mcp-server@0.45.3` exposes 62 tools covering 94 actions. The hosted HTTP endpoint exposes the same catalog minus the three signup tools: 59 tools covering 91 actions.
126
126
 
127
127
  Many tools pick what to do with one parameter: `view` (reads), `action` (writes), or `type`. A few pick their mode from which parameters you pass (for example `update_post` with `status` changes the status instead of editing). Every tool description names all of its views and actions.
128
128
 
package/RELEASING.md CHANGED
@@ -101,3 +101,15 @@ up on their next client restart; there is nothing for them to do.
101
101
  Do not `npm unpublish` and do not republish the same number — npm versions are
102
102
  immutable and unpublishing breaks anyone who already resolved it. Cut the next
103
103
  patch version and publish that.
104
+
105
+
106
+ ## Publishing from GitHub (trusted publishing)
107
+
108
+ npm now requires 2FA or a trusted publisher to publish. The repo has a manual
109
+ workflow, `.github/workflows/publish-mcp.yml` ("Publish MCP"), that publishes
110
+ `mcp-server/` with GitHub OIDC, so no npm token or one-time code is needed.
111
+ One-time setup on npmjs.com: package `posterly-mcp-server` > Settings >
112
+ Trusted Publisher > GitHub Actions, owner `awpthorp`, repository `posterly`,
113
+ workflow `publish-mcp.yml`. After an MCP change merges and deploys, run it from
114
+ the Actions tab (or `gh workflow run publish-mcp.yml`), then
115
+ `npm run check:mcp-published`. It skips if the version is already on npm.
@@ -1 +1 @@
1
- export declare const POSTERLY_MCP_VERSION = "0.45.1";
1
+ export declare const POSTERLY_MCP_VERSION = "0.45.3";
@@ -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.45.1';
8
+ export const POSTERLY_MCP_VERSION = '0.45.3';
@@ -1,5 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import type { PosterlyClient } from '../lib/api-client.js';
3
+ export declare function formatGoogleBusinessAuditText(result: {
4
+ audit?: any;
5
+ fetchedAt?: string;
6
+ }): string;
3
7
  export declare const auditGoogleBusinessProfileTool: {
4
8
  name: string;
5
9
  description: string;
@@ -1,7 +1,37 @@
1
1
  import { z } from 'zod';
2
+ const AUDIT_SECTION_ORDER = ['basicInfo', 'hours', 'categories', 'description', 'media', 'attributes', 'reviews'];
3
+ export function formatGoogleBusinessAuditText(result) {
4
+ const audit = result.audit || {};
5
+ const recommendations = Array.isArray(audit.recommendations) ? audit.recommendations : [];
6
+ const sections = audit.sections && typeof audit.sections === 'object' ? audit.sections : {};
7
+ const lines = [
8
+ `Google Business audit: ${audit.businessName || 'Unknown business'}`,
9
+ `Score: ${audit.overallScore ?? 'n/a'} (${audit.overallGrade || 'n/a'})`,
10
+ `Fetched: ${result.fetchedAt || 'unknown'}`,
11
+ ];
12
+ const keys = [
13
+ ...AUDIT_SECTION_ORDER.filter((key) => sections[key]),
14
+ ...Object.keys(sections).filter((key) => !AUDIT_SECTION_ORDER.includes(key)),
15
+ ];
16
+ if (keys.length > 0) {
17
+ lines.push('Sections:');
18
+ for (const key of keys) {
19
+ const section = sections[key] || {};
20
+ const label = section.label || key;
21
+ lines.push(`- ${label}: ${section.score ?? 'n/a'}/${section.maxScore ?? 'n/a'}`);
22
+ }
23
+ }
24
+ if (recommendations.length > 0) {
25
+ lines.push('Top recommendations:');
26
+ for (const rec of recommendations.slice(0, 5)) {
27
+ lines.push(`- ${rec.priority || 'medium'}: ${rec.description || rec.title || 'Recommendation'}`);
28
+ }
29
+ }
30
+ return lines.join('\n');
31
+ }
2
32
  export const auditGoogleBusinessProfileTool = {
3
33
  name: 'audit_google_business_profile',
4
- description: 'Run a live local-profile audit for a connected Google Business Profile location, including completeness, categories, media, attributes, reviews, and recommendations.',
34
+ description: 'Run a live local-profile audit for a connected Google Business Profile location. Returns the 0-100 completeness score, letter grade, a score for each section (profile basics, hours, categories, description, photos, attributes, review health), and recommendations.',
5
35
  inputSchema: z.object({
6
36
  workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
7
37
  account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts.'),
@@ -9,19 +39,6 @@ export const auditGoogleBusinessProfileTool = {
9
39
  }),
10
40
  async execute(client, input) {
11
41
  const result = await client.auditGoogleBusinessProfile(input);
12
- const audit = result.audit || {};
13
- const recommendations = Array.isArray(audit.recommendations) ? audit.recommendations : [];
14
- const lines = [
15
- `Google Business audit: ${audit.businessName || 'Unknown business'}`,
16
- `Score: ${audit.overallScore ?? 'n/a'} (${audit.overallGrade || 'n/a'})`,
17
- `Fetched: ${result.fetchedAt || 'unknown'}`,
18
- ];
19
- if (recommendations.length > 0) {
20
- lines.push('Top recommendations:');
21
- for (const rec of recommendations.slice(0, 5)) {
22
- lines.push(`- ${rec.priority || 'medium'}: ${rec.description || rec.title || 'Recommendation'}`);
23
- }
24
- }
25
- return lines.join('\n');
42
+ return formatGoogleBusinessAuditText(result);
26
43
  },
27
44
  };
@@ -310,6 +310,15 @@ export const createPostTool = {
310
310
  `• Scheduled: ${when}`,
311
311
  `• View in posterly: ${dashboardUrl}`,
312
312
  ];
313
+ if (p.reel_cover_url) {
314
+ lines.push(`• Reel cover: ${p.reel_cover_url}`);
315
+ }
316
+ if (p.thumbnail_url) {
317
+ lines.push(`• Thumbnail: ${p.thumbnail_url}`);
318
+ }
319
+ if (p.youtube_thumbnail_url) {
320
+ lines.push(`• YouTube thumbnail: ${p.youtube_thumbnail_url}`);
321
+ }
313
322
  if (thread_posts) {
314
323
  lines.push(`• Thread: ${thread_posts.length} posts`);
315
324
  }
@@ -191,6 +191,9 @@ async function editPost(client, input) {
191
191
  ['Status', statusLabel(p.status)],
192
192
  ['Scheduled', formatDateTime(p.scheduled_at)],
193
193
  ['Type', p.post_type || 'unknown'],
194
+ ...(p.reel_cover_url ? [['Reel cover', p.reel_cover_url]] : []),
195
+ ...(p.thumbnail_url ? [['Thumbnail', p.thumbnail_url]] : []),
196
+ ...(p.youtube_thumbnail_url ? [['YouTube thumbnail', p.youtube_thumbnail_url]] : []),
194
197
  ]),
195
198
  mdSection('Caption', p.content ? mdQuote(p.content) : '_(empty)_'),
196
199
  ].join('\n\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "posterly-mcp-server",
3
- "version": "0.45.1",
3
+ "version": "0.45.3",
4
4
  "mcpName": "io.github.awpthorp/posterly",
5
5
  "description": "MCP server for posterly: schedule and publish social media posts across 18 platforms from any MCP client (Claude, ChatGPT, Cursor, Windsurf, Cline, and more)",
6
6
  "license": "MIT",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.awpthorp/posterly",
4
4
  "title": "posterly",
5
5
  "description": "Validate, schedule, publish, and analyze social content across 18 platforms with posterly.",
6
- "version": "0.45.1",
6
+ "version": "0.45.3",
7
7
  "websiteUrl": "https://www.poster.ly/mcp",
8
8
  "repository": {
9
9
  "url": "https://github.com/awpthorp/posterly",
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "registryType": "npm",
16
16
  "identifier": "posterly-mcp-server",
17
- "version": "0.45.1",
17
+ "version": "0.45.3",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },
@@ -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.45.1';
8
+ export const POSTERLY_MCP_VERSION = '0.45.3';
@@ -1,10 +1,42 @@
1
1
  import { z } from 'zod';
2
2
  import type { PosterlyClient } from '../lib/api-client.js';
3
3
 
4
+ const AUDIT_SECTION_ORDER = ['basicInfo', 'hours', 'categories', 'description', 'media', 'attributes', 'reviews'];
5
+
6
+ export function formatGoogleBusinessAuditText(result: { audit?: any; fetchedAt?: string }): string {
7
+ const audit = result.audit || {};
8
+ const recommendations = Array.isArray(audit.recommendations) ? audit.recommendations : [];
9
+ const sections = audit.sections && typeof audit.sections === 'object' ? audit.sections : {};
10
+ const lines = [
11
+ `Google Business audit: ${audit.businessName || 'Unknown business'}`,
12
+ `Score: ${audit.overallScore ?? 'n/a'} (${audit.overallGrade || 'n/a'})`,
13
+ `Fetched: ${result.fetchedAt || 'unknown'}`,
14
+ ];
15
+ const keys = [
16
+ ...AUDIT_SECTION_ORDER.filter((key) => sections[key]),
17
+ ...Object.keys(sections).filter((key) => !AUDIT_SECTION_ORDER.includes(key)),
18
+ ];
19
+ if (keys.length > 0) {
20
+ lines.push('Sections:');
21
+ for (const key of keys) {
22
+ const section = sections[key] || {};
23
+ const label = section.label || key;
24
+ lines.push(`- ${label}: ${section.score ?? 'n/a'}/${section.maxScore ?? 'n/a'}`);
25
+ }
26
+ }
27
+ if (recommendations.length > 0) {
28
+ lines.push('Top recommendations:');
29
+ for (const rec of recommendations.slice(0, 5)) {
30
+ lines.push(`- ${rec.priority || 'medium'}: ${rec.description || rec.title || 'Recommendation'}`);
31
+ }
32
+ }
33
+ return lines.join('\n');
34
+ }
35
+
4
36
  export const auditGoogleBusinessProfileTool = {
5
37
  name: 'audit_google_business_profile',
6
38
  description:
7
- 'Run a live local-profile audit for a connected Google Business Profile location, including completeness, categories, media, attributes, reviews, and recommendations.',
39
+ 'Run a live local-profile audit for a connected Google Business Profile location. Returns the 0-100 completeness score, letter grade, a score for each section (profile basics, hours, categories, description, photos, attributes, review health), and recommendations.',
8
40
  inputSchema: z.object({
9
41
  workspace_id: z.string().optional().describe('Filter to a workspace ID from whoami.'),
10
42
  account_id: z.number().int().positive().optional().describe('Google Business account ID from list_accounts.'),
@@ -20,19 +52,6 @@ export const auditGoogleBusinessProfileTool = {
20
52
  },
21
53
  ) {
22
54
  const result = await client.auditGoogleBusinessProfile(input);
23
- const audit = result.audit || {};
24
- const recommendations = Array.isArray(audit.recommendations) ? audit.recommendations : [];
25
- const lines = [
26
- `Google Business audit: ${audit.businessName || 'Unknown business'}`,
27
- `Score: ${audit.overallScore ?? 'n/a'} (${audit.overallGrade || 'n/a'})`,
28
- `Fetched: ${result.fetchedAt || 'unknown'}`,
29
- ];
30
- if (recommendations.length > 0) {
31
- lines.push('Top recommendations:');
32
- for (const rec of recommendations.slice(0, 5)) {
33
- lines.push(`- ${rec.priority || 'medium'}: ${rec.description || rec.title || 'Recommendation'}`);
34
- }
35
- }
36
- return lines.join('\n');
55
+ return formatGoogleBusinessAuditText(result);
37
56
  },
38
57
  };
@@ -345,6 +345,15 @@ export const createPostTool = {
345
345
  `• Scheduled: ${when}`,
346
346
  `• View in posterly: ${dashboardUrl}`,
347
347
  ];
348
+ if (p.reel_cover_url) {
349
+ lines.push(`• Reel cover: ${p.reel_cover_url}`);
350
+ }
351
+ if (p.thumbnail_url) {
352
+ lines.push(`• Thumbnail: ${p.thumbnail_url}`);
353
+ }
354
+ if (p.youtube_thumbnail_url) {
355
+ lines.push(`• YouTube thumbnail: ${p.youtube_thumbnail_url}`);
356
+ }
348
357
  if (thread_posts) {
349
358
  lines.push(`• Thread: ${thread_posts.length} posts`);
350
359
  }
@@ -217,6 +217,9 @@ async function editPost(client: PosterlyClient, input: EditInput): Promise<strin
217
217
  ['Status', statusLabel(p.status)],
218
218
  ['Scheduled', formatDateTime(p.scheduled_at)],
219
219
  ['Type', p.post_type || 'unknown'],
220
+ ...(p.reel_cover_url ? [['Reel cover', p.reel_cover_url] as [string, string]] : []),
221
+ ...(p.thumbnail_url ? [['Thumbnail', p.thumbnail_url] as [string, string]] : []),
222
+ ...(p.youtube_thumbnail_url ? [['YouTube thumbnail', p.youtube_thumbnail_url] as [string, string]] : []),
220
223
  ]),
221
224
  mdSection('Caption', p.content ? mdQuote(p.content) : '_(empty)_'),
222
225
  ].join('\n\n');