TablePro: A Native macOS Database Client Built on a Plugin Protocol

TablePro: A Native macOS Database Client Built on a Plugin Protocol

TablePro is a native macOS database client — MySQL, PostgreSQL, SQLite, MongoDB, Redis, DuckDB, and more — built in Swift with SwiftUI and AppKit. It ships as a free download and opens in under a second.

Why I starred it

The database GUI space is dominated by Electron apps with the memory footprint to match, or paid tools that cost $100+/year. TablePlus (the obvious comparison) is proprietary. TablePro is the open-source answer that doesn't look like it was ported from 2010.

What caught me was the architecture: every database driver is a plugin implementing a Swift protocol. The app doesn't hardcode MySQL or PostgreSQL — it loads drivers at runtime from signed .bundle packages. That's a real design commitment, not just a "plugin system" mentioned in the README.

How it works

The foundation is DatabaseDriver, a protocol in TablePro/Core/Database/DatabaseDriver.swift that every driver must satisfy:

protocol DatabaseDriver: AnyObject {
    var connection: DatabaseConnection { get }
    var status: ConnectionStatus { get }

    func connect() async throws
    func execute(query: String) async throws -> QueryResult
    func executeParameterized(query: String, parameters: [Any?]) async throws -> QueryResult
    func fetchTables() async throws -> [TableInfo]
    func fetchAllColumns() async throws -> [String: [ColumnInfo]]
    func fetchAllForeignKeys() async throws -> [String: [ForeignKeyInfo]]
    func fetchApproximateRowCount(table: String) async throws -> Int?
    // ... ~30 more methods
}

The fetchAllColumns() and fetchAllForeignKeys() signatures are worth noting — they explicitly avoid N+1 queries by requiring a single batch fetch for the full schema. There's a default implementation that falls back to per-table calls, but the intention is clear in the comments: "Avoids N+1."

Driver plugins live in Plugins/ as standalone Swift packages — MySQLDriverPlugin, PostgreSQLDriverPlugin, DuckDBDriverPlugin, and so on. The public contract they implement is in Plugins/TableProPluginKit/DriverPlugin.swift:

public protocol DriverPlugin: TableProPlugin {
    static var databaseTypeId: String { get }
    static var databaseDisplayName: String { get }
    static var defaultPort: Int { get }
    static var parameterStyle: ParameterStyle { get }

    func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver
}

The parameterStyle field is the kind of detail that matters in practice: MySQL uses ? placeholders, PostgreSQL uses $1, $2. The protocol makes that explicit instead of burying it in driver-specific SQL string construction.

The app ships an MCP server — TablePro/Core/MCP/ — that exposes the database as a set of tools any MCP-compatible AI client can call. The tool list is comprehensive: execute_query, describe_table, list_tables, get_table_ddl, export_data, confirm_destructive_operation. The last one is notable: destructive queries (DROP, TRUNCATE, ALTER...DROP) require a two-step confirmation call before execution. The auth policy in MCPAuthPolicy.swift enforces this at the protocol level, not as a UI preference.

Port allocation for the MCP server is done by probing with a raw socket bind:

private static func probe(port: UInt16) -> Bool {
    let fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
    guard fd >= 0 else { return false }
    defer { close(fd) }

    var reuse: Int32 = 1
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, ...)

    var addr = sockaddr_in()
    addr.sin_port = port.bigEndian
    addr.sin_addr.s_addr = INADDR_LOOPBACK.bigEndian

    return bind(fd, ...) == 0
}

Straightforward, but it means the server binds localhost only. That matters if you want to reach it from a container or VM — you can't.

The change tracking layer in Core/ChangeTracking/SQLStatementGenerator.swift generates parameterized statements from in-grid edits. When you modify a cell in the results view, it doesn't build a string like UPDATE t SET col = 'value' WHERE id = 1. It builds UPDATE t SET col = $1 WHERE id = $2 and binds the values separately. The comment at the top of the file says exactly why: "Uses prepared statements instead of string escaping to prevent SQL injection." That's a non-obvious choice for a UI grid editor — most tools just escape the string.

The AI integration supports Anthropic, OpenAI-compatible endpoints, Gemini, and Ollama. Core/AI/AnthropicProvider.swift implements the streaming API using URLSession.bytes and iterates over SSE lines with bytes.lines — no third-party streaming library.

Using it

brew install --cask tablepro

The MCP server is optional and off by default. You enable it in Settings and connect Claude Desktop, VS Code, or Cursor by adding a config entry pointing to the stdio bridge that TablePro writes to ~/Library/Application Support/TablePro/mcp-bridge.json.

Once connected, you can run queries from your AI client:

list all tables in my_connection
 [users, orders, products, sessions, ...]

execute SELECT COUNT(*) FROM orders WHERE created_at > '2026-01-01'
 48291

The confirm_destructive_operation guard means a hallucinated DROP TABLE hits a wall before touching your data.

Rough edges

Test coverage is thin. The TableProTests/Database/ directory contains one file: ConnectionStringParserTests.swift. The driver plugins themselves have no tests in the repo. For a tool managing writes to production databases, that's a gap.

The MCP server binds to localhost only (INADDR_LOOPBACK), so remote AI clients or Docker-based setups can't reach it without an SSH tunnel or proxy. There's no documented workaround.

The plugin system requires code signing — PluginManager+Validation.swift validates signatures before loading. That's the right call for security, but it means third-party drivers have friction to distribute. Currently every driver ships from the first-party plugin registry.

The AGPL-3.0 license with a required CLA for contributors is a specific combination worth reading before forking for commercial use. The "free with paid premium features" model is also still forming — the website sells licenses, but what exactly they unlock isn't clear from the source.

Bottom line

If you're on macOS and tired of Electron database clients eating 400 MB of RAM for a SELECT statement, TablePro is worth installing. The plugin architecture is sound, the MCP integration is genuinely useful for AI-assisted data work, and the parameterized write layer shows that someone thought carefully about the non-obvious parts.

TableProApp/TablePro on GitHub
TableProApp/TablePro