ecowitt-mcp
Version:
MCP server for Ecowitt Weather API v3 integration
250 lines (231 loc) • 8.2 kB
JavaScript
#!/usr/bin/env node
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { getConfig } from "../config/index.js";
import { EcowittClient } from "../ecowitt/client.js";
import { formatMacAddress } from "../utils/mac.js";
import { extractUnitOptions, UnitOptionsSchema } from "../utils/unit_options.js";
import { DeviceHandlers } from "./handlers/device.js";
/**
* Create and configure the MCP server
* @param {Object} config - Full configuration object
* @param {Object} config.ecowitt - Ecowitt API configuration
* @param {string} config.ecowitt.applicationKey - Ecowitt application key
* @param {string} config.ecowitt.apiKey - Ecowitt API key
* @param {string} config.ecowitt.baseUrl - Base URL for the Ecowitt API
* @param {Object} config.server - Server configuration
* @param {string} config.server.name - Server name
* @param {string} config.server.version - Server version
* @param {number} config.server.requestTimeout - Request timeout in milliseconds
* @returns {Promise<McpServer>} Configured MCP server instance
*/
export async function createMCPServer(config) {
// Create the MCP server and advertise capabilities for resources
const server = new McpServer(
{
name: config.server.name,
version: config.server.version,
},
{
capabilities: {
resources: {
listChanged: false, // Set to true if you implement notifications
subscribe: false, // Set to true if you implement subscriptions
},
tools: {},
logging: {},
},
}
);
// Create device handlers
const ecowittClient = new EcowittClient(config);
const deviceHandlers = new DeviceHandlers(ecowittClient);
// Register the standard 'resources' primitive
server.registerResource(
"devices",
new ResourceTemplate("ecowitt://device/{mac}", {
list: async () => {
try {
const resources = await deviceHandlers.handleDeviceList();
return { resources };
} catch (error) {
server.server.sendLoggingMessage({
level: "error",
data: `Error listing devices: ${error.message}`,
});
throw error;
}
},
}),
{
title: "Ecowitt Devices",
description: "Access Ecowitt weather station device information.",
mimeType: "application/json",
},
async (uri, { mac }) => {
try {
const formattedMac = formatMacAddress(mac);
const deviceData = await deviceHandlers.getDeviceByMac(formattedMac);
return {
contents: [
{
uri: uri.href,
mac: formattedMac,
title: deviceData.name,
text: JSON.stringify(deviceData, null, 2),
contentType: "application/json",
},
],
};
} catch (error) {
server.server.sendLoggingMessage({
level: "error",
data: error.message,
});
throw error;
}
}
);
// Register tools
server.registerTool(
"get_devices",
{
name: "get_devices",
description: "Get information about all Ecowitt weather station devices",
outputSchema: {
devices: z.array(
z.object({
uri: z.string().describe("Device URI"),
name: z.string().describe("Device name"),
mac: z.string().describe("Device MAC address"),
type: z.number().describe("Device type"), // TODO: Map type to string
stationType: z.string().describe("Device station type"),
dateZoneId: z.string().describe("Device timezone"),
longitude: z.number().describe("Longitude of device"),
latitude: z.number().describe("Latitude of device"),
})
),
},
},
async () => {
try {
const devices = await deviceHandlers.handleDeviceList();
return {
structuredContent: { devices },
};
} catch (error) {
server.server.sendLoggingMessage({
level: "error",
data: `Error getting devices: ${error.message}`,
});
throw error;
}
}
);
server.registerTool(
"get_device_realtime_info",
{
name: "get_device_realtime_info",
description: "Get real-time information from an Ecowitt weather station device",
inputSchema: {
mac: z.string().describe("Device MAC address (format: AA:BB:CC:DD:EE:FF or AABBCCDDEEFF)"),
callback: z
.string()
.optional()
.describe("Optional field types to return (e.g., 'all', 'outdoor', 'indoor.humidity')"),
...UnitOptionsSchema,
},
},
async (args) => {
try {
const { mac, callback, ...rest } = args;
const formattedMac = formatMacAddress(mac);
const unitOptions = extractUnitOptions(rest);
const realtimeData = await deviceHandlers.getDeviceRealTimeInfo(formattedMac, callback, unitOptions);
return {
content: [{ type: "text", text: JSON.stringify(realtimeData, null, 2), contentType: "application/json" }],
};
} catch (error) {
server.server.sendLoggingMessage({
level: "error",
data: `Error getting realtime info for device ${args.mac}: ${error.message}`,
});
throw error;
}
}
);
server.registerTool(
"get_device_historical_info",
{
name: "get_device_historical_info",
description: "Get historical data from an Ecowitt weather station device",
inputSchema: {
mac: z.string().describe("Device MAC address (format: AA:BB:CC:DD:EE:FF or AABBCCDDEEFF)"),
start_date: z.string().describe("Start time of data query (ISO8601: 'YYYY-MM-DD HH:mm:ss')"),
end_date: z.string().describe("End time of data query (ISO8601: 'YYYY-MM-DD HH:mm:ss')"),
call_back: z
.string()
.describe("Comma-separated list of field types to return (e.g., 'outdoor.temp,indoor.humidity')"),
cycle_type: z.string().optional().describe("Data resolution: 'auto', '5min', '30min', '4hour', '1day'"),
...UnitOptionsSchema,
},
outputSchema: {
history: z.any().describe("Historical device data (raw Ecowitt API response)"),
},
},
async (args) => {
try {
const { mac, start_date, end_date, call_back, cycle_type, ...rest } = args;
const formattedMac = formatMacAddress(mac);
const unitOptions = extractUnitOptions(rest);
const historyData = await deviceHandlers.getDeviceHistory(
formattedMac,
start_date,
end_date,
call_back,
cycle_type,
unitOptions
);
return {
structuredContent: { history: historyData },
content: [{ type: "text", text: JSON.stringify(historyData, null, 2), contentType: "application/json" }],
};
} catch (error) {
server.server.sendLoggingMessage({
level: "error",
data: `Error getting historical data for device ${args.mac}: ${error.message}`,
});
throw error;
}
}
);
server.registerTool(
"get_current_datetime",
{
name: "get_current_datetime",
description: "Get the current datetime in ISO 8601 format (UTC).",
outputSchema: {
datetime: z.string().datetime().describe("Current datetime in ISO 8601 format (UTC)"),
},
},
async () => {
return {
structuredContent: { datetime: new Date().toISOString() },
};
}
);
return server;
}
try {
// Create and configure the MCP server instance
const config = getConfig();
const server = await createMCPServer(config);
// Initialize stdio transport and establish connection
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Ecowitt MCP Server started successfully");
} catch (error) {
console.error("Failed to start Ecowitt MCP Server:", error.message);
process.exit(1);
}