@cmo-so/mcp 0.9.0 → 0.9.1
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 +86 -16
- 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.9.
|
|
7202
|
+
version: "0.9.1",
|
|
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",
|
|
@@ -8838,11 +8838,11 @@ var confirmCopy = {
|
|
|
8838
8838
|
},
|
|
8839
8839
|
social_pictures_attach: {
|
|
8840
8840
|
en: (a) => {
|
|
8841
|
-
const n = Array.isArray(a.urls) ? a.urls.length : 0;
|
|
8841
|
+
const n = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
|
|
8842
8842
|
return `${a.append ? "Add" : "Put"} your ${n} picture${n === 1 ? "" : "s"} ${a.append ? "after the pictures on" : "on"} post #${a.id}${a.append ? "" : ", in place of its picture"} (free). On LinkedIn a set goes out as a swipeable PDF carousel.`;
|
|
8843
8843
|
},
|
|
8844
8844
|
"zh-Hant": (a) => {
|
|
8845
|
-
const n = Array.isArray(a.urls) ? a.urls.length : 0;
|
|
8845
|
+
const n = (Array.isArray(a.urls) ? a.urls.length : 0) + (Array.isArray(a.files) ? a.files.length : 0);
|
|
8846
8846
|
return `${a.append ? `\u5728\u8CBC\u6587 #${a.id} \u73FE\u6709\u5716\u7247\u5F8C\u52A0\u4E0A` : `\u628A\u8CBC\u6587 #${a.id} \u7684\u5716\u7247\u63DB\u6210`}\u4F60\u7684 ${n} \u5F35\u5716\u7247\uFF08\u514D\u8CBB\uFF09\u3002\u5728 LinkedIn \u4E0A\uFF0C\u4E00\u7D44\u5716\u7247\u6703\u4EE5\u53EF\u6ED1\u52D5\u7684 PDF \u8F2A\u64AD\u767C\u51FA\u3002`;
|
|
8847
8847
|
}
|
|
8848
8848
|
},
|
|
@@ -11613,6 +11613,17 @@ var channelsParam = {
|
|
|
11613
11613
|
items: { type: "string", enum: CHANNEL_KEYS },
|
|
11614
11614
|
description: "Which channels to write for: linkedin, facebook, instagram, google_business. Leave out to write for every connected channel (one post each, one credit each); give one only when the user names it. YouTube and TikTok take a video: use social_video_add."
|
|
11615
11615
|
};
|
|
11616
|
+
var FILE_SCHEMA = {
|
|
11617
|
+
type: "object",
|
|
11618
|
+
properties: {
|
|
11619
|
+
download_url: { type: "string" },
|
|
11620
|
+
file_id: { type: "string" },
|
|
11621
|
+
mime_type: { type: "string" },
|
|
11622
|
+
file_name: { type: "string" }
|
|
11623
|
+
},
|
|
11624
|
+
required: ["download_url", "file_id"]
|
|
11625
|
+
};
|
|
11626
|
+
var fileLinks = (value) => (Array.isArray(value) ? value : value ? [value] : []).map((f) => String(f?.download_url ?? "").trim()).filter(Boolean);
|
|
11616
11627
|
var queuedAt = positionParam("the posts", "Leave out for the front (the next posting times).");
|
|
11617
11628
|
var channelsOf = (value) => {
|
|
11618
11629
|
const list2 = Array.isArray(value) ? value.map((v) => String(v).toLowerCase()).filter((v) => CHANNEL_KEYS.includes(v)) : [];
|
|
@@ -11982,27 +11993,35 @@ var socialTools = [
|
|
|
11982
11993
|
}),
|
|
11983
11994
|
tool({
|
|
11984
11995
|
name: "social_pictures_attach",
|
|
11985
|
-
description: "Put the user's own pictures on a post, in order: public https links to image files (JPEG, PNG or WebP
|
|
11996
|
+
description: "Put the user's own pictures on a post, in order: pictures from this chat (files: ones the user added or that were made here) or public https links to image files (urls), JPEG, PNG or WebP, for example carousel slides. 1-20 pictures; append adds them after the post's current ones. Free (no AI). They replace Maggie's picture; a post from an idea shares them with its other channels. Instagram and Facebook post the first 10, Google Business the first. On LinkedIn a set goes out as a swipeable PDF carousel unless social_carousel_set says images. If you can pass neither, give the user social_upload_link. Preview first.",
|
|
11986
11997
|
parameters: {
|
|
11987
11998
|
type: "object",
|
|
11988
11999
|
properties: {
|
|
11989
12000
|
id: { type: "integer", description: "The post (from social_list), not gone out yet" },
|
|
12001
|
+
files: {
|
|
12002
|
+
type: "array",
|
|
12003
|
+
items: FILE_SCHEMA,
|
|
12004
|
+
description: "Pictures from this chat, in the order they should appear"
|
|
12005
|
+
},
|
|
11990
12006
|
urls: {
|
|
11991
12007
|
type: "array",
|
|
11992
12008
|
items: { type: "string" },
|
|
11993
|
-
description: "Public https links to the image files, in the
|
|
12009
|
+
description: "Public https links to the image files, in order (after the files, when both are given)"
|
|
11994
12010
|
},
|
|
11995
12011
|
append: { type: "boolean", description: "Add after the post's current pictures instead of replacing them" }
|
|
11996
12012
|
},
|
|
11997
|
-
required: ["id"
|
|
12013
|
+
required: ["id"]
|
|
11998
12014
|
},
|
|
12015
|
+
fileParams: ["files"],
|
|
11999
12016
|
group: "social",
|
|
12000
12017
|
need: write5,
|
|
12001
12018
|
risk: "W1",
|
|
12002
12019
|
gates: ["VER"],
|
|
12003
|
-
run: async (ctx, { id, urls, append }) => {
|
|
12004
|
-
const list2 = (Array.isArray(urls) ? urls : []).map((u) => String(u).trim()).filter(
|
|
12005
|
-
|
|
12020
|
+
run: async (ctx, { id, urls, files, append }) => {
|
|
12021
|
+
const list2 = [...fileLinks(files), ...(Array.isArray(urls) ? urls : []).map((u) => String(u).trim())].filter(
|
|
12022
|
+
Boolean
|
|
12023
|
+
);
|
|
12024
|
+
if (!list2.length) throw new Error("Give the pictures (files from this chat, or https links).");
|
|
12006
12025
|
if (list2.length > 20) throw new Error("A post takes up to 20 pictures.");
|
|
12007
12026
|
if (list2.some((u) => !u.startsWith("https://"))) throw new Error("Every picture needs a public https link.");
|
|
12008
12027
|
const out = await ws(ctx).setSocialPictures(Number(id), { urls: list2, append: append === true });
|
|
@@ -12015,30 +12034,57 @@ var socialTools = [
|
|
|
12015
12034
|
}),
|
|
12016
12035
|
tool({
|
|
12017
12036
|
name: "social_carousel_pdf_attach",
|
|
12018
|
-
description: "A LinkedIn post goes out as this PDF: a swipeable document carousel (a public https link to
|
|
12037
|
+
description: "A LinkedIn post goes out as this PDF: a swipeable document carousel. The PDF from this chat (file: one the user added or that was made here) or a public https link to it (url); up to 300 pages and 20 MB, no password. Its first 20 pages also become the post's pictures (what its other channels post). Free. Change the pictures later and the carousel is made from them instead. If you can pass neither, give the user social_upload_link. Preview first.",
|
|
12019
12038
|
parameters: {
|
|
12020
12039
|
type: "object",
|
|
12021
12040
|
properties: {
|
|
12022
12041
|
id: { type: "integer", description: "A LinkedIn post (from social_list), not gone out yet" },
|
|
12042
|
+
file: { ...FILE_SCHEMA, description: "The PDF from this chat" },
|
|
12023
12043
|
url: { type: "string", description: "Public https link to the PDF file" },
|
|
12024
12044
|
title: {
|
|
12025
12045
|
type: "string",
|
|
12026
12046
|
description: "Shown on the carousel (up to 120 characters); the post's topic if left out"
|
|
12027
12047
|
}
|
|
12028
12048
|
},
|
|
12029
|
-
required: ["id"
|
|
12049
|
+
required: ["id"]
|
|
12030
12050
|
},
|
|
12051
|
+
fileParams: ["file"],
|
|
12031
12052
|
group: "social",
|
|
12032
12053
|
need: write5,
|
|
12033
12054
|
risk: "W1",
|
|
12034
12055
|
gates: ["VER", "LI"],
|
|
12035
|
-
run: async (ctx, { id, url, title }) => {
|
|
12036
|
-
const link = String(url ?? "").trim();
|
|
12037
|
-
if (!link.startsWith("https://"))
|
|
12056
|
+
run: async (ctx, { id, url, file, title }) => {
|
|
12057
|
+
const link = fileLinks(file)[0] ?? String(url ?? "").trim();
|
|
12058
|
+
if (!link.startsWith("https://"))
|
|
12059
|
+
throw new Error("Give the PDF (a file from this chat, or a public https link).");
|
|
12038
12060
|
const out = await ws(ctx).attachSocialCarouselPdf(Number(id), { url: link, ...title ? { title } : {} });
|
|
12039
12061
|
return { id: out.id, pages: out.pages, pictures: out.pictures, carousel: out.carousel ?? null };
|
|
12040
12062
|
}
|
|
12041
12063
|
}),
|
|
12064
|
+
tool({
|
|
12065
|
+
name: "social_upload_link",
|
|
12066
|
+
description: "A link that opens one post's picture in the CMO.so app, where the user uploads their own photos (up to 20, a carousel on LinkedIn) or, on a computer, a PDF carousel themselves. Use it when you can't pass the files (this chat can't send files to tools, or they are on the user's device). Give the link; nothing changes until they upload.",
|
|
12067
|
+
parameters: {
|
|
12068
|
+
type: "object",
|
|
12069
|
+
properties: { id: { type: "integer", description: "The post (from social_list), not gone out yet" } },
|
|
12070
|
+
required: ["id"]
|
|
12071
|
+
},
|
|
12072
|
+
group: "social",
|
|
12073
|
+
need: read5,
|
|
12074
|
+
risk: "R0",
|
|
12075
|
+
run: async (ctx, { id }) => {
|
|
12076
|
+
const post = await ws(ctx).socialPost(Number(id));
|
|
12077
|
+
if (["published", "publishing", "cancelled"].includes(String(post.status)))
|
|
12078
|
+
throw new Error("That post has already gone out (or was skipped): its pictures can't change.");
|
|
12079
|
+
const site = ctx.domain ? `&site=${encodeURIComponent(ctx.domain)}` : "";
|
|
12080
|
+
return {
|
|
12081
|
+
id: post.id,
|
|
12082
|
+
channel: post.provider ?? null,
|
|
12083
|
+
url: `https://app.cmo.so/social?picture=${post.id}${site}`,
|
|
12084
|
+
how: post.provider === "linkedin" ? "Open it, then Upload several photos (in order), or on a computer Upload a PDF carousel." : "Open it, then Upload several photos (in order)."
|
|
12085
|
+
};
|
|
12086
|
+
}
|
|
12087
|
+
}),
|
|
12042
12088
|
tool({
|
|
12043
12089
|
name: "social_carousel_set",
|
|
12044
12090
|
description: "How a LinkedIn post with a set of pictures goes out: format pdf (a swipeable PDF carousel made from the pictures, the default) or images (several pictures); and the carousel's title. Preview first.",
|
|
@@ -23167,6 +23213,30 @@ function description(t) {
|
|
|
23167
23213
|
].filter(Boolean);
|
|
23168
23214
|
return `${t.description}${notes.length ? ` (${notes.join("; ")}.)` : ""} Changes data: call without confirm for a preview, then with confirm: true and the confirmation after the user agrees.`;
|
|
23169
23215
|
}
|
|
23216
|
+
function fileMeta(t) {
|
|
23217
|
+
return t.fileParams?.length ? { "openai/fileParams": t.fileParams } : void 0;
|
|
23218
|
+
}
|
|
23219
|
+
function withFreshFiles(t, signed, now) {
|
|
23220
|
+
if (!t.fileParams?.length) return signed;
|
|
23221
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
23222
|
+
const collect = (v) => {
|
|
23223
|
+
for (const f of Array.isArray(v) ? v : [v])
|
|
23224
|
+
if (f && typeof f === "object" && typeof f.file_id === "string")
|
|
23225
|
+
fresh.set(f.file_id, f);
|
|
23226
|
+
};
|
|
23227
|
+
for (const p of t.fileParams) collect(now[p]);
|
|
23228
|
+
const swap = (v) => {
|
|
23229
|
+
if (Array.isArray(v)) return v.map(swap);
|
|
23230
|
+
if (v && typeof v === "object" && typeof v.file_id === "string") {
|
|
23231
|
+
const f = fresh.get(v.file_id);
|
|
23232
|
+
return f && typeof f.download_url === "string" ? { ...v, download_url: f.download_url } : v;
|
|
23233
|
+
}
|
|
23234
|
+
return v;
|
|
23235
|
+
};
|
|
23236
|
+
const out = { ...signed };
|
|
23237
|
+
for (const p of t.fileParams) if (p in out) out[p] = swap(out[p]);
|
|
23238
|
+
return out;
|
|
23239
|
+
}
|
|
23170
23240
|
function toolArgs(args) {
|
|
23171
23241
|
const out = {};
|
|
23172
23242
|
for (const [k, v] of Object.entries(args ?? {})) if (!CONTROL_ARGS.includes(k) && v !== void 0) out[k] = v;
|
|
@@ -23296,7 +23366,7 @@ function createMcpServer(deps) {
|
|
|
23296
23366
|
description: description(t),
|
|
23297
23367
|
inputSchema: inputSchema(t),
|
|
23298
23368
|
annotations: { title: t.name.replace(/_/g, " "), ...annotations(t) },
|
|
23299
|
-
...deps.widgets && toolMeta(t.name) ? { _meta: toolMeta(t.name) } : {}
|
|
23369
|
+
...fileMeta(t) || deps.widgets && toolMeta(t.name) ? { _meta: { ...deps.widgets ? toolMeta(t.name) : {}, ...fileMeta(t) } } : {}
|
|
23300
23370
|
}))
|
|
23301
23371
|
};
|
|
23302
23372
|
});
|
|
@@ -23332,7 +23402,7 @@ function createMcpServer(deps) {
|
|
|
23332
23402
|
return fail(
|
|
23333
23403
|
access.keyAccess === "read" && isWrite(t) ? "This key can only read. Reconnect with write access to make changes." : `You don't have permission to do this on ${domain ?? "this website"}.`
|
|
23334
23404
|
);
|
|
23335
|
-
const callArgs = approval ? approval.calls[0]?.args ?? {} : toolArgs(args);
|
|
23405
|
+
const callArgs = approval ? withFreshFiles(t, approval.calls[0]?.args ?? {}, toolArgs(args)) : toolArgs(args);
|
|
23336
23406
|
const ctx = { api: api2, domain, client: "mcp" };
|
|
23337
23407
|
if (!isWrite(t)) return ok(await t.run(ctx, callArgs), viewForTool(t.name) ? { workspace: domain } : void 0);
|
|
23338
23408
|
let standing = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmo-so/mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
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",
|