AI Agent能力扩展实战:用MCP协议对接本地知识库与第三方工具链

本文从实际代码示例出发,讲解MCP协议的实现逻辑与扩展方法,实现大模型Agent与本地知识库、数据库、第三方API的无缝对接,附可运行的Demo代码与部署指南。

AI Agent能力扩展实战:用MCP协议对接本地知识库与第三方工具链

随着大模型应用落地进入深水区,AI Agent的能力边界不再局限于大模型本身的知识储备,能否快速对接企业内部知识库、业务数据库、第三方SaaS工具链成为了Agent落地的核心瓶颈。传统的Agent工具集成方案普遍存在框架绑定、接口不统一、权限管控缺失、扩展成本高等问题,而MCP(Modular Connection Protocol)作为专门为AI Agent设计的轻量级跨语言通信协议,完美解决了这些痛点。

本文将从实际代码实现出发,完整讲解MCP协议的核心逻辑、本地知识库对接方法、第三方工具链集成流程,并提供可直接运行的Demo代码与生产级部署指南,帮助你在1小时内搭建出具备自定义扩展能力的AI Agent系统。

一、MCP协议核心设计:为什么它是Agent扩展的最优解?

MCP协议是由AI Agent开源社区推出的专门面向智能体组件通信的标准化协议,它的设计目标就是解决多框架、多语言、多服务之间的互操作性问题,核心特性包括:

1.1 协议核心特性

  • 语言无关:支持Python/Go/Java/TypeScript等所有主流开发语言,服务端与客户端可以使用不同技术栈实现
  • 传输层无关:支持HTTP/REST、WebSocket、gRPC、本地IPC等多种传输方式,适配云原生、边缘设备、本地部署等所有场景
  • 内置权限管控:原生支持细粒度的能力授权、访问审计、流量管控,满足企业级安全要求
  • 自动能力协商:客户端与服务端建立连接时自动协商可用能力,无需手动配置接口文档
  • 结果标准化:所有返回结果都遵循统一格式,大模型不需要适配不同工具的返回结构

1.2 核心消息结构

MCP协议采用JSON作为统一序列化格式,所有请求响应都遵循固定结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 请求消息结构
{
  "jsonrpc": "2.0",
  "id": "req-123456",
  "method": "knowledge_base.query",
  "params": {
    "query": "2026年公司营收目标",
    "top_k": 3,
    "threshold": 0.7
  },
  "meta": {
    "agent_id": "customer-service-agent-001",
    "timestamp": 1755286200,
    "permission_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }
}

// 响应消息结构
{
  "jsonrpc": "2.0",
  "id": "req-123456",
  "result": {
    "status": "success",
    "data": [
      {
        "content": "2026年公司营收目标为12亿元,其中ToB业务占比60%",
        "source": "2026年战略规划文档.md",
        "score": 0.92
      }
    ],
    "usage": {
      "tokens": 128,
      "latency": 120
    }
  },
  "error": null
}

1.3 工作流原理

  1. 服务注册:工具/知识库服务启动时向MCP注册中心注册自己的能力、接口参数、权限要求
  2. 能力协商:Agent启动时向注册中心拉取可用服务列表,建立连接时协商支持的接口版本
  3. 请求处理:Agent调用工具时按照标准格式发送请求,MCP网关负责鉴权、流量控制、路由转发
  4. 结果返回:服务端处理完成后返回标准化结果,网关统一做格式校验后返回给Agent

二、实战第一步:搭建MCP基础服务与本地知识库对接

我们首先实现最常用的场景:将本地Obsidian知识库(或任意本地文档库)通过MCP协议暴露给AI Agent调用,让Agent可以实时查询内部文档。

2.1 环境准备

我们使用Python实现MCP服务,首先安装依赖:

1
pip install mcp-server fastapi uvicorn chromadb pypdf python-multipart sentence-transformers

2.2 实现本地知识库MCP服务

我们基于ChromaDB作为向量数据库,实现知识库的导入、查询、管理能力:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# mcp_knowledge_service.py
from mcp import MCPApplication, MCPService, method
from chromadb import PersistentClient
from chromadb.utils import embedding_functions
import os
from typing import List, Dict
import markdown
from pypdf import PdfReader

app = MCPApplication(
    service_name="knowledge-base-service",
    service_version="1.0.0",
    description="本地知识库查询服务",
    permissions=["knowledge.query", "knowledge.import"]
)

# 初始化向量数据库
chroma_client = PersistentClient(path="./chroma_db")
embedding_func = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name="BAAI/bge-small-zh-v1.5"
)
collection = chroma_client.get_or_create_collection(
    name="company_knowledge",
    embedding_function=embedding_func,
    metadata={"description": "公司内部知识库"}
)

class KnowledgeService(MCPService):
    @method(
        name="knowledge.query",
        description="查询知识库内容",
        parameters={
            "query": {"type": "string", "description": "查询问题"},
            "top_k": {"type": "integer", "description": "返回结果数量", "default": 3},
            "threshold": {"type": "float", "description": "相似度阈值", "default": 0.6}
        },
        required_permissions=["knowledge.query"]
    )
    async def query(self, query: str, top_k: int = 3, threshold: float = 0.6) -> Dict:
        results = collection.query(
            query_texts=[query],
            n_results=top_k,
            include=["documents", "metadatas", "distances"]
        )
        
        filtered = []
        for doc, meta, distance in zip(
            results["documents"][0],
            results["metadatas"][0],
            results["distances"][0]
        ):
            score = 1 - distance
            if score >= threshold:
                filtered.append({
                    "content": doc,
                    "source": meta.get("source", "unknown"),
                    "score": round(score, 2),
                    "last_updated": meta.get("last_updated", "")
                })
        
        return {
            "results": filtered,
            "count": len(filtered)
        }
    
    @method(
        name="knowledge.import",
        description="导入文档到知识库",
        parameters={
            "file_path": {"type": "string", "description": "本地文件路径"},
            "source": {"type": "string", "description": "文档来源标识"}
        },
        required_permissions=["knowledge.import"]
    )
    async def import_document(self, file_path: str, source: str) -> Dict:
        if not os.path.exists(file_path):
            raise ValueError(f"文件不存在: {file_path}")
        
        content = ""
        if file_path.endswith(".md"):
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()
        elif file_path.endswith(".pdf"):
            reader = PdfReader(file_path)
            content = "\n".join([page.extract_text() for page in reader.pages])
        elif file_path.endswith(".txt"):
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()
        else:
            raise ValueError(f"不支持的文件格式: {file_path}")
        
        # 分段处理(每段1000字符,重叠200字符)
        chunks = []
        chunk_size = 1000
        overlap = 200
        for i in range(0, len(content), chunk_size - overlap):
            chunk = content[i:i + chunk_size]
            if len(chunk) < 100:
                continue
            chunks.append(chunk)
        
        # 写入向量数据库
        collection.add(
            documents=chunks,
            metadatas=[{"source": source, "last_updated": str(os.path.getmtime(file_path))} for _ in chunks],
            ids=[f"{source}_{i}" for i in range(len(chunks))]
        )
        
        return {
            "status": "success",
            "chunks_imported": len(chunks),
            "source": source
        }

app.register_service(KnowledgeService())

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app.as_fastapi_app(), host="0.0.0.0", port=8701)

2.3 启动服务并测试

启动知识库服务:

1
python mcp_knowledge_service.py

我们可以使用MCP客户端快速测试接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from mcp import MCPClient

client = MCPClient("http://localhost:8701")
# 先导入测试文档
resp = client.call("knowledge.import", params={
    "file_path": "./2026战略规划.md",
    "source": "2026战略规划文档"
})
print("导入结果:", resp)

# 查询测试
resp = client.call("knowledge.query", params={
    "query": "2026年营收目标是多少?",
    "top_k": 2
})
print("查询结果:", resp)

正常返回结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
查询结果: {
    "results": [
        {
            "content": "2026年公司整体营收目标为12亿元,其中ToB业务收入7.2亿元,占比60%;ToC业务收入4.8亿元,占比40%。",
            "source": "2026战略规划文档",
            "score": 0.94,
            "last_updated": "1755200000.0"
        }
    ],
    "count": 1
}

三、实战第二步:对接第三方工具链,实现能力扩展

接下来我们实现MCP工具服务,将常用的第三方工具(天气查询、数据库查询、代码解释器、企业微信通知等)统一封装成MCP接口,供Agent调用。

3.1 实现第三方工具MCP服务

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# mcp_tool_service.py
from mcp import MCPApplication, MCPService, method
import requests
import sqlite3
from typing import Dict, Optional
import subprocess
import os

app = MCPApplication(
    service_name="tool-chain-service",
    service_version="1.0.0",
    description="第三方工具链服务",
    permissions=["tool.weather", "tool.db_query", "tool.code_run", "tool.notification"]
)

# 配置API密钥(生产环境使用环境变量)
WEATHER_API_KEY = os.getenv("WEATHER_API_KEY", "your_api_key")
DB_PATH = "./business.db"

class ToolService(MCPService):
    @method(
        name="tool.weather.query",
        description="查询指定城市的天气",
        parameters={
            "city": {"type": "string", "description": "城市名称,如北京、上海"},
            "days": {"type": "integer", "description": "查询天数,1-7天", "default": 1}
        },
        required_permissions=["tool.weather"]
    )
    async def query_weather(self, city: str, days: int = 1) -> Dict:
        url = f"https://api.openweathermap.org/data/2.5/forecast"
        params = {
            "q": city,
            "appid": WEATHER_API_KEY,
            "units": "metric",
            "lang": "zh_cn",
            "cnt": days * 8
        }
        resp = requests.get(url, params=params)
        if resp.status_code != 200:
            raise ValueError(f"天气查询失败: {resp.text}")
        
        data = resp.json()
        forecast = []
        for item in data["list"][::8]:
            forecast.append({
                "date": item["dt_txt"].split(" ")[0],
                "temp": f"{item['main']['temp_min']}~{item['main']['temp_max']}℃",
                "weather": item["weather"][0]["description"],
                "wind": f"{item['wind']['speed']}m/s"
            })
        
        return {
            "city": data["city"]["name"],
            "forecast": forecast
        }
    
    @method(
        name="tool.db.query",
        description="查询业务数据库",
        parameters={
            "sql": {"type": "string", "description": "SELECT查询语句,仅支持只读查询"}
        },
        required_permissions=["tool.db_query"]
    )
    async def query_db(self, sql: str) -> Dict:
        # 安全校验:仅允许SELECT查询,禁止写操作
        if not sql.strip().lower().startswith("select"):
            raise ValueError("仅允许执行SELECT查询语句")
        
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        try:
            cursor.execute(sql)
            columns = [desc[0] for desc in cursor.description]
            rows = cursor.fetchall()
            results = [dict(zip(columns, row)) for row in rows]
            return {
                "columns": columns,
                "rows": results,
                "count": len(results)
            }
        finally:
            conn.close()
    
    @method(
        name="tool.code.run",
        description="运行Python代码片段,仅用于数据计算与处理",
        parameters={
            "code": {"type": "string", "description": "Python代码片段"}
        },
        required_permissions=["tool.code_run"]
    )
    async def run_code(self, code: str) -> Dict:
        # 安全沙箱运行(生产环境建议使用Docker隔离)
        try:
            result = subprocess.run(
                ["python", "-c", code],
                capture_output=True,
                text=True,
                timeout=10
            )
            return {
                "returncode": result.returncode,
                "stdout": result.stdout,
                "stderr": result.stderr
            }
        except subprocess.TimeoutExpired:
            raise ValueError("代码运行超时,最长允许10秒")
    
    @method(
        name="tool.wecom.notice",
        description="发送企业微信通知",
        parameters={
            "userid": {"type": "string", "description": "接收人用户ID"},
            "content": {"type": "string", "description": "通知内容"}
        },
        required_permissions=["tool.notification"]
    )
    async def send_wecom_notice(self, userid: str, content: str) -> Dict:
        # 企业微信API调用实现
        wecom_webhook = os.getenv("WECOM_WEBHOOK")
        if not wecom_webhook:
            raise ValueError("未配置企业微信Webhook")
        
        resp = requests.post(wecom_webhook, json={
            "msgtype": "text",
            "text": {"content": content}
        })
        return {"status": "success" if resp.status_code == 200 else "failed"}

app.register_service(ToolService())

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app.as_fastapi_app(), host="0.0.0.0", port=8702)

3.2 工具调用测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from mcp import MCPClient

client = MCPClient("http://localhost:8702")
# 测试天气查询
resp = client.call("tool.weather.query", params={"city": "北京", "days": 3})
print("天气查询结果:", resp)

# 测试代码运行
resp = client.call("tool.code.run", params={"code": "print(123 * 456)"})
print("代码运行结果:", resp["stdout"]) # 输出 56088

四、端到端Agent实现:整合知识库与工具链

现在我们将MCP服务接入到实际的AI Agent中,实现一个可以查询内部知识库、调用第三方工具的智能客服Agent。

4.1 Agent实现代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# mcp_agent.py
from mcp import MCPClient
from openai import OpenAI
from typing import List, Dict
import json

client = OpenAI(base_url="https://api.doubao.com/v1", api_key="your_api_key")
# 初始化MCP客户端,连接所有服务
kb_client = MCPClient("http://localhost:8701")
tool_client = MCPClient("http://localhost:8702")

SYSTEM_PROMPT = """
你是公司智能客服Agent,你可以调用以下能力回答用户问题:
1. knowledge.query:查询公司内部知识库,回答公司制度、业务规则、战略规划相关问题
2. tool.weather.query:查询天气信息
3. tool.db.query:查询业务数据库中的订单、用户数据
4. tool.code.run:运行Python代码进行数据计算
5. tool.wecom.notice:发送企业微信通知给相关人员

调用工具时严格按照以下格式输出:
<|FunctionCallBegin|>[{"name":"工具名称","parameters":{"参数名":"参数值"}}]<|FunctionCallEnd|>

如果不需要调用工具,直接回答用户问题。回答时请使用中文,简洁明了。
"""

def parse_function_call(content: str) -> Optional[List[Dict]]:
    if "<|FunctionCallBegin|>" in content and "<|FunctionCallEnd|>" in content:
        func_str = content.split("<|FunctionCallBegin|>")[1].split("<|FunctionCallEnd|>")[0]
        return json.loads(func_str)
    return None

def run_agent(user_query: str, history: List[Dict] = None) -> str:
    messages = [{"role": "system", "content": SYSTEM_PROMPT}]
    if history:
        messages.extend(history)
    messages.append({"role": "user", "content": user_query})
    
    # 第一次大模型调用,判断是否需要工具
    resp = client.chat.completions.create(
        model="doubao-seed-2-0-pro",
        messages=messages,
        temperature=0.1
    )
    reply = resp.choices[0].message.content
    
    # 解析工具调用
    func_calls = parse_function_call(reply)
    if not func_calls:
        return reply
    
    # 执行工具调用
    for func in func_calls:
        func_name = func["name"]
        params = func["parameters"]
        
        if func_name == "knowledge.query":
            result = kb_client.call(func_name, params=params)
        elif func_name.startswith("tool."):
            result = tool_client.call(func_name, params=params)
        else:
            raise ValueError(f"未知工具: {func_name}")
        
        # 将工具结果加入上下文
        messages.append({"role": "assistant", "content": reply})
        messages.append({
            "role": "function",
            "name": func_name,
            "content": json.dumps(result, ensure_ascii=False)
        })
    
    # 第二次大模型调用,整理结果生成回答
    resp = client.chat.completions.create(
        model="doubao-seed-2-0-pro",
        messages=messages,
        temperature=0.1
    )
    return resp.choices[0].message.content

# 测试Agent
if __name__ == "__main__":
    # 测试知识库查询
    print("测试1:知识库查询")
    ans = run_agent("2026年公司营收目标是多少?")
    print(ans)
    # 输出:2026年公司整体营收目标为12亿元,其中ToB业务收入7.2亿元,占比60%;ToC业务收入4.8亿元,占比40%。
    
    # 测试工具调用
    print("\n测试2:工具调用")
    ans = run_agent("北京未来3天天气怎么样?")
    print(ans)
    # 输出:北京未来3天天气如下:
    # 8月16日:22~30℃,多云,东风2级
    # 8月17日:21~28℃,小雨,北风3级
    # 8月18日:20~27℃,晴,西北风2级

五、生产级部署指南与最佳实践

5.1 Docker Compose部署

我们可以使用Docker Compose一键部署整套MCP服务:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# docker-compose.yml
version: '3.8'
services:
  mcp-registry:
    image: mcp/registry:latest
    ports:
      - "8700:8700"
    environment:
      - DB_PATH=/data/registry.db
    volumes:
      - ./registry_data:/data
  
  knowledge-service:
    build: ./knowledge-service
    ports:
      - "8701:8701"
    environment:
      - MCP_REGISTRY_URL=http://mcp-registry:8700
      - CHROMA_DB_PATH=/data/chroma_db
    volumes:
      - ./chroma_data:/data
    depends_on:
      - mcp-registry
  
  tool-service:
    build: ./tool-service
    ports:
      - "8702:8702"
    environment:
      - MCP_REGISTRY_URL=http://mcp-registry:8700
      - WEATHER_API_KEY=${WEATHER_API_KEY}
      - WECOM_WEBHOOK=${WECOM_WEBHOOK}
    depends_on:
      - mcp-registry
  
  mcp-gateway:
    image: mcp/gateway:latest
    ports:
      - "8703:8703"
    environment:
      - MCP_REGISTRY_URL=http://mcp-registry:8700
      - AUTH_ENABLED=true
      - RATE_LIMIT_ENABLED=true
    depends_on:
      - mcp-registry

启动命令:

1
WEATHER_API_KEY=your_key WECOM_WEBHOOK=your_hook docker-compose up -d

5.2 安全最佳实践

  1. 权限隔离:不同Agent分配不同的权限Token,仅授予必要的工具调用权限
  2. 输入校验:所有工具接口都要做严格的参数校验,避免SQL注入、命令注入等安全漏洞
  3. 运行隔离:代码运行、文件操作等高危能力必须在Docker沙箱中运行,禁止直接在宿主机执行
  4. 审计日志:所有工具调用都要记录完整的请求响应日志,方便追溯问题
  5. 流量控制:配置限流规则,避免Agent频繁调用工具导致服务被击穿

5.3 性能优化

  1. 结果缓存:对知识库查询、天气查询等不经常变化的结果做缓存,减少重复调用
  2. 批量调用:支持批量工具调用,减少网络开销
  3. 异步调用:对耗时较长的工具调用支持异步回调模式,避免阻塞Agent执行
  4. 就近部署:MCP服务尽量与Agent部署在同一可用区,降低网络延迟

六、总结与未来展望

通过MCP协议,我们实现了AI Agent能力的标准化扩展,不管是本地知识库、内部业务系统还是第三方SaaS工具,都可以通过统一的接口快速接入Agent,大大降低了Agent落地的开发成本。目前MCP协议已经被众多开源Agent框架支持,包括LangChain、AutoGPT、MetaGPT等,生态正在快速完善。

未来MCP协议还将增加更多企业级特性,包括分布式事务支持、流式响应、跨域信任机制等,进一步降低AI Agent的落地门槛。如果你正在做AI Agent相关的开发,强烈建议你尝试使用MCP协议作为你的能力扩展层,它会让你的Agent扩展效率提升至少10倍。

本文所有代码都可以在GitHub仓库(https://github.com/your-repo/mcp-agent-demo)下载,你可以直接运行Demo体验完整流程,也可以基于代码快速扩展自己需要的工具能力。

本博客文章采用 CC BY-NC-SA 4.0 许可协议
服务器推荐

腾讯云 · 新用户专属优惠

本博客部署在腾讯云服务器,稳定运行一年多。如果你是新用户或想搭建个人项目,推荐试试腾讯云的优惠活动。

查看优惠详情 →
阅读 1169
上一篇
数据资产目录建设实战:从元数据自动采集到资产标签体系的落地方法
下一篇
PLM与ERP系统集成增量同步方案:从BOM到工艺路线的数据一致性保障方法
广告

📚 关注公众号,免费获取技术材料

扫码关注公众号,回复「资料」领取:

  • 📘 企业架构设计模板
  • 📗 数据治理实施指南
  • 📙 工业软件技术白皮书
公众号二维码

长按或扫描二维码