Markdown
Deploy a remote vault 0.2.1 · current

From a local CLI to a network service in two minutes.

In remote mode, the same vault binary speaks Streamable HTTP MCP with OAuth auth-code + PKCE. Stand it up on a real host so agents can resolve, read, propose, and update signed skills through MCP; local filesystem-native host folders still require local profile sync or a future mirror helper.

Transport: Streamable HTTP MCP
Auth: OAuth 2.1 + PKCE
Storage: SQLite on a persistent volume
Remote topologylive trace
AgentClaude CodeCodex · CursorHTTPSOAuthautovault-remote/mcpStreamable HTTP/authorize · /tokenauth-code · PKCE · refresh/admin/usersowner routespolicy layerscopes · roles · filterCORS · origin guardallowed originsSQLiteuserstokens

Pick a host

Self-host the remote MCP service with the documented Docker or Railway paths. Local installs remain the source of truth for host profile links. Remote mode cannot create symlinks on client machines. Remote clients should discover and read skills through get_skill when they cannot read your local filesystem.

Deploy to Railway · 5 steps · ~3 min
Deploy on Railway
1

Open the Railway template

Start from the AutoVault template rather than a raw repo deploy. The template is wired for the remote MCP container and keeps the public setup path reproducible.

https://railway.com/deploy/autovault?referralCode=VuFE6g&utm_medium=integration&utm_source=template&utm_campaign=generic
2

Set the owner credentials

Railway should prompt for the first owner email and a secret admin password. The password must be at least 12 characters and is hashed on first boot.

AUTOVAULT_ADMIN_EMAIL=admin@example.com
AUTOVAULT_ADMIN_PASSWORD=<long random string, min 12 chars>
3

Confirm the persistent volume

The remote server stores users, OAuth keys, signing keys, and skills under the vault path. Keep the Railway volume mounted at /data/autovault before the first healthy deploy.

AUTOVAULT_STORAGE_PATH=/data/autovault
volume mount: /data/autovault
recommended size: 1 GB
4

Confirm the public URL

Railway injects PORT, so leave it unset. AUTOVAULT_PUBLIC_URL must match the generated *.up.railway.app domain because OAuth metadata uses it as the issuer.

AUTOVAULT_MODE=remote
AUTOVAULT_PUBLIC_URL=https://<your-service>.up.railway.app
AUTOVAULT_SECURITY_STRICT=true
AUTOVAULT_LOG_LEVEL=info
5

Verify the remote MCP service

Healthz returns 200, OAuth discovery exposes the issuer, and unauthenticated MCP returns 401 with a WWW-Authenticate hint pointing at OAuth metadata.

URL=https://<your-service>.up.railway.app
curl -fsS "$URL/healthz" | jq
curl -fsS "$URL/.well-known/oauth-authorization-server" | jq
curl -i -X POST "$URL/mcp" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
Manual image deploy

For advanced operators who need to debug the service shape directly, deploy the public GHCR image yourself and keep the same volume and env contract.

ghcr.io/autoworks-ai/autovault:v0.2.1
volume: /data/autovault
leave PORT unset
After deploy
autovault status
$ curl -fsS https://your.vault/healthz | jq
 
{
"ok": true,
"name": "autovault",
"mode": "remote"
}
 
$ curl -fsS https://your.vault/.well-known/oauth-authorization-server | jq
 
{
"issuer": "https://your.vault/",
"authorization_endpoint": "https://your.vault/authorize",
"token_endpoint": "https://your.vault/token",
"registration_endpoint": "https://your.vault/register",
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"scopes_supported": ["mcp:tools", "autovault:read", "autovault:write", "autovault:admin"]
}
 
✓ remote vault healthy

Environment

All knobs are env-var driven. The first card is the breaking change in remote mode — Compose now hard-fails if these aren't set, instead of falling back to a known-default password.

Admin seed required
AUTOVAULT_ADMIN_EMAIL
Email address for the bootstrap owner. Created on first boot only; subsequent boots ignore it.
AUTOVAULT_ADMIN_PASSWORD
Min 12 chars. Hashed at rest; the plaintext is not logged or recoverable. Compose and Railway refuse to start without it.
Public surface mixed
AUTOVAULT_MODE
Set to remote to enable the HTTP MCP service. local (default) is stdio-only.
AUTOVAULT_PUBLIC_URL
Public HTTPS origin (no path). Used as the OAuth issuer and embedded in /.well-known metadata. Required in remote mode.
AUTOVAULT_HTTP_PORT optional
Bind port. Default 3000. On Railway, leave it unset because Railway injects PORT and the server reads that automatically.
Storage mixed
AUTOVAULT_STORAGE_PATH
Filesystem root for skills, signing keys, and SQLite. On Railway this should be the volume mount, e.g. /data/autovault.
AUTOVAULT_DB_PATH optional
Optional override for the SQLite path. Defaults to $AUTOVAULT_STORAGE_PATH/autovault.sqlite.
Hardening optional
AUTOVAULT_SECURITY_STRICT
Default true. When true, security-scanner flags block writes; when false, they become warnings. Leave on in production.
AUTOVAULT_ALLOWED_ORIGINS
Comma-separated origin list for the CORS allowlist. Server-to-server MCP calls don't need it; browsers do.
AUTOVAULT_LOG_LEVEL
debug · info · warn · error. JSON lines emitted to stderr.

OAuth handshake

Streamable HTTP MCP layered on OAuth 2.1 — auth-code flow with mandatory PKCE, dynamic client registration, refresh rotation, and revoke. Below is the exact sequence the bundled smoke suite walks for every release.

Agent CLIBrowser/authorize/mcp1open authorize URLPKCE challenge2GET /authorizecode_challenge + redirect_uri3/login formsession cookie set4redirect with code?code=...&state=...5POST /tokencode + verifier6access + refresh tokensscopes: read write7POST /mcpAuthorization: Bearer …8tool resultpolicy-filtered by scope

Endpoint reference

Every route the remote service exposes. Public routes are reachable pre-auth; bearer routes need a valid access token; owner routes additionally require role:owner.

MethodPathDescriptionAuth
GET/healthzLiveness probe. Returns <code>{ ok, name, mode }</code>.public
GET/.well-known/oauth-authorization-serverRFC 8414 metadata. Issuer, endpoints, supported flows.public
GET/.well-known/oauth-protected-resource/mcpResource metadata for the /mcp endpoint. Referenced from the WWW-Authenticate header.public
POST/registerDynamic client registration. Returns client_id.public
GET/authorizeAuth-code endpoint. Requires PKCE challenge.public
POST/tokenCode → access+refresh tokens. Refresh tokens rotate on use.public
POST/revokeRevoke an access or refresh token.public
GET/loginHTML login form, used by the authorize redirect for first-party owner login.public
POST/loginSubmit credentials, set session cookie, continue the OAuth flow.public
POST/logoutClear the session cookie.public
POST/mcpStreamable HTTP MCP transport. All tool calls land here.bearer
GET/mcpServer-sent event stream for MCP sessions.bearer
DELETE/mcpTerminate an MCP session.bearer
GET/admin/usersList users with roles and last-seen timestamps.owner
POST/admin/usersCreate a non-owner user with scoped grants.owner

Policy gate on every call

The same role + scope filter runs at the MCP boundary. Non-owner reads are filtered by capability access; writes require explicit scope:write.

src/remote/policy.ts

PKCE is mandatory

The /authorize endpoint rejects requests without a code_challenge. Refresh tokens rotate on use.

src/remote/auth.ts

CORS & origin pinning

Browser access is opt-in. AUTOVAULT_ALLOWED_ORIGINS is a strict allowlist; server-to-server calls bypass cleanly.

src/remote/server.ts

Verify with npm run smoke:remote

Bundled in the repo. Walks the full OAuth flow against your live deployment, calls real MCP tools, and verifies policy enforcement end-to-end. Wire it into CI to catch drift before users do.

npm run smoke:remote — bash
$ AUTOVAULT_REMOTE_URL=https://vault.acme.dev \
AUTOVAULT_ADMIN_EMAIL=admin@example.com \
AUTOVAULT_ADMIN_PASSWORD=… \
npm run smoke:remote
 
=== Targeting deployed server: https://vault.acme.dev ===
{ "ok": true, "name": "autovault", "mode": "remote" }
 
▸ register dynamic client → 201
▸ POST /authorize → /login → code → 302
▸ POST /token (auth code + PKCE) → 200
▸ MCP initialize over Streamable → 200
 
=== propose_skill ===
success: true
name: remote-smoke-skill
dedup: { tier: "novel" }
 
=== get_skill query ===
matches: [ { name: "remote-smoke-skill", score: 4.31 } ]
skill.skill_md: <277 chars>
 
=== Remote smoke test completed ===