Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ dbro mcp --http # streamable H
claude mcp add --transport http dbro http://127.0.0.1:7811/mcp
```

Tools: `list_connections`, `list_databases`, `list_tables`, `describe_table`, `query` (always in a read-only transaction) and `execute` (only where *Allow agents to change data* is on). Agent statements appear in the app's SQL log marked `[mcp]`.
Tools: `list_connections`, `list_databases`, `list_tables`, `describe_table`, `query` (always in a read-only transaction) and `execute` (only where *Allow agents to change data* is on: in Edit for saved connections, in the right-click menu for Lerd projects and services). Agent statements appear in the app's SQL log marked `[mcp]`.

## Headless mode

Expand Down
16 changes: 15 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -192,7 +193,7 @@ func (a *App) SaveThemeAs(name string, colors map[string]string) (string, error)
// SaveSettings stores settings (theme changes go through SetTheme).
func (a *App) SaveSettings(s config.Settings) error {
cur, _ := config.LoadSettings()
s.Theme, s.FirstRunDone, s.OmarchyAsked = cur.Theme, cur.FirstRunDone, cur.OmarchyAsked
s.Theme, s.FirstRunDone, s.OmarchyAsked, s.AgentWrites = cur.Theme, cur.FirstRunDone, cur.OmarchyAsked, cur.AgentWrites
return config.SaveSettings(s)
}

Expand Down Expand Up @@ -880,3 +881,16 @@ func (a *App) Restart() error {
runtime.Quit(a.ctx)
return nil
}

// SetAgentWrites lets agents change data on a lerd connection, or makes it read-only again.
func (a *App) SetAgentWrites(name string, allow bool) error {
s, err := config.LoadSettings()
if err != nil {
return err
}
s.AgentWrites = slices.DeleteFunc(s.AgentWrites, func(n string) bool { return n == name })
if allow {
s.AgentWrites = append(s.AgentWrites, name)
}
return config.SaveSettings(s)
}
15 changes: 15 additions & 0 deletions frontend/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,27 @@ export function connectionMenu(name: string, t: Tree): MenuItem[] {
{ separator: true, label: '' },
{ label: 'New database…', disabled: dialect(name) === 'sqlite', action: () => newDatabase(name) },
{ separator: true, label: '' },
...(manual ? [] : [
c?.allowAgentWrites
? { label: 'Make read-only for agents', action: () => setAgentWrites(name, false) }
: { label: 'Allow agents to change data…', action: () => setAgentWrites(name, true) },
{ separator: true, label: '' },
]),
{ label: 'Edit…', disabled: !manual, action: () => editConnection(name) },
{ label: 'Duplicate…', disabled: !manual, action: () => editConnection(name, true) },
{ label: 'Delete', disabled: !manual, danger: true, action: () => deleteConnection(name) },
];
}

/** setAgentWrites switches MCP writes on a lerd connection; saved connections do it in Edit. */
async function setAgentWrites(name: string, allow: boolean) {
if (allow && !(await confirm('Allow agents to change data', `Agents connected over MCP can then run INSERT, UPDATE, DELETE and schema changes on ${displayName(name)}.`, 'Allow', true))) return;
await Go.SetAgentWrites(name, allow).catch((e) => showError(e, 'Could not save'));
await tree.reload();
if (state.selection?.conn === name) emit('select', state.selection);
toast(`${displayName(name)}: agents ${allow ? 'can change data' : 'are read-only'}`);
}

export function databaseMenu(conn: string, dbName: string, t: Tree): MenuItem[] {
const sel: Selection = { kind: 'database', conn, db: dbName };
return [
Expand Down
2 changes: 2 additions & 0 deletions frontend/wailsjs/go/main/App.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ export function SaveSnippet(arg1:history.Snippet):Promise<void>;

export function SaveThemeAs(arg1:string,arg2:Record<string, string>):Promise<string>;

export function SetAgentWrites(arg1:string,arg2:boolean):Promise<void>;

export function SetTheme(arg1:string):Promise<theme.Theme>;

export function Snippets():Promise<Array<history.Snippet>>;
Expand Down
4 changes: 4 additions & 0 deletions frontend/wailsjs/go/main/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ export function SaveThemeAs(arg1, arg2) {
return window['go']['main']['App']['SaveThemeAs'](arg1, arg2);
}

export function SetAgentWrites(arg1, arg2) {
return window['go']['main']['App']['SetAgentWrites'](arg1, arg2);
}

export function SetTheme(arg1) {
return window['go']['main']['App']['SetTheme'](arg1);
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/wailsjs/go/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export namespace config {
firstRunDone: boolean;
queryTimeout: number;
checkUpdates: boolean;
agentWrites: string[];

static createFrom(source: any = {}) {
return new Settings(source);
Expand All @@ -109,6 +110,7 @@ export namespace config {
this.firstRunDone = source["firstRunDone"];
this.queryTimeout = source["queryTimeout"];
this.checkUpdates = source["checkUpdates"];
this.agentWrites = source["agentWrites"];
}
}

Expand Down
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type Settings struct {
FirstRunDone bool `toml:"first_run_done,omitempty" json:"firstRunDone"`
QueryTimeout int `toml:"query_timeout,omitempty" json:"queryTimeout"` // seconds
CheckUpdates bool `toml:"check_updates" json:"checkUpdates"` // look for a new release on startup
// AgentWrites names the lerd connections agents may change; they cannot be edited like saved ones.
AgentWrites []string `toml:"agent_writes,omitempty" json:"agentWrites"`
}

// Dir returns the config directory, honoring DBRO_CONFIG_DIR and XDG_CONFIG_HOME.
Expand Down
6 changes: 6 additions & 0 deletions internal/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"errors"
"fmt"
"slices"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -80,6 +81,11 @@ func (m *Manager) Connections() ([]config.Connection, []string) {
warn = append(warn, "lerd: "+err.Error())
}
out = append(out, lerd...)
for i := range out {
if out[i].Source == "lerd" || out[i].Source == "lerd-project" {
out[i].AllowAgentWrites = slices.Contains(s.AgentWrites, out[i].Name)
}
}
}
rank := map[string]int{"manual": 0, "laravel": 0, "lerd-project": 1, "lerd": 2}
sort.SliceStable(out, func(i, j int) bool { return rank[out[i].Source] < rank[out[j].Source] })
Expand Down
Loading