Taruvi backend provisioning
Use the Taruvi MCP server to provision and modify backend resources. This skill covers tool selection, invocation order, the shapes the tools expect, and the gotchas that don't come through in tool descriptions.
This skill is the control plane. If you're writing Python that runs inside a deployed function, switch to taruvi-functions. If you're building a Refine frontend, switch to taruvi-refine-frontend.
Core principles
- Trust the MCP tool, not your memory. The MCP server is authoritative for Taruvi's current behavior. Read tool responses carefully — they return structured IDs, slugs, and status you need for the next step.
- Verify before destructive ops.
delete_datatable, schema changes that drop columns, policy overwrites, and raw SQL DDL all cause irreversible or hard-to-reverse changes. Always plan → validate → execute for these. - Frictionless schemas are upserts, not merges.
create_update_schemareplaces table definitions — fields missing from your new payload are dropped, not preserved. Always inspect the current schema first. - Cerbos policies are REPLACEMENTS.
manage_policies(action="create_update")fully replaces the policy body, not merged. - Tenant context is ambient. The MCP server resolves tenant + app from the session context. Do not try to pass tenant slugs in tool arguments unless a specific tool accepts
app_slug.
Tool index (24 tools)
Grouped by domain. See references/mcp-tool-quickref.md for one-line signatures.
Datatables (5): get_datatable_schema, create_update_schema, datatable_data, datatable_edges, delete_datatable
Storage (1): manage_storage (sub-actions: list_buckets, create_bucket, list_objects, get_quota)
Secrets (4): list_secrets, get_secret, create_update_secret, manage_secret_types
Users (3): list_users, create_user, update_user
User attributes (1): user_attributes_schema
Roles (2): manage_roles, manage_role_assignments
Functions (2): manage_function, execute_function
Policies (1): manage_policies
Analytics (2): manage_query, execute_query
Raw SQL (1): execute_raw_sql
Meta (2): manage_tags, get_ai_docs
Datatable provisioning
The most common workflow. See references/datatable-schema-patterns.md for the full Frictionless reference.
Creating a new datatable
Inspect existing schema (if the table might exist):
get_datatable_schema(table_name="orders")Returns the current definition or a NOT_FOUND error.
Prepare a Frictionless Data Package. Minimal shape:
{ "resources": [ { "name": "orders", "schema": { "fields": [ {"name": "id", "type": "integer", "constraints": {"required": true}}, {"name": "customer_id", "type": "integer"}, {"name": "total", "type": "number"}, {"name": "created_at", "type": "datetime"} ], "primaryKey": ["id"] } } ] }Apply:
create_update_schema(datapackage={...})Materializes the physical PostgreSQL table automatically. Returns created/updated/error counts.
Type mapping (Frictionless → Postgres)
| Frictionless | Postgres |
|---|---|
string |
TEXT |
integer |
INTEGER |
number |
NUMERIC |
boolean |
BOOLEAN |
date |
DATE |
datetime |
TIMESTAMP WITH TIME ZONE |
object, array |
JSONB |
Adding indexes, FKs, search, graph
Advanced features (indexes, foreign keys, populate, search, hierarchy/graph edges, column renames) live in references/datatable-schema-patterns.md. Read that file before authoring non-trivial schemas — the Frictionless shape has a lot of Taruvi-specific extensions (indexes, hierarchy, graph, search_fields, x-rename-from).
CRUD on rows
datatable_data(action="query", table_name="orders", filters={"total__gte": 100}, limit=100)
datatable_data(action="upsert", table_name="orders", data=[{...}, {...}], unique_fields="id")
datatable_data(action="delete", table_name="orders", ids=[1, 2, 3])
(limit defaults to 100, capped at 1000. Omit the arg to use the default.)
Filter operators follow DRF conventions: field__gte, field__in=1,2,3, field__contains=foo, field__null=true, etc.
Graph edges (for hierarchy/graph-enabled tables)
datatable_edges(action="list", table_name="categories")
datatable_edges(action="create", table_name="categories", edges=[{"from_id": 1, "to_id": 2, "type": "parent"}])
datatable_edges(action="delete", table_name="categories", edge_ids=[10])
The table must have been created with graph/hierarchy enabled in its Frictionless schema.
Deleting a datatable
Destructive. Drops the physical table, the edges table (if any), and the metadata row.
delete_datatable(table_name="orders") # fails on FK dependencies
delete_datatable(table_name="orders", force=True) # bypass FK checks — DANGEROUS
Always confirm with the user before passing force=True. Prefer the non-force call first so the error tells you what depends on the table.
Users, roles, and policies
See references/cerbos-policy-cookbook.md for policy authoring.
Creating a user
create_user accepts username, email, optional password (generate one if omitted), attributes dict, and role_slugs list.
If the user hasn't specified roles, list them first:
manage_roles(action="list")
Pick an appropriate role from the response and assign via role_slugs=["..."] in the create call.
Roles
manage_roles(action="list")
manage_roles(action="create", name="editor", description="...", parent_slug="viewer")
manage_roles(action="bulk_create", roles=[{"name": "..."}, ...])
manage_roles(action="delete", role_slug="editor") # fails if role has members or children
Role assignments
manage_role_assignments(action="assign", roles=["editor"], usernames=["alice", "bob"])
manage_role_assignments(action="revoke", roles=["editor"], usernames=["alice"])
Both roles and usernames accept a single value or list. Assignment supports expires_at (ISO datetime).
User attributes schema
user_attributes_schema(action="get")
user_attributes_schema(action="update", schema={...}) # REPLACES the entire schema
Requires manage_site cloud permission. The schema is JSON Schema Draft 2020-12 and is tenant-wide (singleton). Call get_ai_docs(category="users", topic="attributes") if you need the full authoring guide.
Cerbos policies
manage_policies(action="create_update", policy_data={...}) # REPLACES, does not merge
manage_policies(action="get", policy_id="...")
manage_policies(action="get", name_regexp="order.*") # list with filter
manage_policies(action="enable", policy_id="...")
manage_policies(action="disable", policy_id="...")
Policy authoring is non-trivial. Before writing a policy from scratch, load references/cerbos-policy-cookbook.md or call get_ai_docs(category="policies", topic="guide").
Storage
manage_storage(action="list_buckets")
manage_storage(action="create_bucket", name="uploads", visibility="private", app_category="attachments", max_size_bytes=10485760)
manage_storage(action="list_objects", bucket_slug="uploads", prefix="2026/", limit=50)
manage_storage(action="get_quota", bucket_slug="uploads")
app_category is required for create_bucket and must be "assets" or "attachments". The bucket's RLS policy is created automatically by the serializer.
Secrets
See references/secrets-and-types.md.
Before creating a secret, a secret type must exist that matches the value's shape. System types (OAuth creds, API keys, etc.) are pre-provisioned; create custom types as needed:
manage_secret_types(action="list")
manage_secret_types(action="create", name="stripe-cred", description="...", schema={...}, sensitivity_level="sensitive")
Then:
create_update_secret(key="STRIPE_KEY", value="sk_live_...", secret_type="stripe-cred", tags=["prod"])
get_secret(key="STRIPE_KEY") # returns [ENCRYPTED] for non-public sensitivities
list_secrets(secret_type="stripe-cred")
Sensitivity levels: public (value returned verbatim), private, sensitive (value masked in responses).
Functions (registration)
manage_function(action="list")
manage_function(
action="create_update",
name="send-email",
execution_mode="app",
code="def main(params, user_data, sdk_client): ...",
description="...",
is_active=True,
is_public=False, # if True, function is callable without authentication
async_mode=False, # if True, execution returns a task_id instead of a result
config={...}, # optional runtime config (timeouts, env)
auth_config={...}, # optional auth policy override
headers={...}, # optional default request headers
tags=["notifications"], # optional tag slugs
)
manage_function(action="get", function_slug="send-email")
manage_function(action="delete", function_slug="send-email")
execute_function(function_slug="send-email", params={...}, async_mode=False)
Execution modes: app (Python body runs in Taruvi runtime), proxy (forwards to webhook_url), system (privileged).
Security note on is_public=True: public functions run unauthenticated (user_data is None). Verify a shared secret or signature from the caller in the function body before trusting params. The function body should assume the input is adversarial.
For writing the actual function body, switch to taruvi-functions. This skill only registers the metadata; the body must follow the runtime conventions documented there.
Analytics queries
manage_query(action="create", name="daily-revenue", query_text="SELECT ...", connection_type="internal", tags=["reporting"])
manage_query(action="list")
execute_query(query_slug="daily-revenue", params={"date": "2026-04-17"})
connection_type is "internal" (against tenant DB) or "external" (requires a secret_key that resolves to a DB credential). Default is "external" — pass connection_type="internal" explicitly when you want to query the tenant DB. For external queries, create the credential secret first.
Raw SQL
See references/raw-sql-safety.md.
execute_raw_sql(sql="SELECT ...", params={"min_total": 100}, max_rows=1000)
execute_raw_sql(sql="ALTER TABLE orders ADD COLUMN ...", auto_reflect=True)
- Tool rejects cross-tenant access, system schemas, view/trigger/function DDL.
- DDL commits before any following DML in the same batch (don't mix in one call).
- DML is audited to
alembic_revision_history. - Prefer
datatable_dataover raw DML. Prefercreate_update_schemaover raw DDL. Use raw SQL only when MCP tools can't express what you need.
Destructive-op protocol
Always plan-validate-execute for:
delete_datatable(especially withforce=True)manage_policies(action="create_update")on an existing policy (full replacement)user_attributes_schema(action="update")(replaces the whole schema)manage_secret_types(action="delete")execute_raw_sqlcontainingDROP,TRUNCATE, destructiveALTER, orDELETEwithoutWHEREdelete_function,manage_roles(action="delete")
Procedure:
- Plan — state what will be deleted/replaced and what depends on it. Use
get_datatable_schema,manage_policies(action="get"), etc. to inspect current state. - Validate — surface the blast radius to the user in plain language ("This will drop the
orderstable and 3 dependent FK constraints ininvoices"). Ask for explicit confirmation. - Execute — only after confirmation. Report back with the tool's response verbatim.
Gotchas
create_update_schemadrops missing fields. Alwaysget_datatable_schemafirst so your new payload preserves fields that must stay.create_userwithoutrole_slugscreates an unroled user. List roles first withmanage_roles(action="list")and pick one based on context. Generate a password (12+ chars, mix of classes) if the user didn't provide one.manage_policies(action="create_update")replaces, not merges. Get the existing policy first if you only want to change part of it.delete_datatablewithoutforce=Truefails on FK deps. The error lists what depends on the table — read it carefully before suggestingforce=True.- Secret types cannot be renamed post-create and system types cannot be modified at all. Choose names carefully.
execute_raw_sqlDDL commits immediately, even if a later statement in the same batch fails. The tool warns when you mix DDL + DML.manage_storage(action="create_bucket")requiresapp_category. Pick"assets"(public-ish static content) or"attachments"(user-uploaded files).- User updates don't accept password changes. Use a separate password-reset flow (outside this MCP surface).
manage_role_assignmentsaccepts strings or lists for bothrolesandusernames. Single-value semantics are fine; check theis_bulkflag in the response.- The
is_active=Truedefault onlist_userssilently hides inactive users. Passis_active=Falseor a different filter if you need all users.
Verification checklist
After any provisioning task, confirm before reporting done:
- For new/updated tables:
get_datatable_schema(table_name=...)returns the expected shape (fields, PK, FKs, indexes). - For any new table with data-plane consumers: a Cerbos policy exists (
manage_policies(action="get", name_regexp="^datatable:<name>$")) and is enabled. - For new roles:
manage_roles(action="list")shows the role with the correct parent (if hierarchical). - For role assignments: verify a test user has the role via
list_users(role_slug=...)or the user's apps/roles lookup. - For new buckets:
manage_storage(action="list_buckets")returns the bucket with the intendedvisibilityandapp_category. - For new functions:
manage_function(action="get", function_slug=...)returns the metadata; if the code was just updated, confirm thecodefield matches. - For new secrets:
get_secret(key=...)returns the right type and (for public) the expected value. - For any destructive op (delete_datatable, raw SQL DROP/TRUNCATE, policy replace): the plan was surfaced to the user and explicit confirmation was captured.
- No workflow instructions were added to MCP tool descriptions — that content belongs here in the skill.
If any item fails, fix it before presenting the work as done.
When you get stuck
- Tool-level docs:
get_ai_docs(category="policies"|"sdk"|"users", topic="guide"|"attributes"). - MCP quickref: references/mcp-tool-quickref.md.
- Frictionless schema detail: references/datatable-schema-patterns.md.
- Cerbos policy examples: references/cerbos-policy-cookbook.md.
- Secret type schemas: references/secrets-and-types.md.
- Raw SQL rules: references/raw-sql-safety.md.
- Analytics queries: references/analytics-queries.md.
- Drift check:
bash scripts/check-versions.shin this skill directory.