feat(mcp): get_chat_history 加 msg_types 按类型过滤
LLM 用 \`get_chat_history\` 查"和 X 的所有图片消息"时, 只能拉 50 条
混合消息再客户端过滤 —— 大部分 token 浪费在不需要的文本上。同样
"只看转账记录" / "只看语音" 的场景, 没有原生过滤手段。
\`get_chat_history\` 加一个可选 kwarg \`msg_types: list[str] | None = None\`:
- 接受 \`['text', 'image', 'voice', 'video', 'file', 'emoji', 'location',
'namecard', 'voip', 'system']\` 子集
- \`'file'\` 是 alias → 'app' (WeChat 把文件归到 \`local_type=49\`,
俗称 file)
- 输入大小写不敏感, 自动 strip
- 未知类型立即报错并列出可选值 (不偷偷过滤合法部分)
- None 或 \`[]\` 表示不过滤, 完全等价于旧行为 (向后兼容)
实现上拆 3 件:
1. \`_MSG_TYPE_MAP\` 常量 (字符串 → \`local_type\` 整数列表)
2. \`_resolve_msg_types()\` helper 做输入校验 + 翻译
3. \`_build_message_filters\` / \`_query_messages\` /
\`_collect_chat_history_lines\` 链路加 \`type_filter=None\` 透传, SQL
注入 \`local_type IN (?,?,...)\` clause
\`tests/test_msg_types_filter.py\` 12 个 case:
- None / 空 → 不过滤
- 单类型 / 多类型解析
- \`file\` alias → app
- 大小写 + strip 不敏感
- 未知类型报错且不放过合法的
- SQL 生成: 无过滤时 clauses 不含 \`local_type\`, 单类型生成 \`IN (?)\`,
多类型生成 \`IN (?,?,?)\`
- 与 time / keyword 组合时 param 顺序正确
全量 \`pytest tests/\` 212/212 通过。
新参数默认 None, **既有调用方零修改**。
类型映射表 (\`_MSG_TYPE_MAP\`) 命名是有立场的判断 (比如 \`'app'\` 这一
桶实际混了文件 / 分享卡 / 小程序 / 转账 / 引用回复), 如果维护者
不同意具体 label 或想拆细, 改 dict 就行, 不影响接口。
与 #103 (\`_pagination_hint\`) 触碰同一文件 \`mcp_server.py\`, 后合的
rebase 即可, 无逻辑冲突。