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 reach | built-in Postgres executor |
| a SQLite file | built-in SQLite executor |
| MySQL, SQL Server, MariaDB, Oracle, … | any MCP SQL server, e.g. DBHub |
| a database only reachable inside another network | a 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.yamland does not show up in process listings. - Every query runs in a
READ ONLYtransaction with a statement timeout and is rolled back. The timeout islimits.query_timeout_sfromfastdata.yaml. - Options (add them to
args):--url-env <NAME>(defaultDATABASE_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}"]
| Key | Meaning |
|---|---|
command, args | How to start the server (stdio). Relative paths in args resolve from the config folder. |
env | Environment 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. |
tool | Name of the tool that runs SQL. Optional: detected automatically (execute_sql, run_query, query, or a name containing sql/query/execute). |
sql_arg | Name of the tool argument that takes the SQL. Optional: detected (sql, query, sql_query, statement, or the only string argument). |
extra_args | Constant 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/headersalso work for column names, as do column objects with aname) - either of these nested in an envelope, e.g.
{"success": true, "data": {"rows": […]}}; the first array underrows,data,results,result,records,itemsorvaluesis 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.