Connect MySQL
Tasks query MySQL through qbash.integrations.mysql with a database user you save on the Connectors page. The connector works with MySQL 5.7 and later and with MariaDB, including managed services such as Amazon RDS, Aurora, Google Cloud SQL and Azure Database for MySQL.
After you connect MySQL, you can add its tools to a qbash MCP server for ChatGPT, Claude and other MCP clients, or call it from a Python task. The MySQL MCP server page lists what the connector can do.
Every statement runs with the rights of that user. A user granted only SELECT cannot change data, whatever SQL a task sends.
Create a read-only user
Connect to the server as an administrator and create a user for qbash. Replace shop with your database name and choose a long random password.
CREATE USER 'qbash_reader'@'%' IDENTIFIED BY 'a-long-random-password';
GRANT SELECT ON shop.* TO 'qbash_reader'@'%';
'%' lets the user sign in from any address. To limit it to qbash, create one user per address in Let qbash reach the server in place of '%'. To report across several databases, repeat the GRANT for each one. Grant INSERT, UPDATE or DELETE on specific tables only if a task needs to write.
On Amazon RDS, Cloud SQL and Azure, run the same statements as the admin user the service created for you.
Let qbash reach the server
The server must accept connections on a public address. qbash cannot reach it through a private or loopback address, such as 10.0.0.5, 192.168.1.20 or localhost. For a managed database, turn on public access and add an inbound rule for the MySQL port, which is 3306 unless you changed it.
If the firewall admits only known addresses, add these to the inbound rule:
69.55.55.112157.230.11.123.131.208.66
qbash uses these addresses for every connection once the credential has Requires static egress IP selected.
Add the user in qbash
- Open Connectors, then MySQL, and click Connect.
- Enter a credential name, then the host, port, database, username and password. The database you enter is the default for
list_tablesanddescribe_table. - Choose an SSL Mode:
- Required encrypts the connection without checking the server's certificate. It is the default.
- Verify CA also checks that the certificate was issued by a trusted authority.
- Verify CA and host name checks the authority and that the certificate names the host you entered.
- Disabled sends traffic, including the password, unencrypted. Use it only for a server that does not support TLS.
- For the two verify modes, paste your provider's CA certificate, such as the Amazon RDS certificate bundle, into CA Certificate in PEM format. Without one, qbash checks against public certificate authorities, which a self-signed server certificate fails.
- Select Requires static egress IP if your firewall allows only the addresses above, and save.
- In the task editor, open Resources → Integrations, and select the credential. See Connect resources.
Query MySQL from a task
report = qbash.integrations.mysql.execute_query(
query="""SELECT order_id, total, created_at
FROM orders
WHERE status = ? AND shipped_at IS NULL
ORDER BY created_at
LIMIT 25""",
query_params=["paid"],
)
for row in report["result"]:
qbash.log(f'{row["order_id"]} {row["total"]} {row["created_at"]}')
Bind values with ? placeholders and query_params rather than formatting them into the SQL string. output_format also accepts single_value, which returns the first column of the first row, and columns_and_rows, which returns column names and rows as lists.
To find table and column names, call list_schemas() for the databases the user can see, list_tables(schema=None) for the tables and views in one, and describe_table(table="orders") for its columns, indexes, foreign keys and check constraints. schema defaults to the database on the credential. The SDK Reference lists every method and parameter.
Run several statements on one connection
qbash.integrations.mysql.connect() opens a direct connection with the same credential, using PyMySQL. Use it when a script needs several statements in one transaction. Statements inside with db: commit together, or roll back together if one raises. DDL statements such as CREATE TABLE commit on their own in MySQL, and MyISAM tables do not roll back. This connection uses %s placeholders rather than ?.
db = qbash.integrations.mysql.connect()
paid = db.scalar("SELECT COUNT(*) FROM orders WHERE status = %s", ("paid",))
with db:
db.execute("UPDATE orders SET status = %s WHERE order_id = %s", ("held", 1042))
db.execute("INSERT INTO order_notes (order_id, note) VALUES (%s, %s)", (1042, "Held for review"))
The UPDATE and INSERT above need a user with write grants on those tables.
Add test data
Create a small table in a test database and grant the qbash user SELECT on that database:
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
status VARCHAR(20) NOT NULL,
total DECIMAL(10, 2) NOT NULL,
created_at DATETIME NOT NULL,
shipped_at DATETIME NULL
);
INSERT INTO orders (status, total, created_at, shipped_at) VALUES
('paid', 189.50, '2026-09-28 09:00:00', NULL),
('paid', 42.00, '2026-09-30 14:10:00', NULL),
('paid', 75.25, '2026-09-25 11:30:00', '2026-09-26 08:00:00');
Details that affect results
execute_queryruns one statement per call. The server refuses a second statement after a semicolon, so split the work across calls or useconnect().DECIMALvalues come back as strings, such as"189.50", to keep their precision. Convert them withfloat()orDecimal()before doing arithmetic.DATE,DATETIME,TIMESTAMPandTIMEvalues come back as ISO 8601 strings.TIMESTAMPvalues are in the session time zone and carry no offset. ATIMEthat is negative or 24 hours or more fails the call, so select it asCAST(duration AS CHAR).JSONcolumns come back as dicts or lists on MySQL. MariaDB storesJSONasLONGTEXT, so there they come back as JSON text that needs parsing before you index into it.TINYINT(1)columns come back as0or1rather thanTrueorFalse, andBITcolumns come back as integers unless their width is a multiple of 8, such asBIT(8). Those come back as raw bytes, so selectflags + 0to get the integer.- With the default
json_rowsformat,row_countis the number of rows returned, so anUPDATEreports 0 even when rows changed. Useoutput_format="columns_and_rows"to get MySQL's count of affected rows. read_only=Trueruns the statement in a read-only transaction that is always rolled back. Test runs and the task assistant always runexecute_querythis way, so a write in a test run changes nothing.list_tablesanddescribe_tablereturn empty lists for a database or table that does not exist or that the user cannot read. Treat emptycolumnsfromdescribe_tableas not found.- Check constraints are listed only on MySQL 8.0.16 and later and on MariaDB 10.2 and later. Older servers accept them in
CREATE TABLEbut do not store them. retry_attemptsretries refused or timed-out connections up to 4 times, waiting 15 to 60 seconds between attempts. A query that fails is never retried.