whisper-windows-mcp 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/TROUBLESHOOTING.md +136 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +273 -0
- package/package.json +40 -0
- package/src/index.ts +330 -0
- package/tsconfig.json +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 whisper-windows-mcp contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# whisper-windows-mcp
|
|
2
|
+
|
|
3
|
+
A Windows-native MCP (Model Context Protocol) server that lets Claude Desktop transcribe audio files locally using [whisper.cpp](https://github.com/ggerganov/whisper.cpp) — no internet connection required, no data sent to the cloud.
|
|
4
|
+
|
|
5
|
+
> **Why does this exist?**
|
|
6
|
+
> The popular `whisper-mcp` package was built for macOS and assumes a Unix environment. It does not work on Windows. This package was written specifically for Windows users who want the same local transcription functionality in Claude Desktop.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What you can do with it
|
|
11
|
+
|
|
12
|
+
Once installed, you can say things like this directly in Claude Desktop:
|
|
13
|
+
|
|
14
|
+
- *"Transcribe C:\Users\Me\Downloads\meeting.mp3"*
|
|
15
|
+
- *"Transcribe this recording and summarise the key points"*
|
|
16
|
+
- *"Transcribe with timestamps so I can find specific moments"*
|
|
17
|
+
|
|
18
|
+
Everything runs on your own machine. No audio ever leaves your computer.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
You need the following installed before proceeding. Each one is free.
|
|
25
|
+
|
|
26
|
+
| Requirement | Purpose |
|
|
27
|
+
|---|---|
|
|
28
|
+
| [Node.js 18+](https://nodejs.org/en/download) | Runs the MCP server |
|
|
29
|
+
| [whisper.cpp](https://github.com/ggerganov/whisper.cpp/releases/latest) | The transcription engine |
|
|
30
|
+
| A Whisper model file | The AI model (downloaded in Step 2) |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Step 1 — Install whisper.cpp
|
|
35
|
+
|
|
36
|
+
1. Go to the [whisper.cpp latest release](https://github.com/ggerganov/whisper.cpp/releases/latest)
|
|
37
|
+
2. Download the file named **`whisper-bin-x64.zip`** (look for `win` and `x64` in the filename)
|
|
38
|
+
3. Extract the ZIP and move the contents to **`C:\whisper\Release\`** — create this folder if it doesn't exist
|
|
39
|
+
|
|
40
|
+
✅ You should now have **`C:\whisper\Release\whisper-cli.exe`**
|
|
41
|
+
|
|
42
|
+
> **Why this path?** You can install whisper.cpp anywhere, but `C:\whisper\Release\` matches the default config below and means less to edit later.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Step 2 — Download a Whisper model
|
|
47
|
+
|
|
48
|
+
The model is the AI that does the actual transcription. Click a link below to download directly:
|
|
49
|
+
|
|
50
|
+
| Model | Download | Size | Speed | Best for |
|
|
51
|
+
|---|---|---|---|---|
|
|
52
|
+
| tiny.en | [Download](https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en.bin) | 75 MB | Very fast | Quick tests |
|
|
53
|
+
| **base.en** | [**Download**](https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin) | 142 MB | Fast | **Recommended starting point** |
|
|
54
|
+
| small.en | [Download](https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin) | 466 MB | Moderate | Better accuracy |
|
|
55
|
+
| medium.en | [Download](https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en.bin) | 1.5 GB | Slow | High accuracy |
|
|
56
|
+
| large-v3 | [Download](https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin) | 2.9 GB | Very slow | Maximum accuracy |
|
|
57
|
+
|
|
58
|
+
Save the downloaded `.bin` file to **`C:\whisper\models\`** — create this folder if it doesn't exist.
|
|
59
|
+
|
|
60
|
+
✅ You should now have something like **`C:\whisper\models\ggml-base.en.bin`**
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Step 3 — Install Node.js
|
|
65
|
+
|
|
66
|
+
If you don't already have Node.js:
|
|
67
|
+
|
|
68
|
+
1. Go to [nodejs.org](https://nodejs.org/en/download) and download the **Windows Installer (.msi)** — choose the LTS version
|
|
69
|
+
2. Run the installer and accept all defaults
|
|
70
|
+
|
|
71
|
+
✅ To verify, open Command Prompt and run `node --version` — you should see something like `v20.x.x`
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Step 4 — Configure Claude Desktop
|
|
76
|
+
|
|
77
|
+
1. Open Claude Desktop → **Settings → Developer → Edit Config**
|
|
78
|
+
2. Add the following (or merge the `mcpServers` block if you already have other servers):
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"whisper": {
|
|
84
|
+
"command": "npx",
|
|
85
|
+
"args": ["-y", "whisper-windows-mcp"],
|
|
86
|
+
"env": {
|
|
87
|
+
"WHISPER_CLI_PATH": "C:\\whisper\\Release\\whisper-cli.exe",
|
|
88
|
+
"WHISPER_MODEL": "C:\\whisper\\models\\ggml-base.en.bin"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> ⚠️ **Path format:** In the JSON config, all backslashes must be doubled (`\\`). This is a JSON requirement. When typing paths into Claude in chat, use normal single backslashes.
|
|
96
|
+
|
|
97
|
+
3. If you downloaded a different model, update `ggml-base.en.bin` to match your filename
|
|
98
|
+
4. Save the file, fully quit Claude Desktop, and reopen it
|
|
99
|
+
5. Go to **Settings → Developer** — you should see **whisper** with a green **running** badge
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Step 5 — Test it
|
|
104
|
+
|
|
105
|
+
In Claude Desktop, type:
|
|
106
|
+
|
|
107
|
+
> *"Can you check your whisper config?"*
|
|
108
|
+
|
|
109
|
+
Claude will verify that `whisper-cli.exe` and your model file are both found. Then try:
|
|
110
|
+
|
|
111
|
+
> *"Please transcribe C:\Users\YourName\Downloads\recording.mp3"*
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Converting video files to audio
|
|
116
|
+
|
|
117
|
+
Whisper processes audio. If you have a video file (MP4, MKV, etc.) you may want to extract the audio first — audio-only files are much smaller and faster to process.
|
|
118
|
+
|
|
119
|
+
> **Tip:** whisper.cpp may handle MP4 files directly if FFmpeg is installed. Try transcribing an MP4 first before converting.
|
|
120
|
+
|
|
121
|
+
**Using VLC Media Player** (free, recommended for beginners):
|
|
122
|
+
|
|
123
|
+
1. Download [VLC](https://www.videolan.org/vlc/) if you don't have it
|
|
124
|
+
2. Open VLC → **Media → Convert / Save**
|
|
125
|
+
3. Click **Add**, select your video, then click **Convert / Save**
|
|
126
|
+
4. Under **Profile**, choose **Audio - MP3**
|
|
127
|
+
5. Set a destination filename and click **Start**
|
|
128
|
+
|
|
129
|
+
A 1-hour MP4 that might be 2–4 GB typically becomes a 50–100 MB MP3.
|
|
130
|
+
|
|
131
|
+
**Using FFmpeg** (command line, for advanced users):
|
|
132
|
+
```
|
|
133
|
+
ffmpeg -i "C:\path\to\video.mp4" -vn -ac 1 -ar 16000 "C:\path\to\output.wav"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Output formats
|
|
139
|
+
|
|
140
|
+
| Format | What you get | Ask Claude... |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `text` (default) | Plain transcript, no timestamps | *"Transcribe this file"* |
|
|
143
|
+
| `timestamps` | Transcript with `[00:00:00 --> 00:00:05]` time codes | *"Transcribe with timestamps"* |
|
|
144
|
+
| `json` | Structured data | *"Transcribe as JSON"* |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Transcription speed
|
|
149
|
+
|
|
150
|
+
Whisper runs on CPU by default. Rough estimates for a 1-hour recording:
|
|
151
|
+
|
|
152
|
+
| Model | Approximate time (CPU) |
|
|
153
|
+
|---|---|
|
|
154
|
+
| tiny.en | 5–10 minutes |
|
|
155
|
+
| base.en | 10–20 minutes |
|
|
156
|
+
| small.en | 20–35 minutes |
|
|
157
|
+
| medium.en | 35–60 minutes |
|
|
158
|
+
| large-v3 | 60–120 minutes |
|
|
159
|
+
|
|
160
|
+
> **GPU acceleration** for AMD (ROCm) and NVIDIA (CUDA) on Windows is planned for a future update.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Full config example
|
|
165
|
+
|
|
166
|
+
If you have other MCP servers already configured:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"preferences": {
|
|
171
|
+
"coworkWebSearchEnabled": true
|
|
172
|
+
},
|
|
173
|
+
"mcpServers": {
|
|
174
|
+
"whisper": {
|
|
175
|
+
"command": "npx",
|
|
176
|
+
"args": ["-y", "whisper-windows-mcp"],
|
|
177
|
+
"env": {
|
|
178
|
+
"WHISPER_CLI_PATH": "C:\\whisper\\Release\\whisper-cli.exe",
|
|
179
|
+
"WHISPER_MODEL": "C:\\whisper\\models\\ggml-base.en.bin"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Config file location:
|
|
187
|
+
```
|
|
188
|
+
C:\Users\YourUsername\AppData\Roaming\Claude\claude_desktop_config.json
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
> The `AppData` folder is hidden by default. To show it: File Explorer → **View → Show → Hidden items**
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Tested on
|
|
196
|
+
|
|
197
|
+
- Windows 10 Pro (10.0.19045)
|
|
198
|
+
- Windows 11 — untested, feedback welcome via [Issues](../../issues)
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Troubleshooting
|
|
203
|
+
|
|
204
|
+
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for detailed solutions.
|
|
205
|
+
|
|
206
|
+
Quick checklist:
|
|
207
|
+
|
|
208
|
+
- [ ] Config paths use **double backslashes** (`C:\\whisper\\...`)
|
|
209
|
+
- [ ] `whisper-cli.exe` exists at the path specified
|
|
210
|
+
- [ ] The model `.bin` file exists at the path specified
|
|
211
|
+
- [ ] Claude Desktop was **fully restarted** after editing the config
|
|
212
|
+
- [ ] Whisper shows **running** in Settings → Developer
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Roadmap
|
|
217
|
+
|
|
218
|
+
- [ ] SRT subtitle output
|
|
219
|
+
- [ ] Direct MP4/video file support via FFmpeg
|
|
220
|
+
- [ ] Translation to English from other languages
|
|
221
|
+
- [ ] AMD GPU acceleration (ROCm)
|
|
222
|
+
- [ ] NVIDIA GPU acceleration (CUDA)
|
|
223
|
+
- [ ] Speaker diarization (automatic speaker identification)
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT — free to use, modify, and distribute.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Contributing
|
|
234
|
+
|
|
235
|
+
Pull requests welcome. GPU acceleration solutions for AMD or NVIDIA especially appreciated. Windows 11 feedback welcome via Issues.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
This document covers the most common issues encountered when setting up `whisper-windows-mcp` on Windows.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## "whisper-cli not found" error
|
|
8
|
+
|
|
9
|
+
**Symptom:** Claude reports `whisper-cli not found` or suggests running `brew install whisper-cpp`.
|
|
10
|
+
|
|
11
|
+
**What's happening:** The `brew install` suggestion is a macOS instruction and does not apply to Windows. This error means the MCP server cannot locate `whisper-cli.exe`.
|
|
12
|
+
|
|
13
|
+
**Fix:** Make sure `WHISPER_CLI_PATH` in your `claude_desktop_config.json` points to the exact location of `whisper-cli.exe` on your system, using double backslashes:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
"WHISPER_CLI_PATH": "C:\\whisper\\Release\\whisper-cli.exe"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Verify the file actually exists at that path by opening File Explorer and navigating there.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## "File not found" when trying to transcribe
|
|
24
|
+
|
|
25
|
+
**Symptom:** Claude says the audio file was not found.
|
|
26
|
+
|
|
27
|
+
**Causes and fixes:**
|
|
28
|
+
|
|
29
|
+
1. **Wrong path format** — Windows paths must use backslashes. In the chat, type the path with single backslashes as normal:
|
|
30
|
+
```
|
|
31
|
+
C:\Users\Me\Downloads\recording.mp3
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Spaces in the path** — Paths with spaces are supported but make sure you give Claude the full path.
|
|
35
|
+
|
|
36
|
+
3. **File doesn't exist** — Double-check the file is where you think it is by navigating to it in File Explorer.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## The whisper server shows as "error" or doesn't appear in Settings → Developer
|
|
41
|
+
|
|
42
|
+
**Symptom:** After adding the config, the whisper server shows a red error badge or doesn't appear at all.
|
|
43
|
+
|
|
44
|
+
**Fixes to try in order:**
|
|
45
|
+
|
|
46
|
+
1. Make sure your JSON is valid — a single missing comma or bracket will break the entire config file. Use a JSON validator like [jsonlint.com](https://jsonlint.com) if unsure.
|
|
47
|
+
|
|
48
|
+
2. Fully quit Claude Desktop (check Task Manager to make sure no Claude processes remain) and reopen it.
|
|
49
|
+
|
|
50
|
+
3. Make sure Node.js is installed. Open Command Prompt and run:
|
|
51
|
+
```
|
|
52
|
+
node --version
|
|
53
|
+
```
|
|
54
|
+
If you get an error, download Node.js from [nodejs.org](https://nodejs.org) and install it.
|
|
55
|
+
|
|
56
|
+
4. Make sure `npx` works. In Command Prompt:
|
|
57
|
+
```
|
|
58
|
+
npx --version
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Transcription runs but produces garbled or incorrect text
|
|
64
|
+
|
|
65
|
+
**Symptom:** The transcript is mostly nonsense, repeating phrases, or in the wrong language.
|
|
66
|
+
|
|
67
|
+
**Fixes:**
|
|
68
|
+
|
|
69
|
+
- **Wrong language:** Add `"language": "en"` (or your language code) when asking Claude to transcribe.
|
|
70
|
+
- **Poor audio quality:** Whisper works best with clear speech. Background noise, multiple overlapping speakers, or very low bitrate audio will reduce accuracy.
|
|
71
|
+
- **Try a larger model:** Larger models (small, medium) are significantly more accurate than tiny or base, at the cost of speed.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Transcription is very slow
|
|
76
|
+
|
|
77
|
+
**Expected:** On CPU, a 1-hour recording takes roughly 20–60 minutes depending on the model and your processor. This is normal.
|
|
78
|
+
|
|
79
|
+
**Ways to speed it up:**
|
|
80
|
+
|
|
81
|
+
- Use a smaller model (`base.en` instead of `medium.en`)
|
|
82
|
+
- If you only need the first portion of a recording, trim it first using VLC (**Media → Convert/Save → Edit Profile → set stop time**) or Audacity
|
|
83
|
+
|
|
84
|
+
> GPU acceleration for AMD (ROCm) and NVIDIA (CUDA) on Windows may be added in a future update.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Claude Desktop fails to install or update (HRESULT 0x80073CF9 / 0x80073CF6)
|
|
89
|
+
|
|
90
|
+
**Symptom:** The Claude Desktop installer fails with an HRESULT error code.
|
|
91
|
+
|
|
92
|
+
**This is a Windows MSIX packaging issue, not related to this MCP server.** Steps to resolve:
|
|
93
|
+
|
|
94
|
+
1. Fully uninstall Claude Desktop via **Settings → Apps → Apps & features**
|
|
95
|
+
2. Open Task Manager (**Ctrl+Shift+Esc → Details tab**) and end any remaining Claude or Cowork processes
|
|
96
|
+
3. Delete the folder `C:\Users\YourUsername\AppData\Local\AnthropicClaude` if it exists
|
|
97
|
+
4. **Restart your computer** — this is important; some package registrations persist in memory until reboot
|
|
98
|
+
5. Run the installer again after the reboot
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Config file location
|
|
103
|
+
|
|
104
|
+
If you're not sure where to find `claude_desktop_config.json`:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
C:\Users\YourUsername\AppData\Roaming\Claude\claude_desktop_config.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
You can also reach it via Claude Desktop → **Settings → Developer → Edit Config**.
|
|
111
|
+
|
|
112
|
+
The `AppData` folder is hidden by default. To show it in File Explorer: **View → Show → Hidden items**.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Double backslashes in JSON — why?
|
|
117
|
+
|
|
118
|
+
In JSON, the backslash `\` is an escape character. To represent a single literal backslash (as used in Windows paths), you must write `\\`. So:
|
|
119
|
+
|
|
120
|
+
| Windows path | In JSON |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `C:\whisper\Release\whisper-cli.exe` | `"C:\\whisper\\Release\\whisper-cli.exe"` |
|
|
123
|
+
| `C:\Users\Me\Downloads\audio.mp3` | `"C:\\Users\\Me\\Downloads\\audio.mp3"` |
|
|
124
|
+
|
|
125
|
+
This is only required inside the JSON config file. When typing paths directly into Claude in the chat, use normal single backslashes.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Still stuck?
|
|
130
|
+
|
|
131
|
+
Open an issue at [github.com/eviscerations/whisper-windows-mcp/issues](https://github.com/eviscerations/whisper-windows-mcp/issues) and include:
|
|
132
|
+
|
|
133
|
+
- Your Windows version
|
|
134
|
+
- The contents of your `claude_desktop_config.json` (remove any personal info)
|
|
135
|
+
- The MCP server log from **Settings → Developer → View Logs**
|
|
136
|
+
- What you've already tried
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* whisper-windows-mcp
|
|
4
|
+
* A Windows-native MCP server for local audio transcription using whisper.cpp
|
|
5
|
+
* https://github.com/eviscerations/whisper-windows-mcp
|
|
6
|
+
*/
|
|
7
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { execFile } from "child_process";
|
|
11
|
+
import { existsSync, unlinkSync } from "fs";
|
|
12
|
+
import { cpus, tmpdir } from "os";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import { promisify } from "util";
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Configuration
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const WHISPER_CLI_PATH = process.env.WHISPER_CLI_PATH ?? "C:\\whisper\\Release\\whisper-cli.exe";
|
|
20
|
+
const WHISPER_MODEL = process.env.WHISPER_MODEL ?? "C:\\whisper\\models\\ggml-base.en.bin";
|
|
21
|
+
const FFMPEG_PATH = process.env.FFMPEG_PATH ?? "ffmpeg";
|
|
22
|
+
const SYSTEM_THREADS = cpus().length;
|
|
23
|
+
const DEFAULT_THREADS = Math.max(2, Math.floor(SYSTEM_THREADS / 2));
|
|
24
|
+
const WHISPER_THREADS = parseInt(process.env.WHISPER_THREADS ?? String(DEFAULT_THREADS), 10);
|
|
25
|
+
// Video/audio formats that need FFmpeg conversion before whisper-cli can read them
|
|
26
|
+
const NEEDS_CONVERSION = [".mp4", ".mkv", ".avi", ".mov", ".webm", ".flv", ".wmv", ".m4v", ".m4a", ".ogg", ".flac"];
|
|
27
|
+
const NATIVE_FORMATS = [".wav", ".mp3"];
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Helpers
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
function validatePaths() {
|
|
32
|
+
if (!existsSync(WHISPER_CLI_PATH)) {
|
|
33
|
+
return (`whisper-cli.exe not found at: ${WHISPER_CLI_PATH}\n` +
|
|
34
|
+
`Check your WHISPER_CLI_PATH environment variable in claude_desktop_config.json`);
|
|
35
|
+
}
|
|
36
|
+
if (!existsSync(WHISPER_MODEL)) {
|
|
37
|
+
return (`Whisper model not found at: ${WHISPER_MODEL}\n` +
|
|
38
|
+
`Check your WHISPER_MODEL environment variable in claude_desktop_config.json`);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function needsConversion(filePath) {
|
|
43
|
+
const ext = filePath.toLowerCase().slice(filePath.lastIndexOf("."));
|
|
44
|
+
return NEEDS_CONVERSION.includes(ext);
|
|
45
|
+
}
|
|
46
|
+
async function convertToWav(inputPath) {
|
|
47
|
+
const tmpFile = join(tmpdir(), `whisper_tmp_${Date.now()}.wav`);
|
|
48
|
+
await execFileAsync(FFMPEG_PATH, [
|
|
49
|
+
"-y", // overwrite if exists
|
|
50
|
+
"-i", inputPath,
|
|
51
|
+
"-ar", "16000", // 16kHz sample rate (whisper optimal)
|
|
52
|
+
"-ac", "1", // mono
|
|
53
|
+
"-c:a", "pcm_s16le", // 16-bit PCM WAV
|
|
54
|
+
tmpFile,
|
|
55
|
+
], { windowsHide: true });
|
|
56
|
+
return tmpFile;
|
|
57
|
+
}
|
|
58
|
+
function buildArgs(filePath, model, language, outputFormat, threads) {
|
|
59
|
+
const args = [
|
|
60
|
+
"-m", model,
|
|
61
|
+
"-f", filePath,
|
|
62
|
+
"-l", language,
|
|
63
|
+
"-t", String(threads),
|
|
64
|
+
];
|
|
65
|
+
if (outputFormat === "timestamps") {
|
|
66
|
+
// default whisper output includes timestamps
|
|
67
|
+
}
|
|
68
|
+
else if (outputFormat === "json") {
|
|
69
|
+
args.push("-oj");
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
args.push("--no-timestamps");
|
|
73
|
+
}
|
|
74
|
+
return args;
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// MCP Server
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
const server = new Server({ name: "whisper-windows-mcp", version: "1.1.0" }, { capabilities: { tools: {} } });
|
|
80
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
81
|
+
tools: [
|
|
82
|
+
{
|
|
83
|
+
name: "transcribe_audio",
|
|
84
|
+
description: "Transcribe an audio or video file using whisper.cpp running locally on Windows. " +
|
|
85
|
+
"Natively supports mp3 and wav. " +
|
|
86
|
+
"Automatically converts video and other audio formats (mp4, mkv, avi, mov, webm, m4a, flac, ogg, etc.) " +
|
|
87
|
+
"via FFmpeg before transcription — no manual conversion needed. " +
|
|
88
|
+
"Note: long files (over 30 minutes) may take significant time and " +
|
|
89
|
+
"cannot be cancelled cleanly from Claude — use Task Manager to stop if needed.",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {
|
|
93
|
+
file_path: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Absolute Windows path to the file, e.g. C:\\Users\\You\\Downloads\\recording.mp4",
|
|
96
|
+
},
|
|
97
|
+
model: {
|
|
98
|
+
type: "string",
|
|
99
|
+
description: "Override the default model path. Leave blank to use the model configured in WHISPER_MODEL.",
|
|
100
|
+
},
|
|
101
|
+
language: {
|
|
102
|
+
type: "string",
|
|
103
|
+
description: "Language code, e.g. en, es, fr. Defaults to en.",
|
|
104
|
+
default: "en",
|
|
105
|
+
},
|
|
106
|
+
output_format: {
|
|
107
|
+
type: "string",
|
|
108
|
+
enum: ["text", "timestamps", "json"],
|
|
109
|
+
description: "text = plain transcript (default), timestamps = transcript with time codes, json = structured output.",
|
|
110
|
+
default: "text",
|
|
111
|
+
},
|
|
112
|
+
threads: {
|
|
113
|
+
type: "number",
|
|
114
|
+
description: `Number of CPU threads to use. Defaults to ${WHISPER_THREADS} (half of your ${SYSTEM_THREADS} logical cores). ` +
|
|
115
|
+
`Increase for faster processing, decrease to keep the system responsive.`,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
required: ["file_path"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "check_config",
|
|
123
|
+
description: "Verify that whisper-cli.exe and the configured model file can both be found. " +
|
|
124
|
+
"Also reports thread count and FFmpeg availability. Run this first if transcription fails.",
|
|
125
|
+
inputSchema: { type: "object", properties: {} },
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
}));
|
|
129
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
130
|
+
const { name, arguments: args } = request.params;
|
|
131
|
+
// -------------------------------------------------------------------------
|
|
132
|
+
// check_config
|
|
133
|
+
// -------------------------------------------------------------------------
|
|
134
|
+
if (name === "check_config") {
|
|
135
|
+
const error = validatePaths();
|
|
136
|
+
if (error) {
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text", text: `❌ Configuration error:\n\n${error}` }],
|
|
139
|
+
isError: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// Check FFmpeg availability
|
|
143
|
+
let ffmpegStatus = "✅ Found";
|
|
144
|
+
try {
|
|
145
|
+
await execFileAsync(FFMPEG_PATH, ["-version"], { windowsHide: true });
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
ffmpegStatus = "⚠️ Not found — video/non-WAV formats will not work without FFmpeg in PATH";
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `✅ Configuration looks good!\n\n` +
|
|
155
|
+
`whisper-cli: ${WHISPER_CLI_PATH}\n` +
|
|
156
|
+
`Model: ${WHISPER_MODEL}\n` +
|
|
157
|
+
`Threads: ${WHISPER_THREADS} of ${SYSTEM_THREADS} logical cores\n` +
|
|
158
|
+
`FFmpeg: ${ffmpegStatus}\n\n` +
|
|
159
|
+
`To change thread count, add WHISPER_THREADS to your claude_desktop_config.json env block.\n` +
|
|
160
|
+
`To use a custom FFmpeg path, add FFMPEG_PATH to the env block.`,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// -------------------------------------------------------------------------
|
|
166
|
+
// transcribe_audio
|
|
167
|
+
// -------------------------------------------------------------------------
|
|
168
|
+
if (name === "transcribe_audio") {
|
|
169
|
+
const filePath = args?.file_path;
|
|
170
|
+
const model = args?.model || WHISPER_MODEL;
|
|
171
|
+
const language = args?.language || "en";
|
|
172
|
+
const outputFormat = (args?.output_format || "text");
|
|
173
|
+
const threads = Math.min(SYSTEM_THREADS, Math.max(1, Math.round(args?.threads || WHISPER_THREADS)));
|
|
174
|
+
if (!filePath) {
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: "file_path is required." }],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (!existsSync(filePath)) {
|
|
181
|
+
return {
|
|
182
|
+
content: [
|
|
183
|
+
{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: `File not found: ${filePath}\n\n` +
|
|
186
|
+
`Make sure the path uses backslashes and is absolute, ` +
|
|
187
|
+
`e.g. C:\\Users\\You\\Downloads\\audio.mp3`,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const configError = validatePaths();
|
|
194
|
+
if (configError) {
|
|
195
|
+
return {
|
|
196
|
+
content: [{ type: "text", text: configError }],
|
|
197
|
+
isError: true,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Convert to WAV via FFmpeg if needed
|
|
201
|
+
let transcribeFrom = filePath;
|
|
202
|
+
let tmpFile = null;
|
|
203
|
+
const converting = needsConversion(filePath);
|
|
204
|
+
if (converting) {
|
|
205
|
+
try {
|
|
206
|
+
tmpFile = await convertToWav(filePath);
|
|
207
|
+
transcribeFrom = tmpFile;
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
const msg = err?.stderr || err?.message || String(err);
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: `FFmpeg conversion failed:\n\n${msg}\n\n` +
|
|
216
|
+
`Make sure FFmpeg is installed and in your system PATH.\n` +
|
|
217
|
+
`Download from https://ffmpeg.org/download.html\n` +
|
|
218
|
+
`Or convert to MP3 manually using VLC (Media → Convert/Save → Audio - MP3).`,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
isError: true,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Run whisper-cli
|
|
226
|
+
try {
|
|
227
|
+
const cliArgs = buildArgs(transcribeFrom, model, language, outputFormat, threads);
|
|
228
|
+
const { stdout, stderr } = await execFileAsync(WHISPER_CLI_PATH, cliArgs, {
|
|
229
|
+
maxBuffer: 100 * 1024 * 1024,
|
|
230
|
+
windowsHide: true,
|
|
231
|
+
});
|
|
232
|
+
const output = stdout || stderr || "(no output)";
|
|
233
|
+
const note = converting
|
|
234
|
+
? "\n\n[Automatically converted from video/audio format via FFmpeg before transcription]"
|
|
235
|
+
: "";
|
|
236
|
+
return { content: [{ type: "text", text: output.trim() + note }] };
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
const message = err?.stderr || err?.stdout || err?.message || String(err);
|
|
240
|
+
return {
|
|
241
|
+
content: [
|
|
242
|
+
{ type: "text", text: `Transcription failed:\n\n${message}` },
|
|
243
|
+
],
|
|
244
|
+
isError: true,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
// Always clean up temp file
|
|
249
|
+
if (tmpFile && existsSync(tmpFile)) {
|
|
250
|
+
try {
|
|
251
|
+
unlinkSync(tmpFile);
|
|
252
|
+
}
|
|
253
|
+
catch { /* ignore cleanup errors */ }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
259
|
+
isError: true,
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// Start
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
async function main() {
|
|
266
|
+
const transport = new StdioServerTransport();
|
|
267
|
+
await server.connect(transport);
|
|
268
|
+
console.error(`whisper-windows-mcp v1.1.0 running | threads: ${WHISPER_THREADS}/${SYSTEM_THREADS}`);
|
|
269
|
+
}
|
|
270
|
+
main().catch((err) => {
|
|
271
|
+
console.error("Fatal error:", err);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "whisper-windows-mcp",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Windows-native MCP server for local audio transcription using whisper.cpp — works with Claude Desktop on Windows",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"whisper-windows-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"whisper",
|
|
18
|
+
"whisper.cpp",
|
|
19
|
+
"transcription",
|
|
20
|
+
"speech-to-text",
|
|
21
|
+
"windows",
|
|
22
|
+
"claude",
|
|
23
|
+
"claude-desktop",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"audio",
|
|
26
|
+
"local"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.0.0",
|
|
35
|
+
"@types/node": "^20.0.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* whisper-windows-mcp
|
|
4
|
+
* A Windows-native MCP server for local audio transcription using whisper.cpp
|
|
5
|
+
* https://github.com/eviscerations/whisper-windows-mcp
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ListToolsRequestSchema,
|
|
13
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
import { execFile } from "child_process";
|
|
15
|
+
import { existsSync, unlinkSync } from "fs";
|
|
16
|
+
import { cpus, tmpdir } from "os";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
import { promisify } from "util";
|
|
19
|
+
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Configuration
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
const WHISPER_CLI_PATH =
|
|
26
|
+
process.env.WHISPER_CLI_PATH ?? "C:\\whisper\\Release\\whisper-cli.exe";
|
|
27
|
+
|
|
28
|
+
const WHISPER_MODEL =
|
|
29
|
+
process.env.WHISPER_MODEL ?? "C:\\whisper\\models\\ggml-base.en.bin";
|
|
30
|
+
|
|
31
|
+
const FFMPEG_PATH =
|
|
32
|
+
process.env.FFMPEG_PATH ?? "ffmpeg";
|
|
33
|
+
|
|
34
|
+
const SYSTEM_THREADS = cpus().length;
|
|
35
|
+
const DEFAULT_THREADS = Math.max(2, Math.floor(SYSTEM_THREADS / 2));
|
|
36
|
+
const WHISPER_THREADS = parseInt(process.env.WHISPER_THREADS ?? String(DEFAULT_THREADS), 10);
|
|
37
|
+
|
|
38
|
+
// Video/audio formats that need FFmpeg conversion before whisper-cli can read them
|
|
39
|
+
const NEEDS_CONVERSION = [".mp4", ".mkv", ".avi", ".mov", ".webm", ".flv", ".wmv", ".m4v", ".m4a", ".ogg", ".flac"];
|
|
40
|
+
const NATIVE_FORMATS = [".wav", ".mp3"];
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
function validatePaths(): string | null {
|
|
46
|
+
if (!existsSync(WHISPER_CLI_PATH)) {
|
|
47
|
+
return (
|
|
48
|
+
`whisper-cli.exe not found at: ${WHISPER_CLI_PATH}\n` +
|
|
49
|
+
`Check your WHISPER_CLI_PATH environment variable in claude_desktop_config.json`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (!existsSync(WHISPER_MODEL)) {
|
|
53
|
+
return (
|
|
54
|
+
`Whisper model not found at: ${WHISPER_MODEL}\n` +
|
|
55
|
+
`Check your WHISPER_MODEL environment variable in claude_desktop_config.json`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function needsConversion(filePath: string): boolean {
|
|
62
|
+
const ext = filePath.toLowerCase().slice(filePath.lastIndexOf("."));
|
|
63
|
+
return NEEDS_CONVERSION.includes(ext);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function convertToWav(inputPath: string): Promise<string> {
|
|
67
|
+
const tmpFile = join(tmpdir(), `whisper_tmp_${Date.now()}.wav`);
|
|
68
|
+
await execFileAsync(FFMPEG_PATH, [
|
|
69
|
+
"-y", // overwrite if exists
|
|
70
|
+
"-i", inputPath,
|
|
71
|
+
"-ar", "16000", // 16kHz sample rate (whisper optimal)
|
|
72
|
+
"-ac", "1", // mono
|
|
73
|
+
"-c:a", "pcm_s16le", // 16-bit PCM WAV
|
|
74
|
+
tmpFile,
|
|
75
|
+
], { windowsHide: true });
|
|
76
|
+
return tmpFile;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type OutputFormat = "text" | "timestamps" | "json";
|
|
80
|
+
|
|
81
|
+
function buildArgs(
|
|
82
|
+
filePath: string,
|
|
83
|
+
model: string,
|
|
84
|
+
language: string,
|
|
85
|
+
outputFormat: OutputFormat,
|
|
86
|
+
threads: number
|
|
87
|
+
): string[] {
|
|
88
|
+
const args: string[] = [
|
|
89
|
+
"-m", model,
|
|
90
|
+
"-f", filePath,
|
|
91
|
+
"-l", language,
|
|
92
|
+
"-t", String(threads),
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
if (outputFormat === "timestamps") {
|
|
96
|
+
// default whisper output includes timestamps
|
|
97
|
+
} else if (outputFormat === "json") {
|
|
98
|
+
args.push("-oj");
|
|
99
|
+
} else {
|
|
100
|
+
args.push("--no-timestamps");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return args;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// MCP Server
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
const server = new Server(
|
|
110
|
+
{ name: "whisper-windows-mcp", version: "1.1.0" },
|
|
111
|
+
{ capabilities: { tools: {} } }
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
115
|
+
tools: [
|
|
116
|
+
{
|
|
117
|
+
name: "transcribe_audio",
|
|
118
|
+
description:
|
|
119
|
+
"Transcribe an audio or video file using whisper.cpp running locally on Windows. " +
|
|
120
|
+
"Natively supports mp3 and wav. " +
|
|
121
|
+
"Automatically converts video and other audio formats (mp4, mkv, avi, mov, webm, m4a, flac, ogg, etc.) " +
|
|
122
|
+
"via FFmpeg before transcription — no manual conversion needed. " +
|
|
123
|
+
"Note: long files (over 30 minutes) may take significant time and " +
|
|
124
|
+
"cannot be cancelled cleanly from Claude — use Task Manager to stop if needed.",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
file_path: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description:
|
|
131
|
+
"Absolute Windows path to the file, e.g. C:\\Users\\You\\Downloads\\recording.mp4",
|
|
132
|
+
},
|
|
133
|
+
model: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description:
|
|
136
|
+
"Override the default model path. Leave blank to use the model configured in WHISPER_MODEL.",
|
|
137
|
+
},
|
|
138
|
+
language: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "Language code, e.g. en, es, fr. Defaults to en.",
|
|
141
|
+
default: "en",
|
|
142
|
+
},
|
|
143
|
+
output_format: {
|
|
144
|
+
type: "string",
|
|
145
|
+
enum: ["text", "timestamps", "json"],
|
|
146
|
+
description:
|
|
147
|
+
"text = plain transcript (default), timestamps = transcript with time codes, json = structured output.",
|
|
148
|
+
default: "text",
|
|
149
|
+
},
|
|
150
|
+
threads: {
|
|
151
|
+
type: "number",
|
|
152
|
+
description:
|
|
153
|
+
`Number of CPU threads to use. Defaults to ${WHISPER_THREADS} (half of your ${SYSTEM_THREADS} logical cores). ` +
|
|
154
|
+
`Increase for faster processing, decrease to keep the system responsive.`,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
required: ["file_path"],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "check_config",
|
|
162
|
+
description:
|
|
163
|
+
"Verify that whisper-cli.exe and the configured model file can both be found. " +
|
|
164
|
+
"Also reports thread count and FFmpeg availability. Run this first if transcription fails.",
|
|
165
|
+
inputSchema: { type: "object", properties: {} },
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
171
|
+
const { name, arguments: args } = request.params;
|
|
172
|
+
|
|
173
|
+
// -------------------------------------------------------------------------
|
|
174
|
+
// check_config
|
|
175
|
+
// -------------------------------------------------------------------------
|
|
176
|
+
if (name === "check_config") {
|
|
177
|
+
const error = validatePaths();
|
|
178
|
+
if (error) {
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: `❌ Configuration error:\n\n${error}` }],
|
|
181
|
+
isError: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check FFmpeg availability
|
|
186
|
+
let ffmpegStatus = "✅ Found";
|
|
187
|
+
try {
|
|
188
|
+
await execFileAsync(FFMPEG_PATH, ["-version"], { windowsHide: true });
|
|
189
|
+
} catch {
|
|
190
|
+
ffmpegStatus = "⚠️ Not found — video/non-WAV formats will not work without FFmpeg in PATH";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
content: [
|
|
195
|
+
{
|
|
196
|
+
type: "text",
|
|
197
|
+
text:
|
|
198
|
+
`✅ Configuration looks good!\n\n` +
|
|
199
|
+
`whisper-cli: ${WHISPER_CLI_PATH}\n` +
|
|
200
|
+
`Model: ${WHISPER_MODEL}\n` +
|
|
201
|
+
`Threads: ${WHISPER_THREADS} of ${SYSTEM_THREADS} logical cores\n` +
|
|
202
|
+
`FFmpeg: ${ffmpegStatus}\n\n` +
|
|
203
|
+
`To change thread count, add WHISPER_THREADS to your claude_desktop_config.json env block.\n` +
|
|
204
|
+
`To use a custom FFmpeg path, add FFMPEG_PATH to the env block.`,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// -------------------------------------------------------------------------
|
|
211
|
+
// transcribe_audio
|
|
212
|
+
// -------------------------------------------------------------------------
|
|
213
|
+
if (name === "transcribe_audio") {
|
|
214
|
+
const filePath = args?.file_path as string;
|
|
215
|
+
const model = (args?.model as string) || WHISPER_MODEL;
|
|
216
|
+
const language = (args?.language as string) || "en";
|
|
217
|
+
const outputFormat = ((args?.output_format as string) || "text") as OutputFormat;
|
|
218
|
+
const threads = Math.min(
|
|
219
|
+
SYSTEM_THREADS,
|
|
220
|
+
Math.max(1, Math.round((args?.threads as number) || WHISPER_THREADS))
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
if (!filePath) {
|
|
224
|
+
return {
|
|
225
|
+
content: [{ type: "text", text: "file_path is required." }],
|
|
226
|
+
isError: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!existsSync(filePath)) {
|
|
231
|
+
return {
|
|
232
|
+
content: [
|
|
233
|
+
{
|
|
234
|
+
type: "text",
|
|
235
|
+
text:
|
|
236
|
+
`File not found: ${filePath}\n\n` +
|
|
237
|
+
`Make sure the path uses backslashes and is absolute, ` +
|
|
238
|
+
`e.g. C:\\Users\\You\\Downloads\\audio.mp3`,
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
isError: true,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const configError = validatePaths();
|
|
246
|
+
if (configError) {
|
|
247
|
+
return {
|
|
248
|
+
content: [{ type: "text", text: configError }],
|
|
249
|
+
isError: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Convert to WAV via FFmpeg if needed
|
|
254
|
+
let transcribeFrom = filePath;
|
|
255
|
+
let tmpFile: string | null = null;
|
|
256
|
+
const converting = needsConversion(filePath);
|
|
257
|
+
|
|
258
|
+
if (converting) {
|
|
259
|
+
try {
|
|
260
|
+
tmpFile = await convertToWav(filePath);
|
|
261
|
+
transcribeFrom = tmpFile;
|
|
262
|
+
} catch (err: any) {
|
|
263
|
+
const msg = err?.stderr || err?.message || String(err);
|
|
264
|
+
return {
|
|
265
|
+
content: [
|
|
266
|
+
{
|
|
267
|
+
type: "text",
|
|
268
|
+
text:
|
|
269
|
+
`FFmpeg conversion failed:\n\n${msg}\n\n` +
|
|
270
|
+
`Make sure FFmpeg is installed and in your system PATH.\n` +
|
|
271
|
+
`Download from https://ffmpeg.org/download.html\n` +
|
|
272
|
+
`Or convert to MP3 manually using VLC (Media → Convert/Save → Audio - MP3).`,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
isError: true,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Run whisper-cli
|
|
281
|
+
try {
|
|
282
|
+
const cliArgs = buildArgs(transcribeFrom, model, language, outputFormat, threads);
|
|
283
|
+
const { stdout, stderr } = await execFileAsync(WHISPER_CLI_PATH, cliArgs, {
|
|
284
|
+
maxBuffer: 100 * 1024 * 1024,
|
|
285
|
+
windowsHide: true,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const output = stdout || stderr || "(no output)";
|
|
289
|
+
const note = converting
|
|
290
|
+
? "\n\n[Automatically converted from video/audio format via FFmpeg before transcription]"
|
|
291
|
+
: "";
|
|
292
|
+
|
|
293
|
+
return { content: [{ type: "text", text: output.trim() + note }] };
|
|
294
|
+
} catch (err: any) {
|
|
295
|
+
const message = err?.stderr || err?.stdout || err?.message || String(err);
|
|
296
|
+
return {
|
|
297
|
+
content: [
|
|
298
|
+
{ type: "text", text: `Transcription failed:\n\n${message}` },
|
|
299
|
+
],
|
|
300
|
+
isError: true,
|
|
301
|
+
};
|
|
302
|
+
} finally {
|
|
303
|
+
// Always clean up temp file
|
|
304
|
+
if (tmpFile && existsSync(tmpFile)) {
|
|
305
|
+
try { unlinkSync(tmpFile); } catch { /* ignore cleanup errors */ }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
312
|
+
isError: true,
|
|
313
|
+
};
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// Start
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
async function main() {
|
|
320
|
+
const transport = new StdioServerTransport();
|
|
321
|
+
await server.connect(transport);
|
|
322
|
+
console.error(
|
|
323
|
+
`whisper-windows-mcp v1.1.0 running | threads: ${WHISPER_THREADS}/${SYSTEM_THREADS}`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
main().catch((err) => {
|
|
328
|
+
console.error("Fatal error:", err);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist"]
|
|
15
|
+
}
|