We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/gotalab/skillport'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Unit tests for Transport Mode (SPEC4)."""
import base64
from unittest.mock import patch
import pytest
from skillport.interfaces.mcp.instructions import build_xml_instructions
from skillport.interfaces.mcp.server import _get_registered_tools_list
from skillport.modules.skills.public.types import FileContent
from skillport.shared.config import Config
class TestRegisteredToolsList:
"""Tests for _get_registered_tools_list helper."""
def test_local_mode_two_tools(self):
"""Local mode (stdio) registers 2 tools."""
tools = _get_registered_tools_list(is_remote=False)
assert tools == ["search_skills", "load_skill"]
assert "read_skill_file" not in tools
def test_remote_mode_three_tools(self):
"""Remote mode (HTTP) registers 3 tools including read_skill_file."""
tools = _get_registered_tools_list(is_remote=True)
assert tools == ["search_skills", "load_skill", "read_skill_file"]
class TestDynamicInstructions:
"""Dynamic instruction generation tests."""
def test_local_mode_instructions_no_read_skill_file(self):
"""Local mode instructions don't mention read_skill_file in tools list."""
cfg = Config(core_skills_mode="none")
tools = ["search_skills", "load_skill"]
with patch(
"skillport.interfaces.mcp.instructions.get_core_skills",
return_value=[],
):
result = build_xml_instructions(cfg, tools)
# Should mention using native Read tool
assert "Use your Read tool" in result or "your native Read" in result
# Tools section should not have read_skill_file
lines = result.split("\n")
tools_section = False
tips_section = False
for line in lines:
if "## Tools" in line:
tools_section = True
if "## Tips" in line:
tips_section = True
tools_section = False
if tools_section and not tips_section:
if "read_skill_file" in line:
pytest.fail("read_skill_file should not be in Tools section for local mode")
def test_remote_mode_instructions_has_read_skill_file(self):
"""Remote mode instructions include read_skill_file."""
cfg = Config(core_skills_mode="none")
tools = ["search_skills", "load_skill", "read_skill_file"]
with patch(
"skillport.interfaces.mcp.instructions.get_core_skills",
return_value=[],
):
result = build_xml_instructions(cfg, tools)
# Should mention read_skill_file
assert "read_skill_file" in result
# Should mention encoding
assert "encoding" in result or "base64" in result
def test_instructions_workflow_differs_by_mode(self):
"""Workflow step 3 differs between local and remote mode."""
cfg = Config(core_skills_mode="none")
with patch(
"skillport.interfaces.mcp.instructions.get_core_skills",
return_value=[],
):
local = build_xml_instructions(cfg, ["search_skills", "load_skill"])
remote = build_xml_instructions(cfg, ["search_skills", "load_skill", "read_skill_file"])
# Local mentions Read tool
assert "Read tool" in local or "{path}/file" in local
# Remote mentions read_skill_file
assert "read_skill_file" in remote
def test_default_tools_when_none_provided(self):
"""When registered_tools is None, defaults to local mode."""
cfg = Config(core_skills_mode="none")
with patch(
"skillport.interfaces.mcp.instructions.get_core_skills",
return_value=[],
):
result = build_xml_instructions(cfg, None)
# Should be local mode (no read_skill_file in Tools)
assert "your Read tool" in result or "native Read" in result
class TestFileContentType:
"""FileContent type tests."""
def test_file_content_encoding_default_utf8(self):
"""encoding defaults to utf-8."""
fc = FileContent(content="hello", path="/test.txt", size=5)
assert fc.encoding == "utf-8"
def test_file_content_mime_type_default_text_plain(self):
"""mime_type defaults to text/plain."""
fc = FileContent(content="hello", path="/test.txt", size=5)
assert fc.mime_type == "text/plain"
def test_file_content_with_base64_encoding(self):
"""FileContent can have base64 encoding."""
fc = FileContent(
content="SGVsbG8=",
path="/test.bin",
size=5,
encoding="base64",
mime_type="application/octet-stream",
)
assert fc.encoding == "base64"
assert fc.mime_type == "application/octet-stream"
def test_file_content_with_image_mime_type(self):
"""FileContent can have image mime type."""
fc = FileContent(
content="iVBORw0KGgo=",
path="/test.png",
size=100,
encoding="base64",
mime_type="image/png",
)
assert fc.mime_type == "image/png"
class TestReadSkillFileBinarySupport:
"""Binary file support in read_skill_file."""
def test_text_file_returns_utf8(self, tmp_path):
"""Text files return encoding=utf-8."""
# Setup test skill
skill_dir = tmp_path / "test-skill"
skill_dir.mkdir()
text_file = skill_dir / "readme.txt"
text_file.write_text("Hello, World!", encoding="utf-8")
skill_record = {
"id": "test-skill",
"path": str(skill_dir),
"category": "",
}
with patch(
"skillport.modules.skills.public.read.idx_get_by_id",
return_value=skill_record,
):
from skillport.modules.skills.public.read import read_skill_file
cfg = Config()
result = read_skill_file("test-skill", "readme.txt", config=cfg)
assert result.encoding == "utf-8"
assert result.content == "Hello, World!"
assert "text" in result.mime_type
def test_binary_file_returns_base64(self, tmp_path):
"""Binary files return encoding=base64."""
# Setup test skill with binary file
skill_dir = tmp_path / "test-skill"
skill_dir.mkdir()
binary_file = skill_dir / "image.png"
# Minimal PNG header
png_bytes = bytes([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
binary_file.write_bytes(png_bytes)
skill_record = {
"id": "test-skill",
"path": str(skill_dir),
"category": "",
}
with patch(
"skillport.modules.skills.public.read.idx_get_by_id",
return_value=skill_record,
):
from skillport.modules.skills.public.read import read_skill_file
cfg = Config()
result = read_skill_file("test-skill", "image.png", config=cfg)
assert result.encoding == "base64"
assert result.mime_type == "image/png"
# Verify base64 can be decoded
decoded = base64.b64decode(result.content)
assert decoded == png_bytes
def test_json_file_returns_utf8(self, tmp_path):
"""JSON files (text extension) return encoding=utf-8."""
skill_dir = tmp_path / "test-skill"
skill_dir.mkdir()
json_file = skill_dir / "config.json"
json_file.write_text('{"key": "value"}', encoding="utf-8")
skill_record = {
"id": "test-skill",
"path": str(skill_dir),
"category": "",
}
with patch(
"skillport.modules.skills.public.read.idx_get_by_id",
return_value=skill_record,
):
from skillport.modules.skills.public.read import read_skill_file
cfg = Config()
result = read_skill_file("test-skill", "config.json", config=cfg)
assert result.encoding == "utf-8"
assert result.content == '{"key": "value"}'
def test_yaml_file_returns_utf8(self, tmp_path):
"""YAML files return encoding=utf-8."""
skill_dir = tmp_path / "test-skill"
skill_dir.mkdir()
yaml_file = skill_dir / "config.yaml"
yaml_file.write_text("key: value", encoding="utf-8")
skill_record = {
"id": "test-skill",
"path": str(skill_dir),
"category": "",
}
with patch(
"skillport.modules.skills.public.read.idx_get_by_id",
return_value=skill_record,
):
from skillport.modules.skills.public.read import read_skill_file
cfg = Config()
result = read_skill_file("test-skill", "config.yaml", config=cfg)
assert result.encoding == "utf-8"
assert result.content == "key: value"
def test_unknown_extension_binary_returns_base64(self, tmp_path):
"""Unknown extensions with binary content return base64."""
skill_dir = tmp_path / "test-skill"
skill_dir.mkdir()
# Use a truly unknown extension
bin_file = skill_dir / "data.qqqxxx"
bin_file.write_bytes(bytes([0x00, 0x01, 0x02, 0xFF]))
skill_record = {
"id": "test-skill",
"path": str(skill_dir),
"category": "",
}
with patch(
"skillport.modules.skills.public.read.idx_get_by_id",
return_value=skill_record,
):
from skillport.modules.skills.public.read import read_skill_file
cfg = Config()
result = read_skill_file("test-skill", "data.qqqxxx", config=cfg)
assert result.encoding == "base64"
assert result.mime_type == "application/octet-stream"