SwitchBot MCP Server
by genm
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
import crypto from 'crypto';
const SWITCHBOT_TOKEN = process.env.SWITCHBOT_TOKEN;
const SWITCHBOT_SECRET = process.env.SWITCHBOT_SECRET;
if (!SWITCHBOT_TOKEN || !SWITCHBOT_SECRET) {
throw new Error('SWITCHBOT_TOKEN and SWITCHBOT_SECRET environment variables are required');
}
interface SwitchBotDevice {
deviceId: string;
deviceName: string;
deviceType: string;
enableCloudService: boolean;
hubDeviceId: string;
}
interface SwitchBotStatus {
deviceId: string;
deviceType: string;
power: 'on' | 'off';
temperature?: number;
humidity?: number;
}
class SwitchBotServer {
private server: Server;
private axiosInstance;
constructor() {
this.server = new Server(
{
name: 'switchbot-server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
// SwitchBot APIのタイムスタンプとノンスを生成する関数
const generateAuthHeaders = () => {
const t = Date.now();
const nonce = 'requestID';
const data = SWITCHBOT_TOKEN! + t + nonce;
const signTerm = crypto.createHmac('sha256', SWITCHBOT_SECRET!)
.update(Buffer.from(data, 'utf-8'))
.digest('base64');
return {
Authorization: SWITCHBOT_TOKEN,
sign: signTerm,
nonce: nonce,
t: t.toString(),
};
};
this.axiosInstance = axios.create({
baseURL: 'https://api.switch-bot.com/v1.1',
headers: generateAuthHeaders(),
});
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'list_devices',
description: 'デバイス一覧を取得します',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'get_device_status',
description: 'デバイスのステータスを取得します',
inputSchema: {
type: 'object',
properties: {
deviceId: {
type: 'string',
description: 'デバイスID',
},
},
required: ['deviceId'],
},
},
{
name: 'control_device',
description: 'デバイスを制御します',
inputSchema: {
type: 'object',
properties: {
deviceId: {
type: 'string',
description: 'デバイスID',
},
command: {
type: 'string',
description: 'コマンド(turnOn, turnOff)',
enum: ['turnOn', 'turnOff'],
},
},
required: ['deviceId', 'command'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
switch (request.params.name) {
case 'list_devices': {
const response = await this.axiosInstance.get('/devices');
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data.body.deviceList, null, 2),
},
],
};
}
case 'get_device_status': {
const args = request.params.arguments;
if (!args || typeof args.deviceId !== 'string') {
throw new McpError(
ErrorCode.InvalidParams,
'デバイスIDが必要です'
);
}
const response = await this.axiosInstance.get(
`/devices/${args.deviceId}/status`
);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data.body, null, 2),
},
],
};
}
case 'control_device': {
const args = request.params.arguments;
if (!args || typeof args.deviceId !== 'string' || typeof args.command !== 'string') {
throw new McpError(
ErrorCode.InvalidParams,
'デバイスIDとコマンドが必要です'
);
}
const response = await this.axiosInstance.post(
`/devices/${args.deviceId}/commands`,
{
command: args.command,
parameter: 'default',
commandType: 'command',
}
);
return {
content: [
{
type: 'text',
text: JSON.stringify(response.data, null, 2),
},
],
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `SwitchBot API error: ${error.response?.data?.message ?? error.message
}`,
},
],
isError: true,
};
}
throw error;
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('SwitchBot MCP server running on stdio');
}
}
const server = new SwitchBotServer();
server.run().catch(console.error);