slave
A Laravel-inspired Rust framework for MCP-first applications.
Every business operation in a slave app is an
MCP tool served over Streamable HTTP, so AI
agents and your own web apps use the same, single API. The slave CLI
scaffolds the backend (users, roles, teams, passwordless sign-in,
SurrealDB with SurrealQL migrations), adds server-rendered web apps (MiniJinja, Tera, Handlebars or
Liquid templates), and serves them all from one process routed by domain or
path (any app can also run as a process of its own, proxied), in the spirit of laravel new, php artisan make:*
and Route::domain().
browser / AI agent
│
▼
runtime (one process) ── config/apps.toml: route by domain and/or path
├──► MCP backend /mcp: auth_*, user_*, role_*, your tools … ← owns the database
└──► apps/storefront theme, server-rendered templates (calls MCP tools in-process)
Why
- One interface. No REST layer next to MCP: the tools an AI agent calls are the same ones your dashboards call.
- Batteries included, no passwords. Users and roles, sign-in with a
code sent by email or a passkey, server-side sessions that end at once on
sign-out, SurrealDB with SurrealQL migrations, and tests that drive
/mcpend to end with a throwaway database per test. - Pick a template language per app. Server-rendered pages (MiniJinja, Tera, Handlebars or Liquid) for a storefront theme or an admin area; apps reach the backend only through MCP.
- Laravel ergonomics. Generators, optional domain and path routing,
route:list,slave buildthen oneslave servefor everything. - Agent-ready projects. Every new project ships an
AGENTS.md(and aCLAUDE.mdthat imports it) teaching coding agents the architecture, the rules and recipes for adding features.
Install
Requirements: Rust 1.85+. Deploying to Fly.io needs the Fly CLI.
Quick start
&&
Open http://admin.localhost:8000 and sign in with any email; the code is
printed in the log until SMTP_URL and MAIL_FROM are set in .env. The
first user becomes super admin, and anyone can then add a passkey for quick
sign-in. slave doctor checks the setup and says what to fix. Add apps with
slave make:app storefront --domain "{toko}.localhost".
Everything runs in one process, the runtime (runtime/). Without
--domain/--path, an app is served directly on its own port (still in that
process). make:app --separate or app:separate <app> runs an app as a
process of its own, proxied by the runtime, to deploy or scale it separately.
MCP backend http://127.0.0.1:3000/mcp
Runtime http://127.0.0.1:8000 (one process)
http://mcp.localhost:8000 → MCP backend
http://<toko>.localhost:8000 → storefront
MCP clients sign in with the auth_email_start and auth_email_verify tools
and send the session token they get, for example Claude Code:
(with --header "Authorization: Bearer $TOKEN").
Commands
| Command | Does |
|---|---|
slave new <name> |
Create a project: MCP backend, auth, users, roles, migrations, the runtime, AGENTS.md |
slave build [<component>...] [--release] |
Compile everything, or only backend, runtime or a separate app, e.g. only what changed in CI (--list shows them) |
slave serve [--release] |
Run what was built: the runtime plus each separate app; never compiles |
slave doctor |
Check database, sign-in email, passkeys and build |
slave app:separate <app>, app:embed <app> |
Run an app as a process of its own (proxied by the runtime), or back inside it |
slave install:fly [--app <name>] [--region <code>] [--domain <host>] |
Fly.io files for the single runtime: Dockerfile, fly.toml, config/apps.fly.toml |
slave deploy:fly [--secrets <env file>] |
Create the Fly app if needed, stage secrets, check the required ones, fly deploy (migrations as the release command) |
slave make:tools <Name>Tools [--module m] |
MCP tools for a module (src/modules/<m>/tools.rs) |
slave make:model <Name> [--module m] |
A model in src/modules/<m>/models/ plus a migration defining its table |
slave make:migration <name> |
An empty SurrealQL migration in database/migrations/ |
slave login [--url], slave logout |
Sign in to the project's backend (a code by email; needs slave serve running) |
slave make:app <name> [--team <slug>] [--project <path>] [--domain <host>] [--path </prefix>] [--customer] [--separate] [--stack minijinja|tera|handlebars|liquid] |
A web app in apps/<name>, owned by one of your teams (requires slave login; outside a project, it uses the one you last logged in from); direct on its port, or routed by domain/path; in the runtime or separate |
slave migrate, migrate:status, migrate:fresh [--force] |
Apply pending migrations; list them; drop everything and re-apply (asks to confirm) |
slave route:list, route:match <host>[/path] |
Inspect domain and path routing |
Database
The backend keeps the platform's own data (users, roles, teams, and what you
add) in SurrealDB, on a server it reaches over
WebSocket: locally surreal start --user root --pass root, in production
SurrealDB Cloud or your own cluster. Set SURREAL_URL, SURREAL_NS,
SURREAL_DB, SURREAL_USER, SURREAL_PASS and SURREAL_AUTH_LEVEL in
.env, then slave migrate. Migrations are SurrealQL files in
database/migrations/, each applied once, in one transaction. Web apps never
open the database; they call MCP tools. Tests create a fresh database per
test on TEST_SURREAL_URL (default ws://127.0.0.1:8000, root/root)
and remove it afterwards.
Deploying to Fly.io
install:fly writes a Dockerfile, fly.toml and config/apps.fly.toml
(your routes with localhost moved under --domain). deploy:fly creates
the app, stages the env file as Fly secrets, refuses to deploy while a
required secret is missing, and runs fly deploy; pending migrations run
first (release_command). Machines are stateless, so fly scale count 2
works. Environment on Fly:
| Variable | Required | What |
|---|---|---|
SURREAL_URL |
yes | The platform's SurrealDB, e.g. wss://<instance>.surreal.cloud. Migrations run against it on every deploy (release_command) |
SURREAL_USER, SURREAL_PASS |
yes | A SurrealDB user; prefer a database user (DEFINE USER ... ON DATABASE, SURREAL_AUTH_LEVEL=database) |
SURREAL_NS, SURREAL_DB, SURREAL_AUTH_LEVEL |
no | Namespace and database (default main); where the user is defined (root by default) |
SMTP_URL, MAIL_FROM |
yes | Where sign-in codes are sent from, e.g. smtps://user:pass@smtp.resend.com and Shop <no-reply@shop.com> |
PASSKEY_RP_ID, PASSKEY_ORIGIN |
yes | The domain passkeys belong to (shared by its subdomains), e.g. shop.com and https://shop.com |
<APP>_PUBLIC_URL |
no | Fixed public origin of an app (otherwise taken from the request) |
MCP_ALLOWED_HOSTS |
no | Only if MCP clients reach the backend directly instead of through a routed host |
APP_NAME, RUST_LOG |
no | Set in fly.toml / the image (info) |
Set by the image, do not override: APP_HOST, APP_PORT, GATEWAY_LISTEN
(0.0.0.0:8080), SLAVE_ROUTES (config/apps.fly.toml) and
SLAVE_TRUSTED_PROXY (1, so apps see https behind Fly's proxy).
Routing
config/apps.toml maps requests to apps, like Laravel's Route::domain() and
Route::prefix():
[[]]
= "admin.localhost" # by domain
= "dashboard"
= 4000
[[]]
= "{toko}.localhost" # {toko} reaches the app as X-Slave-Param-Toko
= "storefront"
= 4001
[[]]
= "/console" # by path, on any host (add `host` to restrict)
= "console"
= 4002
The most specific entry wins regardless of order: the longest path prefix,
then the most specific host. Unknown routes get 404, a down app gets 502, and
spoofed X-Slave-* / X-Forwarded-* headers are dropped.
Crates
| Crate | Role |
|---|---|
slave |
This CLI and the project templates |
slave-framework |
Runtime used by generated apps: server lifecycle, config, logging, the SurrealDB connection and migrations, passwordless sign-in (email codes, passkeys, sessions), and a test database |
slave-gateway |
Domain/path routing for the runtime: apps in-process, a hyper proxy for separate apps |
Repository layout
src/ the slave CLI (commands, generators)
templates/ files written by `slave new` and `slave make:*`
framework/ slave-framework
gateway/ slave-gateway
tests/ end-to-end tests of the CLI and generated projects
Run the tests with cargo test --workspace; cargo test -p slave --test cli -- --ignored also builds a generated project and runs its own test suite.
License
MIT