MDK Logo

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 is a container that hosts plugins and adds an HTTP interface on top of Kernel: each plugin builds its own @tetherto/mdk-client — the MDK protocol connector to Kernel — from its context. Consumers connect through the Gateway's plugin routes.

An AI agent reaches MDK through the standalone @tetherto/mdk-mcp package instead of the Gateway's HTTP surface.

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: each plugin, through its own @tetherto/mdk-client, when using the toolkit's Gateway.

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.

A controller receives just (req). Each plugin reads its own config from require('@tetherto/mdk-gateway/plugin') and builds its own @tetherto/mdk-client from it, so protocol knowledge stays inside the client, not the controller. The default plugins (telemetry, site-hashrate, site-monitor) load automatically this way. An auth plugin ships beside them but the Gateway neither registers it nor gives its controllers what they still expect (a second handler parameter carrying an identity layer), 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 plugin context, 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

There is one data source inside a plugin controller: the @tetherto/mdk-client the plugin built for itself from its context config. Live reads (pullTelemetry, sendCommand, listWorkers) go straight through it. There is no separate Gateway-side store for historical or aggregated data — a plugin that needs a time series fans the same client's pullWorkerTelemetry out across every registered Worker and reads it from the Worker's own persisted tail-log; telemetry/lib/site-data.js shows the pattern the bundled telemetry plugin uses. Both "live" and "historical" reads are therefore network calls through the client and can fail if the Worker (or Kernel, for listWorkers/getStatus) is unreachable — guard both the same way, and map a failure to your own error rather than assuming one path degrades gracefully and the other doesn't.

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 from kernel.getPublicKey() on the Kernel host. When Kernel's auth.whitelist is 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.

Next steps

On this page