IMA 知识库接入 Trae MCP 完整教程

本文档记录了在 Trae IDE 中通过 MCP 协议接入腾讯 IMA 知识库的完整流程,包括踩坑和最终解决方案。
适用环境:Windows + Python 3.14 + Trae IDE
“内容由AI整理生成,如有不当之处请指正!”


一、前置准备

1.1 获取 IMA OpenAPI 凭证

IMA 知识库的 MCP 接入使用官方 OpenAPI 认证,需要两个凭证:

  • Client ID:应用标识
  • API Key:访问密钥

获取地址https://ima.qq.com/agent-interface

操作步骤:

  1. 打开上述网址,登录你的 IMA 账号
  2. 页面会显示你的 Client ID 和 API Key
  3. 复制保存好这两个值(API Key 可能会过期,需定期检查更新)

⚠️ 重要提醒:API Key 会过期!如果调用 API 返回错误码 200002(skill auth failed),说明 Key 已失效,需要回到官网重新获取或刷新。

1.2 安装 Python 环境

确保系统有 Python 3.11+ 环境,并能正常运行。Windows 上用 PowerShell 验证:

python --version

建议记下 Python 的绝对路径(如 D:\workSpace\Python314\python.exe),Trae 配置中需要用到。

1.3 安装依赖包

# 用你的 Python 绝对路径安装
D:\workSpace\Python314\python.exe -m pip install mcp httpx

依赖说明:

  • mcp:官方 MCP Python SDK,提供 FastMCP 框架,正确实现 stdio 协议
  • httpx:HTTP 客户端,用于调用 IMA OpenAPI 接口

二、为什么不能用社区现有的 MCP 包?

2.1踩坑记录

项目 问题
duhanjun/ima-mcp(pip 包) ❌ stdio 实现是自写的简易版,用 json.loads() 直接解析 stdin,不符合 MCP 协议规范。Trae 发送多行 JSON-RPC 消息时解析报错:JSONDecodeError: Extra data
suraimu/tencent-ima-copilot-mcp ❌ 使用浏览器 Cookie/Bkn 认证(需 F12 抓包),Cookie 会过期需反复重新抓取;仅支持 HTTP 传输模式
hdsz25/tencent_ima_mcp ❌ 功能最丰富但仅支持 Cookie 认证,官方 OpenAPI 的 ClientID/APIKey 尚未实现
wangjianghu/ima-note-mcp ❌ 仅支持笔记操作,不支持知识库;要求 macOS + Python 3.13+

核心问题:社区 MCP 包要么 stdio 协议实现不规范(Trae 不兼容),要么认证方式依赖浏览器 Cookie(不稳定),要么功能覆盖不全。

2.2 最终方案:自建 MCP Server

使用官方 mcp Python SDK(FastMCP)自建 Server,正确实现 stdio 协议,使用官方 OpenAPI 凭证认证,最稳定可靠。


三、自建 MCP Server 实现

3.1 源码文件

创建文件 ima_mcp_server.py,内容如下:

"""
IMA Knowledge Base MCP Server
基于官方 mcp SDK 实现 stdio 协议,兼容 Trae / Cursor / Claude Desktop 等IDE
使用 IMA OpenAPI (ClientID + APIKey) 认证
"""

import json
import os
import re
import httpx
from mcp.server.fastmcp import FastMCP

# ============================================================
# 认证配置:从环境变量读取
# ============================================================
IMA_CLIENT_ID = os.environ.get("IMA_OPENAPI_CLIENTID", "")
IMA_API_KEY = os.environ.get("IMA_OPENAPI_APIKEY", "")
IMA_BASE_URL = "https://ima.qq.com"

# ============================================================
# ElementPlus 组件中英文映射表
# 当 search_knowledge 搜中文无结果时,自动用英文关键词重试
# ============================================================
COMPONENT_NAME_MAP = {
    # 常见中文别名 → 英文文件名关键词
    "卡片": "card", "对话框": "dialog", "弹窗": "dialog", "模态框": "dialog",
    "上传": "upload", "图片上传": "upload", "文件上传": "upload",
    "表格": "table", "数据表格": "table",
    "表单": "form", "按钮": "button",
    "输入框": "input", "输入": "input",
    "选择器": "select", "下拉选择": "select", "下拉框": "select",
    "复选框": "checkbox", "多选框": "checkbox",
    "单选框": "radio", "开关": "switch",
    "日期选择器": "date-picker", "日期选择": "date-picker", "日期": "date-picker",
    "时间选择器": "time-picker", "时间选择": "time-picker", "时间": "time-picker",
    "颜色选择器": "color-picker", "颜色": "color-picker",
    "轮播": "carousel", "走马灯": "carousel",
    "折叠面板": "collapse", "折叠": "collapse",
    "容器": "container", "布局容器": "container",
    "下拉菜单": "dropdown", "菜单": "menu",
    "分页": "pagination", "标签页": "tabs", "标签": "tag",
    "树形控件": "tree", "树": "tree", "穿梭框": "transfer",
    "滑块": "slider", "评分": "rate", "进度条": "progress",
    "消息提示": "message", "消息弹框": "message-box",
    "通知": "notification", "气泡确认框": "popconfirm",
    "气泡卡片": "popover", "文字提示": "tooltip",
    "加载": "loading", "骨架屏": "skeleton", "空状态": "empty",
    "结果": "result", "步骤条": "steps", "时间线": "timeline",
    "统计数值": "statistic", "分割线": "divider",
    "描述列表": "descriptions", "导航": "nav", "页头": "page-header",
    "面包屑": "breadcrumb", "头像": "avatar", "日历": "calendar",
    "无限滚动": "infinite-scroll", "虚拟化表格": "table-v2",
    "虚拟化选择器": "select-v2", "分段控制器": "segmented",
    "浮层": "affix", "回到顶部": "backtop",
    "水印": "watermark", "排版": "typography",
    "链接": "link", "间距": "space", "分割": "splitter",
    "引导": "tour", "滚动条": "scrollbar",
    "数字输入框": "input-number", "标签输入框": "input-tag",
    "OTP输入框": "input-otp", "树形选择": "tree-select",
}

# ElXxx 组件名 → 去掉 El 前缀,映射到文件名关键词
# e.g. ElCard → card, ElDialog → dialog
def extract_component_keyword(query: str) -> str | None:
    """从 query 中提取 ElXxx 组件名,转换为英文关键词"""
    m = re.match(r'El([A-Z][a-zA-Z]*)', query)
    if m:
        # ElDatePicker → date-picker
        name = m.group(1)
        # 在驼峰位置插入连字符并转小写
        parts = re.findall(r'[A-Z][a-z]*', name)
        return '-'.join(parts).lower()
    return None

# ============================================================
# MCP Server 实例
# ============================================================
mcp = FastMCP("IMA Knowledge Base")


# ============================================================
# HTTP 请求工具
# ============================================================
def ima_post(endpoint: str, body: dict) -> dict:
    """调用 IMA OpenAPI POST 接口"""
    url = f"{IMA_BASE_URL}/{endpoint}"
    headers = {
        "ima-openapi-clientid": IMA_CLIENT_ID,
        "ima-openapi-apikey": IMA_API_KEY,
        "Content-Type": "application/json",
    }
    with httpx.Client(timeout=30) as client:
        resp = client.post(url, headers=headers, json=body)
        data = resp.json()
        if data.get("code") != 0:
            return {"error": f"API error: code={data.get('code')}, msg={data.get('msg')}"}
        return data.get("data", {})


# ============================================================
# MCP 工具定义
# ============================================================

@mcp.tool()
def search_knowledge_base(query: str = "", limit: int = 20) -> str:
    """搜索用户的知识库列表。可按名称搜索,或传入空字符串列出所有知识库。返回知识库ID、名称、描述等信息。

    Args:
        query: 搜索关键词,空字符串则列出所有知识库
        limit: 返回数量上限,默认20
    """
    result = ima_post("openapi/wiki/v1/search_knowledge_base", {
        "query": query,
        "cursor": "",
        "limit": limit,
    })
    if "error" in result:
        return result["error"]
    info_list = result.get("info_list", [])
    if not info_list:
        return "没有找到知识库"
    lines = []
    for kb in info_list:
        lines.append(
            f"- 名称: {kb.get('kb_name')}\n"
            f"  ID: {kb.get('kb_id')}\n"
            f"  描述: {kb.get('description', '无')}\n"
            f"  内容数: {kb.get('content_count', 0)}\n"
            f"  成员数: {kb.get('member_count', 0)}"
        )
    return "\n\n".join(lines)


@mcp.tool()
def list_knowledge_contents(knowledge_base_id: str, folder_id: str = "", limit: int = 50) -> str:
    """浏览知识库中的文件和文件夹列表。可逐级浏览,不传 folder_id 则列出根目录。

    Args:
        knowledge_base_id: 知识库ID(可通过 search_knowledge_base 获取)
        folder_id: 文件夹ID,空字符串则列出根目录
        limit: 返回数量上限,默认50
    """
    body = {
        "cursor": "",
        "limit": limit,
        "knowledge_base_id": knowledge_base_id,
    }
    if folder_id:
        body["folder_id"] = folder_id
    result = ima_post("openapi/wiki/v1/get_knowledge_list", body)
    if "error" in result:
        return result["error"]
    knowledge_list = result.get("knowledge_list", [])
    if not knowledge_list:
        return "该目录下没有内容"
    lines = []
    for item in knowledge_list:
        type_map = {
            1: "PDF", 2: "网页", 3: "Word", 4: "PPT", 5: "Excel",
            6: "公众号文章", 7: "Markdown", 9: "图片", 11: "笔记",
            99: "文件夹"
        }
        media_type = item.get("media_type", 0)
        type_name = type_map.get(media_type, f"类型{media_type}")
        lines.append(
            f"- {item.get('title')}\n"
            f"  ID: {item.get('media_id')}\n"
            f"  类型: {type_name}\n"
            f"  标签: {', '.join(item.get('tags', [])) or '无'}"
        )
    # 显示当前路径
    current_path = result.get("current_path", [])
    if current_path:
        path_str = " > ".join([p.get("name", "") for p in current_path])
        lines.insert(0, f"📍 当前路径: {path_str}")
    is_end = result.get("is_end", True)
    if not is_end:
        lines.append("⚠️ 还有更多内容,可继续翻页获取")
    return "\n\n".join(lines)


@mcp.tool()
def search_knowledge(query: str, knowledge_base_id: str) -> str:
    """在指定知识库中搜索文件或文件夹。支持中文描述和英文关键词。
    会自动将中文组件名(如"卡片组件"、"对话框")和 ElXxx 名(如 ElCard、ElDialog)
    映射为对应的英文文件名关键词进行搜索。

    Args:
        query: 搜索关键词,支持中文描述(如"卡片"、"对话框")或英文(如"card"、"dialog")
        knowledge_base_id: 目标知识库ID
    """
    # 首先直接搜索
    result = ima_post("openapi/wiki/v1/search_knowledge", {
        "query": query,
        "knowledge_base_id": knowledge_base_id,
    })
    if "error" in result:
        return result["error"]
    info_list = result.get("info_list", [])

    # 如果直接搜索没结果,尝试自动映射
    if not info_list:
        mapped_keywords = []

        # 1. 尝试提取 ElXxx 组件名
        el_keyword = extract_component_keyword(query)
        if el_keyword:
            mapped_keywords.append(el_keyword)

        # 2. 尝试中文→英文映射
        for cn_name, en_name in COMPONENT_NAME_MAP.items():
            if cn_name in query:
                mapped_keywords.append(en_name)

        # 用映射后的关键词重试搜索
        for kw in mapped_keywords:
            retry_result = ima_post("openapi/wiki/v1/search_knowledge", {
                "query": kw,
                "knowledge_base_id": knowledge_base_id,
            })
            if "error" not in retry_result:
                retry_list = retry_result.get("info_list", [])
                if retry_list:
                    info_list = retry_list
                    query = f"{query} → (映射为: {kw})"
                    break

        # 3. 如果中文映射也失败,尝试把 query 分词后用每个词搜
        if not info_list and not mapped_keywords:
            # 简单分词:去掉中文标点和"组件使用方法"等后缀
            cleaned = re.sub(r'(组件|使用|方法|用法|的|El)', '', query)
            words = cleaned.strip().split()
            for word in words:
                if len(word) >= 2:
                    retry_result = ima_post("openapi/wiki/v1/search_knowledge", {
                        "query": word,
                        "knowledge_base_id": knowledge_base_id,
                    })
                    if "error" not in retry_result:
                        retry_list = retry_result.get("info_list", [])
                        if retry_list:
                            info_list = retry_list
                            query = f"{query} → (尝试关键词: {word})"
                            break

    if not info_list:
        return f"在知识库中未找到 '{query}' 相关内容。建议用英文组件名搜索,如 card、dialog、upload 等。"

    type_map = {
        1: "PDF", 2: "网页", 3: "Word", 4: "PPT", 5: "Excel",
        6: "公众号文章", 7: "Markdown", 9: "图片", 11: "笔记",
        99: "文件夹"
    }
    lines = []
    for item in info_list:
        media_type = item.get("media_type", 0)
        type_name = type_map.get(media_type, f"类型{media_type}")
        lines.append(
            f"- {item.get('title')}\n"
            f"  ID: {item.get('media_id')}\n"
            f"  类型: {type_name}\n"
            f"  父文件夹ID: {item.get('parent_folder_id', '根目录')}"
        )
    return "\n\n".join(lines)


@mcp.tool()
def get_knowledge_base_detail(knowledge_base_id: str) -> str:
    """获取指定知识库的详细信息,包括名称、描述、成员数、内容数等。

    Args:
        knowledge_base_id: 知识库ID
    """
    result = ima_post("openapi/wiki/v1/search_knowledge_base", {
        "query": "",
        "cursor": "",
        "limit": 1,
        "knowledge_base_id": knowledge_base_id,
    })
    if "error" in result:
        return result["error"]
    info_list = result.get("info_list", [])
    if not info_list:
        return f"未找到知识库 {knowledge_base_id}"
    kb = info_list[0]
    return (
        f"名称: {kb.get('kb_name')}\n"
        f"ID: {kb.get('kb_id')}\n"
        f"描述: {kb.get('description', '无')}\n"
        f"内容数: {kb.get('content_count', 0)}\n"
        f"成员数: {kb.get('member_count', 0)}\n"
        f"创建者: {kb.get('creator', '未知')}\n"
        f"类型: {kb.get('base_type', '未知')}\n"
        f"你的角色: {kb.get('role_type', '未知')}"
    )


# ============================================================
# 启动
# ============================================================
if __name__ == "__main__":
    if not IMA_CLIENT_ID or not IMA_API_KEY:
        print("ERROR: 请设置环境变量 IMA_OPENAPI_CLIENTID 和 IMA_OPENAPI_APIKEY")
        print("获取方式: https://ima.qq.com/agent-interface")
        exit(1)
    mcp.run(transport="stdio")

3.2 提供的 MCP 工具

工具名 功能 参数
search_knowledge_base 搜索/列出知识库 query(搜索关键词,空=全部),limit
list_knowledge_contents 浏览知识库目录 knowledge_base_id,folder_id(空=根目录),limit
search_knowledge 搜索知识库内文件 query(关键词),knowledge_base_id
get_knowledge_base_detail 知识库详细信息 knowledge_base_id

四、Trae 配置步骤

4.1 复制脚本文件

ima_mcp_server.py 复制到一个固定目录,例如:

mkdir D:\workSpace\mcp
# 把 ima_mcp_server.py 放到这个目录下

4.2 测试脚本能否启动

PowerShell 中执行:

$env:IMA_OPENAPI_CLIENTID="你的ClientID"
$env:IMA_OPENAPI_APIKEY="你的APIKey"
D:\workSpace\Python314\python.exe D:\workSpace\mcp\ima_mcp_server.py

⚠️ PowerShell 用 $env:变量名="值",不是 CMD 的 set 变量名=值

如果启动后出现 JSONRPCMessage 验证错误是正常的——那是你手动敲空行导致的,Trae 通过程序化管道发送不会出现这个问题。

4.3 配置 Trae MCP

打开 Trae → 设置 → MCP → 手动添加,粘贴以下 JSON:

{
  "mcpServers": {
    "ima": {
      "command": "D:\\workSpace\\Python314\\python.exe",
      "args": ["D:\\workSpace\\mcp\\ima_mcp_server.py"],
      "env": {
        "IMA_OPENAPI_CLIENTID": "你的ClientID",
        "IMA_OPENAPI_APIKEY": "你的APIKey"
      }
    }
  }
}

⚠️ 注意事项:

  • JSON 中路径反斜杠要双写D:\D:\\
  • commandargs 都要用绝对路径,Trae 的进程 PATH 和终端不一样
  • 不要用 pythonuvx 这种短命令,容易找不到(ENOENT 错误)
  • 配置完成后需要重启 Trae

4.4 验证连接

重启 Trae 后,在对话窗口测试:

  • “列出我的知识库” → 应返回知识库列表
  • “搜索前端-elementPlus组件知识库里的 button” → 应返回搜索结果

五、常见问题与解决

Q1: spawn uvx ENOENT'ima-mcp' 不是内部或外部命令

原因:Trae 启动进程的 PATH 和终端不同,短命令找不到。

解决:所有路径都用绝对路径,不要用 pythonuvxima-mcp 等短命令。

Q2: JSONDecodeError: Extra data 或 MCP 连接立即断开

原因:社区 ima-mcp pip 包的 stdio 实现不符合 MCP 协议规范——它用 json.loads() 解析 stdin,无法处理多行 JSON-RPC 消息。

解决:使用本文的自建 MCP Server(基于官方 mcp SDK),正确实现 stdio 协议。

Q3: API 返回错误码 200002(skill auth failed)

原因:API Key 过期或失效。

解决

  1. 回到 https://ima.qq.com/agent-interface 检查/重新获取凭证
  2. 更新 Trae 配置中的 IMA_OPENAPI_APIKEY
  3. 重启 Trae

Q4: PowerShell 中 $env: 设置的环境变量重启后丢失

说明$env: 是临时的,仅当前 PowerShell 会话有效。但 Trae 配置中的 env 字段是永久生效的——每次启动 MCP Server 时 Trae 都会注入这些环境变量,所以不需要在系统层面持久设置。

Q5: 想在其他 IDE 中使用(Cursor / Claude Desktop)

同样的 MCP Server 脚本和配置格式适用于 Cursor 和 Claude Desktop,只需调整配置文件位置:

IDE 配置文件位置
Trae 项目级:.trae/mcp.json,或全局:设置 → MCP
Cursor 项目级:.cursor/mcp.json,或全局:设置 → MCP
Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json(macOS)

六、技术要点总结

  1. MCP 协议核心:Trae/Cursor/Claude Desktop 通过 stdio 管道与 MCP Server 通信,使用 JSON-RPC 2.0 协议。Server 必须用官方 SDK 实现,自写 stdin 解析会不兼容。

  2. 认证方式选择:IMA 有两种认证方式:

    • OpenAPI 认证(ClientID + APIKey):稳定,从官网获取,推荐使用
    • Cookie 认证(x-ima-cookie + x-ima-bkn):需从浏览器 F12 抓包,Cookie 会过期,不推荐
  3. Windows 特殊注意:路径要用绝对路径,JSON 中反斜杠要双写,PowerShell 环境变量用 $env: 语法。


七、参考链接

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐