# 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:
--------------------------------------------------------------------------------
```
SPEAKER_ID=
SPEED_SCALE=1.0
VOICEVOX_API_URL=http://localhost:50021
```
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
```
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2
}
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment variables
.env
.env.local
.env.*.local
# Build output
dist/
build/
out/
# Coverage directory
coverage/
# IDE and editor files
.idea/
.vscode/
*.swp
*.swo
.DS_Store
# Logs
logs/
*.log
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# Persona
.cursor/rules/persona.mdc
```
--------------------------------------------------------------------------------
/compose.yml:
--------------------------------------------------------------------------------
```yaml
services:
voicevox_engine:
image: voicevox/voicevox_engine:cpu-latest
ports:
- '50021:50021'
tty: true
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "voicevox-mcp",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"scripts": {
"build": "tsc",
"dev": "tsc --watch & node --watch dist/index.js",
"clean": "rimraf dist"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.4",
"dotenv": "^16.5.0",
"zod": "^3.24.4"
},
"devDependencies": {
"@types/node": "^22.15.18",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
},
"description": ""
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fs from "fs";
import { exec } from "child_process";
import dotenv from "dotenv";
import { fetchSpeakers, createAudioQuery, synthesizeVoice } from "./api.js";
dotenv.config();
// Create an MCP server
const server = new McpServer({
name: "voicevox-mcp",
version: "1.0.0",
});
// ファイル保存用関数
function saveAudioFile(buffer: Buffer, filePath: string) {
try {
fs.writeFileSync(filePath, buffer);
console.log("ファイル保存成功");
} catch (e) {
console.error("ファイル保存エラー:", e);
}
}
// 音声再生用関数
function playAudio(filePath: string) {
exec(`afplay ${filePath}`, (err) => {
if (err) {
console.error("音声再生エラー:", err);
} else {
console.log("Audio playback completed");
}
});
}
// Add an additional tool
server.tool("speakers",
{},
async () => {
const data = await fetchSpeakers();
return {
content: [{ type: "text", text: JSON.stringify(data) }],
}
}
)
server.tool("speak",
{ text: z.string() },
async ({ text }) => {
const resolvedSpeakerId = Number(process.env.SPEAKER_ID);
if (!resolvedSpeakerId || isNaN(resolvedSpeakerId)) {
throw new Error("speaker_idが指定されてないか、環境変数SPEAKER_IDが不正です");
}
const query = await createAudioQuery(text, resolvedSpeakerId);
const buffer = await synthesizeVoice(query, resolvedSpeakerId);
const filePath = "/tmp/voicevox.wav";
saveAudioFile(buffer, filePath);
playAudio(filePath);
return {
content: [
{
type: "text",
text: "OK",
}
]
};
}
)
const transport = new StdioServerTransport();
await server.connect(transport);
```