no checksums to verify
installing to /Users/nicolas/.local/bin
uv
uvx
everything's installed!

(2)安裝所需的依賴包

# Create a new directory for our project
uv init weather
cd weather

# Create virtual environment and activate it
uv venv
source .venv/bin/activate

# Install dependencies
uv add "mcp[cli]" httpx

# Create our server file
touch server.py

(3)在server.py中構(gòu)建相應(yīng)的get-alerts和 get-forecast工具:

from typing import Any
import asyncio
import httpx
from mcp.server.models import InitializationOptions
import mcp.types as types
from mcp.server import NotificationOptions, Server
import mcp.server.stdio
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

#@server.list_tools() - 注冊用于列出可用工具的處理器
#@server.call_tool() - 注冊用于執(zhí)行工具調(diào)用的處理器

server = Server("weather")

@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""
List available tools.
Each tool specifies its arguments using JSON Schema validation.
"""
return [
types.Tool(
name="get-alerts",
description="Get weather alerts for a state",
inputSchema={
"type": "object",
"properties": {
"state": {
"type": "string",
"description": "Two-letter state code (e.g. CA, NY)",
},
},
"required": ["state"],
},
),
types.Tool(
name="get-forecast",
description="Get weather forecast for a location",
inputSchema={
"type": "object",
"properties": {
"latitude": {
"type": "number",
"description": "Latitude of the location",
},
"longitude": {
"type": "number",
"description": "Longitude of the location",
},
},
"required": ["latitude", "longitude"],
},
),
]

async def make_nws_request(client: httpx.AsyncClient, url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json"
}

try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None

def format_alert(feature: dict) -> str:
"""Format an alert feature into a concise string."""
props = feature["properties"]
return (
f"Event: {props.get('event', 'Unknown')}\n"
f"Area: {props.get('areaDesc', 'Unknown')}\n"
f"Severity: {props.get('severity', 'Unknown')}\n"
f"Status: {props.get('status', 'Unknown')}\n"
f"Headline: {props.get('headline', 'No headline')}\n"
"---"
)

@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""
Handle tool execution requests.
Tools can fetch weather data and notify clients of changes.
"""
if not arguments:
raise ValueError("Missing arguments")

if name == "get-alerts":
state = arguments.get("state")
if not state:
raise ValueError("Missing state parameter")

# Convert state to uppercase to ensure consistent format
state = state.upper()
if len(state) != 2:
raise ValueError("State must be a two-letter code (e.g. CA, NY)")

async with httpx.AsyncClient() as client:
alerts_url = f"{NWS_API_BASE}/alerts?area={state}"
alerts_data = await make_nws_request(client, alerts_url)

if not alerts_data:
return [types.TextContent(type="text", text="Failed to retrieve alerts data")]

features = alerts_data.get("features", [])
if not features:
return [types.TextContent(type="text", text=f"No active alerts for {state}")]

# Format each alert into a concise string
formatted_alerts = [format_alert(feature) for feature in features[:20]] # only take the first 20 alerts
alerts_text = f"Active alerts for {state}:\n\n" + "\n".join(formatted_alerts)

return [
types.TextContent(
type="text",
text=alerts_text
)
]
elif name == "get-forecast":
try:
latitude = float(arguments.get("latitude"))
longitude = float(arguments.get("longitude"))
except (TypeError, ValueError):
return [types.TextContent(
type="text",
text="Invalid coordinates. Please provide valid numbers for latitude and longitude."
)]

# Basic coordinate validation
if not (-90 <= latitude <= 90) or not (-180 <= longitude <= 180):
return [types.TextContent(
type="text",
text="Invalid coordinates. Latitude must be between -90 and 90, longitude between -180 and 180."
)]

async with httpx.AsyncClient() as client:
# First get the grid point
lat_str = f"{latitude}"
lon_str = f"{longitude}"
points_url = f"{NWS_API_BASE}/points/{lat_str},{lon_str}"
points_data = await make_nws_request(client, points_url)

if not points_data:
return [types.TextContent(type="text", text=f"Failed to retrieve grid point data for coordinates: {latitude}, {longitude}. This location may not be supported by the NWS API (only US locations are supported).")]

# Extract forecast URL from the response
properties = points_data.get("properties", {})
forecast_url = properties.get("forecast")

if not forecast_url:
return [types.TextContent(type="text", text="Failed to get forecast URL from grid point data")]

# Get the forecast
forecast_data = await make_nws_request(client, forecast_url)

if not forecast_data:
return [types.TextContent(type="text", text="Failed to retrieve forecast data")]

# Format the forecast periods
periods = forecast_data.get("properties", {}).get("periods", [])
if not periods:
return [types.TextContent(type="text", text="No forecast periods available")]

# Format each period into a concise string
formatted_forecast = []
for period in periods:
forecast_text = (
f"{period.get('name', 'Unknown')}:\n"
f"Temperature: {period.get('temperature', 'Unknown')}°{period.get('temperatureUnit', 'F')}\n"
f"Wind: {period.get('windSpeed', 'Unknown')} {period.get('windDirection', '')}\n"
f"{period.get('shortForecast', 'No forecast available')}\n"
"---"
)
formatted_forecast.append(forecast_text)

forecast_text = f"Forecast for {latitude}, {longitude}:\n\n" + "\n".join(formatted_forecast)

return [types.TextContent(
type="text",
text=forecast_text
)]
else:
raise ValueError(f"Unknown tool: {name}")

async def main():
# Run the server using stdin/stdout streams
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="weather",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)

# This is needed if you'd like to connect to a custom client
if __name__ == "__main__":
asyncio.run(main())

這段代碼中,最核心的其實(shí)就是@server.list_tools() 以及 @server.call_tool() 這兩個(gè)注解。

@server.list_tools() - 注冊用于列出可用工具的處理器
@server.call_tool() - 注冊用于執(zhí)行工具調(diào)用的處理器

調(diào)用函數(shù)的邏輯也比較簡單,匹配到對應(yīng)的工具名稱,然后抽取對應(yīng)的輸入?yún)?shù),然后發(fā)起api的請求,對獲得的結(jié)果進(jìn)行處理:

async def main():
# Run the server using stdin/stdout streams
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="weather",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)

# This is needed if you'd like to connect to a custom client
if __name__ == "__main__":
asyncio.run(main())

(4)服務(wù)端與客戶端交互

測試服務(wù)器與 Claude for Desktop。【2】也給出了構(gòu)建MCP 客戶端的教程。其中核心的邏輯如下:

async def process_query(self, query: str) -> str:
"""Process a query using Claude and available tools"""
messages = [
{
"role": "user",
"content": query
}
]

response = await self.session.list_tools()
available_tools = [{
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema
} for tool in response.tools]

# Initial Claude API call
response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
messages=messages,
tools=available_tools
)

# Process response and handle tool calls
final_text = []

assistant_message_content = []
for content in response.content:
if content.type == 'text':
final_text.append(content.text)
assistant_message_content.append(content)
elif content.type == 'tool_use':
tool_name = content.name
tool_args = content.input

# Execute tool call
result = await self.session.call_tool(tool_name, tool_args)
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")

assistant_message_content.append(content)
messages.append({
"role": "assistant",
"content": assistant_message_content
})
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": content.id,
"content": result.content
}
]
})

# Get next response from Claude
response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
messages=messages,
tools=available_tools
)

final_text.append(response.content[0].text)

return "\n".join(final_text)

啟動(dòng)客戶端,需要打開 Claude for Desktop 應(yīng)用配置文件:

~/Library/Application Support/Claude/claude_desktop_config.json

如果該文件不存在,確保先創(chuàng)建出來,然后配置以下信息,以示例說明,我們uv init的是weather,所以這里mcpServers配置weather的服務(wù),args中的路徑設(shè)置為你weather的絕對路徑。

{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"server.py"
]
}
}
}

保存文件,并重新啟動(dòng) Claude for Desktop。可以看到Claude for Desktop 能夠識別在天氣服務(wù)器中暴露的兩個(gè)工具。

然后在客戶端詢問天氣,會(huì)提示調(diào)用get-forecast的tool:

3、MCP到底解決了什么問題

工具是智能體框架的重要組成部分,允許大模型與外界互動(dòng)并擴(kuò)展其能力。即使沒有MCP協(xié)議,也是可以實(shí)現(xiàn)LLM智能體,只不過存在幾個(gè)弊端,當(dāng)有許多不同的 API 時(shí),啟用工具使用變得很麻煩,因?yàn)槿魏喂ぞ叨夹枰菏謩?dòng)構(gòu)建prompt,每當(dāng)其 API 發(fā)生變化時(shí)手動(dòng)更新【3,4】。

如下圖所示:

MCP其實(shí)解決了當(dāng)存在大量工具時(shí),能夠自動(dòng)發(fā)現(xiàn),并自動(dòng)構(gòu)建prompt。

整體流程示例:
(1)以總結(jié)git項(xiàng)目最近5次提交為例,MCP 主機(jī)(與客戶端一起)將首先調(diào)用 MCP 服務(wù)器,詢問有哪些工具可用。

MCP 主機(jī):像 Claude Desktop、IDE 或其他 AI 工具等程序,希望通過 MCP 訪問數(shù)據(jù)。
MCP 客戶端:與服務(wù)器保持 1:1 連接 的協(xié)議客戶端。

(2)MPC 客戶端接收到所列出的可用工具后,發(fā)給LLM,LLM 收到信息后,可能會(huì)選擇使用某個(gè)工具。它通過主機(jī)向 MCP 服務(wù)器發(fā)送請求,然后接收結(jié)果,包括所使用的工具。

(3)LLM 收到工具處理結(jié)果(包括原始的query等信息),之后就可以向用戶輸出最終的答案。

總結(jié)起來,就一句話,MCP協(xié)議其實(shí)是讓智能體更容易管理、發(fā)現(xiàn)、使用工具。

文章轉(zhuǎn)載自:【大模型實(shí)戰(zhàn)篇】基于Claude MCP協(xié)議的智能體落地示例

熱門推薦
一個(gè)賬號試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
3000+提示詞助力AI大模型
和專業(yè)工程師共享工作效率翻倍的秘密
熱門推薦
一個(gè)賬號試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
返回頂部
上一篇
零代碼打造高效 AI Agents:初學(xué)者快速上手指南
下一篇
MCP 企業(yè)級落地案例架構(gòu)設(shè)計(jì)和落地實(shí)戰(zhàn)
国内精品久久久久影院日本,日本中文字幕视频,99久久精品99999久久,又粗又大又黄又硬又爽毛片
日本中文字幕一区二区有限公司| 久久久99久久精品欧美| 欧美日韩免费一区二区三区 | 久草这里只有精品视频| 欧美丰满嫩嫩电影| 日韩高清在线不卡| 欧美一区二区三区在线| 日本欧美肥老太交大片| 日韩欧美成人激情| 国产成人在线免费观看| 国产精品天天摸av网| 色婷婷综合久久久久中文| 亚洲五码中文字幕| 亚洲精品一区二区三区香蕉| 成人美女视频在线观看| 亚洲精品国产品国语在线app| 欧美色视频在线观看| 五月天视频一区| 久久久久久亚洲综合影院红桃 | 久久精品国产一区二区三区免费看| 日韩你懂的电影在线观看| 国产一区二区三区| 亚洲精品久久久久久国产精华液| 精品欧美乱码久久久久久1区2区| 91亚洲精品一区二区乱码| 日韩成人午夜精品| 国产精品乱子久久久久| 51久久夜色精品国产麻豆| 成人精品高清在线| 蜜臀av性久久久久蜜臀aⅴ流畅| 国产日韩欧美精品一区| 欧美丰满一区二区免费视频| 成人av片在线观看| 免费一区二区视频| 一区二区三区产品免费精品久久75| 日韩一区二区三区在线| 91捆绑美女网站| 久久99国产精品尤物| 亚洲免费观看视频| 国产欧美一区二区精品忘忧草| 欧美欧美欧美欧美| 色婷婷av一区二区三区软件| 国产一二精品视频| 首页国产欧美久久| 亚洲国产成人精品视频| 最新热久久免费视频| 久久久久久久久久看片| 日韩欧美国产系列| 欧美一区二区三区免费观看视频 | 亚洲18色成人| 亚洲无人区一区| 亚洲第一在线综合网站| 亚洲男同性恋视频| 国产精品久久久久影院亚瑟| 中文字幕不卡在线观看| 国产精品久久久久四虎| 中文字幕欧美一| 一区二区三区在线观看网站| 夜夜爽夜夜爽精品视频| 亚洲123区在线观看| 奇米777欧美一区二区| 青青草97国产精品免费观看无弹窗版 | 成人综合婷婷国产精品久久 | 日韩黄色小视频| 欧美成人一级视频| 欧美三级韩国三级日本一级| 欧美精品一区二区久久婷婷| 久久综合中文字幕| 国产欧美综合在线| 中文字幕一区三区| 亚洲自拍都市欧美小说| 丝袜美腿亚洲色图| 国产精品亚洲成人| 欧美性大战久久久久久久蜜臀| 777奇米成人网| 久久久国产一区二区三区四区小说 | 欧美高清在线一区二区| 最新中文字幕一区二区三区| 亚洲国产日韩在线一区模特| 全部av―极品视觉盛宴亚洲| 国产美女一区二区三区| 91同城在线观看| 日韩免费观看2025年上映的电影 | 三级欧美在线一区| 国产麻豆午夜三级精品| 在线视频你懂得一区| 欧美xxxxxxxxx| 亚洲丝袜精品丝袜在线| 欧美aaaaa成人免费观看视频| 成人avav在线| 欧美v日韩v国产v| 亚洲视频在线一区观看| 久久99国产精品麻豆| 色菇凉天天综合网| 国产欧美一区二区精品性色| 日韩不卡一区二区| 色婷婷综合久久| 亚洲国产成人一区二区三区| 日本sm残虐另类| 欧美视频中文一区二区三区在线观看| 26uuu国产在线精品一区二区| 午夜精品久久久久久久久久久| 不卡一区在线观看| 久久久久国产免费免费| 美日韩一级片在线观看| 在线视频你懂得一区二区三区| 国产亚洲精品精华液| 国产呦精品一区二区三区网站| 欧美精品一级二级三级| 亚洲午夜国产一区99re久久| 色94色欧美sute亚洲线路一久 | 麻豆成人综合网| 日韩一区二区三区高清免费看看| 亚洲精品高清视频在线观看| 粉嫩aⅴ一区二区三区四区五区| 日韩精品最新网址| 精品综合免费视频观看| 欧美一区二区三区的| 欧美a级一区二区| 91精品国产黑色紧身裤美女| 麻豆成人久久精品二区三区红 | 欧美丝袜丝交足nylons| 三级成人在线视频| 日韩欧美中文一区| 国产在线视频不卡二| 久久久www成人免费无遮挡大片 | 一区二区三区日本| 欧美日韩一二三| 七七婷婷婷婷精品国产| 亚洲精品一区二区精华| 国产成人精品亚洲午夜麻豆| 国产精品久久免费看| 色诱视频网站一区| 日韩激情视频在线观看| 欧美不卡在线视频| 国产91综合网| 亚洲精品网站在线观看| 欧美少妇bbb| 激情五月婷婷综合网| 国产精品全国免费观看高清| 91成人免费电影| 精品伊人久久久久7777人| 国产欧美在线观看一区| 欧美专区日韩专区| 激情久久久久久久久久久久久久久久| 国产片一区二区三区| 884aa四虎影成人精品一区| 国产成人av在线影院| 亚洲精品国产品国语在线app| 欧美一级日韩一级| 99re这里只有精品首页| 蜜臀av在线播放一区二区三区 | 久久夜色精品一区| 色丁香久综合在线久综合在线观看| 黄页网站大全一区二区| 亚洲一区二区在线免费观看视频 | 亚洲午夜精品网| 国产精品嫩草99a| 精品国产欧美一区二区| 欧美亚洲图片小说| av电影天堂一区二区在线| 久久精品国产精品亚洲精品| 亚洲风情在线资源站| 国产亚洲精品超碰| 日韩欧美一级特黄在线播放| 欧美综合一区二区| www.66久久| 成人免费视频app| 国产成人精品一区二区三区网站观看| 日韩影院精彩在线| 午夜国产精品一区| 亚洲一级片在线观看| 一区二区视频免费在线观看| 亚洲人午夜精品天堂一二香蕉| 亚洲国产精品精华液ab| 2023国产精品视频| 久久综合九色综合97婷婷| 欧美r级电影在线观看| 精品久久一区二区| 精品国产制服丝袜高跟| 精品久久国产老人久久综合| 欧美tk—视频vk| 国产精品视频yy9299一区| 国产精品免费观看视频| 1区2区3区国产精品| 亚洲精品成人悠悠色影视| 亚洲激情网站免费观看| 亚洲精品精品亚洲| 五月天一区二区三区| 青娱乐精品在线视频| 免费看日韩a级影片| 国产一区二区看久久| 成人激情电影免费在线观看| 91蜜桃免费观看视频| 欧美日韩免费观看一区二区三区 | 成人一级片在线观看| 欧美午夜一区二区三区| 欧美一个色资源| 国产欧美精品区一区二区三区 | 欧美日韩在线精品一区二区三区激情|