mcp-speak
by mumei
README.md
# mcp-speak
1.1.0で共有キュー、保留・破棄ミュート、AI向け使用方針を追加しました。
macOS標準の `say` コマンドを使い、MCPクライアントからテキストを読み上げるローカルサーバーです。読み上げ処理に外部の音声合成APIやAPIキーは使いません。
複数のClaude Codeスレッド・MCPプロセスからの依頼を、同じログインユーザーの共有ワーカーで順番に再生します。Rustや別の音声サーバーの導入は不要です。
初めて使う場合は「必要な環境」「インストール」「MCPクライアントへの登録」の順に進めてください。ツールの引数や開発手順は、必要なときに参照できます。
## 必要な環境
- macOS(`/usr/bin/say` が利用可能な環境)
- Node.js 22以上とnpm
- ローカルの標準入出力(stdio)接続に対応したMCPクライアント
音声は、このサーバーを実行するMacから再生されます。音声ファイルやストリームをクライアントへ送る機能はありません。
## インストール
```sh
git clone https://github.com/mumei/mcp-speak.git
cd mcp-speak
npm ci
```
`package-lock.json` をGitで管理し、`npm ci` で依存関係を再現します。npmレジストリへの公開は行っていないため、GitHubから取得してください。
MCP受付プロセスを手動で起動する場合は、次のコマンドを使います。
```sh
npm start
```
起動後はMCPクライアントからの入力を待ちます。ターミナルに通常の文章を入力して読み上げるCLIではありません。終了は `Ctrl+C` です。Claude Codeに登録した場合はClaude Codeが受付プロセスを起動するため、通常は `npm start` の手動実行は不要です。
## MCPクライアントへの登録
### Claude Codeのコマンド登録
上記の `npm ci` を済ませてから登録します。まず `claude mcp get mcp-speak` または `claude mcp list` で、同名の登録がないか確認してください。登録済みの場合は内容を確認し、削除・上書きせずに既存の設定を使えるか判断します。
新規登録のコマンドは次のとおりです。
```sh
claude mcp add --transport stdio --scope user mcp-speak -- "/absolute/path/to/node" "/absolute/path/to/mcp-speak/index.js"
```
`command -v node` でNode.jsの場所を確認し、例の2つのパスをNode.jsと取得した `index.js` の絶対パスに置き換えてください。空白を含むパスも渡せるよう、引用符は残します。
`--scope user` は、同じユーザーの全プロジェクトで使うための登録先です。`--` より後ろは、MCPサーバーを起動するコマンドと引数です。
登録後は、次のコマンドで登録内容と接続状態を確認できます。Claude Code内では `/mcp` でも確認できます。
```sh
claude mcp get mcp-speak
claude mcp list
```
登録形式・スコープ・確認方法は [Claude Code公式のMCPドキュメント](https://code.claude.com/docs/en/mcp) を参照してください。
### 設定ファイルの登録例
クライアントの設定に、次のようなstdioサーバーを登録します。設定キーや保存先はクライアントによって異なります。
```json
{
"mcpServers": {
"mcp-speak": {
"command": "/absolute/path/to/node",
"args": ["/absolute/path/to/mcp-speak/index.js"]
}
}
}
```
`command -v node` でNode.jsの場所を確認し、`command` をその絶対パスに置き換えてください。`args` も取得した `index.js` の絶対パスにします。クライアントの作業ディレクトリやGUIアプリのPATHに依存せず起動できます。
各Claude Codeスレッドには、このstdio設定をそのまま使えます。各スレッドのMCPプロセスは別々に起動しても、再生ワーカーは1つです。クライアント設定や `CLAUDE.md`、hooksをこのプロジェクトが書き換えることはありません。
## MCP受付と共有再生ワーカー
```text
Claude Code A → MCPプロセスA ─┐
├→ 共通FIFO → 再生ガード → say(1つずつ)
Claude Code B → MCPプロセスB ─┘
```
MCPプロセスは入力検証・キュー受付・操作を担当します。共有ワーカーは受け取った順に依頼を並べ、前の `say` の終了を確認してから次を再生します。同時送信の順番はワーカーが受け取った順番です。他アプリが直接起動した `say` や別ログインユーザーの音声は、このキューの対象外です。
最初のキュー操作で、画面のないNode製ワーカーが自動で起動します。通常は `npm run worker` の手動実行も不要です。最後のクライアントが切断し、再生・待機分がなくなると60秒後に終了します。Web GUI・メニューバーUI・ログイン時の自動起動登録はありません。手動で起動したい場合は `npm run worker` を使い、終了は `Ctrl+C` です。
接続先は `127.0.0.1` のみです。既定ポートは `43000 + ユーザーID % 1000` で、OSの排他的bindにより二重起動を防ぎます。`/tmp/mcp-speak-<ユーザーID>-v1` に権限700の専用ディレクトリを作り、権限600のランダムトークンで認証します。同じOSユーザーによる操作は信頼境界の内側です。
テストなどで変える場合は、全クライアントに同じ `MCP_SPEAK_QUEUE_DIR`(絶対パス)と `MCP_SPEAK_QUEUE_PORT` を設定します。別のディレクトリやポートに分けると共通FIFOを共有できません。既定ポートを他アプリが使っている場合は、そのアプリを停止する代わりに共通の別ポートを指定してください。
## ツールと引数
### `speak`:テキストの読み上げ
| 引数 | 型 | 必須 | 内容 |
| --- | --- | --- | --- |
| `text` | 文字列 | はい | 読み上げる文章。空文字・空白のみは不可。UTF-8で64KiB以内 |
| `voice` | 文字列 | いいえ | このMacで利用可能な音声名。省略時はシステム既定。256文字以内 |
| `rate` | 整数 | いいえ | 1分あたりの単語数。1〜500、既定値175 |
```json
{
"text": "作業が完了しました。",
"voice": "Kyoko",
"rate": 175
}
```
`Kyoko` は例です。使用する音声は `list_voices` で確認してください。音声の追加や既定音声の変更はmacOS側で行います。
テキストは `say` の標準入力へ渡すため、先頭の `-` やシェルの記号をコマンドの引数として扱いません。未対応の引数、不正な型、範囲外の速度は `isError: true` で返します。
### `list_voices`:利用可能な音声一覧
引数は不要です。空オブジェクト `{}` でも呼び出せます。音声名・言語・サンプル文章を含む一覧を返します。
ターミナルから音声を確認する場合は、次のコマンドも使えます。
```sh
say -v '?'
```
## 応答は再生完了を保証しません
`speak` は共通FIFOへの受付後に応答し、受付IDとその時点の順番を返します。応答は再生開始・完了を意味しません。破棄ミュート中は、再生せず破棄したことを返します。受付後の失敗は依頼元MCPの標準エラーへ記録します。
再生中と待機分を合わせて100件までです。満杯になった場合は新しい依頼を `isError: true` で拒否し、既存の順番を保ちます。1件の再生は最大120秒で停止します。再生完了を待つMCPツールはありません。
## 状態・停止・ミュートを操作する
| MCPツール | 引数 | 共有ワーカーでの動作 |
| --- | --- | --- |
| `queue_status` | なし | 再生中か、待機件数、ミュート状態・モード、異常状態を表示 |
| `stop_speech` | なし | 現在の発声を止め、待機分を消す。ミュート状態は維持 |
| `mute_speech` | `{"mode":"hold"}` | 現在の発声を止め、待機分と新規依頼を上限付きでFIFO保留 |
| `mute_speech` | `{"mode":"discard"}` | 現在の発声を止め、待機分を消し、新規依頼も破棄 |
| `unmute_speech` | なし | ミュートを解除。保留分があれば順番に再生 |
どちらのミュートも途中の発声を再キューしません。保留から破棄へ切り替えると待機分を消します。破棄から保留へ切り替えると、その後の新規依頼から保持します。OSの音量や他アプリの音声は変更しません。
ミュート状態・モードは権限600の `mute-state.json` に保存し、ワーカーが再起動しても維持します。待機テキストはメモリ内だけに保持し、ディスクへ保存しません。
ターミナルからも同じ操作が可能です。状態確認CLIはワーカーを新しく起動しません。
```sh
npm run queue:status
npm run queue:mute -- hold
npm run queue:mute -- discard
npm run queue:unmute
npm run queue:stop
npm run queue:shutdown
```
`queue:shutdown` はこの共有ワーカーを終了します。接続中のMCPは切断され、次の操作で接続し直します。ミュートは自動解除しません。
## AIへ発話の使い方を伝える
MCP初期化応答の正式な `instructions` フィールドと、`tools/list` のツール説明・入力説明に、AI向けの使用方針を載せています。対応クライアントではMCP登録と接続だけで受け取れます。方針を受け取るためにClaude側の `CLAUDE.md` やhooksの追加は必須ではありません。ただし、クライアントが指示をモデルへ渡すことや、AIが必ず従うことは保証できません。
方針は「必要な完了報告」「問題やユーザー判断が必要な場面」「明示された読み上げ依頼」に、短い要点だけを発話するものです。細かな途中経過・長いコード・生ログ・認証情報を読まず、同じ内容を繰り返しません。ユーザーのミュートや停止を尊重し、AIが勝手に解除・モード変更・再送しないよう伝えます。
キューを導入しただけではAIの自動発話は始まりません。AIが使用方針を踏まえて `speak` を呼ぶことで再生されます。
## 切断・失敗・異常終了
MCP接続が切断すると、その接続が依頼した待機分と再生中の発声だけをキャンセルします。他クライアントの待機分は維持します。受付前のMCPリクエスト取消にも対応します。受付後の発声は `stop_speech` などで操作してください。通常の再生失敗・キャンセルでは、所有する `say` の終了を確認してから次へ進みます。
ワーカーが強制終了しても、再生ガードが親との切断を検知し、自分が起動した `say` だけを停止します。別ワーカーは `playback.json` が解放されるまで次を始めません。ガード自体が強制終了するなど、前の発声の停止を確認できない場合は、新規再生を拒否して異常状態を返します。`killall say` は使いません。
異常状態では `queue:status` を確認し、`queue:shutdown` でワーカーを終了してください。自分のツールの発声が停止したと確認できた場合だけ、専用ディレクトリに残った `playback.json` を削除して再起動します。停止確認なしに再生記録を消すと音声が重なるおそれがあります。認証トークンを削除するとクライアント間の認証が変わるため、復旧時にディレクトリ全体を削除しないでください。
ワーカーの異常終了や接続喪失では、未完了の受付分を自動再送しません。待機分は失われる場合があります。ミュート状態は保存済みの設定を維持します。
## 開発とテスト
```sh
npm ci
npm run check
npm test
```
`npm test` はNode.js標準のテストランナーを使います。専用の一時ディレクトリ・ポート・疑似プレイヤーで、2つの実MCPプロセスのFIFOと重複なし、ミュート2モードと切替、上限、停止・切断・ワーカー強制終了、認証、初期化のAI向け方針を検査します。通常のテストで音声は再生しません。
Macの実際の `say` とMCP接続を確認するには、次を実行します。短い音声を1回再生するため、音を出せる環境で実行してください。
```sh
npm run test:smoke
```
スモークテストは接続、音声一覧、不正入力の拒否、読み上げの受付を検査します。再生完了や音質は検査しません。接続を閉じるとその発声がキャンセルされるため、実際の再生確認には2クライアント検証を使います。
```sh
npm run test:queue:smoke
```
専用の一時ディレクトリ・ポートを使い、独立した2つのMCPプロセスから「一番です。」「二番です。」だけを送ります。実際の `say` の正常終了と、前の再生終了後に次が始まることを検査します。普段使っている共有キューやミュート設定には触れません。
GitHub Actionsでも、macOS上のNode.js 22・24で構文チェックと自動テストを実行します。CIでは音声再生を伴うスモークテストを実行しません。
配布内容の確認には `npm pack --dry-run` を使います。パッケージには実装・README・LICENSEのみを含め、IDE設定、テスト、ローカル設定は除外します。
## 問題が起きたとき
接続できない場合は、登録したNode.jsと `index.js` のパス、`npm ci` の完了を確認してください。音が出ない場合は、Macの出力先・音量と `say 'テストです'` の動作を確認します。標準エラーのログには、受付後に発生した音声処理の失敗も出力されます。
不具合の報告は [GitHub Issues](https://github.com/mumei/mcp-speak/issues) へお願いします。macOSとNode.jsのバージョン、再現手順を添え、読み上げ文章に含まれる個人情報や秘密情報は伏せてください。
MCPはアプリからツールを呼び出すためのプロトコルです。このサーバーのstdio接続では、標準入力でリクエストを受け、標準出力で応答を返します。ログには標準エラーを使います。
## ライセンス
[MIT](LICENSE)。既存の `package.json` のライセンス指定に合わせています。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues