Skip to content

feat: schema context, estimated plans and table search for agents - #12

Merged
fuseraft merged 2 commits into
masterfrom
feat/agent-ergonomics
Oct 19, 2026
Merged

fuseraft merged 2 commits into
masterfrom
feat/agent-ergonomics

Conversation

@fuseraft

Copy link
Copy Markdown
Owner

Follows #11. Agents could see table and column names, but not how tables join, what a code column means, how big a table is, or what a query would cost. In a large schema they also couldn't search for a table.

Schema context

  • Foreign keys. describe_table lists the keys a table holds (foreignKeys) and the keys of other tables that reference it (referencedBy), so join columns come from the schema instead of name-matching. Keys whose other table the login can't read are left out. That matters most on Postgres, where pg_constraint is visible to every login.
  • Comments. Table and column comments (COMMENT ON, SQL Server's MS_Description, MySQL's COMMENT) appear in describe_table, and table comments in list_tables, shortened to 200 characters there.
  • Estimated row counts. list_tables and describe_table give approxRows from the database's statistics, so an agent can tell a lookup table from one it shouldn't scan. Absent for views and for tables never analyzed.
{ "schema": "public", "name": "sqlmcp_child", "type": "table", "comment": "Rows that point at a keyed row",
  "columns": [ …, { "name": "keyed_id", "dataType": "integer", "nullable": true, "primaryKey": false, "comment": "The keyed row this one belongs to" }, … ],
  "foreignKeys": [{ "columns": ["keyed_id"], "referencedTable": "public.sqlmcp_keyed", "referencedColumns": ["id"] }],
  "referencedBy": [] }

Checking and finding before querying

  • explain_query. Takes the same arguments and checks as query_database, but returns the estimated plan instead of running the query. An agent can see a full scan of a large table before it runs into the timeout. Plans come from EXPLAIN on Postgres (never ANALYZE), EXPLAIN FORMAT=TREE on MySQL, SHOWPLAN_ALL on SQL Server (with each operator's estimated rows and cost), and EXPLAIN PLAN + DBMS_XPLAN on Oracle.
    • SQL Server's SHOWPLAN plans a parameterized command as the sp_executesql call that carries it, so parameters are declared in the batch, typed from their values. Nothing runs under SHOWPLAN, so the values aren't needed. Oracle plans its placeholders unbound.
    • Oracle's EXPLAIN PLAN writes to PLAN_TABLE, which a read-only transaction refuses (ORA-01456). So on Oracle it runs in an ordinary transaction, still rolled back. The query itself is only parsed, and the README's Security section says so.
  • list_tables search. search matches text in a table's name or comment, ignoring case, with %, _ and [ matched as text. The truncation note suggests it.
{ "plan": "Nested Loop  (cost=0.15..27.08 rows=4 width=168)\n  ->  Index Scan using sqlmcp_keyed_pkey on sqlmcp_keyed k  (cost=0.15..8.17 rows=1 width=82)\n        Index Cond: (id = '1'::bigint)\n  ->  Seq Scan on sqlmcp_child c  (cost=0.00..18.88 rows=4 width=86)\n        Filter: (keyed_id = '1'::bigint)" }

Tested locally: the full suite against all four databases (351 tests). New tests cover:

  • foreign keys in both directions, comments, and row estimates after gathering statistics
  • a read-only login that can't see a key to a table it can't read; this test fails on Postgres when the privilege filter is removed
  • explaining a query that fails only when run (it isn't run), and writes refused by explain_query
  • search by name and comment, with literal wildcards

I also called the tools over MCP against Postgres and got the output shown above.

- describe_table lists the foreign keys a table holds and those of other tables
  that reference it, so agents pick join columns instead of guessing from names.
  Keys whose other table the login can't read are left out.
- Table and column comments (COMMENT ON, SQL Server's MS_Description, MySQL's
  COMMENT) appear in describe_table, and table comments in list_tables, shortened
  to 200 characters there.
- list_tables and describe_table give each table's estimated row count from the
  database's statistics, so an agent can tell a lookup table from one it shouldn't
  scan. Absent for views and for tables never analyzed.
- explain_query takes the same arguments and checks as query_database but returns
  the database's estimated plan instead of running the query, so an agent can see
  a full scan of a large table before it runs into the timeout. EXPLAIN on Postgres
  and MySQL, SHOWPLAN_ALL on SQL Server (with each operator's estimated rows and
  cost), EXPLAIN PLAN with DBMS_XPLAN on Oracle.
- SQL Server's SHOWPLAN plans a parameterized command as its sp_executesql call, so
  parameters are declared in the batch, typed from their values. Oracle plans its
  placeholders unbound.
- Oracle's EXPLAIN PLAN writes to PLAN_TABLE, which a read-only transaction refuses,
  so there it runs in an ordinary transaction that is still rolled back; the query
  is only parsed.
- list_tables takes search: text the table's name or comment contains, ignoring
  case, with LIKE wildcards matched as text. The truncation note suggests it.
@fuseraft
fuseraft merged commit 07e42c3 into master Oct 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant