Skip to main content

Python SDK for Trove — files and commands for AI agents

Project description

trove-sdk · Python

Python client for Trove — files and commands for AI agents. Persistent storage that survives every session, isolated per customer, with real Unix tools (awk, jq, pdftotext, ffmpeg) preinstalled.

Installation

pip install trove-sdk
# or with the CLI:
pip install 'trove-sdk[cli]'
# or with the MCP server (Claude Desktop, Cursor, Claude Code):
pip install 'trove-sdk[cli,mcp]'
# or
uv add 'trove-sdk[cli,mcp]'

Requires Python 3.10+.

Use Trove from Claude Desktop / Cursor / Claude Code

Trove ships an MCP server. After logging in, one command wires it into every detected client — no JSON editing.

pip install 'trove-sdk[cli,mcp]'
trove login --api-key trove-sk-... --namespace my-project
trove mcp install                       # every detected client
# or scope it: --client claude-desktop, --client cursor, --client claude-code

Restart the client and your agent gets three tools:

Tool What it does
trove_exec(command, stdin?) Run any shell command in your workspace. jq, awk, pdftotext, ffmpeg, python3, etc. preinstalled.
trove_read(path) Read a UTF-8 text file (1 MB cap).
trove_write(path, content) Write a UTF-8 text file.

trove mcp status shows which clients are wired up; trove mcp uninstall removes the entry. The MCP server reads TROVE_API_KEY / TROVE_NAMESPACE from the env block written into the client's config — point at a different namespace by re-running install with -n <ns>.

Multi-tenant agent isolation (three-key pattern)

If you're running an agent product where each end-user gets their own sandbox, this is the pattern you want. One namespace per session, one scoped key per session, hard isolation enforced server-side.

                       ┌─────────────────────────────────────┐
                       │ secrets manager                     │
                       │   TROVE_ADMIN_KEY     (scope:admin) │
                       │   TROVE_RUNTIME_KEY   (unscoped)    │
                       └─────────────────────────────────────┘
                                    │
       ┌────────────────────────────┼────────────────────────┐
       │                            │                        │
       ▼                            ▼                        ▼
   provisioner                  agent runtime            ops dashboard
   (admin key)                 (scoped key)            (unscoped key)
       │                            │                        │
       │  mints scoped key          │  hard-isolated to      │  reads across
       │  per session               │  one namespace          │  every namespace
       │                            │                        │
       └────────► session-abc123 ◄──┘                        │
                  session-xyz789  ◄────────────────────────► │
Key Where it lives What it does Why not just one key?
Admin Backend secrets manager Mint and revoke session keys Mint/revoke needs scope=admin; runtime keys get 403
Scoped runtime Agent process for one session Read/write its own namespace only One per session means one revoke instantly stops a runaway agent
Unscoped runtime Backend ops jobs (billing, metrics) Walk every namespace Scoped keys can't see other tenants; admin keys can't touch the filesystem
# Backend — provision a session
from trove_sdk import TroveAdminClient

with TroveAdminClient(api_key=ADMIN_KEY, workspace_id=WS_ID) as admin:
    key = admin.create_key(f"session-{user_id}", namespace=f"session-{user_id}")
    # Hand key.api_key to the agent runtime — it can ONLY touch this namespace.

# Agent runtime — single-session
from trove_sdk import TroveClient

with TroveClient(api_key=session_key, namespace=f"session-{user_id}") as fs:
    fs.exec("...")   # confined to session-{user_id}/
    # Pointing at a different namespace returns 403 — the key is scoped.

# Session ends — revoke the scoped key
admin.revoke_key(key.key_id)

A complete runnable example (provisioner + runtime + dashboard) lives in examples/sessions/ — copy it as a starting point.

CLI

A trove command ships in the [cli] extra. After installing, log in once and then drive your workspace from the terminal:

# One-time setup. The CLI calls /v1/me to discover your workspace_id from
# the key, so you only paste one secret. --namespace is optional.
trove login --api-key trove-sk-... --namespace alice

# Save under a non-default profile name:
trove login --save-as staging --api-key trove-sk-...
trove --profile staging tail        # use it later

# Filesystem (mirrors the SDK)
trove run "ls workspace/"          # POST /v1/exec  (exit code propagates!)
trove run --json "build"           # one JSON line: {exit_code,stdout,stderr,...}
echo '{"x":1}' | trove run "jq .x" # piped stdin auto-forwards (1 MB cap)
trove ls workspace/                # GET  /v1/files
trove cat workspace/notes.txt      # GET  /v1/files/content
trove put report.pdf workspace/    # PUT  /files/{path}
trove get workspace/img.png        # GET  /files/{path}  (binary-safe)
trove write workspace/n.txt "hi"   # POST /write
trove rm workspace/old.txt         # POST /delete

# Diagnostics: "why is this CLI hitting the wrong tenant?"
trove doctor                       # version, profile, env, live /v1/me ping

# Activity log (the killer dev flow)
trove tail                         # long-poll the event feed
trove tail -t exec.completed -v    # only exec events, full command + first stdout line
trove events list --since 1h30m    # paged replay (compound durations + ISO timestamps OK)

# Multi-tenant key & webhook management (admin scope required)
trove keys list
trove keys create alice --namespace alice
trove keys revoke key-abc123
trove webhooks create https://api.example.com/trove/events
trove webhooks test wh-xyz

# Snapshots
trove snapshot create --label "before refactor"
trove snapshot list
trove snapshot restore snap-abc123

# MCP server (Claude Desktop, Cursor, Claude Code)
trove mcp install                  # detects clients, writes a 'trove' server entry
trove mcp install --client cursor --namespace alice
trove mcp status                   # which clients have it wired up
trove mcp uninstall

whoami shows the active key's scope and namespace lock so you don't accidentally point a customer-scoped key at someone else's namespace:

$ trove whoami
profile         : default
workspace       : ws-abc123...
scope           : workspace
namespace lock  : alice  (key is scoped  cannot access other namespaces)

Profiles & env vars

  • --profile staging switches between saved logins.
  • TROVE_API_KEY + TROVE_WORKSPACE_ID (and optional TROVE_NAMESPACE, TROVE_BASE_URL) override the saved profile when no --profile is set.
  • Per-command -n/--namespace beats both.

Output

Event timestamps render in your local timezone. Today's events show HH:MM:SS; older events get an MM-DD prefix so the log doesn't look stuck in a single day. --json mode preserves the raw ISO strings for piping into jq or downstream tools.

Usage

Filesystem operations

from trove_sdk import TroveClient

with TroveClient(api_key="trove-sk-...", namespace="alice") as client:
    # Run shell commands
    client.exec("mkdir -p workspace/data")
    output = client.exec("ls workspace/")

    # Structured exec for agent loops — separate stdout/stderr + exit code.
    result = client.exec_detailed("pytest tests/")
    if result.exit_code != 0:
        print("failures on stderr:", result.stderr)

    # Read a text file (1 MB cap; raises on binary).
    notes = client.read_text("workspace/data/notes.txt")

    # Read a binary file (100 MB cap, no encoding).
    png = client.read_bytes("workspace/data/image.png")

    # List a directory.
    for entry in client.list_dir("workspace/data/"):
        print(entry.name, entry.size_bytes)

    # Write a text file
    client.write("workspace/data/notes.txt", "hello world")

    # Upload binary
    with open("image.png", "rb") as f:
        client.upload("workspace/data/image.png", f)

    # Delete
    client.delete("workspace/data/notes.txt")

Persistent shell context (init.sh)

exec is stateless by default — every call gets a fresh shell. If you find yourself prefixing every command with cd workspace/data && source .venv/bin/activate && ..., set a namespace-level init script instead. The server sources it before every /exec call, so cwd, env vars, activated venvs, and shell functions all carry across calls — and across agent process restarts, because the script lives in the namespace volume.

client.set_init("""
cd workspace/data
source .venv/bin/activate
export REPORT_DATE=2026-05-06
""")

client.exec("python analyze.py")    # cwd=workspace/data, venv active, env set
client.exec("pytest tests/")        # same context — no re-setup

client.get_init()                   # → the script text, or None if unset
client.clear_init()                 # → True if removed, False if never set

It's stored at workspace/.trove/init.sh. Snapshots include it, events fire when it changes, and namespace isolation holds. If the script errors at runtime, stderr is interleaved with your command's output but the command still runs — use exec_detailed to see them separately.

Async clients have the same three methods: await client.set_init(...), await client.get_init(), await client.clear_init().

Async

from trove_sdk import AsyncTroveClient

async with AsyncTroveClient(api_key="trove-sk-...", namespace="alice") as client:
    await client.exec("echo hello")
    await client.write("workspace/hello.txt", "hi")

Key management (multi-tenant)

Use an admin key from the dashboard to mint scoped keys per customer:

from trove_sdk import TroveAdminClient

with TroveAdminClient(api_key="trove-sk-admin-...", workspace_id="ws-...") as admin:
    # Mint a scoped key for a customer
    key = admin.create_key("customer-alice", namespace="alice")
    print(key.api_key)  # store this — shown once

    # List active keys
    keys = admin.list_keys()

    # Revoke
    admin.revoke_key(key.key_id)

Webhooks

Subscribe a URL to filesystem and auth events. Trove signs every delivery with HMAC-SHA256; use verify_webhook to validate the signature in your receiver.

Register an endpoint

from trove_sdk import TroveAdminClient

with TroveAdminClient(api_key="trove-sk-admin-...", workspace_id="ws-...") as admin:
    hook = admin.create_webhook(
        url="https://api.example.com/trove/events",
        events=["file.written", "file.deleted", "exec.completed"],
        # namespace="alice",  # optional — only fire for one customer
    )
    print(hook.signing_secret)  # save this — shown once

Available events: file.written, file.deleted, exec.completed, snapshot.created, snapshot.restored, snapshot.deleted, namespace.deleted, workspace.created, key.created, key.revoked, webhook.test. Pass events=["*"] (or omit) to subscribe to all of them, including future ones.

Receive an event (Flask)

import os
from flask import Flask, request, abort
from trove_sdk import verify_webhook, WebhookSignatureError

app = Flask(__name__)
SECRET = os.environ["TROVE_WEBHOOK_SECRET"]

@app.post("/trove/events")
def receive():
    try:
        event = verify_webhook(
            secret=SECRET,
            body=request.get_data(),  # raw bytes — DO NOT use request.json
            signature_header=request.headers["X-Trove-Signature"],
        )
    except WebhookSignatureError:
        abort(400)
    print(f"{event.type}: {event.data}")
    return "", 204

The body argument MUST be the raw request bytes. Re-serializing JSON (e.g. json.dumps(request.json)) reorders keys and invalidates the HMAC.

A minimal subscribe + verify script lives in examples/webhook.py.

API reference

TroveClient(api_key, namespace, *, base_url?)

Method Description
exec(command, *, stdin=None) Run a shell command. Returns stdout as a string (legacy text response).
exec_detailed(command, *, stdin=None) Run a shell command. Returns ExecResult(exit_code, stdout, stderr, duration_ms).
write(path, content) Write a UTF-8 text file. Returns FileResult.
upload(path, data) Upload bytes or a file-like object. Returns FileResult.
read_text(path) Read a UTF-8 text file (1 MB cap). Raises TroveError on binary content.
read_bytes(path) Download a file's raw bytes (100 MB cap). Binary-safe.
read_file(path) Read metadata + content. Returns FileContent (encoding field flags binary).
list_dir(path) List a directory. Returns list[FileInfo].
delete(path) Delete a file or directory. Returns the deleted path.
set_init(text) Write workspace/.trove/init.sh — sourced before every /exec call. Returns FileResult.
get_init() Read the init script. Returns the text, or None if unset.
clear_init() Delete the init script. Returns True if it existed, False otherwise.
create_snapshot(label?) Tar the namespace and store it. Returns Snapshot.
list_snapshots() List snapshots newest-first. Returns list[Snapshot].
restore_snapshot(id) Wipe the namespace and restore. Returns # files restored.
delete_snapshot(id) Delete a snapshot from S3.

AsyncTroveClient mirrors the same interface with async/await.

TroveAdminClient(api_key, workspace_id, *, base_url?)

Method Description
create_key(name, *, namespace?) Mint a new workspace key, optionally scoped to a namespace.
list_keys() List all active keys for the workspace.
revoke_key(key_id) Revoke a key immediately.
create_webhook(url, *, events?, namespace?, description?) Subscribe a URL to events. Returns a WebhookCreated (signing secret shown once).
list_webhooks() List all registered webhook endpoints.
delete_webhook(webhook_id) Remove an endpoint.
test_webhook(webhook_id) Fire a webhook.test event and return the delivery result.

AsyncTroveAdminClient mirrors the same interface with async/await.

verify_webhook(*, secret, body, signature_header, tolerance_seconds=300)

Validates a webhook delivery and returns the parsed WebhookEvent. Raises WebhookSignatureError on bad signature, missing fields, or stale timestamp (default tolerance: 5 minutes). Pass the raw request body — re-serialized JSON will not match the signature.

Errors

All errors raise TroveError(message, status_code). WebhookSignatureError is a subclass raised by verify_webhook.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

trove_sdk-0.7.4.tar.gz (152.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

trove_sdk-0.7.4-py3-none-any.whl (50.8 kB view details)

Uploaded Python 3

File details

Details for the file trove_sdk-0.7.4.tar.gz.

File metadata

  • Download URL: trove_sdk-0.7.4.tar.gz
  • Upload date:
  • Size: 152.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for trove_sdk-0.7.4.tar.gz
Algorithm Hash digest
SHA256 b50e336118d36c420343936a15cc320c9919c97871c8f76e5fa14dd54f499add
MD5 b277626cd907d48a0facd16f46110649
BLAKE2b-256 694dd2cbbe425cdbc79fb78a674278964e432f6de467bb3a176b73b6f77fc08e

See more details on using hashes here.

File details

Details for the file trove_sdk-0.7.4-py3-none-any.whl.

File metadata

  • Download URL: trove_sdk-0.7.4-py3-none-any.whl
  • Upload date:
  • Size: 50.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for trove_sdk-0.7.4-py3-none-any.whl
Algorithm Hash digest
SHA256 c20f0581c2a167e8207866de0d6f9b493fe18def4bebd0c961d6f47beaf2449a
MD5 a9d82594c3acf8aed9ec214fb2cf55b3
BLAKE2b-256 b554837c903adc253eb95aea3d9c0cb83b4554e27e15aab98460d53968664464

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page