Skip to content

Commit bf99434

Browse files
author
BrokenDuck
committed
Add download test cases
1 parent 980e9b9 commit bf99434

File tree

2 files changed

+62
-3
lines changed

2 files changed

+62
-3
lines changed

mcp-run-python/src/files.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ export function registerFileFunctions(server: McpServer, rootDir: string) {
7878
// Check if it's text-based
7979
if (mime && /^(text\/|.*\/json$|.*\/csv$|.*\/javascript$|.*\/xml$)/.test(mime.split(';')[0])) {
8080
const text = new TextDecoder().decode(fileBytes)
81-
return { contents: [{ uri: uri.href, name: filename, mimeType: mime, text: text }] }
81+
return { contents: [{ uri: uri.href, mimeType: mime, text: text }] }
8282
} else {
83-
const base64 = encodeBase64(String.fromCharCode(...fileBytes))
84-
return { contents: [{ uri: uri.href, name: filename, mimeType: mime, blob: base64 }] }
83+
const base64 = encodeBase64(fileBytes)
84+
return { contents: [{ uri: uri.href, mimeType: mime, blob: base64 }] }
8585
}
8686
},
8787
)

mcp-run-python/test_mcp_servers.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations as _annotations
22

33
import asyncio
4+
import base64
45
import re
56
import secrets
67
import subprocess
@@ -20,6 +21,7 @@
2021
from mcp.client.sse import sse_client
2122
from mcp.client.stdio import stdio_client
2223
from mcp.client.streamable_http import streamablehttp_client
24+
from pydantic import FileUrl
2325

2426
if TYPE_CHECKING:
2527
from mcp import ClientSession
@@ -46,6 +48,8 @@ class McpTools(StrEnum):
4648
On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot foresee the pain and trouble that are bound to ensue; and equal blame belongs to those who fail in their duty through weakness of will, which is the same as saying through shrinking from toil and pain. These cases are perfectly simple and easy to distinguish. In a free hour, when our power of choice is untrammelled and when nothing prevents our being able to do what we like best, every pleasure is to be welcomed and every pain avoided. But in certain circumstances and owing to the claims of duty or the obligations of business it will frequently occur that pleasures have to be repudiated and annoyances accepted. The wise man therefore always holds in these matters to this principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains.
4749
"""
4850

51+
BASE_64_IMAGE = 'iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAAEX0lEQVR4nOzdO8vX9R/HcS/56f8PWotGQkPBBUWESCQYNJR0GjIn6UBTgUMZTiGE4ZgRVKNkuDSEFtgBQqIiKunkEFdkWLmEBQUWiNUQYd2KNwTPx+MGvD7Tk/f2/S7O7tmyatKnJx8b3f/p6EOj+5euu2Z0/+Sxt0f3N++9fHR/+57/j+7vuPuT0f3Vo+vwHycA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQtDr561+gDpzf9PLp/4eNzo/uXzv41uv/BM0+O7h9/bsPo/vqPdo3u7965GN13AUgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSlh5ce+XoA9+eODK6v3r7naP7b31zaHT/4p+3jO4f2/Tb6P7K41tH9zff+8LovgtAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkLb09ZmLow8sb1ke3d92YXR+1dO7PhzdX7f2xtH9Q5fN/t/g2j9eHt3/cc350X0XgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBtcf3eW0cfePTE7Pf1D9yxMrq/4YrR+VWvnN84uv/lvs2j+2v3nx3dv3rT/0b3XQDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmAtKWrzq0ffeD312f339h5ZnT/npsPj+7//cPDo/un739idP/Xg5+P7j/y/G2j+y4AaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQNpi/5FfRh94753XRvcP7F0zuv/V7e+O7t906v3R/WdP/zO6f9/ixdH9G3Z/NrrvApAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkLb25vDL6wLoHjo7ur7z03ej++u+fGt0/vm/2+/dfHF4e3d9xauPo/taN20b3XQDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmAtH8DAAD//9drYGg9ROu9AAAAAElFTkSuQmCC'
52+
4953

5054
@pytest.fixture
5155
def anyio_backend():
@@ -372,6 +376,61 @@ async def test_upload_files(
372376
assert createdFile.is_file()
373377
assert createdFile.read_text() == LOREM_IPSUM
374378

379+
@pytest.mark.parametrize('content_type', ['bytes', 'text'])
380+
async def test_download_files(
381+
self,
382+
mcp_session: ClientSession,
383+
server_type: Literal['stdio', 'sse', 'streamable_http'],
384+
mount: bool | str,
385+
content_type: Literal['bytes', 'text'],
386+
):
387+
if mount is False:
388+
pytest.skip('No directory mounted.')
389+
result = await mcp_session.initialize()
390+
391+
# Extract directory from response
392+
storageDir = self.get_dir_from_instructions(result.instructions)
393+
assert storageDir.is_dir()
394+
395+
match content_type:
396+
case 'bytes':
397+
filename = 'image.png'
398+
ctype = 'image/png'
399+
file_path = storageDir / filename
400+
file_path.write_bytes(base64.b64decode(BASE_64_IMAGE))
401+
402+
case 'text':
403+
filename = 'lorem.txt'
404+
ctype = 'text/plain'
405+
file_path = storageDir / filename
406+
file_path.write_text(LOREM_IPSUM)
407+
408+
result = await mcp_session.list_resources()
409+
410+
assert len(result.resources) == 1
411+
resource = result.resources[0]
412+
assert resource.name == filename
413+
assert resource.mimeType is not None
414+
assert resource.mimeType.startswith(ctype)
415+
assert str(resource.uri) == f'file:///{filename}'
416+
417+
result = await mcp_session.read_resource(FileUrl(f'file:///{filename}'))
418+
419+
assert len(result.contents) == 1
420+
resource = result.contents[0]
421+
assert str(resource.uri) == f'file:///{filename}'
422+
assert resource.mimeType is not None
423+
assert resource.mimeType.startswith(ctype)
424+
425+
match content_type:
426+
case 'bytes':
427+
assert isinstance(resource, types.BlobResourceContents)
428+
assert resource.blob == BASE_64_IMAGE
429+
430+
case 'text':
431+
assert isinstance(resource, types.TextResourceContents)
432+
assert resource.text == LOREM_IPSUM
433+
375434

376435
async def test_install_run_python_code() -> None:
377436
node_modules = Path(__file__).parent / 'node_modules'

0 commit comments

Comments
 (0)