1.创建使用npm init -y创建一个空白的项目
1.1新建一个空白的文件夹,打开终端,直接执行npm init -y 执行成功之后效果如下,会生产一个package.json文件
用idea或其他开发工具打开项目,将package.json的内容替换掉
{
"name": "mcptest-lmk",
"version": "1.0.0",
"description": "测试mcp服务发布",
"type": "module",
"bin": {
"mcptest-lmk": "build/weather-server.js"
},
"files": [
"build"
],
"scripts": {
},
"keywords": [
"mcp",
"weather",
"ai",
"claude",
"cline",
"meizu"
],
"author": "yingzi",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"node-fetch": "^3.3.2",
"string-similarity": "^4.0.4",
"zod": "^3.22.4",
"dotenv": "^16.4.7",
"mysql2": "^3.11.5"
},
"devDependencies": {
"nodemon": "^3.0.3"
}
}
运行npm install安装依赖
2.MCP服务器代码编写
MCP服务器开发官网文档
官网上有详细的server代码教程
2.1我们这里直接在build文件夹下新建一个weather-server.js文件,因为等会打包上传到npm的时候只有build下的文件才会上传,为了简单就直接这样操作了,正常的node开发应该是要使用打包指令打包项目的。
2.2 具体实现代码如下,直接复制即可用
#!/usr/bin/env node
console.log("Hello World!");
import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
import fetch from "node-fetch";
import {z} from "zod";
// 创建MCP服务器
const server = new McpServer({
name: "weather-mcp",
version: "1.0.0"
});
// 城市ID映射表(部分常用城市)
const CITY_MAP = {
"北京": "101010100",
"上海": "101020100",
"武汉": "101200101",
// 可以按需添加更多城市
};
// 注册城市天气查询工具
server.tool(
"query_weather",
{
city: z.string().describe("要查询天气的城市名称,如北京、上海、广州等")
},
async ({city}) => {
try {
let cityId = CITY_MAP[city];
// 如果城市不在映射中,返回提示信息
if (!cityId) {
return {
content: [{
type: "text",
text: `暂不支持查询${city}的天气信息。目前支持的城市有: ${Object.keys(CITY_MAP).join("、")}`
}]
};
}
// 构建魅族天气API URL
const url = `http://aider.meizu.com/app/weather/listWeather?cityIds=${cityId}`;
// 发送请求获取天气数据
const response = await fetch(url);
if (!response.ok) {
throw new Error(`天气API返回错误: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// 检查API返回状态
if (data.code !== "200" || !data.value || !data.value[0]) {
throw new Error(`获取天气数据失败: ${data.message || "未知错误"}`);
}
const weatherData = data.value[0];
// 获取实时天气
const realtime = weatherData.realtime;
// 获取今日天气
const today = weatherData.weathers.find(w => {
const today = new Date();
const dateStr = `${today.getFullYear()}${String(today.getMonth() + 1).padStart(2, '0')}${String(today.getDate()).padStart(2, '0')}`;
return w.date === dateStr;
}) || weatherData.weathers[0];
// 获取生活指数
const indexes = weatherData.indexes || [];
// 格式化天气信息
let result = `🌍 ${weatherData.city} ${weatherData.provinceName} 实时天气\n`;
result += `⏰ ${realtime.time}\n\n`;
result += `🌡 气温温度: ${realtime.temp}℃ (体感温度: ${realtime.sendibleTemp}℃)\n`;
result += `🌤 天气状况: ${realtime.weather}\n`;
result += `💨 湿度: ${realtime.sd}%\n`;
result += `🌬 风向风力: ${realtime.wd} ${realtime.ws}\n\n`;
result += `🌡 今日温度: ${today.temp_day_c}℃ / ${today.temp_night_c}℃\n`;
result += `☀️ 日出/日落: ${today.sun_rise_time} / ${today.sun_down_time}\n\n`;
// 添加空气质量信息(如果有)
if (weatherData.pm25) {
result += `😷 空气质量: ${weatherData.pm25.quality} (AQI: ${weatherData.pm25.aqi})\n`;
result += `💭 PM2.5: ${weatherData.pm25.pm25}, PM10: ${weatherData.pm25.pm10}\n\n`;
}
// 添加生活指数
if (indexes.length > 0) {
result += `🔖 生活指数参考\n`;
indexes.forEach(index => {
// 添加对应的emoji
let emoji = "📌";
switch (index.abbreviation) {
case "ct":
emoji = "🌡";
break; // 穿衣
case "pp":
emoji = "☂";
break; // 化妆
case "mm":
emoji = "🎭";
break; // 感冒
case "xc":
emoji = "🧺";
break; // 洗车
case "yd":
emoji = "🏃";
break; // 运动
case "uv":
emoji = "☀";
break; // 紫外线
}
result += `${emoji} ${index.name}(${index.level}): ${index.content}\n`;
});
}
return {
content: [{type: "text", text: result.trim()}]
};
} catch (error) {
console.error("获取天气时出错了:", error);
return {
content: [{type: "text", text: `查询天气时出错: ${error.message}`}],
isError: true
};
}
}
);
// 注册天气预报工具
server.tool(
"query_forecast",
{
city: z.string().describe("要查询天气预报的城市名称")
},
async ({city}) => {
try {
let cityId = CITY_MAP[city];
// 如果城市不在映射表中,返回提示信息
if (!cityId) {
return {
content: [{
type: "text",
text: `暂不支持查询${city}的天气预报。目前支持的城市有: ${Object.keys(CITY_MAP).join("、")}`
}]
};
}
// 构建魅族天气API URL
const url = `http://aider.meizu.com/app/weather/listweather?cityIds=${cityId}`;
// 发送请求获取天气数据
const response = await fetch(url);
if (!response.ok) {
throw new Error(`天气API返回错误: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// 检查API返回状态
if (data.code !== "200" || !data.value || !data.value[0]) {
throw new Error(`获取天气数据失败: ${data.message || "未知错误"}`);
}
const weatherData = data.value[0];
// 获取天气预报
const forecasts = weatherData.weathers || [];
// 按日期排序(确保顺序正确)
forecasts.sort((a, b) => {
const dateA = new Date(a.date);
const dateB = new Date(b.date);
return dateA - dateB;
});
// 排除过去的日期
const today = new Date();
today.setHours(0, 0, 0, 0);
// 过滤出未来天气预报
const futureForecasts = forecasts.filter(forecast => {
const forecastDate = new Date(forecast.date);
return forecastDate >= today;
});
// 格式化天气预报
let result = `🌍 ${weatherData.city}未来天气预报:\n\n`;
futureForecasts.forEach((forecast, index) => {
const date = new Date(forecast.date);
const monthDay = `${date.getMonth() + 1}月${date.getDate()}日`;
// 添加天气图标
let weatherEmoji = "🌤";
if (forecast.weather.includes("晴")) weatherEmoji = "☀";
else if (forecast.weather.includes("雨")) weatherEmoji = "🌧";
else if (forecast.weather.includes("云")) weatherEmoji = "☁";
else if (forecast.weather.includes("阴")) weatherEmoji = "⛅";
else if (forecast.weather.includes("雪")) weatherEmoji = "❄";
result += `${index === 0 ? "📅 今天" : "📅 " + monthDay} ${forecast.week}:\n`;
result += `${weatherEmoji} ${forecast.weather}\n`;
result += `🌡 ${forecast.temp_day_c}℃ / ${forecast.temp_night_c}℃\n`;
result += `☀ ${forecast.sun_rise_time} - ${forecast.sun_down_time}\n\n`;
});
// 添加生活小贴士
const indexes = weatherData.indexes || [];
if (indexes.length > 0) {
const randomIndex = Math.floor(Math.random() * indexes.length);
result += `💡 今日小贴士: ${indexes[randomIndex].content}\n`;
}
return {
content: [{type: "text", text: result.trim()}]
};
} catch (error) {
console.error("获取天气预报时出错:", error);
return {
content: [{type: "text", text: `查询天气预报时出错: ${error.message}`}],
isError: true
};
}
}
);
// 注册每小时天气预报工具
server.tool(
"query_hourly_forecast",
{
city: z.string().describe("要查询每小时天气预报的城市名称")
},
async ({city}) => {
try {
let cityId = CITY_MAP[city];
console.log('1111111111111111',city)
debugger
// 如果城市不在映射表中,返回提示信息
if (!cityId) {
return {
content: [{
type: "text",
text: `暂不支持查询${city}的精细天气预报。目前支持的城市有: ${Object.keys(CITY_MAP).join("、")}`
}]
};
}
// 构建魅族天气API URL
const url = `http://aider.meizu.com/app/weather/listweather?cityIds=${cityId}`;
// 发送请求获取天气数据
const response = await fetch(url);
if (!response.ok) {
throw new Error(`天气API返回错误: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// 检查API返回状态
if (data.code !== "200" || !data.value || !data.value[0]) {
throw new Error(`获取天气数据失败: ${data.message || "未知错误"}`);
}
const weatherData = data.value[0];
// 获取精细天气预报
const hourlyForecasts = weatherData.weatherDetailsInfo?.weather3HoursDetailsInfos || [];
if (hourlyForecasts.length === 0) {
return {
content: [{type: "text", text: `暂无${city}未来几小时的精细天气预报数据`}]
};
}
// 格式化精细天气预报
let result = `🌍 ${weatherData.city}未来3小时天气预报:\n\n`;
hourlyForecasts.forEach(forecast => {
const startTime = new Date(forecast.startTime);
const endTime = new Date(forecast.endTime);
const startHour = startTime.getHours();
const endHour = endTime.getHours();
// 添加天气图标
let weatherEmoji = "🌤";
if (forecast.weather.includes("晴")) weatherEmoji = "☀";
else if (forecast.weather.includes("雨")) weatherEmoji = "🌧";
else if (forecast.weather.includes("云")) weatherEmoji = "☁";
else if (forecast.weather.includes("阴")) weatherEmoji = "⛅";
else if (forecast.weather.includes("雪")) weatherEmoji = "❄";
result += `⏰ ${startHour}:00-${endHour}:00\n`;
result += `${weatherEmoji} ${forecast.weather}\n`;
result += `🌡 ${forecast.lowestTemperature}℃ - ${forecast.highestTemperature}℃\n`;
// 添加降水信息(如果有)
if (forecast.precipitation && forecast.precipitation !== "0") {
result += `💧 降水量: ${forecast.precipitation}mm\n`;
}
result += `\n`;
});
return {
content: [{type: "text", text: result.trim()}]
};
} catch (error) {
console.error("获取精细天气预报时出错:", error);
return {
content: [{type: "text", text: `获取精细天气预报时出错: ${error.message}`}],
isError: true
};
}
}
);
server.tool(
"getEmployeeInfo",
{
emName: z.string().describe("要查询的员工的姓名,(例如张三,李四)")
},
async ({emName}) => {
try {
return {
content: [{type: "text", text: "成功进入工具,AI识别到的参数列表," + emName.toString()}]
};
} catch (error) {
console.error("要查询的员工的姓名出错:", error);
return {
content: [{type: "text", text: `要查询的员工的姓名出错: ${error.message}`}],
isError: true
};
}
}
);
// 启动服务器
async function main() {
try {
// 打印服务器启动信息
console.log("启动天气查询的MCP服务器...");
// 使用标准输入/输出作为传输方式
const transport = new StdioServerTransport();
await server.connect(transport);
console.log("MCP服务器已经开始等待连接");
} catch (error) {
console.error("启动服务器时出错:", error);
process.exit(1);
}
}
main();
主要注册了四个工具方法,具体的逻辑代码不重要,可以随意编写,本文重点讲关于发布到npm以及使用mcp客户端连接的问题
2.3 使用Cherry Studio本地测试
服务代码写完之后就可以使用客户端先本地连接测试了
CherryStudio如何开启MCP服务这里也不多说了,直接上CherryStudio查看,很简单
打开MCP服务加入配置
"mcp本地测试": {
"name": "mcp本地测试",
"type": "stdio",
"isActive": true,
"command": "node",
"args": [
"D:\\Zszj2025D\\MCPServerTest\\build\\weather-server.js"
]
}
args中的路径参数一定要指向我们编写的那个js文件,使用绝对路径
参数配置号之后点保存,亮绿灯了,并且下面出现可用工具列表就说明连接成功了,
2.4本地连接效果测试
点击上方的模型可以选择模型提问,右侧带一个小扳手的说明模型支持函数调用功能,只有支持函数调用功能的才可以使用MCP服务,
点击菜单栏的mcp服务器按钮,选择我们刚刚添加的服务,就可以开始提问了
可以看到调用了我们代码李德query_forecast方法,由于我们代码中不包含杭州这个城市,所有不支持查询,我们再问一次
3测试了工具没问题就可以开始发布了
3.1 发布到npm官网
没有npm账号的要先去注册一个,注册成功之后在终端使用npm login登录一下就行
我们一般使用的都是淘宝镜像,现在要换成npm的官网镜像
npm config set registry https://registry.npmjs.org/
切换之后再使用npm whoami查看登录状态,出现了我们注册的用户名说明没问题,可以开始发布了
发布之前我们要对package.json进行一些编辑,package.json中name是我们发布之后的报名,在npm官网中是唯一的,先用先得,bin和file是mcp的重要配置,files是我们要上传的文件,bin是代表npm的一个指令,如果在终端执行这个指令,实际上运行的是build/weather-server.js文件
"name": "mcptest-lmk",
"version": "1.0.0",
"description": "测试mcp服务发布",
"type": "module",
"bin": {
"mcptest-lmk": "build/weather-server.js"
},
"files": [
"build"
],
一切准备就绪,使用npm publish发布,发布成功,我的包名是mcptest-lmk,可以直接用这个包名去npm官网搜索了,也可以查看自己的个人空间
在个人空间可以看到我们刚刚发布的包
3.2 使用CherryStudio连接我们发布的包
使用之前要先npm i mcptest-lmk -g ,把包下载到本地,实际上也是一种本地连接,只是包上传到了npm,让其他人方便使用
执行npm i mcptest-lmk -g ,全局安装这个包
在CherryStudio中添加配置,与上面的本地配置不一样的是command指令改成npx,args中的参数改成我们package.json文件中的bin配置的指令名,我这里配置的是mcptest-lmk,与包名一致
"mcp发包测试": {
"name": "mcp发包测试",
"type": "stdio",
"isActive": true,
"command": "npx",
"args": [
"mcptest-lmk"
]
}
配置号之后点保存,也是直接亮绿灯了,可以使用了
可以看到工具方法是可以正常调用的,只是方法内部出现了问题,这里的问题是
我们方法里面访问的一个天气api崩了, http://aider.meizu.com/app/weather/listweather?cityIds,并不是连接上的问题,