# Directory Structure
```
├── .cursor
│ └── rules
│ └── general.mdc
├── .env.sample
├── .gitignore
├── .prettierrc
├── compose.yml
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│ ├── api.ts
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
```
1 | SPEAKER_ID=
2 | SPEED_SCALE=1.0
3 | VOICEVOX_API_URL=http://localhost:50021
```
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
```
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "trailingComma": "es5",
5 | "printWidth": 100,
6 | "tabWidth": 2
7 | }
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Dependencies
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | # Environment variables
8 | .env
9 | .env.local
10 | .env.*.local
11 |
12 | # Build output
13 | dist/
14 | build/
15 | out/
16 |
17 | # Coverage directory
18 | coverage/
19 |
20 | # IDE and editor files
21 | .idea/
22 | .vscode/
23 | *.swp
24 | *.swo
25 | .DS_Store
26 |
27 | # Logs
28 | logs/
29 | *.log
30 |
31 | # Optional npm cache directory
32 | .npm
33 |
34 | # Optional eslint cache
35 | .eslintcache
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 |
40 | # Output of 'npm pack'
41 | *.tgz
42 |
43 | # Yarn Integrity file
44 | .yarn-integrity
45 |
46 | # Persona
47 | .cursor/rules/persona.mdc
```
--------------------------------------------------------------------------------
/compose.yml:
--------------------------------------------------------------------------------
```yaml
1 | services:
2 | voicevox_engine:
3 | image: voicevox/voicevox_engine:cpu-latest
4 | ports:
5 | - '50021:50021'
6 | tty: true
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "outDir": "dist",
11 | "rootDir": "src",
12 | "declaration": true,
13 | "sourceMap": true
14 | },
15 | "include": ["src/**/*"],
16 | "exclude": ["node_modules", "dist"]
17 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "voicevox-mcp",
3 | "version": "1.0.0",
4 | "main": "dist/index.js",
5 | "types": "dist/index.d.ts",
6 | "type": "module",
7 | "scripts": {
8 | "build": "tsc",
9 | "dev": "tsc --watch & node --watch dist/index.js",
10 | "clean": "rimraf dist"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@modelcontextprotocol/sdk": "^1.11.4",
17 | "dotenv": "^16.5.0",
18 | "zod": "^3.24.4"
19 | },
20 | "devDependencies": {
21 | "@types/node": "^22.15.18",
22 | "prettier": "^3.5.3",
23 | "typescript": "^5.8.3"
24 | },
25 | "description": ""
26 | }
27 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3 | import { z } from "zod";
4 | import fs from "fs";
5 | import { exec } from "child_process";
6 | import dotenv from "dotenv";
7 | import { fetchSpeakers, createAudioQuery, synthesizeVoice } from "./api.js";
8 | dotenv.config();
9 |
10 |
11 | // Create an MCP server
12 | const server = new McpServer({
13 | name: "voicevox-mcp",
14 | version: "1.0.0",
15 | });
16 |
17 | // ファイル保存用関数
18 | function saveAudioFile(buffer: Buffer, filePath: string) {
19 | try {
20 | fs.writeFileSync(filePath, buffer);
21 | console.log("ファイル保存成功");
22 | } catch (e) {
23 | console.error("ファイル保存エラー:", e);
24 | }
25 | }
26 |
27 | // 音声再生用関数
28 | function playAudio(filePath: string) {
29 | exec(`afplay ${filePath}`, (err) => {
30 | if (err) {
31 | console.error("音声再生エラー:", err);
32 | } else {
33 | console.log("Audio playback completed");
34 | }
35 | });
36 | }
37 |
38 | // Add an additional tool
39 | server.tool("speakers",
40 | {},
41 | async () => {
42 | const data = await fetchSpeakers();
43 | return {
44 | content: [{ type: "text", text: JSON.stringify(data) }],
45 | }
46 | }
47 | )
48 |
49 | server.tool("speak",
50 | { text: z.string() },
51 | async ({ text }) => {
52 | const resolvedSpeakerId = Number(process.env.SPEAKER_ID);
53 | if (!resolvedSpeakerId || isNaN(resolvedSpeakerId)) {
54 | throw new Error("speaker_idが指定されてないか、環境変数SPEAKER_IDが不正です");
55 | }
56 | const query = await createAudioQuery(text, resolvedSpeakerId);
57 | const buffer = await synthesizeVoice(query, resolvedSpeakerId);
58 | const filePath = "/tmp/voicevox.wav";
59 | saveAudioFile(buffer, filePath);
60 | playAudio(filePath);
61 | return {
62 | content: [
63 | {
64 | type: "text",
65 | text: "OK",
66 | }
67 | ]
68 | };
69 | }
70 | )
71 |
72 |
73 | const transport = new StdioServerTransport();
74 | await server.connect(transport);
```