Power tools — the generic engine

Most of the time you'll use a product's named tools — create_account, search_accounts, summarize_account — on its own endpoint. Underneath those sits a generic engine: a low-level "do anything" layer that works against any table in your active product's live schema. It's the fallback when there isn't a named tool for what you want — ad-hoc inspection, reporting across arbitrary tables, bulk reads, or relating records that the named tools don't connect for you.

💬 Just ask

  • "Pull every account created last month into a table for me."
  • "Link this contact to that support ticket."

You don't call these tools yourself — just tell your assistant; the technical reference below is for when you want the details.

The generic engine lives on the concierge endpoint https://hdls.ai/api/mcp — the same control plane you use to install products and invite teammates, not a product endpoint. (Product endpoints at /api/mcp/<slug> deliberately expose only their named tools; the generic engine lives only on the concierge.) The active product for a request is the schema your credential is pointed at; switch it for a single request with the x-hdls-product: <slug> header.

Discover, then act. The engine is schema-driven. The pattern is always list_tablesdescribe_table → read/write. Every identifier you name is validated against the live schema, every value is parameterized, and every query is tenant-scoped — you never pass a tenant_id.

At a glance

StageToolsMinimum role
Discoverlist_tables, describe_tablereader
Read & analysequery_records, get_record, aggregate_recordsreader
Searchsemantic_search, hybrid_searchreader
Relatelist_links, traverse_link, list_relations, query_relatedreader
Writeinsert_record, update_record, delete_recordmember
Linkcreate_link, delete_linkmember

Discover: list_tables & describe_table

Start by seeing what's there.

// tool: list_tables
{}

Then inspect a table's shape — its columns, types, primary key, foreign keys, and the grants that decide whether you can write to it:

// tool: describe_table
{ "table": "deal" }

You read these first so you know the real column names before you query or write — the engine rejects any column it didn't discover.


Read & analyse

query_records — filter, search, sort, paginate

The workhorse read tool. Filters accept either a simple equality map or a list of operator conditions, all AND-ed:

// tool: query_records
{
  "table": "deal",
  "filters": [
    { "column": "stage",  "op": "neq", "value": "lost" },
    { "column": "amount", "op": "gte", "value": 10000 }
  ],
  "orderBy": { "column": "amount", "dir": "desc" },
  "limit": 25
}

The available operators are eq, neq, gt, gte, lt, lte, like, ilike, in (value is an array), between (value is [lo, hi]), is_null, and is_not_null. A free-text search string matches across the table's text columns.

get_record — one row by id

// tool: get_record
{ "table": "account", "id": "acc_01H…" }

aggregate_records — group-by / pivot reporting

Run a SQL GROUP BY (or a spreadsheet-style pivot) safely. Give it groupBy columns and one or more metrics (count, sum, avg, min, max); count may omit its column for COUNT(*). You can filter rows, filter groups with HAVING on a metric alias, order by a group column or metric alias, and limit.

// tool: aggregate_records
{
  "table": "deal",
  "groupBy": ["stage"],
  "metrics": [
    { "fn": "count",                 "alias": "deals" },
    { "fn": "sum", "column": "amount", "alias": "pipeline_value" }
  ],
  "orderBy": { "column": "pipeline_value", "dir": "desc" }
}

You get one row per group with the computed metrics — your pipeline by stage, tickets by severity, revenue by month, all without writing SQL.


Search: semantic & hybrid

For tables that carry an embedding column (most product entities do — e.g. CRM contacts and activities), the engine offers meaning-based search.

semantic_search — nearest by meaning

// tool: semantic_search
{ "table": "contact", "query": "VP of engineering interested in security", "k": 10 }

Embeds your query and returns the top-k nearest rows by cosine distance (lower = closer). Rows not yet embedded are skipped.

hybrid_search — meaning + keywords, fused

// tool: hybrid_search
{ "table": "activity", "query": "renewal risk pricing pushback", "k": 10 }

Fuses the semantic (vector) results with full-text/keyword results using Reciprocal Rank Fusion for higher precision than either alone — each hit comes back with a fused score and its rank in both lists.


This is the engine's most distinctive capability. Records in different products can be associated and followed — a CRM contact linked to a support ticket, a deal linked to a contract — without any hard-wired cross-product schema.

// tool: create_link
{
  "fromTable": "account",
  "fromId": "acc_01H…",
  "toSchema": "support",
  "toTable": "ticket",
  "toId": "tkt_01H…",
  "relation": "ticket_of"
}

Both sides are validated against the live schema before the link is stored; the link is scoped to your workspace.

// tool: traverse_link
{ "table": "account", "id": "acc_01H…", "relation": "ticket_of" }

Follows the links from a record and fetches each linked record — even when it lives in another product's schema — returning [{ link, record }]. Cross-product reads stay tenant-scoped: you only ever reach records your workspace is allowed to see, and only along links it owns.

  • list_links shows every link a record participates in (both directions).
  • list_relations shows how a table connects via real foreign keys (and links).
  • query_related walks a foreign-key relationship from a base row to its related rows.

Write: insert_record, update_record, delete_record (member+)

// tool: insert_record
{ "table": "account", "values": { "name": "Acme Corp", "domain": "acme.com" } }
// tool: update_record
{ "table": "account", "id": "acc_01H…", "values": { "industry": "Manufacturing" } }
// tool: delete_record
{ "table": "ticket", "id": "tkt_01H…" }

On managed workspaces, tenant_id (and any record owner) is stamped server-side — never supplied by you. Append-only tables reject update and delete by design.


When to reach for the generic engine

  • There's no named tool for it. A product gives you purpose-built tools for its common jobs; the generic engine covers everything else against any table.
  • You're reporting or inspecting across tables. aggregate_records and query_records are the fastest path to an ad-hoc report.
  • You're relating records across products. create_link / traverse_link are the only way to associate records in different products.

Prefer named tools when one exists. A product's own endpoint (/api/mcp/<slug>) exposes tools shaped exactly for that product's workflow. Reach for the generic engine when you need to go off that paved path — it's the universal layer beneath everything. New to the two-endpoint model? See What hdls is & how it works.

Where to go next