Protocols

Protocols

Six protocols that are already listening on your app. No dependency, no plugin, no service to run — a client that speaks the protocol can talk to your app the same day you start it.

These are not engines. Nothing is configured and nothing has to be started: the router answers their paths before it looks at your file routes, so an app with a single route still serves all six. Each one is a plain (req) => Promise<Response>, so you can also mount any of them yourself, elsewhere, with your own options.

What is already mounted

PathProtocolTurn it off
/grpc-webgRPC-Web unary calls. Ships a health check and an echo; add your own services in code.Mount your own handler instead.
/davWebDAV over the default storage disk — Finder, Explorer, Word, davfs2.readOnly: true on your own handler.
/dns-queryDNS over HTTPS (RFC 8484), wire and JSON shapes. A and AAAA only.Mount your own handler instead.
/.well-known/webfingerWebFinger (RFC 7033) discovery for accounts in the auth store.Mount your own handler instead.
/oauth/*OAuth 2.0 authorize, token and revoke, plus OIDC discovery, JWKS and userinfo.Stop sending OAUTH_CLIENTS; there are no clients.
/mcpAn MCP server whose tools are generated from your collections.MCP_ENABLED=0.
Warning
/dav is mounted with no authentication. Anyone who can reach the URL can read, write and delete everything on the default storage disk. That is fine on localhost and wrong on the internet: pass authenticate and mount the handler yourself, or keep /dav behind the auth wall you already have.

One page per protocol

  • gRPC-Web — protobuf calls from a browser, a codec, and a client.
  • WebDAV — files over HTTP for Finder, Explorer and davfs2.
  • DNS over HTTPS — A and AAAA questions, in the wire and JSON shapes.
  • WebFinger — turn an acct: address into links.
  • OAuth 2.0 server — be the login server, with PKCE and rotating refresh tokens.
  • OpenID Connect — discovery, JWKS and signed ID tokens on top of it.
  • MCP server — an agent's tools, generated from your collections.

The defaults are the cautious ones

Each module refuses something a naive implementation would accept. The list matters more than the summary — these are the decisions a client will notice:

  • gRPC-Web requires Content-Type: application/grpc-web, caps a message at 4 MB, refuses compression and streaming, and answers an unknown method with status 12 rather than a 404.
  • WebDAV enforces locks on subtrees, requires a lock body for a new lock (a body-less LOCK is a refresh), and serves anything not on an inline allowlist as a download — a file someone PUT must not run on your origin.
  • DNS over HTTPS answers A and AAAA only. A request for MX gets NOTIMP, not an invented A record.
  • WebFinger sends Access-Control-Allow-Origin: * on every answer, errors included, because a browser cannot read a 404 without it.
  • OAuth 2.0 demands PKCE (S256 only) from public clients, refuses a replayed code and kills the tokens it produced, and checks redirect URIs when the client is registered rather than when a user is redirected.
  • MCP never exposes an auth table, whatever its spelling, and answers 401 before it names a single tool.

Mounting one yourself

Every module takes a store or a lookup and returns a handler. Mounting it is one line, which is how the default routes are wired:

one handler, your own optionsts
import { createWebDAVHandler, createStorageDavStore } from "yatta.js/webdav";import { storage } from "./storage"; const dav = createWebDAVHandler(createStorageDavStore(storage.disk()), {  basePath: "/dav",  readOnly: false,  authenticate: async (req) => {    const user = await auth.getUser(req);    return user !== null;  },}); // Bun.serve({ fetch: dav }) — or hand it to your router.

The handlers are stateless except where the protocol needs state: WebDAV keeps locks in memory for this process, and OAuth keeps codes and tokens in its store. Nothing is written to disk unless you pass a store that writes.

Note
Two honest limits. WebDAV locks do not survive a restart and are not shared between nodes, and OAuth's default store is in memory, so a restart invalidates issued tokens. Both are the kind of thing that works on one machine and quietly does not on three.