DB-TASK-Filtering Data

Hi everyone!
Find all movies where the special features are not listed (i.e.,special_features is NULL).
This query selects all movies where special_features column has no value (NULL).

SELECT * FROM film WHERE special_features IS NULL;

Find all movies where the rental duration is more than 7 days.
selects movies whose rental duration is greater than 7 days.

SELECT * FROM film WHERE rental_duration > 7;

Find all movies that have a rental rate of $4.99 and a replacement cost of more than $20.
this query selects movies that satisfy both rental_rate = 4.99 and replacement_cost greater than 20.

SELECT * FROM film WHERE rental_rate = 4.99 AND replacement_cost > 20;

Find all movies that have a rental rate of $0.99 or a rating of ‘PG-13’.
this query selects movies that satisfy either rental_rate = 0.99 or rating = ‘PG-13’.

SELECT * FROM film WHERE rental_rate = 0.99 OR rating = ‘PG-13’;

Retrieve the first 5 rows of movies sorted alphabetically by title.
this query selects first 5 movies sorted in alphabetical order using title.

SELECT * FROM film ORDER BY title ASC LIMIT 5;

Skip the first 10 rows and fetch the next 3 movies with the highest replacement cost.

SELECT * FROM film ORDER BY replacement_cost DESC LIMIT 3 OFFSET 10;

this query sorts movies by replacement_cost in descending order, skips first 10 rows and selects next 3.

Find all movies where the rating is either ‘G’, ‘PG’, or ‘PG-13’.
this query selects movies whose rating is in the given list.

SELECT * FROM film WHERE rating IN (‘G’,’PG’,’PG-13′);

Find all movies with a rental rate between $2 and $4.
this query selects movies whose rental rate is between 2 and 4.

SELECT * FROM film WHERE rental_rate BETWEEN 2 AND 4;

Find all movies with titles that start with ‘The’.
this query selects movies whose title starts with “The”.

SELECT * FROM film WHERE title LIKE ‘The%’;

Find the first 10 movies with a rental rate of $2.99 or $4.99, a rating of ‘R’, and a title containing the word “Love”.
here it selects from movies that satisfies all conditions and limits result to 10.

SELECT * FROM film WHERE rental_rate IN (2.99,4.99) AND rating = ‘R’ AND title LIKE ‘%Love%’ LIMIT 10;

Find all movies where the title contains the % symbol.
this query selects movies where title contains % symbol using escape.

SELECT * FROM film WHERE title LIKE ‘%%%’ ESCAPE ”;

Find all movies where the title contains an underscore (_).
SELECTS movies where title contains underscore character .

SELECT * FROM film WHERE title LIKE ‘%_%’ ESCAPE ”;

Find all movies where the title starts with “A” or “B” and ends with “s”.
The query selects movies that start with Aor B and end with s.

SELECT * FROM film WHERE (title LIKE ‘A%’ OR title LIKE ‘B%’) AND title LIKE ‘%s’;

Find all movies where the title contains “Man”,”Men”, or”Woman”.
This query selects movies whose title contains given words.

SELECT * FROM film WHERE title LIKE ‘%Man%’ OR title LIKE ‘%Men%’ OR title LIKE ‘%Woman%’;

Find all movies with titles that contain digits (e.g.,”007″, “2”, “300”).
this query selects movies whose title contains numbers.

SELECT * FROM film WHERE title REGEXP ‘[0-9]’;

Find all movies with titles containing a backslash().
this query selects movies whose title contains backslash symbol.

SELECT * FROM film WHERE title LIKE ‘%%’;

Find all movies where the title does contain the words “Love” or “Hate”.
this selects movies containing either Love or Hate in title.

SELECT * FROM film WHERE title LIKE ‘%Love%’ OR title LIKE ‘%Hate%’;

Find the first 5 movies with titles that end with “er”, “or”,or “ar”.

SELECT * FROM film WHERE title LIKE ‘%er’ OR title LIKE ‘%or’ OR title LIKE ‘%ar’ LIMIT 5;

here it selects movies ending with given patterns and limits to 5.

Democratizing Internal Data — Building an MCP Server That Lets You Search 991 Tables in Natural Language

System Overview

Hi, I’m Ryan Tsuji, CTO at airCloset — Japan’s leading fashion rental subscription service.

Today I want to share something I’m genuinely proud of: DB Graph and DB Graph MCP — a Model Context Protocol (MCP) server that lets anyone in our company search and query 15 schemas, 991 tables, 11 SQL databases, and 6 MongoDB instances using natural language through Claude Code.

You don’t need to know a single table name. Ask “find tables related to returns” and it gives you the answer — across schemas, across database engines. And yes, it can query production data safely.

In this post, I’ll walk through everything: what it does, how it works, the tool design, actual response formats, how we built the graph, how we operate it, and how we handle permissions and security.

The Problem: Nobody Knows All 991 Tables

airCloset has been running since 2015 — that’s 10 years of accumulated database schema.

Resource Count
SQL Databases 11 (MySQL 8 + PostgreSQL 3)
MongoDB Databases 6 (DocumentDB 5 + Atlas 1)
Schemas 15
Tables/Collections 991
ORMs 4 (TypeORM, Sequelize, Drizzle, Mongoose)
Repositories 28

Nobody in the company knows all of them. Not even close.

Here’s a real scenario. Customer support asks: “This customer’s app shows the return as completed, but has the warehouse actually confirmed receiving it?”

Think about what you need to investigate this.

The app-side return status lives in the aircloset schema’s delivery order table. If the delivery status is “RETURNED”, the app considers it done. Some people might know this much.

But the warehouse-side confirmation lives in the bridge schema. A receive record table’s status being “COMPLETE” means the warehouse has physically processed the returned package.

The problem? These two live in completely separate databases. No foreign key connects them. To bridge the gap, there’s an intermediate mapping table in aircloset that holds a warehouse order code (varchar) — which corresponds to a shipping order code in bridge. No FK, just a varchar match across schemas.

aircloset delivery order table (status = RETURNED)
  ↓ order_id
aircloset warehouse mapping table
  ↓ warehouse_order_code (varchar)
bridge shipping order table (matched by code — no FK!)
  ↓ shipping_order_id
bridge receive record table (status = COMPLETE = warehouse confirmed)

Table names are generalized for this article.

Four tables, two schemas, a foreign-key-less varchar join. How many people in the company know this path? You could count them on one hand. And if they’re on vacation, the investigation stalls.

This is daily life in a 991-table × 15-schema world. It’s not just “I don’t know the table name.” It’s that the connections between schemas exist only in specific people’s heads. That was the real problem.

DB Graph MCP — The Big Picture

This is what we built to solve it.

System Overview

Four components:

  1. DB Dictionary Graph Builder — A daily batch job that parses ORM definitions from 28 repositories and stores table/column/relationship info as a graph in BigQuery
  2. DB Dictionary Review UI — A web app where humans verify AI-generated descriptions, mark deprecated columns, and add annotations. Review data survives daily rebuilds
  3. DB Graph MCP Server — An MCP server (Cloud Run) that combines graph search with live DB querying
  4. DB Account Pipeline — Fully automated DB access provisioning: application → approval → account creation → notification

Seeing It in Action

Let’s solve the return investigation from above using DB Graph MCP.

Tool response examples below use generalized table/column names. The response format reflects actual output.

Step 1: Natural Language Table Search

Ask Claude Code: “Find tables related to return processing confirmation.” Under the hood, search_tables runs a semantic search.

> search_tables(query: "return processing confirmation", search_type: "semantic")

5 tables found (by vector similarity):

bridge.return_packages (postgresql) (distance: 0.2557)
bridge.receive_records (postgresql) (distance: 0.2720)
cella.receive_confirmation_results (mysql) (distance: 0.2921)
bridge.receive_record_details (postgresql) (distance: 0.2951)
aircloset.return_status_change_histories (mysql) (distance: 0.3170)

A single search returns tables across three schemas (bridge, cella, aircloset). The table name “receive_records” doesn’t contain the word “return” — but the AI-generated description includes “rental return processing” and “warehouse receiving”, so it matches semantically.

Step 2: Table Detail

The second hit in bridge looks promising. Let’s get the details.

> get_table_detail(table_name: "bridge.receive_records")

# bridge.receive_records
DB: POSTGRESQL / ORM: typeorm / Repository: bridge-api

## Columns (9)
- id: int [PK, AI, NOT NULL]
- code: varchar [NOT NULL]
- shipping_order_id: varchar [NOT NULL]
- status: enum [NOT NULL, default=IN_PROGRESS]
- type: enum [NOT NULL]
- receive_datetime: varchar [NOT NULL]
- operated_by: varchar [NOT NULL]
- created_at / updated_at: datetime

## References (2)
- shipping_order_id → bridge.shipping_orders.id (explicit)
- operated_by → bridge.users.id (explicit)

## Referenced By (1)
- bridge.receive_record_details.record_id → id (explicit)

## Enum Definitions (2)
- Status: COMPLETE=Received, IN_PROGRESS=Processing
- Type: RENTAL_RETURN=Rental return, BUSINESS_RETURN=Business return,
        RENTAL_RETURN_LACK=Rental return (missing items), BUSINESS_RETURN_LACK=Business return (missing items)

status = COMPLETE means “the warehouse has finished receiving.” Exactly what we needed. Plus type = RENTAL_RETURN distinguishes rental returns from business returns. Enum definitions with human-readable labels — visible at a glance.

Step 3: Discovering the Cross-Schema Path

Now the question: how do we connect the aircloset delivery order (app side) to the bridge receive record (warehouse side)? Let’s use trace_relationships.

> trace_relationships(table_name: "bridge.shipping_orders", direction: "both", max_depth: 1)

# Relationship trace: bridge.shipping_orders
Nodes: 23, Edges: 22

## Relationships (excerpt)
- shipping_orders.shop_id → shops.id (explicit)
- shipping_orders.warehouse_id → warehouses.id (explicit)
- receive_records.shipping_order_id → shipping_orders.id (explicit)     ← warehouse confirmation!
- return_packages.shipping_order_id → shipping_orders.id (explicit)     ← return shipment
- shipping_packages.shipping_order_id → shipping_orders.id (explicit)   ← outbound shipment
- shipping_inspections.shipping_order_id → shipping_orders.id (explicit) ← inspection
...

Found the path from bridge.shipping_orders to receive_records. Next, we find the mapping table connecting aircloset and bridge.

> search_tables(query: "warehouse_mapping", search_type: "table", adjacent_depth: 1)

aircloset.warehouse_shipping_relations (mysql)

### Related Tables
  → aircloset.delivery_orders (order_id → id)
> get_table_detail(table_name: "aircloset.warehouse_shipping_relations")

## Columns (4)
- order_id: int [PK, NOT NULL]              ← aircloset delivery order ID
- warehouse_order_code: varchar [NOT NULL]   ← bridge shipping order code

Found it. order_id links to the aircloset side, warehouse_order_code links to the bridge side. No FK, but this varchar is the only key connecting two schemas.

Step 4: Querying Real Data

Now we build cross-schema queries. First, get the delivery order and warehouse code from aircloset.

> sql_query_database(database: "aircloset", sql: "SELECT ... WHERE user_id = 12345 AND status = 'RETURNED'")

**aircloset** (staging) — 1 row

| id     | status   | returned_date       | warehouse_order_code |
|--------|----------|---------------------|----------------------|
| 98765  | RETURNED | 2026-03-20 10:30:00 | SO-2026-00012345     |

> **Table**: Manages the full lifecycle of delivery orders — styling → shipping → return status tracking

### Column Descriptions
- **status**: Delivery status (1=Awaiting shipment, 2=Ready, 3=Delivered, 4=Returned, 5=Cancelled)
- **returned_date**: Date/time the warehouse received the customer's return
- **warehouse_order_code**: Mapping code to bridge shipping order

### Related Tables
- → **aircloset.users** (user_id → id): Customer profile...
- → **aircloset.plans** (plan_id → id): Subscription plan definitions...
- ← **aircloset.styling_feedbacks** (delivery_id → id): Customer feedback on styling...
- ← **aircloset.rental_items** (delivery_id → id): Items in this order...

Notice that column descriptions and related tables are automatically appended below the query result. This metadata is pulled from the graph data cached in Redis (cache-invalidated on graph updates). AI can read this enrichment to determine its next step — like “use the warehouse code to query bridge.”

Now check the warehouse side:

> sql_query_database(database: "bridge", sql: "SELECT ... WHERE code = 'SO-2026-00012345'")

**bridge** (staging) — 1 row

| code             | status  | receive_status | type          | receive_datetime    |
|------------------|---------|---------------|---------------|---------------------|
| SO-2026-00012345 | SHIPPED | COMPLETE      | RENTAL_RETURN | 2026-03-21 14:22:00 |

> **Table**: Records warehouse receiving operations — arrival confirmation and inspection status

### Column Descriptions
- **status**: Shipping order status (ORDERED→ALLOCATED→PICKED→INSPECTED→SHIPPED→CANCELED)
- **receive_status**: Receive status (IN_PROGRESS=Processing, COMPLETE=Received)
- **type**: Receive type (RENTAL_RETURN=Rental return, BUSINESS_RETURN=Business return)

### Related Tables
- → **bridge.warehouses** (warehouse_id → id): Source warehouse...
- → **bridge.shops** (shop_id → id): Source shop...
- ← **bridge.receive_record_details** (record_id → id): Individual item details...
- ← **bridge.shipping_packages** (order_id → id): Outbound package info...

receive_status = COMPLETE — the warehouse has confirmed receipt. Both the app-side return status and the warehouse-side physical confirmation are verified.

This enrichment is the key to AI-powered investigation. Claude Code reads the column descriptions and related tables to autonomously decide “what to query next” and “how to interpret these values.” No human guidance needed.

Beyond Operations: Cross-Service Analytics

This isn’t limited to operational investigations. It works for business analytics too.

Try asking Claude Code:

How many customers used our spot rental service last week, what percentage of them are airCloset monthly subscribers, and how frequently do those subscribers use the main service?

Answering this requires crossing the spot rental order table (occasiondress schema) with the main service’s member and usage tables (aircloset schema).

Claude Code uses DB Graph MCP to identify the relevant tables via search_tables, discover join keys via trace_relationships, and run queries against both databases to produce the aggregated result. Cross-service analytics from a single natural language question — that’s the core value.

Without DB Graph MCP

Imagine doing these investigations without any tooling:

Return confirmation:

  1. You need to know the delivery order table exists in aircloset
  2. You need to know about the warehouse mapping table that bridges schemas
  3. You need to know that a varchar warehouse code maps to bridge‘s shipping code
  4. You need to know that bridge‘s receive record table is the warehouse confirmation
  5. You need to know what enum values like COMPLETE and RENTAL_RETURN mean

Cross-service analytics:

  1. You need to know the spot rental DB schema name and table structure
  2. You need to know the join key to the main service’s member table
  3. You need connection credentials for both databases
  4. You need to correctly interpret member statuses and usage counts

In both cases, the required knowledge spans multiple services and schemas. Probably fewer than five people hold all of it in their heads. With DB Graph MCP, anyone can get there through natural language search → table detail → relationship tracing → live queries.

Now let’s dive into how this works.

Tool Design: 7 Tools in 3 Categories

Dictionary Tools (no DB credentials required)

Tool Purpose
search_tables Name search + vector similarity search across tables/columns
get_table_detail Full table info: columns, FKs, enums, DEAD annotations
trace_relationships BFS traversal of table relationships

Dictionary tools read pre-built graph data from BigQuery — no individual DB credentials needed. Anyone with a Google OAuth login can use them immediately, with no access request.

Query Tools (DB credentials required)

Tool Purpose
list_databases List databases you have access to
sql_query_database Execute SELECT queries against MySQL/PostgreSQL
describe_database_table Get live schema from actual DB
mongo_query_database Execute find/aggregate against DocumentDB/Atlas

Query tools use per-user credentials stored in Firestore. You only see databases you’ve been granted access to.

This separation is intentional. The dictionary is open to everyone; data access is permission-controlled. “Everyone should know what tables exist, but accessing the data requires authorization.”

Why BigQuery? — Technology Choices

We use BigQuery as the graph store. “Shouldn’t a graph DB use Neo4j?” you might ask.

We chose BigQuery because one store handles graph + vector search + analytics:

  • VECTOR_SEARCH: Store 768-dimensional embeddings and run cosine similarity search natively. No separate vector DB needed
  • Graph traversal: Node + edge table design enables BFS traversal through simple recursive JOINs
  • JSON type: JSON_SET on a properties column lets us flexibly append review data without schema changes
  • Serverless: No instance management. Pay only for queries, not idle time
  • Vertex AI integration: Gemini 3 Flash for description generation and embedding models connect seamlessly within GCP
  • Google Workspace integration: OAuth uses Google Accounts directly. Domain restriction, nickname resolution, and permission management all flow through the same identity — no separate IdP needed

A dedicated graph DB like Neo4j has superior traversal performance, but at 991 tables, BigQuery is more than sufficient. The operational simplicity of “vector search, JSON, analytics, and graph all in one place” far outweighs the performance difference.

How Natural Language Search Works

How does “return processing confirmation” find a receive records table?

Step 1: Generate Table Descriptions

The DB Dictionary Graph Builder runs daily at 6:00 AM JST, generating AI descriptions for each table using Gemini 3 Flash:

Example: bridge.receive_records
→ "Records warehouse receiving operations. Tracks rental returns
   and business returns with completion/in-progress status.
   Links to shipping orders to trace which order a return belongs to."

Step 2: Generate Embeddings

Each description is converted to a 768-dimensional vector using Vertex AI’s embedding model and stored in BigQuery.

Step 3: VECTOR_SEARCH

The user’s query is also converted to a 768-dimensional vector, then matched via BigQuery’s VECTOR_SEARCH using cosine distance:

SELECT base.qualifiedName, distance
FROM VECTOR_SEARCH(
  TABLE `project.db_graph_nodes`,
  'embedding',
  (SELECT @query_embedding AS embedding),
  top_k => 20,
  distance_type => 'COSINE'
)
WHERE base.nodeType = 'Table'
ORDER BY distance ASC

Even if “return” doesn’t appear in the table name, the AI description’s mention of “rental return processing” places it close in vector space. That’s the core of natural language search.

Building the Graph

6-Phase Pipeline

The builder runs six phases daily:

System Overview
(See the Builder section of the diagram)

① ORM Parsing — Parse 4 ORM types (TypeORM, Sequelize, Drizzle, Mongoose) across 28 repositories to extract table definitions.

② Live DB Validation — Query actual staging DBs via Lambda to compare code definitions against real schemas. Auto-exclude tables that exist in code but not in the database.

③ AI Description — Generate table/column descriptions with Gemini 3 Flash. Incremental detection regenerates only changed tables to minimize AI cost.

④ Graph Construction — Generate 4 node types (Schema/Table/Column/Enum) and 5 edge types (HAS_TABLE/HAS_COLUMN/REFERENCES/USES_ENUM/SAME_ENTITY).

⑤ Embedding Generation — Generate 768-dimensional vectors per table via Vertex AI.

⑥ BQ MERGE — Load into BigQuery using MERGE, preserving human-written descriptions and DEAD flags. Auto-generated data never overwrites manual annotations.

Relationship Confidence Levels

Foreign key detection has varying confidence:

Confidence Detection Method Reliability
explicit Directly from ORM @JoinColumn() or belongsTo() Certain
inferred Naming convention: xxx_id → xxx table High probability
manual Added by human reviewers Certain

This lets AI judge the reliability of suggested JOIN conditions before using them.

SAME_ENTITY Edges

The same logical entity sometimes exists in both SQL and MongoDB — for example, a MySQL users table and a MongoDB user statistics collection both represent the same user. SAME_ENTITY edges express these cross-engine correspondences, enabling seamless cross-database discovery.

Human Review: AI Alone Isn’t Enough

“Are AI-generated descriptions actually accurate?” Honestly — not always.

Gemini 3 Flash produces decent high-level descriptions, but 10 years of business context — “this column was migrated 3 years ago but never dropped from the schema”, “enum value 5 is actually never used” — that kind of tacit knowledge can’t be filled by AI alone.

That’s why we built human review into the system from day one.

Review Web UI

We have a dedicated review web app for the DB Dictionary.

The schema list shows review progress bars. The table list supports filtering by “unchecked”, “checked”, and “has deprecated items.”

The table detail screen displays columns with type badges, FK targets, and enum definitions — with inline editing for descriptions and deprecation flags.

Review UI — Table Detail
Review UI: FK targets and enum definitions shown as badges. Descriptions can be edited inline.

Available review actions:

Action Description
Edit table description Supplement or rewrite the AI-generated description
Edit column description Per-column annotations (“deprecated”, “use XX instead”, etc.)
Mark as DEAD Deprecation flag + reason + empty percentage, at table or column level
Mark as Checked Review completion flag — records who checked and when
Bulk DEAD marking Mark up to 500 tables/columns as deprecated at once

DEAD Flags: Surfacing 10 Years of Tacit Knowledge

After 10 years, deprecated columns accumulate. A flag that once represented member type — migrated years ago, now NULL in every row — still sits in the schema.

When a reviewer marks a column as deprecated, the MCP table detail shows:

- old_member_flag: int [NOT NULL, default=0, DEAD] ⚠ Deprecated. Use membership_status instead
- cancel_date: datetime [DEAD] ⚠ All rows NULL
- legacy_import_id: varchar [DEAD] ⚠ Legacy CSV import field. No longer used

This matters because it prevents AI from writing code that references the wrong column. When Claude Code loads table details into context and sees a DEAD flag, it knows to avoid that column.

Change Detection and Diff Review

When the daily build detects changes in table structure or AI descriptions, they’re recorded as “pending changes.” Reviewers can view before/after diffs in the web UI and mark them as reviewed.

This ensures nothing slips through — if yesterday’s build changed something, someone will see it.

Review Data Persistence

Review data is stored in Firestore and never overwritten by daily builds.

The daily build follows this sequence:

  1. ORM parsing → graph construction — Re-extract table definitions from latest code
  2. BQ MERGE — Merge while preserving human-written textForEmbedding and embedding
  3. Re-apply Firestore reviews — Write humanDescription, isDead, deadNote, checkedAt back to BQ properties

Reviews survive unlimited daily rebuild cycles. Firestore is the source of truth; BQ is its reflection.

Crossing the VPC Wall: Cross-Cloud Architecture

Now for the security design I’m most proud of.

Problem: The MCP server runs on Google Cloud (Cloud Run). The databases are inside AWS VPCs. Cloud Run can’t directly reach VPC-internal RDS/DocumentDB instances.

Solution: A three-stage authentication chain — GCP OIDC → AWS STS → VPC Lambda — enables secure cross-cloud connectivity.

Query Dataflow

Authentication Flow

1. Cloud Run (GCP) → Get OIDC token from GCP metadata server
2. OIDC token → AWS STS AssumeRoleWithWebIdentity
3. STS → Return temporary AWS credentials (1-hour TTL)
4. Temporary credentials → Invoke VPC-internal Lambda
5. Lambda → Execute query against VPC-internal RDS/DocumentDB

Key points:

  • Zero static AWS credentials. Dynamically obtained from GCP service account.
  • Temporary credentials cached for 5 minutes. Avoids per-request STS overhead.
  • Lambda executes inside VPC. DB connections never leave the VPC.
  • Production queries use Read Replicas only. Never connects to the master.

SQL Validation (Defense in Depth)

Query safety is enforced at two layers:

MCP layer (1st):

Allowed: SELECT, SHOW, DESCRIBE, DESC, EXPLAIN, WITH...SELECT
Blocked: INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, multi-statement via semicolons

Lambda layer (2nd):
The same validation runs inside Lambda. Even if the MCP layer is somehow bypassed, Lambda blocks it.

Protecting Production Data — PII Anonymization

Querying production data is powerful, but handling personally identifiable information (PII) requires the most care.

Automatic Anonymization Rules

For production + view permission queries, PII column values are automatically anonymized:

Column Pattern Replacement
Email fields ***@***.com
Name fields ***
Phone fields ***-****-****
Postal code fields ***-****
Address fields ***
Password fields [REDACTED]
Date of birth fields ****-**-**
Card number fields [REDACTED]

Table-specific rules handle ambiguous columns. For example, a generic name column isn’t PII globally, but users.name or orders.buyer_name clearly is. These are configured per-table.

Staging vs Production

Environment PII Anonymization Connection Target
Staging None Master DB
Production (view) Auto-applied Read Replica
Production (edit) None Read Replica

Staging uses test data, so no anonymization needed. Only production view queries get automatic PII protection.

Fully Automated Access Management — DB Account Pipeline

“Who do I talk to about getting database access?”

This question doesn’t get asked anymore. The DB Account Pipeline automates everything.

Credential Flow

Flow

  1. User submits a workflow request — nickname, email, desired databases (multiple allowed)
  2. Manager approves
  3. Cloud Run Job processes automatically — reads approved requests, generates CREATE USER statements per DB, executes via Lambda
  4. Credentials saved to Firestore + Secret Manager — passwords never stored in plaintext
  5. Slack DM with connection info — includes bastion server guide

Zero Plaintext Passwords

Passwords are stored only in Secret Manager.

Firestore db_credentials:
  host: "xxx.rds.amazonaws.com"
  port: 3306
  username: "ryan_view_user"
  passwordSecretId: "db-cred-xxxxx"  ← Reference to Secret Manager only
  permLevel: "view"

When the MCP Server executes a query, it decrypts the password from Secret Manager via passwordSecretId and caches it in memory for 5 minutes. Cloud Run restarts clear the cache.

No plaintext password exists anywhere — this was a deliberate design decision we’re particularly proud of.

Operations

Daily Cron

A cron job fires at 6:00 AM JST daily, triggering a Cloud Run Job:

6:00 AM JST — Cron fires
├── ORM parsing (28 repos × 4 ORMs)
├── Live DB validation (11 staging DBs)
├── Gemini description generation (incremental only)
├── Graph construction + Embedding
├── BQ MERGE (preserving annotations)
└── Slack notification

Cost

Resource Cost
Gemini 3 Flash (daily, incremental) ~$0.10-0.20/day
Vertex AI Embedding ~$0.01/day
Cloud Run Job Near-free (once daily)
BQ Storage A few GB
Lambda Shared with DB Account Pipeline

Thanks to incremental detection, we maintain an AI-powered dictionary for 991 tables at under $10/month.

Incremental Detection

Regenerating all table descriptions daily would spike Gemini costs. So we introduced change detection:

1. Compare previous property hashes
2. Detect column structure changes (additions/removals/type changes)
3. Identify affected tables via enum dependency graph
→ Regenerate only changed tables

If a status enum changes, all tables using that enum are regenerated. No changes? Skip. This cuts AI costs by roughly 90%.

Security Summary

Layer Protection
OAuth Google Account + corporate domain restriction
Credential Resolution email → nickname → per-user DB credentials
Permission Filter Per-user × database × environment × permission level
SQL Validation (MCP) SELECT-only enforcement
SQL Validation (Lambda) Same validation (defense in depth)
PII Anonymization Production + view queries only
Production Connection Read Replicas only
Passwords Secret Manager only, 5-min TTL memory cache
Cross-Cloud Auth GCP OIDC → AWS STS (zero static credentials)
Logging Passwords and query results never logged

Takeaways

DB Graph MCP goes beyond solving the fundamental database problem of “you can’t use what you don’t know exists.” It enables anyone to search real data without knowing SQL at all.

  • As a dictionary — Search 991 tables’ structure, relationships, and enum definitions in natural language
  • As a query tool — Securely query staging and production data with automatic PII protection
  • As a knowledge base — DEAD flags and column annotations surface 10 years of tacit knowledge

The biggest lesson from building this: the real value of MCP is giving AI context. Table structure, relationships, enum definitions, column warnings — when these enter AI’s context window, the SQL and code Claude Code writes become dramatically more accurate.

Making that happen required building the graph, securing cross-cloud access, automating permission management, and protecting PII — unglamorous but essential infrastructure, built with care.

I hope this helps anyone wrestling with internal database management at scale.

I’m CTO at airCloset, a fashion rental subscription service in Japan. We’re building the future of AI-powered development. If you’re interested, check out our engineering careers page.

Top 12 Automated Test Case Generation Tools in 2026

agent B

Here’s what actually happens in most QA teams: a sprint ends, features ship, and someone still has to figure out which test cases cover the new flows and which existing ones just broke because the UI changed.
That cycle doesn’t just waste time. It’s how test coverage quietly degrades over time.

According to the Katalon State of Quality Report 2025, 55% of teams lack sufficient time for thorough testing, while 44% report high workload as a core challenge.

At the same time, automation and AI adoption are accelerating. Over 60% of teams now integrate automated testing into CI/CD pipelines, and AI-driven tools are increasingly being used to reduce the effort required for test creation and maintenance, according to reports from Testlio and PractiTest.

Tools like CoTester AI Test Agent reflect that shift, focusing not just on generating test cases but reducing the effort required to keep them updated as applications evolve.

The tools in this guide represent the sharpest end of that shift. Some are genuinely AI-native. Others are proven open-source workhorses that still belong in the conversation. All 12 are worth understanding before you make a decision.

What Automated Test Case Generation Actually Means?

Test case generation isn’t one thing. Different tools generate tests in very different ways, and that difference directly impacts how useful (or painful) they are in real projects.
Some approaches focus on generating tests from requirements. Others analyze the application itself. Some optimize for coverage, while others prioritize speed and ease of use.
Here’s how the main approaches break down:

Technique How It Works When to Use It
NLP / LLM-Based Generation Parses user stories or requirements and writes executable test steps Agile teams using Jira, Confluence, or plain documentation
Vision-Language Model (VLM) Interprets the live UI visually and contextually like a human tester UI-heavy applications with frequent changes
Model-Based Testing Builds a behavioral model of the app and generates test scenarios Complex workflows with multiple branching paths
Data-Driven Generation Uses varied inputs systematically to expand test coverage Forms, APIs, and input-sensitive functionalities
Search-Based (Genetic) Testing Uses algorithms to optimize test coverage Unit testing and coverage-focused projects
Record-and-Play + AI Repair Records actions and uses AI to maintain scripts as UI evolves Teams new to automation needing quick setup

No single approach is “best.” The right one depends on what problem you’re actually trying to solve.

Top Automated Test Case Generation Tools

1. CoTester by TestGrid

What it is: An AI-driven test generation and execution platform built for complex applications.
Most tools generate test scripts. CoTester AI Test Agent focuses on generating and executing tests by combining natural language input with a visual understanding of the application.
Instead of relying only on DOM-level selectors, it uses a vision-based approach to interpret UI elements and workflows. That makes it more resilient to UI changes compared to traditional script-heavy tools.
Where it stands out is during execution. Its self-healing capability adapts to tests when UI elements change, reducing manual maintenance in many common scenarios.
It also supports multiple usage modes:

  • No-code for business users
  • Low-code for QA teams
  • Full-code flexibility for engineers
    For teams working in regulated environments, deployment options (cloud, private cloud, on-prem) and data isolation become important—and CoTester supports all three.
    Where it’s not a fit: Overkill for small teams with simple apps and infrequent releases.
    Best for: Enterprise teams dealing with complex workflows, frequent UI changes, and mixed-skill contributors.
    Pricing:

  • Starter: Starts at ~$199 per user/month (minimum seats apply)

  • Growth / Enterprise: Custom pricing based on scale, integrations, and deployment model

2. Katalon Studio

What it is: A multi-purpose automation platform covering web, mobile, API, and desktop testing.
Katalon is popular because it doesn’t force teams into a single approach. You can generate, execute, and manage tests across different layers without stitching together multiple tools.
Its AI features—like smart locators and test suggestions—help reduce maintenance, though they’re not as advanced as newer AI-native platforms.
Best for: Teams that want a single tool for multiple testing types without building a custom stack.
Pricing:

  • Free tier available
  • Paid plans start from ~$69/month (node-locked license)
  • Enterprise / floating licenses priced annually (~$1,500+/year depending on usage)

3. mabl

What it is: A low-code platform focused on self-healing and ease of use.
mabl is built for teams that don’t want to spend time maintaining test scripts. Tests can be created in plain language, and its auto-healing handles common UI changes well.
It’s strong for web and API flows, but mobile support is still limited.
Best for: Product and QA teams prioritizing speed and low maintenance over deep customization.
Pricing:

  • Custom pricing based on test volume and usage

4. Tricentis Tosca

What it is: A model-based testing platform designed for enterprise environments.
Tosca is widely used in industries where traceability and compliance matter. It generates test cases from application models and links them directly to requirements.
Recent updates include AI-assisted test creation and visual automation features, but the core strength is still model-based testing.

Best for: Enterprises with complex systems (SAP, Salesforce) and strict audit requirements.
Pricing:

  • Enterprise licensing only
  • Custom pricing based on users, modules, and integrations

5. Testim

What it is: A machine learning-based web testing tool focused on stability.
Testim reduces test breakage by generating smarter element locators based on patterns, not just static attributes.
It allows both low-code and code-based customization, which helps teams scale without losing control.
Best for: Teams struggling with flaky UI tests and frequent frontend changes.
Pricing:

  • Starts around ~$450/month
  • Scales based on the number of tests and parallel executions
    Free trial available

6. Functionize

What it is: A cloud-based AI testing platform that generates tests from plain English inputs.
Functionize is built for speed. You describe what needs to be tested, and it generates executable tests that can run at scale on its cloud infrastructure.
The tradeoff is flexibility—it’s less suited for highly customized or complex logic.
Best for: Teams needing fast test coverage expansion without scaling QA headcount.
Pricing:

  • Custom quotes based on usage, test volume, and infrastructure needs

7. QA Wolf

What it is: A hybrid model combining automation tooling with a managed QA service.
QA Wolf doesn’t just give you tools—it helps build and maintain your test suite. This reduces internal effort but comes at a higher cost.
It’s built on Playwright and focuses primarily on web testing.
Best for: Teams that want reliable test coverage without building a full QA automation team in-house.
Pricing:

  • Custom pricing (includes platform + managed QA service)
  • Typically higher than pure tooling due to the service component

8. Applitools

What it is: A visual testing platform powered by AI.
Applitools doesn’t generate test cases. Instead, it validates the visual correctness of your application across browsers and devices.
It integrates with tools like Selenium, Playwright, and Cypress to enhance existing tests with visual validation.
Best for: Teams where UI consistency is critical.
Pricing:

  • Free tier available (limited usage)
  • Paid plans based on the number of checkpoints, tests, and users
  • Enterprise pricing for large-scale usage

9. EvoSuite

What it is: An open-source tool for automated Java unit test generation.
EvoSuite uses search-based techniques (genetic algorithms) to generate test cases that maximize code coverage.
It’s highly effective within its scope—but that scope is limited to Java unit testing.
Best for: Java teams looking to improve coverage without manual test writing.
Pricing:

  • Open source (free)

10. Diffblue Cover

What it is: An AI-powered unit test generator for Java, built for CI/CD integration.
Diffblue automatically generates unit tests as part of the development pipeline. Every code change can trigger new test generation.
It’s more production-ready than research tools like EvoSuite, but still focused strictly on Java.
Best for: Dev teams that want automated unit test generation integrated into their pipeline.
Pricing:
Community Edition: Free
Enterprise: Custom pricing based on team size and CI/CD integration

11. Selenium

What it is: A foundational web automation framework.
Selenium doesn’t generate test cases, but many tools generate tests for Selenium.
It gives full control, but also comes with setup and maintenance overhead. Newer tools like Playwright and Cypress have simplified parts of this workflow, but Selenium is still widely used.
Best for: Teams that want full control or need a base layer for custom frameworks.
Pricing:

  • Open source (free)
  • Infrastructure and maintenance costs depend on implementation

12. Cucumber

What it is: A BDD framework that turns plain English scenarios into executable tests.
Cucumber enables teams to define tests using Gherkin syntax (Given/When/Then), aligning business requirements with test cases.
It’s powerful when used correctly, but requires discipline across teams.
Best for: Teams practicing BDD with strong collaboration between product, QA, and engineering.
Pricing:

  • Open source (free)
  • Paid options available via CucumberStudio / enterprise integrations

How to Pick the Automated Test Case Generation Tool

  • Don’t start by comparing features. Start with the actual problem:
  • Are you spending too much time writing test cases?
  • Are your tests breaking constantly?
  • Is coverage too low?
  • Or is maintenance the real bottleneck?

Once that’s clear, evaluate tools based on what actually affects your workflow:

  1. Ease of Adoption: If your team needs weeks of training, the tool will slow you down instead of helping. Prioritize tools your team can start using immediately.
  2. Integration with Your Stack: A tool that doesn’t plug cleanly into your CI/CD pipeline will create friction. Make sure it works seamlessly with your existing development and testing ecosystem.
  3. Support for Your Application Type: Web, mobile, APIs, or enterprise apps—your tool must align with what you’re testing. Cross-platform support matters only if you actually need it.
  4. Test Generation Accuracy (AI Capabilities): AI features should reduce manual effort and improve coverage not just generate noisy or redundant test cases.
  5. Scalability and Maintenance Effort: The real cost isn’t creating tests, it’s maintaining them. Choose a tool that minimizes test breakage and ongoing upkeep.
    Tools that combine AI-driven test generation, strong CI/CD integration, and reliable cross-platform support can significantly reduce manual effort and improve release speed, but only if they align with your actual testing bottlenecks.

Final Take

There’s a clear shift happening: teams are moving away from manually writing and maintaining large test suites toward tools that generate and adapt tests automatically.

But most tools don’t eliminate effort; they just move it around.

The ones worth investing in are the ones that:

  • Reduce maintenance
  • Improve coverage without adding noise
  • Fit into your existing workflow

If you’re dealing with complex systems and frequent changes, AI-driven tools like CoTester represent where the space is heading.

If you’re earlier in the journey, tools like Katalon or mabl give you a more gradual path into automation.
Either way, the tools aren’t the bottleneck anymore.
Implementation is.