├── .gitignore ├── LICENSE ├── README.md ├── images └── adding_voice.png ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build output 8 | build/ 9 | dist/ 10 | *.tsbuildinfo 11 | 12 | # IDE 13 | .vscode/ 14 | .idea/ 15 | *.swp 16 | *.swo 17 | 18 | # OS 19 | .DS_Store 20 | Thumbs.db 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 say-mcp-server 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # say-mcp-server 2 | Say Server MCP server 3 | 4 | ![macOS System Voice Settings](images/adding_voice.png) 5 | 6 | An MCP server that provides text-to-speech functionality using macOS's built-in `say` command. 7 | 8 | ## Requirements 9 | 10 | - macOS (uses the built-in `say` command) 11 | - Node.js >= 14.0.0 12 | 13 | ## Configuration 14 | 15 | Add the following to your MCP settings configuration file: 16 | 17 | ```json 18 | { 19 | "mcpServers": { 20 | "say": { 21 | "command": "node", 22 | "args": ["/path/to/say-mcp-server/build/index.js"] 23 | } 24 | } 25 | } 26 | ``` 27 | 28 | 29 | ## Installation 30 | 31 | ```bash 32 | npm install say-mcp-server 33 | ``` 34 | 35 | ## Tools 36 | 37 | ### speak 38 | 39 | The `speak` tool provides access to macOS's text-to-speech capabilities with extensive customization options. 40 | 41 | #### Basic Usage 42 | 43 | Use macOS text-to-speech to speak text aloud. 44 | 45 | Parameters: 46 | - `text` (required): Text to speak. Supports: 47 | - Plain text 48 | - Basic punctuation for pauses 49 | - Newlines for natural breaks 50 | - [[slnc 500]] for 500ms silence 51 | - [[rate 200]] for changing speed mid-text 52 | - [[volm 0.5]] for changing volume mid-text 53 | - [[emph +]] and [[emph -]] for emphasis 54 | - [[pbas +10]] for pitch adjustment 55 | - `voice` (optional): Voice to use (default: "Alex") 56 | - `rate` (optional): Speaking rate in words per minute (default: 175, range: 1-500) 57 | - `background` (optional): Run speech in background to allow further MCP interaction (default: false) 58 | 59 | #### Advanced Features 60 | 61 | 1. Voice Modulation: 62 | ```typescript 63 | use_mcp_tool({ 64 | server_name: "say", 65 | tool_name: "speak", 66 | arguments: { 67 | text: "[[volm 0.7]] This is quieter [[volm 1.0]] and this is normal [[volm 1.5]] and this is louder", 68 | voice: "Victoria" 69 | } 70 | }); 71 | ``` 72 | 73 | 2. Dynamic Rate Changes: 74 | ```typescript 75 | use_mcp_tool({ 76 | server_name: "say", 77 | tool_name: "speak", 78 | arguments: { 79 | text: "Normal speed [[rate 300]] now speaking faster [[rate 100]] and now slower", 80 | voice: "Fred" 81 | } 82 | }); 83 | ``` 84 | 85 | 3. Emphasis and Pitch: 86 | ```typescript 87 | use_mcp_tool({ 88 | server_name: "say", 89 | tool_name: "speak", 90 | arguments: { 91 | text: "[[emph +]] Important point! [[emph -]] [[pbas +10]] Higher pitch [[pbas -10]] Lower pitch", 92 | voice: "Samantha" 93 | } 94 | }); 95 | ``` 96 | 97 | #### Integration Examples 98 | 99 | 1. With Marginalia Search: 100 | ```typescript 101 | // Search for a topic and have the results read aloud 102 | const searchResult = await use_mcp_tool({ 103 | server_name: "marginalia-mcp-server", 104 | tool_name: "search", 105 | arguments: { query: "quantum computing basics", count: 1 } 106 | }); 107 | 108 | await use_mcp_tool({ 109 | server_name: "say", 110 | tool_name: "speak", 111 | arguments: { 112 | text: searchResult.results[0].description, 113 | voice: "Daniel", 114 | rate: 150 115 | } 116 | }); 117 | ``` 118 | 119 | 2. With YouTube Transcripts: 120 | ```typescript 121 | // Read a YouTube video transcript 122 | const transcript = await use_mcp_tool({ 123 | server_name: "youtube-transcript", 124 | tool_name: "get_transcript", 125 | arguments: { 126 | url: "https://youtube.com/watch?v=example", 127 | lang: "en" 128 | } 129 | }); 130 | 131 | await use_mcp_tool({ 132 | server_name: "say", 133 | tool_name: "speak", 134 | arguments: { 135 | text: transcript.text, 136 | voice: "Samantha", 137 | rate: 175 138 | } 139 | }); 140 | ``` 141 | 142 | 3. Background Speech with Multiple Actions: 143 | ```typescript 144 | // Start long speech in background 145 | await use_mcp_tool({ 146 | server_name: "say", 147 | tool_name: "speak", 148 | arguments: { 149 | text: "This is a long speech that will run in the background...", 150 | voice: "Rocko (Italian (Italy))", 151 | rate: 69, 152 | background: true 153 | } 154 | }); 155 | 156 | // Immediately perform another action while speech continues 157 | await use_mcp_tool({ 158 | server_name: "marginalia-mcp-server", 159 | tool_name: "search", 160 | arguments: { query: "parallel processing" } 161 | }); 162 | ``` 163 | 164 | 4. With Apple Notes: 165 | ```typescript 166 | // Read notes aloud 167 | const notes = await use_mcp_tool({ 168 | server_name: "apple-notes-mcp", 169 | tool_name: "search-notes", 170 | arguments: { query: "meeting notes" } 171 | }); 172 | 173 | if (notes.length > 0) { 174 | await use_mcp_tool({ 175 | server_name: "say", 176 | tool_name: "speak", 177 | arguments: { 178 | text: notes[0].content, 179 | voice: "Karen", 180 | rate: 160 181 | } 182 | }); 183 | } 184 | ``` 185 | 186 | Example: 187 | ```typescript 188 | use_mcp_tool({ 189 | server_name: "say", 190 | tool_name: "speak", 191 | arguments: { 192 | text: "Hello, world!", 193 | voice: "Victoria", 194 | rate: 200 195 | } 196 | }); 197 | ``` 198 | 199 | ### list_voices 200 | 201 | List all available text-to-speech voices on the system. 202 | 203 | Example: 204 | ```typescript 205 | use_mcp_tool({ 206 | server_name: "say", 207 | tool_name: "list_voices", 208 | arguments: {} 209 | }); 210 | ``` 211 | 212 | ## Recommended Voices 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 |
VoiceLanguage/RegionIntellectual FigureHaikuCLI Specification
Anna (Premium)GermanEmmy NoetherSymmetrie haucht Leben
Algebras verborgne Form
Abstraktion blüht

Symmetry breathes life
Algebra's hidden forms
Abstraction blooms
-v "Anna (Premium)"
Emma (Premium)ItalianMaria Adelaide SneiderAlgoritmi in danza
Macchina sussurra dolce
Il codice vive

Algorithms dance
Machine whispers secrets soft
Code becomes alive
-v "Emma (Premium)"
Federica (Premium)ItalianPia NalliTeoremi fluenti
Numeri danzano liberi
Verità emerge

Flowing theorems dance
Numbers move in freedom's space
Truth emerges pure
-v "Federica (Premium)"
Serena (Premium)English (UK)Bertha SwirlesQuantum waves ripple
Through mathematical seas deep
Truth's light emerges

Quantum waves ripple
Through mathematical seas deep
Truth's light emerges
-v "Serena (Premium)"
Petra (Premium)GermanRuth MoufangAlgebra spricht
In Symmetrien versteckt
Wahrheit erblüht

Algebra speaks soft
Hidden in symmetries pure
Truth blooms anew here
-v "Petra (Premium)"
Yuna (Premium)KoreanHee Oh숨은 패턴 빛나고
마음의 방정식 핀다
지식 자라나

Hidden patterns gleam
Mind's equations softly bloom
Knowledge multiplies
-v "Yuna (Premium)"
Alva (Premium)SwedishSonja KorovkinMönster flödar fritt
Genom tankens labyrinter
Visdom blomstrar här

Patterns flowing free
Through labyrinths of the mind
Wisdom blooms right here
-v "Alva (Premium)"
Amélie (Premium)French (Canada)Sophie GermainNombres premiers murmurent
Dansent entre les silences
Symétrie s'ouvre

Prime numbers whisper
Dancing between the silence
Symmetry unfolds
-v "Amélie (Premium)"
Ewa (Premium)PolishMaria WielgusLogiki korzenie
Matematyczne krainy
Myśl kiełkująca

Logic's tender roots
Mathematical landscapes
Thought's seeds germinate
-v "Ewa (Premium)"
Kiyara (Premium)HindiShakuntala Deviगणित की लय में
अंक नृत्य करते हैं
ज्ञान जगता है

In rhythm of math
Numbers dance their sacred steps
Knowledge awakens
-v "Kiyara (Premium)"
Majed (Premium)ArabicMaha Al-Aswadأرقام ترقص
في فضاء اللانهاية
الحقيقة تشرق

Numbers dance freely
In infinity's vast space
Truth rises like dawn
-v "Majed (Premium)"
Tünde (Premium)HungarianJulia ErdősSzámok táncolnak
Végtelen térben szállnak
Igazság virrad

Numbers dance and soar
Through infinite space they glide
Truth dawns pure and bright
-v "Tünde (Premium)"
Fiona (Enhanced)English (Scottish)Mary SomervilleHighland mists reveal
Mathematical mysteries
Truth shines like the stars

Highland mists reveal
Mathematical mysteries
Truth shines like the stars
-v "Fiona (Enhanced)"
Lesya (Enhanced)UkrainianOlena VoinovaТиша говорить
Між зірками знання спить
Думка проростає

Silence speaks softly
Knowledge sleeps among the stars
Thought begins to grow
-v "Lesya (Enhanced)"
Carmit (Enhanced)HebrewTali Serorמילים נושמות בשקט
בין שורות של דממה
שיר מתעורר

Words breathe silently
Between lines of deep stillness
Poem awakening
-v "Carmit (Enhanced)"
Milena (Enhanced)RussianOlga LadyzhenskayaПамять шепчет нам
Уравнения текут
Истина молчит

Memory whispers
Equations flow like rivers
Truth speaks silently
-v "Milena (Enhanced)"
Katya (Enhanced)RussianSofia KovalevskayaЧисла танцуют
В пространстве бесконечном
Истина цветёт

Numbers dance freely
In space of infinity
Truth blooms like a flower
-v "Katya (Enhanced)"
Damayanti (Enhanced)IndonesianSri PekertiAngka menari
Dalam ruang tak batas
Kebenaran tumbuh

Numbers dance gently
In boundless space they flutter
Truth grows like new leaves
-v "Damayanti (Enhanced)"
Dariush (Enhanced)PersianMaryam Mirzakhaniاعداد می رقصند
در فضای بی پایان
حقیقت می روید

Numbers dance with grace
In endless space they traverse
Truth springs forth anew
-v "Dariush (Enhanced)"
Rocko (Italian)ItalianAstro Boy (Tetsuwan Atomu)
Italian dub
Robot di metallo
Cuore umano batte forte
Pace nel futuro

Metal robot form
Human heart beats strong within
Peace in future dawns
-v "Rocko (Italian (Italy))"
Rocko (Italian)ItalianJeeg Robot d'Acciaio
(Kōtetsu Jeeg)
Acciaio lucente
Protettore dei deboli
Vola nel cielo

Shining steel warrior
Protector of the helpless
Soars through the heavens
-v "Rocko (Italian (Italy))"
Rocko (Italian)ItalianNumero 5
(Short Circuit)
Input infinito
La coscienza si risveglia
Vita artificiale

Infinite input
Consciousness awakening
Artificial life
-v "Rocko (Italian (Italy))"
Binbin (Enhanced)Chinese (Mainland)Li Shanlan算术之道流
数理演绎真理
智慧绽放

Arithmetic flows
Logic unfolds truth's pattern
Wisdom blossoms bright
-v "Binbin (Enhanced)"
Han (Premium)Chinese (Mainland)Chen Jingrun素数之舞动
哥德巴赫猜想
真理永恒

Prime numbers dancing
Goldbach's conjecture whispers
Truth eternal flows
-v "Han (Premium)"
Lilian (Premium)Chinese (Mainland)Hua Luogeng数论之光芒
解析延续美
智慧升华

Number theory shines
Analysis extends grace
Wisdom ascends pure
-v "Lilian (Premium)"
MeijiaChinese (Taiwan)Sun-Yung Alice Chang幾何之美現
曲率流動不息
空間展開

Geometry shows
Curvature flows endlessly
Space unfolds anew
-v "Meijia"
Sinji (Premium)Chinese (Hong Kong)Shing-Tung Yau流形之奧秘
卡拉比空間動
維度交織

Manifolds reveal
Calabi spaces in flow
Dimensions weave truth
-v "Sinji (Premium)"
TingtingChinese (Mainland)Wang Zhenyi星辰轨迹明
天文数学融
智慧闪耀

Starlit paths shine bright
Astronomy meets numbers
Wisdom radiates
-v "Tingting"
Yue (Premium)Chinese (Mainland)Chern Shiing-shen微分几何
纤维丛中寻真
本质显现

Differential forms
In fiber bundles seek truth
Essence emerges
-v "Yue (Premium)"
426 | 427 | ## Configuration 428 | 429 | Add the following to your MCP settings configuration file: 430 | 431 | ```json 432 | { 433 | "mcpServers": { 434 | "say": { 435 | "command": "node", 436 | "args": ["/path/to/say-mcp-server/build/index.js"] 437 | } 438 | } 439 | } 440 | ``` 441 | 442 | ## Requirements 443 | 444 | - macOS (uses the built-in `say` command) 445 | - Node.js >= 14.0.0 446 | 447 | ## Contributors 448 | 449 | - Barton Rhodes ([@bmorphism](https://github.com/bmorphism)) - barton@vibes.lol 450 | 451 | ## License 452 | 453 | MIT 454 | -------------------------------------------------------------------------------- /images/adding_voice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmorphism/say-mcp-server/f79862b600dd8cdff09f13e91dda450800ac78a5/images/adding_voice.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "say-mcp-server", 3 | "version": "0.1.0", 4 | "description": "MCP server for macOS text-to-speech functionality", 5 | "type": "module", 6 | "main": "build/index.js", 7 | "bin": { 8 | "say-mcp-server": "build/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 12 | "start": "node build/index.js", 13 | "dev": "tsc -w", 14 | "prepare": "npm run build" 15 | }, 16 | "files": [ 17 | "build", 18 | "README.md", 19 | "LICENSE" 20 | ], 21 | "keywords": [ 22 | "mcp", 23 | "text-to-speech", 24 | "tts", 25 | "macos", 26 | "say", 27 | "speech" 28 | ], 29 | "author": { 30 | "name": "Barton Rhodes", 31 | "email": "barton@vibes.lol", 32 | "url": "https://github.com/bmorphism" 33 | }, 34 | "license": "MIT", 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/bmorphism/say-mcp-server.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/bmorphism/say-mcp-server/issues" 41 | }, 42 | "homepage": "https://github.com/bmorphism/say-mcp-server#readme", 43 | "engines": { 44 | "node": ">=14.0.0" 45 | }, 46 | "os": ["darwin"], 47 | "dependencies": { 48 | "@modelcontextprotocol/sdk": "^1.1.0" 49 | }, 50 | "devDependencies": { 51 | "@types/node": "^18.0.0", 52 | "typescript": "^4.7.4" 53 | } 54 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | CallToolRequestSchema, 6 | ErrorCode, 7 | ListToolsRequestSchema, 8 | McpError, 9 | } from '@modelcontextprotocol/sdk/types.js'; 10 | import { exec } from 'child_process'; 11 | import { promisify } from 'util'; 12 | 13 | const execAsync = promisify(exec); 14 | 15 | class SayServer { 16 | private server: Server; 17 | 18 | constructor() { 19 | this.server = new Server( 20 | { 21 | name: 'say-mcp-server', 22 | version: '0.1.0', 23 | }, 24 | { 25 | capabilities: { 26 | tools: {}, 27 | }, 28 | } 29 | ); 30 | 31 | this.setupToolHandlers(); 32 | 33 | // Error handling 34 | this.server.onerror = (error) => console.error('[MCP Error]', error); 35 | process.on('SIGINT', async () => { 36 | await this.server.close(); 37 | process.exit(0); 38 | }); 39 | } 40 | 41 | private setupToolHandlers() { 42 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 43 | tools: [ 44 | { 45 | name: 'speak', 46 | description: 'Use macOS text-to-speech to speak text aloud', 47 | inputSchema: { 48 | type: 'object', 49 | properties: { 50 | text: { 51 | type: 'string', 52 | description: 'Text to speak', 53 | }, 54 | voice: { 55 | type: 'string', 56 | description: 'Voice to use (e.g., "Alex", "Victoria", "Daniel")', 57 | default: 'Alex', 58 | }, 59 | rate: { 60 | type: 'number', 61 | description: 'Speaking rate (words per minute)', 62 | minimum: 1, 63 | maximum: 500, 64 | default: 175, 65 | }, 66 | background: { 67 | type: 'boolean', 68 | description: 'Run speech in background to unblock further MCP interaction', 69 | default: false, 70 | }, 71 | }, 72 | required: ['text'], 73 | }, 74 | }, 75 | { 76 | name: 'list_voices', 77 | description: 'List available text-to-speech voices', 78 | inputSchema: { 79 | type: 'object', 80 | properties: {}, 81 | }, 82 | }, 83 | ], 84 | })); 85 | 86 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 87 | switch (request.params.name) { 88 | case 'speak': { 89 | const { text, voice = 'Alex', rate = 175, background = false } = request.params.arguments as { 90 | text: string; 91 | voice?: string; 92 | rate?: number; 93 | background?: boolean; 94 | }; 95 | 96 | try { 97 | await execAsync(`say -v "${voice}" -r ${rate} "${text.replace(/"/g, '\"')}"${background ? ' &' : ''}`); 98 | return { 99 | content: [ 100 | { 101 | type: 'text', 102 | text: `Successfully spoke text using voice "${voice}" at ${rate} words per minute${background ? ' (in background)' : ''}`, 103 | }, 104 | ], 105 | }; 106 | } catch (error: any) { 107 | throw new McpError( 108 | ErrorCode.InternalError, 109 | `Failed to speak text: ${error.message}` 110 | ); 111 | } 112 | } 113 | 114 | case 'list_voices': { 115 | try { 116 | const { stdout } = await execAsync('say -v "?"'); 117 | return { 118 | content: [ 119 | { 120 | type: 'text', 121 | text: stdout, 122 | }, 123 | ], 124 | }; 125 | } catch (error: any) { 126 | throw new McpError( 127 | ErrorCode.InternalError, 128 | `Failed to list voices: ${error.message}` 129 | ); 130 | } 131 | } 132 | 133 | default: 134 | throw new McpError( 135 | ErrorCode.MethodNotFound, 136 | `Unknown tool: ${request.params.name}` 137 | ); 138 | } 139 | }); 140 | } 141 | 142 | async run() { 143 | const transport = new StdioServerTransport(); 144 | await this.server.connect(transport); 145 | console.error('Say MCP server running on stdio'); 146 | } 147 | } 148 | 149 | const server = new SayServer(); 150 | server.run().catch(console.error); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } --------------------------------------------------------------------------------