Settra
https://github.com/user-attachments/assets/63f8b52a-7618-405d-9601-d24eea2bdbbf
Ask once. Keep the model.
Settra gives AI assistants a governed analytics layer over live business apps. Ask a question, then keep the resulting business model for every question that follows.
Architecture
Settra connects tools such as Google Sheets, Stripe, and HubSpot through Steampipe, mounts Cube Core model files for those live app schemas, and exposes trusted cubes, measures, dimensions, joins, segments, AI context, and query execution through MCP. The agent does not need a complete data model before it starts. It inspects the applications involved in a question, proposes the required metrics and relationships, validates them, and saves them as reusable semantics.
MCP client
|
v
/mcp streamable HTTP
|
v
FastAPI backend (:8000)
|
+-- MCP tools and resources backed by Cube metadata and queries
+-- Cube REST API proxy and query execution
+-- aiosqlite -> /data/app.db
| connections, MCP request metrics
+-- aiofiles -> /steampipe/config/*.spc
| connector credentials rendered as Steampipe config
+-- asyncpg -> steampipe:9193
| FDW diagnostics and metadata checks
+-- /cube/conf/model
mounted Cube YAML model files
Admin browser
|
v
FastAPI backend (:8000)
|
+-- /api/connectors, /api/connections, /api/health
+-- /api/semantics/model, /api/semantics/meta
Cube Core (:4000)
|
+-- reads /cube/conf/model
+-- connects to Steampipe with the Postgres driver
+-- exposes metadata and query results at /cubejs-api/v1
Steampipe (:9193)
|
+-- reads /home/steampipe/.steampipe/config/*.spc
+-- installs plugins declared by connectors/*/connection.yaml
+-- exposes each saved connection as a PostgreSQL FDW schema
MCP Surface
Settra mounts its MCP server at /mcp using streamable HTTP. MCP clients use
Cube as the semantic contract; they do not query raw Steampipe tables directly.
The server instructions tell agents to prefer existing compiled cubes and
measures, inspect bounded metadata/profile evidence before proposing cross-app
relationships, and create semantic overlays only after explicit user approval.
Generated overlays should preserve their purpose, grain, assumptions,
relationship rules, metric definitions, evidence, and validation results.
Read-only discovery covers both hand-authored and generated overlays, including
files that did not compile. Agent writes are restricted to
/cube/conf/model/overlays/generated.
MCP tool responses omit normal defaults, empty optional metadata, and request
echoes. The server instructions define absent fields as defaults and direct
clients to use the original tool arguments plus total and next_cursor for
pagination. Resources explicitly described as raw are intentionally exempt.
Available tools:
| Tool | Purpose |
|---|---|
list_cubes |
Search a bounded, paginated catalog of compiled cubes; member previews are opt-in and capped, and get_cube provides compact one-cube semantics. |
get_cube |
Fetch one compact semantic definition with source, SQL, filters, references, relationships, and non-default behavior. |
query_cube |
Execute one bounded Cube REST query object and return one compact data array; annotated numerics and business dates are normalized. |
get_cube_meta |
Search compact, bounded Cube /v1/meta detail; member collections are opt-in, capped, and stripped of default UI fields. |
list_connections |
List saved Settra connections without secrets, including slugs used in generated cube names and schemas. |
get_connection_metadata |
Search a bounded live-table catalog; the first ten columns per table are included by default, with table and column cursor pagination. |
sample_connection_table |
Fetch compact positional rows with column names once; scalar truncation metadata appears only when truncation occurred. |
profile_connection_table |
Return a compact sampled profile keyed by column name; descriptions are opt-in and bounded, and differing source/inferred types are preserved. |
list_semantic_overlays |
List compact overlay summaries with path, models, compile state, manifest state, and purpose. |
get_semantic_overlay |
Read exact overlay YAML once with compact compile status and missing manifest fields; use get_cube for compiled semantics. |
validate_semantic_overlay |
Dry-run proposed Cube YAML; pass the existing generated path for replacements; failures include compiler diagnostics. |
create_semantic_overlay |
Create a validated, approved generated overlay and return compact manifest/compile status; fail if the path already exists. |
update_semantic_overlay |
Replace an existing validated, approved generated overlay; return a diff summary by default and the full diff only when requested. |
save_semantic_overlay |
Deprecated generated-overlay upsert retained for older MCP clients; prefer create or update. |
Available resources:
| Resource | Purpose |
|---|---|
settra://semantics/meta |
Raw compiled Cube metadata. |
settra://semantics/cubes |
First fixed page; use list_cubes to paginate. |
settra://semantics/cubes/{name} |
Compact semantics for one cube or view. |
settra://semantics/model/{path} |
Mounted Cube YAML model file content. |
Connectors
Connector definitions live in:
connectors/<connector-key>/connection.yaml
Connector Cube models live next to them:
connectors/<connector-key>/semantics.yaml
Those semantics.yaml files use Cube YAML directly, but they are templates.
When a saved connection exists, Settra generates an active connection-specific
Cube model under /cube/conf/model/generated/connections/<connection-slug>.yaml.
The generated file rewrites sql_table schemas to the actual Steampipe
connection slug and prefixes cube names when needed to avoid collisions across
multiple connections for the same app. For example, a Stripe connection named
stripe_sandbox generates cubes such as stripe_sandbox_charge with
sql_table: '"stripe_sandbox"."stripe_charge"'.
Workspace-specific cross-app models can live in semantic_overlays/, which is
mounted into Cube at /cube/conf/model/overlays.
Settra reports model file source types so clients can distinguish where semantic definitions came from:
| Source type | Meaning |
|---|---|
bundled_connector |
Static connector semantics packaged from connectors/<key>/semantics.yaml; used as templates, not live connection models. |
generated_connection |
Active connection-specific model generated from a bundled connector template under /cube/conf/model/generated/connections. |
overlay |
Hand-authored workspace overlay under /cube/conf/model/overlays. |
generated_overlay |
Agent-generated, user-specific overlay under /cube/conf/model/overlays/generated. |
Recommended MCP workflow for generated overlays:
- Call
list_connections. - Call
get_connection_metadatafor relevant connections. - Call
sample_connection_tableandprofile_connection_tablefor user-specific or unfamiliar tables. - Inspect compiled semantics with
list_cubesandget_cube, then calllist_semantic_overlaysto discover authored, failed, duplicate, or stale overlays. - Call
get_semantic_overlaybefore extending or replacing an existing model. - Draft the smallest reusable Cube YAML overlay. Preserve provenance under
each cube or view's
meta.settramapping. - Call
validate_semantic_overlaywith the proposed YAML and representative Cube RESTtest_queries; for replacements, pass the existing generated overlay path. - Explain the validation result, warnings, assumptions, and any business decisions that still need approval.
- Use
create_semantic_overlayfor a new approved path orupdate_semantic_overlayfor an approved replacement. - Verify with
list_cubes,get_semantic_overlay, andquery_cube. - Ask the user to clean up failed experiments manually from the admin UI.
New or updated generated overlays require these provenance fields on every
declared cube or view. relationships, metrics, validation, and approval
should also be preserved when relevant:
meta:
settra:
purpose: Compare revenue targets with matched actual revenue
requirement: Show monthly target, actual, attainment, gap, and unmatched revenue
grain: One row per month
assumptions:
- Stripe customers match HubSpot contacts by normalized email
relationships:
- from: stripe_customer.email
to: hubspot_contact.email
cardinality: many_to_one after HubSpot email deduplication
evidence:
matched_customers: 10
unmatched_customers: 2
revenue_coverage_percent: 98.4
The sample/profile tools are structured introspection tools, not raw SQL
execution. They are bounded, redact obvious secret-like columns, and reconstruct
Google Sheets virtual worksheet tables from googlesheets_cell so agents can
infer columns such as dates, owners, notes, and revenue targets before writing
Cube YAML.
For timezone-neutral business dates, mark the Cube member with
meta.settra.semantic_type: business_date. query_cube renders those values as
YYYY-MM-DD instead of timestamp strings so clients do not shift them into a
viewer-local timezone.
HTTP API
The HTTP API supports administration, diagnostics, Cube model editing, and Cube-backed query execution:
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Steampipe connectivity check. |
GET |
/api/health/fdw |
Per-connection FDW diagnostics. |
POST |
/api/health/fdw/{id}/refresh |
Refresh Steampipe metadata cache. |
POST |
/api/health/steampipe/restart |
Restart Steampipe when configured. |
GET |
/api/connectors |
List connector definitions. |
GET/POST/PUT/DELETE |
/api/connections... |
Manage saved app connections. |
POST |
/api/connections/{id}/retry |
Re-validate connection credentials and FDW state. |
POST |
/api/connections/{id}/metadata |
Fetch live Steampipe metadata. |
POST |
/api/query/ |
Execute Cube REST query JSON. |
GET |
/api/semantics/model |
List Cube model files and Cube metadata status. |
POST |
/api/semantics/model/sync |
Refresh the mounted Cube model file view. |
GET/PUT |
/api/semantics/model/files/{path} |
Read or update Cube YAML model files. |
DELETE |
/api/semantics/model/files/{path} |
Delete a generated overlay from the admin UI. |
GET |
/api/semantics/meta |
Proxy Cube /v1/meta metadata. |
GET |
/api/requests |
List MCP request metrics and token estimates. |
GET |
/.well-known/oauth-protected-resource |
OAuth protected-resource metadata for MCP. |
GET |
/.well-known/oauth-authorization-server |
OAuth authorization-server metadata. |
POST |
/oauth/register |
Dynamic client registration for MCP OAuth. |
GET/POST |
/oauth/authorize |
Single-admin authorization-code + PKCE flow. |
POST |
/oauth/token |
Authorization-code token exchange. |
Configuration
Common environment variables:
| Variable | Default | Purpose |
|---|---|---|
STEAMPIPE_HOST |
steampipe |
Steampipe service hostname. |
STEAMPIPE_PORT |
9193 |
Steampipe PostgreSQL port. |
STEAMPIPE_CONFIG_DIR |
/steampipe/config in compose |
Where connector .spc files are written. |
STEAMPIPE_DB_PASSWORD |
steampipe_pass in compose |
Password for the Steampipe PostgreSQL user. |
STEAMPIPE_RESTART_COMMAND |
unset | Optional restart command for non-Docker deployments. |
STEAMPIPE_RESTART_TIMEOUT_SECONDS |
120 |
Restart command timeout. |
CUBE_API_URL |
http://cube:4000/cubejs-api |
Backend-to-Cube REST API base URL. |
CUBE_API_SECRET |
cube-dev-secret-change-me in compose |
Secret used to sign Cube REST API tokens. |
CUBE_MODEL_DIR |
/cube/conf/model |
Directory where Cube model files live. |
DATA_DIR |
/data |
SQLite data directory. |
DB_PATH |
/data/app.db |
SQLite database path. |
CONNECTORS_DIR |
/config/connectors |
Connector definitions and bundled Cube YAML files. |
SECRET_KEY |
dev-secret-change-me |
General secret key material. |
SETTRA_PUBLIC_URL |
derived from request if unset | Public origin used as OAuth issuer and resource audience. |
SETTRA_OAUTH_ENABLED |
false locally; true on Hetzner |
Enables OAuth for ChatGPT-compatible MCP access. |
SETTRA_OAUTH_ADMIN_USER |
settra |
Single-admin username for the built-in OAuth login page. |
SETTRA_OAUTH_ADMIN_PASSWORD |
unset | Single-admin password for the built-in OAuth login page. |
SETTRA_OAUTH_REDIRECT_HOSTS |
chatgpt.com |
Comma-separated allowlist for OAuth redirect hosts. |
SETTRA_OAUTH_SCOPES |
settra:read settra:write |
Space- or comma-separated scopes advertised for /mcp. |
SETTRA_OAUTH_TOKEN_TTL_SECONDS |
3600 |
Lifetime for signed MCP access tokens. |
SETTRA_OAUTH_REFRESH_TOKEN_TTL_SECONDS |
2592000 |
Inactivity lifetime for rotating MCP OAuth refresh tokens. |
SETTRA_OAUTH_CODE_TTL_SECONDS |
300 |
Lifetime for one-time authorization codes. |
LOG_LEVEL |
INFO |
Backend log level. |
MCP_ALLOWED_HOSTS |
localhost defaults | Comma-separated allowed hosts for MCP transport security. |
MCP_ALLOWED_ORIGINS |
localhost defaults | Comma-separated allowed origins for MCP transport security. |
MCP_REQUEST_HISTORY_LIMIT |
10000 |
Maximum retained MCP request metric rows. |
Local Development
# First-time setup
cd frontend && npm install
cd ../backend && pip install -r requirements.txt
cd ..
# Initialize SQLite and verify mounted Cube model files
make init
# Full development stack
make dev
# Docker stack only
make run
Useful commands:
# Rebuild the app image
make build
# Rebuild the Steampipe image
make build-steampipe
# Run backend initialization inside Docker
docker compose exec app python -m app.init
# Inspect Steampipe connections
docker compose exec steampipe steampipe query "SELECT * FROM steampipe_internal.steampipe_connection"
# Watch logs
docker compose logs -f app
docker compose logs -f cube
docker compose logs -f steampipe
Semantic Layer
Cube Core is the canonical semantic layer.
- A saved connection renders a Steampipe
.spcfile. - Cube reads the connector’s mounted
semantics.yamlas Cube YAML. - Cube compiles the model and exposes metadata through
/cubejs-api/v1/meta. - MCP clients inspect compiled metadata through MCP tools and resources.
- MCP clients execute semantic queries through
query_cube, which sends Cube REST query JSON to Cube. - Cube queries Steampipe’s FDW-backed PostgreSQL schemas and returns trusted semantic results.
Deployment
Can run anywhere containers can run. The included Hetzner helper deploys
the app, Cube Core, Steampipe, and Caddy. It derives a public hostname from the
server IP using sslip.io, so a deployment works without owning a domain. Caddy
keeps Basic Auth in front of the admin UI and API, while /mcp uses OAuth
bearer-token authentication for ChatGPT and other OAuth-capable MCP clients.
Deployment
Settra can run anywhere containers can run.
Quick deploy on Hetzner
For now, the lowest cost Hetzner VPS is a CX23 x86 with 2 vCPUs, 4GB of RAM, and a 40GB SSD running Ubuntu 26.04 for $5/month. It sits in Helsinki.
Install the hcloud cli and create a context:
- Install the cli however you want.
- From the Hetzner Console, go to Security -> API Tokens, and generate a token.
- Run
hcloud context create <context-name>and paste in your token.
Create a local SSH key and upload the public key to Hetzner:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -C "settra-hetzner" -f ~/.ssh/settra_hetzner
hcloud ssh-key create \
--name settra \
--public-key-from-file ~/.ssh/settra_hetzner.pub
Deploy:
./deploy/hetzner/deploy.sh
When deploying images from your own DockerHub namespace, pass the same tags you published:
SETTRA_IMAGE=<dockerhub-user>/settra:0.0.1 \
SETTRA_STEAMPIPE_IMAGE=<dockerhub-user>/settra-steampipe:0.0.1 \
./deploy/hetzner/deploy.sh
Connect using the private key:
hcloud server list # to get the server ip
ssh -i ~/.ssh/settra_hetzner root@<server-ip>
The server generates a temporary sslip.io HTTPS hostname and Basic Auth
credentials on first boot. Get them here cat /opt/settra/credentials.txt.
Open the printed Settra URL in a browser. The React app and API are protected by
Basic Auth; Telegram webhooks remain public at
https://<settra-host>/api/messaging/webhooks/telegram/<config_id>.
If the server boots but the app is not running, check cloud-init and Docker from within the VPS:
cloud-init status --long
tail -n 200 /var/log/cloud-init-output.log
cd /opt/settra
docker compose pull
docker compose up -d
docker compose ps
The Hetzner deployment writes generated semantic overlays to a persistent
semantic_overlays volume mounted at /cube/conf/model/overlays. Cube dev mode
is enabled by default so agent-generated model files hot reload; set
CUBEJS_DEV_MODE=false only when model changes are handled as deploy/restart
events.
Remote MCP Clients
For ChatGPT developer-mode connectors, use the generated MCP URL from
/opt/settra/credentials.txt:
https://<settra-host>/mcp/
The OAuth login username and password in credentials.txt are the same initial
single-admin credentials generated for Basic Auth. ChatGPT discovers OAuth
metadata at /.well-known/oauth-protected-resource, registers a client through
/oauth/register, sends the user through /oauth/authorize, exchanges the code
at /oauth/token, then calls /mcp with Authorization: Bearer <token>.
MCP client support depends on how the server is deployed:
| Client/deployment | Supported? | How to connect |
|---|---|---|
| ChatGPT developer-mode connector on Hetzner | Yes | Paste the generated https://<settra-host>/mcp/ URL. ChatGPT uses OAuth discovery and sends bearer tokens after login. |
| Other OAuth-capable MCP clients on Hetzner | Yes | Use the same https://<settra-host>/mcp/ URL and complete OAuth authorization-code + PKCE. |
| Static-header MCP clients on Hetzner | Not by default | Public /mcp does not accept Basic Auth headers. Basic Auth protects only the admin UI and API. |
| Local/dev MCP clients | Yes | Local Docker keeps OAuth disabled by default, so clients can connect directly to http://localhost:8000/mcp/. |
For local/dev static-header clients, omit the headers block unless you add
your own reverse proxy in front of the app. A typical local config looks like:
{
"mcpServers": {
"settra": {
"type": "streamable-http",
"url": "http://localhost:8000/mcp/"
}
}
}
To test the OAuth path locally, set:
SETTRA_OAUTH_ENABLED=true
SETTRA_PUBLIC_URL=http://localhost:8000
SETTRA_OAUTH_ADMIN_USER=settra
SETTRA_OAUTH_ADMIN_PASSWORD=settra
Important OAuth environment variables:
| Variable | Purpose |
|---|---|
SETTRA_PUBLIC_URL |
Canonical public origin used as OAuth issuer and resource audience, for example https://settra-203-0-113-10.sslip.io. |
SETTRA_OAUTH_ENABLED |
Enables OAuth discovery, registration, authorization, token exchange, and /mcp bearer-token checks. |
SETTRA_OAUTH_ADMIN_USER |
Single-admin username for the built-in authorization page. |
SETTRA_OAUTH_ADMIN_PASSWORD |
Single-admin password for the built-in authorization page. |
SETTRA_OAUTH_REDIRECT_HOSTS |
Comma-separated allowlist for OAuth redirect hosts. Defaults to chatgpt.com. |
SETTRA_OAUTH_SCOPES |
Space- or comma-separated scopes advertised and required for /mcp. Defaults to settra:read settra:write. |
SETTRA_OAUTH_TOKEN_TTL_SECONDS |
Lifetime for signed MCP access tokens. Defaults to 3600. |
SETTRA_OAUTH_REFRESH_TOKEN_TTL_SECONDS |
Inactivity lifetime for rotating MCP OAuth refresh tokens. Defaults to 2592000. |
SETTRA_OAUTH_CODE_TTL_SECONDS |
Lifetime for one-time authorization codes. Defaults to 300. |
The built-in OAuth provider is meant to make a self-hosted single-admin Settra deployment easy to connect from ChatGPT. It issues rotating refresh tokens so ChatGPT can renew one-hour access tokens without another interactive login; reusing an invalidated refresh token revokes that token family. For multi-user production deployments, use a dedicated OAuth/OIDC identity provider and keep Settra as the resource server that validates issuer, audience, expiry, and scopes.
Contributing
Contributions are welcome, especially:
- MCP client compatibility testing
- Cube model validation improvements
- new connectors
- connector Cube models
- cross-app semantic overlays
- deployment guides
- security hardening
- documentation fixes
Please see CONTRIBUTING.md for details.
License
Settra is released under the Apache License 2.0. See LICENSE.
No comments yet
Be the first to share your take.