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
| Path | Protocol | Turn it off |
|---|---|---|
| /grpc-web | gRPC-Web unary calls. Ships a health check and an echo; add your own services in code. | Mount your own handler instead. |
| /dav | WebDAV over the default storage disk — Finder, Explorer, Word, davfs2. | readOnly: true on your own handler. |
| /dns-query | DNS over HTTPS (RFC 8484), wire and JSON shapes. A and AAAA only. | Mount your own handler instead. |
| /.well-known/webfinger | WebFinger (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. |
| /mcp | An MCP server whose tools are generated from your collections. | MCP_ENABLED=0. |
/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
LOCKis a refresh), and serves anything not on an inline allowlist as a download — a file someonePUTmust not run on your origin. - DNS over HTTPS answers
AandAAAAonly. A request forMXgetsNOTIMP, 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 (
S256only) 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:
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.