feat: add schema tools (create_table, add_column, modify_column, delete_column)
This commit is contained in:
84
src/grist_mcp/tools/schema.py
Normal file
84
src/grist_mcp/tools/schema.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""Schema tools - create and modify tables and columns."""
|
||||
|
||||
from grist_mcp.auth import Agent, Authenticator, Permission
|
||||
from grist_mcp.grist_client import GristClient
|
||||
|
||||
|
||||
async def create_table(
|
||||
agent: Agent,
|
||||
auth: Authenticator,
|
||||
document: str,
|
||||
table_id: str,
|
||||
columns: list[dict],
|
||||
client: GristClient | None = None,
|
||||
) -> dict:
|
||||
"""Create a new table with columns."""
|
||||
auth.authorize(agent, document, Permission.SCHEMA)
|
||||
|
||||
if client is None:
|
||||
doc = auth.get_document(document)
|
||||
client = GristClient(doc)
|
||||
|
||||
created_id = await client.create_table(table_id, columns)
|
||||
return {"table_id": created_id}
|
||||
|
||||
|
||||
async def add_column(
|
||||
agent: Agent,
|
||||
auth: Authenticator,
|
||||
document: str,
|
||||
table: str,
|
||||
column_id: str,
|
||||
column_type: str,
|
||||
formula: str | None = None,
|
||||
client: GristClient | None = None,
|
||||
) -> dict:
|
||||
"""Add a column to a table."""
|
||||
auth.authorize(agent, document, Permission.SCHEMA)
|
||||
|
||||
if client is None:
|
||||
doc = auth.get_document(document)
|
||||
client = GristClient(doc)
|
||||
|
||||
created_id = await client.add_column(table, column_id, column_type, formula=formula)
|
||||
return {"column_id": created_id}
|
||||
|
||||
|
||||
async def modify_column(
|
||||
agent: Agent,
|
||||
auth: Authenticator,
|
||||
document: str,
|
||||
table: str,
|
||||
column_id: str,
|
||||
type: str | None = None,
|
||||
formula: str | None = None,
|
||||
client: GristClient | None = None,
|
||||
) -> dict:
|
||||
"""Modify a column's type or formula."""
|
||||
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)
|
||||
return {"modified": True}
|
||||
|
||||
|
||||
async def delete_column(
|
||||
agent: Agent,
|
||||
auth: Authenticator,
|
||||
document: str,
|
||||
table: str,
|
||||
column_id: str,
|
||||
client: GristClient | None = None,
|
||||
) -> dict:
|
||||
"""Delete a column from a table."""
|
||||
auth.authorize(agent, document, Permission.SCHEMA)
|
||||
|
||||
if client is None:
|
||||
doc = auth.get_document(document)
|
||||
client = GristClient(doc)
|
||||
|
||||
await client.delete_column(table, column_id)
|
||||
return {"deleted": True}
|
||||
109
tests/test_tools_schema.py
Normal file
109
tests/test_tools_schema.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from grist_mcp.tools.schema import create_table, add_column, modify_column, delete_column
|
||||
from grist_mcp.auth import Authenticator, AuthError
|
||||
from grist_mcp.config import Config, Document, Token, TokenScope
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config():
|
||||
return Config(
|
||||
documents={
|
||||
"budget": Document(
|
||||
url="https://grist.example.com",
|
||||
doc_id="abc123",
|
||||
api_key="key",
|
||||
),
|
||||
},
|
||||
tokens=[
|
||||
Token(
|
||||
token="schema-token",
|
||||
name="schema-agent",
|
||||
scope=[TokenScope(document="budget", permissions=["read", "write", "schema"])],
|
||||
),
|
||||
Token(
|
||||
token="write-token",
|
||||
name="write-agent",
|
||||
scope=[TokenScope(document="budget", permissions=["read", "write"])],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def auth(config):
|
||||
return Authenticator(config)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_client():
|
||||
client = AsyncMock()
|
||||
client.create_table.return_value = "NewTable"
|
||||
client.add_column.return_value = "NewCol"
|
||||
client.modify_column.return_value = None
|
||||
client.delete_column.return_value = None
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_table(auth, mock_client):
|
||||
agent = auth.authenticate("schema-token")
|
||||
|
||||
result = await create_table(
|
||||
agent, auth, "budget", "NewTable",
|
||||
columns=[{"id": "Name", "type": "Text"}],
|
||||
client=mock_client,
|
||||
)
|
||||
|
||||
assert result == {"table_id": "NewTable"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_table_denied_without_schema(auth, mock_client):
|
||||
agent = auth.authenticate("write-token")
|
||||
|
||||
with pytest.raises(AuthError, match="Permission denied"):
|
||||
await create_table(
|
||||
agent, auth, "budget", "NewTable",
|
||||
columns=[{"id": "Name", "type": "Text"}],
|
||||
client=mock_client,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_column(auth, mock_client):
|
||||
agent = auth.authenticate("schema-token")
|
||||
|
||||
result = await add_column(
|
||||
agent, auth, "budget", "Table1", "NewCol", "Text",
|
||||
client=mock_client,
|
||||
)
|
||||
|
||||
assert result == {"column_id": "NewCol"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_modify_column(auth, mock_client):
|
||||
agent = auth.authenticate("schema-token")
|
||||
|
||||
result = await modify_column(
|
||||
agent, auth, "budget", "Table1", "Col1",
|
||||
type="Int",
|
||||
formula="$A + $B",
|
||||
client=mock_client,
|
||||
)
|
||||
|
||||
assert result == {"modified": True}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_column(auth, mock_client):
|
||||
agent = auth.authenticate("schema-token")
|
||||
|
||||
result = await delete_column(
|
||||
agent, auth, "budget", "Table1", "OldCol",
|
||||
client=mock_client,
|
||||
)
|
||||
|
||||
assert result == {"deleted": True}
|
||||
Reference in New Issue
Block a user