Add MCP configuration and setup scripts
Add complete setup infrastructure for MCP integration: - example-mcp-config.json: Template Claude Code MCP configuration - setup_mcp.sh: Automated one-command setup script - test_mcp_server.py: Comprehensive test suite (25 tests, 100% pass) The setup script automates: - Dependency installation - Configuration file generation with absolute paths - Claude Code config directory creation - Validation and verification Tests cover: - All 6 MCP tool functions - Error handling and edge cases - Config validation - Page estimation - Skill packaging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
11
example-mcp-config.json
Normal file
11
example-mcp-config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"skill-seeker": {
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"/mnt/1ece809a-2821-4f10-aecb-fcdf34760c0b/Git/Skill_Seekers/mcp/server.py"
|
||||
],
|
||||
"cwd": "/mnt/1ece809a-2821-4f10-aecb-fcdf34760c0b/Git/Skill_Seekers"
|
||||
}
|
||||
}
|
||||
}
|
||||
222
setup_mcp.sh
Executable file
222
setup_mcp.sh
Executable file
@@ -0,0 +1,222 @@
|
||||
#!/bin/bash
|
||||
# Skill Seeker MCP Server - Quick Setup Script
|
||||
# This script automates the MCP server setup for Claude Code
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "=================================================="
|
||||
echo "Skill Seeker MCP Server - Quick Setup"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Step 1: Check Python version
|
||||
echo "Step 1: Checking Python version..."
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo -e "${RED}❌ Error: python3 not found${NC}"
|
||||
echo "Please install Python 3.7 or higher"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PYTHON_VERSION=$(python3 --version | cut -d' ' -f2)
|
||||
echo -e "${GREEN}✓${NC} Python $PYTHON_VERSION found"
|
||||
echo ""
|
||||
|
||||
# Step 2: Get repository path
|
||||
REPO_PATH=$(pwd)
|
||||
echo "Step 2: Repository location"
|
||||
echo "Path: $REPO_PATH"
|
||||
echo ""
|
||||
|
||||
# Step 3: Install dependencies
|
||||
echo "Step 3: Installing Python dependencies..."
|
||||
echo "This will install: mcp, requests, beautifulsoup4"
|
||||
read -p "Continue? (y/n) " -n 1 -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Installing MCP server dependencies..."
|
||||
pip3 install -r mcp/requirements.txt || {
|
||||
echo -e "${RED}❌ Failed to install MCP dependencies${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Installing CLI tool dependencies..."
|
||||
pip3 install requests beautifulsoup4 || {
|
||||
echo -e "${RED}❌ Failed to install CLI dependencies${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo -e "${GREEN}✓${NC} Dependencies installed successfully"
|
||||
else
|
||||
echo "Skipping dependency installation"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 4: Test MCP server
|
||||
echo "Step 4: Testing MCP server..."
|
||||
timeout 3 python3 mcp/server.py 2>/dev/null || {
|
||||
if [ $? -eq 124 ]; then
|
||||
echo -e "${GREEN}✓${NC} MCP server starts correctly (timeout expected)"
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} MCP server test inconclusive, but may still work"
|
||||
fi
|
||||
}
|
||||
echo ""
|
||||
|
||||
# Step 5: Optional - Run tests
|
||||
echo "Step 5: Run test suite? (optional)"
|
||||
read -p "Run MCP tests to verify everything works? (y/n) " -n 1 -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
# Check if pytest is installed
|
||||
if ! command -v pytest &> /dev/null; then
|
||||
echo "Installing pytest..."
|
||||
pip3 install pytest || {
|
||||
echo -e "${YELLOW}⚠${NC} Could not install pytest, skipping tests"
|
||||
}
|
||||
fi
|
||||
|
||||
if command -v pytest &> /dev/null; then
|
||||
echo "Running MCP server tests..."
|
||||
python3 -m pytest tests/test_mcp_server.py -v --tb=short || {
|
||||
echo -e "${RED}❌ Some tests failed${NC}"
|
||||
echo "The server may still work, but please check the errors above"
|
||||
}
|
||||
fi
|
||||
else
|
||||
echo "Skipping tests"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 6: Configure Claude Code
|
||||
echo "Step 6: Configure Claude Code"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
echo "You need to add this configuration to Claude Code:"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Configuration file:${NC} ~/.config/claude-code/mcp.json"
|
||||
echo ""
|
||||
echo "Add this JSON configuration:"
|
||||
echo ""
|
||||
echo -e "${GREEN}{"
|
||||
echo " \"mcpServers\": {"
|
||||
echo " \"skill-seeker\": {"
|
||||
echo " \"command\": \"python3\","
|
||||
echo " \"args\": ["
|
||||
echo " \"$REPO_PATH/mcp/server.py\""
|
||||
echo " ],"
|
||||
echo " \"cwd\": \"$REPO_PATH\""
|
||||
echo " }"
|
||||
echo " }"
|
||||
echo -e "}${NC}"
|
||||
echo ""
|
||||
echo "To configure automatically, run:"
|
||||
echo ""
|
||||
echo -e "${YELLOW} mkdir -p ~/.config/claude-code${NC}"
|
||||
echo ""
|
||||
echo "Then edit ~/.config/claude-code/mcp.json and add the configuration above"
|
||||
echo ""
|
||||
echo "Or use this one-liner (BE CAREFUL - this may overwrite existing config):"
|
||||
echo ""
|
||||
echo -e "${RED}cat > ~/.config/claude-code/mcp.json << 'EOF'
|
||||
{
|
||||
\"mcpServers\": {
|
||||
\"skill-seeker\": {
|
||||
\"command\": \"python3\",
|
||||
\"args\": [
|
||||
\"$REPO_PATH/mcp/server.py\"
|
||||
],
|
||||
\"cwd\": \"$REPO_PATH\"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF${NC}"
|
||||
echo ""
|
||||
|
||||
# Ask if user wants auto-configure
|
||||
echo ""
|
||||
read -p "Auto-configure Claude Code now? (y/n) " -n 1 -r
|
||||
echo ""
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
# Check if config already exists
|
||||
if [ -f ~/.config/claude-code/mcp.json ]; then
|
||||
echo -e "${YELLOW}⚠ Warning: ~/.config/claude-code/mcp.json already exists${NC}"
|
||||
echo "Current contents:"
|
||||
cat ~/.config/claude-code/mcp.json
|
||||
echo ""
|
||||
read -p "Overwrite? (y/n) " -n 1 -r
|
||||
echo ""
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Skipping auto-configuration"
|
||||
echo "Please manually add the skill-seeker server to your config"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create config directory
|
||||
mkdir -p ~/.config/claude-code
|
||||
|
||||
# Write configuration
|
||||
cat > ~/.config/claude-code/mcp.json << EOF
|
||||
{
|
||||
"mcpServers": {
|
||||
"skill-seeker": {
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"$REPO_PATH/mcp/server.py"
|
||||
],
|
||||
"cwd": "$REPO_PATH"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓${NC} Configuration written to ~/.config/claude-code/mcp.json"
|
||||
else
|
||||
echo "Skipping auto-configuration"
|
||||
echo "Please manually configure Claude Code using the JSON above"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 7: Final instructions
|
||||
echo "=================================================="
|
||||
echo "Setup Complete!"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo ""
|
||||
echo " 1. ${YELLOW}Restart Claude Code${NC} (quit and reopen, don't just close window)"
|
||||
echo " 2. In Claude Code, test with: ${GREEN}\"List all available configs\"${NC}"
|
||||
echo " 3. You should see 6 Skill Seeker tools available"
|
||||
echo ""
|
||||
echo "Available MCP Tools:"
|
||||
echo " • generate_config - Create new config files"
|
||||
echo " • estimate_pages - Estimate scraping time"
|
||||
echo " • scrape_docs - Scrape documentation"
|
||||
echo " • package_skill - Create .zip files"
|
||||
echo " • list_configs - Show available configs"
|
||||
echo " • validate_config - Validate config files"
|
||||
echo ""
|
||||
echo "Example commands to try in Claude Code:"
|
||||
echo " • ${GREEN}List all available configs${NC}"
|
||||
echo " • ${GREEN}Validate configs/react.json${NC}"
|
||||
echo " • ${GREEN}Generate config for Tailwind at https://tailwindcss.com/docs${NC}"
|
||||
echo ""
|
||||
echo "Documentation:"
|
||||
echo " • MCP Setup Guide: ${YELLOW}docs/MCP_SETUP.md${NC}"
|
||||
echo " • Full docs: ${YELLOW}README.md${NC}"
|
||||
echo ""
|
||||
echo "Troubleshooting:"
|
||||
echo " • Check logs: ~/Library/Logs/Claude Code/ (macOS)"
|
||||
echo " • Test server: python3 mcp/server.py"
|
||||
echo " • Run tests: python3 -m pytest tests/test_mcp_server.py -v"
|
||||
echo ""
|
||||
echo "Happy skill creating! 🚀"
|
||||
621
tests/test_mcp_server.py
Normal file
621
tests/test_mcp_server.py
Normal file
@@ -0,0 +1,621 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive test suite for Skill Seeker MCP Server
|
||||
Tests all MCP tools and server functionality
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
import tempfile
|
||||
import shutil
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch, AsyncMock, MagicMock
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
# Import MCP package components (from installed package)
|
||||
try:
|
||||
from mcp.server import Server
|
||||
from mcp.types import Tool, TextContent
|
||||
MCP_AVAILABLE = True
|
||||
except ImportError:
|
||||
MCP_AVAILABLE = False
|
||||
print("Warning: MCP package not available, skipping MCP tests")
|
||||
|
||||
# Import our local MCP server module
|
||||
if MCP_AVAILABLE:
|
||||
# Add mcp directory to path to import our server module
|
||||
mcp_dir = Path(__file__).parent.parent / "mcp"
|
||||
sys.path.insert(0, str(mcp_dir))
|
||||
try:
|
||||
import server as skill_seeker_server
|
||||
except ImportError as e:
|
||||
print(f"Warning: Could not import skill_seeker server: {e}")
|
||||
skill_seeker_server = None
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestMCPServerInitialization(unittest.TestCase):
|
||||
"""Test MCP server initialization"""
|
||||
|
||||
def test_server_import(self):
|
||||
"""Test that server module can be imported"""
|
||||
from mcp import server as mcp_server_module
|
||||
self.assertIsNotNone(mcp_server_module)
|
||||
|
||||
def test_server_initialization(self):
|
||||
"""Test server initializes correctly"""
|
||||
import mcp.server
|
||||
app = mcp.server.Server("test-skill-seeker")
|
||||
self.assertEqual(app.name, "test-skill-seeker")
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestListTools(unittest.IsolatedAsyncioTestCase):
|
||||
"""Test list_tools functionality"""
|
||||
|
||||
async def test_list_tools_returns_tools(self):
|
||||
"""Test that list_tools returns all expected tools"""
|
||||
tools = await skill_seeker_server.list_tools()
|
||||
|
||||
self.assertIsInstance(tools, list)
|
||||
self.assertGreater(len(tools), 0)
|
||||
|
||||
# Check all expected tools are present
|
||||
tool_names = [tool.name for tool in tools]
|
||||
expected_tools = [
|
||||
"generate_config",
|
||||
"estimate_pages",
|
||||
"scrape_docs",
|
||||
"package_skill",
|
||||
"list_configs",
|
||||
"validate_config"
|
||||
]
|
||||
|
||||
for expected in expected_tools:
|
||||
self.assertIn(expected, tool_names, f"Missing tool: {expected}")
|
||||
|
||||
async def test_tool_schemas(self):
|
||||
"""Test that all tools have valid schemas"""
|
||||
tools = await skill_seeker_server.list_tools()
|
||||
|
||||
for tool in tools:
|
||||
self.assertIsInstance(tool.name, str)
|
||||
self.assertIsInstance(tool.description, str)
|
||||
self.assertIn("inputSchema", tool.__dict__)
|
||||
|
||||
# Verify schema has required structure
|
||||
schema = tool.inputSchema
|
||||
self.assertEqual(schema["type"], "object")
|
||||
self.assertIn("properties", schema)
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestGenerateConfigTool(unittest.IsolatedAsyncioTestCase):
|
||||
"""Test generate_config tool"""
|
||||
|
||||
async def asyncSetUp(self):
|
||||
"""Set up test environment"""
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.original_cwd = os.getcwd()
|
||||
os.chdir(self.temp_dir)
|
||||
|
||||
async def asyncTearDown(self):
|
||||
"""Clean up test environment"""
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
async def test_generate_config_basic(self):
|
||||
"""Test basic config generation"""
|
||||
args = {
|
||||
"name": "test-framework",
|
||||
"url": "https://test-framework.dev/",
|
||||
"description": "Test framework skill"
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.generate_config_tool(args)
|
||||
|
||||
self.assertIsInstance(result, list)
|
||||
self.assertGreater(len(result), 0)
|
||||
self.assertIsInstance(result[0], TextContent)
|
||||
self.assertIn("✅", result[0].text)
|
||||
|
||||
# Verify config file was created
|
||||
config_path = Path("configs/test-framework.json")
|
||||
self.assertTrue(config_path.exists())
|
||||
|
||||
# Verify config content
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
self.assertEqual(config["name"], "test-framework")
|
||||
self.assertEqual(config["base_url"], "https://test-framework.dev/")
|
||||
self.assertEqual(config["description"], "Test framework skill")
|
||||
|
||||
async def test_generate_config_with_options(self):
|
||||
"""Test config generation with custom options"""
|
||||
args = {
|
||||
"name": "custom-framework",
|
||||
"url": "https://custom.dev/",
|
||||
"description": "Custom skill",
|
||||
"max_pages": 200,
|
||||
"rate_limit": 1.0
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.generate_config_tool(args)
|
||||
|
||||
# Verify config has custom options
|
||||
config_path = Path("configs/custom-framework.json")
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
self.assertEqual(config["max_pages"], 200)
|
||||
self.assertEqual(config["rate_limit"], 1.0)
|
||||
|
||||
async def test_generate_config_defaults(self):
|
||||
"""Test that default values are applied correctly"""
|
||||
args = {
|
||||
"name": "default-test",
|
||||
"url": "https://test.dev/",
|
||||
"description": "Test defaults"
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.generate_config_tool(args)
|
||||
|
||||
config_path = Path("configs/default-test.json")
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
self.assertEqual(config["max_pages"], 100) # Default
|
||||
self.assertEqual(config["rate_limit"], 0.5) # Default
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestEstimatePagesTool(unittest.IsolatedAsyncioTestCase):
|
||||
"""Test estimate_pages tool"""
|
||||
|
||||
async def asyncSetUp(self):
|
||||
"""Set up test environment"""
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.original_cwd = os.getcwd()
|
||||
os.chdir(self.temp_dir)
|
||||
|
||||
# Create a test config
|
||||
os.makedirs("configs", exist_ok=True)
|
||||
self.config_path = Path("configs/test.json")
|
||||
config_data = {
|
||||
"name": "test",
|
||||
"base_url": "https://example.com/",
|
||||
"selectors": {
|
||||
"main_content": "article",
|
||||
"title": "h1",
|
||||
"code_blocks": "pre"
|
||||
},
|
||||
"rate_limit": 0.5,
|
||||
"max_pages": 50
|
||||
}
|
||||
with open(self.config_path, 'w') as f:
|
||||
json.dump(config_data, f)
|
||||
|
||||
async def asyncTearDown(self):
|
||||
"""Clean up test environment"""
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_estimate_pages_success(self, mock_run):
|
||||
"""Test successful page estimation"""
|
||||
# Mock successful subprocess run
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "Estimated 50 pages"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"config_path": str(self.config_path)
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.estimate_pages_tool(args)
|
||||
|
||||
self.assertIsInstance(result, list)
|
||||
self.assertIsInstance(result[0], TextContent)
|
||||
self.assertIn("50 pages", result[0].text)
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_estimate_pages_with_max_discovery(self, mock_run):
|
||||
"""Test page estimation with custom max_discovery"""
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "Estimated 100 pages"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"config_path": str(self.config_path),
|
||||
"max_discovery": 500
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.estimate_pages_tool(args)
|
||||
|
||||
# Verify subprocess was called with correct args
|
||||
mock_run.assert_called_once()
|
||||
call_args = mock_run.call_args[0][0]
|
||||
self.assertIn("--max-discovery", call_args)
|
||||
self.assertIn("500", call_args)
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_estimate_pages_error(self, mock_run):
|
||||
"""Test error handling in page estimation"""
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 1
|
||||
mock_result.stderr = "Config file not found"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"config_path": "nonexistent.json"
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.estimate_pages_tool(args)
|
||||
|
||||
self.assertIn("Error", result[0].text)
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestScrapeDocsTool(unittest.IsolatedAsyncioTestCase):
|
||||
"""Test scrape_docs tool"""
|
||||
|
||||
async def asyncSetUp(self):
|
||||
"""Set up test environment"""
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.original_cwd = os.getcwd()
|
||||
os.chdir(self.temp_dir)
|
||||
|
||||
# Create test config
|
||||
os.makedirs("configs", exist_ok=True)
|
||||
self.config_path = Path("configs/test.json")
|
||||
config_data = {
|
||||
"name": "test",
|
||||
"base_url": "https://example.com/",
|
||||
"selectors": {
|
||||
"main_content": "article",
|
||||
"title": "h1",
|
||||
"code_blocks": "pre"
|
||||
}
|
||||
}
|
||||
with open(self.config_path, 'w') as f:
|
||||
json.dump(config_data, f)
|
||||
|
||||
async def asyncTearDown(self):
|
||||
"""Clean up test environment"""
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_scrape_docs_basic(self, mock_run):
|
||||
"""Test basic documentation scraping"""
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "Scraping completed successfully"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"config_path": str(self.config_path)
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.scrape_docs_tool(args)
|
||||
|
||||
self.assertIsInstance(result, list)
|
||||
self.assertIn("success", result[0].text.lower())
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_scrape_docs_with_skip_scrape(self, mock_run):
|
||||
"""Test scraping with skip_scrape flag"""
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "Using cached data"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"config_path": str(self.config_path),
|
||||
"skip_scrape": True
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.scrape_docs_tool(args)
|
||||
|
||||
# Verify --skip-scrape was passed
|
||||
call_args = mock_run.call_args[0][0]
|
||||
self.assertIn("--skip-scrape", call_args)
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_scrape_docs_with_dry_run(self, mock_run):
|
||||
"""Test scraping with dry_run flag"""
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "Dry run completed"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"config_path": str(self.config_path),
|
||||
"dry_run": True
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.scrape_docs_tool(args)
|
||||
|
||||
call_args = mock_run.call_args[0][0]
|
||||
self.assertIn("--dry-run", call_args)
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_scrape_docs_with_enhance_local(self, mock_run):
|
||||
"""Test scraping with local enhancement"""
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "Scraping with enhancement"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"config_path": str(self.config_path),
|
||||
"enhance_local": True
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.scrape_docs_tool(args)
|
||||
|
||||
call_args = mock_run.call_args[0][0]
|
||||
self.assertIn("--enhance-local", call_args)
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestPackageSkillTool(unittest.IsolatedAsyncioTestCase):
|
||||
"""Test package_skill tool"""
|
||||
|
||||
async def asyncSetUp(self):
|
||||
"""Set up test environment"""
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.original_cwd = os.getcwd()
|
||||
os.chdir(self.temp_dir)
|
||||
|
||||
# Create a mock skill directory
|
||||
self.skill_dir = Path("output/test-skill")
|
||||
self.skill_dir.mkdir(parents=True)
|
||||
(self.skill_dir / "SKILL.md").write_text("# Test Skill")
|
||||
(self.skill_dir / "references").mkdir()
|
||||
(self.skill_dir / "references/index.md").write_text("# Index")
|
||||
|
||||
async def asyncTearDown(self):
|
||||
"""Clean up test environment"""
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_package_skill_success(self, mock_run):
|
||||
"""Test successful skill packaging"""
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 0
|
||||
mock_result.stdout = "Package created: test-skill.zip"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"skill_dir": str(self.skill_dir)
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.package_skill_tool(args)
|
||||
|
||||
self.assertIsInstance(result, list)
|
||||
self.assertIn("test-skill", result[0].text)
|
||||
|
||||
@patch('subprocess.run')
|
||||
async def test_package_skill_error(self, mock_run):
|
||||
"""Test error handling in skill packaging"""
|
||||
mock_result = MagicMock()
|
||||
mock_result.returncode = 1
|
||||
mock_result.stderr = "Directory not found"
|
||||
mock_run.return_value = mock_result
|
||||
|
||||
args = {
|
||||
"skill_dir": "nonexistent-dir"
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.package_skill_tool(args)
|
||||
|
||||
self.assertIn("Error", result[0].text)
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestListConfigsTool(unittest.IsolatedAsyncioTestCase):
|
||||
"""Test list_configs tool"""
|
||||
|
||||
async def asyncSetUp(self):
|
||||
"""Set up test environment"""
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.original_cwd = os.getcwd()
|
||||
os.chdir(self.temp_dir)
|
||||
|
||||
# Create test configs
|
||||
os.makedirs("configs", exist_ok=True)
|
||||
|
||||
configs = [
|
||||
{
|
||||
"name": "test1",
|
||||
"description": "Test 1 skill",
|
||||
"base_url": "https://test1.dev/"
|
||||
},
|
||||
{
|
||||
"name": "test2",
|
||||
"description": "Test 2 skill",
|
||||
"base_url": "https://test2.dev/"
|
||||
}
|
||||
]
|
||||
|
||||
for config in configs:
|
||||
path = Path(f"configs/{config['name']}.json")
|
||||
with open(path, 'w') as f:
|
||||
json.dump(config, f)
|
||||
|
||||
async def asyncTearDown(self):
|
||||
"""Clean up test environment"""
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
async def test_list_configs_success(self):
|
||||
"""Test listing all configs"""
|
||||
result = await skill_seeker_server.list_configs_tool({})
|
||||
|
||||
self.assertIsInstance(result, list)
|
||||
self.assertIsInstance(result[0], TextContent)
|
||||
self.assertIn("test1", result[0].text)
|
||||
self.assertIn("test2", result[0].text)
|
||||
self.assertIn("https://test1.dev/", result[0].text)
|
||||
self.assertIn("https://test2.dev/", result[0].text)
|
||||
|
||||
async def test_list_configs_empty(self):
|
||||
"""Test listing configs when directory is empty"""
|
||||
# Remove all configs
|
||||
for config_file in Path("configs").glob("*.json"):
|
||||
config_file.unlink()
|
||||
|
||||
result = await skill_seeker_server.list_configs_tool({})
|
||||
|
||||
self.assertIn("No config files found", result[0].text)
|
||||
|
||||
async def test_list_configs_no_directory(self):
|
||||
"""Test listing configs when directory doesn't exist"""
|
||||
# Remove configs directory
|
||||
shutil.rmtree("configs")
|
||||
|
||||
result = await skill_seeker_server.list_configs_tool({})
|
||||
|
||||
self.assertIn("No configs directory", result[0].text)
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestValidateConfigTool(unittest.IsolatedAsyncioTestCase):
|
||||
"""Test validate_config tool"""
|
||||
|
||||
async def asyncSetUp(self):
|
||||
"""Set up test environment"""
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.original_cwd = os.getcwd()
|
||||
os.chdir(self.temp_dir)
|
||||
|
||||
os.makedirs("configs", exist_ok=True)
|
||||
|
||||
async def asyncTearDown(self):
|
||||
"""Clean up test environment"""
|
||||
os.chdir(self.original_cwd)
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
async def test_validate_valid_config(self):
|
||||
"""Test validating a valid config"""
|
||||
# Create valid config
|
||||
config_path = Path("configs/valid.json")
|
||||
valid_config = {
|
||||
"name": "valid-test",
|
||||
"base_url": "https://example.com/",
|
||||
"selectors": {
|
||||
"main_content": "article",
|
||||
"title": "h1",
|
||||
"code_blocks": "pre"
|
||||
},
|
||||
"rate_limit": 0.5,
|
||||
"max_pages": 100
|
||||
}
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(valid_config, f)
|
||||
|
||||
args = {
|
||||
"config_path": str(config_path)
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.validate_config_tool(args)
|
||||
|
||||
self.assertIsInstance(result, list)
|
||||
self.assertIn("✅", result[0].text)
|
||||
self.assertIn("valid", result[0].text.lower())
|
||||
|
||||
async def test_validate_invalid_config(self):
|
||||
"""Test validating an invalid config"""
|
||||
# Create invalid config
|
||||
config_path = Path("configs/invalid.json")
|
||||
invalid_config = {
|
||||
"name": "invalid@name", # Invalid characters
|
||||
"base_url": "example.com" # Missing protocol
|
||||
}
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(invalid_config, f)
|
||||
|
||||
args = {
|
||||
"config_path": str(config_path)
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.validate_config_tool(args)
|
||||
|
||||
self.assertIn("❌", result[0].text)
|
||||
|
||||
async def test_validate_nonexistent_config(self):
|
||||
"""Test validating a nonexistent config"""
|
||||
args = {
|
||||
"config_path": "configs/nonexistent.json"
|
||||
}
|
||||
|
||||
result = await skill_seeker_server.validate_config_tool(args)
|
||||
|
||||
self.assertIn("Error", result[0].text)
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestCallToolRouter(unittest.IsolatedAsyncioTestCase):
|
||||
"""Test call_tool routing"""
|
||||
|
||||
async def test_call_tool_unknown(self):
|
||||
"""Test calling an unknown tool"""
|
||||
result = await skill_seeker_server.call_tool("unknown_tool", {})
|
||||
|
||||
self.assertIsInstance(result, list)
|
||||
self.assertIn("Unknown tool", result[0].text)
|
||||
|
||||
async def test_call_tool_exception_handling(self):
|
||||
"""Test that exceptions are caught and returned as errors"""
|
||||
# Call with invalid arguments that should cause an exception
|
||||
result = await skill_seeker_server.call_tool("generate_config", {})
|
||||
|
||||
self.assertIsInstance(result, list)
|
||||
self.assertIn("Error", result[0].text)
|
||||
|
||||
|
||||
@unittest.skipUnless(MCP_AVAILABLE, "MCP package not installed")
|
||||
class TestMCPServerIntegration(unittest.IsolatedAsyncioTestCase):
|
||||
"""Integration tests for MCP server"""
|
||||
|
||||
async def test_full_workflow_simulation(self):
|
||||
"""Test complete workflow: generate config -> validate -> estimate"""
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
original_cwd = os.getcwd()
|
||||
os.chdir(temp_dir)
|
||||
|
||||
try:
|
||||
# Step 1: Generate config using skill_seeker_server
|
||||
generate_args = {
|
||||
"name": "workflow-test",
|
||||
"url": "https://workflow-test.dev/",
|
||||
"description": "Workflow test skill"
|
||||
}
|
||||
result1 = await skill_seeker_server.generate_config_tool(generate_args)
|
||||
self.assertIn("✅", result1[0].text)
|
||||
|
||||
# Step 2: Validate config
|
||||
validate_args = {
|
||||
"config_path": "configs/workflow-test.json"
|
||||
}
|
||||
result2 = await skill_seeker_server.validate_config_tool(validate_args)
|
||||
self.assertIn("✅", result2[0].text)
|
||||
|
||||
# Step 3: List configs
|
||||
result3 = await skill_seeker_server.list_configs_tool({})
|
||||
self.assertIn("workflow-test", result3[0].text)
|
||||
|
||||
finally:
|
||||
os.chdir(original_cwd)
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user