Skip to content

fix(storage): write column descriptions where the MCP server reads them (#624) - #628

Draft
padak wants to merge 1 commit into
mainfrom
claude/issue-624-column-metadata-write
Draft

fix(storage): write column descriptions where the MCP server reads them (#624)#628
padak wants to merge 1 commit into
mainfrom
claude/issue-624-column-metadata-write

Conversation

@padak

@padak padak commented Aug 21, 2026

Copy link
Copy Markdown
Member

What

storage describe-column / describe-batch now write column descriptions as
KBC.description inside the table's native columnMetadata store, instead of as
flat KBC.column.{name}.description entries on the table's metadata.

Why

Two incompatible conventions never met:

Key Location
kbagent wrote KBC.column.{name}.description flat entry in table metadata
MCP server reads KBC.description inside columnMetadata[{name}]

The client/storage_tables.py docstring justified the flat convention with "Keboola
Storage API does not expose a user-writable column-metadata endpoint". That is not
correct. The same POST /v2/storage/tables/{id}/metadata kbagent already calls accepts
a columnsMetadata payload with provider: user; keboola/mcp-server's
StorageClient.table_metadata_update() does exactly that, and its read paths
(tools/storage/tools.py, tools/storage/search.py) resolve column descriptions only
from 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-detail read back
kbagent's own convention, so the descriptions were reported as correctly applied.

Changes

  • client (client/storage_tables.py) — new set_table_column_metadata() posting a
    JSON {provider, columnsMetadata} payload. It sends JSON rather than form data
    because the nested per-column shape has no form-encoded equivalent; the table-level
    set_table_metadata() is untouched and still form-encoded. Entries carry the
    redundant columnName key, mirroring what the platform's own clients send.
  • service — write (services/storage_service.py::describe_columns) — writes
    KBC.description per column through the new client method. describe_batch picks
    this up for free via its columns: section.
  • service — read (services/storage_service.py::get_table_detail) — reads
    KBC.description from columnMetadata as the primary source, keeping the flat
    KBC.column.* keys as a fallback so descriptions written by an older kbagent stay
    visible here. When both carry a value, columnMetadata wins.
  • line budgetstorage_service.py is a grandfathered file that may not grow, so
    the repetitive five-branch per-column elif chain became a _COLUMN_META_FIELDS
    mapping. Adding a pass-through field is now a one-line data change, and the file
    stays at its 1733 ceiling.
  • docs — corrected the incorrect claim in the client and service docstrings, the
    describe-column command help, AGENT_CONTEXT, storage-describe-workflow.md,
    commands-reference.md, and a rewritten gotchas.md entry tagged (since v0.87.1).

Migration

There is no bulk migration command. Re-running describe-column (or
describe-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 a
different version.

Testing

  • make check green: lint, format, typecheck, skill-check, version-check,
    command-sync-check, changelog-check, error-codes, sentinel-guards, loc-check, and
    5703 tests passed.
  • New service tests: KBC.description read from columnMetadata; columnMetadata
    beating the legacy flat key when both are present.
  • Rewritten client tests: asserts the exact JSON columnsMetadata body and the
    application/json content type, plus the branch-scoped endpoint.
  • Updated write-path tests assert the legacy set_table_metadata call no longer happens
    for columns; the pre-existing flat-key read tests now stand as legacy-fallback
    regression cover.
  • Not verified against a live project — no live-project write access in this run. The
    endpoint contract is corroborated by the issue author's live reproduction on project
    9432 and by keboola/mcp-server's implementation.

Fixes #624

`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.
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.

storage describe-column writes column descriptions where the MCP server never reads them

1 participant