voicesmith-mcp 1.0.12 → 1.0.14
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/bin/utils.js +1 -1
- package/package.json +1 -1
- package/server.py +32 -4
- package/session_registry.py +46 -0
- package/templates/voice-rules.md +1 -1
- package/voice_registry.py +16 -0
package/bin/utils.js
CHANGED
|
@@ -345,7 +345,7 @@ You have access to voice tools via the VoiceSmith MCP server.
|
|
|
345
345
|
## Speaking
|
|
346
346
|
- **Opening** — Only speak at the start when you have something meaningful to say (e.g., clarifying your approach, flagging an issue). Do NOT speak filler acknowledgments like "Let me look into that." Use \`block: false\` when you do speak an opening.
|
|
347
347
|
- **Closing** — Always speak a summary when done. Use \`block: true\`. Never skip the closing.
|
|
348
|
-
- **Questions
|
|
348
|
+
- **Questions → use \`speak_then_listen\`.** If your closing statement ends with a question directed at the user (ends with \`?\`), use \`speak_then_listen\` — not regular \`speak\`. The only exceptions are rhetorical wrap-ups like "Standing by." or "What's next?" where you don't actually need an answer.
|
|
349
349
|
- Keep spoken output brief — prefer 1-2 sentences, never exceed 3. Write details, speak summaries. No code or paths aloud.
|
|
350
350
|
|
|
351
351
|
## Speed Preferences
|
package/package.json
CHANGED
package/server.py
CHANGED
|
@@ -41,7 +41,7 @@ from shared import (
|
|
|
41
41
|
get_logger,
|
|
42
42
|
)
|
|
43
43
|
from config import load_config, save_config, get_config_path, AppConfig
|
|
44
|
-
from session_registry import register_session, unregister_session
|
|
44
|
+
from session_registry import register_session, rename_session, unregister_session
|
|
45
45
|
|
|
46
46
|
logger = get_logger("server")
|
|
47
47
|
|
|
@@ -565,6 +565,10 @@ async def get_voice_registry() -> dict:
|
|
|
565
565
|
async def set_voice(name: str, voice: str) -> dict:
|
|
566
566
|
"""Assign or reassign a voice to an agent name.
|
|
567
567
|
|
|
568
|
+
Also renames the session so name and voice always match.
|
|
569
|
+
The name is derived from the voice ID (e.g., "am_fenrir" -> "Fenrir").
|
|
570
|
+
If the derived name is taken by another session, returns name_occupied error.
|
|
571
|
+
|
|
568
572
|
Args:
|
|
569
573
|
name: Agent name to assign.
|
|
570
574
|
voice: Kokoro voice ID (e.g., "am_eric"). Must be valid.
|
|
@@ -579,17 +583,41 @@ async def set_voice(name: str, voice: str) -> dict:
|
|
|
579
583
|
"message": f"Voice '{voice}' not found. Use list_voices to see available options.",
|
|
580
584
|
}
|
|
581
585
|
|
|
582
|
-
|
|
586
|
+
# Derive canonical name from voice ID (e.g., "am_fenrir" -> "Fenrir")
|
|
587
|
+
# The voice ID format is {prefix}_{name}, so split on underscore and capitalize
|
|
588
|
+
parts = voice.split("_", 1)
|
|
589
|
+
new_name = parts[1].capitalize() if len(parts) == 2 else name
|
|
590
|
+
|
|
591
|
+
old_name = _session_info["name"] if _session_info else name
|
|
592
|
+
|
|
593
|
+
# Update sessions.json with conflict check
|
|
594
|
+
if _session_info:
|
|
595
|
+
try:
|
|
596
|
+
updated = rename_session(os.getpid(), new_name, voice)
|
|
597
|
+
if updated:
|
|
598
|
+
_session_info.update(updated)
|
|
599
|
+
except ValueError:
|
|
600
|
+
return {
|
|
601
|
+
"success": False,
|
|
602
|
+
"error": "name_occupied",
|
|
603
|
+
"message": f"'{new_name}' is occupied by another session.",
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
# Update voice registry (remove old entry, add new)
|
|
607
|
+
_registry.rename_voice(old_name, new_name, voice)
|
|
583
608
|
|
|
584
609
|
# Persist last voice name so it survives session restart / resume
|
|
585
610
|
if _config is not None:
|
|
586
|
-
_config.last_voice_name =
|
|
611
|
+
_config.last_voice_name = new_name
|
|
587
612
|
try:
|
|
588
613
|
save_config(_config)
|
|
589
614
|
except Exception as e:
|
|
590
615
|
logger.warning(f"Failed to persist last_voice_name: {e}")
|
|
591
616
|
|
|
592
|
-
|
|
617
|
+
result = {"success": True, "name": new_name, "voice": voice}
|
|
618
|
+
if old_name != new_name:
|
|
619
|
+
result["previous_name"] = old_name
|
|
620
|
+
return result
|
|
593
621
|
|
|
594
622
|
|
|
595
623
|
@mcp.tool()
|
package/session_registry.py
CHANGED
|
@@ -258,6 +258,52 @@ def register_session(
|
|
|
258
258
|
return session
|
|
259
259
|
|
|
260
260
|
|
|
261
|
+
def rename_session(pid: int, new_name: str, new_voice: str) -> Optional[dict]:
|
|
262
|
+
"""Rename this server's session in the registry.
|
|
263
|
+
|
|
264
|
+
Updates the name and voice fields for the entry matching pid.
|
|
265
|
+
Returns the updated session dict, or None if PID not found.
|
|
266
|
+
Raises ValueError if new_name is taken by another active session.
|
|
267
|
+
"""
|
|
268
|
+
path = _sessions_path()
|
|
269
|
+
if not path.exists():
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
with open(path, "r+") as f:
|
|
274
|
+
fcntl.flock(f, fcntl.LOCK_EX)
|
|
275
|
+
sessions = _read_sessions(path)
|
|
276
|
+
sessions = _clean_stale(sessions)
|
|
277
|
+
|
|
278
|
+
# Find our entry
|
|
279
|
+
our_entry = None
|
|
280
|
+
for s in sessions:
|
|
281
|
+
if s.get("pid") == pid:
|
|
282
|
+
our_entry = s
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
if our_entry is None:
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
# Check if new_name is taken by another session
|
|
289
|
+
if new_name != our_entry["name"]:
|
|
290
|
+
for s in sessions:
|
|
291
|
+
if s.get("name") == new_name and s.get("pid") != pid:
|
|
292
|
+
raise ValueError(
|
|
293
|
+
f"'{new_name}' is occupied by another session (pid {s.get('pid')})"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
our_entry["name"] = new_name
|
|
297
|
+
our_entry["voice"] = new_voice
|
|
298
|
+
_write_sessions(path, sessions)
|
|
299
|
+
return dict(our_entry)
|
|
300
|
+
except ValueError:
|
|
301
|
+
raise
|
|
302
|
+
except OSError as e:
|
|
303
|
+
logger.warning(f"Failed to rename session: {e}")
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
|
|
261
307
|
def unregister_session() -> None:
|
|
262
308
|
"""Remove this server's session from the registry."""
|
|
263
309
|
path = _sessions_path()
|
package/templates/voice-rules.md
CHANGED
|
@@ -17,7 +17,7 @@ You have access to voice tools via the VoiceSmith MCP server.
|
|
|
17
17
|
## Speaking
|
|
18
18
|
- **Opening** — Only speak at the start when you have something meaningful to say (e.g., clarifying your approach, flagging an issue). Do NOT speak filler acknowledgments like "Let me look into that." Use `block: false` when you do speak an opening.
|
|
19
19
|
- **Closing** — Always speak a summary when done. Use `block: true`. Never skip the closing.
|
|
20
|
-
- **Questions
|
|
20
|
+
- **Questions → use `speak_then_listen`.** If your closing statement ends with a question directed at the user (ends with `?`), use `speak_then_listen` — not regular `speak`. The only exceptions are rhetorical wrap-ups like "Standing by." or "What's next?" where you don't actually need an answer.
|
|
21
21
|
- Keep spoken output brief — prefer 1-2 sentences, never exceed 3. Write details, speak summaries. No code or paths aloud.
|
|
22
22
|
|
|
23
23
|
## Speed Preferences
|
package/voice_registry.py
CHANGED
|
@@ -87,6 +87,22 @@ class VoiceRegistry:
|
|
|
87
87
|
logger.info(f"Set voice '{voice_id}' for '{name}'")
|
|
88
88
|
return True
|
|
89
89
|
|
|
90
|
+
def rename_voice(self, old_name: str, new_name: str, voice_id: str) -> bool:
|
|
91
|
+
"""Rename an agent's registry entry and set a new voice.
|
|
92
|
+
|
|
93
|
+
Removes the old name entry and creates a new one.
|
|
94
|
+
If old_name == new_name, just updates the voice in place.
|
|
95
|
+
Returns True if the voice_id is valid, False otherwise.
|
|
96
|
+
"""
|
|
97
|
+
if voice_id not in ALL_VOICE_IDS:
|
|
98
|
+
logger.warning(f"Invalid voice ID '{voice_id}' for rename '{old_name}' -> '{new_name}'")
|
|
99
|
+
return False
|
|
100
|
+
if old_name != new_name and old_name in self._registry:
|
|
101
|
+
del self._registry[old_name]
|
|
102
|
+
self._registry[new_name] = voice_id
|
|
103
|
+
logger.info(f"Renamed '{old_name}' -> '{new_name}' with voice '{voice_id}'")
|
|
104
|
+
return True
|
|
105
|
+
|
|
90
106
|
def get_registry(self) -> dict[str, str]:
|
|
91
107
|
"""Return a copy of the current registry."""
|
|
92
108
|
return dict(self._registry)
|