ガイド
自分だけの連携を作ろう
MCPサーバーを3ステップで作成し、VoiceOSに接続できます。
1
依存パッケージのインストール
pip install mcpnpm install @modelcontextprotocol/sdk zodpip install mcpnpm install @modelcontextprotocol/sdk zodpip install mcpnpm install @modelcontextprotocol/sdkpip install mcpnpm install @modelcontextprotocol/sdk zod2
サーバーを作成する
MCPサーバーは、VoiceOSが音声で呼び出せるツールを公開します。以下のサンプルから始めてみましょう:
ツールを1つだけ持つ最小構成のMCPサーバー。仕組みを理解するのに最適です。
Appleショートカット経由でHomeKitデバイスを操作。「電気を消して」「エアコンを24度にして」と話しかけるだけ。
AppleScriptでSpotifyを操作。「次の曲にして」「今何が流れてる?」と声で操作。
macOSの設定を音声で操作。「音量を50にして」「Safariを開いて」「おやすみモードをオンにして」
my_mcp_server.py
from mcp.server.fastmcp import FastMCP mcp = FastMCP("my-tools") @mcp.tool() def greet(name: str) -> str: """Say hello to someone by name.""" return f"Hello, {name}!" # Add more tools with the @mcp.tool() decorator if __name__ == "__main__": mcp.run(transport="stdio")
my-mcp-server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const server = new McpServer({ name: "my-tools", version: "1.0.0", }); server.tool( "greet", "Say hello to someone by name", { name: z.string().describe("Person to greet") }, async ({ name }) => ({ content: [{ type: "text", text: `Hello, ${name}!` }], }) ); // Add more tools with server.tool(name, description, schema, handler) const transport = new StdioServerTransport(); await server.connect(transport);
home_control.py
import subprocess from mcp.server.fastmcp import FastMCP mcp = FastMCP("home-control") def run_shortcut(name: str, input_text: str = "") -> str: cmd = ["shortcuts", "run", name] if input_text: cmd += ["-i", input_text] result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) return result.stdout.strip() or "Done" @mcp.tool() def lights_on() -> str: """Turn on the lights.""" return run_shortcut("Lights On") @mcp.tool() def lights_off() -> str: """Turn off the lights.""" return run_shortcut("Lights Off") @mcp.tool() def set_thermostat(temperature: int) -> str: """Set the thermostat to a specific temperature.""" return run_shortcut("Set Thermostat", str(temperature)) if __name__ == "__main__": mcp.run(transport="stdio")
home-control.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { execFile } from "child_process"; import { promisify } from "util"; const exec = promisify(execFile); const server = new McpServer({ name: "home-control", version: "1.0.0" }); async function runShortcut(name: string, input?: string) { const args = ["run", name, ...(input ? ["-i", input] : [])]; const { stdout } = await exec("shortcuts", args); return stdout.trim() || "Done"; } server.tool("lights_on", "Turn on the lights", {}, async () => ({ content: [{ type: "text", text: await runShortcut("Lights On") }] })); server.tool("lights_off", "Turn off the lights", {}, async () => ({ content: [{ type: "text", text: await runShortcut("Lights Off") }] })); server.tool("set_thermostat", "Set the thermostat temperature", { temperature: z.number().describe("Target temperature") }, async ({ temperature }) => ({ content: [{ type: "text", text: await runShortcut("Set Thermostat", String(temperature)) }], })); await server.connect(new StdioServerTransport());
spotify_mcp.py
import subprocess from mcp.server.fastmcp import FastMCP mcp = FastMCP("spotify") def osascript(cmd: str) -> str: result = subprocess.run( ["osascript", "-e", f'tell application "Spotify" to {cmd}'], capture_output=True, text=True, ) return result.stdout.strip() or "Done" @mcp.tool() def play() -> str: """Resume Spotify playback.""" return osascript("play") @mcp.tool() def pause() -> str: """Pause Spotify playback.""" return osascript("pause") @mcp.tool() def skip_track() -> str: """Skip to the next track.""" return osascript("next track") @mcp.tool() def current_track() -> str: """Get the currently playing track name and artist.""" name = osascript("name of current track") artist = osascript("artist of current track") return f"{name} by {artist}" if __name__ == "__main__": mcp.run(transport="stdio")
spotify-mcp.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { execFile } from "child_process"; import { promisify } from "util"; const exec = promisify(execFile); const server = new McpServer({ name: "spotify", version: "1.0.0" }); async function osascript(cmd: string) { const { stdout } = await exec("osascript", ["-e", `tell application "Spotify" to ${cmd}`]); return stdout.trim() || "Done"; } server.tool("play", "Resume Spotify playback", {}, async () => ({ content: [{ type: "text", text: await osascript("play") }] })); server.tool("pause", "Pause Spotify playback", {}, async () => ({ content: [{ type: "text", text: await osascript("pause") }] })); server.tool("skip_track", "Skip to the next track", {}, async () => ({ content: [{ type: "text", text: await osascript("next track") }] })); server.tool("current_track", "Get the currently playing track", {}, async () => { const name = await osascript("name of current track"); const artist = await osascript("artist of current track"); return { content: [{ type: "text", text: `${name} by ${artist}` }] }; }); await server.connect(new StdioServerTransport());
system_control.py
import subprocess from mcp.server.fastmcp import FastMCP mcp = FastMCP("system-control") def osascript(script: str) -> str: result = subprocess.run( ["osascript", "-e", script], capture_output=True, text=True, ) return result.stdout.strip() or "Done" @mcp.tool() def set_volume(level: int) -> str: """Set the system volume (0-100).""" return osascript(f"set volume output volume {level}") @mcp.tool() def toggle_do_not_disturb() -> str: """Toggle Do Not Disturb / Focus mode.""" script = 'tell application "System Events" to keystroke "D" ' \ 'using {command down, shift down, option down}' return osascript(script) @mcp.tool() def open_app(app_name: str) -> str: """Open an application by name.""" subprocess.run(["open", "-a", app_name]) return f"Opened {app_name}" @mcp.tool() def get_battery() -> str: """Get the current battery level.""" result = subprocess.run( ["pmset", "-g", "batt"], capture_output=True, text=True, ) return result.stdout.strip() if __name__ == "__main__": mcp.run(transport="stdio")
system-control.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { execFile } from "child_process"; import { promisify } from "util"; const exec = promisify(execFile); const server = new McpServer({ name: "system-control", version: "1.0.0" }); async function osascript(script: string) { const { stdout } = await exec("osascript", ["-e", script]); return stdout.trim() || "Done"; } server.tool("set_volume", "Set the system volume (0-100)", { level: z.number().min(0).max(100).describe("Volume level") }, async ({ level }) => ({ content: [{ type: "text", text: await osascript(`set volume output volume ${level}`) }], })); server.tool("toggle_do_not_disturb", "Toggle Do Not Disturb", {}, async () => ({ content: [{ type: "text", text: await osascript( 'tell application "System Events" to keystroke "D" using {command down, shift down, option down}' ) }], })); server.tool("open_app", "Open an application by name", { app_name: z.string().describe("Application name") }, async ({ app_name }) => { await exec("open", ["-a", app_name]); return { content: [{ type: "text", text: `Opened ${app_name}` }] }; }); server.tool("get_battery", "Get the current battery level", {}, async () => { const { stdout } = await exec("pmset", ["-g", "batt"]); return { content: [{ type: "text", text: stdout.trim() }] }; }); await server.connect(new StdioServerTransport());
@mcp.tool()(Python)またはserver.tool()(TypeScript)で定義した関数が、VoiceOSから呼び出せるツールになります。
3
VoiceOSに接続する
VoiceOSを開き、設定 → 連携 → カスタム連携に移動して追加をクリックし、名前を付けて起動コマンドを以下のように設定します:
python3 /path/to/my_mcp_server.pynpx tsx /path/to/my-mcp-server.tspython3 /path/to/home_control.pynpx tsx /path/to/home-control.tspython3 /path/to/spotify_mcp.pynpx tsx /path/to/spotify-mcp.tspython3 /path/to/system_control.pynpx tsx /path/to/system-control.ts/path/to/ を実際のファイルパスに置き換えてください。