LLM如何与mcp server交互示例

之前深入分析MCP协议的与应用示例

https://blog.csdn.net/liliang199/article/details/160044874

这里进一步探索LLM如何与MCP Server交互,以及一些需要注意地方。

所用示例参考和修改自网络资料。

1 LLM如何与MCP服务交互

1.1 交互过程

LLM与MCP服务的交互,核心是MCP客户端作为中间桥梁,将LLM与MCP服务器连接起来。

1)客户端先从MCP服务器发现可用工具

2)再将LLM指令转化为对工具的标准调用

3)最后将执行结果返回给模型。

1.2 交互步骤

一个典型的交互通常包含以下三个步骤:

1)工具发现(Client ↔ Server)

MCP客户端向MCP服务器发送 tools/list 请求,获取服务器提供的工具列表。

获取信息包括名称、描述和参数模式(JSON Schema)。

然后,客户端会将这些工具定义转换为LLM可理解的"函数调用"(Function Calling)格式。

2)LLM交互(Client ↔ LLM)

用户向LLM发送查询时,客户端会附带转换后的工具定义。

当LLM决定使用某个工具时,它会返回一个包含工具名称和参数的"函数调用"请求。

3)工具执行(Client ↔ Server)

MCP客户端接收到LLM调用请求后,向对应MCP服务器发送调用请求,并将结果返回给LLM。

LLM再结合结果生成最终回复。

基于MCP标准化交互,让LLM能够无缝地扩展和使用外部能力。

2 LLM与MCP服务交互示例

这里参考之前深入分析MCP协议所用的代码,构建LLM和MCP Server交互的代码示例。

https://blog.csdn.net/liliang199/article/details/160044874

在示例过程中,展示如何创建一个简单的MCP客户端并集成LLM。

2.1 工具安装

由于mcp工具相对比较新,所以需要使用新一点的python版本,比如3.11以及以上版本。

这里假设python环境已经安装。

示例会用到sqlite3、faiss、openai、litellm等多种包,安装过程如下。

pip install mcp_use

pip install pysqlite3

pip install mcp litellm faiss-cpu numpy

pip install langchain_openai

2.2 运行环境配置

由于涉及到向量模型,需要从hf下载,这里配置hf-mirror国内站点。

同时配置openai格式的api key和base url,示例代码如下所示。

复制代码
import os
os.environ['HF_ENDPOINT'] = "https://hf-mirror.com"
  
model_name = gpt_model_name # LLM名称,比如deepseek-r1, qwen3.5-8b
os.environ['OPENAI_API_KEY'] = gpt_api_key # LLM供应商提供的api key
os.environ['OPENAI_BASE_URL'] = gpt_api_url # LLM供应商提供llm访问api的url

import openai
client = openai.OpenAI()

2.3 mcp server定义

这里参考之前分析文章,提供一个完整、可运行MCP Server示例 - 智能知识库助手。

https://blog.csdn.net/liliang199/article/details/160044874

示例集成SQLite和FAISS,支持语义检索、问答记录、统计分析等功能,展示MCP原语协同运用。

代码如下,保存为zhishi_assistant.py。

复制代码
import os
os.environ['HF_ENDPOINT'] = "https://hf-mirror.com"

model_name = gpt_model_name # LLM名称,比如deepseek-r1, qwen3.5-8b
os.environ['OPENAI_API_KEY'] = gpt_api_key # LLM供应商提供的api key
os.environ['OPENAI_BASE_URL'] = gpt_api_url # LLM供应商提供llm访问api的url

import os
import json
import sqlite3
import hashlib
from datetime import datetime
from typing import List, Dict, Any, Optional
from dataclasses import dataclass, asdict
from sentence_transformers import SentenceTransformer

import numpy as np
import faiss
from mcp.server.fastmcp import FastMCP
import litellm



# ============================================================================
# 第一部分:数据模型与基础设施
# ============================================================================

@dataclass
class Document:
    """知识库文档模型"""
    id: str
    title: str
    content: str
    source: str
    created_at: str
    embedding: Optional[List[float]] = None

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)


class SQLiteStore:
    """SQLite 数据存储层 - 管理文档元数据和问答记录"""

    def __init__(self, db_path: str = "knowledge_base.db"):
        self.db_path = db_path
        self._init_db()

    def _init_db(self):
        """初始化数据库表结构"""
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()

            # 文档表
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS documents (
                    id TEXT PRIMARY KEY,
                    title TEXT NOT NULL,
                    content TEXT NOT NULL,
                    source TEXT,
                    created_at TEXT NOT NULL,
                    embedding_hash TEXT
                )
            """)

            # 问答记录表 - 用于追踪 LLM 调用历史
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS qa_records (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    question TEXT NOT NULL,
                    answer TEXT NOT NULL,
                    retrieved_docs TEXT,
                    created_at TEXT NOT NULL,
                    latency_ms INTEGER
                )
            """)

            conn.commit()

    def insert_document(self, doc: Document) -> bool:
        try:
            with sqlite3.connect(self.db_path) as conn:
                cursor = conn.cursor()
                cursor.execute("""
                    INSERT OR REPLACE INTO documents
                    (id, title, content, source, created_at, embedding_hash)
                    VALUES (?, ?, ?, ?, ?, ?)
                """, (doc.id, doc.title, doc.content, doc.source,
                      doc.created_at, self._hash_embedding(doc.embedding)))
                conn.commit()
            return True
        except Exception as e:
            # print(f"Failed to insert document: {e}")
            return False

    def get_document(self, doc_id: str) -> Optional[Dict]:
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT id, title, content, source, created_at FROM documents WHERE id = ?",
                (doc_id,))
            row = cursor.fetchone()
            if row:
                return {
                    "id": row[0], "title": row[1], "content": row[2],
                    "source": row[3], "created_at": row[4]
                }
        return None

    def insert_qa_record(self, question: str, answer: str,
                         retrieved_docs: List[str], latency_ms: int) -> int:
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("""
                INSERT INTO qa_records (question, answer, retrieved_docs, created_at, latency_ms)
                VALUES (?, ?, ?, ?, ?)
            """, (question, answer, json.dumps(retrieved_docs),
                  datetime.now().isoformat(), latency_ms))
            conn.commit()
            return cursor.lastrowid

    def get_recent_qa(self, limit: int = 5) -> List[Dict]:
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("""
                SELECT question, answer, retrieved_docs, created_at, latency_ms
                FROM qa_records ORDER BY created_at DESC LIMIT ?
            """, (limit,))
            rows = cursor.fetchall()
            return [{
                "question": r[0], "answer": r[1], "retrieved_docs": json.loads(r[2]),
                "created_at": r[3], "latency_ms": r[4]
            } for r in rows]

    def get_stats(self) -> Dict[str, Any]:
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT COUNT(*) FROM documents")
            doc_count = cursor.fetchone()[0]
            cursor.execute("SELECT COUNT(*) FROM qa_records")
            qa_count = cursor.fetchone()[0]
            cursor.execute("SELECT AVG(latency_ms) FROM qa_records")
            avg_latency = cursor.fetchone()[0] or 0
        return {
            "total_documents": doc_count,
            "total_qa_records": qa_count,
            "avg_latency_ms": round(avg_latency, 2)
        }

    @staticmethod
    def _hash_embedding(emb: Optional[List[float]]) -> str:
        if emb is None:
            return ""
        return hashlib.md5(np.array(emb).tobytes()).hexdigest()[:16]




class FAISSIndex:
    """FAISS 向量索引 - 语义检索核心"""

    def __init__(self, dimension: int = 768, index_path: str = "kb_index.faiss"):
        self.dimension = dimension
        self.index_path = index_path
        self.index: Optional[faiss.IndexFlatIP] = None
        self.doc_ids: List[str] = []
        self._load_or_create()

    def _load_or_create(self):
        if os.path.exists(self.index_path):
            self.index = faiss.read_index(self.index_path)
            # 尝试加载对应的 ID 映射文件
            id_map_path = self.index_path.replace(".faiss", "_ids.json")
            if os.path.exists(id_map_path):
                with open(id_map_path, "r") as f:
                    self.doc_ids = json.load(f)
        else:
            self.index = faiss.IndexFlatIP(self.dimension)

    def add(self, doc_id: str, embedding: List[float]):
        if self.index is None:
            self.index = faiss.IndexFlatIP(self.dimension)
        vec = np.array(embedding, dtype=np.float32).reshape(1, -1)
        faiss.normalize_L2(vec)
        self.index.add(vec)
        self.doc_ids.append(doc_id)

    def search(self, query_embedding: List[float], k: int = 5) -> List[tuple]:
        if self.index is None or self.index.ntotal == 0:
            return []
        vec = np.array(query_embedding, dtype=np.float32).reshape(1, -1)
        faiss.normalize_L2(vec)
        distances, indices = self.index.search(vec, min(k, self.index.ntotal))
        results = []
        for dist, idx in zip(distances[0], indices[0]):
            if idx >= 0 and idx < len(self.doc_ids):
                results.append((self.doc_ids[idx], float(dist)))
        return results

    def save(self):
        if self.index:
            faiss.write_index(self.index, self.index_path)
            id_map_path = self.index_path.replace(".faiss", "_ids.json")
            with open(id_map_path, "w") as f:
                json.dump(self.doc_ids, f)




class EmbeddingService:
    """嵌入服务 - 使用Sentence-Transformer调用真实 LLM 生成向量"""

    def __init__(self, model: str = "maidalun1020/bce-embedding-base_v1", model_path=""):
        self.model = model
        self.model_path = model_path
        if self.model_path: 
            self.embedder = SentenceTransformer(model_name_or_path=model_path, local_files_only=True)
        else:
            self.embedder = SentenceTransformer(self.model)
        
    def generate(self, text: str) -> List[float]:
        """
        生成文本嵌入向量。
        实际部署时可替换为本地模型(如 sentence-transformers)
        """
        try:
            embedding = self.embedder.encode(text[:8000])
            return embedding
        except Exception as e:
            # print(f"Embedding generation failed: {e}")
            # 降级:返回随机向量(仅用于演示,生产环境应使用本地模型)
            return np.random.randn(768)




# ============================================================================
# 第二部分:MCP Server 核心实现
# ============================================================================

# 初始化 MCP Server
mcp = FastMCP(
    "KnowledgeBaseAssistant",
    json_response=True
)

# 初始化底层服务
db = SQLiteStore()
faiss_index = FAISSIndex(dimension=768)
# emb_service = EmbeddingService(model="maidalun1020/bce-embedding-base_v1")
model_path = "./my_local_model"
emb_service = EmbeddingService(model_path=model_path)
embedding = emb_service.generate("hello!").tolist()
# print(len(embedding))


# ----------------------------------------------------------------------------
# 工具定义 - AI 可以主动调用的功能
# ----------------------------------------------------------------------------

@mcp.tool()
def search_knowledge_base(query: str, top_k: int = 3) -> str:
    """
    在知识库中进行语义检索,返回最相关的文档内容。

    Args:
        query: 用户查询的自然语言问题
        top_k: 返回的文档数量,默认 3 条,最大 10 条

    Returns:
        检索到的文档内容,以结构化格式呈现
    """

    # print(f"run search_knowledge_base, query={query}, top_k={top_k}")
    
    # 生成查询向量
    query_emb = emb_service.generate(query)

    # FAISS 向量检索
    results = faiss_index.search(query_emb, k=min(top_k, 10))

    if not results:
        return "知识库中未找到相关文档。"

    # 获取文档详情
    output_parts = [f"检索到 {len(results)} 条相关文档:\n"]
    for i, (doc_id, score) in enumerate(results, 1):
        doc = db.get_document(doc_id)
        if doc:
            output_parts.append(
                f"{i}. 【{doc['title']}】(相关度: {score:.3f})\n"
                f"   来源: {doc['source']}\n"
                f"   内容: {doc['content'][:500]}...\n"
            )
    # print(f"output_parts: {output_parts}")

    # return "\n".join(output_parts)
    return json.dumps(output_parts, ensure_ascii=False, indent=2)
    


@mcp.tool()
def save_qa_record(question: str, answer: str,
                   retrieved_doc_ids: List[str] = None) -> str:
    """
    保存问答记录到数据库,用于追踪和统计。

    Args:
        question: 用户的原始问题
        answer: LLM 生成的回答
        retrieved_doc_ids: 检索到的文档 ID 列表

    Returns:
        保存结果的状态信息
    """
    record_id = db.insert_qa_record(
        question=question,
        answer=answer,
        retrieved_docs=retrieved_doc_ids or [],
        latency_ms=0  # 实际应计算调用耗时
    )
    return f"问答记录已保存,ID: {record_id}"


@mcp.tool()
def get_kb_stats() -> str:
    """
    获取知识库的统计信息,比如获取文档覆盖情况等元信息。

    Returns:
        包含文档数量、问答记录数、平均延迟等统计数据的文本
    """
    stats = db.get_stats()
    return (
        f"📊 知识库统计信息:\n"
        f"  - 文档总数: {stats['total_documents']}\n"
        f"  - 问答记录数: {stats['total_qa_records']}\n"
        f"  - 平均响应延迟: {stats['avg_latency_ms']} ms\n"
        f"  - 向量索引大小: {faiss_index.index.ntotal if faiss_index.index else 0} 条"
    )


@mcp.tool()
def add_document(title: str, content: str, source: str = "manual") -> str:
    """
    向知识库添加新文档。

    Args:
        title: 文档标题
        content: 文档内容
        source: 文档来源(如 URL、文件名等)

    Returns:
        添加结果
    """
    doc_id = hashlib.md5(f"{title}:{source}".encode()).hexdigest()[:16]

    # 生成嵌入向量
    embedding = emb_service.generate(content)

    doc = Document(
        id=doc_id,
        title=title,
        content=content,
        source=source,
        created_at=datetime.now().isoformat(),
        embedding=embedding
    )

    # 保存到 SQLite
    if not db.insert_document(doc):
        return f"❌ 文档保存失败"

    # 添加到 FAISS 索引
    faiss_index.add(doc_id, embedding)
    faiss_index.save()

    return f"✅ 文档已成功添加到知识库,ID: {doc_id}"


# ----------------------------------------------------------------------------
# 资源定义 - AI 可以读取的只读数据
# ----------------------------------------------------------------------------

@mcp.resource("kb://stats")
def get_kb_resource_stats() -> str:
    """
    知识库统计信息资源。
    AI 可通过读取 kb://stats 获取当前知识库的统计摘要。
    """
    stats = db.get_stats()
    return json.dumps(stats, indent=2, ensure_ascii=False)


@mcp.resource("qa://recent")
def get_recent_qa_resource() -> str:
    """
    最近问答记录资源。
    AI 可通过读取 qa://recent 获取最近的问答历史。
    """
    records = db.get_recent_qa(limit=5)
    if not records:
        return "暂无问答记录。"
    output = ["📝 最近 5 条问答记录:\n"]
    for i, r in enumerate(records, 1):
        output.append(f"{i}. Q: {r['question'][:100]}...")
        output.append(f"   A: {r['answer'][:100]}...")
        output.append(f"   时间: {r['created_at']}\n")
    return "\n".join(output)


@mcp.resource("doc://{doc_id}")
def get_document_resource(doc_id: str) -> str:
    """
    获取指定 ID 的文档内容。
    """
    doc = db.get_document(doc_id)
    if not doc:
        return f"未找到文档 ID: {doc_id}"
    return json.dumps(doc, indent=2, ensure_ascii=False)


# ----------------------------------------------------------------------------
# 提示定义 - 预设的交互模板
# ----------------------------------------------------------------------------

@mcp.prompt()
def analyze_knowledge_base(topic: str = "整体情况") -> str:
    """
    生成分析知识库的提示模板。
    引导 LLM 对知识库进行结构化分析。
    """
    stats = db.get_stats()
    return f"""你是一个知识库分析专家。请基于以下信息分析知识库:

**知识库概况:**
- 文档总数:{stats['total_documents']}
- 问答记录数:{stats['total_qa_records']}
- 平均响应延迟:{stats['avg_latency_ms']} ms

**分析主题:** {topic}

**要求:**
1. 评估知识库的覆盖范围和质量
2. 识别可能的知识盲区
3. 提出改进建议

请开始你的分析:"""


@mcp.prompt()
def answer_with_context(question: str) -> str:
    """
    生成带检索增强的回答提示模板。
    用于引导 LLM 基于检索到的文档回答问题。
    """
    # 先检索相关文档
    query_emb = emb_service.generate(question)
    results = faiss_index.search(query_emb, k=3)

    context_parts = []
    for doc_id, _ in results:
        doc = db.get_document(doc_id)
        if doc:
            context_parts.append(f"【文档:{doc['title']}】\n{doc['content']}")

    context = "\n\n".join(context_parts) if context_parts else "(未找到相关文档)"

    return f"""请基于以下参考文档回答问题。

**参考文档:**
{context}

**用户问题:** {question}

**要求:**
1. 回答必须基于参考文档的内容
2. 如果文档中没有相关信息,请明确说明
3. 引用具体的文档内容作为依据

请回答:"""




# ============================================================================
# 第三部分:MCP Client 测试 - 真实 LLM 交互
# ============================================================================

class MCPKnowledgeBaseClient:
    """
    MCP 客户端测试类。
    通过 LiteLLM 调用真实 LLM,展示完整的 MCP 工作流。
    """

    def __init__(self, model: str = "deepseek/deepseek-chat"):
        self.model = f"openai/{model}"
        self.api_key = os.getenv("OPENAI_API_KEY")
        self.base_url = os.getenv("OPENAI_BASE_URL")
        self.conversation_history = []

    def _get_tools_for_llm(self) -> List[Dict]:
        """将 MCP 工具转换为 LLM 可理解的 Function Calling 格式"""
        return [
            {
                "type": "function",
                "function": {
                    "name": "search_knowledge_base",
                    "description": "在知识库中进行语义检索,返回最相关的文档内容。",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "用户查询的自然语言问题"
                            },
                            "top_k": {
                                "type": "integer",
                                "description": "返回的文档数量,默认 3 条,最大 10 条",
                                "default": 3
                            }
                        },
                        "required": ["query"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "get_kb_stats",
                    "description": "获取知识库的统计信息。",
                    "parameters": {"type": "object", "properties": {}}
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "save_qa_record",
                    "description": "保存问答记录到数据库。",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "question": {"type": "string", "description": "用户问题"},
                            "answer": {"type": "string", "description": "回答内容"},
                            "retrieved_doc_ids": {
                                "type": "array",
                                "items": {"type": "string"},
                                "description": "检索到的文档ID"
                            }
                        },
                        "required": ["question", "answer"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "add_document",
                    "description": "向知识库添加新文档。",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "title": {"type": "string", "description": "文档标题"},
                            "content": {"type": "string", "description": "文档内容"},
                            "source": {"type": "string", "description": "文档来源"}
                        },
                        "required": ["title", "content"]
                    }
                }
            }
        ]

    def chat(self, user_message: str) -> str:
        """
        单轮对话,支持工具调用。
        展示 MCP 工具如何在真实 LLM 交互中使用。
        """
        import time

        messages = self.conversation_history + [
            {"role": "user", "content": user_message}
        ]

        # 调用 LLM(可能触发工具调用)
        response = litellm.completion(
            model=self.model,
            messages=messages,
            api_base = self.base_url,
            api_key=self.api_key,
            tools=self._get_tools_for_llm(),
            tool_choice="auto"
        )

        assistant_message = response.choices[0].message

        # 处理工具调用
        if assistant_message.tool_calls:
            tool_results = []
            for tool_call in assistant_message.tool_calls:
                func_name = tool_call.function.name
                func_args = json.loads(tool_call.function.arguments)

                start_time = time.time()

                # 执行对应的 MCP 工具
                if func_name == "search_knowledge_base":
                    result = search_knowledge_base(**func_args)
                elif func_name == "get_kb_stats":
                    result = get_kb_stats()
                elif func_name == "save_qa_record":
                    result = save_qa_record(**func_args)
                elif func_name == "add_document":
                    result = add_document(**func_args)
                else:
                    result = f"未知工具: {func_name}"

                latency = int((time.time() - start_time) * 1000)
                tool_results.append({
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "content": result
                })

                # 如果是问答,自动保存记录
                if func_name == "search_knowledge_base":
                    save_qa_record(user_message, result, [])

            # 将工具结果发回 LLM 生成最终回答
            final_messages = messages + [
                {"role": "assistant", "content": None, "tool_calls": [
                    {"id": tc.id, "type": "function", "function": {
                        "name": tc.function.name, "arguments": tc.function.arguments
                    }} for tc in assistant_message.tool_calls
                ]}
            ] + tool_results

            
            
            final_response = litellm.completion(
                model=self.model,
                api_base = self.base_url,
                api_key=self.api_key,
                messages=final_messages
            )

            return final_response.choices[0].message.content

        return assistant_message.content or ""

    def run_test_scenarios(self):
        """
        运行预设的测试场景,验证 MCP 所有功能。
        """
        # print("=" * 60)
        # print("MCP 智能知识库助手 - 工业级测试")
        # print("=" * 60)

        # 场景 1:初始化测试数据
        # print("\n【场景 1】初始化知识库...")
        test_docs = [
            ("MCP 协议简介", "Model Context Protocol (MCP) 是 Anthropic 于 2024 年 11 月发布的开放标准,用于标准化 LLM 与外部工具的连接。MCP 采用客户端-服务器架构,基于 JSON-RPC 2.0 通信。", "test"),
            ("MCP 核心原语", "MCP 定义了三种核心能力:Tools(工具)用于执行操作,Resources(资源)用于提供只读数据,Prompts(提示)用于预设交互模板。", "test"),
            ("MCP 传输层", "MCP 支持 stdio 和 Streamable HTTP 两种传输方式。stdio 适用于本地工具调用,Streamable HTTP 适用于远程服务。", "test"),
        ]
        for title, content, source in test_docs:
            result = add_document(title, content, source)
            # print(f"  添加文档: {title} -> {result}")

        # 场景 2:测试统计信息工具
        # print("\n【场景 2】测试工具 - 获取知识库统计...")
        # print(f"  结果: {get_kb_stats()}")

        # 场景 3:测试语义检索工具
        # print("\n【场景 3】测试工具 - 语义检索...")
        result = search_knowledge_base("MCP 是什么?")
        # print(f"  结果: {result[:300]}...")

        # 场景 4:测试资源读取
        # print("\n【场景 4】测试资源 - 读取统计信息...")
        # print(f"  kb://stats 资源内容: {get_kb_resource_stats()}")

        # print("\n【场景 5】测试资源 - 读取问答记录...")
        # print(f"  qa://recent 资源内容: {get_recent_qa_resource()}")

        # 场景 6:测试提示模板
        # print("\n【场景 6】测试提示 - 生成分析模板...")
        # prompt = analyze_knowledge_base("MCP 文档覆盖情况")
        # print(f"  生成的提示: {prompt[:200]}...")

        # 场景 7:真实 LLM 交互测试
        # print("\n【场景 7】真实 LLM 交互测试...")
        # print("  向 LLM 提问: 请帮我查询 MCP 协议的核心原语有哪些?")
        response = self.chat("请帮我查询 MCP 协议的核心原语有哪些?")
        # print(f"  LLM 回答: {response[:300]}...")

        # 场景 8:验证问答记录
        # print("\n【场景 8】验证问答记录已保存...")
        # records = db.get_recent_qa(limit=2)
        # for r in records:
        #     print(f"  Q: {r['question'][:50]}...")
        #     print(f"  A: {r['answer'][:50]}...")
        #     print(f"  时间: {r['created_at']}")

        # print("\n" + "=" * 60)
        # print("所有测试场景执行完毕!")
        # print("=" * 60)
'''
============================================================================
第四部分:入口
============================================================================
'''

if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1 and sys.argv[1] == "--test":
        # 运行测试模式
        client = MCPKnowledgeBaseClient(model=model_name)
        client.run_test_scenarios()
    else:
        # 启动 MCP Server(stdio 传输,供 Claude Desktop 等客户端连接)
        # print("启动 MCP Knowledge Base Server (stdio)...", file=sys.stderr)
        mcp.run(transport="stdio")

需要注意的是mcp server定义代码中,注释掉所有print,或将print改为log方式,否则会报错。

2.4 mcp client定义

MCP支持Stdio(标准输入输出)和SSE(Server-Sent Events)两种传输方式。

Stdio适用于本地集成的IDE插件或CLI工具,而SSE适用于通过网络部署的Web服务或远程调用

这里使用MCP Stdio,通过如下方式与zhishi_assistant.py定义mcp server交互。

server_params = StdioServerParameters(command="python", args=["zhishi_assistant.py"])

1)示例代码

client示例代码如下

复制代码
import os
os.environ['HF_ENDPOINT'] = "https://hf-mirror.com"

model_name = gpt_model_name # LLM名称,比如deepseek-r1, qwen3.5-8b
os.environ['OPENAI_API_KEY'] = gpt_api_key # LLM供应商提供的api key
os.environ['OPENAI_BASE_URL'] = gpt_api_url # LLM供应商提供llm访问api的url
 
import openai
client = openai.OpenAI()



import asyncio
import json
from openai import OpenAI
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

# --- 1. 工具发现:从MCP服务器获取工具并转换为OpenAI格式 ---
async def fetch_tools(session: ClientSession):
    await session.initialize()
    tool_result = await session.list_tools()
    # 将MCP工具转换为OpenAI Function Calling的格式
    openai_tools = [{
        "type": "function",
        "function": {
            "name": tool.name,
            "description": tool.description,
            "parameters": tool.inputSchema
        }
    } for tool in tool_result.tools]
    return openai_tools

# --- 2. LLM交互:调用OpenAI模型进行对话和工具调用决策 ---
def ask_llm(client, messages, tools):
    return client.chat.completions.create(
        model=model_name,  # 替换为支持工具调用的模型
        messages=messages,
        tools=tools,
        tool_choice="auto"
    )

# --- 3. 工具执行:解析LLM响应并调用MCP工具 ---
async def execute_tool_call(session, tool_name, tool_args):
    try:
        # 调用MCP工具
        print(f"running tools.....")
        result = await session.call_tool(tool_name, arguments=tool_args)
        print(f"tool result: {result}")
        return f"[Tool Result] {result.content}"
    except Exception as e:
        print(f"fail to call {tool_name} with {e}")
        return f"[Error calling tool {tool_name}: {e}]"

async def main():
    # 初始化OpenAI客户端
    # llm_client = OpenAI(api_key="your-api-key")
    # 假设你的MCP服务器以stdio方式运行(例如:python my_mcp_server.py)
    server_params = StdioServerParameters(command="python", args=["zhishi_assistant.py"])

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            # 1. 获取并转换工具
            available_tools = await fetch_tools(session)

            # query = "请帮我查询 MCP 协议的核心原语有哪些?"
            query = "MCP 是什么?"
            # query = "MCP 文档覆盖情况"
            # query = "8*5=?"
            
            messages = [{"role": "user", "content": query}]

            # 2. 调用LLM
            response = ask_llm(client, messages, available_tools)
            response_message = response.choices[0].message

            # 3. 处理工具调用请求
            if response_message.tool_calls:
                messages.append(response_message)
                for tool_call in response_message.tool_calls:
                    tool_name = tool_call.function.name
                    tool_args = json.loads(tool_call.function.arguments)
                    
                    print(f"LLM请求调用工具: {tool_name}({tool_args})")
                    tool_result = await execute_tool_call(session, tool_name, tool_args)
                    
                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": tool_result
                    })
                # 将工具结果返回给LLM生成最终回答
                final_response = ask_llm(client, messages, available_tools)
                print("最终回答:", final_response.choices[0].message.content)
            else:
                # 没有工具调用,直接输出回答
                print("回答:", response_message.content)

if __name__ == "__main__":
    asyncio.run(main())
2)运行分析

查询问题是query = "MCP 是什么?"

所以,运行代码

python zhishi_agent.py

具体为通过调用工具search_knowledge_base({'query': 'MCP 是什么', 'top_k': 3})

获取到问题答案相关材料,然后由LLM生成最终回复。

根据知识库检索结果,**MCP** 是 **Model Context Protocol(模型上下文协议)** 的缩写。

MCP 简介

| 项目 | 说明 |

|------|------|

| **发布方** | Anthropic |

| **发布时间** | 2024 年 11 月 |

| **性质** | 开放标准 |

| **用途** | 标准化 LLM 与外部工具的连接 |

| **架构** | 客户端-服务器架构 |

| **通信协议** | 基于 JSON-RPC 2.0 |

核心作用

MCP 的主要目的是为大语言模型(LLM)与外部工具、数据源之间的交互提供一个统一的标准化协议。通过 MCP,开发者可以更便捷地让 AI 模型访问各种外部资源,如:

  • 数据库

  • 文件系统

  • API 服务

  • 其他工具和应用

这种标准化设计有助于提高互操作性,降低集成成本,促进 AI 生态系统的开放发展。

如果您需要了解更多关于 MCP 的技术细节或使用方式,可以进一步提问!

3)详细日志

详细代码运行日志如下

09:52:52 - LiteLLM:WARNING: get_model_cost_map.py:271 - LiteLLM: Failed to fetch remote model cost map from https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json: [Errno 8] nodename nor servname provided, or not known. Falling back to local backup.

04/12/26 09:52:53\] INFO No device provided, using mps model.py:177 INFO Loading SentenceTransformer model from ./my_local_model. model.py:968 Loading weights: 100%\|███████████████████████████████████████████████████████████████████████████████████████████████████████████\| 199/199 \[00:00\<00:00, 6277.81it/s

Batches: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 2.46it/s]

04/12/26 09:52:56\] INFO Processing request of type ListToolsRequest server.py:727 LLM请求调用工具: search_knowledge_base({'query': 'MCP 是什么', 'top_k': 3}) running tools..... \[04/12/26 09:52:57\] INFO Processing request of type CallToolRequest server.py:727 Batches: 100%\|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████\| 1/1 \[00:00\<00:00, 12.31it/s

tool result: meta=None content=[TextContent(type='text', text='[\n "检索到 3 条相关文档:\\n",\n "1. 【MCP 协议简介】(相关度: 0.691)\\n 来源: test\\n 内容: Model Context Protocol (MCP) 是 Anthropic 于 2024 年 11 月发布的开放标准,用于标准化 LLM 与外部工具的连接。MCP 采用客户端-服务器架构,基于 JSON-RPC 2.0 通信。...\\n",\n "2. 【MCP 协议简介】(相关度: 0.691)\\n 来源: test\\n 内容: Model Context Protocol (MCP) 是 Anthropic 于 2024 年 11 月发布的开放标准,用于标准化 LLM 与外部工具的连接。MCP 采用客户端-服务器架构,基于 JSON-RPC 2.0 通信。...\\n",\n "3. 【MCP 协议简介】(相关度: 0.691)\\n 来源: test\\n 内容: Model Context Protocol (MCP) 是 Anthropic 于 2024 年 11 月发布的开放标准,用于标准化 LLM 与外部工具的连接。MCP 采用客户端-服务器架构,基于 JSON-RPC 2.0 通信。...\\n"\n]', annotations=None, meta=None)] structuredContent={'result': '[\n "检索到 3 条相关文档:\\n",\n "1. 【MCP 协议简介】(相关度: 0.691)\\n 来源: test\\n 内容: Model Context Protocol (MCP) 是 Anthropic 于 2024 年 11 月发布的开放标准,用于标准化 LLM 与外部工具的连接。MCP 采用客户端-服务器架构,基于 JSON-RPC 2.0 通信。...\\n",\n "2. 【MCP 协议简介】(相关度: 0.691)\\n 来源: test\\n 内容: Model Context Protocol (MCP) 是 Anthropic 于 2024 年 11 月发布的开放标准,用于标准化 LLM 与外部工具的连接。MCP 采用客户端-服务器架构,基于 JSON-RPC 2.0 通信。...\\n",\n "3. 【MCP 协议简介】(相关度: 0.691)\\n 来源: test\\n 内容: Model Context Protocol (MCP) 是 Anthropic 于 2024 年 11 月发布的开放标准,用于标准化 LLM 与外部工具的连接。MCP 采用客户端-服务器架构,基于 JSON-RPC 2.0 通信。...\\n"\n]'} isError=False

最终回答:

根据知识库检索结果,**MCP** 是 **Model Context Protocol(模型上下文协议)** 的缩写。

MCP 简介

| 项目 | 说明 |

|------|------|

| **发布方** | Anthropic |

| **发布时间** | 2024 年 11 月 |

| **性质** | 开放标准 |

| **用途** | 标准化 LLM 与外部工具的连接 |

| **架构** | 客户端-服务器架构 |

| **通信协议** | 基于 JSON-RPC 2.0 |

核心作用

MCP 的主要目的是为大语言模型(LLM)与外部工具、数据源之间的交互提供一个统一的标准化协议。通过 MCP,开发者可以更便捷地让 AI 模型访问各种外部资源,如:

  • 数据库

  • 文件系统

  • API 服务

  • 其他工具和应用

这种标准化设计有助于提高互操作性,降低集成成本,促进 AI 生态系统的开放发展。

如果您需要了解更多关于 MCP 的技术细节或使用方式,可以进一步提问!

reference


MCP协议的深度分析与应用示例

https://blog.csdn.net/liliang199/article/details/160044874

AI应用的USB-C接口 Model Context Protocol (MCP)

https://modelcontextprotocol.info/zh-cn/

sentence-transformer如何离线加载和使用模型

https://blog.csdn.net/liliang199/article/details/160060904

相关推荐
小夏子_riotous6 小时前
openstack的使用——7. 共享文件系统manila服务
linux·运维·服务器·系统架构·centos·openstack·运维开发
Lupino6 小时前
拯救迷失的荧光溶解氧传感器:从“三无”到“复活”的全记录
python
军军君016 小时前
Three.js基础功能学习十五:智能黑板实现实例二
开发语言·前端·javascript·vue.js·3d·threejs·三维
南境十里·墨染春水6 小时前
linux学习进展 进程的内存管理
linux·服务器·学习
Bert.Cai6 小时前
Linux cp命令详解
linux·运维
维齐洛波奇特利(male)6 小时前
@Pointcut(“execution(* com.hdzx..*(..))“)切入点与aop 导致无限循环
java·开发语言
一个人旅程~6 小时前
macOS装进移动硬盘成为双系统的操作方法
linux·经验分享·macos·电脑
xcjbqd06 小时前
SQL中视图能否嵌套存储过程_实现复杂自动化报表逻辑
jvm·数据库·python
ZC跨境爬虫6 小时前
海南大学交友平台开发实战day7(实现核心匹配算法+解决JSON请求报错问题)
前端·python·算法·html·json