feat: replace MCP attachment tool with proxy endpoint
All checks were successful
Build and Push Docker Image / build (push) Successful in 8s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8s
The MCP tool approach was impractical because it required the LLM to generate large base64 strings token-by-token, causing timeouts. Changes: - Remove upload_attachment MCP tool - Add POST /api/v1/attachments endpoint for multipart/form-data uploads - Update proxy documentation to show both endpoints - Uses existing GristClient.upload_attachment() method - Requires write permission in session token
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import base64
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from grist_mcp.tools.write import add_records, update_records, delete_records, upload_attachment
|
||||
from grist_mcp.tools.write import add_records, update_records, delete_records
|
||||
from grist_mcp.auth import Authenticator, AuthError
|
||||
from grist_mcp.config import Config, Document, Token, TokenScope
|
||||
|
||||
@@ -96,105 +94,3 @@ async def test_delete_records(auth, mock_client):
|
||||
)
|
||||
|
||||
assert result == {"deleted": True}
|
||||
|
||||
|
||||
# Upload attachment tests
|
||||
|
||||
@pytest.fixture
|
||||
def mock_client_with_attachment():
|
||||
client = AsyncMock()
|
||||
client.upload_attachment.return_value = {
|
||||
"attachment_id": 42,
|
||||
"filename": "invoice.pdf",
|
||||
"size_bytes": 1024,
|
||||
}
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_attachment_success(auth, mock_client_with_attachment):
|
||||
agent = auth.authenticate("write-token")
|
||||
content = b"PDF content"
|
||||
content_base64 = base64.b64encode(content).decode()
|
||||
|
||||
result = await upload_attachment(
|
||||
agent, auth, "budget",
|
||||
filename="invoice.pdf",
|
||||
content_base64=content_base64,
|
||||
client=mock_client_with_attachment,
|
||||
)
|
||||
|
||||
assert result == {
|
||||
"attachment_id": 42,
|
||||
"filename": "invoice.pdf",
|
||||
"size_bytes": 1024,
|
||||
}
|
||||
mock_client_with_attachment.upload_attachment.assert_called_once_with(
|
||||
"invoice.pdf", content, "application/pdf"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_attachment_invalid_base64(auth, mock_client_with_attachment):
|
||||
agent = auth.authenticate("write-token")
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid base64 encoding"):
|
||||
await upload_attachment(
|
||||
agent, auth, "budget",
|
||||
filename="test.txt",
|
||||
content_base64="not-valid-base64!!!",
|
||||
client=mock_client_with_attachment,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_attachment_auth_required(auth, mock_client_with_attachment):
|
||||
agent = auth.authenticate("read-token")
|
||||
content_base64 = base64.b64encode(b"test").decode()
|
||||
|
||||
with pytest.raises(AuthError, match="Permission denied"):
|
||||
await upload_attachment(
|
||||
agent, auth, "budget",
|
||||
filename="test.txt",
|
||||
content_base64=content_base64,
|
||||
client=mock_client_with_attachment,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_attachment_mime_detection(auth, mock_client_with_attachment):
|
||||
agent = auth.authenticate("write-token")
|
||||
content = b"PNG content"
|
||||
content_base64 = base64.b64encode(content).decode()
|
||||
|
||||
await upload_attachment(
|
||||
agent, auth, "budget",
|
||||
filename="image.png",
|
||||
content_base64=content_base64,
|
||||
client=mock_client_with_attachment,
|
||||
)
|
||||
|
||||
# Should auto-detect image/png from filename
|
||||
mock_client_with_attachment.upload_attachment.assert_called_once_with(
|
||||
"image.png", content, "image/png"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_attachment_explicit_content_type(auth, mock_client_with_attachment):
|
||||
agent = auth.authenticate("write-token")
|
||||
content = b"custom content"
|
||||
content_base64 = base64.b64encode(content).decode()
|
||||
|
||||
await upload_attachment(
|
||||
agent, auth, "budget",
|
||||
filename="file.dat",
|
||||
content_base64=content_base64,
|
||||
content_type="application/custom",
|
||||
client=mock_client_with_attachment,
|
||||
)
|
||||
|
||||
# Should use explicit content type
|
||||
mock_client_with_attachment.upload_attachment.assert_called_once_with(
|
||||
"file.dat", content, "application/custom"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user