fix(storage): write column descriptions where the MCP server reads them (#624) - #628
fix(storage): write column descriptions where the MCP server reads them (#624)#628padak wants to merge 1 commit into
Conversation
`storage describe-column` / `describe-batch` stored each description as a
flat `KBC.column.{name}.description` entry on the TABLE's metadata, on the
documented assumption that Keboola exposes no user-writable column-metadata
endpoint. That assumption is wrong: the same
`POST /v2/storage/tables/{id}/metadata` kbagent already calls accepts a
`columnsMetadata` payload with `provider: user`, and that native
`columnMetadata` store is what the Keboola UI and the Keboola MCP server
(`get_table_detail`, `search`) read.
Nothing read the flat keys, so column descriptions written by kbagent were
invisible to every AI client using the MCP server -- the exact audience they
are written for. The failure was silent in both directions: `table-detail`
read back kbagent's own convention, so the descriptions were reported as
correctly applied.
- client: new `set_table_column_metadata()` posting a JSON `columnsMetadata`
payload (the nested per-column shape has no form-encoded equivalent).
- service: `describe_columns()` writes `KBC.description` per column there;
`get_table_detail()` reads it from `columnMetadata` first and keeps the flat
keys as a fallback so older descriptions stay visible, with `columnMetadata`
winning when both carry a value.
- the repetitive per-column metadata elif chain becomes a `_COLUMN_META_FIELDS`
mapping, keeping the file inside its grandfathered line budget.
There is no bulk migration: re-running `describe-column` on an affected table
rewrites it into the right place.
|
Superseded by #631, which merged the same issue (#624) with a different — and better — write path. #628 writes to Two things #631 gets that this approach cannot:
Merging this on top of #631 would therefore regress the fix rather than extend it. The branch is also Closing as superseded. The analysis here — in particular that |
What
storage describe-column/describe-batchnow write column descriptions asKBC.descriptioninside the table's nativecolumnMetadatastore, instead of asflat
KBC.column.{name}.descriptionentries on the table's metadata.Why
Two incompatible conventions never met:
KBC.column.{name}.descriptionKBC.descriptioncolumnMetadata[{name}]The
client/storage_tables.pydocstring justified the flat convention with "KeboolaStorage API does not expose a user-writable column-metadata endpoint". That is not
correct. The same
POST /v2/storage/tables/{id}/metadatakbagent already calls acceptsa
columnsMetadatapayload withprovider: user;keboola/mcp-server'sStorageClient.table_metadata_update()does exactly that, and its read paths(
tools/storage/tools.py,tools/storage/search.py) resolve column descriptions onlyfrom
columnMetadata.So every column description kbagent ever wrote was invisible to every AI client using
the Keboola MCP server — precisely the audience those descriptions are written for.
The failure was silent in both directions:
storage table-detailread backkbagent's own convention, so the descriptions were reported as correctly applied.
Changes
client/storage_tables.py) — newset_table_column_metadata()posting aJSON
{provider, columnsMetadata}payload. It sends JSON rather than form databecause the nested per-column shape has no form-encoded equivalent; the table-level
set_table_metadata()is untouched and still form-encoded. Entries carry theredundant
columnNamekey, mirroring what the platform's own clients send.services/storage_service.py::describe_columns) — writesKBC.descriptionper column through the new client method.describe_batchpicksthis up for free via its
columns:section.services/storage_service.py::get_table_detail) — readsKBC.descriptionfromcolumnMetadataas the primary source, keeping the flatKBC.column.*keys as a fallback so descriptions written by an older kbagent stayvisible here. When both carry a value,
columnMetadatawins.storage_service.pyis a grandfathered file that may not grow, sothe repetitive five-branch per-column
elifchain became a_COLUMN_META_FIELDSmapping. Adding a pass-through field is now a one-line data change, and the file
stays at its 1733 ceiling.
describe-columncommand help,AGENT_CONTEXT,storage-describe-workflow.md,commands-reference.md, and a rewrittengotchas.mdentry tagged(since v0.87.1).Migration
There is no bulk migration command. Re-running
describe-column(ordescribe-batch) on an affected table rewrites the description into the right place;until then those tables stay invisible to MCP clients. The read fallback means nothing
disappears from kbagent's own output in the meantime.
A dedicated migration command was left out deliberately — it would be a new CLI surface
(with the full set of drift surfaces to update) for a one-shot operation that re-running
an existing command already performs. Happy to add one if you would rather have it.
Version
Bumped to 0.87.1 with a changelog entry. This changes where data is written and
carries a migration note, so shipping it without a changelog entry would leave users
unaware they need to re-run
describe-column. Renumber freely if it should land under adifferent version.
Testing
make checkgreen: lint, format, typecheck, skill-check, version-check,command-sync-check, changelog-check, error-codes, sentinel-guards, loc-check, and
5703 tests passed.
KBC.descriptionread fromcolumnMetadata;columnMetadatabeating the legacy flat key when both are present.
columnsMetadatabody and theapplication/jsoncontent type, plus the branch-scoped endpoint.set_table_metadatacall no longer happensfor columns; the pre-existing flat-key read tests now stand as legacy-fallback
regression cover.
endpoint contract is corroborated by the issue author's live reproduction on project
9432 and by
keboola/mcp-server's implementation.Fixes #624