talktocursor 1.0.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/INSTALL.md +249 -0
- package/README.md +177 -0
- package/build/config.js +82 -0
- package/build/index.js +166 -0
- package/build/settings-server.js +124 -0
- package/package.json +54 -0
- package/public/index.html +1574 -0
- package/scripts/auto-submit.py +394 -0
- package/scripts/silence_detector.py +146 -0
package/INSTALL.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# TalkToCursor - Installation Guide
|
|
2
|
+
|
|
3
|
+
A hands-free voice interface for Cursor AI. Your coding assistant speaks progress updates aloud and can listen for voice commands using ElevenLabs TTS.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Install (via npm)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g talktocursor
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then add to your Cursor MCP config (`~/.cursor/mcp.json`):
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"tts": {
|
|
19
|
+
"command": "npx",
|
|
20
|
+
"args": ["-y", "talktocursor"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Skip to [Step 3: Get your ElevenLabs API Key](#step-3-get-your-elevenlabs-api-key).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Manual Install (from source)
|
|
31
|
+
|
|
32
|
+
### Step 1: Download and extract
|
|
33
|
+
|
|
34
|
+
**Option A** - From tar.gz:
|
|
35
|
+
```bash
|
|
36
|
+
tar -xzf talk-to-cursor.tar.gz
|
|
37
|
+
cd cursor-tts-mcp
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Option B** - From GitHub:
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/yourusername/cursor-tts-mcp.git
|
|
43
|
+
cd cursor-tts-mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Step 2: Install dependencies and build
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install
|
|
50
|
+
npm run build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then add to your Cursor MCP config (`~/.cursor/mcp.json`):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"tts": {
|
|
59
|
+
"command": "node",
|
|
60
|
+
"args": ["/ABSOLUTE/PATH/TO/cursor-tts-mcp/build/index.js"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **Important:** Replace `/ABSOLUTE/PATH/TO/cursor-tts-mcp` with the actual path on your machine.
|
|
67
|
+
>
|
|
68
|
+
> - macOS/Linux: `/Users/yourname/cursor-tts-mcp/build/index.js`
|
|
69
|
+
> - Windows: `C:\\Users\\yourname\\cursor-tts-mcp\\build\\index.js`
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Step 3: Get your ElevenLabs API Key
|
|
74
|
+
|
|
75
|
+
1. Go to [elevenlabs.io/app/settings/api-keys](https://elevenlabs.io/app/settings/api-keys)
|
|
76
|
+
2. Sign up or log in (free tier available with 10,000 characters/month)
|
|
77
|
+
3. Create a new API key and copy it
|
|
78
|
+
|
|
79
|
+
## Step 4: Configure via Settings UI
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run settings
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Open **http://localhost:3847** in your browser, then:
|
|
86
|
+
|
|
87
|
+
1. Paste your ElevenLabs API key and click **Save API Key**
|
|
88
|
+
2. Click **Test Key** to verify it works
|
|
89
|
+
3. (Optional) Browse and select a voice
|
|
90
|
+
4. (Optional) Adjust voice settings (speed, stability, style)
|
|
91
|
+
5. (Optional) Enable Auto-Listen for hands-free voice loop
|
|
92
|
+
|
|
93
|
+
> **Alternatively**, you can set your API key via environment variable:
|
|
94
|
+
> ```json
|
|
95
|
+
> {
|
|
96
|
+
> "mcpServers": {
|
|
97
|
+
> "tts": {
|
|
98
|
+
> "command": "npx",
|
|
99
|
+
> "args": ["-y", "talktocursor"],
|
|
100
|
+
> "env": {
|
|
101
|
+
> "ELEVENLABS_API_KEY": "your-api-key-here"
|
|
102
|
+
> }
|
|
103
|
+
> }
|
|
104
|
+
> }
|
|
105
|
+
> }
|
|
106
|
+
> ```
|
|
107
|
+
|
|
108
|
+
## Step 5: Restart Cursor
|
|
109
|
+
|
|
110
|
+
**Fully quit Cursor** (Cmd+Q on Mac) and reopen it. The MCP server needs a fresh restart to load.
|
|
111
|
+
|
|
112
|
+
## Step 6: Test it
|
|
113
|
+
|
|
114
|
+
1. Open a new Cursor chat (Cmd+L)
|
|
115
|
+
2. Check that the `speak` tool appears in "Available Tools"
|
|
116
|
+
3. Type: **"Say hello using the speak tool"**
|
|
117
|
+
4. You should hear the voice through your speakers!
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Optional: Voice Feedback Rule
|
|
122
|
+
|
|
123
|
+
For the best experience, create a Cursor rule so the agent automatically speaks at key moments.
|
|
124
|
+
|
|
125
|
+
Create the file `~/.cursor/rules/voice-feedback.mdc`:
|
|
126
|
+
|
|
127
|
+
```markdown
|
|
128
|
+
---
|
|
129
|
+
description: MANDATORY voice feedback - agent MUST speak at task start and completion
|
|
130
|
+
alwaysApply: true
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
# Voice Feedback Rule
|
|
134
|
+
|
|
135
|
+
You MUST use the `speak` tool at these moments:
|
|
136
|
+
- **Task Start**: Briefly announce what you're about to do
|
|
137
|
+
- **Task Completion**: Summarize what was done
|
|
138
|
+
|
|
139
|
+
Keep messages concise (1-2 sentences). Always speak at start and end of every task.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Optional: Hands-Free Dictation (macOS only)
|
|
145
|
+
|
|
146
|
+
For a fully hands-free experience with voice dictation:
|
|
147
|
+
|
|
148
|
+
### Auto-Submit Setup
|
|
149
|
+
|
|
150
|
+
1. Enable **Auto-Submit** in the settings UI
|
|
151
|
+
2. Set up a Python virtual environment:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
cd cursor-tts-mcp
|
|
155
|
+
python3 -m venv .venv
|
|
156
|
+
source .venv/bin/activate
|
|
157
|
+
pip install pynput pyobjc-framework-ApplicationServices
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
3. Run in a separate terminal:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm run auto-submit
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
4. Grant Accessibility permissions when prompted:
|
|
167
|
+
- System Settings > Privacy & Security > Accessibility
|
|
168
|
+
- Add your terminal app (Terminal.app, iTerm, or Cursor)
|
|
169
|
+
|
|
170
|
+
### Wispr Voice Loop Setup (requires Wispr Flow)
|
|
171
|
+
|
|
172
|
+
For a full conversational voice loop using [Wispr Flow](https://wispr.com):
|
|
173
|
+
|
|
174
|
+
1. Install Wispr Flow and configure its dictation hotkey
|
|
175
|
+
2. Enable **Wispr Voice Loop** in the settings UI
|
|
176
|
+
3. Configure the hotkey to match your Wispr Flow settings
|
|
177
|
+
4. Install additional Python dependency:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
source .venv/bin/activate
|
|
181
|
+
pip install sounddevice numpy
|
|
182
|
+
brew install portaudio
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
5. Grant Microphone permissions to your terminal app
|
|
186
|
+
6. Run the auto-submit script (handles both auto-submit and voice loop):
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npm run auto-submit
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Configuration
|
|
195
|
+
|
|
196
|
+
All settings are stored in `config.json` in the project root. You can edit this directly or use the settings UI.
|
|
197
|
+
|
|
198
|
+
| Setting | Description | Default |
|
|
199
|
+
|---------|-------------|---------|
|
|
200
|
+
| `apiKey` | ElevenLabs API key | (required) |
|
|
201
|
+
| `voiceId` | ElevenLabs voice ID | Rachel |
|
|
202
|
+
| `model` | TTS model | `eleven_flash_v2_5` |
|
|
203
|
+
| `voiceSettings.speed` | Speech speed (0.7-1.2) | 1.0 |
|
|
204
|
+
| `voiceSettings.stability` | Voice stability (0-1) | 0.5 |
|
|
205
|
+
| `voiceSettings.similarityBoost` | Voice similarity (0-1) | 0.75 |
|
|
206
|
+
| `voiceSettings.style` | Style exaggeration (0-1) | 0.0 |
|
|
207
|
+
| `autoListen` | Auto-listen after tasks | true |
|
|
208
|
+
| `autoSubmit.enabled` | Auto-press Enter | false |
|
|
209
|
+
| `wisprLoop.enabled` | Voice loop with Wispr | false |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Troubleshooting
|
|
214
|
+
|
|
215
|
+
### Tool doesn't appear in Cursor
|
|
216
|
+
- Fully quit and restart Cursor (Cmd+Q)
|
|
217
|
+
- Verify `~/.cursor/mcp.json` has the correct path
|
|
218
|
+
- Run `npm run build` to ensure the project is compiled
|
|
219
|
+
|
|
220
|
+
### "API key not set" error
|
|
221
|
+
- Open settings: `npm run settings`
|
|
222
|
+
- Enter your API key and save
|
|
223
|
+
- Restart Cursor
|
|
224
|
+
|
|
225
|
+
### No audio output
|
|
226
|
+
- Check system volume and speaker output
|
|
227
|
+
- Verify `mpv` is installed: `brew install mpv`
|
|
228
|
+
- Test your API key in the settings UI
|
|
229
|
+
|
|
230
|
+
### Auto-submit not working
|
|
231
|
+
- Ensure macOS Accessibility permissions are granted
|
|
232
|
+
- Check that Cursor is the frontmost app
|
|
233
|
+
- Try increasing the silence delay in settings
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Scripts Reference
|
|
238
|
+
|
|
239
|
+
| Command | Description |
|
|
240
|
+
|---------|-------------|
|
|
241
|
+
| `npm run build` | Compile TypeScript |
|
|
242
|
+
| `npm run settings` | Open settings UI (port 3847) |
|
|
243
|
+
| `npm run auto-submit` | Start auto-submit + voice loop (macOS) |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Cursor TTS MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that adds text-to-speech capabilities to Cursor AI. The agent can speak progress updates, completions, and responses aloud using ElevenLabs TTS, enabling hands-free coding workflows.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔊 **Text-to-Speech** - Agent speaks aloud via ElevenLabs API
|
|
8
|
+
- 🎛️ **Settings UI** - Web interface to configure API key, voice, and speech parameters
|
|
9
|
+
- ⚡ **Auto-Submit** - Optional: automatically press Enter when dictation finishes (hands-free)
|
|
10
|
+
- 🎨 **Voice Presets** - Quick settings for fast, slow, expressive, stable, and dramatic speech
|
|
11
|
+
- 🔧 **Configurable** - Speed, stability, similarity boost, and style exaggeration controls
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### 1. Clone or download this repository
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/yourusername/cursor-tts-mcp.git
|
|
19
|
+
cd cursor-tts-mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or download and extract the ZIP.
|
|
23
|
+
|
|
24
|
+
### 2. Install dependencies
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 3. Build the project
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm run build
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 4. Configure Cursor to use the MCP server
|
|
37
|
+
|
|
38
|
+
Edit (or create) `~/.cursor/mcp.json`:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"tts": {
|
|
44
|
+
"command": "node",
|
|
45
|
+
"args": ["/ABSOLUTE/PATH/TO/cursor-tts-mcp/build/index.js"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Important:** Replace `/ABSOLUTE/PATH/TO/cursor-tts-mcp` with the actual full path to where you cloned/downloaded this project.
|
|
52
|
+
|
|
53
|
+
For example:
|
|
54
|
+
- macOS/Linux: `/Users/yourname/cursor-tts-mcp/build/index.js`
|
|
55
|
+
- Windows: `C:\\Users\\yourname\\cursor-tts-mcp\\build\\index.js`
|
|
56
|
+
|
|
57
|
+
### 5. Get your ElevenLabs API key
|
|
58
|
+
|
|
59
|
+
1. Go to [elevenlabs.io/app/settings/api-keys](https://elevenlabs.io/app/settings/api-keys)
|
|
60
|
+
2. Sign up or log in (free tier available)
|
|
61
|
+
3. Create a new API key and copy it
|
|
62
|
+
|
|
63
|
+
### 6. Configure the MCP server
|
|
64
|
+
|
|
65
|
+
Open the settings UI:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run settings
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then open http://localhost:3847 in your browser and:
|
|
72
|
+
1. Paste your ElevenLabs API key
|
|
73
|
+
2. Click "Test Key" to verify it works
|
|
74
|
+
3. Click "Save API Key"
|
|
75
|
+
4. (Optional) Choose a voice, model, and voice settings
|
|
76
|
+
5. (Optional) Enable Auto-Submit if you want hands-free dictation
|
|
77
|
+
|
|
78
|
+
### 7. Restart Cursor
|
|
79
|
+
|
|
80
|
+
**Fully quit Cursor** (Cmd+Q on Mac, or close completely on Windows/Linux) and reopen it.
|
|
81
|
+
|
|
82
|
+
### 8. Test it
|
|
83
|
+
|
|
84
|
+
1. Open a new Cursor chat (Cmd+L)
|
|
85
|
+
2. Check that the `speak` or `user-tts-speak` tool appears in "Available Tools"
|
|
86
|
+
3. Type: **"Say hello using the speak tool"**
|
|
87
|
+
4. You should hear the voice through your speakers!
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
Once installed, the Cursor AI agent will automatically speak at key moments:
|
|
92
|
+
- When starting a task
|
|
93
|
+
- When completing a task
|
|
94
|
+
- When encountering errors or needing clarification
|
|
95
|
+
- At major progress milestones
|
|
96
|
+
|
|
97
|
+
You can customize when the agent speaks by editing `~/.cursor/rules/voice-feedback.mdc`.
|
|
98
|
+
|
|
99
|
+
## Voice Settings
|
|
100
|
+
|
|
101
|
+
The settings UI lets you adjust:
|
|
102
|
+
|
|
103
|
+
- **Speed** (0.7x - 1.2x) - How fast the speech is delivered
|
|
104
|
+
- **Stability** (0-1) - More consistent vs. more expressive
|
|
105
|
+
- **Similarity Boost** (0-1) - How closely it matches the original voice
|
|
106
|
+
- **Style Exaggeration** (0-1) - Amplifies the speaker's style (V2+ models)
|
|
107
|
+
|
|
108
|
+
**Quick Presets:**
|
|
109
|
+
- Default - Balanced settings
|
|
110
|
+
- Fast - Quick and energetic
|
|
111
|
+
- Slow - Clear and measured
|
|
112
|
+
- Expressive - Dynamic and varied
|
|
113
|
+
- Stable - Consistent tone
|
|
114
|
+
- Dramatic - Maximum style
|
|
115
|
+
|
|
116
|
+
## Auto-Submit (Optional)
|
|
117
|
+
|
|
118
|
+
For completely hands-free dictation:
|
|
119
|
+
|
|
120
|
+
1. Enable "Auto-Submit" in the settings UI
|
|
121
|
+
2. Adjust the silence delay (how long to wait after you stop speaking)
|
|
122
|
+
3. Save the settings
|
|
123
|
+
4. Run in a separate terminal:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npm run auto-submit
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Requirements:**
|
|
130
|
+
- macOS only (uses Accessibility API)
|
|
131
|
+
- Grant Accessibility permissions: System Settings > Privacy & Security > Accessibility > Add your terminal app
|
|
132
|
+
|
|
133
|
+
The script monitors the text field and automatically presses Enter when dictation finishes.
|
|
134
|
+
|
|
135
|
+
## Configuration Files
|
|
136
|
+
|
|
137
|
+
- **`config.json`** - Stores API key, voice settings, and auto-submit preferences
|
|
138
|
+
- **`~/.cursor/mcp.json`** - Registers the MCP server with Cursor
|
|
139
|
+
- **`~/.cursor/rules/voice-feedback.mdc`** - Controls when the agent speaks
|
|
140
|
+
|
|
141
|
+
## Troubleshooting
|
|
142
|
+
|
|
143
|
+
**Tool doesn't appear in Cursor?**
|
|
144
|
+
- Make sure you fully quit and restarted Cursor (Cmd+Q)
|
|
145
|
+
- Check that `~/.cursor/mcp.json` has the correct absolute path
|
|
146
|
+
- Run `npm run build` to ensure the project is compiled
|
|
147
|
+
|
|
148
|
+
**"API key not set" error?**
|
|
149
|
+
- Open the settings UI: `npm run settings`
|
|
150
|
+
- Enter your ElevenLabs API key and click "Save API Key"
|
|
151
|
+
- Restart Cursor
|
|
152
|
+
|
|
153
|
+
**No audio?**
|
|
154
|
+
- Check system volume and speaker output
|
|
155
|
+
- Verify `mpv` is installed: `mpv --version` (installed automatically by ElevenLabs SDK)
|
|
156
|
+
- Test your API key in the settings UI
|
|
157
|
+
|
|
158
|
+
**Auto-submit not working?**
|
|
159
|
+
- Ensure macOS Accessibility permissions are granted
|
|
160
|
+
- Check that Cursor is the frontmost app when dictating
|
|
161
|
+
- Adjust the "Min Text Length" if short dictations aren't triggering
|
|
162
|
+
- Increase "Silence Delay" if prompts are being submitted too early
|
|
163
|
+
|
|
164
|
+
## Scripts
|
|
165
|
+
|
|
166
|
+
- `npm run build` - Compile TypeScript to JavaScript
|
|
167
|
+
- `npm run settings` - Open the web settings UI
|
|
168
|
+
- `npm run auto-submit` - Start the auto-submit script (macOS only)
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
173
|
+
|
|
174
|
+
## Credits
|
|
175
|
+
|
|
176
|
+
- [ElevenLabs](https://elevenlabs.io) for TTS API
|
|
177
|
+
- [Model Context Protocol](https://modelcontextprotocol.io) for MCP SDK
|
package/build/config.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
const CONFIG_PATH = join(__dirname, "..", "config.json");
|
|
7
|
+
const DEFAULT_VOICE_SETTINGS = {
|
|
8
|
+
speed: 1.0,
|
|
9
|
+
stability: 0.5,
|
|
10
|
+
similarityBoost: 0.75,
|
|
11
|
+
style: 0.0,
|
|
12
|
+
};
|
|
13
|
+
const DEFAULT_AUTO_SUBMIT = {
|
|
14
|
+
enabled: false,
|
|
15
|
+
silenceDelay: 3.0,
|
|
16
|
+
minTextLength: 15,
|
|
17
|
+
targetApp: "Cursor",
|
|
18
|
+
};
|
|
19
|
+
const DEFAULT_WISPR_LOOP = {
|
|
20
|
+
enabled: false,
|
|
21
|
+
ttsDelay: 8.0,
|
|
22
|
+
silenceThreshold: 0.02,
|
|
23
|
+
silenceDuration: 2.0,
|
|
24
|
+
wisprHotkey: "shift+ctrl",
|
|
25
|
+
manualTriggerHotkey: "ctrl+shift+l",
|
|
26
|
+
};
|
|
27
|
+
const DEFAULT_CONFIG = {
|
|
28
|
+
apiKey: "",
|
|
29
|
+
voiceId: "21m00Tcm4TlvDq8ikWAM",
|
|
30
|
+
model: "eleven_flash_v2_5",
|
|
31
|
+
voiceSettings: { ...DEFAULT_VOICE_SETTINGS },
|
|
32
|
+
autoSubmit: { ...DEFAULT_AUTO_SUBMIT },
|
|
33
|
+
wisprLoop: { ...DEFAULT_WISPR_LOOP },
|
|
34
|
+
autoListen: true,
|
|
35
|
+
};
|
|
36
|
+
export function loadConfig() {
|
|
37
|
+
try {
|
|
38
|
+
if (existsSync(CONFIG_PATH)) {
|
|
39
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
40
|
+
const parsed = JSON.parse(raw);
|
|
41
|
+
return {
|
|
42
|
+
...DEFAULT_CONFIG,
|
|
43
|
+
...parsed,
|
|
44
|
+
voiceSettings: {
|
|
45
|
+
...DEFAULT_VOICE_SETTINGS,
|
|
46
|
+
...(parsed.voiceSettings || {}),
|
|
47
|
+
},
|
|
48
|
+
autoSubmit: {
|
|
49
|
+
...DEFAULT_AUTO_SUBMIT,
|
|
50
|
+
...(parsed.autoSubmit || {}),
|
|
51
|
+
},
|
|
52
|
+
wisprLoop: {
|
|
53
|
+
...DEFAULT_WISPR_LOOP,
|
|
54
|
+
...(parsed.wisprLoop || {}),
|
|
55
|
+
},
|
|
56
|
+
autoListen: parsed.autoListen !== undefined ? parsed.autoListen : DEFAULT_CONFIG.autoListen,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error("[Config] Error reading config.json:", error);
|
|
62
|
+
}
|
|
63
|
+
return { ...DEFAULT_CONFIG, voiceSettings: { ...DEFAULT_VOICE_SETTINGS }, autoSubmit: { ...DEFAULT_AUTO_SUBMIT }, wisprLoop: { ...DEFAULT_WISPR_LOOP }, autoListen: DEFAULT_CONFIG.autoListen };
|
|
64
|
+
}
|
|
65
|
+
export function saveConfig(config) {
|
|
66
|
+
const current = loadConfig();
|
|
67
|
+
const updated = { ...current, ...config };
|
|
68
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(updated, null, 2), "utf-8");
|
|
69
|
+
return updated;
|
|
70
|
+
}
|
|
71
|
+
export function getEffectiveConfig() {
|
|
72
|
+
const fileConfig = loadConfig();
|
|
73
|
+
return {
|
|
74
|
+
apiKey: process.env.ELEVENLABS_API_KEY || fileConfig.apiKey,
|
|
75
|
+
voiceId: process.env.ELEVENLABS_VOICE_ID || fileConfig.voiceId,
|
|
76
|
+
model: fileConfig.model || DEFAULT_CONFIG.model,
|
|
77
|
+
voiceSettings: fileConfig.voiceSettings,
|
|
78
|
+
autoSubmit: fileConfig.autoSubmit,
|
|
79
|
+
wisprLoop: fileConfig.wisprLoop,
|
|
80
|
+
autoListen: fileConfig.autoListen,
|
|
81
|
+
};
|
|
82
|
+
}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ElevenLabsClient, play } from "@elevenlabs/elevenlabs-js";
|
|
6
|
+
import { getEffectiveConfig } from "./config.js";
|
|
7
|
+
import { writeFileSync } from "fs";
|
|
8
|
+
import { join, dirname } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
// Load config (config.json with env var overrides)
|
|
13
|
+
const config = getEffectiveConfig();
|
|
14
|
+
// Create server instance
|
|
15
|
+
const server = new McpServer({
|
|
16
|
+
name: "cursor-tts",
|
|
17
|
+
version: "1.0.0",
|
|
18
|
+
});
|
|
19
|
+
// Initialize ElevenLabs client
|
|
20
|
+
const elevenlabs = new ElevenLabsClient({
|
|
21
|
+
apiKey: config.apiKey,
|
|
22
|
+
});
|
|
23
|
+
const voiceId = config.voiceId;
|
|
24
|
+
const ttsQueue = [];
|
|
25
|
+
let isProcessingQueue = false;
|
|
26
|
+
async function processTTSQueue() {
|
|
27
|
+
if (isProcessingQueue || ttsQueue.length === 0) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
isProcessingQueue = true;
|
|
31
|
+
while (ttsQueue.length > 0) {
|
|
32
|
+
const item = ttsQueue.shift();
|
|
33
|
+
try {
|
|
34
|
+
console.error(`[TTS] Speaking: ${item.text}`);
|
|
35
|
+
// Call ElevenLabs TTS API
|
|
36
|
+
const audio = await elevenlabs.textToSpeech.convert(voiceId, {
|
|
37
|
+
text: item.text,
|
|
38
|
+
modelId: config.model,
|
|
39
|
+
voiceSettings: {
|
|
40
|
+
speed: config.voiceSettings.speed,
|
|
41
|
+
stability: config.voiceSettings.stability,
|
|
42
|
+
similarityBoost: config.voiceSettings.similarityBoost,
|
|
43
|
+
style: config.voiceSettings.style,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
// Play and WAIT for audio to complete
|
|
47
|
+
await play(audio);
|
|
48
|
+
// Write TTS completion signal for background script
|
|
49
|
+
const completionPath = join(__dirname, "..", "tts-complete.json");
|
|
50
|
+
const completionSignal = {
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
completed: true,
|
|
53
|
+
};
|
|
54
|
+
writeFileSync(completionPath, JSON.stringify(completionSignal, null, 2), "utf-8");
|
|
55
|
+
console.error(`[TTS] Playback complete, signal written: ${completionPath}`);
|
|
56
|
+
item.resolve({
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: "text",
|
|
60
|
+
text: `Spoken: "${item.text}"`,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
67
|
+
console.error(`[TTS] Error: ${errorMessage}`);
|
|
68
|
+
item.reject({
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Failed to speak: ${errorMessage}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
isProcessingQueue = false;
|
|
80
|
+
}
|
|
81
|
+
function queueTTS(text) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
ttsQueue.push({ text, resolve, reject });
|
|
84
|
+
processTTSQueue();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// Register the speak tool
|
|
88
|
+
server.registerTool("speak", {
|
|
89
|
+
description: "Speak text aloud using text-to-speech. Use this to announce task progress, completions, and important updates so the user can follow along without looking at the screen.",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
text: z
|
|
92
|
+
.string()
|
|
93
|
+
.describe("The text to speak aloud. Keep it concise (1-2 sentences max)."),
|
|
94
|
+
},
|
|
95
|
+
}, async ({ text }) => {
|
|
96
|
+
// Queue the TTS request to prevent overlapping audio
|
|
97
|
+
return await queueTTS(text);
|
|
98
|
+
});
|
|
99
|
+
// Register the listen tool
|
|
100
|
+
server.registerTool("listen", {
|
|
101
|
+
description: "Signal the background script to start listening for user voice input via Wispr Flow. Call this after speaking task completion to enable hands-free conversational loop.",
|
|
102
|
+
inputSchema: {},
|
|
103
|
+
}, async () => {
|
|
104
|
+
try {
|
|
105
|
+
// Check if auto-listen is enabled
|
|
106
|
+
if (!config.autoListen) {
|
|
107
|
+
console.error(`[TTS] Auto-listen is disabled, skipping listen signal`);
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: "Auto-listen is disabled",
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const signalPath = join(__dirname, "..", "listen-signal.json");
|
|
118
|
+
const signal = {
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
triggered: true,
|
|
121
|
+
};
|
|
122
|
+
writeFileSync(signalPath, JSON.stringify(signal, null, 2), "utf-8");
|
|
123
|
+
console.error(`[TTS] Listen signal written: ${signalPath}`);
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: "Listening for user input...",
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
135
|
+
console.error(`[TTS] Listen error: ${errorMessage}`);
|
|
136
|
+
return {
|
|
137
|
+
content: [
|
|
138
|
+
{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: `Failed to start listening: ${errorMessage}`,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
isError: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
// Main function to start the server
|
|
148
|
+
async function main() {
|
|
149
|
+
// Validate API key
|
|
150
|
+
if (!config.apiKey) {
|
|
151
|
+
console.error("[TTS] ERROR: No API key found! Set ELEVENLABS_API_KEY env var or configure via the settings UI.");
|
|
152
|
+
console.error("[TTS] Run 'npm run settings' to open the settings UI.");
|
|
153
|
+
console.error("[TTS] Or get your API key from: https://elevenlabs.io/app/settings/api-keys");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
console.error(`[TTS] Starting Cursor TTS MCP Server...`);
|
|
157
|
+
console.error(`[TTS] Voice ID: ${voiceId}`);
|
|
158
|
+
console.error(`[TTS] Model: ${config.model}`);
|
|
159
|
+
const transport = new StdioServerTransport();
|
|
160
|
+
await server.connect(transport);
|
|
161
|
+
console.error("[TTS] Server running on stdio");
|
|
162
|
+
}
|
|
163
|
+
main().catch((error) => {
|
|
164
|
+
console.error("[TTS] Fatal error in main():", error);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|