
深入探索 Model Context Protocol:从理论到实践 原创 精华
在当今快速发展的技术领域,人工智能和机器学习的创新不断涌现。其中,Model Context Protocol(MCP)作为一种新兴的开源协议,正逐渐成为研究和应用的热点。虽然目前人们更多地关注 MCP 在工具集成方面的表现,但其在上下文管理方面的强大能力,才是真正值得我们深入探讨的核心价值。
一、什么是 Model Context Protocol?
Model Context Protocol(MCP)是由 Anthropic 开发的一种开源协议,旨在解决大型语言模型(LLMs)在处理上下文信息时所面临的诸多挑战。它不仅关注工具的集成,更注重如何高效地管理和优化上下文信息,从而提升模型在实际应用中的表现。
二、MCP 的上下文管理能力:解决 LLMs 的核心痛点
(一)上下文窗口限制
大型语言模型的上下文窗口是有限的,这意味着它们在处理长篇对话或复杂任务时,可能会因为信息量过大而无法有效处理。MCP 提供了一种标准化的方法,能够对对话历史进行管理、优先级排序和压缩,从而最大化地利用有限的上下文空间。这就好比在一个拥挤的图书馆里,通过合理安排书架和书籍,让有限的空间能够容纳更多的知识。
(二)有状态的对话
传统的语言模型对话往往是无状态的,即每次交互都是独立的,无法记住之前的对话内容。而 MCP 通过在模型之外维护对话状态,使得对话能够跨越单次交互,实现更长、更连贯的交流。想象一下,当你与朋友聊天时,你们可以随时回忆起之前的对话内容,这种连续性让交流更加自然和深入。
(三)记忆管理
在处理大量信息时,MCP 能够选择性地保留重要信息,同时丢弃不相关的内容,为 AI 系统创建一个更高效的“工作记忆”。这就像人类的大脑,能够自动筛选出重要的记忆,而忽略那些无关紧要的细节,从而提高信息处理的效率。
(四)跨会话的上下文共享
通过合理的实现,MCP 可以在不同的会话甚至不同的模型之间保持上下文的连续性。这意味着用户在不同设备或不同场景下使用 AI 服务时,依然能够享受到无缝的体验。这就好比你在不同的手机或电脑上登录同一个账号,依然能够看到之前的浏览记录和设置。
(五)结构化的知识表示
MCP 不再将上下文视为简单的字符序列,而是能够以更结构化的方式表示知识。这使得 AI 系统能够更高效地理解和处理信息,就像将杂乱无章的文件整理成有序的文件夹,方便查找和使用。
(六)检索增强
MCP 提供了一个框架,能够动态地从外部源检索相关信息,并将其整合到上下文中。这就好比在做研究时,你可以随时从图书馆或互联网上获取最新的资料,丰富你的知识库。
三、为什么上下文管理如此重要?
尽管工具集成在 MCP 中备受关注,因为它能够让语言模型“行动”起来,但上下文管理能力同样具有革命性。它解决了语言模型在时间维度上与信息交互的根本限制。实际上,有效的上下文管理是工具使用的基础——模型需要理解之前使用了哪些工具、这些工具返回了什么信息,以及这些信息与当前对话状态的关系。
四、实践中的 MCP:一个简单的服务器与客户端示例
为了更好地理解 MCP 的实际应用,我们可以通过一个简单的服务器和客户端示例来展示其上下文管理能力。
(一)搭建 MCP 服务器
首先,我们需要创建一个本地的 MCP 服务器。在你的 MacBook 上,打开终端窗口,创建一个名为 mcp_context_server.py
的文件,并将以下代码复制到文件中并保存。
# mcp_context_server.py
import json
import uuid
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qs, urlparse
# In-memory storage for context/sessions
context_store = {}
# Simple weather database
WEATHER_DATA = {
"New York": {"temperature": 72, "condition": "Sunny", "humidity": 65},
"London": {"temperature": 60, "condition": "Rainy", "humidity": 80},
"Tokyo": {"temperature": 75, "condition": "Partly Cloudy", "humidity": 70},
"Sydney": {"temperature": 80, "condition": "Clear", "humidity": 55},
"Paris": {"temperature": 68, "condition": "Cloudy", "humidity": 75}
}
class SessionContext:
"""Represents a user session with context data"""
def __init__(self, session_id):
self.session_id = session_id
self.created_at = time.time()
self.last_accessed = time.time()
self.data = {
"recent_searches": [],
"preferred_unit": "celsius",
"visits": 0
}
def update_access(self):
self.last_accessed = time.time()
self.data["visits"] += 1
def add_search(self, city):
# Keep only the 5 most recent searches
if city notin self.data["recent_searches"]:
self.data["recent_searches"].insert(0, city)
self.data["recent_searches"] = self.data["recent_searches"][:5]
def set_preference(self, key, value):
self.data[key] = value
def to_dict(self):
return {
"session_id": self.session_id,
"created_at": self.created_at,
"last_accessed": self.last_accessed,
"data": self.data
}
class MCPRequestHandler(BaseHTTPRequestHandler):
def _set_headers(self, content_type='application/json'):
self.send_response(200)
self.send_header('Content-type', content_type)
# Now set the cookie if needed
if hasattr(self, 'should_set_cookie') and self.should_set_cookie:
self.send_header('Set-Cookie', f'session_id={self.new_session_id}; Path=/')
self.end_headers()
def _get_or_create_session(self):
# Check if client sent a session cookie
session_id = None
if'Cookie'in self.headers:
cookies = self.headers['Cookie'].split('; ')
for cookie in cookies:
if cookie.startswith('session_id='):
session_id = cookie.split('=')[1]
break
# Create new session if none exists or if it's expired
ifnot session_id or session_id notin context_store:
session_id = str(uuid.uuid4())
context_store[session_id] = SessionContext(session_id)
# Don't set the cookie header here - it's too early!
self.should_set_cookie = True# Flag to set cookie when sending headers
self.new_session_id = session_id
else:
self.should_set_cookie = False
# Update last accessed time
context_store[session_id].update_access()
return context_store[session_id]
def _clean_expired_sessions(self, max_age=3600):# 1 hour
"""Remove sessions that haven't been used for max_age seconds"""
current_time = time.time()
expired_keys = []
for key, session in context_store.items():
if current_time - session.last_accessed > max_age:
expired_keys.append(key)
for key in expired_keys:
del context_store[key]
def do_GET(self):
self._clean_expired_sessions()
parsed_url = urlparse(self.path)
path = parsed_url.path
query = parse_qs(parsed_url.query)
session = self._get_or_create_session()
# Get user's temperature preference
unit = query.get('unit', [session.data.get('preferred_unit')])[0]
# Set temperature unit preference if specified
if'unit'in query:
session.set_preference('preferred_unit', unit)
if path == '/api/weather':
self._set_headers()
# Get city from query parameters
if'city'in query:
city = query['city'][0]
session.add_search(city)
if city in WEATHER_DATA:
data = WEATHER_DATA[city].copy()
# Convert temperature based on user preference
if unit == 'fahrenheit'and'temperature'in data:
# Data is stored in Fahrenheit
pass
elif unit == 'celsius'and'temperature'in data:
# Convert Fahrenheit to Celsius
data['temperature'] = round((data['temperature'] - 32) * 5/9, 1)
response = {
"city": city,
"weather": data,
"unit": unit
}
else:
response = {"error": f"City '{city}' not found"}
else:
response = {"cities": list(WEATHER_DATA.keys())}
# Include session context data in response
response["context"] = session.to_dict()
self.wfile.write(json.dumps(response).encode())
elif path == '/api/context':
self._set_headers()
response = {"context": session.to_dict()}
self.wfile.write(json.dumps(response).encode())
else:
# Serve a simple HTML interface for manual testing
self._set_headers('text/html')
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>MCP Weather Service</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
.card {{ border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin-bottom: 15px; }}
.recent {{ color: #666; font-size: 0.9em; }}
</style>
</head>
<body>
<h1>MCP Weather Service</h1>
<div class="card">
<h2>Your Session</h2>
<p>Session ID: {session.session_id}</p>
<p>Number of visits: {session.data["visits"]}</p>
<p>Temperature unit preference: {session.data["preferred_unit"]}</p>
<form>
<label>
Change temperature unit:
<select name="unit" notallow="this.form.submit()">
<option value="celsius" {"selected" if session.data["preferred_unit"] == "celsius" else ""}>Celsius</option>
<option value="fahrenheit" {"selected" if session.data["preferred_unit"] == "fahrenheit" else ""}>Fahrenheit</option>
</select>
</label>
</form>
</div>
<div class="card">
<h2>Get Weather</h2>
<form actinotallow="/" method="get">
<select name="city">
{' '.join([f'<option value="{city}">{city}</option>' for city in WEATHER_DATA.keys()])}
</select>
<button type="submit">Get Weather</button>
</form>
</div>
{"<div class='card'><h2>Your Recent Searches</h2><ul>" +
''.join([f'<li><a href="/?city={city}">{city}</a></li>' for city in session.data["recent_searches"]]) +
"</ul></div>" if session.data["recent_searches"] else ""}
{"<div class='card'><h2>Weather Result</h2>" +
f"<h3>Weather in {query['city'][0]}</h3>" +
f"<p>Temperature: {WEATHER_DATA[query['city'][0]]['temperature']}°F " +
f"({round((WEATHER_DATA[query['city'][0]]['temperature'] - 32) * 5/9, 1)}°C)</p>" +
f"<p>Condition: {WEATHER_DATA[query['city'][0]]['condition']}</p>" +
f"<p>Humidity: {WEATHER_DATA[query['city'][0]]['humidity']}%</p></div>"
if 'city' in query and query['city'][0] in WEATHER_DATA else ""}
</body>
</html>
"""
self.wfile.write(html.encode())
def do_POST(self):
self._clean_expired_sessions()
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
request_json = json.loads(post_data.decode('utf-8'))
session = self._get_or_create_session()
if self.path == '/api/preferences':
# Update user preferences
for key, value in request_json.items():
session.set_preference(key, value)
self._set_headers()
response = {
"status": "success",
"message": "Preferences updated",
"context": session.to_dict()
}
self.wfile.write(json.dumps(response).encode())
else:
self.send_response(404)
self.end_headers()
self.wfile.write(json.dumps({"error": "Not found"}).encode())
def run_server(port=8000):
server_address = ('', port)
httpd = HTTPServer(server_address, MCPRequestHandler)
print(f"Starting MCP context-aware server on port {port}...")
httpd.serve_forever()
if __name__ == "__main__":
run_server()
_set_headers() response = {"context": session.to_dict()} self.wfile.write(json.dumps(response).encode())
else:
# Serve a simple HTML interface for manual testing
self._set_headers('text/html')
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>MCP Weather Service</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
.card {{ border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin-bottom: 15px; }}
.recent {{ color: #666; font-size: 0.9em; }}
</style>
</head>
<body>
<h1>MCP Weather Service</h1>
<div class="card">
<h2>Your Session</h2>
<p>Session ID: {session.session_id}</p>
<p>Number of visits: {session.data["visits"]}</p>
<p>Temperature unit preference: {session.data["preferred_unit"]}</p>
<form>
<label>
Change temperature unit:
<select name="unit" notallow="this.form.submit()">
<option value="celsius" {"selected" if session.data["preferred_unit"] == "celsius" else ""}>Celsius</option>
<option value="fahrenheit" {"selected" if session.data["preferred_unit"] == "fahrenheit" else ""}>Fahrenheit</option>
</select>
</label>
</form>
</div>
<div class="card">
<h2>Get Weather</h2>
<form actinotallow="/" method="get">
<select name="city">
{' '.join([f'<option value="{city}">{city}</option>' for city in WEATHER_DATA.keys()])}
</select>
<button type="submit">Get Weather</button>
</form>
</div>
{"<div class='card'><h2>Your Recent Searches</h2><ul>" +
''.join([f'<li><a href="/?city={city}">{city}</a></li>' for city in session.data["recent_searches"]]) +
"</ul></div>" if session.data["recent_searches"] else ""}
{"<div class='card'><h2>Weather Result</h2>" +
f"<h3>Weather in {query['city'][0]}</h3>" +
f"<p>Temperature: {WEATHER_DATA[query['city'][0]]['temperature']}°F " +
f"({round((WEATHER_DATA[query['city'][0]]['temperature'] - 32) * 5/9, 1)}°C)</p>" +
f"<p>Condition: {WEATHER_DATA[query['city'][0]]['condition']}</p>" +
f"<p>Humidity: {WEATHER_DATA[query['city'][0]]['humidity']}%</p></div>"
if 'city' in query and query['city'][0] in WEATHER_DATA else ""}
</body>
</html>
"""
self.wfile.write(html.encode())
def do_POST(self):
self._clean_expired_sessions()
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
request_json = json.loads(post_data.decode('utf-8'))
session = self._get_or_create_session()
if self.path == '/api/preferences':
# Update user preferences
for key, value in request_json.items():
session.set_preference(key, value)
self._set_headers()
response = {
"status": "success",
"message": "Preferences updated",
"context": session.to_dict()
}
self.wfile.write(json.dumps(response).encode())
else:
self.send_response(404)
self.end_headers()
self.wfile.write(json.dumps({"error": "Not found"}).encode())
def run_server(port=8000): server_address = ('', port) httpd = HTTPServer(server_address, MCPRequestHandler) print(f"Starting MCP context-aware server on port {port}...") httpd.serve_forever()
if name == "main": run_server()
运行服务器的命令如下:
```bash
python3 mcp_context_server.py
当服务器运行时,终端窗口会显示服务器正在运行的状态,命令提示符不会返回,这表明服务器已经成功启动。
(二)创建 MCP 客户端
接下来,我们需要创建一个客户端来与服务器交互。创建一个名为 mcp_context_client.py
的文件,并将以下代码复制到文件中。
# mcp_context_client.py
import json
import requests
import sys
import os
class WeatherClient:
def __init__(self, server_url="http://localhost:8000"):
self.server_url = server_url
self.session = requests.Session() # Use a session to maintain cookies
self.context = None
# Try to load saved session ID from file
self.session_file = "session.json"
if os.path.exists(self.session_file):
try:
with open(self.session_file, "r") as f:
saved_data = json.load(f)
if"session_id"in saved_data:
self.session.cookies.set("session_id", saved_data["session_id"])
if"context"in saved_data:
self.context = saved_data["context"]
print("Restored previous session")
except Exception as e:
print(f"Error loading session: {e}")
def save_session(self):
"""Save the session ID and context to a file"""
if"session_id"in self.session.cookies:
session_data = {
"session_id": self.session.cookies.get("session_id"),
"context": self.context
}
with open(self.session_file, "w") as f:
json.dump(session_data, f)
def get_cities(self):
"""Get list of available cities"""
response = self.session.get(f"{self.server_url}/api/weather")
data = response.json()
# Update context if available
if"context"in data:
self.context = data["context"]
self.save_session()
return data["cities"]
def get_weather(self, city, unit=None):
"""Get weather for a specific city"""
url = f"{self.server_url}/api/weather?city={city}"
# Use stored preference or provided unit
if unit:
url += f"&unit={unit}"
elif self.context and"data"in self.context and"preferred_unit"in self.context["data"]:
url += f"&unit={self.context['data']['preferred_unit']}"
response = self.session.get(url)
data = response.json()
# Update context if available
if"context"in data:
self.context = data["context"]
self.save_session()
return data
def update_preferences(self, preferences):
"""Update user preferences"""
response = self.session.post(
f"{self.server_url}/api/preferences",
jsnotallow=preferences
)
data = response.json()
# Update context if available
if"context"in data:
self.context = data["context"]
self.save_session()
return data
def get_context(self):
"""Get current session context"""
ifnot self.context:
response = self.session.get(f"{self.server_url}/api/context")
data = response.json()
self.context = data["context"]
self.save_session()
return self.context
def display_weather(self, weather_data):
"""Display weather information nicely"""
if"error"in weather_data:
print(f"\nError: {weather_data['error']}")
return
city = weather_data["city"]
weather = weather_data["weather"]
unit = weather_data.get("unit", "celsius")
unit_symbol = "°F"if unit == "fahrenheit"else"°C"
print(f"\nWeather for {city}:")
print(f"Temperature: {weather['temperature']}{unit_symbol}")
print(f"Condition: {weather['condition']}")
print(f"Humidity: {weather['humidity']}%")
def display_context(self):
"""Display current context information"""
context = self.get_context()
ifnot context:
print("\nNo context available")
return
print("\n=== Your Session Context ===")
print(f"Session ID: {context['session_id']}")
print(f"Created: {context['created_at']}")
print(f"Last accessed: {context['last_accessed']}")
print("\nPreferences:")
print(f"Temperature unit: {context['data']['preferred_unit']}")
print(f"Total visits: {context['data']['visits']}")
if context['data']['recent_searches']:
print("\nRecent searches:")
for i, city in enumerate(context['data']['recent_searches'], 1):
print(f"{i}. {city}")
def run_client():
client = WeatherClient()
whileTrue:
print("\n--- MCP Weather Client with Context ---")
print("1. List all cities")
print("2. Get weather for a city")
print("3. Change temperature unit preference")
print("4. View session context")
print("5. Exit")
choice = input("Enter your choice (1-5): ")
if choice == "1":
cities = client.get_cities()
print("\nAvailable cities:")
for city in cities:
print(f"- {city}")
elif choice == "2":
city = input("Enter city name: ")
try:
weather_data = client.get_weather(city)
client.display_weather(weather_data)
except Exception as e:
print(f"Error getting weather: {e}")
elif choice == "3":
unit = input("Choose temperature unit (celsius/fahrenheit): ").lower()
if unit in ["celsius", "fahrenheit"]:
try:
result = client.update_preferences({"preferred_unit": unit})
print(f"Temperature unit updated to {unit}")
except Exception as e:
print(f"Error updating preferences: {e}")
else:
print("Invalid unit. Please enter 'celsius' or 'fahrenheit'.")
elif choice == "4":
client.display_context()
elif choice == "5":
print("Exiting weather client.")
sys.exit(0)
else:
print("Invalid choice. Please try again.")
if __name__ == "__main__":
run_client()
运行客户端的命令如下:
```bash
python3 mcp_context_client.py
通过这个简单的客户端,你可以与服务器进行交互,查询天气信息、更改温度单位偏好设置,以及查看当前会话的上下文信息。这不仅展示了 MCP 在上下文管理方面的强大功能,还体现了其在实际应用中的灵活性和实用性。
五、MCP 上下文管理的关键特性
通过上述服务器和客户端的实现,我们可以总结出 MCP 在上下文管理方面的几个关键特性:
(一)为每个客户端创建唯一的会话 ID
服务器会为每个连接的客户端生成一个唯一的会话 ID,并通过 cookie 机制将其传递给客户端。这个会话 ID 是上下文管理的核心,它使得服务器能够识别和跟踪每个用户的会话状态。
(二)使用 cookie 维护会话
通过 cookie,客户端在每次请求时都会携带会话 ID,服务器可以根据这个 ID 恢复用户的上下文信息。这种方式不仅简单高效,还能确保会话信息在多次请求之间的连续性。
(三)上下文数据跨多次请求持久化
在 MCP 的上下文管理中,用户的偏好设置(如温度单位)、使用历史(如最近搜索的城市)以及会话统计信息(如访问次数、会话创建时间)等数据都会被持久化。这意味着即使用户关闭客户端后重新连接,服务器依然能够恢复之前的上下文状态。
(四)上下文数据元素
- 用户偏好设置:例如温度单位的选择(摄氏或华氏),这些偏好设置会根据用户的操作进行更新,并在后续的交互中被应用。
- 使用历史:记录用户的最近搜索记录,方便用户快速查找之前查询过的信息。
- 会话统计信息:包括会话的创建时间、最后访问时间以及访问次数等,这些信息有助于服务器进行会话管理和优化。
六、MCP 的实际应用场景与未来展望
(一)实际应用场景
MCP 的上下文管理能力使其在多种实际应用场景中具有巨大的潜力。例如:
- 智能客服系统:通过维护用户的对话历史和偏好设置,MCP 可以让智能客服系统提供更加个性化和连贯的服务。用户无需重复说明问题,系统能够根据上下文直接提供解决方案。
- 智能语音助手:在语音交互场景中,MCP 可以帮助语音助手更好地理解用户的意图,并根据上下文提供更准确的回答。例如,用户可以连续提问,而助手能够根据之前的对话内容进行回答。
- 多模态应用:在结合文本、图像和语音的多模态应用中,MCP 的上下文管理能力可以确保不同模态之间的信息能够无缝衔接,提升用户体验。
(二)未来展望
随着人工智能技术的不断发展,MCP 的上下文管理能力将为语言模型的应用带来更多的可能性。未来,我们可以期待:
- 更高效的上下文压缩技术:随着模型规模的增大,上下文窗口的限制将更加明显。MCP 将不断优化上下文压缩技术,以更好地利用有限的上下文空间。
- 跨平台的上下文共享:MCP 将进一步扩展其跨平台能力,使得用户在不同的设备和应用之间能够无缝切换,同时保持一致的上下文体验。
- 与更多工具的集成:MCP 的工具连接能力将与上下文管理能力深度融合,为用户提供更加智能化和自动化的解决方案。
七、总结
Model Context Protocol(MCP)不仅仅是一个工具集成的协议,更是一个强大的上下文管理框架。它通过解决语言模型在上下文窗口限制、有状态对话、记忆管理、跨会话共享、结构化知识表示和检索增强等方面的痛点,为人工智能的应用带来了全新的可能性。
通过实际的服务器和客户端示例,我们看到了 MCP 在上下文管理方面的强大功能。它不仅能够为用户提供更加个性化和连贯的交互体验,还能在多种实际应用场景中发挥重要作用。
随着技术的不断进步,MCP 的未来充满了无限可能。它将不断优化和扩展其功能,为人工智能的发展提供更加坚实的基础。让我们拭目以待,看看 MCP 将如何改变我们与人工智能的交互方式。
如果你对这篇文章感兴趣,不妨在 Medium 上给我点赞,或者在 LinkedIn 和 X 上关注我,获取更多关于 MCP 和人工智能的最新资讯。
本文转载自公众号Halo咯咯 作者:基咯咯
原文链接:https://mp.weixin.qq.com/s/f2v0M5Dt53MXtEh_zRrl0w
