An account lookup doesn't need a new SQL query every time. If the agent already knows the account name, you can give it an account_by_name tool and keep the table, returned columns, and row limit in SQL you control.
Google MCP Toolbox's postgres-sql tool supports that setup with prepared statements and bound parameters. In our Toolbox 1.10.0 example, the agent supplies a required string called name. The query returns account IDs and names, up to two matches. There is no generic SQL tool in the exposed catalog.
That last detail deserves a check: adding a named lookup won't remove a generic query tool you've already exposed. We'll verify the catalog as well as the lookup's inputs. Datatape publishes this tutorial and offers hosted infrastructure for SQL tools.
Define the source and the reviewed query
This configuration comes from the runnable example. It connects a restricted Postgres login to one named tool. database is the runner's Docker network alias and fixture-only is a public lab password; use your own connection details outside the fixture.
kind: source
name: lab
type: postgres
host: database
port: 5432
database: lab
user: tenant_a
password: fixture-only
---
kind: tool
name: account_by_name
type: postgres-sql
source: lab
description: Find a synthetic account by its exact name
statement: SELECT id, name FROM allowed.accounts WHERE name = $1 ORDER BY id LIMIT 2
parameters:
- name: name
type: string
description: Exact account name
You choose the table, output columns, ordering, and row limit when you write the query. The agent supplies the lookup value. $1 receives the first configured parameter. Toolbox's Postgres SQL reference explains positional binding and distinguishes it from templateParameters, which can alter SQL structure before execution.
Use a bound value for an account name. A table name or arbitrary WHERE clause isn't the same kind of input. Review those separately: they can change the SQL the tool runs.
The database login is still restricted. A reviewed query can expose too much data if its author selects private columns or uses an overprivileged source. The permissions tutorial explains the grants used here.
Check the actual MCP catalog
After MCP initialization, send tools/list. The runner does this through the Python MCP SDK before making any calls. In this configuration, Toolbox returned one tool: account_by_name, with a required string parameter named name.
The successful call uses these MCP method parameters:
{
"name": "account_by_name",
"arguments": {
"name": "Acme"
}
}
Pass that object as params in a tools/call request through an initialized MCP session. The recorded response contains:
{"id":1,"name":"Acme"}
Then try calling execute_sql explicitly. Our server returned an unknown-tool error, confirming that this configuration didn't leave that generic query path available. Check both tools/list and the attempted call in your own deployment.
Exercise inputs the happy path misses
The lab records these calls under parameter-missing, parameter-type, and parameter-sql-string:
| Input | Observed result in Toolbox 1.10.0 |
|---|---|
{"name":"Acme"} | Acme's ID and name |
{} | Required parameter error |
{"name":7} | String type error |
{"name":"Acme' OR '1'='1"} | No rows |
The SQL-like input is treated as the account name to find. No synthetic account has that exact name, so the query returns no rows. This verifies binding for the tested query and input; it isn't a comprehensive injection audit.
No rows is also a valid result for an ordinary unknown account. Clients should distinguish an empty successful result from a validation error or database failure.
Decide what the tool is allowed to answer
This lookup accepts a name and returns up to two matching records. Names aren't necessarily unique, so the limit doesn't guarantee a single account. If the caller already has a stable account ID, a tool keyed by that ID may provide a clearer contract.
Likewise, a tenant_id parameter supplied by an agent isn't tenant authorization. The trusted caller identity and database access policy must decide which tenant the caller may query. Keep that decision separate from the agent's choice of lookup value.
When the query changes, review its output columns, joins, filters, inputs, and limits together. Repeat the catalog and input tests after deployment. A renamed tool or an added generic query path changes what an existing client can do.
Run the example
Download the fixture and runner. With Docker running and Python 3.11 or later, run these commands from the extracted directory:
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python run.py --output ./results
.venv/bin/python verify.py ./results
The runner pins the server images by digest, creates a synthetic database, and tests both generic and custom-only configurations. For this example, inspect toolbox-curated-tenant_a.yaml and toolbox-curated-tenant_a.json in the output directory. The recorded evidence contains the same files from our runs.
Where Datatape fits
You can run this pattern with Toolbox today. DBHub also supported a custom-only catalog in our comparison lab, so predefined parameterized SQL isn't a Datatape-only capability.
Toolbox suits teams that want to define and operate the server themselves. If you're evaluating Datatape's hosted workflow, start with the SQL template documentation and parameter reference. Check who owns database credentials, tool changes, caller access, and endpoint operations before choosing the hosting model.