議構(gòu)建天氣查詢工具:從原理到Node.js實戰(zhàn))
1. 項目概述為什么從MCP天氣查詢工具入手最近和幾個做AI應(yīng)用開發(fā)的朋友聊天發(fā)現(xiàn)大家不約而同地都在研究一個叫“模型上下文協(xié)議”的東西也就是MCP。這玩意兒聽起來挺高大上但說白了它就像給大語言模型比如ChatGPT、Claude裝上了一套標(biāo)準(zhǔn)化的“手”和“眼睛”。模型本身是個聰明的“大腦”但它沒法直接操作電腦、讀取文件、查詢網(wǎng)絡(luò)數(shù)據(jù)。MCP就是定義了一套標(biāo)準(zhǔn)方法讓“大腦”可以安全、可控地指揮“手”去執(zhí)行具體任務(wù)。那為什么選擇從“天氣查詢”這個工具開始呢原因很簡單它是一個絕佳的MCP入門練手項目。首先它的業(yè)務(wù)邏輯清晰——輸入地點返回天氣信息。其次它涉及了MCP最核心的幾個概念工具Tools的定義、服務(wù)器Server的實現(xiàn)、以及客戶端Client的調(diào)用。最后它需要與外部API天氣服務(wù)交互這正好體現(xiàn)了MCP“連接模型與現(xiàn)實世界”的核心價值。通過親手實現(xiàn)一個天氣查詢MCP工具你能把MCP的抽象概念迅速具象化理解數(shù)據(jù)是如何在模型、MCP服務(wù)器和外部服務(wù)之間流轉(zhuǎn)的。這比你讀十篇文檔都管用。2. 核心概念與工具選型構(gòu)建MCP的基石在動手寫代碼之前我們必須把幾個關(guān)鍵概念和工具理清楚。這就像蓋房子前得先認(rèn)識磚瓦和圖紙。2.1 MCP的三層架構(gòu)Server, Client ToolsMCP的架構(gòu)非常清晰主要包含三層MCP Server服務(wù)器這是你將要編寫的核心部分。它對外暴露一組定義好的“工具”Tools。你可以把它想象成一個“技能提供者”。在我們的天氣項目中這個服務(wù)器就提供了一個叫g(shù)et_weather的工具。MCP Client客戶端這是與大語言模型如Claude Desktop、Cursor等集成的部分??蛻舳素?fù)責(zé)與MCP Server建立連接獲取可用的工具列表并在模型需要時代表模型去調(diào)用服務(wù)器上的工具??蛻舳送ǔS葾I應(yīng)用平臺提供我們不需要從頭寫。Tools工具這是MCP協(xié)議中定義的“能力單元”。每個工具都有明確的名稱、描述、輸入?yún)?shù)input_schema和輸出。模型的“大腦”通過閱讀工具的描述來決定在什么時候、使用什么參數(shù)來調(diào)用它。對于我們開發(fā)者而言核心工作就是實現(xiàn)一個MCP Server并在其中定義好我們想讓模型使用的工具。2.2 為什么選擇Node.js和官方SDK實現(xiàn)MCP Server有多種語言選擇比如Python、TypeScript等。這里我強烈推薦使用TypeScript (Node.js)并結(jié)合modelcontextprotocol/sdk這個官方SDK。理由如下官方背書與活躍度這是由AnthropicClaude的創(chuàng)造者官方維護的SDK更新及時與協(xié)議標(biāo)準(zhǔn)同步性最好遇到問題也容易找到答案。開發(fā)體驗優(yōu)秀TypeScript提供了完善的類型提示SDK的封裝讓建立連接、定義工具、處理請求變得非常直觀能避免很多低級錯誤。生態(tài)成熟Node.js的異步和非阻塞I/O特性非常適合處理MCP這種多請求、需要調(diào)用外部網(wǎng)絡(luò)API的場景。NPM上有海量的包可以輔助開發(fā)比如我們馬上會用到的axios。注意雖然Python也有社區(qū)實現(xiàn)的庫但就目前的穩(wěn)定性和文檔完整性來看官方的Node.js SDK是新手入門阻力最小的選擇。2.3 天氣數(shù)據(jù)源的選擇與考量工具的核心是數(shù)據(jù)。為天氣查詢工具選擇一個可靠、免費或低成本、易于使用的數(shù)據(jù)源至關(guān)重要。這里有幾個常見選項OpenWeatherMap老牌服務(wù)提供免費層每分鐘60次調(diào)用數(shù)據(jù)全面文檔清晰。免費層需要注冊獲取API Key。WeatherAPI另一個流行的選擇免費層額度也不錯提供多種數(shù)據(jù)。和風(fēng)天氣國內(nèi)如果你主要查詢國內(nèi)地點這是個非常優(yōu)秀的選擇中文支持好免費額度足夠個人開發(fā)使用。我個人的選擇是 OpenWeatherMap。原因在于其國際覆蓋廣API設(shè)計規(guī)范社區(qū)資源多遇到問題容易搜索到解決方案。我們接下來的實現(xiàn)也將以它為例。關(guān)鍵一步請立即去 OpenWeatherMap官網(wǎng) 注冊一個免費賬戶獲取你的API Key。這個Key將用于在代碼中認(rèn)證你的請求。3. 手把手實現(xiàn)MCP天氣查詢服務(wù)器理論鋪墊完畢現(xiàn)在進(jìn)入實戰(zhàn)環(huán)節(jié)。請確保你的開發(fā)環(huán)境已經(jīng)安裝了Node.js (版本18或以上)和npm。3.1 項目初始化與依賴安裝首先創(chuàng)建一個新的項目目錄并初始化mkdir mcp-weather-server cd mcp-weather-server npm init -y接著安裝我們需要的核心依賴npm install modelcontextprotocol/sdk axios npm install --save-dev typescript ts-node types/nodemodelcontextprotocol/sdk MCP官方SDK。axios 一個優(yōu)秀的HTTP客戶端用于調(diào)用OpenWeatherMap的API。typescript,ts-node,types/node 用于TypeScript開發(fā)和執(zhí)行。然后初始化TypeScript配置npx tsc --init你可以根據(jù)需要修改生成的tsconfig.json一個簡單的可用于快速啟動的配置如下{ compilerOptions: { target: ES2022, module: commonjs, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src/**/*], exclude: [node_modules] }創(chuàng)建源代碼目錄和入口文件mkdir src touch src/index.ts3.2 構(gòu)建MCP服務(wù)器骨架現(xiàn)在打開src/index.ts開始編寫服務(wù)器的核心代碼。我們先從導(dǎo)入依賴和搭建基礎(chǔ)結(jié)構(gòu)開始import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import axios from axios; // 1. 定義工具Tool的輸入?yún)?shù)結(jié)構(gòu) // 這告訴MCP客戶端和模型調(diào)用get_weather工具時需要提供一個location字符串參數(shù)。 const WEATHER_TOOL { name: get_weather, description: 獲取指定城市或地區(qū)的當(dāng)前天氣信息。, inputSchema: { type: object, properties: { location: { type: string, description: 城市或地區(qū)名稱例如: Beijing, London, Tokyo, }, }, required: [location], }, }; // 2. 創(chuàng)建MCP服務(wù)器實例 // WeatherServer 是我們給這個服務(wù)器起的名字。 const server new Server( { name: WeatherServer, version: 1.0.0, }, { capabilities: { tools: {}, // 這里先留空我們會在后面動態(tài)添加工具處理邏輯 }, } );3.3 實現(xiàn)工具處理邏輯這是服務(wù)器的“大腦”。我們需要告訴服務(wù)器當(dāng)get_weather工具被調(diào)用時具體要執(zhí)行什么操作。// 3. 設(shè)置工具處理函數(shù) server.setRequestHandler(tools/call, async (request) { // 檢查被調(diào)用的工具名稱是否是我們定義的get_weather if (request.params.name WEATHER_TOOL.name) { const location (request.params.arguments as any).location; if (!location) { throw new Error(Location parameter is required.); } // 你的OpenWeatherMap API Key務(wù)必替換成你自己的 const API_KEY YOUR_OPENWEATHERMAP_API_KEY_HERE; // 使用標(biāo)準(zhǔn)單位攝氏度、米/秒等 const url https://api.openweathermap.org/data/2.5/weather?q${encodeURIComponent(location)}appid${API_KEY}unitsmetric; try { // 調(diào)用外部天氣API const response await axios.get(url); const data response.data; // 從API響應(yīng)中提取我們需要的信息 const weatherInfo { location: data.name, country: data.sys.country, temperature: ${data.main.temp}°C, feels_like: ${data.main.feels_like}°C, humidity: ${data.main.humidity}%, pressure: ${data.main.pressure} hPa, weather: data.weather[0].description, wind_speed: ${data.wind.speed} m/s, }; // 將結(jié)果格式化成易讀的文本返回給MCP客戶端最終給到大模型 const contentText 當(dāng)前 ${weatherInfo.location} (${weatherInfo.country}) 的天氣狀況 - 天氣${weatherInfo.weather} - 溫度${weatherInfo.temperature} (體感 ${weatherInfo.feels_like}) - 濕度${weatherInfo.humidity} - 氣壓${weatherInfo.pressure} - 風(fēng)速${weatherInfo.wind_speed} .trim(); return { content: [ { type: text, text: contentText, }, ], }; } catch (error: any) { // 錯誤處理網(wǎng)絡(luò)問題、城市未找到、API Key無效等 let errorMessage 無法獲取天氣信息。; if (axios.isAxiosError(error) error.response) { if (error.response.status 404) { errorMessage 未找到地點 ${location}請檢查名稱是否正確。; } else if (error.response.status 401) { errorMessage 天氣服務(wù)認(rèn)證失敗請檢查API Key。; } else { errorMessage 天氣服務(wù)返回錯誤: ${error.response.status}; } } // 將錯誤信息返回模型可以據(jù)此回復(fù)用戶 return { content: [ { type: text, text: 錯誤: ${errorMessage}, }, ], isError: true, }; } } // 如果收到其他未知工具的調(diào)用請求拋出錯誤 throw new Error(Unknown tool: ${request.params.name}); });3.4 啟動服務(wù)器與連接傳輸MCP服務(wù)器需要通過一種“傳輸”方式與客戶端通信。對于本地開發(fā)調(diào)試最常用、最簡單的方式是標(biāo)準(zhǔn)輸入輸出stdio。這意味著我們的服務(wù)器將通過命令行啟動并通過控制臺的輸入輸出來與客戶端如Claude Desktop交換數(shù)據(jù)。// 4. 啟動服務(wù)器 async function runServer() { // 使用Stdio傳輸這是與桌面客戶端集成的最常見方式 const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP Weather Server is running on stdio...); } // 5. 在服務(wù)器連接前告知客戶端本服務(wù)器提供哪些工具 // 這是關(guān)鍵一步客戶端在連接時會請求工具列表。 server.setRequestHandler(tools/list, async () { return { tools: [WEATHER_TOOL], }; }); runServer().catch((error) { console.error(Server fatal error:, error); process.exit(1); });至此一個完整的MCP天氣查詢服務(wù)器就編寫完成了。你的src/index.ts文件現(xiàn)在應(yīng)該包含了以上所有代碼塊。實操心得在開發(fā)過程中務(wù)必用你自己的真實API Key替換YOUR_OPENWEATHERMAP_API_KEY_HERE。一個常見的錯誤是忘記替換或誤將Key提交到公開的代碼倉庫這會導(dǎo)致API調(diào)用失敗或Key泄露。建議使用環(huán)境變量來管理敏感信息例如process.env.OPENWEATHER_API_KEY。4. 編譯、運行與測試代碼寫好了我們得讓它跑起來并驗證是否工作。4.1 編譯TypeScript并運行首先編譯TypeScript代碼到JavaScriptnpx tsc這會在dist目錄下生成index.js文件。更便捷的方式是使用ts-node直接運行省去編譯步驟特別適合開發(fā)階段npx ts-node src/index.ts如果一切正常你會看到MCP Weather Server is running on stdio...這條信息輸出到標(biāo)準(zhǔn)錯誤流stderr然后程序看起來就“掛起”了。這是正常的因為它正在等待來自標(biāo)準(zhǔn)輸入stdin的MCP協(xié)議消息。此時你需要一個MCP客戶端來連接它。4.2 使用MCP Inspector進(jìn)行本地測試在集成到Claude Desktop等大型應(yīng)用之前強烈建議先用一個輕量級的調(diào)試工具進(jìn)行測試。這就是MCP Inspector。全局安裝MCP Inspectornpm install -g modelcontextprotocol/inspector啟動Inspector并連接我們的服務(wù)器 我們需要告訴Inspector如何啟動我們的服務(wù)器。創(chuàng)建一個簡單的配置文件比如server-config.json{ mcpServers: { weather: { command: node, args: [/ABSOLUTE/PATH/TO/YOUR/mcp-weather-server/dist/index.js], env: { NODE_ENV: development } } } }注意args中的路徑必須替換為你項目dist/index.js的絕對路徑。如果使用ts-nodecommand可以是npxargs可以是[ts-node, /ABSOLUTE/PATH/TO/src/index.ts]。運行Inspectormcp-inspector --config ./server-config.json這會打開一個本地網(wǎng)頁通常是http://localhost:5173這就是MCP Inspector的界面。在Inspector中測試工具在Inspector網(wǎng)頁中你應(yīng)該能在左側(cè)看到連接的weather服務(wù)器。點擊它你會看到它提供的get_weather工具。在工具面板的輸入框里輸入{location: Beijing}。點擊 “Call Tool”。如果一切配置正確右側(cè)會顯示來自O(shè)penWeatherMap API的、格式化的北京天氣信息。成功這證明你的MCP服務(wù)器邏輯正確能夠處理請求、調(diào)用外部API并返回結(jié)果。4.3 集成到Claude Desktop可選但推薦真正的魅力在于讓AI模型使用你的工具。以Claude Desktop為例找到Claude Desktop的配置文件位置。macOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.json編輯這個JSON文件如果不存在則創(chuàng)建{ mcpServers: { weather: { command: node, args: [/ABSOLUTE/PATH/TO/YOUR/mcp-weather-server/dist/index.js] } } }同樣請?zhí)鎿Q為你的絕對路徑。如果使用ts-node配置方式同Inspector。重啟Claude Desktop。現(xiàn)在當(dāng)你和Claude對話時你可以直接說“幫我查一下東京的天氣。” Claude會自動識別出它有一個get_weather工具可用并在后臺調(diào)用你的服務(wù)器然后將結(jié)果融入它的回復(fù)中。整個過程無縫銜接用戶感知到的就是Claude“知道”了天氣。5. 進(jìn)階優(yōu)化與問題排查實錄一個基礎(chǔ)工具跑起來后我們可以讓它更健壯、更實用。下面分享幾個我在實際開發(fā)中總結(jié)的優(yōu)化點和常見坑位。5.1 功能優(yōu)化從基礎(chǔ)查詢到實用工具多單位支持讓用戶或模型可以選擇溫度單位攝氏/華氏。修改工具的inputSchema增加一個unit可選參數(shù)然后在調(diào)用API時根據(jù)這個參數(shù)決定units字段是metric還是imperial。位置模糊處理OpenWeatherMap對某些中文地名支持可能不完美??梢砸胍粋€地理位置解析服務(wù)如OpenCage Geocoder先將地名轉(zhuǎn)換為經(jīng)緯度再用經(jīng)緯度去查詢天氣提高準(zhǔn)確率。緩存機制天氣數(shù)據(jù)變化不頻繁頻繁調(diào)用API會浪費額度且慢。可以引入一個簡單的內(nèi)存緩存如node-cache將相同地點的結(jié)果緩存5-10分鐘。更豐富的信息除了當(dāng)前天氣OpenWeatherMap的API還提供預(yù)報、空氣質(zhì)量等。你可以定義更多工具如get_forecast或者擴展當(dāng)前工具的參數(shù)來返回更多數(shù)據(jù)。5.2 常見問題與解決方案速查表問題現(xiàn)象可能原因排查步驟與解決方案Inspector/Claude 無法連接服務(wù)器1. 配置文件路徑錯誤。2. Node命令執(zhí)行失敗。3. 服務(wù)器代碼有語法錯誤立即崩潰。1.檢查絕對路徑確保args中的路徑完全正確特別是使用ts-node時。一個技巧是先在對應(yīng)目錄下用命令行手動執(zhí)行該命令看是否成功。2.查看日志Claude Desktop會在其日志文件中記錄MCP服務(wù)器的啟動錯誤。去上述配置文件的同級目錄找日志文件。3.獨立運行測試在項目目錄下直接運行node dist/index.js觀察是否有錯誤輸出。調(diào)用工具返回“未找到地點”1. 輸入的地點名稱API不認(rèn)識。2. 地點名稱含有特殊字符或格式問題。1.嘗試英文名用“Beijing”而不是“北京”試試。2.URL編碼確保代碼中使用了encodeURIComponent(location)來處理輸入。3.提供更具體信息在工具描述中提示用戶輸入“城市名,國家代碼”格式如“London,GB”。返回“認(rèn)證失敗”1. API Key未設(shè)置或錯誤。2. API Key對應(yīng)的免費額度已用盡。1.檢查代碼確認(rèn)API_KEY變量已正確替換。2.訪問OpenWeatherMap網(wǎng)站登錄賬號在控制面板檢查API Key狀態(tài)和調(diào)用次數(shù)。服務(wù)器運行后無響應(yīng)或卡死1. 沒有正確處理請求或響應(yīng)格式不符合MCP協(xié)議。2.async/await使用不當(dāng)Promise未捕獲。1.使用Inspector調(diào)試Inspector能清晰顯示通信的原始JSON消息對比MCP協(xié)議文檔檢查你的請求處理器返回的結(jié)構(gòu)是否正確。2.強化錯誤處理確保所有可能的異常都被try...catch包裹并返回格式正確的錯誤信息給客戶端而不是讓進(jìn)程崩潰或掛起。Claude不主動使用工具1. 工具描述不夠清晰。2. 用戶提問方式不夠直接。1.優(yōu)化工具描述description字段要寫得非常清晰說明工具用途、輸入是什么。例如“獲取全球城市的當(dāng)前天氣情況需要提供城市名稱?!?.明確指令直接對Claude說“請使用天氣工具查詢XX的天氣”看它是否會調(diào)用。這是測試工具是否成功加載的好方法。5.3 安全與生產(chǎn)環(huán)境考量保護API Key永遠(yuǎn)不要將硬編碼的API Key提交到Git倉庫。使用環(huán)境變量.env文件配合dotenv包或運行時配置來管理。輸入驗證與清理雖然MCP客戶端和模型會進(jìn)行初步校驗但服務(wù)器端仍應(yīng)對location參數(shù)進(jìn)行基本的清理和驗證防止注入攻擊。限流與配額如果你的工具公開使用需要考慮對調(diào)用頻率進(jìn)行限制防止濫用耗盡你的API免費額度。6. 總結(jié)與延伸思考通過這個簡易的MCP天氣查詢工具項目我們完整走通了一個MCP Server從概念到實現(xiàn)、再到測試集成的全流程。你親手搭建了一個橋梁讓原本“困在”文本世界的大模型獲得了感知現(xiàn)實世界天氣的能力。這個項目的價值遠(yuǎn)不止于查詢天氣。它提供了一個可復(fù)用的范式。下一次當(dāng)你想讓AI模型幫你“讀取某個GitHub倉庫的最新Issue”、“查詢數(shù)據(jù)庫里的用戶數(shù)據(jù)”、“控制家里的智能燈光”時你只需要做同樣的事情定義一個工具實現(xiàn)一個MCP Server然后將它連接到你的AI助手。我個人在實踐中的體會是MCP最大的魅力在于標(biāo)準(zhǔn)化和生態(tài)。一旦工具按照協(xié)議實現(xiàn)它就可以被任何支持MCP的客戶端Claude, Cursor, 未來可能更多的AI應(yīng)用所使用。這意味著你的一次開發(fā)可以賦能多個AI入口。隨著協(xié)議的發(fā)展或許未來會出現(xiàn)一個豐富的“MCP工具商店”開發(fā)者可以共享工具用戶則可以像安裝插件一樣為自己AI助手?jǐn)U展各種超能力。從這個小工具出發(fā)你可以嘗試更復(fù)雜的場景比如一個需要多步交互的工具先搜索再選擇最后執(zhí)行或者結(jié)合多個API的工具鏈。MCP的世界剛剛打開更多的可能性正等待被構(gòu)建。