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 relay-id         # prints this device's relay id (generating one if missing)

~/.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.2.tar.gz (94.4 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.2-py3-none-win_amd64.whl (4.7 MB view details)

Uploaded Python 3Windows x86-64

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

Uploaded Python 3musllinux: musl 1.2+ ARM64

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

Uploaded Python 3manylinux: glibc 2.28+ ARM64

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

Uploaded Python 3macOS 11.0+ ARM64

zenyard_relay-0.1.2-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.2.tar.gz.

File metadata

  • Download URL: zenyard_relay-0.1.2.tar.gz
  • Upload date:
  • Size: 94.4 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.2.tar.gz
Algorithm Hash digest
SHA256 7d0270e1656ce506157c996e75963624541687817bef65218473807d214ec091
MD5 eca91d685cf420abd92f9eaf92f83624
BLAKE2b-256 fa3679b94919a6f0995151e5a17645ce05ccfb06408b1866fda23cd31b77de06

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for zenyard_relay-0.1.2-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 605215ecaedb5b0881512987953ce078023257e6efe95858f59c4be950eb6e63
MD5 976561530803deb59d6f4fd0d97f8c39
BLAKE2b-256 c671d0f5dcb49eec1b36b562fcc26047a76bd2786ba0391cbe0e4bab187536c5

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for zenyard_relay-0.1.2-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 5871ab69ae38ac3eb0ce882acfbcbc77c98ad626dab1725f36e08ce04958e42e
MD5 db5e29ffc88cd4999cf362aab4f48a27
BLAKE2b-256 83559e10ad17ebbd43a244434a1c25599901b0882f868b6068d3d71fd95dee30

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for zenyard_relay-0.1.2-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 7629a3321f56dd18e80df8920fdaf6365215b241e174e7c7d03c5f5cb7f03e23
MD5 046e25eeceaccf972c7cbf8504025cc0
BLAKE2b-256 c900a02ce254ca56114c29b067409e4228d6e8af6b7356624cb166160ec6f83d

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for zenyard_relay-0.1.2-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 ad89de4055c7a16d2b03172c7a9861e10fe3c59cb6dc8a9dcd4700c4e964c42d
MD5 570beae7847ea0b859289dcf651577a6
BLAKE2b-256 3165275190071ae36b2b32e1d484d53a5b63b2f8da855716ed92db3e9277a90d

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for zenyard_relay-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 1c3d79022a6d0bc976b2b6bc17ee7af178a42ec1361b4348ba9e44093bda5408
MD5 018eb4e37f5be1cb2a23805f41f84147
BLAKE2b-256 2e7027049c3abb36c1252c4a5f123c9a61ad631dc7d5bcf8194a3004a61bd591

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for zenyard_relay-0.1.2-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 de84f85f36106a1e3dfc8b73e47e10a395e889011c94fc0e297ccc0b27036b3f
MD5 f91caa8e33da0629afa03ac5e3224579
BLAKE2b-256 50725f5b0668759e50acdff7c0ca4c327170a0b2c372209470f0eb263264cc6d

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for zenyard_relay-0.1.2-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 275f081de892e4c7bca126af47608f16a1cc3ddc888d758789d51d32f2ff5926
MD5 6edcbae0c08df553eb2e35584c82bbac
BLAKE2b-256 44a2406b837cf80a1a90929a70afc2d802c50ed4a44dfb9bc06e1e69885a7836

See more details on using hashes here.

Provenance

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