Gateway
The Gateway as a development canvas — extension model, data access, auth design, and Kernel connection
Overview
This page introduces the Gateway surface. It explains what concerns it owns, how to extend it with plugins and routes, how data flows from Kernel to your controllers, and why authentication lives here rather than in the kernel. Read this before building plugins, auth flows, or aggregation routes on top of MDK.
The Gateway is the backend layer of the MDK App Toolkit, which aligns the plugin system and frontend packages into the supported development path for this monorepo.
What the Gateway owns
The Gateway wraps @tetherto/mdk-client — the MDK protocol connector to Kernel — and adds an HTTP
interface on top. Consumers connect through the Gateway using @tetherto/mdk-client. Agents can reach MDK over MCP through the standalone @tetherto/mdk-mcp package.
The Gateway owns concerns that Kernel deliberately does not handle:
- The place where authentication belongs: user identity is a Gateway-tier concern: validating callers falls to your plugin controllers and the identity layer you supply
- API surface: REST endpoints and command dispatch
- Fleet aggregation: cross-Worker queries that compute site hashrate, average temperature, and cross-rack efficiency — resolved in controller code, not in Kernel
The Kernel is a pass-through, routing commands to Workers, collecting telemetry, and maintaining
the device registry. Everything above the kernel — authentication, business logic, API surface — is owned by the caller:
the Gateway (which wraps @tetherto/mdk-client internally) when using the toolkit.
Extension model
The Gateway offers two ways to add routes, in order of preference.
1. Plugin system
The recommended path. A plugin is a directory with an mdk-plugin.json manifest and one or more controller files.
Pass the directory path to startGateway() via extraPluginDirs.
Controllers receive a services bag on every request — mdkClient, dataProxy, and conf — with no protocol knowledge required.
The default plugins (telemetry, site-hashrate, site-monitor) load automatically. An auth plugin ships beside them
but the Gateway neither registers it nor provides the services.authLib its controllers expect, so mounting it does not yield working identity
endpoints.
The plugin authoring guide covers the build process end to end. The plugin reference documents the manifest schema, controller contract, and loader errors.
2. Raw Fastify routes
For one-off handlers that do not need a manifest, pass additionalRoutes to startGateway(). These are plain Fastify route objects —
no services injection, no manifest validation, no auth wiring. Use this path sparingly; a plugin is easier to test in isolation
and easier for a later maintainer to follow.
Connect without the Gateway
If your use case does not need the Gateway's HTTP surface or plugin system, for example a background service that only
dispatches commands — you can use @tetherto/mdk-client directly against Kernel without running the Gateway at all.
This is the direct path. Such an approach is not directly supported by this monorepo, as most applications build on the Gateway.
Data access
Two services are available inside every plugin controller.
services.mdkClient gives access to live Kernel data: pull a telemetry snapshot, dispatch a command, list registered Workers.
It's null when the Gateway starts without a live Kernel connection, so guard it before use.
services.dataProxy reads from persisted Worker tail-logs: time-series aggregation, historical hashrate, efficiency trends.
Use this for data that does not require a live Kernel round-trip.
The split exists because the two sources have different latency and availability characteristics. mdkClient calls are network
operations that can fail if Kernel is unreachable. dataProxy reads from local storage and remains available whether Kernel is online or not.
Authentication design
Neither tier authenticates a user. The Gateway serves whatever routes its plugins declare, to any caller, and Kernel does no user-level
authentication by design. The HRPC connection is an encrypted Noise channel, and Kernel maintains an allowlist; pre v1.0 it is
opt-in (the default auth.whitelist is empty and admits any caller), but when configured the Gateway's DHT public key must be added before the
connection is accepted. Once the transport is established, Kernel trusts all messages from the Gateway without inspecting user identity.
User authentication and RBAC belong to the application you build on the Gateway. A route is reachable by anyone unless its controller
validates the token and checks permissions itself, so protecting a route is controller work. The "auth" and "permissions" fields
in mdk-plugin.json have no reader and trigger no enforcement.
Kernel does check one thing on the write path: ActionManager and ActionCaller require the device-family write permission (miner:w,
container:w) in the authPerms array your controller passes with each action, and reject the action with ERR_ACTION_DENIED without it.
Kernel connection
The Gateway is the active side of this connection — it dials Kernel. Kernel is the passive listener; it does not initiate contact with the Gateway.
The connection is Hyperswarm RPC (HRPC) — an encrypted peer-to-peer transport addressed by Kernel's public key. What varies is how the Gateway obtains that key:
- Same host (zero-config default): Kernel publishes its HRPC public key to a well-known key file (
<tmpdir>/mdk/.kernel-key) on start; the Gateway reads it from there automatically when no key is passed - Separate hosts: pass the key explicitly (
startGateway({ kernelKey })), obtained fromkernel.getPublicKey()on the Kernel host. When Kernel'sauth.whitelistis configured, the Gateway's DHT public key must be added to it before the connection is accepted
Pre v1.0, the allowlist is opt-in. Kernel's auth.whitelist defaults to empty, which admits any HRPC caller. When an allowlist
is configured, the Gateway's DHT public key must appear in it before Kernel accepts the connection.