# 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,
});
}
```