aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-04-13 12:29:57 +0000
committerMohamed Bassem <me@mbassem.com>2025-04-13 12:31:10 +0000
commite5bacda644ae8b421e9b056bcde4fd57548899c1 (patch)
tree195112503b5b543350b6ee6654b1a0a47d4b9d66 /apps
parent1f92ced36ec0af11ec0d10bee6aa2474b46462b6 (diff)
downloadkarakeep-e5bacda644ae8b421e9b056bcde4fd57548899c1.tar.zst
fix(mcp): Reduce token usage of the MCP server
Diffstat (limited to 'apps')
-rw-r--r--apps/mcp/package.json2
-rw-r--r--apps/mcp/src/bookmarks.ts129
-rw-r--r--apps/mcp/src/shared.ts7
3 files changed, 131 insertions, 7 deletions
diff --git a/apps/mcp/package.json b/apps/mcp/package.json
index 5e41dd18..7fa4e9c4 100644
--- a/apps/mcp/package.json
+++ b/apps/mcp/package.json
@@ -18,6 +18,7 @@
"@karakeep/prettier-config": "workspace:^0.1.0",
"@karakeep/tsconfig": "workspace:^0.1.0",
"@tsconfig/node22": "^22.0.0",
+ "@types/turndown": "^5.0.5",
"shx": "^0.4.0",
"tsx": "^4.7.1",
"vite": "^5.1.0"
@@ -46,6 +47,7 @@
"dependencies": {
"@karakeep/sdk": "workspace:*",
"@modelcontextprotocol/sdk": "^1.9.0",
+ "turndown": "^7.2.0",
"zod": "^3.24.2"
}
}
diff --git a/apps/mcp/src/bookmarks.ts b/apps/mcp/src/bookmarks.ts
index bcafba91..04e4480d 100644
--- a/apps/mcp/src/bookmarks.ts
+++ b/apps/mcp/src/bookmarks.ts
@@ -1,7 +1,89 @@
import { CallToolResult } from "@modelcontextprotocol/sdk/types";
import { z } from "zod";
-import { karakeepClient, mcpServer, toMcpToolError } from "./shared";
+import { KarakeepAPISchemas } from "@karakeep/sdk";
+
+import {
+ karakeepClient,
+ mcpServer,
+ toMcpToolError,
+ turndownService,
+} from "./shared";
+
+interface CompactBookmark {
+ id: string;
+ createdAt: string;
+ title: string;
+ summary: string;
+ note: string;
+ content:
+ | {
+ type: "link";
+ url: string;
+ description: string;
+ author: string;
+ publisher: string;
+ }
+ | {
+ type: "text";
+ sourceUrl: string;
+ }
+ | {
+ type: "media";
+ assetId: string;
+ assetType: string;
+ sourceUrl: string;
+ }
+ | {
+ type: "unknown";
+ };
+ tags: string[];
+}
+
+function compactBookmark(
+ bookmark: KarakeepAPISchemas["Bookmark"],
+): CompactBookmark {
+ let content: CompactBookmark["content"];
+ if (bookmark.content.type === "link") {
+ content = {
+ type: "link",
+ url: bookmark.content.url,
+ description: bookmark.content.description ?? "",
+ author: bookmark.content.author ?? "",
+ publisher: bookmark.content.publisher ?? "",
+ };
+ } else if (bookmark.content.type === "text") {
+ content = {
+ type: "text",
+ sourceUrl: bookmark.content.sourceUrl ?? "",
+ };
+ } else if (bookmark.content.type === "asset") {
+ content = {
+ type: "media",
+ assetId: bookmark.content.assetId,
+ assetType: bookmark.content.assetType,
+ sourceUrl: bookmark.content.sourceUrl ?? "",
+ };
+ } else {
+ content = {
+ type: "unknown",
+ };
+ }
+
+ return {
+ id: bookmark.id,
+ createdAt: bookmark.createdAt,
+ title: bookmark.title
+ ? bookmark.title
+ : ((bookmark.content.type === "link"
+ ? bookmark.content.title
+ : undefined) ?? ""),
+ summary: bookmark.summary ?? "",
+ note: bookmark.note ?? "",
+ content,
+ tags: bookmark.tags.map((t) => t.name),
+ };
+}
// Tools
mcpServer.tool(
@@ -44,7 +126,7 @@ machine learning is:fav`),
return {
content: res.data.bookmarks.map((bookmark) => ({
type: "text",
- text: JSON.stringify(bookmark),
+ text: JSON.stringify(compactBookmark(bookmark)),
})),
};
},
@@ -71,7 +153,7 @@ mcpServer.tool(
content: [
{
type: "text",
- text: JSON.stringify(res.data),
+ text: JSON.stringify(compactBookmark(res.data)),
},
],
};
@@ -100,7 +182,7 @@ mcpServer.tool(
content: [
{
type: "text",
- text: JSON.stringify(res.data),
+ text: JSON.stringify(compactBookmark(res.data)),
},
],
};
@@ -129,7 +211,44 @@ mcpServer.tool(
content: [
{
type: "text",
- text: JSON.stringify(res.data),
+ text: JSON.stringify(compactBookmark(res.data)),
+ },
+ ],
+ };
+ },
+);
+
+mcpServer.tool(
+ "get-bookmark-content",
+ `Get a bookmark content.`,
+ {
+ bookmarkId: z.string().describe(`The bookmarkId to get content for.`),
+ },
+ async ({ bookmarkId }): Promise<CallToolResult> => {
+ const res = await karakeepClient.GET(`/bookmarks/{bookmarkId}`, {
+ params: {
+ path: {
+ bookmarkId,
+ },
+ },
+ });
+ if (res.error) {
+ return toMcpToolError(res.error);
+ }
+ let content;
+ if (res.data.content.type === "link") {
+ const htmlContent = res.data.content.htmlContent;
+ content = turndownService.turndown(htmlContent);
+ } else if (res.data.content.type === "text") {
+ content = res.data.content.text;
+ } else if (res.data.content.type === "asset") {
+ content = "";
+ }
+ return {
+ content: [
+ {
+ type: "text",
+ text: content ?? "",
},
],
};
diff --git a/apps/mcp/src/shared.ts b/apps/mcp/src/shared.ts
index 69672769..2c553d17 100644
--- a/apps/mcp/src/shared.ts
+++ b/apps/mcp/src/shared.ts
@@ -1,12 +1,13 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { CallToolResult } from "@modelcontextprotocol/sdk/types";
+import TurndownService from "turndown";
-import { createHoarderClient } from "@karakeep/sdk";
+import { createKarakeepClient } from "@karakeep/sdk";
const addr = process.env.KARAKEEP_API_ADDR;
const apiKey = process.env.KARAKEEP_API_KEY;
-export const karakeepClient = createHoarderClient({
+export const karakeepClient = createKarakeepClient({
baseUrl: `${addr}/api/v1`,
headers: {
"Content-Type": "application/json",
@@ -19,6 +20,8 @@ export const mcpServer = new McpServer({
version: "0.23.0",
});
+export const turndownService = new TurndownService();
+
export function toMcpToolError(
error: { code: string; message: string } | undefined,
): CallToolResult {