Developer

Database API (SQLite)

Per-resource SQLite database for structured data storage.

Each resource gets its own SQLite database for structured data storage. The database is created automatically on first use - just start executing SQL.

Capability required: database:resource (plain) or database:resource:encrypted (SQLCipher encrypted)

Frontend (iframe)

import { createResourceClient } from "@rightplace/sdk";

const rp = createResourceClient();
await rp.ready();

// Create a table
await rp.db.execute(`
  CREATE TABLE IF NOT EXISTS notes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    content TEXT,
    created_at INTEGER DEFAULT (unixepoch())
  )
`);

// Insert data with parameters
await rp.db.execute(
  "INSERT INTO notes (title, content) VALUES (?1, ?2)",
  ["My First Note", "Hello world!"]
);

// Query data
const notes = await rp.db.query("SELECT * FROM notes ORDER BY created_at DESC");
// → [{ id: 1, title: "My First Note", content: "Hello world!", created_at: 1713250000 }]

// Query with parameters
const results = await rp.db.query(
  "SELECT * FROM notes WHERE title LIKE ?1",
  ["%First%"]
);

// Update
const result = await rp.db.execute(
  "UPDATE notes SET content = ?1 WHERE id = ?2",
  ["Updated content", 1]
);
console.log(result.rowsAffected); // 1

// Delete
await rp.db.execute("DELETE FROM notes WHERE id = ?1", [1]);

Backend (Node.js)

import { createResourceServer } from "@rightplace/sdk/server";

const server = createResourceServer({
  onInit: async ({ rp }) => {
    await rp.db.execute(`
      CREATE TABLE IF NOT EXISTS items (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        price REAL,
        in_stock INTEGER DEFAULT 1
      )
    `);
  },

  methods: {
    addItem: async (params, { rp }) => {
      const result = await rp.db.execute(
        "INSERT INTO items (name, price) VALUES (?1, ?2)",
        [params.name, params.price]
      );
      return { id: result.lastInsertId };
    },

    getItems: async (_params, { rp }) => {
      return rp.db.query("SELECT * FROM items WHERE in_stock = 1 ORDER BY name");
    },
  },
});

server.start();

MCP

The same SQLite store is reachable from agents through three MCP tools. All of them take a resourceId so the call is scoped to one resource’s database.

ToolPurposePermission
rightplace_db_queryRun a SELECT and return rowsdatabase:read
rightplace_db_executeRun INSERT / UPDATE / DELETE / DDLdatabase:write
rightplace_db_list_tablesList tables + column schemadatabase:read
{
  "name": "rightplace_db_query",
  "arguments": {
    "resourceId": "res_abc",
    "sql": "SELECT id, title FROM notes WHERE title LIKE ?1",
    "params": ["%hello%"]
  }
}

RobinPath Bridge

From a script:

# DDL
rightplace.db_execute {
  resourceId: "res_abc",
  sql: "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, title TEXT, body TEXT)"
}

# Write
rightplace.db_execute {
  resourceId: "res_abc",
  sql: "INSERT INTO notes (title, body) VALUES (?1, ?2)",
  params: ["hello", "world"]
} into $res
log "inserted id:" $res.lastInsertId

# Read
rightplace.db_query {
  resourceId: "res_abc",
  sql: "SELECT id, title FROM notes ORDER BY id DESC LIMIT ?1",
  params: [10]
} into $rows
for $r in $rows
  log $r.id $r.title
endfor

# Schema discovery
rightplace.db_list_tables {resourceId: "res_abc"} into $tables

API Reference

rp.db.execute(sql, params?)

Execute a SQL statement (INSERT, UPDATE, DELETE, CREATE TABLE, etc.).

ParameterTypeDescription
sqlstringSQL statement
paramsunknown[] (optional)Positional parameters (?1, ?2, …)
ReturnsPromise<{ rowsAffected: number, lastInsertId: number }>

rp.db.query(sql, params?)

Execute a SELECT query and return rows.

ParameterTypeDescription
sqlstringSQL SELECT statement
paramsunknown[] (optional)Positional parameters (?1, ?2, …)
ReturnsPromise<Record<string, unknown>[]>Array of row objects

Each row is an object with column names as keys.

Parameter Types

JavaScript TypeSQLite Type
stringTEXT
number (integer)INTEGER
number (float)REAL
booleanINTEGER (1 or 0)
nullNULL

Encrypted Databases

Use the database:resource:encrypted capability for sensitive data. The database is encrypted with SQLCipher using the user’s encryption key.

{
  "capabilities": [
    "database:resource:encrypted"
  ]
}

The API is identical - encryption is transparent. The only difference is that the database file on disk is encrypted and cannot be opened with standard SQLite tools.

Plain Databases

Use database:resource for non-sensitive data. The database is a standard SQLite file.

{
  "capabilities": [
    "database:resource"
  ]
}

Storage Location

The database is stored at:

~/.rightplace/{user_id}/resources/{manifest_id}/resource.db

One database per resource. Use tables for logical separation. The database persists across app restarts and resource updates. It is deleted when the resource is uninstalled.

Tips

  • Use CREATE TABLE IF NOT EXISTS - your resource may be opened multiple times
  • Use parameterized queries (?1, ?2) - never concatenate user input into SQL
  • Use INTEGER PRIMARY KEY AUTOINCREMENT for auto-incrementing IDs
  • SQLite supports JSON functions: json(), json_extract(), json_array()
  • Use unixepoch() for timestamps