@linwheel/mcp-server 0.1.0 → 0.2.0
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 +8 -1
- package/dist/index.js +1 -1
- package/dist/tools/carousels-scheduled.d.ts +3 -0
- package/dist/tools/carousels-scheduled.js +9 -0
- package/dist/tools/carousels-scheduled.js.map +1 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/onboard.d.ts +3 -0
- package/dist/tools/onboard.js +25 -0
- package/dist/tools/onboard.js.map +1 -0
- package/dist/tools/post-carousel-caption.d.ts +3 -0
- package/dist/tools/post-carousel-caption.js +13 -0
- package/dist/tools/post-carousel-caption.js.map +1 -0
- package/dist/tools/post-carousel-compile.d.ts +3 -0
- package/dist/tools/post-carousel-compile.js +12 -0
- package/dist/tools/post-carousel-compile.js.map +1 -0
- package/dist/tools/post-carousel-delete.d.ts +3 -0
- package/dist/tools/post-carousel-delete.js +12 -0
- package/dist/tools/post-carousel-delete.js.map +1 -0
- package/dist/tools/post-carousel-regenerate.d.ts +3 -0
- package/dist/tools/post-carousel-regenerate.js +27 -0
- package/dist/tools/post-carousel-regenerate.js.map +1 -0
- package/dist/tools/voice-extract.d.ts +3 -0
- package/dist/tools/voice-extract.js +41 -0
- package/dist/tools/voice-extract.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -80,7 +80,14 @@ If it works, you're in.
|
|
|
80
80
|
|
|
81
81
|
## 🛠️ Tools
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
24 tools across 6 categories. Claude picks the right one — just describe what you want.
|
|
84
|
+
|
|
85
|
+
### Onboarding
|
|
86
|
+
|
|
87
|
+
| Tool | What it does |
|
|
88
|
+
|------|-------------|
|
|
89
|
+
| `linwheel_onboard` | Guided setup — walks you from zero to your first scheduled post (start here) |
|
|
90
|
+
| `linwheel_voice_extract` | Build a clean voice profile from samples — rejects off-voice / AI-slop outliers |
|
|
84
91
|
|
|
85
92
|
### Content Creation
|
|
86
93
|
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { LinWheelClient } from "./client.js";
|
|
|
5
5
|
import { registerAllTools } from "./tools/index.js";
|
|
6
6
|
const BASE_URL = process.env.LINWHEEL_BASE_URL ?? "https://www.linwheel.io";
|
|
7
7
|
const API_KEY = process.env.LINWHEEL_API_KEY;
|
|
8
|
-
const server = new McpServer({ name: "linwheel", version: "0.
|
|
8
|
+
const server = new McpServer({ name: "linwheel", version: "0.2.0" }, { capabilities: { tools: {} } });
|
|
9
9
|
if (API_KEY) {
|
|
10
10
|
const client = new LinWheelClient({
|
|
11
11
|
baseUrl: BASE_URL,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function register(server, client) {
|
|
2
|
+
server.tool("linwheel_carousels_scheduled", "List all scheduled carousels as calendar items. Returns both post carousels and article carousels with status='scheduled', including parent content references, caption, pageCount, scheduledAt, and autoPublish. Use to see what carousels are queued for publication. ~500 tokens.", {}, async () => {
|
|
3
|
+
const result = await client.get("/api/agent/carousels/scheduled");
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
6
|
+
};
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=carousels-scheduled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"carousels-scheduled.js","sourceRoot":"","sources":["../../src/tools/carousels-scheduled.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,8BAA8B,EAC9B,sRAAsR,EACtR,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAClE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/dist/tools/index.js
CHANGED
|
@@ -14,8 +14,15 @@ import * as postCarousel from "./post-carousel.js";
|
|
|
14
14
|
import * as postCarouselCompanion from "./post-carousel-companion.js";
|
|
15
15
|
import * as postCarouselStatus from "./post-carousel-status.js";
|
|
16
16
|
import * as postCarouselSchedule from "./post-carousel-schedule.js";
|
|
17
|
+
import * as postCarouselCompile from "./post-carousel-compile.js";
|
|
18
|
+
import * as postCarouselRegenerate from "./post-carousel-regenerate.js";
|
|
19
|
+
import * as postCarouselDelete from "./post-carousel-delete.js";
|
|
20
|
+
import * as postCarouselCaption from "./post-carousel-caption.js";
|
|
21
|
+
import * as carouselsScheduled from "./carousels-scheduled.js";
|
|
17
22
|
import * as postComment from "./post-comment.js";
|
|
18
23
|
import * as voiceProfiles from "./voice-profiles.js";
|
|
24
|
+
import * as voiceExtract from "./voice-extract.js";
|
|
25
|
+
import * as onboard from "./onboard.js";
|
|
19
26
|
import * as brandStyles from "./brand-styles.js";
|
|
20
27
|
const ALL_TOOLS = [
|
|
21
28
|
analyze,
|
|
@@ -34,8 +41,15 @@ const ALL_TOOLS = [
|
|
|
34
41
|
postCarouselCompanion,
|
|
35
42
|
postCarouselStatus,
|
|
36
43
|
postCarouselSchedule,
|
|
44
|
+
postCarouselCompile,
|
|
45
|
+
postCarouselRegenerate,
|
|
46
|
+
postCarouselDelete,
|
|
47
|
+
postCarouselCaption,
|
|
48
|
+
carouselsScheduled,
|
|
37
49
|
postComment,
|
|
38
50
|
voiceProfiles,
|
|
51
|
+
voiceExtract,
|
|
52
|
+
onboard,
|
|
39
53
|
brandStyles,
|
|
40
54
|
];
|
|
41
55
|
export function registerAllTools(server, client) {
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,qBAAqB,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,kBAAkB,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,oBAAoB,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,aAAa,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,MAAM,SAAS,GAAG;IAChB,OAAO;IACP,OAAO;IACP,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;IACT,OAAO;IACP,UAAU;IACV,WAAW;IACX,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,qBAAqB;IACrB,kBAAkB;IAClB,oBAAoB;IACpB,WAAW;IACX,aAAa;IACb,WAAW;CACZ,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,MAAsB;IACxE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,qBAAqB,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,kBAAkB,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,oBAAoB,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,mBAAmB,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,sBAAsB,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,kBAAkB,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,mBAAmB,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,kBAAkB,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,aAAa,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,MAAM,SAAS,GAAG;IAChB,OAAO;IACP,OAAO;IACP,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;IACT,OAAO;IACP,UAAU;IACV,WAAW;IACX,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,qBAAqB;IACrB,kBAAkB;IAClB,oBAAoB;IACpB,mBAAmB;IACnB,sBAAsB;IACtB,kBAAkB;IAClB,mBAAmB;IACnB,kBAAkB;IAClB,WAAW;IACX,aAAa;IACb,YAAY;IACZ,OAAO;IACP,WAAW;CACZ,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,MAAsB;IACxE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_onboard", 'Guided onboarding — walks you from zero to your first scheduled LinkedIn post. Call it with no arguments (or step="status") to see exactly where you are and what to do next: it derives your progress from live data (voice profile, drafts, scheduled posts), explains WHY each step matters, and hands back the precise next tool to call. It performs the voice-profile step directly — call with step="voice" and 3+ of your own writing samples to build and activate a voice profile (off-voice/AI-slop samples are dropped automatically). If you are new to LinWheel, start here.', {
|
|
4
|
+
step: z
|
|
5
|
+
.enum(["status", "voice", "compose", "schedule", "done"])
|
|
6
|
+
.optional()
|
|
7
|
+
.describe('Which step to run. Default "status" reports your current state and the recommended next action. "voice" builds a voice profile from `samples`.'),
|
|
8
|
+
samples: z
|
|
9
|
+
.array(z.string())
|
|
10
|
+
.optional()
|
|
11
|
+
.describe('For step="voice": 3+ of your own writing samples (posts, articles) to extract a voice profile from.'),
|
|
12
|
+
voiceName: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('For step="voice": an optional name for the created profile.'),
|
|
16
|
+
}, async (args) => {
|
|
17
|
+
const result = await client.post("/api/agent/onboard", args);
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=onboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/tools/onboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,4jBAA4jB,EAC5jB;QACE,IAAI,EAAE,CAAC;aACJ,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;aACxD,QAAQ,EAAE;aACV,QAAQ,CACP,gJAAgJ,CACjJ;QACH,OAAO,EAAE,CAAC;aACP,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CACP,qGAAqG,CACtG;QACH,SAAS,EAAE,CAAC;aACT,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,6DAA6D,CAAC;KAC3E,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;QAC7D,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aACjE;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_post_carousel_caption", "Update the companion caption text for a post's carousel without regenerating slides. Use this to edit the LinkedIn caption that accompanies the carousel PDF. Returns the updated caption and carouselId. ~100 tokens.", {
|
|
4
|
+
postId: z.string().describe("The post ID whose carousel caption to update"),
|
|
5
|
+
caption: z.string().describe("The new caption text for the carousel"),
|
|
6
|
+
}, async ({ postId, caption }) => {
|
|
7
|
+
const result = await client.put(`/api/agent/posts/${postId}/carousel`, { caption });
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=post-carousel-caption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-carousel-caption.js","sourceRoot":"","sources":["../../src/tools/post-carousel-caption.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,gCAAgC,EAChC,wNAAwN,EACxN;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;QAC3E,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;KACtE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAC7B,oBAAoB,MAAM,WAAW,EACrC,EAAE,OAAO,EAAE,CACZ,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_post_carousel_compile", "Compile carousel slides into a PDF document. Use this when a carousel has slides but no PDF (carouselUrl is null) — typically for carousels created before automatic PDF compilation was added. Returns carouselId, carouselUrl, and pageCount. ~150 tokens. Call linwheel_post_carousel_status first to check if compilation is needed.", {
|
|
4
|
+
postId: z.string().describe("The post ID whose carousel to compile"),
|
|
5
|
+
}, async ({ postId }) => {
|
|
6
|
+
const result = await client.post(`/api/agent/posts/${postId}/carousel/compile`, {});
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=post-carousel-compile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-carousel-compile.js","sourceRoot":"","sources":["../../src/tools/post-carousel-compile.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,gCAAgC,EAChC,0UAA0U,EAC1U;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;KACrE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAC9B,oBAAoB,MAAM,mBAAmB,EAC7C,EAAE,CACH,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_post_carousel_delete", "Delete a carousel from a post entirely. Use this to remove an incorrectly generated or unwanted carousel. Cannot delete published carousels. Returns success confirmation. ~100 tokens.", {
|
|
4
|
+
postId: z.string().describe("The post ID whose carousel to delete"),
|
|
5
|
+
}, async ({ postId }) => {
|
|
6
|
+
const result = await client.delete(`/api/agent/posts/${postId}/carousel`);
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=post-carousel-delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-carousel-delete.js","sourceRoot":"","sources":["../../src/tools/post-carousel-delete.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,+BAA+B,EAC/B,yLAAyL,EACzL;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;KACpE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAChC,oBAAoB,MAAM,WAAW,CACtC,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_post_carousel_regenerate", "Regenerate a single slide in an existing carousel. Use this to fix a typo or rephrase one slide without regenerating the entire carousel. Specify the 0-based slideIndex and the new headlineText. Returns the updated slide. ~200 tokens.", {
|
|
4
|
+
postId: z.string().describe("The post ID whose carousel slide to regenerate"),
|
|
5
|
+
slideIndex: z
|
|
6
|
+
.number()
|
|
7
|
+
.int()
|
|
8
|
+
.min(0)
|
|
9
|
+
.describe("0-based index of the slide to regenerate"),
|
|
10
|
+
headlineText: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("New headline text for the slide"),
|
|
13
|
+
caption: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("New caption for the slide (omit to keep current)"),
|
|
17
|
+
}, async ({ postId, slideIndex, headlineText, caption }) => {
|
|
18
|
+
const body = { slideIndex, headlineText };
|
|
19
|
+
if (caption !== undefined)
|
|
20
|
+
body.caption = caption;
|
|
21
|
+
const result = await client.patch(`/api/agent/posts/${postId}/carousel`, body);
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=post-carousel-regenerate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-carousel-regenerate.js","sourceRoot":"","sources":["../../src/tools/post-carousel-regenerate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,mCAAmC,EACnC,4OAA4O,EAC5O;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;QAC7E,UAAU,EAAE,CAAC;aACV,MAAM,EAAE;aACR,GAAG,EAAE;aACL,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CAAC,0CAA0C,CAAC;QACvD,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,QAAQ,CAAC,iCAAiC,CAAC;QAC9C,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,kDAAkD,CAAC;KAChE,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,EAAE;QACtD,MAAM,IAAI,GAA4B,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;QACnE,IAAI,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAElD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAC/B,oBAAoB,MAAM,WAAW,EACrC,IAAI,CACL,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, client) {
|
|
3
|
+
server.tool("linwheel_voice_extract", "Extract a clean voice profile from writing samples — or re-extract from an existing profile to de-poison it. Runs a validation pass that judges each sample against the dominant voice and REJECTS off-voice outliers (generic/AI-slop/boilerplate, or a different register) so they don't poison few-shot style matching, then synthesizes concrete style notes from the samples that survive. By default it's a dry run that returns per-sample keep/reject verdicts with reasons; set save=true to persist the curated profile. With fromProfileId + updateInPlace=true it cleans an existing profile in place.", {
|
|
4
|
+
samples: z
|
|
5
|
+
.array(z.string())
|
|
6
|
+
.optional()
|
|
7
|
+
.describe("Raw candidate writing samples to extract a voice from."),
|
|
8
|
+
fromProfileId: z
|
|
9
|
+
.string()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("Re-extract from an existing profile's samples (de-poison). Provide this or samples (or both)."),
|
|
12
|
+
save: z
|
|
13
|
+
.boolean()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Persist the curated profile. Default false — a dry run returns verdicts without saving."),
|
|
16
|
+
updateInPlace: z
|
|
17
|
+
.boolean()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("With fromProfileId + save: update that profile in place instead of creating a new one."),
|
|
20
|
+
name: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Name for the saved profile (defaults to the suggested name)."),
|
|
24
|
+
activate: z
|
|
25
|
+
.boolean()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Set the profile as active when saving."),
|
|
28
|
+
minKeep: z
|
|
29
|
+
.number()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Minimum samples to keep (default 2); guards against over-aggressive rejection producing a too-thin profile."),
|
|
32
|
+
}, async (args) => {
|
|
33
|
+
const result = await client.post("/api/agent/voice-profiles/extract", args);
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=voice-extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-extract.js","sourceRoot":"","sources":["../../src/tools/voice-extract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,QAAQ,CAAC,MAAiB,EAAE,MAAsB;IAChE,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,olBAAolB,EACplB;QACE,OAAO,EAAE,CAAC;aACP,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aACjB,QAAQ,EAAE;aACV,QAAQ,CAAC,wDAAwD,CAAC;QACrE,aAAa,EAAE,CAAC;aACb,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,+FAA+F,CAChG;QACH,IAAI,EAAE,CAAC;aACJ,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CACP,yFAAyF,CAC1F;QACH,aAAa,EAAE,CAAC;aACb,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CACP,wFAAwF,CACzF;QACH,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,8DAA8D,CAAC;QAC3E,QAAQ,EAAE,CAAC;aACR,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CAAC,wCAAwC,CAAC;QACrD,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,6GAA6G,CAC9G;KACJ,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAC9B,mCAAmC,EACnC,IAAI,CACL,CAAC;QACF,OAAO;YACL,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;aACjE;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linwheel/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for LinWheel — AI-powered LinkedIn content engine. Draft, schedule, and publish posts from Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"zod": "^3.24.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.5.0",
|
|
24
25
|
"tsx": "^4.19.0",
|
|
25
26
|
"typescript": "^5.9.3",
|
|
26
27
|
"vitest": "^3.2.1"
|