@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.
Files changed (2) hide show
  1. package/dist/cmo-mcp.mjs +230 -19
  2. 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.5",
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
- /** Like or save a skill (on: false undoes it). */
7530
- react: (id, kind, on2 = true) => request(
7531
- `/v1/skills/${encodeURIComponent(id)}/react`,
7532
- { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ kind, on: on2 }) }
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: (sitemapUrl) => post(
8205
+ runSitemapMatch: (sitemapUrl2) => post(
8205
8206
  `${base}/rewrites/sitemap-match`,
8206
- sitemapUrl ? { sitemap_url: sitemapUrl } : {}
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 like on" : "Like"} the skill ${q(a.skill_id)} on cmo.so.`,
8643
- "zh-Hant": (a) => `${a.undo ? "\u53D6\u6D88\u5C0D" : "\u5728 cmo.so \u8B9A"}\u6280\u80FD ${q(a.skill_id)}${a.undo ? " \u7684\u8B9A" : ""}\u3002`
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, likes 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.",
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) => ({ id: s.id, name: s.name, summary: s.summary, likes: s.likes, page: s.url }))
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
- likes: s.likes,
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: "Like a skill in the cmo.so library for the user (or take the like back with undo). Likes help the library decide what to keep improving. Only when the user asks.",
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 like back" } },
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, liked: r.liked, likes: r.counts.likes };
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/saved or with skills_saved_list.",
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 likes 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.",
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.5",
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",