@cmo-so/mcp 0.12.12 → 0.12.14
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/dist/cmo-mcp.mjs +78 -22
- package/package.json +1 -1
package/dist/cmo-mcp.mjs
CHANGED
|
@@ -7199,7 +7199,7 @@ var require_dist = __commonJS({
|
|
|
7199
7199
|
// package.json
|
|
7200
7200
|
var package_default = {
|
|
7201
7201
|
name: "@cmo-so/cli",
|
|
7202
|
-
version: "0.12.
|
|
7202
|
+
version: "0.12.14",
|
|
7203
7203
|
description: "CMO.so on the command line: your websites, ideas, blog posts, LinkedIn queue and results, plus a local MCP server for Claude Desktop, Cursor and other MCP clients.",
|
|
7204
7204
|
type: "module",
|
|
7205
7205
|
license: "MIT",
|
|
@@ -16896,7 +16896,29 @@ async function techAudit(site, o = {}) {
|
|
|
16896
16896
|
const add = (f) => findings.push(f);
|
|
16897
16897
|
const robotsR = await get(`${base}/robots.txt`, "follow");
|
|
16898
16898
|
const robots = robotsR?.status === 200 ? robotsR.text : "";
|
|
16899
|
-
const {
|
|
16899
|
+
const visible = await site.request({ path: "/wp/v2/settings", query: { _fields: "blog_public" } }).then((r) => r.status === 200 && r.data.blog_public != null ? !!Number(r.data.blog_public) : null).catch(() => null);
|
|
16900
|
+
const engagedOff = visible === false;
|
|
16901
|
+
let { urls, sitemap } = await sitemapUrls(base, robots);
|
|
16902
|
+
let discovered = "sitemap";
|
|
16903
|
+
if (!urls.length) {
|
|
16904
|
+
const rows = await Promise.all(
|
|
16905
|
+
["/wp/v2/pages", "/wp/v2/posts"].map(
|
|
16906
|
+
(path2) => all(site, path2, { status: "publish", _fields: "link", per_page: 100 }, limit).catch(() => [])
|
|
16907
|
+
)
|
|
16908
|
+
);
|
|
16909
|
+
urls = [...new Set(rows.flat().map((r) => r.link))];
|
|
16910
|
+
discovered = "wordpress";
|
|
16911
|
+
}
|
|
16912
|
+
if (engagedOff)
|
|
16913
|
+
add({
|
|
16914
|
+
id: "search-engines-off",
|
|
16915
|
+
priority: "p1",
|
|
16916
|
+
area: "settings",
|
|
16917
|
+
title: "Search engines are switched off for this site",
|
|
16918
|
+
why: "WordPress tells every search engine not to index it (noindex on every page, no sitemap). Right for a test or unfinished site; check MEMORY.md before changing it.",
|
|
16919
|
+
fix: "When the site is ready: wp_settings_set search_engines true.",
|
|
16920
|
+
tool: { name: "wp_settings_set", args: { search_engines: true } }
|
|
16921
|
+
});
|
|
16900
16922
|
if (!robotsR || robotsR.status !== 200)
|
|
16901
16923
|
add({
|
|
16902
16924
|
id: "robots-missing",
|
|
@@ -16916,7 +16938,7 @@ async function techAudit(site, o = {}) {
|
|
|
16916
16938
|
fix: "Turn search engines on (wp_settings_set search_engines true) or fix the robots.txt file.",
|
|
16917
16939
|
tool: { name: "wp_settings_set", args: { search_engines: true } }
|
|
16918
16940
|
});
|
|
16919
|
-
if (robots && !/^sitemap:/im.test(robots))
|
|
16941
|
+
if (robots && !engagedOff && !/^sitemap:/im.test(robots))
|
|
16920
16942
|
add({
|
|
16921
16943
|
id: "robots-sitemap",
|
|
16922
16944
|
priority: "p3",
|
|
@@ -16935,7 +16957,7 @@ async function techAudit(site, o = {}) {
|
|
|
16935
16957
|
why: "ChatGPT, Claude, Perplexity and Google's AI answers can't read or cite the site.",
|
|
16936
16958
|
fix: "Allow them in robots.txt unless the owner chose to block AI."
|
|
16937
16959
|
});
|
|
16938
|
-
if (!sitemap)
|
|
16960
|
+
if (!sitemap && !engagedOff)
|
|
16939
16961
|
add({
|
|
16940
16962
|
id: "sitemap-missing",
|
|
16941
16963
|
priority: "p1",
|
|
@@ -16957,6 +16979,7 @@ async function techAudit(site, o = {}) {
|
|
|
16957
16979
|
tool: { name: "wp_llms_txt_set", args: { generate: true } }
|
|
16958
16980
|
});
|
|
16959
16981
|
const crawl = urls.length ? urls.slice(0, limit) : [`${base}/`];
|
|
16982
|
+
if (!crawl.some((u) => sitePath(u, base) === "/")) crawl.unshift(`${base}/`);
|
|
16960
16983
|
const scans = await pool3(crawl, 6, async (u) => {
|
|
16961
16984
|
const r = await get(u);
|
|
16962
16985
|
if (!r) return null;
|
|
@@ -16976,7 +16999,7 @@ async function techAudit(site, o = {}) {
|
|
|
16976
16999
|
fix: p.status >= 400 ? "Publish the page again, or remove it (wp_post_delete / wp_page_publish)." : "List the final address; the redirect is fine for visitors."
|
|
16977
17000
|
});
|
|
16978
17001
|
for (const p of ok2) {
|
|
16979
|
-
if (/noindex/.test(p.robots))
|
|
17002
|
+
if (/noindex/.test(p.robots) && !engagedOff && discovered === "sitemap")
|
|
16980
17003
|
add({
|
|
16981
17004
|
id: `noindex-in-sitemap:${p.path}`,
|
|
16982
17005
|
priority: "p1",
|
|
@@ -17153,7 +17176,9 @@ async function techAudit(site, o = {}) {
|
|
|
17153
17176
|
summary: {
|
|
17154
17177
|
...Object.fromEntries(order2.map((p) => [p, findings.filter((f) => f.priority === p).length])),
|
|
17155
17178
|
sitemap,
|
|
17156
|
-
|
|
17179
|
+
pages_from: discovered,
|
|
17180
|
+
search_engines: visible,
|
|
17181
|
+
sitemap_urls: discovered === "sitemap" ? urls.length : 0,
|
|
17157
17182
|
crawled: pages.length,
|
|
17158
17183
|
live: ok2.length,
|
|
17159
17184
|
llms_txt: hasLlms,
|
|
@@ -17181,7 +17206,10 @@ async function pageSpeed(url) {
|
|
|
17181
17206
|
const r = await fetch(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?${q3}`, {
|
|
17182
17207
|
signal: AbortSignal.timeout(9e4)
|
|
17183
17208
|
}).catch(() => null);
|
|
17184
|
-
if (!r || !r.ok)
|
|
17209
|
+
if (!r || !r.ok)
|
|
17210
|
+
return {
|
|
17211
|
+
error: `PageSpeed Insights didn't answer (${r?.status ?? "timeout"})${r?.status === 429 && !key ? ": the server needs a PAGESPEED_API_KEY (Google Cloud, free)" : ""}.`
|
|
17212
|
+
};
|
|
17185
17213
|
const j = await r.json();
|
|
17186
17214
|
const f = j.loadingExperience?.metrics ?? {};
|
|
17187
17215
|
const a = j.lighthouseResult?.audits ?? {};
|
|
@@ -17298,8 +17326,24 @@ async function linksPlan(site, o = {}) {
|
|
|
17298
17326
|
const blogPage = docs.find((d) => d.id === settings.page_for_posts && d.kind === "page");
|
|
17299
17327
|
const actions = [];
|
|
17300
17328
|
const findings = [];
|
|
17301
|
-
const
|
|
17302
|
-
|
|
17329
|
+
const sample = docs.filter(
|
|
17330
|
+
(d) => d.kind === "page" && (/\/sample-page\/$/.test(d.path) || d.title === "Sample Page") || d.kind === "post" && (/\/hello-world\/$/.test(d.path) || d.title === "Hello world!")
|
|
17331
|
+
);
|
|
17332
|
+
for (const d of sample)
|
|
17333
|
+
findings.push({
|
|
17334
|
+
id: `sample:${d.path}`,
|
|
17335
|
+
priority: "p2",
|
|
17336
|
+
area: "outside",
|
|
17337
|
+
title: `WordPress's sample ${d.kind === "page" ? "page" : "article"} \u201C${d.title}\u201D is still published`,
|
|
17338
|
+
why: "It tells visitors and search engines the site isn't finished, and dilutes the real pages.",
|
|
17339
|
+
fix: "Move it to the trash.",
|
|
17340
|
+
tool: {
|
|
17341
|
+
name: d.kind === "page" ? "wp_page_delete" : "wp_post_delete",
|
|
17342
|
+
args: d.kind === "page" ? { page: d.id } : { post: d.id }
|
|
17343
|
+
}
|
|
17344
|
+
});
|
|
17345
|
+
const pageDocs = docs.filter((d) => d.kind === "page" && d !== home && !sample.includes(d));
|
|
17346
|
+
const postDocs = docs.filter((d) => d.kind === "post" && !sample.includes(d));
|
|
17303
17347
|
const menuItems = flatMenu(menu?.items ?? []);
|
|
17304
17348
|
const top = (menu?.items ?? []).length;
|
|
17305
17349
|
for (const i of menuItems) {
|
|
@@ -17387,7 +17431,7 @@ async function linksPlan(site, o = {}) {
|
|
|
17387
17431
|
for (const post of postDocs) {
|
|
17388
17432
|
let best = null;
|
|
17389
17433
|
let score = 0;
|
|
17390
|
-
for (const pg of pageDocs) {
|
|
17434
|
+
for (const pg of home ? [...pageDocs, home] : pageDocs) {
|
|
17391
17435
|
if (pg === blogPage) continue;
|
|
17392
17436
|
const s = overlap(post.words, pg.words, idf);
|
|
17393
17437
|
if (s > score) [best, score] = [pg, s];
|
|
@@ -17419,7 +17463,7 @@ async function linksPlan(site, o = {}) {
|
|
|
17419
17463
|
}
|
|
17420
17464
|
const lonelyPosts = [];
|
|
17421
17465
|
for (const d of docs) {
|
|
17422
|
-
if (d === home || d === blogPage) continue;
|
|
17466
|
+
if (d === home || d === blogPage || sample.includes(d)) continue;
|
|
17423
17467
|
const ins = inbound.get(d.path);
|
|
17424
17468
|
if (ins && ins.size || chrome.has(d.path)) continue;
|
|
17425
17469
|
if (d.kind === "post") lonelyPosts.push(d);
|
|
@@ -17495,7 +17539,7 @@ async function llmsTxt(site, o = {}) {
|
|
|
17495
17539
|
const base = (site.url ?? "").replace(/\/+$/, "");
|
|
17496
17540
|
const s = await site.request({
|
|
17497
17541
|
path: "/wp/v2/settings",
|
|
17498
|
-
query: { _fields: "title,description,page_on_front,page_for_posts" }
|
|
17542
|
+
query: { _fields: "title,description,page_on_front,page_for_posts,blog_public" }
|
|
17499
17543
|
}).then((r) => r.status === 200 ? r.data : {});
|
|
17500
17544
|
const pages = await listPages(site, { status: "publish" });
|
|
17501
17545
|
const posts = await all(
|
|
@@ -17504,10 +17548,20 @@ async function llmsTxt(site, o = {}) {
|
|
|
17504
17548
|
{ status: "publish", per_page: 100, _fields: "link,title,excerpt,meta", orderby: "date", order: "desc" },
|
|
17505
17549
|
o.posts ?? 25
|
|
17506
17550
|
);
|
|
17507
|
-
const
|
|
17551
|
+
const clip3 = (t, n2 = 200) => {
|
|
17552
|
+
const x = t.replace(/\s+/g, " ").trim();
|
|
17553
|
+
if (x.length <= n2) return x;
|
|
17554
|
+
const cut = x.slice(0, n2);
|
|
17555
|
+
return `${cut.slice(0, Math.max(cut.lastIndexOf(" "), n2 - 30)).replace(/[\s,;:–-]+$/, "")}\u2026`;
|
|
17556
|
+
};
|
|
17557
|
+
const desc = (m, fallback = "") => clip3(String(m?.maggie_seo_description || fallback));
|
|
17558
|
+
const isSample = (link, title) => /\/(sample-page|hello-world)\/?$/.test(link) || title === "Sample Page" || title === "Hello world!";
|
|
17508
17559
|
const home = pages.find((p) => p.id === s.page_on_front);
|
|
17509
17560
|
const noise = /privacy|terms|cookie|thank|cart|checkout|my-account|login/i;
|
|
17510
|
-
const visible = pages.filter(
|
|
17561
|
+
const visible = pages.filter(
|
|
17562
|
+
(p) => p.id !== s.page_on_front && p.id !== s.page_for_posts && !noise.test(p.link) && !isSample(p.link, titleOf(p.title))
|
|
17563
|
+
);
|
|
17564
|
+
const realPosts = posts.filter((p) => !isSample(p.link, strip(p.title.rendered ?? "")));
|
|
17511
17565
|
const byParent = /* @__PURE__ */ new Map();
|
|
17512
17566
|
for (const p of visible) byParent.set(p.parent, [...byParent.get(p.parent) ?? [], p]);
|
|
17513
17567
|
const line = (title, url, d) => `- [${title.replace(/[[\]]/g, "")}](${url})${d ? `: ${d}` : ""}`;
|
|
@@ -17515,25 +17569,27 @@ async function llmsTxt(site, o = {}) {
|
|
|
17515
17569
|
const summary = desc(home?.meta, s.description ?? "");
|
|
17516
17570
|
if (summary) out.push(`> ${summary}`, "");
|
|
17517
17571
|
const top = byParent.get(0) ?? [];
|
|
17518
|
-
if (top.length) {
|
|
17572
|
+
if (top.length || home) {
|
|
17519
17573
|
out.push("## Pages", "");
|
|
17574
|
+
if (home) out.push(line(titleOf(home.title) || "Home", home.link, summary));
|
|
17520
17575
|
for (const p of top) {
|
|
17521
17576
|
out.push(line(titleOf(p.title), p.link, desc(p.meta)));
|
|
17522
17577
|
for (const c of byParent.get(p.id) ?? []) out.push(` ${line(titleOf(c.title), c.link, desc(c.meta))}`);
|
|
17523
17578
|
}
|
|
17524
17579
|
out.push("");
|
|
17525
17580
|
}
|
|
17526
|
-
if (
|
|
17581
|
+
if (realPosts.length) {
|
|
17527
17582
|
out.push("## Articles", "");
|
|
17528
|
-
for (const p of
|
|
17529
|
-
out.push(
|
|
17530
|
-
line(strip(p.title.rendered ?? ""), p.link, desc(p.meta, strip(p.excerpt?.rendered ?? "").slice(0, 160)))
|
|
17531
|
-
);
|
|
17583
|
+
for (const p of realPosts)
|
|
17584
|
+
out.push(line(strip(p.title.rendered ?? ""), p.link, desc(p.meta, strip(p.excerpt?.rendered ?? ""))));
|
|
17532
17585
|
out.push("");
|
|
17533
17586
|
}
|
|
17534
17587
|
const legal = pages.filter((p) => /privacy|terms/i.test(p.link));
|
|
17535
|
-
|
|
17536
|
-
|
|
17588
|
+
const optional2 = [
|
|
17589
|
+
...s.blog_public === 0 ? [] : [line("Sitemap", `${base}/wp-sitemap.xml`, "every page and article")],
|
|
17590
|
+
...legal.map((p) => line(titleOf(p.title), p.link, ""))
|
|
17591
|
+
];
|
|
17592
|
+
if (optional2.length) out.push("## Optional", "", ...optional2);
|
|
17537
17593
|
return `${out.join("\n").replace(/\n{3,}/g, "\n\n").trim()}
|
|
17538
17594
|
`;
|
|
17539
17595
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/mcp",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.14",
|
|
4
4
|
"description": "CMO.so's MCP server for Claude Desktop, Cursor, VS Code and other MCP clients: manage your websites, blog posts, LinkedIn queue and results with Maggie.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|