stackchan-mcp 0.6.0__tar.gz → 0.7.0__tar.gz
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.
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/PKG-INFO +1 -1
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/pyproject.toml +1 -1
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/stdio_server.py +97 -3
- stackchan_mcp-0.7.0/stackchan_mcp/stt/orchestrator.py +552 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_stdio_server.py +194 -0
- stackchan_mcp-0.7.0/tests/test_stt_orchestrator.py +1150 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/uv.lock +1 -1
- stackchan_mcp-0.6.0/stackchan_mcp/stt/orchestrator.py +0 -306
- stackchan_mcp-0.6.0/tests/test_stt_orchestrator.py +0 -441
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/.env.example +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/.gitignore +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/LICENSE +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/README.md +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/__init__.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/__main__.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/audio_stream.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/capture_server.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/cli.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/esp32_client.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/gateway.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/handlers/__init__.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/handlers/audio.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/handlers/camera.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/handlers/robot.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/mcp_router.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/protocol.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/server.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/stt/__init__.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/stt/audio_utils.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/stt/base.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/stt/faster_whisper.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/stt/openai_whisper.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/tools.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/tts/__init__.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/tts/audio_utils.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/tts/base.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/tts/orchestrator.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/stackchan_mcp/tts/voicevox.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/_audio_fixtures.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/conftest.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_audio_stream.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_audio_utils.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_capture_server.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_cli.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_esp32_client.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_gateway.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_mcp_router.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_orchestrator.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_protocol.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_stt_audio_utils.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_stt_framework.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_tts_framework.py +0 -0
- {stackchan_mcp-0.6.0 → stackchan_mcp-0.7.0}/tests/test_voicevox.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stackchan-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Two-faced MCP gateway for StackChan (xiaozhi-esp32): bridges stdio MCP clients to the ESP32 over WebSocket + HTTP.
|
|
5
5
|
Project-URL: Homepage, https://github.com/kisaragi-mochi/stackchan-mcp
|
|
6
6
|
Project-URL: Repository, https://github.com/kisaragi-mochi/stackchan-mcp
|
|
@@ -103,8 +103,14 @@ def create_server() -> Server:
|
|
|
103
103
|
Tool(
|
|
104
104
|
name="move_head",
|
|
105
105
|
description=(
|
|
106
|
-
"Move the robot's head to
|
|
107
|
-
"yaw: horizontal (-90 to 90), pitch: vertical (
|
|
106
|
+
"Move the robot's head to safe, recommended angles. "
|
|
107
|
+
"yaw: horizontal (-90 to 90), pitch: vertical (5 to 85, "
|
|
108
|
+
"the M5Stack-recommended operating range). Out-of-range "
|
|
109
|
+
"requests are rejected at this MCP layer; for advanced "
|
|
110
|
+
"callers that need the firmware hard clamp (pitch 0..88), "
|
|
111
|
+
"use the firmware-side `set_head_angles` device tool, "
|
|
112
|
+
"which exposes a permissive schema and the authoritative "
|
|
113
|
+
"two-tier guard described in the README."
|
|
108
114
|
),
|
|
109
115
|
inputSchema={
|
|
110
116
|
"type": "object",
|
|
@@ -112,10 +118,19 @@ def create_server() -> Server:
|
|
|
112
118
|
"yaw": {
|
|
113
119
|
"type": "integer",
|
|
114
120
|
"description": "Horizontal angle in degrees (-90 to 90)",
|
|
121
|
+
"minimum": -90,
|
|
122
|
+
"maximum": 90,
|
|
115
123
|
},
|
|
116
124
|
"pitch": {
|
|
117
125
|
"type": "integer",
|
|
118
|
-
"description":
|
|
126
|
+
"description": (
|
|
127
|
+
"Vertical angle in degrees (5 to 85, "
|
|
128
|
+
"M5Stack-recommended operating range). For the "
|
|
129
|
+
"wider firmware hard clamp (0..88), use the "
|
|
130
|
+
"`set_head_angles` device tool instead."
|
|
131
|
+
),
|
|
132
|
+
"minimum": 5,
|
|
133
|
+
"maximum": 85,
|
|
119
134
|
},
|
|
120
135
|
},
|
|
121
136
|
"required": ["yaw", "pitch"],
|
|
@@ -422,6 +437,9 @@ def create_server() -> Server:
|
|
|
422
437
|
"minimal firmware change to handle the inbound 'listen' "
|
|
423
438
|
"wire type (paired with this gateway release). Engine is "
|
|
424
439
|
"selectable via 'engine' (default 'faster-whisper', local). "
|
|
440
|
+
"Optional 'motion' feedback can switch the avatar to "
|
|
441
|
+
"'thinking' during capture ('face-only') or tilt the head "
|
|
442
|
+
"up while preserving yaw ('look-up'). "
|
|
425
443
|
"Install the relevant extra "
|
|
426
444
|
"('pip install stackchan-mcp[stt-faster-whisper]' or "
|
|
427
445
|
"'stt-openai'); calling this tool before an engine is "
|
|
@@ -465,6 +483,29 @@ def create_server() -> Server:
|
|
|
465
483
|
"fall back to their default when omitted."
|
|
466
484
|
),
|
|
467
485
|
},
|
|
486
|
+
"motion": {
|
|
487
|
+
"type": "string",
|
|
488
|
+
"enum": ["none", "face-only", "look-up"],
|
|
489
|
+
"description": (
|
|
490
|
+
"Optional visible feedback during capture. "
|
|
491
|
+
"'none' preserves the previous behaviour. "
|
|
492
|
+
"'face-only' shows the thinking avatar during "
|
|
493
|
+
"capture and restores idle at the end. "
|
|
494
|
+
"'look-up' preserves yaw, tilts pitch to "
|
|
495
|
+
"look_up_pitch, and holds the pose on success."
|
|
496
|
+
),
|
|
497
|
+
"default": "none",
|
|
498
|
+
},
|
|
499
|
+
"look_up_pitch": {
|
|
500
|
+
"type": "number",
|
|
501
|
+
"description": (
|
|
502
|
+
"Pitch angle for motion='look-up'. Must be "
|
|
503
|
+
"between 5 and 85 degrees."
|
|
504
|
+
),
|
|
505
|
+
"default": 50.0,
|
|
506
|
+
"minimum": 5,
|
|
507
|
+
"maximum": 85,
|
|
508
|
+
},
|
|
468
509
|
},
|
|
469
510
|
},
|
|
470
511
|
),
|
|
@@ -526,6 +567,59 @@ def create_server() -> Server:
|
|
|
526
567
|
)
|
|
527
568
|
]
|
|
528
569
|
|
|
570
|
+
if name == "move_head":
|
|
571
|
+
# Belt-and-suspenders validation for the recommended pitch range.
|
|
572
|
+
# The Tool inputSchema already declares minimum/maximum for both
|
|
573
|
+
# yaw and pitch, but mcp Python SDK server-side enforcement of
|
|
574
|
+
# JSON Schema bounds is not guaranteed across versions and
|
|
575
|
+
# clients. Reject out-of-recommended values here as a clean
|
|
576
|
+
# MCP error JSON before any motion command reaches the device.
|
|
577
|
+
# Callers that genuinely need the firmware hard clamp 0..88
|
|
578
|
+
# should use the firmware-side `set_head_angles` device tool,
|
|
579
|
+
# which exposes the authoritative two-tier guard described in
|
|
580
|
+
# the README "Y-axis (pitch) safe range" section.
|
|
581
|
+
yaw_val = arguments.get("yaw")
|
|
582
|
+
pitch_val = arguments.get("pitch")
|
|
583
|
+
if (
|
|
584
|
+
not isinstance(yaw_val, int)
|
|
585
|
+
or isinstance(yaw_val, bool)
|
|
586
|
+
or not (-90 <= yaw_val <= 90)
|
|
587
|
+
):
|
|
588
|
+
return [
|
|
589
|
+
TextContent(
|
|
590
|
+
type="text",
|
|
591
|
+
text=json.dumps(
|
|
592
|
+
{
|
|
593
|
+
"error": (
|
|
594
|
+
"yaw must be an integer in -90..90 "
|
|
595
|
+
f"(got {yaw_val!r})"
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
),
|
|
599
|
+
)
|
|
600
|
+
]
|
|
601
|
+
if (
|
|
602
|
+
not isinstance(pitch_val, int)
|
|
603
|
+
or isinstance(pitch_val, bool)
|
|
604
|
+
or not (5 <= pitch_val <= 85)
|
|
605
|
+
):
|
|
606
|
+
return [
|
|
607
|
+
TextContent(
|
|
608
|
+
type="text",
|
|
609
|
+
text=json.dumps(
|
|
610
|
+
{
|
|
611
|
+
"error": (
|
|
612
|
+
"pitch must be an integer in 5..85 "
|
|
613
|
+
"(M5Stack-recommended operating range; "
|
|
614
|
+
"for the wider firmware hard clamp "
|
|
615
|
+
"0..88 use `set_head_angles`). got "
|
|
616
|
+
f"{pitch_val!r}"
|
|
617
|
+
)
|
|
618
|
+
}
|
|
619
|
+
),
|
|
620
|
+
)
|
|
621
|
+
]
|
|
622
|
+
|
|
529
623
|
# Map MCP client tool names to ESP32 MCP tool names (self.* prefix)
|
|
530
624
|
tool_map: dict[str, tuple[str, dict[str, Any]]] = {
|
|
531
625
|
"get_device_info": (
|