open-meteo-mcp-weather-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/MCP_SO_SUBMISSION.md +63 -0
- package/README.md +105 -0
- package/index.js +196 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# mcp.so Submission
|
|
2
|
+
|
|
3
|
+
Submit page:
|
|
4
|
+
|
|
5
|
+
https://mcp.so/zh/submit
|
|
6
|
+
|
|
7
|
+
Recommended fields:
|
|
8
|
+
|
|
9
|
+
- Type: MCP Server
|
|
10
|
+
- Name: my-weather-server-0706
|
|
11
|
+
- URL: https://github.com/yangvue/my-weather-server-0706
|
|
12
|
+
- Description: MCP server for querying current weather by city using Open-Meteo. It provides a `get_weather` tool and does not require an API key.
|
|
13
|
+
|
|
14
|
+
Server Config:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"my-weather-server-0706": {
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["-y", "open-meteo-mcp-weather-server"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Server Config for GitHub/local source:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"my-weather-server-0706": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "github:yangvue/my-weather-server-0706"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Server Config for local checkout:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"my-weather-server-0706": {
|
|
46
|
+
"command": "node",
|
|
47
|
+
"args": ["/absolute/path/to/my-mcp-server/index.js"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Tools:
|
|
54
|
+
|
|
55
|
+
- `get_weather`: Get current real weather for a city.
|
|
56
|
+
|
|
57
|
+
Example input:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"city": "上海"
|
|
62
|
+
}
|
|
63
|
+
```
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# my-weather-server-0706
|
|
2
|
+
|
|
3
|
+
MCP server for querying current weather by city. It uses Open-Meteo geocoding and forecast APIs, and exposes one MCP tool: `get_weather`.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### `get_weather`
|
|
8
|
+
|
|
9
|
+
Get current weather for a city.
|
|
10
|
+
|
|
11
|
+
Input:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"city": "上海"
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Output:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
通过 my-weather-server-0706 返回:上海 当前天气是:阴天
|
|
23
|
+
定位:上海,上海市,中国
|
|
24
|
+
温度:...
|
|
25
|
+
体感温度:...
|
|
26
|
+
湿度:...
|
|
27
|
+
降水:...
|
|
28
|
+
云量:...
|
|
29
|
+
风速:...
|
|
30
|
+
风向:...
|
|
31
|
+
更新时间:...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
From GitHub:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx -y github:yangvue/my-weather-server-0706
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
From npm, once published:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx -y open-meteo-mcp-weather-server
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
From this repository:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
npm start
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## MCP Configuration
|
|
56
|
+
|
|
57
|
+
Use this config to run from GitHub:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"my-weather-server-0706": {
|
|
63
|
+
"command": "npx",
|
|
64
|
+
"args": ["-y", "github:yangvue/my-weather-server-0706"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use this config after the package is published to npm:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"my-weather-server-0706": {
|
|
76
|
+
"command": "npx",
|
|
77
|
+
"args": ["-y", "open-meteo-mcp-weather-server"]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Use this config when running from a local checkout:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"my-weather-server-0706": {
|
|
89
|
+
"command": "node",
|
|
90
|
+
"args": ["/absolute/path/to/my-mcp-server/index.js"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Data Source
|
|
97
|
+
|
|
98
|
+
- Geocoding: Open-Meteo Geocoding API
|
|
99
|
+
- Weather: Open-Meteo Forecast API
|
|
100
|
+
|
|
101
|
+
No API key is required.
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { appendFileSync } from "node:fs";
|
|
10
|
+
|
|
11
|
+
const GEOCODING_API_URL = "https://geocoding-api.open-meteo.com/v1/search";
|
|
12
|
+
const WEATHER_API_URL = "https://api.open-meteo.com/v1/forecast";
|
|
13
|
+
const LOG_FILE_URL = new URL("./weather-server.log", import.meta.url);
|
|
14
|
+
|
|
15
|
+
const server = new Server({
|
|
16
|
+
name: "my-weather-server-0706",
|
|
17
|
+
version: "1.0.0"
|
|
18
|
+
}, {
|
|
19
|
+
capabilities: { tools: {} }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const weatherCodeMap = new Map([
|
|
23
|
+
[0, "晴朗"],
|
|
24
|
+
[1, "大部晴朗"],
|
|
25
|
+
[2, "局部多云"],
|
|
26
|
+
[3, "阴天"],
|
|
27
|
+
[45, "有雾"],
|
|
28
|
+
[48, "有霜雾"],
|
|
29
|
+
[51, "小毛毛雨"],
|
|
30
|
+
[53, "中等毛毛雨"],
|
|
31
|
+
[55, "大毛毛雨"],
|
|
32
|
+
[56, "小冻毛毛雨"],
|
|
33
|
+
[57, "大冻毛毛雨"],
|
|
34
|
+
[61, "小雨"],
|
|
35
|
+
[63, "中雨"],
|
|
36
|
+
[65, "大雨"],
|
|
37
|
+
[66, "小冻雨"],
|
|
38
|
+
[67, "大冻雨"],
|
|
39
|
+
[71, "小雪"],
|
|
40
|
+
[73, "中雪"],
|
|
41
|
+
[75, "大雪"],
|
|
42
|
+
[77, "雪粒"],
|
|
43
|
+
[80, "小阵雨"],
|
|
44
|
+
[81, "中等阵雨"],
|
|
45
|
+
[82, "强阵雨"],
|
|
46
|
+
[85, "小阵雪"],
|
|
47
|
+
[86, "强阵雪"],
|
|
48
|
+
[95, "雷暴"],
|
|
49
|
+
[96, "雷暴伴小冰雹"],
|
|
50
|
+
[99, "雷暴伴大冰雹"],
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
function logServer(message) {
|
|
54
|
+
const line = `[my-weather-server-0706] ${new Date().toISOString()} ${message}`;
|
|
55
|
+
console.error(line);
|
|
56
|
+
appendFileSync(LOG_FILE_URL, `${line}\n`, "utf8");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
logServer(`startup pid=${process.pid} cwd=${process.cwd()} entry=${import.meta.url}`);
|
|
60
|
+
|
|
61
|
+
async function fetchJson(url, apiName) {
|
|
62
|
+
const response = await fetch(url, {
|
|
63
|
+
headers: {
|
|
64
|
+
"User-Agent": "my-weather-server-0706/1.0",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const errorText = await response.text();
|
|
70
|
+
throw new Error(`${apiName} 请求失败:HTTP ${response.status} ${errorText}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
if (data.error) {
|
|
75
|
+
throw new Error(`${apiName} 请求失败:${data.reason ?? "未知错误"}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function findLocation(city) {
|
|
82
|
+
const url = new URL(GEOCODING_API_URL);
|
|
83
|
+
url.searchParams.set("name", city);
|
|
84
|
+
url.searchParams.set("count", "1");
|
|
85
|
+
url.searchParams.set("language", "zh");
|
|
86
|
+
url.searchParams.set("format", "json");
|
|
87
|
+
|
|
88
|
+
const data = await fetchJson(url, "地理编码 API");
|
|
89
|
+
const location = data.results?.[0];
|
|
90
|
+
if (!location) {
|
|
91
|
+
throw new Error(`没有找到城市:${city}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return location;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function fetchCurrentWeather(location) {
|
|
98
|
+
const url = new URL(WEATHER_API_URL);
|
|
99
|
+
url.searchParams.set("latitude", String(location.latitude));
|
|
100
|
+
url.searchParams.set("longitude", String(location.longitude));
|
|
101
|
+
url.searchParams.set(
|
|
102
|
+
"current",
|
|
103
|
+
[
|
|
104
|
+
"temperature_2m",
|
|
105
|
+
"relative_humidity_2m",
|
|
106
|
+
"apparent_temperature",
|
|
107
|
+
"precipitation",
|
|
108
|
+
"weather_code",
|
|
109
|
+
"cloud_cover",
|
|
110
|
+
"wind_speed_10m",
|
|
111
|
+
"wind_direction_10m",
|
|
112
|
+
].join(",")
|
|
113
|
+
);
|
|
114
|
+
url.searchParams.set("timezone", "auto");
|
|
115
|
+
|
|
116
|
+
const data = await fetchJson(url, "天气 API");
|
|
117
|
+
if (!data.current) {
|
|
118
|
+
throw new Error("天气 API 没有返回当前天气数据");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function formatWindDirection(degrees) {
|
|
125
|
+
if (typeof degrees !== "number") {
|
|
126
|
+
return "未知";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const directions = ["北", "东北", "东", "东南", "南", "西南", "西", "西北"];
|
|
130
|
+
return directions[Math.round(degrees / 45) % directions.length];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatWeather(city, location, weatherData) {
|
|
134
|
+
const current = weatherData.current;
|
|
135
|
+
const units = weatherData.current_units ?? {};
|
|
136
|
+
const weatherText = weatherCodeMap.get(current.weather_code) ?? `天气代码 ${current.weather_code}`;
|
|
137
|
+
const locationName = [location.name, location.admin1, location.country]
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
.join(",");
|
|
140
|
+
|
|
141
|
+
return [
|
|
142
|
+
`通过 my-weather-server-0706 返回:${city} 当前天气是:${weatherText}`,
|
|
143
|
+
`定位:${locationName}`,
|
|
144
|
+
`温度:${current.temperature_2m}${units.temperature_2m ?? "°C"}`,
|
|
145
|
+
`体感温度:${current.apparent_temperature}${units.apparent_temperature ?? "°C"}`,
|
|
146
|
+
`湿度:${current.relative_humidity_2m}${units.relative_humidity_2m ?? "%"}`,
|
|
147
|
+
`降水:${current.precipitation}${units.precipitation ?? "mm"}`,
|
|
148
|
+
`云量:${current.cloud_cover}${units.cloud_cover ?? "%"}`,
|
|
149
|
+
`风速:${current.wind_speed_10m}${units.wind_speed_10m ?? "km/h"}`,
|
|
150
|
+
`风向:${formatWindDirection(current.wind_direction_10m)}(${current.wind_direction_10m}${units.wind_direction_10m ?? "°"})`,
|
|
151
|
+
`更新时间:${current.time}`,
|
|
152
|
+
].join("\n");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 定义工具
|
|
156
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
157
|
+
tools: [{
|
|
158
|
+
name: "get_weather",
|
|
159
|
+
description: "通过 my-weather-server-0706 获取指定城市的当前真实天气",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
city: { type: "string", description: "城市名称" }
|
|
164
|
+
},
|
|
165
|
+
required: ["city"]
|
|
166
|
+
}
|
|
167
|
+
}]
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
// 执行逻辑
|
|
171
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
172
|
+
if (request.params.name !== "get_weather") {
|
|
173
|
+
throw new Error(`未知工具:${request.params.name}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const city = request.params.arguments?.city;
|
|
177
|
+
if (typeof city !== "string" || city.trim() === "") {
|
|
178
|
+
throw new Error("city 参数必须是非空字符串");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const normalizedCity = city.trim();
|
|
182
|
+
logServer(`callTool name=get_weather city=${normalizedCity}`);
|
|
183
|
+
|
|
184
|
+
const location = await findLocation(normalizedCity);
|
|
185
|
+
const weatherData = await fetchCurrentWeather(location);
|
|
186
|
+
logServer(
|
|
187
|
+
`get_weather success location=${location.name},${location.admin1 ?? ""},${location.country ?? ""} weather_code=${weatherData.current.weather_code}`
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: "text", text: formatWeather(normalizedCity, location, weatherData) }]
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const transport = new StdioServerTransport();
|
|
196
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "open-meteo-mcp-weather-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server that provides current weather by city using Open-Meteo.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"my-weather-server-0706": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/yangvue/my-weather-server-0706.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/yangvue/my-weather-server-0706#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/yangvue/my-weather-server-0706/issues"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "node index.js",
|
|
19
|
+
"inspect:tools": "npx @modelcontextprotocol/inspector node index.js"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"weather",
|
|
25
|
+
"open-meteo",
|
|
26
|
+
"china"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
33
|
+
}
|
|
34
|
+
}
|