Compare commits

...

3 Commits

Author SHA1 Message Date
77bf95817d chore: update uv.lock
All checks were successful
Build and Push Docker Image / build (push) Successful in 8s
2026-01-26 15:23:21 -05:00
29a72ab005 docs: update changelog for v1.5.0 column label support 2026-01-26 15:19:20 -05:00
33bb464102 feat: add label parameter to add_column and modify_column tools
Allow setting a human-readable display label for columns, separate from
the column_id used in formulas and API calls. The label defaults to the
column_id if not provided.
2026-01-26 15:18:11 -05:00
7 changed files with 114 additions and 6 deletions

View File

@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.0] - 2026-01-26
### Added
#### Column Label Support
- **`add_column`**: New optional `label` parameter for setting display name
- **`modify_column`**: New optional `label` parameter for updating display name
Labels are human-readable names shown in Grist column headers, separate from the `column_id` used in formulas and API calls. If not provided, Grist defaults the label to the column ID.
#### Usage
```python
# Create column with display label
add_column(document="crm", table="Contacts", column_id="first_name", column_type="Text", label="First Name")
# Update existing column's label
modify_column(document="crm", table="Contacts", column_id="first_name", label="Given Name")
```
## [1.4.1] - 2026-01-14
### Added

View File

@@ -203,11 +203,14 @@ class GristClient:
column_id: str,
column_type: str,
formula: str | None = None,
label: str | None = None,
) -> str:
"""Add a column to a table. Returns column ID."""
fields = {"type": column_type}
if formula:
fields["formula"] = formula
if label:
fields["label"] = label
payload = {"columns": [{"id": column_id, "fields": fields}]}
data = await self._request("POST", f"/tables/{table}/columns", json=payload)
@@ -219,13 +222,16 @@ class GristClient:
column_id: str,
type: str | None = None,
formula: str | None = None,
label: str | None = None,
) -> None:
"""Modify a column's type or formula."""
"""Modify a column's type, formula, or label."""
fields = {}
if type is not None:
fields["type"] = type
if formula is not None:
fields["formula"] = formula
if label is not None:
fields["label"] = label
payload = {"columns": [{"id": column_id, "fields": fields}]}
await self._request("PATCH", f"/tables/{table}/columns", json=payload)

View File

@@ -186,13 +186,14 @@ def create_server(
"column_id": {"type": "string"},
"column_type": {"type": "string"},
"formula": {"type": "string"},
"label": {"type": "string", "description": "Display label for the column"},
},
"required": ["document", "table", "column_id", "column_type"],
},
),
Tool(
name="modify_column",
description="Modify a column's type or formula",
description="Modify a column's type, formula, or label",
inputSchema={
"type": "object",
"properties": {
@@ -201,6 +202,7 @@ def create_server(
"column_id": {"type": "string"},
"type": {"type": "string"},
"formula": {"type": "string"},
"label": {"type": "string", "description": "Display label for the column"},
},
"required": ["document", "table", "column_id"],
},
@@ -311,6 +313,7 @@ def create_server(
_current_agent, auth, arguments["document"], arguments["table"],
arguments["column_id"], arguments["column_type"],
formula=arguments.get("formula"),
label=arguments.get("label"),
)
elif name == "modify_column":
result = await _modify_column(
@@ -318,6 +321,7 @@ def create_server(
arguments["column_id"],
type=arguments.get("type"),
formula=arguments.get("formula"),
label=arguments.get("label"),
)
elif name == "delete_column":
result = await _delete_column(

View File

@@ -31,6 +31,7 @@ async def add_column(
column_id: str,
column_type: str,
formula: str | None = None,
label: str | None = None,
client: GristClient | None = None,
) -> dict:
"""Add a column to a table."""
@@ -40,7 +41,9 @@ async def add_column(
doc = auth.get_document(document)
client = GristClient(doc)
created_id = await client.add_column(table, column_id, column_type, formula=formula)
created_id = await client.add_column(
table, column_id, column_type, formula=formula, label=label
)
return {"column_id": created_id}
@@ -52,16 +55,17 @@ async def modify_column(
column_id: str,
type: str | None = None,
formula: str | None = None,
label: str | None = None,
client: GristClient | None = None,
) -> dict:
"""Modify a column's type or formula."""
"""Modify a column's type, formula, or label."""
auth.authorize(agent, document, Permission.SCHEMA)
if client is None:
doc = auth.get_document(document)
client = GristClient(doc)
await client.modify_column(table, column_id, type=type, formula=formula)
await client.modify_column(table, column_id, type=type, formula=formula, label=label)
return {"modified": True}

View File

@@ -155,6 +155,27 @@ async def test_add_column(client, httpx_mock: HTTPXMock):
col_id = await client.add_column("Table1", "NewCol", "Text", formula=None)
assert col_id == "NewCol"
request = httpx_mock.get_request()
import json
payload = json.loads(request.content)
assert payload == {"columns": [{"id": "NewCol", "fields": {"type": "Text"}}]}
@pytest.mark.asyncio
async def test_add_column_with_label(client, httpx_mock: HTTPXMock):
httpx_mock.add_response(
url="https://grist.example.com/api/docs/abc123/tables/Table1/columns",
method="POST",
json={"columns": [{"id": "first_name"}]},
)
col_id = await client.add_column("Table1", "first_name", "Text", label="First Name")
assert col_id == "first_name"
request = httpx_mock.get_request()
import json
payload = json.loads(request.content)
assert payload == {"columns": [{"id": "first_name", "fields": {"type": "Text", "label": "First Name"}}]}
@pytest.mark.asyncio
@@ -169,6 +190,22 @@ async def test_modify_column(client, httpx_mock: HTTPXMock):
await client.modify_column("Table1", "Amount", type="Int", formula="$Price * $Qty")
@pytest.mark.asyncio
async def test_modify_column_with_label(client, httpx_mock: HTTPXMock):
httpx_mock.add_response(
url="https://grist.example.com/api/docs/abc123/tables/Table1/columns",
method="PATCH",
json={},
)
await client.modify_column("Table1", "Col1", label="Column One")
request = httpx_mock.get_request()
import json
payload = json.loads(request.content)
assert payload == {"columns": [{"id": "Col1", "fields": {"label": "Column One"}}]}
@pytest.mark.asyncio
async def test_delete_column(client, httpx_mock: HTTPXMock):
httpx_mock.add_response(

View File

@@ -81,6 +81,25 @@ async def test_add_column(auth, mock_client):
)
assert result == {"column_id": "NewCol"}
mock_client.add_column.assert_called_once_with(
"Table1", "NewCol", "Text", formula=None, label=None
)
@pytest.mark.asyncio
async def test_add_column_with_label(auth, mock_client):
agent = auth.authenticate("schema-token")
result = await add_column(
agent, auth, "budget", "Table1", "first_name", "Text",
label="First Name",
client=mock_client,
)
assert result == {"column_id": "NewCol"}
mock_client.add_column.assert_called_once_with(
"Table1", "first_name", "Text", formula=None, label="First Name"
)
@pytest.mark.asyncio
@@ -95,6 +114,25 @@ async def test_modify_column(auth, mock_client):
)
assert result == {"modified": True}
mock_client.modify_column.assert_called_once_with(
"Table1", "Col1", type="Int", formula="$A + $B", label=None
)
@pytest.mark.asyncio
async def test_modify_column_with_label(auth, mock_client):
agent = auth.authenticate("schema-token")
result = await modify_column(
agent, auth, "budget", "Table1", "Col1",
label="Column One",
client=mock_client,
)
assert result == {"modified": True}
mock_client.modify_column.assert_called_once_with(
"Table1", "Col1", type=None, formula=None, label="Column One"
)
@pytest.mark.asyncio

2
uv.lock generated
View File

@@ -153,7 +153,7 @@ wheels = [
[[package]]
name = "grist-mcp"
version = "1.4.0"
version = "1.4.1"
source = { editable = "." }
dependencies = [
{ name = "httpx" },