#
tokens: 4740/50000 19/19 files
lines: off (toggle) GitHub
raw markdown copy
# 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)

![preview](./preview.png)

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

```