Run the gateway on your own infrastructure
Docker Compose and PostgreSQL 17. Your credentials never leave your database, your audit log never leaves your disk, and nothing about the running system depends on reaching us.
$ curl -fsSL https://toolspoke.com/self-host/compose.yaml -o compose.self-host.yaml
$ echo "BETTER_AUTH_SECRET=$(openssl rand -base64 32)" >> .env
$ echo "CREDENTIALS_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env
$ docker compose -f compose.self-host.yaml up -d
Four values have no default and Compose refuses to start without them: those two secrets, a POSTGRES_PASSWORD and the APP_URL this deployment answers on. Generating a secret for you would mean finding out after a restart that your stored credentials can no longer be decrypted.
Nothing above needs an account with us. The compose file is served at toolspoke.com/self-host/compose.yaml and the image is public on GHCR, so docker pull works unauthenticated.
What the compose file runs
Nothing is hidden behind a hosted control plane. What you read in compose.self-host.yaml is the whole of what runs. It reads no vault and takes every value from your own .env.
- postgrespostgres:17-alpine
- Every workspace, policy, credential and audit row. Not published to the host at all, so it is reachable from the app container and from nowhere else. Its data lives in a named volume.
- appghcr.io/onvo-ai/toolspoke
- The MCP endpoint, the dashboard and the API in one Next.js server. Pulled rather than built, and it applies the schema itself before it starts listening, so there is no migration step.
What you have to configure
One signs sessions. One encrypts every stored credential before it touches the database. Neither has a default, because a default would mean every deployment in the world shares a key, and neither has a recovery path.
The rest of the configuration surface has sensible defaults or is simply absent, and each absence has a defined behaviour rather than a crash.
Stripe
Not configured means no billing. Every workspace starts on the Free plan with 5,000 credits and nothing anywhere asks for a card. This is the normal shape of an internal deployment, not a trial mode.
SMTP
Invitations are created but not delivered. You can still add people by handing them the invitation link yourself.
Google and GitHub sign-in
Without a client id and secret pair, sign-in is email and password only. Nothing breaks; one route simply is not offered.
A GitHub token
The catalog falls back to unauthenticated GitHub requests, which are rate limited to sixty an hour per address. Enough to try things, not enough for a busy team.
Embeddings
Tool search ranks on words alone. Results are still ranked and still useful; the semantic layer is an improvement on top, not a prerequisite.
What to know before you deploy it
Sandboxed tools need the sandbox runtime
Direct calls run in the gateway process and need nothing extra. Tools that execute in a container, which is how CLIs and stdio MCP servers run, are handed to Harborbox, which Toolspoke does not ship and you deploy separately. Leave its two variables unset and everything reachable over HTTP still works, which is most of the catalog.
Audit retention is yours to set
A workspace keeps its call log indefinitely unless you set a retention window on it. The sweep that enforces the window runs when the application starts, so it applies at deploy time rather than continuously.
You bring the TLS
The app container serves plain HTTP on port 3000, published to the host. Put a reverse proxy in front of it, terminate TLS there, and set APP_URL to that origin: it is the address MCP clients are handed and the audience stamped into every OAuth token.
Read the self-hosting guide
Compose file, required secrets, the health check and the upgrade path, written for somebody deploying it rather than somebody evaluating it.