stackchan-mcp 0.8.0__tar.gz → 0.9.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.9.0/.gitignore +41 -0
- stackchan_mcp-0.9.0/AGENTS.md +79 -0
- stackchan_mcp-0.9.0/LICENSE-THIRD-PARTY +65 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/PKG-INFO +47 -9
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/README.md +42 -7
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/pyproject.toml +43 -3
- stackchan_mcp-0.9.0/stackchan_mcp/__init__.py +81 -0
- stackchan_mcp-0.9.0/stackchan_mcp/_libs/SOURCES.md +130 -0
- stackchan_mcp-0.9.0/stackchan_mcp/audio_input_hook.py +432 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/audio_stream.py +11 -0
- stackchan_mcp-0.9.0/stackchan_mcp/capture_server.py +469 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/cli.py +390 -25
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/esp32_client.py +359 -2
- stackchan_mcp-0.9.0/stackchan_mcp/event_log.py +189 -0
- stackchan_mcp-0.9.0/stackchan_mcp/gateway.py +274 -0
- stackchan_mcp-0.9.0/stackchan_mcp/http_server.py +398 -0
- stackchan_mcp-0.9.0/stackchan_mcp/mdns_advertiser.py +347 -0
- stackchan_mcp-0.9.0/stackchan_mcp/notify.example.yml +21 -0
- stackchan_mcp-0.9.0/stackchan_mcp/notify_config.py +235 -0
- stackchan_mcp-0.9.0/stackchan_mcp/ownership.py +270 -0
- stackchan_mcp-0.9.0/stackchan_mcp/queue.py +191 -0
- stackchan_mcp-0.9.0/stackchan_mcp/stdio_server.py +1365 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/stt/orchestrator.py +17 -1
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/tts/__init__.py +8 -1
- stackchan_mcp-0.9.0/stackchan_mcp/tts/orchestrator.py +688 -0
- stackchan_mcp-0.9.0/tests/test_audio_input_hook.py +315 -0
- stackchan_mcp-0.9.0/tests/test_capture_server.py +201 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_cli.py +353 -8
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_esp32_client.py +159 -0
- stackchan_mcp-0.9.0/tests/test_event_dispatch.py +335 -0
- stackchan_mcp-0.9.0/tests/test_event_log.py +369 -0
- stackchan_mcp-0.9.0/tests/test_gateway.py +251 -0
- stackchan_mcp-0.9.0/tests/test_http_server.py +575 -0
- stackchan_mcp-0.9.0/tests/test_mdns_advertiser.py +379 -0
- stackchan_mcp-0.9.0/tests/test_notify_config.py +231 -0
- stackchan_mcp-0.9.0/tests/test_ownership.py +92 -0
- stackchan_mcp-0.9.0/tests/test_send_pcm_audio.py +440 -0
- stackchan_mcp-0.9.0/tests/test_send_pcm_stream.py +572 -0
- stackchan_mcp-0.9.0/tests/test_stackchan_event.py +274 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_stdio_server.py +252 -4
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_stt_orchestrator.py +38 -1
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/uv.lock +93 -2
- stackchan_mcp-0.8.0/.gitignore +0 -30
- stackchan_mcp-0.8.0/stackchan_mcp/__init__.py +0 -12
- stackchan_mcp-0.8.0/stackchan_mcp/capture_server.py +0 -91
- stackchan_mcp-0.8.0/stackchan_mcp/gateway.py +0 -123
- stackchan_mcp-0.8.0/stackchan_mcp/stdio_server.py +0 -829
- stackchan_mcp-0.8.0/stackchan_mcp/tts/orchestrator.py +0 -282
- stackchan_mcp-0.8.0/tests/test_capture_server.py +0 -25
- stackchan_mcp-0.8.0/tests/test_gateway.py +0 -77
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/.env.example +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/LICENSE +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/__main__.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/handlers/__init__.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/handlers/audio.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/handlers/camera.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/handlers/robot.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/mcp_router.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/protocol.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/server.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/stt/__init__.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/stt/audio_utils.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/stt/base.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/stt/faster_whisper.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/stt/openai_whisper.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/tools.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/tts/audio_utils.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/tts/base.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/stackchan_mcp/tts/voicevox.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/_audio_fixtures.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/conftest.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_audio_stream.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_audio_utils.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_mcp_router.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_orchestrator.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_protocol.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_stt_audio_utils.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_stt_framework.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_tts_framework.py +0 -0
- {stackchan_mcp-0.8.0 → stackchan_mcp-0.9.0}/tests/test_voicevox.py +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
.venv/
|
|
6
|
+
venv/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
|
|
10
|
+
# Environment / secrets
|
|
11
|
+
.env
|
|
12
|
+
.env.local
|
|
13
|
+
*.local
|
|
14
|
+
|
|
15
|
+
# Captures (user photos)
|
|
16
|
+
captures/
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.vscode/
|
|
20
|
+
.idea/
|
|
21
|
+
*.swp
|
|
22
|
+
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
|
|
27
|
+
# Build artifacts
|
|
28
|
+
*.zip
|
|
29
|
+
build/
|
|
30
|
+
dist/
|
|
31
|
+
|
|
32
|
+
# Bundled native binaries. The publish workflow builds `opus.dll` on
|
|
33
|
+
# a Windows runner via vcpkg (verified against a pinned SHA256) and
|
|
34
|
+
# drops it under `stackchan_mcp/_libs/` before running `uv build`.
|
|
35
|
+
# Local Windows development can mirror that by either building opus
|
|
36
|
+
# via vcpkg locally, downloading the same DLL from a release
|
|
37
|
+
# artifact, or installing system libopus and copying into `_libs/`.
|
|
38
|
+
# On Linux / macOS the system package manager already provides
|
|
39
|
+
# libopus and this path stays empty. `SOURCES.md` is tracked so the
|
|
40
|
+
# bundling documentation lives alongside the code that consumes it.
|
|
41
|
+
stackchan_mcp/_libs/opus.dll
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# gateway/ AGENTS.md (stackchan-mcp)
|
|
2
|
+
|
|
3
|
+
Python MCP gateway (`stackchan_mcp` package) developer guide.
|
|
4
|
+
|
|
5
|
+
For ESP32 firmware build/flash/device operations, see `firmware/AGENTS.md`. For board-specific behavior (servo, touch, avatar), see `firmware/main/boards/stackchan/AGENTS.md`.
|
|
6
|
+
|
|
7
|
+
## 1. stdio MCP constraint
|
|
8
|
+
|
|
9
|
+
The gateway runs as a **stdio MCP server** — it lives as long as the host process keeps stdin/stdout open.
|
|
10
|
+
|
|
11
|
+
- Editing `.env` does NOT reload the running process. **MCP process restart is required** (restart the host application or reconnect).
|
|
12
|
+
- Killing the gateway process directly does NOT auto-restart it. The host must reconnect.
|
|
13
|
+
|
|
14
|
+
### Verifying token configuration
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
grep "^STACKCHAN_TOKEN=" gateway/.env
|
|
18
|
+
grep "^BEARER_TOKEN=" gateway/.env # legacy alias
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 2. `STACKCHAN_TOKEN` vs `BEARER_TOKEN` priority
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
expected = os.getenv("STACKCHAN_TOKEN") or os.getenv("BEARER_TOKEN")
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**`STACKCHAN_TOKEN` takes priority**; `BEARER_TOKEN` is a legacy alias checked only when the primary is empty. Keep both set to the same value as insurance.
|
|
28
|
+
|
|
29
|
+
The ESP32 firmware token (`CONFIG_DEFAULT_WEBSOCKET_TOKEN` in sdkconfig) must match — see `firmware/AGENTS.md` for details.
|
|
30
|
+
|
|
31
|
+
## 3. Gateway status check
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Process check
|
|
35
|
+
lsof -i :8765 | grep LISTEN # python LISTEN = OK
|
|
36
|
+
pgrep -fl stackchan_mcp # process list
|
|
37
|
+
|
|
38
|
+
# Port conflict check
|
|
39
|
+
lsof -i :8765 # ESTABLISHED sockets are MCP client connections, not conflicts
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If `address already in use ('0.0.0.0', 8765)` occurs, identify the existing process with `lsof` before killing.
|
|
43
|
+
|
|
44
|
+
## 4. LAN IP changes (gateway side)
|
|
45
|
+
|
|
46
|
+
The gateway listens on `0.0.0.0:8765`, so LAN IP changes do not directly affect it. The issue is on the ESP32 side — see `firmware/AGENTS.md` for details.
|
|
47
|
+
|
|
48
|
+
## 5. Validation commands (pre-PR)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd gateway
|
|
52
|
+
uv sync # resolve dependencies
|
|
53
|
+
uv run pytest # tests must pass
|
|
54
|
+
uv run ruff check . # lint must pass
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
CI runs the same three commands. A local failure means CI will also fail.
|
|
58
|
+
|
|
59
|
+
## 6. PyPI publishing
|
|
60
|
+
|
|
61
|
+
- Bump `version` in `gateway/pyproject.toml`
|
|
62
|
+
- Promote `CHANGELOG.md` `[Unreleased]` Gateway subsection to `[X.Y.Z] - YYYY-MM-DD`
|
|
63
|
+
- Tag push (`git tag vX.Y.Z && git push origin vX.Y.Z`) triggers Trusted Publishing
|
|
64
|
+
- Verify with `pipx install --force stackchan-mcp` after publishing
|
|
65
|
+
|
|
66
|
+
## 7. `.env` edit checklist
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 1. Verify values
|
|
70
|
+
grep "^STACKCHAN_TOKEN=" gateway/.env
|
|
71
|
+
|
|
72
|
+
# 2. Restart MCP process (host restart or /mcp reconnect)
|
|
73
|
+
|
|
74
|
+
# 3. Verify reconnection
|
|
75
|
+
# In host: get_status → connected: true / tools_count: 14+
|
|
76
|
+
|
|
77
|
+
# 4. If token mismatch, also update ESP32 sdkconfig
|
|
78
|
+
# See firmware/AGENTS.md for token alignment procedure
|
|
79
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Third-party software included with stackchan-mcp
|
|
2
|
+
==================================================
|
|
3
|
+
|
|
4
|
+
The `stackchan-mcp` gateway is distributed under the MIT license (see
|
|
5
|
+
`LICENSE`). The `win_amd64` wheel additionally bundles native binary
|
|
6
|
+
components that are distributed under their own permissive licenses,
|
|
7
|
+
listed below. Non-Windows wheels and the sdist do not contain these
|
|
8
|
+
binaries.
|
|
9
|
+
|
|
10
|
+
The notices in this file are reproduced verbatim from the upstream
|
|
11
|
+
projects for license-compliance purposes. They apply only to the
|
|
12
|
+
specific binaries listed; the rest of the gateway remains MIT.
|
|
13
|
+
|
|
14
|
+
--------------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
Opus codec (libopus)
|
|
17
|
+
--------------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
Component: `stackchan_mcp/_libs/opus.dll`
|
|
20
|
+
Architecture: `win_amd64` (x86_64)
|
|
21
|
+
Upstream: https://opus-codec.org/
|
|
22
|
+
License: BSD 3-clause + Xiph extension
|
|
23
|
+
|
|
24
|
+
Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
|
|
25
|
+
Jean-Marc Valin, Timothy B. Terriberry,
|
|
26
|
+
CSIRO, Gregory Maxwell, Mark Borgerding,
|
|
27
|
+
Erik de Castro Lopo
|
|
28
|
+
|
|
29
|
+
Redistribution and use in source and binary forms, with or without
|
|
30
|
+
modification, are permitted provided that the following conditions
|
|
31
|
+
are met:
|
|
32
|
+
|
|
33
|
+
- Redistributions of source code must retain the above copyright
|
|
34
|
+
notice, this list of conditions and the following disclaimer.
|
|
35
|
+
|
|
36
|
+
- Redistributions in binary form must reproduce the above copyright
|
|
37
|
+
notice, this list of conditions and the following disclaimer in the
|
|
38
|
+
documentation and/or other materials provided with the distribution.
|
|
39
|
+
|
|
40
|
+
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
|
41
|
+
names of specific contributors, may be used to endorse or promote
|
|
42
|
+
products derived from this software without specific prior written
|
|
43
|
+
permission.
|
|
44
|
+
|
|
45
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
46
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
47
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
48
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
49
|
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
50
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
51
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
52
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
53
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
54
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
55
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
56
|
+
|
|
57
|
+
Opus is also subject to the Internet Society "Note Well" IPR
|
|
58
|
+
disclosure as referenced from RFC 6716. The IPR statement is
|
|
59
|
+
available at:
|
|
60
|
+
|
|
61
|
+
https://datatracker.ietf.org/ipr/search/?submit=ipr&rfc=6716
|
|
62
|
+
|
|
63
|
+
Provenance and SHA256 for the specific binary shipped in each
|
|
64
|
+
release are recorded in `stackchan_mcp/_libs/SOURCES.md` inside the
|
|
65
|
+
wheel.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stackchan-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -8,6 +8,7 @@ Project-URL: Issues, https://github.com/kisaragi-mochi/stackchan-mcp/issues
|
|
|
8
8
|
Author: kisaragi-mochi
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
|
+
License-File: LICENSE-THIRD-PARTY
|
|
11
12
|
Keywords: esp32,llm,mcp,robotics,stackchan,xiaozhi
|
|
12
13
|
Classifier: Development Status :: 3 - Alpha
|
|
13
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -23,10 +24,12 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
23
24
|
Classifier: Topic :: System :: Hardware
|
|
24
25
|
Requires-Python: >=3.10
|
|
25
26
|
Requires-Dist: aiohttp>=3
|
|
26
|
-
Requires-Dist: mcp
|
|
27
|
+
Requires-Dist: mcp<2.0,>=1.27
|
|
27
28
|
Requires-Dist: pydantic>=2
|
|
28
29
|
Requires-Dist: python-dotenv
|
|
30
|
+
Requires-Dist: pyyaml>=6
|
|
29
31
|
Requires-Dist: websockets>=12
|
|
32
|
+
Requires-Dist: zeroconf>=0.149
|
|
30
33
|
Provides-Extra: stt
|
|
31
34
|
Requires-Dist: opuslib>=3; extra == 'stt'
|
|
32
35
|
Provides-Extra: stt-faster-whisper
|
|
@@ -120,6 +123,29 @@ Default ports:
|
|
|
120
123
|
- WebSocket (ESP32 -> gateway): `0.0.0.0:8765`
|
|
121
124
|
- HTTP capture (ESP32 -> gateway): `0.0.0.0:8766`
|
|
122
125
|
|
|
126
|
+
## Daemon mode (Phase B)
|
|
127
|
+
|
|
128
|
+
For multi-client setups, run one shared Streamable HTTP daemon instead of
|
|
129
|
+
letting each MCP client spawn its own stdio gateway:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
uv run stackchan-mcp serve --transport streamable-http
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The daemon exposes MCP at `http://127.0.0.1:8767/mcp` by default, keeps the
|
|
136
|
+
existing ESP32 WebSocket and capture listeners, and serializes ESP32-bound
|
|
137
|
+
tool calls through a bounded command queue. See
|
|
138
|
+
[`../docs/178-daemon-setup.md`](../docs/178-daemon-setup.md) for environment
|
|
139
|
+
variables, bearer-token rules, `MCP_HTTP_ALLOWED_HOSTS`, bind safety, and
|
|
140
|
+
migration notes.
|
|
141
|
+
|
|
142
|
+
The zero-subcommand stdio mode remains supported and unchanged for existing
|
|
143
|
+
client configs.
|
|
144
|
+
|
|
145
|
+
By default, the gateway advertises the WebSocket endpoint as
|
|
146
|
+
`_stackchan-mcp._tcp.local.` via mDNS/DNS-SD so fresh firmware can discover it
|
|
147
|
+
on the local LAN. Run `stackchan-mcp --no-mdns` to disable this advertisement.
|
|
148
|
+
|
|
123
149
|
For non-LAN setups, see [`../docs/remote-access.md`](../docs/remote-access.md)
|
|
124
150
|
for the Tailscale Funnel flow.
|
|
125
151
|
|
|
@@ -131,10 +157,10 @@ again, check `get_status` from the stdio MCP side to confirm the device is back.
|
|
|
131
157
|
## Configuration changes
|
|
132
158
|
|
|
133
159
|
The gateway reads `.env` once at process start. Because the gateway runs as a
|
|
134
|
-
**stdio MCP server**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
160
|
+
**stdio MCP server** by default, editing `.env` while it is connected to an MCP
|
|
161
|
+
client does not take effect on the running process — and killing that stdio
|
|
162
|
+
gateway process directly will not auto-restart it; the MCP client owns the
|
|
163
|
+
lifecycle. In daemon mode, restart the daemon process after changing `.env`.
|
|
138
164
|
|
|
139
165
|
After editing `.env` (for example to update `STACKCHAN_TOKEN`, `VISION_URL`,
|
|
140
166
|
or `VISION_TOKEN`):
|
|
@@ -278,9 +304,21 @@ asyncio.run(smoke())
|
|
|
278
304
|
|
|
279
305
|
## License
|
|
280
306
|
|
|
281
|
-
The gateway is distributed under the MIT License (see
|
|
282
|
-
|
|
283
|
-
|
|
307
|
+
The gateway Python code is distributed under the **MIT License** (see
|
|
308
|
+
`LICENSE`). The Windows wheel (`*-win_amd64.whl`) additionally bundles
|
|
309
|
+
a native `opus.dll` built from upstream Opus source via vcpkg by the
|
|
310
|
+
publish workflow. That binary is distributed under the **BSD 3-clause
|
|
311
|
+
license + Xiph extension**; the full notice ships in every
|
|
312
|
+
distribution form (sdist, `py3-none-any` wheel, `win_amd64` wheel) as
|
|
313
|
+
`LICENSE-THIRD-PARTY`. Non-Windows wheels and the sdist do not contain
|
|
314
|
+
any binary subject to that license — they rely on a system `libopus`
|
|
315
|
+
provided by the OS package manager (e.g. `apt install libopus0`,
|
|
316
|
+
`brew install opus`). See `stackchan_mcp/_libs/SOURCES.md` (also
|
|
317
|
+
shipped in the wheel) for build provenance and the per-release
|
|
318
|
+
SHA256 logged by CI.
|
|
319
|
+
|
|
320
|
+
The parent monorepo's `firmware/` directory contains SCServo_lib code
|
|
321
|
+
under GPL-3.0, but those files live only inside
|
|
284
322
|
`firmware/main/boards/stackchan/` and never enter this package. The
|
|
285
323
|
gateway and firmware communicate only over WebSocket, so the GPL/MIT
|
|
286
324
|
boundary is preserved at the process level.
|
|
@@ -75,6 +75,29 @@ Default ports:
|
|
|
75
75
|
- WebSocket (ESP32 -> gateway): `0.0.0.0:8765`
|
|
76
76
|
- HTTP capture (ESP32 -> gateway): `0.0.0.0:8766`
|
|
77
77
|
|
|
78
|
+
## Daemon mode (Phase B)
|
|
79
|
+
|
|
80
|
+
For multi-client setups, run one shared Streamable HTTP daemon instead of
|
|
81
|
+
letting each MCP client spawn its own stdio gateway:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
uv run stackchan-mcp serve --transport streamable-http
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The daemon exposes MCP at `http://127.0.0.1:8767/mcp` by default, keeps the
|
|
88
|
+
existing ESP32 WebSocket and capture listeners, and serializes ESP32-bound
|
|
89
|
+
tool calls through a bounded command queue. See
|
|
90
|
+
[`../docs/178-daemon-setup.md`](../docs/178-daemon-setup.md) for environment
|
|
91
|
+
variables, bearer-token rules, `MCP_HTTP_ALLOWED_HOSTS`, bind safety, and
|
|
92
|
+
migration notes.
|
|
93
|
+
|
|
94
|
+
The zero-subcommand stdio mode remains supported and unchanged for existing
|
|
95
|
+
client configs.
|
|
96
|
+
|
|
97
|
+
By default, the gateway advertises the WebSocket endpoint as
|
|
98
|
+
`_stackchan-mcp._tcp.local.` via mDNS/DNS-SD so fresh firmware can discover it
|
|
99
|
+
on the local LAN. Run `stackchan-mcp --no-mdns` to disable this advertisement.
|
|
100
|
+
|
|
78
101
|
For non-LAN setups, see [`../docs/remote-access.md`](../docs/remote-access.md)
|
|
79
102
|
for the Tailscale Funnel flow.
|
|
80
103
|
|
|
@@ -86,10 +109,10 @@ again, check `get_status` from the stdio MCP side to confirm the device is back.
|
|
|
86
109
|
## Configuration changes
|
|
87
110
|
|
|
88
111
|
The gateway reads `.env` once at process start. Because the gateway runs as a
|
|
89
|
-
**stdio MCP server**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
**stdio MCP server** by default, editing `.env` while it is connected to an MCP
|
|
113
|
+
client does not take effect on the running process — and killing that stdio
|
|
114
|
+
gateway process directly will not auto-restart it; the MCP client owns the
|
|
115
|
+
lifecycle. In daemon mode, restart the daemon process after changing `.env`.
|
|
93
116
|
|
|
94
117
|
After editing `.env` (for example to update `STACKCHAN_TOKEN`, `VISION_URL`,
|
|
95
118
|
or `VISION_TOKEN`):
|
|
@@ -233,9 +256,21 @@ asyncio.run(smoke())
|
|
|
233
256
|
|
|
234
257
|
## License
|
|
235
258
|
|
|
236
|
-
The gateway is distributed under the MIT License (see
|
|
237
|
-
|
|
238
|
-
|
|
259
|
+
The gateway Python code is distributed under the **MIT License** (see
|
|
260
|
+
`LICENSE`). The Windows wheel (`*-win_amd64.whl`) additionally bundles
|
|
261
|
+
a native `opus.dll` built from upstream Opus source via vcpkg by the
|
|
262
|
+
publish workflow. That binary is distributed under the **BSD 3-clause
|
|
263
|
+
license + Xiph extension**; the full notice ships in every
|
|
264
|
+
distribution form (sdist, `py3-none-any` wheel, `win_amd64` wheel) as
|
|
265
|
+
`LICENSE-THIRD-PARTY`. Non-Windows wheels and the sdist do not contain
|
|
266
|
+
any binary subject to that license — they rely on a system `libopus`
|
|
267
|
+
provided by the OS package manager (e.g. `apt install libopus0`,
|
|
268
|
+
`brew install opus`). See `stackchan_mcp/_libs/SOURCES.md` (also
|
|
269
|
+
shipped in the wheel) for build provenance and the per-release
|
|
270
|
+
SHA256 logged by CI.
|
|
271
|
+
|
|
272
|
+
The parent monorepo's `firmware/` directory contains SCServo_lib code
|
|
273
|
+
under GPL-3.0, but those files live only inside
|
|
239
274
|
`firmware/main/boards/stackchan/` and never enter this package. The
|
|
240
275
|
gateway and firmware communicate only over WebSocket, so the GPL/MIT
|
|
241
276
|
boundary is preserved at the process level.
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stackchan-mcp"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.9.0"
|
|
4
4
|
description = "Two-faced MCP gateway for StackChan (xiaozhi-esp32): bridges stdio MCP clients to the ESP32 over WebSocket + HTTP."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
license = "MIT"
|
|
8
|
-
|
|
8
|
+
# `LICENSE-THIRD-PARTY` carries the BSD-3-Clause + Xiph notice for the
|
|
9
|
+
# native libopus binary bundled into the `win_amd64` wheel. The file is
|
|
10
|
+
# always shipped (sdist and every wheel variant) so license scanners
|
|
11
|
+
# pointed at any distribution form can see it, even though the binary
|
|
12
|
+
# itself is only present in the `win_amd64` wheel.
|
|
13
|
+
license-files = ["LICENSE", "LICENSE-THIRD-PARTY"]
|
|
9
14
|
authors = [
|
|
10
15
|
{ name = "kisaragi-mochi" },
|
|
11
16
|
]
|
|
@@ -28,8 +33,19 @@ dependencies = [
|
|
|
28
33
|
"websockets>=12",
|
|
29
34
|
"pydantic>=2",
|
|
30
35
|
"python-dotenv",
|
|
31
|
-
"
|
|
36
|
+
"PyYAML>=6",
|
|
37
|
+
# The stdio server captures the active ServerSession via a subclassed
|
|
38
|
+
# Server.run() loop because the public MCP SDK does not yet expose a
|
|
39
|
+
# stable hook for server-side notifications. That subclass relies on
|
|
40
|
+
# `Server._experimental_handlers` and `Server._handle_message`, which
|
|
41
|
+
# are private members. We pin to the verified 1.x line and gate access
|
|
42
|
+
# behind a startup compatibility guard (see stdio_server.py) so a
|
|
43
|
+
# future SDK release cannot silently break the gateway via PyPI
|
|
44
|
+
# auto-resolution. Bump the upper bound after verifying the next
|
|
45
|
+
# major line.
|
|
46
|
+
"mcp>=1.27,<2.0",
|
|
32
47
|
"aiohttp>=3",
|
|
48
|
+
"zeroconf>=0.149",
|
|
33
49
|
]
|
|
34
50
|
|
|
35
51
|
[project.optional-dependencies]
|
|
@@ -84,3 +100,27 @@ dev = [
|
|
|
84
100
|
[build-system]
|
|
85
101
|
requires = ["hatchling"]
|
|
86
102
|
build-backend = "hatchling.build"
|
|
103
|
+
|
|
104
|
+
# Bundle the native libopus binary into the Windows wheel so that
|
|
105
|
+
# `pip install stackchan-mcp[tts]` (or `[stt]`) works out-of-the-box
|
|
106
|
+
# on Windows. The DLL is loaded at import time via
|
|
107
|
+
# `os.add_dll_directory()` in `stackchan_mcp/__init__.py`. See
|
|
108
|
+
# `stackchan_mcp/_libs/SOURCES.md` for provenance and license.
|
|
109
|
+
#
|
|
110
|
+
# Build-time contract: `opus.dll` is NOT tracked in git. The publish
|
|
111
|
+
# workflow builds it from upstream Opus source via vcpkg on a Windows
|
|
112
|
+
# runner (verified against a pinned SHA256), drops it under
|
|
113
|
+
# `stackchan_mcp/_libs/`, then runs `uv build` which produces the
|
|
114
|
+
# `win_amd64` wheel. Builds on non-Windows runners do not place a
|
|
115
|
+
# DLL under `_libs/`, so the sdist and the `py3-none-any` wheel
|
|
116
|
+
# produced on those runners ship without the binary. Glob patterns
|
|
117
|
+
# below match `opus.dll` if and only if it exists at build time, so
|
|
118
|
+
# the same pyproject config covers both wheel variants without an
|
|
119
|
+
# unmatched-pattern error on the non-Windows runner.
|
|
120
|
+
[tool.hatch.build.targets.wheel]
|
|
121
|
+
packages = ["stackchan_mcp"]
|
|
122
|
+
artifacts = [
|
|
123
|
+
"stackchan_mcp/_libs/opus.dll",
|
|
124
|
+
"stackchan_mcp/_libs/SOURCES.md",
|
|
125
|
+
"stackchan_mcp/notify.example.yml",
|
|
126
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""stackchan-mcp: Two-faced gateway for StackChan (xiaozhi-esp32).
|
|
2
|
+
|
|
3
|
+
MCP client side: stdio MCP server (mcp Python SDK)
|
|
4
|
+
ESP32 side: WebSocket server (MCP client over JSON-RPC 2.0)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os as _os
|
|
8
|
+
import platform as _platform
|
|
9
|
+
import sys as _sys
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
+
from pathlib import Path as _Path
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
__version__ = version("stackchan-mcp")
|
|
15
|
+
except PackageNotFoundError: # pragma: no cover - source checkout without install
|
|
16
|
+
__version__ = "0.0.0+unknown"
|
|
17
|
+
|
|
18
|
+
# Windows: register the bundled native libs directory with the DLL
|
|
19
|
+
# search path before any submodule pulls in `opuslib` (or any other
|
|
20
|
+
# wrapper that calls `ctypes.util.find_library`). On Linux/macOS the
|
|
21
|
+
# system package manager typically already provides libopus, so we do
|
|
22
|
+
# nothing on those platforms.
|
|
23
|
+
#
|
|
24
|
+
# Why this is here and not in tts/__init__.py or stt/__init__.py:
|
|
25
|
+
# opuslib's libopus lookup happens at import time (the wrapper's
|
|
26
|
+
# top-level module unconditionally calls `find_library('opus')` and
|
|
27
|
+
# raises if it returns None). That means we need the DLL search path
|
|
28
|
+
# update to have run before *any* code imports opuslib, no matter
|
|
29
|
+
# which subpackage of stackchan_mcp loads first. The package
|
|
30
|
+
# `__init__.py` is the only place guaranteed to run before all
|
|
31
|
+
# sibling submodules.
|
|
32
|
+
#
|
|
33
|
+
# Why we update BOTH `os.add_dll_directory()` AND `os.environ["PATH"]`:
|
|
34
|
+
# - `os.add_dll_directory()` is the modern, isolated mechanism used by
|
|
35
|
+
# `LoadLibraryEx(..., LOAD_LIBRARY_SEARCH_USER_DIRS)`. Importantly,
|
|
36
|
+
# `ctypes.util.find_library()` on Windows uses the legacy
|
|
37
|
+
# `LoadLibraryW()` path which does **not** consult the
|
|
38
|
+
# `add_dll_directory()` list (see CPython issue #43603). Since
|
|
39
|
+
# `opuslib/api/__init__.py` calls exactly that — `find_library('opus')`
|
|
40
|
+
# — we also have to prepend the directory to PATH so the legacy
|
|
41
|
+
# resolver picks it up.
|
|
42
|
+
# - We add to `add_dll_directory()` too because direct `ctypes.CDLL(...)`
|
|
43
|
+
# / extension-module imports use the modern resolver, and we want
|
|
44
|
+
# bundle discovery to work for both API styles future-proof.
|
|
45
|
+
#
|
|
46
|
+
# See `stackchan_mcp/_libs/SOURCES.md` for the bundled DLL provenance.
|
|
47
|
+
# Architecture gate: the bundled `opus.dll` is built for `win_amd64`
|
|
48
|
+
# (x86_64). On Windows ARM64 / Windows x86 (32-bit), loading the x64
|
|
49
|
+
# DLL would fail with a native-image mismatch — *exactly* the
|
|
50
|
+
# "looks installed but fails at runtime" footgun this bundling is
|
|
51
|
+
# meant to remove. Skip the DLL search-path setup on those
|
|
52
|
+
# architectures so the user falls back to the same
|
|
53
|
+
# "find_library returns None" failure mode they had before this
|
|
54
|
+
# fix, which at least produces a clean ImportError on
|
|
55
|
+
# `import opuslib` rather than a confusing crash inside the DLL
|
|
56
|
+
# loader. A platform-specific wheel build would have rejected those
|
|
57
|
+
# architectures at install time (no compatible wheel), so this
|
|
58
|
+
# guard mostly matters for users who bypass wheel selection (e.g.
|
|
59
|
+
# by installing from sdist on a non-x64 Windows host).
|
|
60
|
+
_machine = _platform.machine().upper() if _sys.platform == "win32" else ""
|
|
61
|
+
_dll_dir_handle = None # kept alive at module scope; see comment below
|
|
62
|
+
|
|
63
|
+
if _sys.platform == "win32" and _machine in ("AMD64", "X86_64"):
|
|
64
|
+
_libs_dir = _Path(__file__).resolve().parent / "_libs"
|
|
65
|
+
if _libs_dir.is_dir():
|
|
66
|
+
# Retain the directory handle at module scope. Per CPython docs
|
|
67
|
+
# (`os.add_dll_directory`), the returned object is "an opaque
|
|
68
|
+
# value that has a `close()` method ... the returned object
|
|
69
|
+
# remains valid until close() is called". On garbage
|
|
70
|
+
# collection, the directory de-registers itself, so direct
|
|
71
|
+
# `ctypes.CDLL(...)` callers that rely on the modern resolver
|
|
72
|
+
# path would lose access to the bundle. Holding the handle on
|
|
73
|
+
# the module keeps the registration live for the process
|
|
74
|
+
# lifetime — matching the intent documented above for both
|
|
75
|
+
# `find_library` (legacy) and `LoadLibraryEx` (modern) lookup
|
|
76
|
+
# paths.
|
|
77
|
+
_dll_dir_handle = _os.add_dll_directory(str(_libs_dir))
|
|
78
|
+
_libs_str = str(_libs_dir)
|
|
79
|
+
_existing_path = _os.environ.get("PATH", "")
|
|
80
|
+
if _libs_str not in _existing_path.split(_os.pathsep):
|
|
81
|
+
_os.environ["PATH"] = _libs_str + _os.pathsep + _existing_path
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Bundled Native Libraries
|
|
2
|
+
|
|
3
|
+
This directory contains pre-built native shared libraries that the
|
|
4
|
+
gateway needs on platforms where the system package manager does not
|
|
5
|
+
typically ship them. They are loaded at import time by
|
|
6
|
+
`stackchan_mcp/__init__.py` via `os.add_dll_directory()` (Windows) so
|
|
7
|
+
that `ctypes.util.find_library()` calls inside Python wrapper packages
|
|
8
|
+
(e.g. `opuslib`) resolve to the bundled copy without any user setup.
|
|
9
|
+
|
|
10
|
+
## Why bundle?
|
|
11
|
+
|
|
12
|
+
The Python wrapper packages that depend on these libraries (currently
|
|
13
|
+
`opuslib`, pulled in via the `[tts]` and `[stt]` extras) only ship
|
|
14
|
+
Python bindings — they do **not** ship the underlying native library.
|
|
15
|
+
On Linux and macOS most users already have `libopus` available through
|
|
16
|
+
their distro's package manager (`apt install libopus0`,
|
|
17
|
+
`brew install opus`, etc.), but on Windows there is no equivalent
|
|
18
|
+
default install path, which means a plain `pip install stackchan-mcp[tts]`
|
|
19
|
+
fails at runtime with `Could not find Opus library. Make sure it is
|
|
20
|
+
installed.` even though the Python wrappers installed cleanly.
|
|
21
|
+
|
|
22
|
+
Bundling the Windows binary in the wheel removes that footgun: every
|
|
23
|
+
Windows user who installs `stackchan-mcp[tts]` (or `[stt]`) gets a
|
|
24
|
+
working installation on the first try, with no extra `vcpkg` /
|
|
25
|
+
`conda install -c conda-forge libopus` / manual DLL placement step.
|
|
26
|
+
|
|
27
|
+
The decision to bundle (vs. download at install time vs. require source
|
|
28
|
+
build) was made on these criteria:
|
|
29
|
+
|
|
30
|
+
| Criterion | Verdict for libopus |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Maturity of the dependency | Mature (Opus is a frozen IETF codec, RFC 6716, 2012) |
|
|
33
|
+
| Frequency of security advisories | Very low (the codec parser is small and well-audited) |
|
|
34
|
+
| File size | ~480 KB — fits comfortably in the wheel |
|
|
35
|
+
| Re-distribution license | BSD 3-clause (Xiph) — redistribution allowed with attribution |
|
|
36
|
+
| Long-term availability of upstream | Excellent (Xiph.Org maintains the source indefinitely) |
|
|
37
|
+
|
|
38
|
+
If any of those change (e.g. a future ML-based bundle that ships
|
|
39
|
+
hundreds of MB), revisit and consider the "CI downloads a pinned
|
|
40
|
+
version at build time" approach instead.
|
|
41
|
+
|
|
42
|
+
## opus.dll
|
|
43
|
+
|
|
44
|
+
| Field | Value |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Architecture | x86_64 (`win_amd64`) |
|
|
47
|
+
| License | BSD 3-clause + Xiph extension — see <https://opus-codec.org/license/> |
|
|
48
|
+
| Provenance | Built from upstream Opus source by the publish workflow via vcpkg |
|
|
49
|
+
| Build command | `vcpkg install opus:x64-windows` (CI runner: `windows-latest`) |
|
|
50
|
+
|
|
51
|
+
### Provenance note
|
|
52
|
+
|
|
53
|
+
`opus.dll` is **not** tracked in git. The publish workflow
|
|
54
|
+
(`.github/workflows/publish.yml`, job `build-windows-wheel`)
|
|
55
|
+
bootstraps a fresh vcpkg checkout on a `windows-latest` runner,
|
|
56
|
+
runs `vcpkg install opus:x64-windows`, copies the produced
|
|
57
|
+
`opus.dll` into `stackchan_mcp/_libs/`, and logs its SHA256 to the
|
|
58
|
+
job log so reviewers can spot vcpkg-side binary drift before a tag
|
|
59
|
+
publishes. The wheel build that follows picks the DLL up via
|
|
60
|
+
`tool.hatch.build.targets.wheel.artifacts` in
|
|
61
|
+
`gateway/pyproject.toml`, and the resulting wheel is renamed from
|
|
62
|
+
`*-py3-none-any.whl` to `*-py3-none-win_amd64.whl` so pip resolves
|
|
63
|
+
it only on Windows x64 installs.
|
|
64
|
+
|
|
65
|
+
Builds on the Ubuntu runner (sdist + the `py3-none-any` wheel they
|
|
66
|
+
produce) do not place a DLL under `_libs/`, so those distributions
|
|
67
|
+
ship clean — non-Windows installs and non-x64 Windows installs
|
|
68
|
+
either fall back to a system `libopus` (Linux/macOS) or get a
|
|
69
|
+
clean "no compatible wheel" install-time message (Windows ARM64 /
|
|
70
|
+
x86 32-bit).
|
|
71
|
+
|
|
72
|
+
### Local development
|
|
73
|
+
|
|
74
|
+
If you need a local Windows checkout to test the bundling path
|
|
75
|
+
(running `uv build` outside CI), mirror the CI step by:
|
|
76
|
+
|
|
77
|
+
1. Installing libopus via vcpkg (`vcpkg install opus:x64-windows`)
|
|
78
|
+
and copying the produced DLL into `stackchan_mcp/_libs/opus.dll`.
|
|
79
|
+
2. Or downloading the same `opus.dll` from a release artifact
|
|
80
|
+
uploaded by the publish workflow.
|
|
81
|
+
3. Or installing system libopus and copying it into the directory.
|
|
82
|
+
|
|
83
|
+
The DLL is gitignored (see `gateway/.gitignore`) so a local copy
|
|
84
|
+
never sneaks into a commit.
|
|
85
|
+
|
|
86
|
+
## License compliance
|
|
87
|
+
|
|
88
|
+
The Opus codec is distributed under the 3-clause BSD license (with the
|
|
89
|
+
optional Xiph patent grant), which permits redistribution in source or
|
|
90
|
+
binary form provided the copyright notice and license text are
|
|
91
|
+
preserved. The canonical notice ships at the top of every gateway
|
|
92
|
+
distribution as `LICENSE-THIRD-PARTY` (declared in
|
|
93
|
+
`gateway/pyproject.toml`'s `license-files`); the same text is
|
|
94
|
+
reproduced below as the bundling-rationale narrative for readers of
|
|
95
|
+
this document.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
|
|
99
|
+
Jean-Marc Valin, Timothy B. Terriberry,
|
|
100
|
+
CSIRO, Gregory Maxwell, Mark Borgerding,
|
|
101
|
+
Erik de Castro Lopo
|
|
102
|
+
|
|
103
|
+
Redistribution and use in source and binary forms, with or without
|
|
104
|
+
modification, are permitted provided that the following conditions
|
|
105
|
+
are met:
|
|
106
|
+
|
|
107
|
+
- Redistributions of source code must retain the above copyright
|
|
108
|
+
notice, this list of conditions and the following disclaimer.
|
|
109
|
+
|
|
110
|
+
- Redistributions in binary form must reproduce the above copyright
|
|
111
|
+
notice, this list of conditions and the following disclaimer in the
|
|
112
|
+
documentation and/or other materials provided with the distribution.
|
|
113
|
+
|
|
114
|
+
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
|
115
|
+
names of specific contributors, may be used to endorse or promote
|
|
116
|
+
products derived from this software without specific prior written
|
|
117
|
+
permission.
|
|
118
|
+
|
|
119
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
120
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
121
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
122
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
123
|
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
124
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
125
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
126
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
127
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
128
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
129
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
130
|
+
```
|