fastdata
Guide

Connectors

A connector runs SQL and returns rows. It is the only part that touches your database, and it holds the credentials: fastdata sends it a query and gets data back, nothing else.

Without a connector, fastdata runs in manual mode.

Every query is checked by fastdata's read-only guard before it reaches the connector. Still, always connect with a database role that can only read; see Security.

Choosing one

You have…Use
a Postgres database you can reachbuilt-in Postgres executor
a SQLite filebuilt-in SQLite executor
MySQL, SQL Server, MariaDB, Oracle, …any MCP SQL server, e.g. DBHub
a database only reachable inside another networka remote MCP server running there
an existing "execute SQL" REST API (e.g. from a Custom GPT)the HTTP connector

Built-in Postgres

connector:
  type: mcp
  command: fastdata
  args: ["connector", "postgres"]
  env:
    DATABASE_URL: ${MY_DB_URL}
# .env next to fastdata.yaml
MY_DB_URL=postgres://readonly_user:password@db.example.com:5432/analytics
  • The connection string goes only into the connector process's environment. It is not in fastdata.yaml and does not show up in process listings.
  • Every query runs in a READ ONLY transaction with a statement timeout and is rolled back. The timeout is limits.query_timeout_s from fastdata.yaml.
  • Options (add them to args): --url-env <NAME> (default DATABASE_URL), --max-rows <n> (default 10000), --timeout-ms <n> (default 30000, used when a caller sends no timeout).

Create a read-only role once (as an admin):

CREATE ROLE fastdata_ro LOGIN PASSWORD '…';
GRANT CONNECT ON DATABASE analytics TO fastdata_ro;
GRANT USAGE ON SCHEMA public TO fastdata_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO fastdata_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO fastdata_ro;
ALTER ROLE fastdata_ro SET default_transaction_read_only = on;
ALTER ROLE fastdata_ro SET statement_timeout = '30s';

Grant SELECT only on the schemas and tables people should be able to ask about.

Built-in SQLite

connector:
  type: mcp
  command: fastdata
  args: ["connector", "sqlite", "./data/app.db"]

The file is opened read-only. Relative paths are resolved from the folder containing fastdata.yaml. Option: --max-rows <n> (default 10000).

Any MCP SQL server

Any MCP server that has a tool for running SQL works. fastdata starts it, lists its tools and picks the SQL tool automatically.

connector:
  type: mcp
  command: npx
  args: ["-y", "@bytebase/dbhub", "--transport", "stdio", "--dsn", "${DATABASE_URL}"]
KeyMeaning
command, argsHow to start the server (stdio). Relative paths in args resolve from the config folder.
envEnvironment variables for the server. Only these (plus basics such as PATH and HOME) are passed; your shell's other variables are not, so secrets don't leak to servers that don't need them.
toolName of the tool that runs SQL. Optional: detected automatically (execute_sql, run_query, query, or a name containing sql/query/execute).
sql_argName of the tool argument that takes the SQL. Optional: detected (sql, query, sql_query, statement, or the only string argument).
extra_argsConstant arguments sent with every call, e.g. { database: "prod" }.

Run fastdata doctor to see which tools the server offers and which one fastdata picked. If the guess is wrong, set tool and sql_arg.

Remote MCP server (HTTP)

Run the SQL server inside the network where the database lives, and point fastdata at it:

connector:
  type: mcp
  url: https://sql-executor.internal.example.com/mcp
  headers:
    Authorization: "Bearer ${EXECUTOR_TOKEN}"

Uses the MCP streamable HTTP transport. Set exactly one of command or url.

HTTP endpoint

For an existing API that accepts SQL in a JSON body and returns rows:

connector:
  type: http
  url: https://dataselfserviceapi.example.com/execute-sql-and-fetch-data
  headers:
    Authorization: "Bearer ${EXECUTOR_TOKEN}"
  sql_field: sql_query            # default: sql
  extra_body:                     # optional constant fields
    format: json

fastdata sends POST url with { "<sql_field>": "<query>", ...extra_body }. Any non-2xx response is reported to the agent as an error, with the response body.

What a connector may return

fastdata recognizes the common shapes, whether they come as MCP structured content, MCP text or an HTTP body:

  • an array of objects: [{"country": "IT", "revenue": 17558.2}, …]
  • columns and rows: {"columns": ["country", "revenue"], "rows": [["IT", 17558.2]]} (fields / headers also work for column names, as do column objects with a name)
  • either of these nested in an envelope, e.g. {"success": true, "data": {"rows": […]}}; the first array under rows, data, results, result, records, items or values is used
  • CSV, TSV or a Markdown table as text

"truncated": true (or has_more) anywhere tells fastdata the result was cut off. An MCP tool error (isError) is shown to the agent, which usually fixes the query and tries again.

Writing your own executor

The built-in executors are about 50 lines each and make a good starting point: src/connector/postgres-server.ts and src/connector/serve.ts. One tool, execute_sql({ sql }), that returns { columns, rows, truncated } as JSON text is all fastdata needs. If the tool also takes max_rows and timeout_ms, fastdata sends limits.max_rows and limits.query_timeout_s with every call.