Skip to main content

README.md

# barrel_server

The multi-protocol server for the barrel edge database. It exposes the `barrel`
API (documents, attachments, vectors, search, changes, timeline) as a
REST/JSON API over HTTP/1.1 and HTTP/2 using `livery`, and the same data as an
MCP endpoint for agents. It holds no database logic: every handler calls
`barrel` through a database lifecycle manager.

You need this when you want to reach a barrel database over the network (other
languages, remote clients, agents) instead of embedding it in an Erlang
application. For embedded use, depend on `barrel` directly and skip this app.

[Documentation](https://barrel-db.eu/docs/lib/server/) |
[HexDocs](https://hexdocs.pm/barrel_server) |
[Repository](https://github.com/barrel-db/barrel)

## Build and run

`barrel_server` is opt-in, behind the umbrella `server` profile (it pulls
`livery` and its transports). It is not part of the default embeddable build.

```console
$ rebar3 as server compile
$ rebar3 as server shell
1> application:ensure_all_started(barrel_server).
```

## Configuration

All keys live in the `barrel_server` app env. Set them in `sys.config`, or with
`application:set_env/3` after the app is loaded (loading resets the env from the
`.app` file, so a `set_env` before `ensure_all_started/1` is discarded).

```erlang
[{barrel_server, [
    {http_port, 8080},
    {data_dir, "/var/lib/barrel"},
    %% Request body ceiling. Must clear the largest attachment you sync.
    {max_body, 1073741824},
    %% Options passed to barrel:open_db/2 when a database opens lazily.
    {open_opts, #{}},
    %% Bearer auth. Omit the key entirely to leave the server open.
    {auth, #{tokens => [<<"secret-one">>, <<"secret-two">>]}},
    %% CORS. Omit to emit no CORS headers.
    {cors, #{origins => '*'}},
    %% MCP endpoint. Enabled by default when the key is absent.
    {mcp, #{enabled => true, allowed_origins => any}}
]}].
```

## Endpoints

Databases open lazily on first use and are cached by name.

```
GET    /                               liveness text
GET    /health                         {"status":"ok"}

PUT    /db/:db                          open/create a database
GET    /db/:db                          database info
DELETE /db/:db                          close a database

PUT    /db/:db/doc/:id                  body = JSON document
GET    /db/:db/doc/:id                  fetch a document
DELETE /db/:db/doc/:id                  delete a document
GET    /db/:db/doc/:id/_versions        live versions (conflict siblings)
GET    /db/:db/doc/:id/_versions/:rev   one version's body
POST   /db/:db/_bulk_docs               {"docs":[...]}  -> {"results":[...]}
POST   /db/:db/_bulk_get                {"ids":[...]}   -> {"results":[...]}
POST   /db/:db/find                     body = query, returns rows
POST   /db/:db/query                    BQL: {"query":"..."}
GET    /db/:db/query                    BQL via query string
GET    /db/:db/changes                  changes feed (JSON, or SSE)
GET    /db/:db/_history                 provenance history

GET    /db/:db/_timeline                timeline info
POST   /db/:db/_timeline/branch         fork a timeline
POST   /db/:db/_timeline/merge          merge a timeline

PUT    /db/:db/doc/:id/att/:name        body = raw bytes
GET    /db/:db/doc/:id/att/:name        fetch attachment bytes
DELETE /db/:db/doc/:id/att/:name        delete attachment

POST   /db/:db/vector                   {"id","text","metadata","vector"}
POST   /db/:db/search/vector            {"vector":[...],"k":10}
POST   /db/:db/search/bm25              {"query":"...","k":10}
POST   /db/:db/search/hybrid            {"query":"...","k":10}
```

### Replication

Replication runs over the wire against these endpoints. A remote barrel pulls
and pushes through them; you do not call them by hand.

```
GET    /db/:db/_sync/info               peer id, HLC, sync state
POST   /db/:db/_sync/hlc                fold the peer's clock
POST   /db/:db/_sync/changes            changes since a version vector
POST   /db/:db/_sync/diff               which versions the peer is missing
GET    /db/:db/_sync/doc/:id            fetch one version
PUT    /db/:db/_sync/doc/:id            push one version
GET    /db/:db/_sync/local/:id          replication checkpoints
PUT    /db/:db/_sync/local/:id
DELETE /db/:db/_sync/local/:id
GET    /db/:db/_sync/att_changes        attachment feed
POST   /db/:db/_sync/att_diff
GET    /db/:db/_sync/att/:id/:name
PUT    /db/:db/_sync/att/:id/:name
DELETE /db/:db/_sync/att/:id/:name
```

### Agent layer

Spaces, capability grants, sessions, and handoffs from `barrel_spaces`.

```
POST   /spaces                          create a space
GET    /spaces                          list spaces
GET    /spaces/:space                   space info
DELETE /spaces/:space                   drop a space

POST   /spaces/:space/grants            mint a capability token
GET    /spaces/:space/grants            list grants
DELETE /spaces/:space/grants/:token_id  revoke a grant

POST   /spaces/:space/sessions          open a session
GET    /spaces/:space/sessions          list sessions
GET    /spaces/:space/sessions/:sid     session info
DELETE /spaces/:space/sessions/:sid     close a session
POST   /spaces/:space/sessions/:sid/touch      extend the TTL
POST   /spaces/:space/sessions/:sid/messages   append a message
GET    /spaces/:space/sessions/:sid/messages   read messages
PUT    /spaces/:space/sessions/:sid/data/:key  set session data
GET    /spaces/:space/sessions/:sid/data/:key  read session data

POST   /handoffs                        offer a handoff
GET    /handoffs                        list handoffs
POST   /handoffs/accept                 accept a handoff
POST   /handoffs/complete               complete a handoff
```

### MCP

When `mcp` is enabled, `/mcp` serves the Model Context Protocol (`POST`, `GET`,
`DELETE`, `OPTIONS`) over the same databases: resources, tools, and the agent
layer. It carries its own origin policy, so the CORS middleware skips it.

## Examples

```console
$ curl -X PUT localhost:8080/db/mydb
{"ok":true,"db":"mydb"}

$ curl -X PUT localhost:8080/db/mydb/doc/a \
    -H 'content-type: application/json' -d '{"title":"hello"}'
{"id":"a","ok":true,"rev":"0000019f46b4c08900000000@8c0010c983917d4b"}

$ curl localhost:8080/db/mydb/doc/a
{"_rev":"0000019f46b4c08900000000@8c0010c983917d4b","id":"a","title":"hello"}

$ curl localhost:8080/db/mydb/changes
{"last":"AAABn0a0wIkAAAAA","changes":[{"id":"a","rev":"0000019f46b4c089...",
 "hlc":"AAABn0a0wIkAAAAA","changes":[{"rev":"0000019f46b4c089..."}],
 "num_conflicts":0}]}

$ curl localhost:8080/db/mydb
{"name":"mydb","config":{},"db_path":"/var/lib/barrel/mydb","att_floor":null,
 "history_floor":null,"keyspace":"mydb","retention_period":2592000}
```

A `rev` is a version token, `<hex(hlc)>@<author>`: the HLC of the write and the
id of the database that authored it. There is no revision tree.

## Authentication

Omit the `auth` key and the server stays open. Configure it and every route
except `/health` requires `Authorization: Bearer <token>`.

Two kinds of bearer are accepted:

- **Global tokens**, from `{auth, #{tokens => [Bin]}}`. They open every route.
  Pass a list so you can rotate. Comparison is constant time.
- **Capability tokens** (`bsp_...`), minted by `barrel_caps` for one space. They
  authenticate the agent-layer routes, and the `/db/:db` surface scoped to the
  space they grant. The server maps method and path to a required right (reads
  need `read`, writes and push need `write`) and checks the database is the
  granted space.

Unmapped routes answer 403, so a new route has to be classified before it can be
reached with a capability token. Bad or revoked tokens answer 401.

## Notes

- The changes feed returns JSON by default. Request `Accept: text/event-stream`
  (or `?feed=sse`) for Server-Sent Events. The `?since=<cursor>` parameter takes
  a cursor from a prior response's `last` field.
- CORS runs in front of auth, so preflights answer 204 without a token and 401
  bodies still carry CORS headers. `expose` defaults to the `x-barrel-*` headers
  a client needs to fold the HLC clock.
- The database manager does not trap exits: if an open store crashes, the
  manager restarts with an empty cache and databases reopen on the next request.
- gRPC, WebTransport, and a unix-socket adapter are later phases.