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": "user-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.1.tar.gz (94.3 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.1-py3-none-win_amd64.whl (4.7 MB view details)

Uploaded Python 3Windows x86-64

zenyard_relay-0.1.1-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.1-py3-none-musllinux_1_2_aarch64.whl (4.6 MB view details)

Uploaded Python 3musllinux: musl 1.2+ ARM64

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

Uploaded Python 3manylinux: glibc 2.28+ ARM64

zenyard_relay-0.1.1-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.1-py3-none-macosx_11_0_arm64.whl (4.6 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

zenyard_relay-0.1.1-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.1.tar.gz.

File metadata

  • Download URL: zenyard_relay-0.1.1.tar.gz
  • Upload date:
  • Size: 94.3 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.1.tar.gz
Algorithm Hash digest
SHA256 4a9a0fa83d654ab4a9f207d52a709d5578f3105da2fe5eba93e727e7bcc5c4f6
MD5 25b9ba531aa2de67913855d2aaebb94c
BLAKE2b-256 edd54ad5b206966b1b745408279bb3e84ab7b959988a74cf442ad1a9a1c29adb

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.1.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.1-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 743fdb9e8744778854a9e54a6b353e39918bc68c255bcb8f43d2f571fe61fa76
MD5 a41a9ee36f25b3fd502a8682119e6c35
BLAKE2b-256 834daba134e7636a9466cb4a765ccba166e3c9d60ebbecd77990f0d250f6a55b

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.1-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.1-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.1-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 038ff6c90c3c59428ac16479acfecd2119b35b73341e0ce629a182b52dc3c5b2
MD5 457d877353766e20b9b031e0f8145735
BLAKE2b-256 049611c050de694bc37d32e6fa851a204b53e473d00167de0a3bb38a2b635897

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.1-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.1-py3-none-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.1-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 4f61d1850c09be837e8f1041ab690482f231f8e5199799815a3de1f7c84064b2
MD5 4778a82993012b9572a7f284949616c2
BLAKE2b-256 1bb3931bff90a03430621f40feec228118d54265178b5de84f12feb61fc8a33d

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.1-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.1-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.1-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 2ee859a8f6fd4e7f5a99861b5b9711253dc323696b555ab33aa34c1ea1f7f7ed
MD5 347fc4c4eddb76f71ba8a2a113710dab
BLAKE2b-256 b9c39fb8763fe2aecd0435b87a92567ecf8eee20a09ffcfc424d1de4d9d6dc5b

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.1-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.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e476c7de25355d6125ddbfd3672f657c89597a8f2c7526e7e90303d3df60ffbb
MD5 e26c11632db7155f410cbecd44cd76c9
BLAKE2b-256 8095e94771467f2c05069cfcc041afa3e5d1e6877cb0ca667bb8944008d7528e

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.1-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.1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0108a11761052cc3740e8e348fdb780ea79ae19ebbd6377abbab0b38d2f91758
MD5 4aa995e145e85b08a2606c077cd29926
BLAKE2b-256 a8fc72a6d696877a30479d8737640a07037b40c9a5475556c1b89c60051aff23

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.1-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.1-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for zenyard_relay-0.1.1-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 bf8686f5735258653e766e078fbe4885ee13fad7d19c82c86c5a21a290840b74
MD5 13184c0338c5a9872264bf62abfcc422
BLAKE2b-256 cb04cf51fe5c667630ae9af5a2ac103afa0b9f945a5d1203d85754f1258a794b

See more details on using hashes here.

Provenance

The following attestation bundles were made for zenyard_relay-0.1.1-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