MCP Tools
The four tools faz exposes over MCP — list_databases, describe_table, query_simple, query — with input and output shapes.
When faz mcp runs (spawned by Claude Desktop, Cursor, Claude Code, or any MCP client), it advertises four tools. Your AI assistant calls these to discover databases, inspect schemas, and run queries through the safety pipeline.
The contract is identical to faz's REST endpoints — same Pydantic schemas, same response shapes, same audit log. The only difference is the transport string (mcp/stdio vs rest/local).
Tool overview
| Tool name | Purpose | REST equivalent |
|---|---|---|
list_databases | Discover configured databases, their reachability, and tables/columns. | GET /v1/databases |
describe_table | Inspect one table's schema in detail. | GET /v1/databases/{db}/tables/{table} |
query_simple | Run a single-database query through the safety pipeline. | POST /v1/query/simple |
query | Run a multi-step federated query across databases. | POST /v1/query |
list_databases
Returns paginated database descriptions including discovered table and column metadata.
{
"page": 1,
"size": 4
}| Field | Type | Default | Notes |
|---|---|---|---|
page | integer | 1 | 1-indexed page number. |
size | integer | 4 | Databases per page. Max 100. Default is small to keep the response under typical MCP content-length limits. |
{
"databases": [
{
"name": "<database>",
"type": "postgresql",
"database": "<db-name>",
"reachable": true,
"tables": [
{
"name": "<table>",
"fields": [
{
"name": "<column>",
"data_type": "integer",
"nullable": false,
"is_primary": true,
"is_foreign_key": false,
"description": "",
"distribution": null
}
],
"row_count_estimate": 8421,
"description": ""
}
],
"error": null
}
],
"pagination": {
"page": 1,
"size": 4,
"total_databases": 2,
"total_pages": 1,
"has_more": false
}
}If a database failed to connect at startup, its entry has reachable: false and the connection error in error. The response always includes the database in the list — the agent can decide whether to skip or retry.
Pagination protects against blowing the MCP message size limit when one database has thousands of tables. Loop until pagination.has_more is false.
describe_table
Drill into one table after list_databases finds it.
{
"database": "<database>",
"table": "<table>"
}| Field | Type | Required | Notes |
|---|---|---|---|
database | string | yes | Connector name from faz.yaml. |
table | string | yes | Table / collection / index / class / label name. |
{
"name": "<table>",
"fields": [
{
"name": "<column>",
"data_type": "integer",
"nullable": false,
"is_primary": true,
"is_foreign_key": false,
"description": "",
"distribution": {
"distinct_count": 8421,
"null_fraction": 0.0,
"sample_values": [1, 2, 3, 4, 5],
"min_value": 1,
"max_value": 8421
}
}
],
"row_count_estimate": 8421,
"description": ""
}distribution is populated for connectors with statistics support (Postgres pg_stats, MySQL information_schema). Returns null for connectors without it (most NoSQL).
query_simple
Run one query against one database. This is the primary tool for read and write operations.
{
"database": "<database>",
"table": "<table>",
"language": "sql",
"query": "SELECT * FROM <table> LIMIT 5"
}| Field | Type | Required | Notes |
|---|---|---|---|
database | string | yes | Connector name from faz.yaml. |
table | string | yes | Declared target table. Used by the safety pipeline for permission lookup. |
language | string | null | no | sql, mql, cypher, es_dsl, vector, dynamo, couchdb. Inferred from connector when omitted. |
query | string | yes | Native query body. Format depends on language. |
Query format per language
- SQL (
postgresql,mysql,oracle,cassandra) — a plain SQL string. Multi-statement queries are blocked at the connector level; submit one statement per call. - MQL (
mongodb) — a JSON string with anoperationfield plus payload. Examples:{"operation": "find", "filter": {"status": "active"}, "limit": 10}{"operation": "insertOne", "document": {"id": 21, "name": "X"}}{"operation": "aggregate", "pipeline": [{"$match": {...}}, {"$group": {...}}]}- Operations:
find,findOne,aggregate,count,distinct,insertOne,insertMany,updateOne,updateMany,replaceOne,deleteOne,deleteMany.
- CYPHER (
neo4j) — a Cypher string. - ES_DSL (
elasticsearch,opensearch) — JSON. For_search, just the body. For other paths:{"method": "GET", "path": "/idx/_search", "body": {...}}. - DYNAMO (
dynamodb) — JSON like{"verb": "Scan", "TableName": "X", ...}. PartiQL also supported viaverb: "ExecuteStatement". - VECTOR (
weaviate,qdrant,milvus,pinecone) — JSON like{"intent": "search", "vector": [...], "limit": 10, ...}. Intents per connector are documented on each connector page. - COUCHDB — JSON like MQL:
{"operation": "find", "selector": {...}, "limit": 10}.
Output (success)
{
"request_id": "req_a1b2c3d4e5f6",
"status": "ok",
"data": {
"columns": ["<column-1>", "<column-2>"],
"rows": [
{ "<column-1>": "<value>", "<column-2>": "<value>" },
{ "<column-1>": "<value>", "<column-2>": "<value>" }
],
"row_count": 2
},
"safety": {
"stages_passed": ["PROMPT_GUARD", "RBAC_GATE", "AST_CHECKER", "INJECTION_ANALYSER", "GUARDRAILS"],
"warnings": []
},
"metadata": {
"database": "<database>",
"execution_time_ms": 23.4,
"transport": "mcp/stdio",
"steps": [],
"merge_latency_ms": null
}
}Output (blocked)
{
"request_id": "req_a1b2c3d4e5f6",
"status": "blocked",
"error": {
"stage": "RBAC_GATE",
"reason": "Access denied: <database>.<sensitive-table> requires permission for SELECT; policy grants none",
"suggestion": null
}
}The agent receives stage and reason and can choose to rephrase, ask the user, or back off. See Query blocked for stage-by-stage diagnoses.
query (federated)
Multi-step federated query across multiple databases. Each step runs independently against its own connector; results land in DuckDB tables s0, s1, ... and an optional merge SQL joins them.
{
"steps": [
{
"step_id": "s0",
"database": "<database-1>",
"table": "<table-1>",
"language": "sql",
"query": "SELECT <link-column>, <other-column> FROM <table-1> WHERE <condition>",
"depends_on": [],
"link_from": null,
"link_to": null
},
{
"step_id": "s1",
"database": "<database-2>",
"table": "<collection>",
"language": "mql",
"query": "{\"operation\": \"find\", \"filter\": {}}",
"depends_on": ["s0"],
"link_from": "<link-column>",
"link_to": "<foreign-key>"
}
],
"merge": "SELECT s1.<column>, s0.<other-column> FROM s0 JOIN s1 ON s0.<link-column> = s1.<foreign-key>"
}| Field | Type | Required | Notes |
|---|---|---|---|
steps | list | yes | At least one. Each is the same shape as a query_simple request, plus DAG fields. |
merge | string | no | DuckDB SELECT/CTE referencing s0, s1, ... Required when 2+ steps produce data. |
Step fields
| Field | Type | Required | Notes |
|---|---|---|---|
step_id | string | yes | Unique within request. By convention "s0", "s1", etc. |
database | string | yes | Connector name from faz.yaml. |
table | string | yes | Declared target table. |
language | string | null | no | Same as query_simple. |
query | string | yes | Native query body. |
depends_on | list | no | Other step_ids this step waits for. Strict topological order — no forward refs. |
link_from | string | null | no | Field to extract from the dependency's results. |
link_to | string | null | no | Field in this step's query to filter on, using values extracted via link_from. |
Output
Same envelope as query_simple, with metadata.steps populated:
{
"metadata": {
"execution_time_ms": 78.3,
"transport": "mcp/stdio",
"steps": [
{ "step_id": "s0", "database": "<database-1>", "rows_returned": 42, "latency_ms": 23.0 },
{ "step_id": "s1", "database": "<database-2>", "rows_returned": 42, "latency_ms": 31.5 }
],
"merge_latency_ms": 11.2
}
}For the full conceptual model, see Federated queries.
Errors versus blocks
The MCP tool surface returns errors (problems with the request shape) and blocks (decisions made by the safety pipeline) differently:
- Block — the safety pipeline rejected the query. Output has
status: "blocked"plus theerrorobject withstageandreason. The agent should readreasonand adjust. - Validation error — the request body didn't parse (wrong type, missing field). Output has
status: "error"with a singleerrorstring. The agent should fix the request shape. - Server error — something downstream failed (connector unreachable mid-query, malformed merge SQL). Output has
status: "error"with the underlying message.
Block envelopes are quoted to the LLM verbatim, including any suggestion field, so the assistant can read what to do next.
Related
- REST endpoints — same contract over HTTP.
- Federated queries — the conceptual model behind
query. - Integrations overview — how to connect MCP clients to faz.
- How faz protects you — what the pipeline runs on every query call.