Tool generation
How a Postgres schema becomes 5–8 well-named MCP tools per table — automatically.
The introspection step
Once your data is loaded, the ingest worker queries Postgres' information_schema and pg_catalog to discover tables, columns, primary keys, foreign keys,
and column cardinality. The result is a typed schema model that drives tool generation.
Per-column rules
| Column trait | Generated tool(s) |
|---|---|
text, varchar (high cardinality) | search_* (Postgres full-text), filter_* (exact match) |
text, varchar (low cardinality) | count_*_by_* (aggregation) |
int, numeric | filter_* with min / max range |
timestamptz, date | filter_* with after / before range |
boolean | filter_* toggle |
| Primary key | get_*_by_id |
| Foreign key | get_*_for_* (cross-table join) |
Worked example
Suppose you upload a products.csv with foreign keys to categories and orders. Schema introspection produces three tables. Tool generation yields:
# products
list_products
get_products_by_id
search_products # searches: name, description
filter_products # category, price (min/max), in_stock, created_at (after/before)
count_products_by_category
# categories
list_categories
get_categories_by_id
# orders
list_orders
get_orders_by_id
filter_orders # customer_id, total (min/max), placed_at (after/before)
get_orders_for_product # FK: orders.product_id → products.id Eleven tools, no hand-written code, all visible to any MCP client via tools/list.
Argument schemas
Every generated tool has a JSON Schema for its inputs. Agents see precise types,
ranges, and which fields are required. filter_* tools accept partial
arguments — pass only the columns you care about.
// Example: filter_products inputSchema
{
"type": "object",
"properties": {
"category": { "type": "string" },
"price_min": { "type": "number" },
"price_max": { "type": "number" },
"in_stock": { "type": "boolean" },
"created_at_after": { "type": "string", "format": "date-time" },
"created_at_before": { "type": "string", "format": "date-time" },
"limit": { "type": "integer", "default": 50, "maximum": 500 }
}
} Markdown-corpus tools
Endpoints built from Markdown / URL extraction get a different tool family geared at document retrieval rather than relational filtering:
search_documents— full-text across the corpusget_document_by_slug— fetch the full markdown bodylist_documents— paginated listing with title + excerptcount_tags_by_tag/get_tags_for_document— tag analyticslist_links— outbound links extracted from each document
Write tools (Pro+ only)
When an endpoint has allow_writes on (Pro tier and above), schema-gen
also produces three write tools per table that has a single-column primary key:
insert_<table>—_meta.kind: "insert". Argument schema is the table's writable columns; required = NOT NULL columns without defaults.update_<table>_by_id—_meta.kind: "update_by_id". Pass the primary key plus only the columns you want to change.delete_<table>_by_id—_meta.kind: "delete_by_id".
Plus four built-in ingest tools (ingest_url, ingest_file, request_upload_url, ingest_uploaded_file) and a get_write_job_by_id tool for polling async results.
Metadata tables (pdf_metadata, audio_metadata, etc.) are
excluded from write generation — they're populated by the ingest pipeline and
shouldn't be written directly. Tables without a single-column PK are also excluded
(no unambiguous "by id" key for update/delete).
See Agent writes & ingest for the full mental
model and the OAuth scope (mcp:write) clients need to call them.
What's not auto-generated
- Mass writes — no
UPDATE … WHERE x = 'y'or bulk inserts. Limits blast radius. - Schema mods — agents can't
CREATE TABLEorALTER. Use the dashboard or Neon SQL editor. - Custom aggregations — sum, avg, percentile aren't generated. They're on the roadmap as opt-in
aggregate_*_by_*tools. - Cross-table joins beyond FKs — only declared FKs produce join tools.
Tip: if your data has implied foreign keys but no actual constraint, add one before uploading. A single ALTER TABLE … ADD FOREIGN KEY in your SQL dump is enough to unlock the cross-table tools.