@spain-ai-kit/aemet-mcp-server 0.1.1

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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { z } from 'zod/v3';
5
+ import { AEMETClient, wrapToolHandler, validateMunicipioCode, validateStationId, validateAEMETArea, } from '@spain-ai-kit/shared';
6
+ const apiKey = process.env.AEMET_API_KEY ?? '';
7
+ const client = new AEMETClient({
8
+ baseURL: 'https://opendata.aemet.es/opendata/',
9
+ apiKey,
10
+ cacheTTL: 30 * 60 * 1000,
11
+ maxRequestsPerSecond: 5,
12
+ });
13
+ const server = new McpServer({
14
+ name: '@spain-ai-kit/aemet-mcp-server',
15
+ version: '0.1.0',
16
+ });
17
+ // --- Municipality cache ---
18
+ let municipiosCache = null;
19
+ async function getMunicipios() {
20
+ if (municipiosCache)
21
+ return municipiosCache;
22
+ municipiosCache =
23
+ await client.getData('api/maestro/municipios');
24
+ return municipiosCache;
25
+ }
26
+ // --- Schemas ---
27
+ const searchMunicipalitiesSchema = {
28
+ query: z
29
+ .string()
30
+ .describe('Municipality name to search (e.g., "Madrid", "Valencia", "Barcelona")'),
31
+ };
32
+ const municipioSchema = {
33
+ municipio: z
34
+ .string()
35
+ .describe('Municipality code (5 digits, e.g., "28079" for Madrid). Use search_municipalities to find codes.'),
36
+ };
37
+ const municipioHourlySchema = {
38
+ municipio: z.string().describe('Municipality code (5 digits)'),
39
+ };
40
+ const stationSchema = {
41
+ stationId: z.string().describe('Station climatological index (e.g., "3129" for Madrid Retiro)'),
42
+ };
43
+ const alertsSchema = {
44
+ area: z
45
+ .string()
46
+ .optional()
47
+ .describe('Area code: "esp" for all Spain, or region code 61-79 (default: "esp")'),
48
+ };
49
+ const beachSchema = {
50
+ beachCode: z.string().describe('Beach code from AEMET'),
51
+ };
52
+ const uvSchema = {
53
+ day: z.number().optional().describe('Day ahead: 0=today, 1=tomorrow, up to 4 (default: 0)'),
54
+ };
55
+ const fireRiskSchema = {
56
+ area: z
57
+ .string()
58
+ .optional()
59
+ .describe('"p" for mainland + Balearics, "c" for Canary Islands (default: "p")'),
60
+ };
61
+ // --- Tools ---
62
+ server.tool('list_municipalities', 'List all Spanish municipalities with their AEMET codes. Use these codes for forecast lookups.', {}, wrapToolHandler(async () => {
63
+ const municipios = await getMunicipios();
64
+ return {
65
+ content: [
66
+ {
67
+ type: 'text',
68
+ text: `${municipios.length} municipalities available. Use search_municipalities to find specific ones by name.`,
69
+ },
70
+ ],
71
+ };
72
+ }));
73
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
74
+ // @ts-ignore TS2589: MCP SDK type inference depth limit with many tools
75
+ server.tool('search_municipalities', 'Search Spanish municipalities by name. Returns matching municipalities with their AEMET codes for use in forecast tools.', searchMunicipalitiesSchema, wrapToolHandler(async ({ query }) => {
76
+ const municipios = await getMunicipios();
77
+ const q = query.toLowerCase();
78
+ const matches = municipios.filter((m) => m.nombre.toLowerCase().includes(q));
79
+ if (matches.length === 0) {
80
+ return {
81
+ content: [{ type: 'text', text: `No municipalities found matching "${query}".` }],
82
+ };
83
+ }
84
+ return {
85
+ content: [
86
+ {
87
+ type: 'text',
88
+ text: JSON.stringify(matches.slice(0, 20), null, 2),
89
+ },
90
+ ],
91
+ };
92
+ }));
93
+ server.tool('get_forecast_daily', 'Get daily weather forecast for a Spanish municipality (today + next days). Includes temperature, precipitation probability, wind, sky state.', municipioSchema, wrapToolHandler(async ({ municipio }) => {
94
+ validateMunicipioCode(municipio);
95
+ const data = await client.getData(`api/prediccion/especifica/municipio/diaria/${municipio}`);
96
+ return {
97
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
98
+ };
99
+ }));
100
+ server.tool('get_forecast_hourly', 'Get hourly weather forecast (48 hours) for a Spanish municipality. More granular than daily forecast.', municipioHourlySchema, wrapToolHandler(async ({ municipio }) => {
101
+ validateMunicipioCode(municipio);
102
+ const data = await client.getData(`api/prediccion/especifica/municipio/horaria/${municipio}`);
103
+ return {
104
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
105
+ };
106
+ }));
107
+ server.tool('get_current_observations', 'Get current weather observations from all AEMET stations across Spain. Returns temperature, humidity, wind, pressure, etc.', {}, wrapToolHandler(async () => {
108
+ const data = await client.getData('api/observacion/convencional/todas');
109
+ return {
110
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
111
+ };
112
+ }));
113
+ server.tool('get_station_observations', 'Get the last 12 hours of weather observations from a specific AEMET station.', stationSchema, wrapToolHandler(async ({ stationId }) => {
114
+ validateStationId(stationId);
115
+ const data = await client.getData(`api/observacion/convencional/datos/estacion/${stationId}`);
116
+ return {
117
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
118
+ };
119
+ }));
120
+ server.tool('get_weather_alerts', 'Get active adverse weather alerts (rain, wind, snow, heat, cold) for a region or all of Spain.', alertsSchema, wrapToolHandler(async ({ area }) => {
121
+ const areaCode = area ?? 'esp';
122
+ validateAEMETArea(areaCode);
123
+ const data = await client.getData(`api/avisos_cap/ultimoelaborado/area/${areaCode}`);
124
+ return {
125
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
126
+ };
127
+ }));
128
+ server.tool('get_beach_forecast', 'Get weather forecast for a specific beach (cloudiness, precipitation, wind, wave height, water temperature).', beachSchema, wrapToolHandler(async ({ beachCode }) => {
129
+ const data = await client.getData(`api/prediccion/especifica/playa/${beachCode}`);
130
+ return {
131
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
132
+ };
133
+ }));
134
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
135
+ // @ts-ignore TS2589: MCP SDK type inference depth limit with many tools
136
+ server.tool('get_uv_index', 'Get UV radiation index prediction for Spain. Higher values mean more sun protection needed.', uvSchema, wrapToolHandler(async ({ day }) => {
137
+ const d = day ?? 0;
138
+ const data = await client.getData(`api/prediccion/especifica/uvi/${d}`);
139
+ return {
140
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
141
+ };
142
+ }));
143
+ server.tool('get_fire_risk', 'Get current forest fire risk levels across Spain.', fireRiskSchema, wrapToolHandler(async ({ area }) => {
144
+ const areaCode = area ?? 'p';
145
+ validateAEMETArea(areaCode);
146
+ const data = await client.getData(`api/incendios/mapasriesgo/estimado/area/${areaCode}`);
147
+ return {
148
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
149
+ };
150
+ }));
151
+ // --- Start ---
152
+ async function main() {
153
+ const transport = new StdioServerTransport();
154
+ await server.connect(transport);
155
+ console.error('AEMET MCP Server running on stdio');
156
+ }
157
+ main().catch((error) => {
158
+ console.error('Fatal error:', error);
159
+ process.exit(1);
160
+ });
161
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@spain-ai-kit/aemet-mcp-server",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for AEMET (Agencia Estatal de Meteorología) - Spanish Weather Service",
5
+ "private": false,
6
+ "main": "dist/index.js",
7
+ "type": "module",
8
+ "bin": {
9
+ "aemet-mcp-server": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/aplaceforallmystuff/spain-ai-kit.git",
19
+ "directory": "mcp/aemet"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "spain",
24
+ "aemet",
25
+ "weather",
26
+ "forecast",
27
+ "meteorology",
28
+ "ai",
29
+ "claude"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "start": "node dist/index.js",
34
+ "dev": "tsx watch src/index.ts",
35
+ "test": "vitest run"
36
+ },
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.17.0",
39
+ "@spain-ai-kit/shared": "*",
40
+ "axios": "^1.11.0",
41
+ "zod": "^3.25.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.0.0",
45
+ "tsx": "^4.0.0",
46
+ "typescript": "^5.9.0",
47
+ "vitest": "^2.0.0"
48
+ }
49
+ }