npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@powercess/qq-mcp

v0.3.0

Published

MCP server that reads QQ (Windows NTQQ) chat records, contacts and database schema from local data — offline, no native dependencies.

Readme

qq-mcp

MCP server that reads QQ (Windows NTQQ) local chat records, contacts and database schema offline — the outcome of a reverse-engineering session against NTQQ 9.9.35.52892 (full findings: docs/reverse-engineering.md).

TypeScript + Node.js (@modelcontextprotocol/sdk), stdio transport, no native dependencies: the SQLCipher page cipher is implemented in this repo.

What works

| Tool | Mode | What it does | |---|---|---| | list_qq_databases | offline | Enumerates nt_qq/nt_db databases (pages, salt, format version, .material presence) and running QQ PIDs | | capture_offline_key | online¹ | Recovers the device passphrase from QQ's process memory, verified against a .material file or database page | | verify_offline_key | offline | Checks a passphrase against one database, a .material file, or every local database at once | | decrypt_qq_database | offline | Decrypts a database (WAL replayed) into a cached plaintext SQLite file under ~/.qq-mcp/plain, with row counts | | query_chat_messages | offline | Queries messages by domain (c2c, group, c2c_temp, dataline, discuss, service_assistant, ai_assistant), peer/group, time range and keyword; returns typed segments | | query_guild_messages | offline | QQ频道 messages from guild_msg.db, same typed-segment decoding | | query_friend_profiles | offline | Friend profile cards (profile_info_v6: nickname, avatar URL, signature, home page, birthday), the buddy list, and pending add-requests with verification Q&A | | query_files | offline | File records: chat files with local nt_data paths (files_in_chat.db), file-transfer-assistant history with download URLs (file_assistant.db), rich-media index (rich_media.db) | | query_group_details | offline | Group metadata from group_info.db: details (owner, member count/cap), bulletin (公告) text, essence (精华), notifications, member levels, avatars | | query_emoji | offline | Personal favourite emoji (CDN URL + local path), system emoji and subscribed market packages from emoji.db | | query_session_state | offline | Session/UI state in nt_msg.db: @me index, unread counts, pinned sessions, deleted sessions, drafts, hidden sessions, search history, message backups | | query_online_status | offline | Online-status records from misc.db (uid → status text, e.g. 手机在线) | | query_flash_transfer | offline | 闪传/闪照 rows from nt_flash_transfer.db (raw, unmodelled columns) | | query_collection | offline | Collection KV store from collection.db | | search_messages_fts | offline | Keyword search over the FTS message-index content tables (buddy/group/c2c_temp/dataline/discuss/guild) — LIKE over the plain content rows, since QQ's pinyin_letter tokenizer is not available outside QQ | | list_contacts | offline | Friends (QQ, NT uid, name, last activity), groups (number, name, members) and the QQ↔uid map | | list_group_members | offline | Group roster from group_info.db: member QQ, uid, nickname, card, last speak time | | get_db_schema | offline | Decrypts a .material DDL blob and returns the full schema (87 tables for nt_msg.db) |

¹ online = QQ must be logged in for memory scraping. Everything else works with QQ closed, straight off the files on disk. Windows only.

Install

npm install -g @powercess/qq-mcp     # or: npx @powercess/qq-mcp

From a source checkout:

npm install
npm run build
npm test                                     # 23 tests, incl. real-database integration
node scripts/smoke-client.mjs                # list tools
node scripts/smoke-client.mjs query_chat_messages '{ "chat": "group", "groupQq": 123456, "limit": 20 }'

Configure as an MCP server (installed globally):

{
  "mcpServers": {
    "qq-mcp": {
      "command": "npx",
      "args": ["-y", "@powercess/qq-mcp"],
      "env": { "QQ_MCP_PASSPHRASE": "<16-char device passphrase>" }
    }
  }
}

The device passphrase

NTQQ derives database keys in-process; the passphrase is not stored on disk. It is a short ASCII string (16 or 20 bytes) inside the QQ process, and the same passphrase unlocks every database on the device.

Acquire it once, either way:

# 1. automatic: scan a logged-in QQ and verify candidates against known artefacts
node scripts/smoke-client.mjs capture_offline_key '{ "save": true }'   # writes ~/.qq-mcp/qqkey (0600)

# 2. manual: debugger route documented in docs/reverse-engineering.md

Provide it to the server via the passphrase argument, QQ_MCP_PASSPHRASE, or ~/.qq-mcp/qqkey (written by capture_offline_key { "save": true }). Other knobs: QQ_MCP_HOME (runtime directory, default ~/.qq-mcp), QQ_MCP_CACHE_DIR (plaintext cache, default $QQ_MCP_HOME/plain), QQ_MCP_DATA_DIR (extra nt_db directories, ;-separated), QQ_MCP_PYTHON (Python for the memory scanner).

How the decryption works

file  = [1024-byte NTQQ header][page 1][page 2]...
page1 = [16-byte salt][ciphertext][16-byte IV][32-byte tag]
pageN = [ciphertext][16-byte IV][32-byte tag]
key   = PBKDF2-HMAC-SHA512(passphrase, salt, 4000, 32)
data  = AES-256-CBC, IV read from the page tail

Verified byte-exact against SQLCipher 4.12 (same row counts, same quick_check verdict) on a 2.4 GB nt_msg.db. Decryption runs as a streaming transform: ~7 s for 2.4 GB, bounded memory. Committed -wal frames are replayed after their checksum chain is validated; frames past a torn write are dropped. A WAL can hold committed frames for pages past the current file's end (QQ is mid-checkpoint); the WAL's dbSize then wins as the page count and the extra pages are taken from WAL frames, so the plaintext is not truncated.

See docs/reverse-engineering.md for how each constant was pinned down and what is still open.

Limitations (honest)

  • The 32-byte page tag is not authenticated: no standard HMAC layout matched, and decryption does not need it. Forged pages would not be detected.
  • A database that QQ is actively writing to can yield a snapshot that mixes two generations: QQ checkpoints while the 2.4 GB file is being read. The tools report this as sourceChanged instead of presenting a torn read as clean. Close QQ for a guaranteed-consistent snapshot.
  • The layout assumes SQLite's reserved byte is 48 or more (NTQQ writes 80, so the usable page area ends at 4016, before the IV/tag at 4048). A stock SQLite file, which packs cells up to the page end, cannot be re-wrapped by encryptPage without losing its last 48 bytes.
  • Some QQ databases ship damaged index pages (database disk image is malformed on indexed queries); table b-trees are intact. Queries page by primary key, and keyword search decodes protobuf text in JS, so query_chat_messages takes maxScan as a depth guard.
  • guild1.db (QQ频道 module state) uses a separate key domain: the device passphrase does not decrypt it. All other databases share the one passphrase.
  • settings.db on some installs is left malformed by QQ's own write path and cannot be read even after repair attempts.
  • The FTS virtual tables use QQ's custom pinyin_letter tokenizer, which stock SQLite lacks — search_messages_fts searches the plain content tables with LIKE instead of a true tokenized index.
  • Blob fields in query_group_details (bulletin/notify), query_session_state and query_online_status are decoded best-effort into readable strings; unmodelled protobuf field ids are reported alongside.
  • Message content coverage is empirical: text, image, video, file, sticker, contact card, interactive card (ark), nudge (戳一戳), reply, forward, legacy forward, call and system messages are typed. Unmodelled protobuf field ids are reported per segment instead of being dropped — see the open-questions list in the docs.
  • Windows-only; memory scanning requires Python 3 (stdlib only).

Layout

src/sqlcipher.ts   page cipher, key derivation, WAL replay, reconstruction
src/messages.ts    protobuf decoding of message bodies into typed segments
src/offline.ts     database discovery, key resolution, plaintext cache, message domains + file/profile/group/emoji/session queries
src/keyscan.ts     passphrase capture (memory scrape + oracle verification)
src/schema.ts      offline .material DDL decryption
src/index.ts       MCP tool surface
scripts/           scan_memory.py (key candidates), smoke-client.mjs
test/              node:test suites + hermetic fixture builders
docs/              reverse-engineering findings

Privacy

Decrypted databases and the captured key live under ~/.qq-mcp (override with QQ_MCP_HOME), never in the package directory; in a source checkout exports/ and .qqkey are gitignored. Captured chat data and keys are never committed — do not publish them.