"""Test tool calls through MCP client to verify Grist API interactions.""" import json from contextlib import asynccontextmanager import httpx import pytest from mcp import ClientSession from mcp.client.sse import sse_client GRIST_MCP_URL = "http://localhost:3000" MOCK_GRIST_URL = "http://localhost:8484" @asynccontextmanager async def create_mcp_session(): """Create and yield an MCP session.""" async with sse_client(f"{GRIST_MCP_URL}/sse") as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() yield session def get_mock_request_log(): """Get the request log from mock Grist server.""" with httpx.Client(base_url=MOCK_GRIST_URL, timeout=10.0) as client: return client.get("/_test/requests").json() def clear_mock_request_log(): """Clear the mock Grist request log.""" with httpx.Client(base_url=MOCK_GRIST_URL, timeout=10.0) as client: client.post("/_test/requests/clear") @pytest.mark.asyncio async def test_all_tools(services_ready): """Test all MCP tools - reads, writes, schema ops, and auth errors.""" async with create_mcp_session() as client: # ===== READ TOOLS ===== # Test list_documents clear_mock_request_log() result = await client.call_tool("list_documents", {}) assert len(result.content) == 1 data = json.loads(result.content[0].text) assert "documents" in data assert len(data["documents"]) == 1 assert data["documents"][0]["name"] == "test-doc" assert "read" in data["documents"][0]["permissions"] # Test list_tables clear_mock_request_log() result = await client.call_tool("list_tables", {"document": "test-doc"}) data = json.loads(result.content[0].text) assert "tables" in data assert "People" in data["tables"] assert "Tasks" in data["tables"] log = get_mock_request_log() assert any("/tables" in entry["path"] for entry in log) # Test describe_table clear_mock_request_log() result = await client.call_tool( "describe_table", {"document": "test-doc", "table": "People"} ) data = json.loads(result.content[0].text) assert "columns" in data column_ids = [c["id"] for c in data["columns"]] assert "Name" in column_ids assert "Age" in column_ids log = get_mock_request_log() assert any("/columns" in entry["path"] for entry in log) # Test get_records clear_mock_request_log() result = await client.call_tool( "get_records", {"document": "test-doc", "table": "People"} ) data = json.loads(result.content[0].text) assert "records" in data assert len(data["records"]) == 2 assert data["records"][0]["Name"] == "Alice" log = get_mock_request_log() assert any("/records" in entry["path"] and entry["method"] == "GET" for entry in log) # Test sql_query clear_mock_request_log() result = await client.call_tool( "sql_query", {"document": "test-doc", "query": "SELECT Name, Age FROM People"} ) data = json.loads(result.content[0].text) assert "records" in data assert len(data["records"]) >= 1 log = get_mock_request_log() assert any("/sql" in entry["path"] for entry in log) # ===== WRITE TOOLS ===== # Test add_records clear_mock_request_log() new_records = [ {"Name": "Charlie", "Age": 35, "Email": "charlie@example.com"} ] result = await client.call_tool( "add_records", {"document": "test-doc", "table": "People", "records": new_records} ) data = json.loads(result.content[0].text) assert "inserted_ids" in data assert len(data["inserted_ids"]) == 1 log = get_mock_request_log() post_requests = [e for e in log if e["method"] == "POST" and "/records" in e["path"]] assert len(post_requests) >= 1 assert post_requests[-1]["body"]["records"][0]["fields"]["Name"] == "Charlie" # Test update_records clear_mock_request_log() updates = [{"id": 1, "fields": {"Age": 31}}] result = await client.call_tool( "update_records", {"document": "test-doc", "table": "People", "records": updates} ) data = json.loads(result.content[0].text) assert "updated" in data log = get_mock_request_log() patch_requests = [e for e in log if e["method"] == "PATCH" and "/records" in e["path"]] assert len(patch_requests) >= 1 # Test delete_records clear_mock_request_log() result = await client.call_tool( "delete_records", {"document": "test-doc", "table": "People", "record_ids": [1, 2]} ) data = json.loads(result.content[0].text) assert "deleted" in data log = get_mock_request_log() delete_requests = [e for e in log if "/data/delete" in e["path"]] assert len(delete_requests) >= 1 assert delete_requests[-1]["body"] == [1, 2] # ===== SCHEMA TOOLS ===== # Test create_table clear_mock_request_log() columns = [ {"id": "Title", "type": "Text"}, {"id": "Count", "type": "Int"}, ] result = await client.call_tool( "create_table", {"document": "test-doc", "table_id": "NewTable", "columns": columns} ) data = json.loads(result.content[0].text) assert "table_id" in data log = get_mock_request_log() post_tables = [e for e in log if e["method"] == "POST" and e["path"].endswith("/tables")] assert len(post_tables) >= 1 # Test add_column clear_mock_request_log() result = await client.call_tool( "add_column", { "document": "test-doc", "table": "People", "column_id": "Phone", "column_type": "Text", } ) data = json.loads(result.content[0].text) assert "column_id" in data log = get_mock_request_log() post_cols = [e for e in log if e["method"] == "POST" and "/columns" in e["path"]] assert len(post_cols) >= 1 # Test modify_column clear_mock_request_log() result = await client.call_tool( "modify_column", { "document": "test-doc", "table": "People", "column_id": "Age", "type": "Numeric", } ) data = json.loads(result.content[0].text) assert "modified" in data log = get_mock_request_log() patch_cols = [e for e in log if e["method"] == "PATCH" and "/columns/" in e["path"]] assert len(patch_cols) >= 1 # Test delete_column clear_mock_request_log() result = await client.call_tool( "delete_column", { "document": "test-doc", "table": "People", "column_id": "Email", } ) data = json.loads(result.content[0].text) assert "deleted" in data log = get_mock_request_log() delete_cols = [e for e in log if e["method"] == "DELETE" and "/columns/" in e["path"]] assert len(delete_cols) >= 1 # ===== AUTHORIZATION ===== # Test unauthorized document fails result = await client.call_tool( "list_tables", {"document": "unauthorized-doc"} ) assert "error" in result.content[0].text.lower() or "authorization" in result.content[0].text.lower()