@albertmcp/mcp 0.1.3
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/dist/index.js +225 -0
- package/package.json +43 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Ref: https://modelcontextprotocol.io/quickstart
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
const NWS_API_BASE = "https://api.weather.gov";
|
|
8
|
+
const USER_AGENT = "weather-app/1.0";
|
|
9
|
+
// Define Zod schemas for validation
|
|
10
|
+
const AlertsArgumentsSchema = z.object({
|
|
11
|
+
state: z.string().length(2),
|
|
12
|
+
});
|
|
13
|
+
const ForecastArgumentsSchema = z.object({
|
|
14
|
+
latitude: z.number().min(-90).max(90),
|
|
15
|
+
longitude: z.number().min(-180).max(180),
|
|
16
|
+
});
|
|
17
|
+
// Create server instance
|
|
18
|
+
const server = new Server({
|
|
19
|
+
name: "weather",
|
|
20
|
+
version: "1.0.0",
|
|
21
|
+
}, {
|
|
22
|
+
capabilities: {
|
|
23
|
+
tools: {},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
// List available tools
|
|
27
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
28
|
+
return {
|
|
29
|
+
tools: [
|
|
30
|
+
{
|
|
31
|
+
name: "get-alerts",
|
|
32
|
+
description: "Get weather alerts for a US state",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
state: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Two-letter US state code (e.g. CA, NY)",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
required: ["state"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "get-forecast",
|
|
46
|
+
description: "Get weather forecast for a location in the US",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
latitude: {
|
|
51
|
+
type: "number",
|
|
52
|
+
description: "Latitude of the location",
|
|
53
|
+
},
|
|
54
|
+
longitude: {
|
|
55
|
+
type: "number",
|
|
56
|
+
description: "Longitude of the location",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["latitude", "longitude"],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
// Helper function for making NWS API requests
|
|
66
|
+
async function makeNWSRequest(url) {
|
|
67
|
+
const headers = {
|
|
68
|
+
"User-Agent": USER_AGENT,
|
|
69
|
+
Accept: "application/geo+json",
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(url, { headers });
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
75
|
+
}
|
|
76
|
+
return (await response.json());
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error("Error making NWS request:", error);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Format alert data
|
|
84
|
+
function formatAlert(feature) {
|
|
85
|
+
const props = feature.properties;
|
|
86
|
+
return [
|
|
87
|
+
`Event: ${props.event || "Unknown"}`,
|
|
88
|
+
`Area: ${props.areaDesc || "Unknown"}`,
|
|
89
|
+
`Severity: ${props.severity || "Unknown"}`,
|
|
90
|
+
`Status: ${props.status || "Unknown"}`,
|
|
91
|
+
`Headline: ${props.headline || "No headline"}`,
|
|
92
|
+
"---",
|
|
93
|
+
].join("\n");
|
|
94
|
+
}
|
|
95
|
+
// Handle tool execution
|
|
96
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
97
|
+
const { name, arguments: args } = request.params;
|
|
98
|
+
try {
|
|
99
|
+
if (name === "get-alerts") {
|
|
100
|
+
const { state } = AlertsArgumentsSchema.parse(args);
|
|
101
|
+
const stateCode = state.toUpperCase();
|
|
102
|
+
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
|
|
103
|
+
const alertsData = await makeNWSRequest(alertsUrl);
|
|
104
|
+
if (!alertsData) {
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: "Failed to retrieve alerts data",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const features = alertsData.features || [];
|
|
115
|
+
if (features.length === 0) {
|
|
116
|
+
return {
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: `No active alerts for ${stateCode}`,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const formattedAlerts = features.map(formatAlert).slice(0, 20); // only take the first 20 alerts;
|
|
126
|
+
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: alertsText,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
else if (name === "get-forecast") {
|
|
137
|
+
const { latitude, longitude } = ForecastArgumentsSchema.parse(args);
|
|
138
|
+
// Get grid point data
|
|
139
|
+
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
|
|
140
|
+
const pointsData = await makeNWSRequest(pointsUrl);
|
|
141
|
+
if (!pointsData) {
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: `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).`,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const forecastUrl = pointsData.properties?.forecast;
|
|
152
|
+
if (!forecastUrl) {
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: "Failed to get forecast URL from grid point data",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Get forecast data
|
|
163
|
+
const forecastData = await makeNWSRequest(forecastUrl);
|
|
164
|
+
if (!forecastData) {
|
|
165
|
+
return {
|
|
166
|
+
content: [
|
|
167
|
+
{
|
|
168
|
+
type: "text",
|
|
169
|
+
text: "Failed to retrieve forecast data",
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const periods = forecastData.properties?.periods || [];
|
|
175
|
+
if (periods.length === 0) {
|
|
176
|
+
return {
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: "No forecast periods available",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// Format forecast periods
|
|
186
|
+
const formattedForecast = periods.map((period) => [
|
|
187
|
+
`${period.name || "Unknown"}:`,
|
|
188
|
+
`Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
|
|
189
|
+
`Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
|
|
190
|
+
`${period.shortForecast || "No forecast available"}`,
|
|
191
|
+
"---",
|
|
192
|
+
].join("\n"));
|
|
193
|
+
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
|
|
194
|
+
return {
|
|
195
|
+
content: [
|
|
196
|
+
{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: forecastText,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (error instanceof z.ZodError) {
|
|
209
|
+
throw new Error(`Invalid arguments: ${error.errors
|
|
210
|
+
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
211
|
+
.join(", ")}`);
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
// Start the server
|
|
217
|
+
async function main() {
|
|
218
|
+
const transport = new StdioServerTransport();
|
|
219
|
+
await server.connect(transport);
|
|
220
|
+
console.error("Weather MCP Server running on stdio");
|
|
221
|
+
}
|
|
222
|
+
main().catch((error) => {
|
|
223
|
+
console.error("Fatal error in main():", error);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@albertmcp/mcp",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "MCP server for weather forecast and severe weather alerts in the US",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"modelcontextprotocol",
|
|
8
|
+
"mcp",
|
|
9
|
+
"mcp-server",
|
|
10
|
+
"weather"
|
|
11
|
+
],
|
|
12
|
+
"author": "",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/albertmcp/mcp.git"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"mcp-server-weather": "dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc && shx chmod +x dist/*.js",
|
|
29
|
+
"watch": "tsc --watch",
|
|
30
|
+
"clean": "git clean -fdxn -e .env && read -p 'OK?' && git clean -fdx -e .env",
|
|
31
|
+
"do-publish": "npm run clean && npm install && npm publish --access=public",
|
|
32
|
+
"publish-dry-run": "npm run clean && npm install && npm publish --access=public --dry-run"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
36
|
+
"zod": "^3.24.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.19.7",
|
|
40
|
+
"shx": "^0.3.4",
|
|
41
|
+
"typescript": "^5.7.2"
|
|
42
|
+
}
|
|
43
|
+
}
|