Skip to main content

Per-plugin sidecar that exposes local MCP servers to a Zenyard backend over an outbound WebSocket.

Project description

zenyard-relay

zenyard-relay connects an MCP server running on a user's machine to the Zenyard backend, even when that machine is behind a NAT or firewall. It dials out to the backend so the server can be reached without exposing any inbound ports.

Run one zenyard-relay serve process per MCP server you want to expose. Each process is a transparent passthrough — it forwards traffic between the backend and the MCP server unchanged. Multiple servers on the same machine appear to the backend as a single device.

The MCP server can run as a local subprocess (stdio) or a remote HTTP endpoint (streamable-http).

Install

The relay ships as a Python wheel built by maturin; the wheel installs the native zenyard-relay binary into the environment's bin/ (or Scripts\ on Windows):

pip install zenyard-relay

Or build from source with a stable Rust toolchain (≥ 1.75):

cargo build --release

The binary lands at target/release/zenyard-relay. Wheels for the common platforms are published from .github/workflows/ci.yml (tests gate the wheel publish step).

One-time device setup

serve runs without any config file as long as --control-endpoint (or --api-url) and the ZENYARD_RELAY_TOKEN env var are supplied. For convenience, a shared ~/.zenyard/relay.json can hold the control endpoint (and optionally the token) so individual sidecars don't have to pass it on the command line:

zenyard-relay config init      # optional; writes a starter ~/.zenyard/relay.json
zenyard-relay config path      # prints the config path
zenyard-relay config validate  # validates the file

~/.zenyard/relay.json example:

{
  "config": {
    "control_endpoint": "wss://relay.zenyard.dev/relay/control",
    "device_description": "ziv-laptop (Linux)"
  }
}

Plugins that already have the backend HTTP API URL configured can pass it as-is — the relay swaps http/https for ws/wss and appends the fixed /relay/control path:

zenyard-relay serve --id <ID> --api-url http://localhost:30465/ ...

The same field is accepted in relay.json as "api_url" (mutually exclusive with "control_endpoint").

The auth token is preferred from ZENYARD_RELAY_TOKEN (keeps secrets out of the JSON file); config.token is the fallback.

Sidecar contract

zenyard-relay serve --id <UPSTREAM_ID> [--display-name <NAME>] [--description <TEXT>] [--tag K=V...]
    [--control-endpoint <URL> | --api-url <URL>]
    [--config <PATH>] [--log-level <LEVEL>] [--log-format <FMT>]
    (--command <CMD> [--arg <V>...] [--env K=V...] [--cwd <DIR>])
  | (--url <URL> [--header "Name: value"...])

--control-endpoint / --api-url may be omitted when ~/.zenyard/relay.json (or the file pointed at by --config) supplies one. ZENYARD_RELAY_TOKEN in the environment is preferred over config.token.

Example — stdio upstream:

zenyard-relay serve --id fs --display-name "Filesystem" \
    --command npx --arg -y --arg @modelcontextprotocol/server-filesystem --arg /tmp

Example — remote upstream:

zenyard-relay serve --id api --display-name "Example API" \
    --url https://api.example.com/mcp \
    --header "Authorization: Bearer ghp_..."

After connecting, the sidecar sends a single Hello frame carrying the connection identity (relay_id / upstream_id) and the initial metadata snapshot (display_name / description / tags). It then reads stdin line-by-line for update ops that mutate the announced metadata:

{"op":"update", "display_name":"Echo (renamed)"}
{"op":"update", "tags":{"file":"sample.exe"}}
{"op":"update", "description":"Decompilation tools for sample.exe"}

Each update op replaces the supplied fields and re-sends UpstreamUpdated. The sidecar logs lifecycle events (connections, sessions, auth failures) to stderr per --log-level / --log-format, and exits when stdin closes.

For "same resource → same upstream across restarts" semantics, derive --id deterministically from the resource (e.g. ida-<sha256(idb_path)>). Conflicting live claims on the same id from two sidecars trigger takeover at the backend: the newer connection wins, the older one's sessions continue until they close naturally.

Exit codes

Code Meaning
0 Ok
1 Generic error
2 Invalid configuration / fatal auth rejection
3 Superseded — another relay took over this (relay_id, upstream_id)

License

This project is licensed under the GNU Affero General Public License v3.0 only.

See the LICENSE file in this repository for the full license text.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zenyard_relay-0.1.0.tar.gz (94.0 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

zenyard_relay-0.1.0-py3-none-win_amd64.whl (4.7 MB view details)

Uploaded Python 3Windows x86-64

zenyard_relay-0.1.0-py3-none-musllinux_1_2_x86_64.whl (5.0 MB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

zenyard_relay-0.1.0-py3-none-musllinux_1_2_aarch64.whl (4.6 MB view details)

Uploaded Python 3musllinux: musl 1.2+ ARM64

zenyard_relay-0.1.0-py3-none-manylinux_2_28_aarch64.whl (4.5 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

zenyard_relay-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.8 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

zenyard_relay-0.1.0-py3-none-macosx_11_0_arm64.whl (4.6 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

zenyard_relay-0.1.0-py3-none-macosx_10_12_x86_64.whl (4.8 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file zenyard_relay-0.1.0.tar.gz.

File metadata

  • Download URL: zenyard_relay-0.1.0.tar.gz
  • Upload date:
  • Size: 94.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for zenyard_relay-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e34c7bd44016d570295391e7ba685e3ba09e97004adf59642bd98408b6bb9427
MD5 c9eebf96acc3769c008111b8d4d80538
BLAKE2b-256 819396f5d69d87227b2f073f8f2f2734f66d7f1794084a8c802083f201b4ff35

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.0.tar.gz:

Publisher: ci.yml on zenyard/zenyard-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zenyard_relay-0.1.0-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 82dcb4c9148b75a854c62763b1e71d085cfd6bcef6ce62bf55562643f984078e
MD5 12bf579569ce82d9be98e565052608c4
BLAKE2b-256 3db83bd0accc1c0fb176ecbe22bf0108eb6f63c063b2c286ad09d4c0d573363b

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.0-py3-none-win_amd64.whl:

Publisher: ci.yml on zenyard/zenyard-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zenyard_relay-0.1.0-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.0-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 ed9c86d1a39a1bc129c7c1094945c74ce42b5bf8ad77f78dd7650f2184d68e93
MD5 f182f3116b8304197ec8bb249c535744
BLAKE2b-256 1068e332c6d181d96ccccb5a232783eb66cc2cd0ead63e4601b584f97e1dcf4c

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.0-py3-none-musllinux_1_2_x86_64.whl:

Publisher: ci.yml on zenyard/zenyard-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zenyard_relay-0.1.0-py3-none-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.0-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 505b1c0372665909e7490144325356f712173da5ccafe5389d7b618d40c317bd
MD5 5d23ae3b39ef63f882b68b9e39fd0d49
BLAKE2b-256 70dcd84f0089be537daf528042a8683f8a11d34127882cd3f47923887ae80d61

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.0-py3-none-musllinux_1_2_aarch64.whl:

Publisher: ci.yml on zenyard/zenyard-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zenyard_relay-0.1.0-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.0-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 04627de4b3a2ceda48af0b12bde0e242abf7fefae0e68a7f13a3f0df30954b56
MD5 1e424f811670d2f685bb8432232ba2e1
BLAKE2b-256 086eb7bbe9f343887203b0d57d9fd835cf610a9303ac5d09daf779c5aa2a87f4

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.0-py3-none-manylinux_2_28_aarch64.whl:

Publisher: ci.yml on zenyard/zenyard-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zenyard_relay-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 fa1ccef4e823cc31059e7a487d93508fd3506165734a665a4b286c801be80b05
MD5 1b0c07ec5bf9be23737d48053e444509
BLAKE2b-256 8199da1b321a9a5f3bc774b456e6ec7c4d26acb84a4d8edc4987c8b91a44b1d1

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: ci.yml on zenyard/zenyard-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zenyard_relay-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 43e72457dd0511e2da3c4892583f42d204bcb35f036a48daa6cb7b5ae148b5eb
MD5 7cf7b25c5934f0a3fd8927d52ea1b591
BLAKE2b-256 419066e8912e847e15030abfdd1326df7e4912e85257d517121cb50409132a42

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.0-py3-none-macosx_11_0_arm64.whl:

Publisher: ci.yml on zenyard/zenyard-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zenyard_relay-0.1.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 5678c51e52eae201f4d9a9abe0b6ad4edf46829f61e7210f601153010d3306ba
MD5 ed1f78a5e6a83b112e3a7091d2967f8a
BLAKE2b-256 76ed384a7ce631176e4cb7085532dd53a7384fa902abae98731b269d8d608c92

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.0-py3-none-macosx_10_12_x86_64.whl:

Publisher: ci.yml on zenyard/zenyard-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page