之前深入分析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如何离线加载和使用模型