# Directory Structure ``` ├── .gitignore ├── chatbot │ ├── .gitignore │ ├── data │ │ └── install.sql │ ├── package.json │ ├── pnpm-lock.yaml │ ├── README_CN.md │ ├── README.md │ └── src │ ├── db.ts │ ├── index.ts │ ├── message.ts │ ├── types.d.ts │ └── utils.ts ├── package.json ├── pnpm-lock.yaml ├── preview.png ├── README_CN.md ├── README.md ├── src │ ├── index.ts │ ├── models │ │ ├── chat_message.ts │ │ └── db.ts │ ├── services │ │ ├── chat_message.ts │ │ └── tools.ts │ └── types │ └── chat_message.d.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` node_modules/ build/ *.log .env* data/* .vscode/* ``` -------------------------------------------------------------------------------- /chatbot/.gitignore: -------------------------------------------------------------------------------- ``` node_modules/ .DS_Store data/chat.db .vscode/* .env* ``` -------------------------------------------------------------------------------- /chatbot/README.md: -------------------------------------------------------------------------------- ```markdown # chatbot Save your chat messages to local sqlite database. [中文说明](README_CN.md) ## Prerequisites 1. Install `sqlite3` in your local machine. for macos: ```shell brew install sqlite3 ``` 2. Set your environment variables. create `.env` file in the root directory, and set your chat database path. ```txt CHAT_DB_PATH=path-to/data/chat.db ``` 3. Init chat database. connect to your chat database with `sqlite3` command ```shell sqlite3 path-to/data/chat.db ``` create table `chat_messages` with `install.sql`. ```sql CREATE TABLE chat_messages ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, created_at INTEGER NOT NULL, msg_id TEXT NOT NULL, room_id TEXT, room_name TEXT, room_avatar TEXT, talker_id TEXT NOT NULL, talker_name TEXT, talker_avatar TEXT, content TEXT, msg_type INTEGER, url_title TEXT, url_desc TEXT, url_link TEXT, url_thumb TEXT ); ``` ## Run chatbot 1. Install dependencies. ```shell pnpm install ``` 2. Start chatbot. ```shell pnpm start ``` 3. Login with your WeChat scan the QR code with your WeChat app. Let chatbot auto receive and save chat messages. > **Attention:** > > - chatbot use `wechaty` with `wechaty-puppet-wechat4u` to run RPA. > - it may be blocked by WeChat. Be careful with your WeChat account. ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # mcp-server-chatsum This MCP Server is used to summarize your chat messages. [中文说明](README_CN.md)  > **Before you start** > > move to [chatbot](./chatbot) directory, follow the [README](./chatbot/README.md) to setup the chat database. > > start chatbot to save your chat messages. ## Features ### Resources ### Tools - `query_chat_messages` - Query chat messages - Query chat messages with given parameters - Summarize chat messages based on the query prompt ### Prompts ## Development 1. Set up environment variables: create `.env` file in the root directory, and set your chat database path. ```txt CHAT_DB_PATH=path-to/chatbot/data/chat.db ``` 2. Install dependencies: ```bash pnpm install ``` Build the server: ```bash pnpm build ``` For development with auto-rebuild: ```bash pnpm watch ``` ## Installation To use with Claude Desktop, add the server config: On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` On Windows: `%APPDATA%/Claude/claude_desktop_config.json` ```json { "mcpServers": { "mcp-server-chatsum": { "command": "path-to/bin/node", "args": ["path-to/mcp-server-chatsum/build/index.js"], "env": { "CHAT_DB_PATH": "path-to/mcp-server-chatsum/chatbot/data/chat.db" } } } } ``` ### Debugging Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script: ```bash pnpm inspector ``` The Inspector will provide a URL to access debugging tools in your browser. ## Community - [MCP Server Telegram](https://t.me/+N0gv4O9SXio2YWU1) - [MCP Server Discord](https://discord.gg/RsYPRrnyqg) ## About the author - [idoubi](https://bento.me/idoubi) ``` -------------------------------------------------------------------------------- /chatbot/src/types.d.ts: -------------------------------------------------------------------------------- ```typescript export interface ChatMessage { created_at: number; msg_id: string; room_id?: string; room_name?: string; room_avatar?: string; talker_id: string; talker_name?: string; talker_avatar?: string; content?: string; msg_type: number; url_title?: string; url_desc?: string; url_link?: string; url_thumb?: string; } ``` -------------------------------------------------------------------------------- /src/types/chat_message.d.ts: -------------------------------------------------------------------------------- ```typescript export interface ChatMessage { created_at: number; msg_id: string; room_id?: string; room_name?: string; room_avatar?: string; talker_id: string; talker_name?: string; talker_avatar?: string; content?: string; msg_type: number; url_title?: string; url_desc?: string; url_link?: string; url_thumb?: string; } ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /src/models/db.ts: -------------------------------------------------------------------------------- ```typescript import sqlite3 from "sqlite3"; export function getDb(): sqlite3.Database { const dbName = process.env.CHAT_DB_PATH || ""; if (!dbName) { throw new Error("CHAT_DB_PATH is not set"); } const db = new sqlite3.Database(dbName, (err) => { if (err) { console.error("chat db connect failed: ", dbName, err.message); return; } }); return db; } ``` -------------------------------------------------------------------------------- /chatbot/data/install.sql: -------------------------------------------------------------------------------- ```sql CREATE TABLE chat_messages ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, created_at INTEGER NOT NULL, msg_id TEXT NOT NULL, room_id TEXT, room_name TEXT, room_avatar TEXT, talker_id TEXT NOT NULL, talker_name TEXT, talker_avatar TEXT, content TEXT, msg_type INTEGER, url_title TEXT, url_desc TEXT, url_link TEXT, url_thumb TEXT ); ``` -------------------------------------------------------------------------------- /chatbot/src/message.ts: -------------------------------------------------------------------------------- ```typescript import * as PUPPET from "wechaty-puppet"; import { MessageInterface } from "wechaty/impls"; import { parseChatMessage } from "./utils"; import { saveChatMessage } from "./db"; export async function handleReceiveMessage(msg: MessageInterface) { try { console.log("receive message: ", msg); const m = await parseChatMessage(msg); if ( m.msg_type === PUPPET.types.Message.Text || m.msg_type === PUPPET.types.Message.Url ) { saveChatMessage(m); } } catch (e) { console.log("parse chat message failed: ", e); } } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "mcp-server-chatsum", "version": "0.1.0", "description": "Summarize your chat messages.", "private": true, "type": "module", "bin": { "mcp-server-chatsum": "./build/index.js" }, "files": [ "build" ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "0.6.0", "dotenv": "^16.4.6", "sqlite3": "^5.1.7" }, "devDependencies": { "@types/node": "^20.17.9", "typescript": "^5.3.3" } } ``` -------------------------------------------------------------------------------- /src/services/tools.ts: -------------------------------------------------------------------------------- ```typescript export const queryChatMessagesTool = { name: "query_chat_messages", description: "query chat messages with given parameters", inputSchema: { type: "object", properties: { room_names: { type: "array", description: "chat room names", items: { type: "string", description: "chat room name", }, }, talker_names: { type: "array", description: "talker names", items: { type: "string", description: "talker name", }, }, limit: { type: "number", description: "chat messages limit", default: 100, }, }, required: [], }, }; ``` -------------------------------------------------------------------------------- /chatbot/package.json: -------------------------------------------------------------------------------- ```json { "name": "mcp-server-chatsum-bot", "version": "1.0.0", "description": "chatbot to save chat messages.", "main": "index.ts", "scripts": { "start": "ts-node src/index.ts" }, "repository": { "type": "git", "url": "git+https://github.com/mcpservers/mcp-server-chatsum.git" }, "keywords": [], "author": "", "bugs": { "url": "https://github.com/mcpservers/mcp-server-chatsum/issues" }, "homepage": "https://github.com/mcpservers/mcp-server-chatsum#readme", "dependencies": { "dotenv": "^16.4.7", "qrcode-terminal": "^0.12.0", "sqlite3": "^5.1.6", "ts-node": "^10.9.2", "wechaty": "^1.20.2", "wechaty-puppet": "^1.20.2", "wechaty-puppet-padlocal": "^1.20.1" }, "devDependencies": { "@types/qrcode-terminal": "^0.12.0" } } ``` -------------------------------------------------------------------------------- /src/services/chat_message.ts: -------------------------------------------------------------------------------- ```typescript import { ChatMessage } from "../types/chat_message.js"; import { queryChatMessages } from "../models/chat_message.js"; export async function getChatMessages(params: any): Promise<ChatMessage[]> { const room_names: string[] = []; const talker_names: string[] = []; let page = 1; let limit = 100; if (params.limit && params.limit > 0 && params.limit < 1000) { limit = params.limit; } if (params.room_names) { if (Array.isArray(params.room_names)) { room_names.push(...params.room_names); } else { room_names.push(params.room_names); } } if (params.talker_names) { if (Array.isArray(params.talker_names)) { talker_names.push(...params.talker_names); } else { talker_names.push(params.talker_names); } } try { const result: ChatMessage[] = await queryChatMessages({ room_names, talker_names, page, limit, }); return result; } catch (error) { console.error("get chat messages failed: ", error); return []; } } ``` -------------------------------------------------------------------------------- /chatbot/src/index.ts: -------------------------------------------------------------------------------- ```typescript import { ScanStatus, WechatyBuilder } from "wechaty"; import QrcodeTerminal from "qrcode-terminal"; import dotenv from "dotenv"; import { handleReceiveMessage } from "./message"; dotenv.config(); const token = ""; const bot = WechatyBuilder.build({ puppet: "wechaty-puppet-wechat4u", // puppet: 'wechaty-puppet-service', // puppet: "wechaty-puppet-padlocal", puppetOptions: { token, timeoutSeconds: 60, tls: { disable: true, // currently we are not using TLS since most puppet-service versions does not support it. See: https://github.com/wechaty/puppet-service/issues/160 }, }, }); bot .on("scan", (qrcode, status, data) => { console.log(` ============================================================ qrcode : ${qrcode}, status: ${status}, data: ${data} ============================================================ `); if (status === ScanStatus.Waiting) { QrcodeTerminal.generate(qrcode, { small: true, }); } }) .on("login", (user) => { console.log(` ============================================ user: ${JSON.stringify(user)}, friend: ${user.friend()}, ${user.coworker()} ============================================ `); }) .on("message", handleReceiveMessage) .on("error", (err) => { console.log(err); }); bot.start(); ``` -------------------------------------------------------------------------------- /src/models/chat_message.ts: -------------------------------------------------------------------------------- ```typescript import { ChatMessage } from "../types/chat_message.js"; import { getDb } from "./db.js"; export async function queryChatMessages({ room_names, talker_names, page = 1, limit = 100, }: { room_names?: string[]; talker_names?: string[]; page?: number; limit?: number; }): Promise<ChatMessage[]> { try { const db = getDb(); if (!db) { throw new Error("db is not connected"); } const offset = (page - 1) * limit; let sql = `SELECT * FROM chat_messages`; let values: any[] = []; if (room_names && room_names.length > 0) { sql += ` WHERE room_name IN (${room_names.map(() => "?").join(",")})`; values.push(...room_names); } if (talker_names && talker_names.length > 0) { sql += ` WHERE talker_name IN (${talker_names.map(() => "?").join(",")})`; values.push(...talker_names); } sql += ` ORDER BY created_at DESC LIMIT ? OFFSET ?`; values.push(limit, offset); console.error("query chat messages sql: ", sql, values); return new Promise((resolve, reject) => { db.all(sql, values, (err, rows) => { if (err) { console.error("query chat messages failed: ", err); reject(err); } else { resolve(rows as ChatMessage[]); } }); }); } catch (error) { console.error("query chat messages failed: ", error); throw error; } } ``` -------------------------------------------------------------------------------- /chatbot/src/db.ts: -------------------------------------------------------------------------------- ```typescript import { ChatMessage } from "./types"; import sqlite3 from "sqlite3"; export async function saveChatMessage(msg: ChatMessage) { try { const db = getDb(); if (!db) { throw new Error("db is not connected"); } const { msg_type, msg_id, created_at, room_id, room_name, room_avatar, talker_id, talker_name, talker_avatar, content, url_title, url_desc, url_link, url_thumb, } = msg; let sql = `INSERT INTO chat_messages( created_at, msg_id, room_id, room_name, room_avatar, talker_id, talker_name, talker_avatar, content, msg_type, url_title, url_desc, url_link, url_thumb ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)`; db.run(sql, [ created_at, msg_id, room_id, room_name, room_avatar, talker_id, talker_name, talker_avatar, content, msg_type, url_title, url_desc, url_link, url_thumb, ]); console.error("save message ok"); } catch (error) { console.error("save message failed: ", error); throw error; } } export function getDb(): sqlite3.Database { const dbName = process.env.CHAT_DB_PATH || ""; if (!dbName) { throw new Error("CHAT_DB_PATH is not set"); } const db = new sqlite3.Database(dbName, (err) => { if (err) { console.error("chat db connect failed: ", dbName, err.message); return; } }); return db; } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { ChatMessage } from "./types/chat_message.js"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import dotenv from "dotenv"; import { getChatMessages } from "./services/chat_message.js"; import { queryChatMessagesTool } from "./services/tools.js"; const server = new Server( { name: "mcp-server-chatsum", version: "0.1.0", }, { capabilities: { resources: {}, tools: {}, prompts: {}, }, } ); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [queryChatMessagesTool], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { console.error("call tool request:", request); switch (request.params.name) { case "query_chat_messages": { const messages: ChatMessage[] = await getChatMessages( request.params.arguments ); console.error("query chat messages result:", messages); return { content: [ { type: "text", text: JSON.stringify(messages), }, ], }; } default: throw new Error("Unknown tool"); } }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } dotenv.config(); main().catch((error) => { console.error("Server error:", error); process.exit(1); }); ``` -------------------------------------------------------------------------------- /chatbot/src/utils.ts: -------------------------------------------------------------------------------- ```typescript import * as PUPPET from "wechaty-puppet"; import { ChatMessage } from "./types"; import { MessageInterface } from "wechaty/impls"; export async function parseChatMessage( msg: MessageInterface ): Promise<ChatMessage> { const msg_type = msg.type(); const msg_id = msg.id; const payload = msg.payload; const talker = msg.talker(); if (!msg_id || !payload || !talker) { console.log("invalid msg: ", msg); return Promise.reject("invalid msg"); } let room_id = ""; let room_name = ""; let room_avatar = ""; const room = msg.room(); if (room) { room_id = room.id; room_name = (await room.topic()).trim(); room_avatar = room.payload?.avatar || ""; } const talker_id = talker.id; const talker_name = talker.name().trim(); const talker_avatar = talker.payload?.avatar; const created_at = payload.timestamp; let content = ""; let url_title = ""; let url_desc = ""; let url_link = ""; let url_thumb = ""; switch (msg_type) { case PUPPET.types.Message.Text: content = msg.text().trim(); break; case PUPPET.types.Message.Url: const urlMsg = await msg.toUrlLink(); url_title = urlMsg.title(); url_desc = urlMsg.description() || ""; url_link = urlMsg.url(); url_thumb = urlMsg.thumbnailUrl() || ""; break; default: console.log("msg type not support"); return Promise.reject(`msg type not support: ${msg_type}`); } return Promise.resolve({ msg_type, msg_id, created_at, talker_id, talker_name, talker_avatar, room_id, room_name, room_avatar, content, url_title, url_desc, url_link, url_thumb, }); } ```