@cmo-so/mcp 0.7.5 → 0.7.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/dist/cmo-mcp.mjs +230 -19
- 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.7.
|
|
7202
|
+
version: "0.7.7",
|
|
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",
|
|
@@ -7526,11 +7526,12 @@ function createApi({ baseUrl, session: session2, fetch: fetchImpl = globalThis.f
|
|
|
7526
7526
|
return request(`/v1/skills?${qs}`);
|
|
7527
7527
|
},
|
|
7528
7528
|
get: (id) => request(`/v1/skills/${encodeURIComponent(id)}`),
|
|
7529
|
-
/**
|
|
7530
|
-
react: (id, kind, on2 = true) => request(
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7529
|
+
/** Vote on or save a skill (on: false undoes it). "like" is an upvote. */
|
|
7530
|
+
react: (id, kind, on2 = true) => request(`/v1/skills/${encodeURIComponent(id)}/react`, {
|
|
7531
|
+
method: "POST",
|
|
7532
|
+
headers: { "Content-Type": "application/json" },
|
|
7533
|
+
body: JSON.stringify({ kind, on: on2 })
|
|
7534
|
+
}),
|
|
7534
7535
|
saved: () => request("/v1/skills/saved"),
|
|
7535
7536
|
/** Suggest an open-source GitHub repo; Maggie researches it (3 a day). */
|
|
7536
7537
|
submit: (repoUrl, note) => request("/v1/skills/submissions", {
|
|
@@ -8201,9 +8202,9 @@ function domainApi(api2, domain) {
|
|
|
8201
8202
|
/** The last time posts delivered by API pull were found in the website's sitemap (gives them their address). */
|
|
8202
8203
|
sitemapMatch: () => api2.request(`${base}/rewrites/sitemap-match`),
|
|
8203
8204
|
/** Read the sitemap (found on its own when blank) and match API-pull posts to their live address. */
|
|
8204
|
-
runSitemapMatch: (
|
|
8205
|
+
runSitemapMatch: (sitemapUrl2) => post(
|
|
8205
8206
|
`${base}/rewrites/sitemap-match`,
|
|
8206
|
-
|
|
8207
|
+
sitemapUrl2 ? { sitemap_url: sitemapUrl2 } : {}
|
|
8207
8208
|
),
|
|
8208
8209
|
rewritePolicy: () => api2.request(`${base}/rewrite-policy`),
|
|
8209
8210
|
setRewritePolicy: (p) => put(`${base}/rewrite-policy`, p),
|
|
@@ -8340,6 +8341,75 @@ function createSession({ oauth: oauth2, store, skewMs = 6e4, now = Date.now, onS
|
|
|
8340
8341
|
};
|
|
8341
8342
|
}
|
|
8342
8343
|
|
|
8344
|
+
// ../../packages/api-client/src/sitemaps.ts
|
|
8345
|
+
var SITEMAPS_MAX = 20;
|
|
8346
|
+
var SITEMAP_LABEL_MAX = 80;
|
|
8347
|
+
var SITEMAP_OWNERS = ["own", "competitor", "other"];
|
|
8348
|
+
var SITEMAP_USES = {
|
|
8349
|
+
own: ["post_ideas", "channel_ideas", "internal_links", "post_matching"],
|
|
8350
|
+
competitor: ["post_ideas", "channel_ideas"],
|
|
8351
|
+
other: ["post_ideas", "channel_ideas"]
|
|
8352
|
+
};
|
|
8353
|
+
function sitemapUrl(input) {
|
|
8354
|
+
let v = input.trim();
|
|
8355
|
+
if (!v || /^[a-z][a-z0-9+.-]*:(?!\/\/|\d)/i.test(v)) return null;
|
|
8356
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(v)) v = `https://${v}`;
|
|
8357
|
+
let u;
|
|
8358
|
+
try {
|
|
8359
|
+
u = new URL(v);
|
|
8360
|
+
} catch {
|
|
8361
|
+
return null;
|
|
8362
|
+
}
|
|
8363
|
+
if (u.protocol !== "http:" && u.protocol !== "https:" || u.username || u.password) return null;
|
|
8364
|
+
if (!u.hostname.includes(".") || /\s/.test(v)) return null;
|
|
8365
|
+
u.protocol = "https:";
|
|
8366
|
+
return u.toString();
|
|
8367
|
+
}
|
|
8368
|
+
function sitemapUses(owner, uses) {
|
|
8369
|
+
const allowed2 = SITEMAP_USES[owner];
|
|
8370
|
+
const kept = allowed2.filter((u) => uses?.includes(u));
|
|
8371
|
+
return kept.length ? kept : [...allowed2];
|
|
8372
|
+
}
|
|
8373
|
+
function onWebsite(url, domain) {
|
|
8374
|
+
let host;
|
|
8375
|
+
try {
|
|
8376
|
+
host = new URL(url).hostname.toLowerCase();
|
|
8377
|
+
} catch {
|
|
8378
|
+
return false;
|
|
8379
|
+
}
|
|
8380
|
+
const d = domain.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^www\./, "");
|
|
8381
|
+
return Boolean(d) && (host === d || host.endsWith(`.${d}`));
|
|
8382
|
+
}
|
|
8383
|
+
function cleanSitemap(entry, domain) {
|
|
8384
|
+
const url = sitemapUrl(entry.url ?? "");
|
|
8385
|
+
if (!url) return "The sitemap must be a web address starting with http:// or https://.";
|
|
8386
|
+
let owner = SITEMAP_OWNERS.includes(entry.owner) ? entry.owner : "other";
|
|
8387
|
+
if (owner === "own" && domain && !onWebsite(url, domain)) owner = "other";
|
|
8388
|
+
const label = (entry.label ?? "").trim().slice(0, SITEMAP_LABEL_MAX);
|
|
8389
|
+
const out = { ...entry, url, owner, label: label || null, uses: sitemapUses(owner, entry.uses) };
|
|
8390
|
+
if (!out.id) delete out.id;
|
|
8391
|
+
return out;
|
|
8392
|
+
}
|
|
8393
|
+
function checkSitemaps(list2) {
|
|
8394
|
+
if (list2.length > SITEMAPS_MAX) return `Up to ${SITEMAPS_MAX} sitemaps. Remove one first.`;
|
|
8395
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8396
|
+
for (const s of list2) {
|
|
8397
|
+
const k = s.url.trim().toLowerCase();
|
|
8398
|
+
if (seen.has(k)) return `${s.url} is already in the list.`;
|
|
8399
|
+
seen.add(k);
|
|
8400
|
+
}
|
|
8401
|
+
return null;
|
|
8402
|
+
}
|
|
8403
|
+
function sitemapCheckLine(check) {
|
|
8404
|
+
if (!check || check.status === "unchecked") return "Checked when you save";
|
|
8405
|
+
if (check.status === "error") return check.error?.trim() || "Couldn't read this sitemap.";
|
|
8406
|
+
const n = check.url_count;
|
|
8407
|
+
const found = n == null ? "Read" : `Found ${n.toLocaleString("en-GB")} ${n === 1 ? "page" : "pages"}`;
|
|
8408
|
+
const at = check.checked_at ? new Date(check.checked_at) : null;
|
|
8409
|
+
const when = at && !Number.isNaN(at.getTime()) ? at.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : null;
|
|
8410
|
+
return when ? `${found} \xB7 checked ${when}` : found;
|
|
8411
|
+
}
|
|
8412
|
+
|
|
8343
8413
|
// ../../packages/domain/src/copy/maggie.ts
|
|
8344
8414
|
var q = (s) => `\u201C${String(s ?? "").slice(0, 120)}\u201D`;
|
|
8345
8415
|
var named = (title, id) => {
|
|
@@ -8391,6 +8461,10 @@ var confirmCopy = {
|
|
|
8391
8461
|
en: (a) => `Remove the call-to-action for ${q(a.target_keyword)}.`,
|
|
8392
8462
|
"zh-Hant": (a) => `\u522A\u9664 ${q(a.target_keyword)} \u7684\u884C\u52D5\u547C\u7C72\u3002`
|
|
8393
8463
|
},
|
|
8464
|
+
internal_links_set: {
|
|
8465
|
+
en: (a) => Number(a.count) ? `Link each new post to ${a.count} of your latest articles.` : "Stop linking new posts to your other articles.",
|
|
8466
|
+
"zh-Hant": (a) => Number(a.count) ? `\u6BCF\u7BC7\u65B0\u6587\u7AE0\u9023\u7D50\u5230\u4F60\u6700\u65B0\u7684 ${a.count} \u7BC7\u6587\u7AE0\u3002` : "\u65B0\u6587\u7AE0\u4E0D\u518D\u9023\u7D50\u5230\u4F60\u7684\u5176\u4ED6\u6587\u7AE0\u3002"
|
|
8467
|
+
},
|
|
8394
8468
|
testimonial_upsert: {
|
|
8395
8469
|
en: (a) => `Save the testimonial from ${a.name}${a.use_in_posts ? " and use testimonials in posts" : ""}.`,
|
|
8396
8470
|
"zh-Hant": (a) => `\u5132\u5B58 ${a.name} \u7684\u5BA2\u6236\u8A55\u50F9${a.use_in_posts ? "\uFF0C\u4E26\u5728\u6587\u7AE0\u88E1\u4F7F\u7528\u8A55\u50F9" : ""}\u3002`
|
|
@@ -8423,6 +8497,22 @@ var confirmCopy = {
|
|
|
8423
8497
|
en: (a) => `Remove the competitor ${q(a.name)}.`,
|
|
8424
8498
|
"zh-Hant": (a) => `\u522A\u9664\u7AF6\u722D\u5C0D\u624B ${q(a.name)}\u3002`
|
|
8425
8499
|
},
|
|
8500
|
+
sitemap_upsert: {
|
|
8501
|
+
en: (a) => {
|
|
8502
|
+
const whose = { own: "your site's ", competitor: "a competitor's ", other: "another site's " }[a.owner];
|
|
8503
|
+
const label = a.label ? ` (${q(a.label)})` : "";
|
|
8504
|
+
return a.url ? `Save ${whose ?? "the "}sitemap ${a.url}${label}.` : `Change the sitemap${label}: ${keys(a).filter((k) => k !== "id").join(", ")}.`;
|
|
8505
|
+
},
|
|
8506
|
+
"zh-Hant": (a) => {
|
|
8507
|
+
const whose = { own: "\u81EA\u5DF1\u7DB2\u7AD9\u7684", competitor: "\u7AF6\u722D\u5C0D\u624B\u7684", other: "\u5176\u4ED6\u7DB2\u7AD9\u7684" }[a.owner];
|
|
8508
|
+
const label = a.label ? `\uFF08${q(a.label)}\uFF09` : "";
|
|
8509
|
+
return a.url ? `\u5132\u5B58${whose ?? ""} sitemap ${a.url}${label}\u3002` : `\u4FEE\u6539 sitemap${label}\uFF1A${keys(a).filter((k) => k !== "id").join("\u3001")}\u3002`;
|
|
8510
|
+
}
|
|
8511
|
+
},
|
|
8512
|
+
sitemap_remove: {
|
|
8513
|
+
en: (a) => a.url ?? a.id ? `Remove the sitemap ${a.url ?? a.id}.` : "Remove a sitemap.",
|
|
8514
|
+
"zh-Hant": (a) => a.url ?? a.id ? `\u522A\u9664 sitemap ${a.url ?? a.id}\u3002` : "\u522A\u9664\u4E00\u500B sitemap\u3002"
|
|
8515
|
+
},
|
|
8426
8516
|
brand_research_update: {
|
|
8427
8517
|
en: (a) => `Update your market notes: ${keys(a).join(", ") || "nothing"}.`,
|
|
8428
8518
|
"zh-Hant": (a) => `\u66F4\u65B0\u5E02\u5834\u8CC7\u6599\uFF1A${keys(a).join("\u3001") || "\u6C92\u6709"}\u3002`
|
|
@@ -8639,8 +8729,8 @@ var confirmCopy = {
|
|
|
8639
8729
|
},
|
|
8640
8730
|
// ---------------------------------------------------------- Marketing skills (docs/26)
|
|
8641
8731
|
skill_like: {
|
|
8642
|
-
en: (a) => `${a.undo ? "Take back your
|
|
8643
|
-
"zh-Hant": (a) => `${a.undo ? "\
|
|
8732
|
+
en: (a) => `${a.undo ? "Take back your upvote on" : "Upvote"} the skill ${q(a.skill_id)} on cmo.so.`,
|
|
8733
|
+
"zh-Hant": (a) => `${a.undo ? "\u6536\u56DE\u5C0D" : "\u5728 cmo.so \u6295\u7968\u652F\u6301"}\u6280\u80FD ${q(a.skill_id)}${a.undo ? " \u7684\u652F\u6301\u7968" : ""}\u3002`
|
|
8644
8734
|
},
|
|
8645
8735
|
skill_save: {
|
|
8646
8736
|
en: (a) => `${a.undo ? "Remove" : "Save"} the skill ${q(a.skill_id)} ${a.undo ? "from" : "to"} your saved skills.`,
|
|
@@ -9668,6 +9758,24 @@ var brandTools = [
|
|
|
9668
9758
|
return { ctas: out.ctas?.map((c) => c.target_keyword) };
|
|
9669
9759
|
}
|
|
9670
9760
|
}),
|
|
9761
|
+
tool({
|
|
9762
|
+
name: "internal_links_set",
|
|
9763
|
+
description: "Set how many of the website's own latest articles each new post links to (internal cross-links, picked for the topic; separate from CTAs). 0 turns it off, max 10, default 3.",
|
|
9764
|
+
parameters: {
|
|
9765
|
+
type: "object",
|
|
9766
|
+
properties: { count: { type: "integer", minimum: 0, maximum: 10 } },
|
|
9767
|
+
required: ["count"]
|
|
9768
|
+
},
|
|
9769
|
+
group: "brand",
|
|
9770
|
+
need: write2,
|
|
9771
|
+
risk: "W1",
|
|
9772
|
+
run: async (ctx, { count }) => {
|
|
9773
|
+
const n = Math.round(Number(count));
|
|
9774
|
+
if (!Number.isFinite(n) || n < 0 || n > 10) throw new Error("count must be 0 to 10.");
|
|
9775
|
+
const out = await ws(ctx).updateBrand({ internal_links: { count: n } });
|
|
9776
|
+
return { internal_links: out.internal_links ?? { count: n } };
|
|
9777
|
+
}
|
|
9778
|
+
}),
|
|
9671
9779
|
tool({
|
|
9672
9780
|
name: "testimonial_upsert",
|
|
9673
9781
|
description: "Add a customer testimonial, or change the one from the same person. Only real quotes the user gives (or examples they asked for). use_in_posts turns testimonials on in posts.",
|
|
@@ -9857,6 +9965,85 @@ var brandTools = [
|
|
|
9857
9965
|
return { found: out.found, suggestions: out.items.map(({ id, name, website }) => ({ id, name, website })) };
|
|
9858
9966
|
}
|
|
9859
9967
|
}),
|
|
9968
|
+
tool({
|
|
9969
|
+
name: "sitemap_list",
|
|
9970
|
+
description: "The sitemaps saved in the business profile: the website's own, competitors' and other sites' (industry sites, partners), each with what it is used for (post_ideas = blog post ideas, channel_ideas = social post ideas, internal_links and post_matching = own sitemaps only) and its last check (pages found or the error). Max 20.",
|
|
9971
|
+
parameters: none,
|
|
9972
|
+
group: "brand",
|
|
9973
|
+
need: { resource: "overview", level: "read" },
|
|
9974
|
+
risk: "R0",
|
|
9975
|
+
run: async (ctx) => {
|
|
9976
|
+
const list2 = (await ws(ctx).brand()).sitemaps ?? [];
|
|
9977
|
+
return { max: SITEMAPS_MAX, sitemaps: list2.map(sitemapResult) };
|
|
9978
|
+
}
|
|
9979
|
+
}),
|
|
9980
|
+
tool({
|
|
9981
|
+
name: "sitemap_upsert",
|
|
9982
|
+
description: "Add a sitemap to the business profile, or change one (found by id from sitemap_list, else by url). owner: own (the website's own; its host must be the website or a subdomain, otherwise it is saved as other), competitor, or other. uses: own may have post_ideas, channel_ideas, internal_links, post_matching; competitor/other only post_ideas, channel_ideas; leave out for all allowed. A new sitemap needs url and owner. Only sitemap addresses the user gives or confirms (never guess one). Saving checks the sitemap; an unreachable one is still saved with the error. Max 20.",
|
|
9983
|
+
parameters: {
|
|
9984
|
+
type: "object",
|
|
9985
|
+
properties: {
|
|
9986
|
+
id: { type: "string", description: "The sitemap to change (from sitemap_list)" },
|
|
9987
|
+
url: { type: "string", description: "The sitemap's http(s) address; with id, its new address" },
|
|
9988
|
+
owner: { type: "string", enum: ["own", "competitor", "other"] },
|
|
9989
|
+
label: { type: "string", description: "A short name, up to 80 characters" },
|
|
9990
|
+
uses: {
|
|
9991
|
+
type: "array",
|
|
9992
|
+
items: { type: "string", enum: ["post_ideas", "channel_ideas", "internal_links", "post_matching"] }
|
|
9993
|
+
}
|
|
9994
|
+
}
|
|
9995
|
+
},
|
|
9996
|
+
group: "brand",
|
|
9997
|
+
need: write2,
|
|
9998
|
+
risk: "W1",
|
|
9999
|
+
run: async (ctx, args) => {
|
|
10000
|
+
const list2 = (await ws(ctx).brand()).sitemaps ?? [];
|
|
10001
|
+
const i = findSitemap(list2, args.id, args.id ? void 0 : args.url);
|
|
10002
|
+
if (args.id && i < 0) throw new Error(`No sitemap with id ${args.id}. Use sitemap_list.`);
|
|
10003
|
+
const old = i >= 0 ? list2[i] : void 0;
|
|
10004
|
+
if (!old && (!args.url || !args.owner)) throw new Error("A new sitemap needs url and owner.");
|
|
10005
|
+
const owner = args.owner ?? old?.owner ?? "other";
|
|
10006
|
+
const entry = cleanSitemap(
|
|
10007
|
+
{
|
|
10008
|
+
...old,
|
|
10009
|
+
url: args.url ?? old?.url ?? "",
|
|
10010
|
+
owner,
|
|
10011
|
+
label: args.label ?? old?.label ?? null,
|
|
10012
|
+
// A new owner without uses gets that owner's defaults.
|
|
10013
|
+
uses: args.uses ?? (args.owner && args.owner !== old?.owner ? [] : old?.uses ?? [])
|
|
10014
|
+
},
|
|
10015
|
+
ctx.domain ?? void 0
|
|
10016
|
+
);
|
|
10017
|
+
if (typeof entry === "string") throw new Error(entry);
|
|
10018
|
+
if (old && sitemapUrl(old.url) !== entry.url) delete entry.check;
|
|
10019
|
+
const next = old ? list2.map((x, n) => n === i ? entry : x) : [...list2, entry];
|
|
10020
|
+
const problem = checkSitemaps(next);
|
|
10021
|
+
if (problem) throw new Error(problem);
|
|
10022
|
+
const saved = (await ws(ctx).updateBrand({ sitemaps: next })).sitemaps ?? next;
|
|
10023
|
+
const mine = saved[findSitemap(saved, void 0, entry.url)];
|
|
10024
|
+
return {
|
|
10025
|
+
saved: mine ? sitemapResult(mine) : sitemapResult(entry),
|
|
10026
|
+
...(mine?.owner ?? entry.owner) !== owner ? { note: "Not on this website, so it was saved as another site's sitemap (owner other)." } : {},
|
|
10027
|
+
sitemaps: saved.length
|
|
10028
|
+
};
|
|
10029
|
+
}
|
|
10030
|
+
}),
|
|
10031
|
+
tool({
|
|
10032
|
+
name: "sitemap_remove",
|
|
10033
|
+
description: "Remove a sitemap from the business profile (by id from sitemap_list, or by url). Maggie stops reading it; ideas already found stay.",
|
|
10034
|
+
parameters: { type: "object", properties: { id: { type: "string" }, url: { type: "string" } } },
|
|
10035
|
+
group: "brand",
|
|
10036
|
+
need: write2,
|
|
10037
|
+
risk: "W1",
|
|
10038
|
+
run: async (ctx, { id, url }) => {
|
|
10039
|
+
if (!id && !url) throw new Error("Say which sitemap: id or url.");
|
|
10040
|
+
const list2 = (await ws(ctx).brand()).sitemaps ?? [];
|
|
10041
|
+
const i = findSitemap(list2, id, url);
|
|
10042
|
+
if (i < 0) throw new Error(`No sitemap ${id ?? url} was found. Use sitemap_list.`);
|
|
10043
|
+
const saved = (await ws(ctx).updateBrand({ sitemaps: list2.filter((_, n) => n !== i) })).sitemaps ?? [];
|
|
10044
|
+
return { removed: list2[i]?.url, sitemaps: saved.map((s) => s.url) };
|
|
10045
|
+
}
|
|
10046
|
+
}),
|
|
9860
10047
|
tool({
|
|
9861
10048
|
name: "brand_research_update",
|
|
9862
10049
|
description: "Change the profile's market notes: market_research (text), additional_information (anything else Maggie should know) or the SWOT list (each {name: Strengths|Weaknesses|Opportunities|Threats, description}). A list replaces the whole list.",
|
|
@@ -9992,6 +10179,22 @@ function brandGaps(b) {
|
|
|
9992
10179
|
check("industries", b.industries, "the industry, for examples and wording");
|
|
9993
10180
|
return { complete: gaps.length === 0, gaps };
|
|
9994
10181
|
}
|
|
10182
|
+
function findSitemap(list2, id, url) {
|
|
10183
|
+
if (id) return list2.findIndex((s) => s.id === id);
|
|
10184
|
+
const u = url ? (sitemapUrl(url) ?? url).toLowerCase() : "";
|
|
10185
|
+
return u ? list2.findIndex((s) => (sitemapUrl(s.url) ?? s.url).toLowerCase() === u) : -1;
|
|
10186
|
+
}
|
|
10187
|
+
function sitemapResult(s) {
|
|
10188
|
+
return {
|
|
10189
|
+
id: s.id ?? null,
|
|
10190
|
+
url: s.url,
|
|
10191
|
+
owner: s.owner,
|
|
10192
|
+
label: s.label ?? null,
|
|
10193
|
+
uses: s.uses,
|
|
10194
|
+
check: s.check?.status ?? "unchecked",
|
|
10195
|
+
status: sitemapCheckLine(s.check)
|
|
10196
|
+
};
|
|
10197
|
+
}
|
|
9995
10198
|
|
|
9996
10199
|
// ../../packages/tools/src/feedback.ts
|
|
9997
10200
|
var account = { resource: "account", level: "read" };
|
|
@@ -10746,7 +10949,7 @@ var ID = { type: "string", description: "The skill id, like cmo-ad-creative" };
|
|
|
10746
10949
|
var skillTools = [
|
|
10747
10950
|
tool({
|
|
10748
10951
|
name: "skills_search",
|
|
10749
|
-
description: "Search cmo.so's open library of marketing skills (step-by-step methods an AI agent follows, free and MIT licensed): returns matching skills with their summary,
|
|
10952
|
+
description: "Search cmo.so's open library of marketing skills (step-by-step methods an AI agent follows, free and MIT licensed): returns matching skills with their summary, vote score and page. Use it when the user asks how to do a marketing job, wants a playbook or template, or asks which skills exist. Empty query: the most useful ones and the categories.",
|
|
10750
10953
|
parameters: {
|
|
10751
10954
|
type: "object",
|
|
10752
10955
|
properties: {
|
|
@@ -10770,7 +10973,13 @@ var skillTools = [
|
|
|
10770
10973
|
return {
|
|
10771
10974
|
total_in_library: r.total,
|
|
10772
10975
|
...a.query ? {} : { categories: r.categories.map((c) => `${c.id} (${c.count})`) },
|
|
10773
|
-
skills: r.skills.map((s) => ({
|
|
10976
|
+
skills: r.skills.map((s) => ({
|
|
10977
|
+
id: s.id,
|
|
10978
|
+
name: s.name,
|
|
10979
|
+
summary: s.summary,
|
|
10980
|
+
score: s.score ?? s.likes,
|
|
10981
|
+
page: s.url
|
|
10982
|
+
}))
|
|
10774
10983
|
};
|
|
10775
10984
|
}
|
|
10776
10985
|
}),
|
|
@@ -10789,7 +10998,7 @@ var skillTools = [
|
|
|
10789
10998
|
name: s.name,
|
|
10790
10999
|
summary: s.summary,
|
|
10791
11000
|
page: s.url,
|
|
10792
|
-
|
|
11001
|
+
score: s.score ?? s.likes,
|
|
10793
11002
|
saves: s.saves,
|
|
10794
11003
|
uses_tools: s.mcp_tools,
|
|
10795
11004
|
pairs_with: s.pairs_with,
|
|
@@ -10801,10 +11010,10 @@ var skillTools = [
|
|
|
10801
11010
|
}),
|
|
10802
11011
|
tool({
|
|
10803
11012
|
name: "skill_like",
|
|
10804
|
-
description: "
|
|
11013
|
+
description: "Upvote a skill in the cmo.so library for the user (or take the upvote back with undo). Votes help the library decide what to keep improving. Only when the user asks.",
|
|
10805
11014
|
parameters: {
|
|
10806
11015
|
type: "object",
|
|
10807
|
-
properties: { skill_id: ID, undo: { type: "boolean", description: "true takes the
|
|
11016
|
+
properties: { skill_id: ID, undo: { type: "boolean", description: "true takes the upvote back" } },
|
|
10808
11017
|
required: ["skill_id"]
|
|
10809
11018
|
},
|
|
10810
11019
|
group: "account",
|
|
@@ -10813,12 +11022,12 @@ var skillTools = [
|
|
|
10813
11022
|
idempotent: true,
|
|
10814
11023
|
run: async (ctx, a) => {
|
|
10815
11024
|
const r = await ctx.api.skills.react(String(a.skill_id), "like", !a.undo);
|
|
10816
|
-
return { skill_id: r.item,
|
|
11025
|
+
return { skill_id: r.item, upvoted: r.liked, score: r.counts.score ?? r.counts.likes };
|
|
10817
11026
|
}
|
|
10818
11027
|
}),
|
|
10819
11028
|
tool({
|
|
10820
11029
|
name: "skill_save",
|
|
10821
|
-
description: "Save a skill to the user's saved skills on cmo.so (or remove it with undo), to find it again at cmo.so/skills
|
|
11030
|
+
description: "Save a skill to the user's saved skills on cmo.so (or remove it with undo), to find it again under Saved at cmo.so/skills or with skills_saved_list.",
|
|
10822
11031
|
parameters: {
|
|
10823
11032
|
type: "object",
|
|
10824
11033
|
properties: { skill_id: ID, undo: { type: "boolean", description: "true removes it from saved" } },
|
|
@@ -10851,7 +11060,7 @@ var skillTools = [
|
|
|
10851
11060
|
}),
|
|
10852
11061
|
tool({
|
|
10853
11062
|
name: "skill_submit",
|
|
10854
|
-
description: "Suggest a public open-source GitHub repo for cmo.so's skill library. Maggie researches it within minutes and decides: add a new skill, improve an existing one, keep it as a candidate that needs 10
|
|
11063
|
+
description: "Suggest a public open-source GitHub repo for cmo.so's skill library. Maggie researches it within minutes and decides: add a new skill, improve an existing one, keep it as a candidate that needs a vote score of 10 in 30 days, or decline (licence first: permissive licences can be adapted, copyleft only inspires, no licence is declined). 3 suggestions a day. Follow up with skill_submissions_list.",
|
|
10855
11064
|
parameters: {
|
|
10856
11065
|
type: "object",
|
|
10857
11066
|
properties: {
|
|
@@ -22393,7 +22602,9 @@ var OPEN_WORLD = /* @__PURE__ */ new Set([
|
|
|
22393
22602
|
"seo_audit_run",
|
|
22394
22603
|
"ai_visibility_prompt_check",
|
|
22395
22604
|
"idea_add",
|
|
22396
|
-
"competitor_suggestions_find"
|
|
22605
|
+
"competitor_suggestions_find",
|
|
22606
|
+
// Saving makes CMO.so fetch the sitemap from its website.
|
|
22607
|
+
"sitemap_upsert"
|
|
22397
22608
|
]);
|
|
22398
22609
|
var OVERWRITES = /* @__PURE__ */ new Set([
|
|
22399
22610
|
"ai_visibility_keywords_set",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/mcp",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
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",
|