# 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:
--------------------------------------------------------------------------------
```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
5 | data/*
6 | .vscode/*
```
--------------------------------------------------------------------------------
/chatbot/.gitignore:
--------------------------------------------------------------------------------
```
1 | node_modules/
2 | .DS_Store
3 | data/chat.db
4 | .vscode/*
5 | .env*
```
--------------------------------------------------------------------------------
/chatbot/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # chatbot
2 |
3 | Save your chat messages to local sqlite database.
4 |
5 | [中文说明](README_CN.md)
6 |
7 | ## Prerequisites
8 |
9 | 1. Install `sqlite3` in your local machine.
10 |
11 | for macos:
12 |
13 | ```shell
14 | brew install sqlite3
15 | ```
16 |
17 | 2. Set your environment variables.
18 |
19 | create `.env` file in the root directory, and set your chat database path.
20 |
21 | ```txt
22 | CHAT_DB_PATH=path-to/data/chat.db
23 | ```
24 |
25 | 3. Init chat database.
26 |
27 | connect to your chat database with `sqlite3` command
28 |
29 | ```shell
30 | sqlite3 path-to/data/chat.db
31 | ```
32 |
33 | create table `chat_messages` with `install.sql`.
34 |
35 | ```sql
36 | CREATE TABLE chat_messages (
37 | id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
38 | created_at INTEGER NOT NULL,
39 | msg_id TEXT NOT NULL,
40 | room_id TEXT,
41 | room_name TEXT,
42 | room_avatar TEXT,
43 | talker_id TEXT NOT NULL,
44 | talker_name TEXT,
45 | talker_avatar TEXT,
46 | content TEXT,
47 | msg_type INTEGER,
48 | url_title TEXT,
49 | url_desc TEXT,
50 | url_link TEXT,
51 | url_thumb TEXT
52 | );
53 | ```
54 |
55 | ## Run chatbot
56 |
57 | 1. Install dependencies.
58 |
59 | ```shell
60 | pnpm install
61 | ```
62 |
63 | 2. Start chatbot.
64 |
65 | ```shell
66 | pnpm start
67 | ```
68 |
69 | 3. Login with your WeChat
70 |
71 | scan the QR code with your WeChat app. Let chatbot auto receive and save chat messages.
72 |
73 | > **Attention:**
74 | >
75 | > - chatbot use `wechaty` with `wechaty-puppet-wechat4u` to run RPA.
76 | > - it may be blocked by WeChat. Be careful with your WeChat account.
77 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # mcp-server-chatsum
2 |
3 | This MCP Server is used to summarize your chat messages.
4 |
5 | [中文说明](README_CN.md)
6 |
7 | 
8 |
9 | > **Before you start**
10 | >
11 | > move to [chatbot](./chatbot) directory, follow the [README](./chatbot/README.md) to setup the chat database.
12 | >
13 | > start chatbot to save your chat messages.
14 |
15 | ## Features
16 |
17 | ### Resources
18 |
19 | ### Tools
20 |
21 | - `query_chat_messages` - Query chat messages
22 | - Query chat messages with given parameters
23 | - Summarize chat messages based on the query prompt
24 |
25 | ### Prompts
26 |
27 | ## Development
28 |
29 | 1. Set up environment variables:
30 |
31 | create `.env` file in the root directory, and set your chat database path.
32 |
33 | ```txt
34 | CHAT_DB_PATH=path-to/chatbot/data/chat.db
35 | ```
36 |
37 | 2. Install dependencies:
38 |
39 | ```bash
40 | pnpm install
41 | ```
42 |
43 | Build the server:
44 |
45 | ```bash
46 | pnpm build
47 | ```
48 |
49 | For development with auto-rebuild:
50 |
51 | ```bash
52 | pnpm watch
53 | ```
54 |
55 | ## Installation
56 |
57 | To use with Claude Desktop, add the server config:
58 |
59 | On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
60 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
61 |
62 | ```json
63 | {
64 | "mcpServers": {
65 | "mcp-server-chatsum": {
66 | "command": "path-to/bin/node",
67 | "args": ["path-to/mcp-server-chatsum/build/index.js"],
68 | "env": {
69 | "CHAT_DB_PATH": "path-to/mcp-server-chatsum/chatbot/data/chat.db"
70 | }
71 | }
72 | }
73 | }
74 | ```
75 |
76 | ### Debugging
77 |
78 | 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:
79 |
80 | ```bash
81 | pnpm inspector
82 | ```
83 |
84 | The Inspector will provide a URL to access debugging tools in your browser.
85 |
86 | ## Community
87 |
88 | - [MCP Server Telegram](https://t.me/+N0gv4O9SXio2YWU1)
89 | - [MCP Server Discord](https://discord.gg/RsYPRrnyqg)
90 |
91 | ## About the author
92 |
93 | - [idoubi](https://bento.me/idoubi)
94 |
```
--------------------------------------------------------------------------------
/chatbot/src/types.d.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface ChatMessage {
2 | created_at: number;
3 | msg_id: string;
4 | room_id?: string;
5 | room_name?: string;
6 | room_avatar?: string;
7 | talker_id: string;
8 | talker_name?: string;
9 | talker_avatar?: string;
10 | content?: string;
11 | msg_type: number;
12 | url_title?: string;
13 | url_desc?: string;
14 | url_link?: string;
15 | url_thumb?: string;
16 | }
17 |
```
--------------------------------------------------------------------------------
/src/types/chat_message.d.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface ChatMessage {
2 | created_at: number;
3 | msg_id: string;
4 | room_id?: string;
5 | room_name?: string;
6 | room_avatar?: string;
7 | talker_id: string;
8 | talker_name?: string;
9 | talker_avatar?: string;
10 | content?: string;
11 | msg_type: number;
12 | url_title?: string;
13 | url_desc?: string;
14 | url_link?: string;
15 | url_thumb?: string;
16 | }
17 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"]
15 | }
16 |
```
--------------------------------------------------------------------------------
/src/models/db.ts:
--------------------------------------------------------------------------------
```typescript
1 | import sqlite3 from "sqlite3";
2 |
3 | export function getDb(): sqlite3.Database {
4 | const dbName = process.env.CHAT_DB_PATH || "";
5 | if (!dbName) {
6 | throw new Error("CHAT_DB_PATH is not set");
7 | }
8 |
9 | const db = new sqlite3.Database(dbName, (err) => {
10 | if (err) {
11 | console.error("chat db connect failed: ", dbName, err.message);
12 | return;
13 | }
14 | });
15 |
16 | return db;
17 | }
18 |
```
--------------------------------------------------------------------------------
/chatbot/data/install.sql:
--------------------------------------------------------------------------------
```sql
1 | CREATE TABLE chat_messages (
2 | id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
3 | created_at INTEGER NOT NULL,
4 | msg_id TEXT NOT NULL,
5 | room_id TEXT,
6 | room_name TEXT,
7 | room_avatar TEXT,
8 | talker_id TEXT NOT NULL,
9 | talker_name TEXT,
10 | talker_avatar TEXT,
11 | content TEXT,
12 | msg_type INTEGER,
13 | url_title TEXT,
14 | url_desc TEXT,
15 | url_link TEXT,
16 | url_thumb TEXT
17 | );
```
--------------------------------------------------------------------------------
/chatbot/src/message.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as PUPPET from "wechaty-puppet";
2 |
3 | import { MessageInterface } from "wechaty/impls";
4 | import { parseChatMessage } from "./utils";
5 | import { saveChatMessage } from "./db";
6 |
7 | export async function handleReceiveMessage(msg: MessageInterface) {
8 | try {
9 | console.log("receive message: ", msg);
10 |
11 | const m = await parseChatMessage(msg);
12 |
13 | if (
14 | m.msg_type === PUPPET.types.Message.Text ||
15 | m.msg_type === PUPPET.types.Message.Url
16 | ) {
17 | saveChatMessage(m);
18 | }
19 | } catch (e) {
20 | console.log("parse chat message failed: ", e);
21 | }
22 | }
23 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-server-chatsum",
3 | "version": "0.1.0",
4 | "description": "Summarize your chat messages.",
5 | "private": true,
6 | "type": "module",
7 | "bin": {
8 | "mcp-server-chatsum": "./build/index.js"
9 | },
10 | "files": [
11 | "build"
12 | ],
13 | "scripts": {
14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 | "prepare": "npm run build",
16 | "watch": "tsc --watch",
17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18 | },
19 | "dependencies": {
20 | "@modelcontextprotocol/sdk": "0.6.0",
21 | "dotenv": "^16.4.6",
22 | "sqlite3": "^5.1.7"
23 | },
24 | "devDependencies": {
25 | "@types/node": "^20.17.9",
26 | "typescript": "^5.3.3"
27 | }
28 | }
29 |
```
--------------------------------------------------------------------------------
/src/services/tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const queryChatMessagesTool = {
2 | name: "query_chat_messages",
3 | description: "query chat messages with given parameters",
4 | inputSchema: {
5 | type: "object",
6 | properties: {
7 | room_names: {
8 | type: "array",
9 | description: "chat room names",
10 | items: {
11 | type: "string",
12 | description: "chat room name",
13 | },
14 | },
15 | talker_names: {
16 | type: "array",
17 | description: "talker names",
18 | items: {
19 | type: "string",
20 | description: "talker name",
21 | },
22 | },
23 | limit: {
24 | type: "number",
25 | description: "chat messages limit",
26 | default: 100,
27 | },
28 | },
29 | required: [],
30 | },
31 | };
32 |
```
--------------------------------------------------------------------------------
/chatbot/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-server-chatsum-bot",
3 | "version": "1.0.0",
4 | "description": "chatbot to save chat messages.",
5 | "main": "index.ts",
6 | "scripts": {
7 | "start": "ts-node src/index.ts"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/mcpservers/mcp-server-chatsum.git"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "bugs": {
16 | "url": "https://github.com/mcpservers/mcp-server-chatsum/issues"
17 | },
18 | "homepage": "https://github.com/mcpservers/mcp-server-chatsum#readme",
19 | "dependencies": {
20 | "dotenv": "^16.4.7",
21 | "qrcode-terminal": "^0.12.0",
22 | "sqlite3": "^5.1.6",
23 | "ts-node": "^10.9.2",
24 | "wechaty": "^1.20.2",
25 | "wechaty-puppet": "^1.20.2",
26 | "wechaty-puppet-padlocal": "^1.20.1"
27 | },
28 | "devDependencies": {
29 | "@types/qrcode-terminal": "^0.12.0"
30 | }
31 | }
32 |
```
--------------------------------------------------------------------------------
/src/services/chat_message.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ChatMessage } from "../types/chat_message.js";
2 | import { queryChatMessages } from "../models/chat_message.js";
3 |
4 | export async function getChatMessages(params: any): Promise<ChatMessage[]> {
5 | const room_names: string[] = [];
6 | const talker_names: string[] = [];
7 | let page = 1;
8 | let limit = 100;
9 |
10 | if (params.limit && params.limit > 0 && params.limit < 1000) {
11 | limit = params.limit;
12 | }
13 |
14 | if (params.room_names) {
15 | if (Array.isArray(params.room_names)) {
16 | room_names.push(...params.room_names);
17 | } else {
18 | room_names.push(params.room_names);
19 | }
20 | }
21 |
22 | if (params.talker_names) {
23 | if (Array.isArray(params.talker_names)) {
24 | talker_names.push(...params.talker_names);
25 | } else {
26 | talker_names.push(params.talker_names);
27 | }
28 | }
29 |
30 | try {
31 | const result: ChatMessage[] = await queryChatMessages({
32 | room_names,
33 | talker_names,
34 | page,
35 | limit,
36 | });
37 |
38 | return result;
39 | } catch (error) {
40 | console.error("get chat messages failed: ", error);
41 | return [];
42 | }
43 | }
44 |
```
--------------------------------------------------------------------------------
/chatbot/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ScanStatus, WechatyBuilder } from "wechaty";
2 |
3 | import QrcodeTerminal from "qrcode-terminal";
4 | import dotenv from "dotenv";
5 | import { handleReceiveMessage } from "./message";
6 |
7 | dotenv.config();
8 |
9 | const token = "";
10 | const bot = WechatyBuilder.build({
11 | puppet: "wechaty-puppet-wechat4u",
12 | // puppet: 'wechaty-puppet-service',
13 | // puppet: "wechaty-puppet-padlocal",
14 | puppetOptions: {
15 | token,
16 | timeoutSeconds: 60,
17 | tls: {
18 | disable: true,
19 | // currently we are not using TLS since most puppet-service versions does not support it. See: https://github.com/wechaty/puppet-service/issues/160
20 | },
21 | },
22 | });
23 |
24 | bot
25 | .on("scan", (qrcode, status, data) => {
26 | console.log(`
27 | ============================================================
28 | qrcode : ${qrcode}, status: ${status}, data: ${data}
29 | ============================================================
30 | `);
31 | if (status === ScanStatus.Waiting) {
32 | QrcodeTerminal.generate(qrcode, {
33 | small: true,
34 | });
35 | }
36 | })
37 | .on("login", (user) => {
38 | console.log(`
39 | ============================================
40 | user: ${JSON.stringify(user)}, friend: ${user.friend()}, ${user.coworker()}
41 | ============================================
42 | `);
43 | })
44 | .on("message", handleReceiveMessage)
45 | .on("error", (err) => {
46 | console.log(err);
47 | });
48 |
49 | bot.start();
50 |
```
--------------------------------------------------------------------------------
/src/models/chat_message.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ChatMessage } from "../types/chat_message.js";
2 | import { getDb } from "./db.js";
3 |
4 | export async function queryChatMessages({
5 | room_names,
6 | talker_names,
7 | page = 1,
8 | limit = 100,
9 | }: {
10 | room_names?: string[];
11 | talker_names?: string[];
12 | page?: number;
13 | limit?: number;
14 | }): Promise<ChatMessage[]> {
15 | try {
16 | const db = getDb();
17 | if (!db) {
18 | throw new Error("db is not connected");
19 | }
20 |
21 | const offset = (page - 1) * limit;
22 |
23 | let sql = `SELECT * FROM chat_messages`;
24 | let values: any[] = [];
25 |
26 | if (room_names && room_names.length > 0) {
27 | sql += ` WHERE room_name IN (${room_names.map(() => "?").join(",")})`;
28 | values.push(...room_names);
29 | }
30 |
31 | if (talker_names && talker_names.length > 0) {
32 | sql += ` WHERE talker_name IN (${talker_names.map(() => "?").join(",")})`;
33 | values.push(...talker_names);
34 | }
35 |
36 | sql += ` ORDER BY created_at DESC LIMIT ? OFFSET ?`;
37 | values.push(limit, offset);
38 |
39 | console.error("query chat messages sql: ", sql, values);
40 |
41 | return new Promise((resolve, reject) => {
42 | db.all(sql, values, (err, rows) => {
43 | if (err) {
44 | console.error("query chat messages failed: ", err);
45 | reject(err);
46 | } else {
47 | resolve(rows as ChatMessage[]);
48 | }
49 | });
50 | });
51 | } catch (error) {
52 | console.error("query chat messages failed: ", error);
53 | throw error;
54 | }
55 | }
56 |
```
--------------------------------------------------------------------------------
/chatbot/src/db.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ChatMessage } from "./types";
2 | import sqlite3 from "sqlite3";
3 |
4 | export async function saveChatMessage(msg: ChatMessage) {
5 | try {
6 | const db = getDb();
7 | if (!db) {
8 | throw new Error("db is not connected");
9 | }
10 |
11 | const {
12 | msg_type,
13 | msg_id,
14 | created_at,
15 | room_id,
16 | room_name,
17 | room_avatar,
18 | talker_id,
19 | talker_name,
20 | talker_avatar,
21 | content,
22 | url_title,
23 | url_desc,
24 | url_link,
25 | url_thumb,
26 | } = msg;
27 |
28 | let sql = `INSERT INTO chat_messages(
29 | created_at,
30 | msg_id,
31 | room_id,
32 | room_name,
33 | room_avatar,
34 | talker_id,
35 | talker_name,
36 | talker_avatar,
37 | content,
38 | msg_type,
39 | url_title,
40 | url_desc,
41 | url_link,
42 | url_thumb
43 | ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
44 |
45 | db.run(sql, [
46 | created_at,
47 | msg_id,
48 | room_id,
49 | room_name,
50 | room_avatar,
51 | talker_id,
52 | talker_name,
53 | talker_avatar,
54 | content,
55 | msg_type,
56 | url_title,
57 | url_desc,
58 | url_link,
59 | url_thumb,
60 | ]);
61 |
62 | console.error("save message ok");
63 | } catch (error) {
64 | console.error("save message failed: ", error);
65 | throw error;
66 | }
67 | }
68 |
69 | export function getDb(): sqlite3.Database {
70 | const dbName = process.env.CHAT_DB_PATH || "";
71 | if (!dbName) {
72 | throw new Error("CHAT_DB_PATH is not set");
73 | }
74 |
75 | const db = new sqlite3.Database(dbName, (err) => {
76 | if (err) {
77 | console.error("chat db connect failed: ", dbName, err.message);
78 | return;
79 | }
80 | });
81 |
82 | return db;
83 | }
84 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import {
4 | CallToolRequestSchema,
5 | ListToolsRequestSchema,
6 | } from "@modelcontextprotocol/sdk/types.js";
7 |
8 | import { ChatMessage } from "./types/chat_message.js";
9 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11 | import dotenv from "dotenv";
12 | import { getChatMessages } from "./services/chat_message.js";
13 | import { queryChatMessagesTool } from "./services/tools.js";
14 |
15 | const server = new Server(
16 | {
17 | name: "mcp-server-chatsum",
18 | version: "0.1.0",
19 | },
20 | {
21 | capabilities: {
22 | resources: {},
23 | tools: {},
24 | prompts: {},
25 | },
26 | }
27 | );
28 |
29 | server.setRequestHandler(ListToolsRequestSchema, async () => {
30 | return {
31 | tools: [queryChatMessagesTool],
32 | };
33 | });
34 |
35 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
36 | console.error("call tool request:", request);
37 | switch (request.params.name) {
38 | case "query_chat_messages": {
39 | const messages: ChatMessage[] = await getChatMessages(
40 | request.params.arguments
41 | );
42 | console.error("query chat messages result:", messages);
43 |
44 | return {
45 | content: [
46 | {
47 | type: "text",
48 | text: JSON.stringify(messages),
49 | },
50 | ],
51 | };
52 | }
53 |
54 | default:
55 | throw new Error("Unknown tool");
56 | }
57 | });
58 |
59 | async function main() {
60 | const transport = new StdioServerTransport();
61 | await server.connect(transport);
62 | }
63 |
64 | dotenv.config();
65 |
66 | main().catch((error) => {
67 | console.error("Server error:", error);
68 | process.exit(1);
69 | });
70 |
```
--------------------------------------------------------------------------------
/chatbot/src/utils.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as PUPPET from "wechaty-puppet";
2 |
3 | import { ChatMessage } from "./types";
4 | import { MessageInterface } from "wechaty/impls";
5 |
6 | export async function parseChatMessage(
7 | msg: MessageInterface
8 | ): Promise<ChatMessage> {
9 | const msg_type = msg.type();
10 | const msg_id = msg.id;
11 | const payload = msg.payload;
12 | const talker = msg.talker();
13 |
14 | if (!msg_id || !payload || !talker) {
15 | console.log("invalid msg: ", msg);
16 | return Promise.reject("invalid msg");
17 | }
18 |
19 | let room_id = "";
20 | let room_name = "";
21 | let room_avatar = "";
22 |
23 | const room = msg.room();
24 |
25 | if (room) {
26 | room_id = room.id;
27 | room_name = (await room.topic()).trim();
28 | room_avatar = room.payload?.avatar || "";
29 | }
30 |
31 | const talker_id = talker.id;
32 | const talker_name = talker.name().trim();
33 | const talker_avatar = talker.payload?.avatar;
34 | const created_at = payload.timestamp;
35 |
36 | let content = "";
37 |
38 | let url_title = "";
39 | let url_desc = "";
40 | let url_link = "";
41 | let url_thumb = "";
42 |
43 | switch (msg_type) {
44 | case PUPPET.types.Message.Text:
45 | content = msg.text().trim();
46 | break;
47 | case PUPPET.types.Message.Url:
48 | const urlMsg = await msg.toUrlLink();
49 |
50 | url_title = urlMsg.title();
51 | url_desc = urlMsg.description() || "";
52 | url_link = urlMsg.url();
53 | url_thumb = urlMsg.thumbnailUrl() || "";
54 | break;
55 | default:
56 | console.log("msg type not support");
57 | return Promise.reject(`msg type not support: ${msg_type}`);
58 | }
59 |
60 | return Promise.resolve({
61 | msg_type,
62 | msg_id,
63 | created_at,
64 | talker_id,
65 | talker_name,
66 | talker_avatar,
67 | room_id,
68 | room_name,
69 | room_avatar,
70 | content,
71 | url_title,
72 | url_desc,
73 | url_link,
74 | url_thumb,
75 | });
76 | }
77 |
```