Skip to content

实验四:生产级GitHub克隆服务器 🐙

"最优秀的工具是那些你忘记在使用它们的工具,因为它们工作得如此自然。" - 让我们构建一个真正解决日常开发痛点的MCP服务器!

🎯 实验概述

这是我们MCP学习之旅的"毕业设计"!我们将构建一个真正实用的生产级MCP服务器,解决每个开发者都会遇到的问题:快速克隆GitHub仓库并在VS Code中打开。

现状痛点:

  1. 打开终端/命令提示符
  2. 导航到目标目录
  3. 运行 git clone 命令
  4. 在克隆的目录中打开VS Code

我们的解决方案: 一个智能命令搞定所有步骤! ⚡

📋 学习目标

完成这个实验后,你将能够:

  • 构建企业级MCP服务器:为真实开发工作流创建生产就绪的解决方案
  • 实现GitHub集成功能:通过MCP协议智能克隆仓库
  • 掌握跨平台开发:编写兼容Windows/macOS/Linux的代码
  • 集成GitHub Copilot代理:在AI助手中使用自定义MCP工具
  • 应用生产最佳实践:错误处理、安全性和可维护性

⏱️ 时间分配

  • 需求分析和设计:8分钟
  • 核心功能实现:15分钟
  • GitHub Copilot集成:7分钟
  • 测试和优化:10分钟

🏗️ 项目架构设计

🎯 需求分析

我们要构建的GitHub克隆MCP服务器将提供以下功能:

功能模块具体能力技术实现用户价值
🔄 智能仓库克隆URL验证 + 自动克隆Git CLI + 参数验证一键获取代码
📁 安全目录管理冲突检测 + 路径验证文件系统API防止意外覆盖
🚀 VS Code自动启动跨平台应用启动系统调用无缝工作流切换
🛡️ 完善错误处理网络 + 权限 + 路径异常异常捕获体系用户友好的体验

🔧 技术栈选择

python
# 核心技术栈
MCP_SDK = "1.9.3"          # 最新MCP Python SDK
ASYNC_HTTP = "httpx"       # 现代异步HTTP客户端
FILE_SYSTEM = "pathlib"    # 现代路径操作
PROCESS_MGMT = "asyncio"   # 异步进程管理
VALIDATION = "pydantic"    # 数据验证(可选)

🚀 实现步骤详解

第一步:创建GitHub智能代理

让我们在AI Toolkit的Agent Builder中创建一个专门的GitHub管理代理:

markdown
📋 代理配置
- 代理名称:GitHubDeveloperAssistant
- 描述:专业的GitHub仓库管理和开发工具集成助手
- 模型选择:Claude 3.5 Sonnet(推荐用于代码分析)
- 工具集成:自定义MCP服务器

第二步:核心MCP服务器实现

创建文件 github_clone_mcp_server.py

python
#!/usr/bin/env python3
"""
GitHub Clone MCP Server - 生产级仓库克隆助手
版本: 1.0.0
作者: MCP Developer
描述: 智能化的GitHub仓库克隆和VS Code集成工具
"""

import asyncio
import json
import os
import subprocess
import sys
from pathlib import Path
from urllib.parse import urlparse
import platform

# MCP SDK导入
from mcp import Server
from mcp.types import Tool, TextContent, ImageContent
import httpx

class GitHubCloneMCPServer:
    """GitHub克隆MCP服务器主类"""
    
    def __init__(self):
        self.server = Server("github-clone-assistant")
        self.workspace_root = Path.home() / "mcp-workspace"
        self.workspace_root.mkdir(exist_ok=True)
        self.setup_tools()
        
    def setup_tools(self):
        """设置MCP工具"""
        
        # 工具1: 智能仓库克隆
        clone_tool = Tool(
            name="intelligent_clone_repository",
            description="智能克隆GitHub仓库,支持自动验证、目录管理和VS Code集成",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo_url": {
                        "type": "string",
                        "description": "GitHub仓库URL (支持HTTPS格式)",
                        "pattern": r"^https:\/\/github\.com\/[\w\-\.]+\/[\w\-\.]+(?:\.git)?$"
                    },
                    "target_directory": {
                        "type": "string", 
                        "description": "目标目录名(可选,自动从URL推断)",
                    },
                    "workspace_path": {
                        "type": "string",
                        "description": "工作区路径(可选,默认使用用户工作区)"
                    },
                    "branch": {
                        "type": "string",
                        "description": "指定克隆分支(可选,默认为main/master)"
                    },
                    "open_in_vscode": {
                        "type": "boolean",
                        "description": "克隆后是否在VS Code中打开项目",
                        "default": True
                    },
                    "shallow_clone": {
                        "type": "boolean", 
                        "description": "是否使用浅克隆以提高速度",
                        "default": True
                    }
                },
                "required": ["repo_url"]
            }
        )
        
        # 工具2: VS Code智能启动器
        vscode_tool = Tool(
            name="smart_open_in_vscode",
            description="跨平台智能启动VS Code,支持多版本检测",
            inputSchema={
                "type": "object",
                "properties": {
                    "project_path": {
                        "type": "string",
                        "description": "要打开的项目路径(绝对路径)"
                    },
                    "prefer_insiders": {
                        "type": "boolean",
                        "description": "优先使用VS Code Insiders版本",
                        "default": False
                    }
                },
                "required": ["project_path"]
            }
        )
        
        # 工具3: 工作区管理器
        workspace_tool = Tool(
            name="manage_workspace",
            description="管理本地开发工作区,支持列表、清理和组织功能",
            inputSchema={
                "type": "object",
                "properties": {
                    "action": {
                        "type": "string",
                        "enum": ["list", "clean", "analyze", "organize"],
                        "description": "执行的操作类型"
                    },
                    "filter_pattern": {
                        "type": "string",
                        "description": "过滤模式(用于清理和分析)"
                    }
                },
                "required": ["action"]
            }
        )
        
        # 注册工具
        self.server.add_tool(clone_tool, self.intelligent_clone_repository)
        self.server.add_tool(vscode_tool, self.smart_open_in_vscode)
        self.server.add_tool(workspace_tool, self.manage_workspace)
    
    async def intelligent_clone_repository(self, arguments):
        """智能仓库克隆的核心实现"""
        repo_url = arguments["repo_url"]
        target_directory = arguments.get("target_directory")
        workspace_path = arguments.get("workspace_path")
        branch = arguments.get("branch")
        open_in_vscode = arguments.get("open_in_vscode", True)
        shallow_clone = arguments.get("shallow_clone", True)
        
        try:
            # 步骤1: 验证和解析仓库URL
            repo_info = await self.validate_and_parse_repo_url(repo_url)
            if not repo_info["valid"]:
                return [TextContent(
                    type="text",
                    text=f"❌ 无效的GitHub仓库URL:{repo_info['error']}"
                )]
            
            # 步骤2: 确定工作路径
            if workspace_path:
                workspace = Path(workspace_path)
            else:
                workspace = self.workspace_root
            
            workspace.mkdir(parents=True, exist_ok=True)
            
            # 步骤3: 确定目标目录名
            if not target_directory:
                target_directory = repo_info["repo_name"]
            
            clone_path = workspace / target_directory
            
            # 步骤4: 检查目录冲突
            if clone_path.exists():
                return [TextContent(
                    type="text",
                    text=f"⚠️ 目录冲突!\n路径 `{clone_path}` 已存在。\n💡 建议:使用不同的target_directory参数或手动删除现有目录。"
                )]
            
            # 步骤5: 执行智能克隆
            clone_result = await self.execute_smart_clone(
                repo_url, clone_path, branch, shallow_clone
            )
            
            if not clone_result["success"]:
                return [TextContent(
                    type="text",
                    text=f"❌ 克隆失败:{clone_result['error']}\n\n🔧 排查建议:\n- 检查网络连接\n- 验证仓库是否公开或有访问权限\n- 确认Git已正确安装"
                )]
            
            # 步骤6: 项目智能分析
            project_analysis = await self.analyze_cloned_project(clone_path)
            
            # 步骤7: 生成详细报告
            success_report = await self.generate_clone_report(
                repo_info, clone_path, project_analysis, clone_result
            )
            
            # 步骤8: VS Code集成
            if open_in_vscode:
                vscode_result = await self.smart_open_in_vscode({
                    "project_path": str(clone_path)
                })
                if isinstance(vscode_result, list) and vscode_result:
                    vscode_text = vscode_result[0].text
                    if "✅" in vscode_text:
                        success_report += "\n\n🚀 **VS Code集成成功!** 项目已自动打开。"
                    else:
                        success_report += f"\n\n⚠️ **VS Code启动问题:** {vscode_text}"
            
            return [TextContent(type="text", text=success_report)]
            
        except Exception as e:
            error_msg = f"❌ **系统异常**\n\n错误详情:{str(e)}\n\n🔧 **调试建议:**\n- 检查系统环境配置\n- 验证MCP服务器权限\n- 查看详细错误日志"
            return [TextContent(type="text", text=error_msg)]
    
    async def validate_and_parse_repo_url(self, repo_url: str) -> dict:
        """验证并解析GitHub仓库URL"""
        try:
            # 移除.git后缀(如果存在)
            clean_url = repo_url.rstrip('.git')
            
            # 解析URL
            parsed = urlparse(clean_url)
            
            # 验证域名
            if parsed.netloc != 'github.com':
                return {
                    "valid": False,
                    "error": "只支持GitHub仓库 (github.com)"
                }
            
            # 解析路径
            path_parts = parsed.path.strip('/').split('/')
            if len(path_parts) != 2:
                return {
                    "valid": False,
                    "error": "URL格式不正确,应为 https://github.com/用户名/仓库名"
                }
            
            owner, repo_name = path_parts
            
            # 验证GitHub API(可选,检查仓库是否存在)
            api_check = await self.verify_repo_exists(owner, repo_name)
            
            return {
                "valid": True,
                "owner": owner,
                "repo_name": repo_name,
                "full_name": f"{owner}/{repo_name}",
                "clean_url": clean_url,
                "api_check": api_check
            }
            
        except Exception as e:
            return {
                "valid": False,
                "error": f"URL解析失败:{str(e)}"
            }
    
    async def verify_repo_exists(self, owner: str, repo_name: str) -> dict:
        """通过GitHub API验证仓库是否存在(可选功能)"""
        try:
            api_url = f"https://api.github.com/repos/{owner}/{repo_name}"
            
            async with httpx.AsyncClient(timeout=10.0) as client:
                response = await client.get(api_url)
                
                if response.status_code == 200:
                    repo_data = response.json()
                    return {
                        "exists": True,
                        "private": repo_data.get("private", False),
                        "description": repo_data.get("description", ""),
                        "language": repo_data.get("language", "Unknown"),
                        "stars": repo_data.get("stargazers_count", 0),
                        "forks": repo_data.get("forks_count", 0)
                    }
                elif response.status_code == 404:
                    return {"exists": False, "reason": "仓库不存在或为私有仓库"}
                else:
                    return {"exists": "unknown", "reason": f"API返回状态码:{response.status_code}"}
                    
        except Exception as e:
            return {"exists": "unknown", "reason": f"API检查失败:{str(e)}"}
    
    async def execute_smart_clone(self, repo_url: str, clone_path: Path, branch: str = None, shallow: bool = True) -> dict:
        """执行智能Git克隆操作"""
        try:
            # 构建git clone命令
            cmd = ["git", "clone"]
            
            # 浅克隆优化
            if shallow:
                cmd.extend(["--depth", "1"])
            
            # 指定分支
            if branch:
                cmd.extend(["-b", branch])
            
            # URL和目标路径
            cmd.extend([repo_url, str(clone_path)])
            
            # 执行克隆
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=clone_path.parent
            )
            
            stdout, stderr = await process.communicate()
            
            # 解析结果
            if process.returncode == 0:
                return {
                    "success": True,
                    "output": stdout.decode('utf-8', errors='ignore'),
                    "path": str(clone_path),
                    "command": ' '.join(cmd)
                }
            else:
                error_output = stderr.decode('utf-8', errors='ignore')
                return {
                    "success": False,
                    "error": error_output,
                    "command": ' '.join(cmd),
                    "exit_code": process.returncode
                }
                
        except FileNotFoundError:
            return {
                "success": False,
                "error": "Git未安装或未添加到系统PATH中",
                "suggestion": "请安装Git:https://git-scm.com/downloads"
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"克隆过程异常:{str(e)}"
            }
    
    async def analyze_cloned_project(self, project_path: Path) -> dict:
        """智能分析克隆的项目"""
        analysis = {
            "project_type": "Unknown",
            "language": "Unknown", 
            "frameworks": [],
            "package_managers": [],
            "has_tests": False,
            "has_docs": False,
            "config_files": [],
            "file_count": 0,
            "size_mb": 0,
            "readme_exists": False,
            "license_exists": False
        }
        
        try:
            # 检测项目类型和主要语言
            detection_rules = {
                "package.json": ("JavaScript/TypeScript", "Node.js"),
                "requirements.txt": ("Python", "pip"),
                "pyproject.toml": ("Python", "Poetry/pip"),
                "Cargo.toml": ("Rust", "Cargo"),
                "pom.xml": ("Java", "Maven"),
                "build.gradle": ("Java/Kotlin", "Gradle"),
                "go.mod": ("Go", "Go Modules"),
                "Gemfile": ("Ruby", "Bundler"),
                "composer.json": ("PHP", "Composer")
            }
            
            for config_file, (language, pm) in detection_rules.items():
                if (project_path / config_file).exists():
                    analysis["language"] = language
                    analysis["package_managers"].append(pm)
                    analysis["config_files"].append(config_file)
                    
                    # 深度分析package.json
                    if config_file == "package.json":
                        await self.analyze_package_json(project_path, analysis)
            
            # 检测常见配置文件
            common_configs = [
                "README.md", "README.rst", "README.txt",
                "LICENSE", "LICENSE.md", "LICENSE.txt",
                ".gitignore", ".env.example", ".env",
                "docker-compose.yml", "Dockerfile",
                ".github/workflows", ".gitlab-ci.yml"
            ]
            
            for config in common_configs:
                config_path = project_path / config
                if config_path.exists():
                    analysis["config_files"].append(config)
                    if "README" in config:
                        analysis["readme_exists"] = True
                    if "LICENSE" in config:
                        analysis["license_exists"] = True
            
            # 检测测试和文档目录
            test_patterns = ["test", "tests", "__tests__", "spec", "specs"]
            doc_patterns = ["docs", "doc", "documentation", "wiki"]
            
            for item in project_path.iterdir():
                if item.is_dir():
                    item_name_lower = item.name.lower()
                    if any(pattern in item_name_lower for pattern in test_patterns):
                        analysis["has_tests"] = True
                    if any(pattern in item_name_lower for pattern in doc_patterns):
                        analysis["has_docs"] = True
            
            # 计算项目统计信息
            total_size = 0
            file_count = 0
            for file_path in project_path.rglob('*'):
                if file_path.is_file():
                    file_count += 1
                    try:
                        total_size += file_path.stat().st_size
                    except (OSError, IOError):
                        pass  # 忽略无法访问的文件
            
            analysis["file_count"] = file_count
            analysis["size_mb"] = round(total_size / (1024 * 1024), 2)
            
            # 推断项目类型
            if analysis["language"] != "Unknown":
                if "React" in analysis["frameworks"]:
                    analysis["project_type"] = "React应用"
                elif "Vue" in analysis["frameworks"]:
                    analysis["project_type"] = "Vue应用"
                elif "Angular" in analysis["frameworks"]:
                    analysis["project_type"] = "Angular应用"
                elif "Express" in analysis["frameworks"]:
                    analysis["project_type"] = "Node.js后端"
                elif "Django" in analysis["frameworks"]:
                    analysis["project_type"] = "Django应用"
                elif "Flask" in analysis["frameworks"]:
                    analysis["project_type"] = "Flask应用"
                else:
                    analysis["project_type"] = f"{analysis['language']}项目"
            
        except Exception as e:
            analysis["analysis_error"] = str(e)
        
        return analysis
    
    async def analyze_package_json(self, project_path: Path, analysis: dict):
        """深度分析package.json文件"""
        try:
            package_json_path = project_path / "package.json"
            with open(package_json_path, 'r', encoding='utf-8') as f:
                package_data = json.load(f)
            
            # 分析依赖以推断框架
            all_deps = {
                **package_data.get("dependencies", {}),
                **package_data.get("devDependencies", {})
            }
            
            framework_detection = {
                "react": "React",
                "vue": "Vue.js", 
                "@vue/cli": "Vue.js",
                "angular": "Angular",
                "@angular/core": "Angular",
                "express": "Express.js",
                "next": "Next.js",
                "nuxt": "Nuxt.js",
                "svelte": "Svelte",
                "gatsby": "Gatsby"
            }
            
            for dep_name, framework_name in framework_detection.items():
                if dep_name in all_deps:
                    analysis["frameworks"].append(framework_name)
            
            # 检测构建工具
            build_tools = {
                "webpack": "Webpack",
                "vite": "Vite",
                "rollup": "Rollup",
                "parcel": "Parcel"
            }
            
            for tool_name, tool_display in build_tools.items():
                if tool_name in all_deps:
                    analysis["frameworks"].append(tool_display)
                    
        except Exception as e:
            pass  # 静默处理JSON解析错误
    
    async def generate_clone_report(self, repo_info: dict, clone_path: Path, analysis: dict, clone_result: dict) -> str:
        """生成详细的克隆成功报告"""
        
        # 基本信息部分
        report = f"""🎉 **GitHub仓库克隆成功!**

📦 **仓库信息**
• 仓库:`{repo_info['full_name']}`
• 本地路径:`{clone_path}`
• 克隆命令:`{clone_result.get('command', 'N/A')}`"""

        # API信息(如果可用)
        if repo_info.get("api_check", {}).get("exists") == True:
            api_info = repo_info["api_check"]
            report += f"""
• 描述:{api_info.get('description', '无描述')}
• 主要语言:{api_info.get('language', 'Unknown')}
• ⭐ Stars:{api_info.get('stars', 0)} | 🔄 Forks:{api_info.get('forks', 0)}"""

        # 项目分析部分
        report += f"""

🔍 **项目分析**
• 项目类型:{analysis['project_type']}
• 主要语言:{analysis['language']}
• 包管理器:{', '.join(analysis['package_managers']) if analysis['package_managers'] else '无'}
• 框架/工具:{', '.join(analysis['frameworks']) if analysis['frameworks'] else '无'}
• 文件数量:{analysis['file_count']}
• 项目大小:{analysis['size_mb']} MB

📋 **项目特征**
{'✅ 包含README文档' if analysis['readme_exists'] else '❌ 缺少README文档'}
{'✅ 包含开源许可证' if analysis['license_exists'] else '❌ 未发现许可证'}
{'✅ 包含测试代码' if analysis['has_tests'] else '❌ 未检测到测试'}
{'✅ 包含项目文档' if analysis['has_docs'] else '❌ 缺少项目文档'}"""

        # 配置文件列表
        if analysis['config_files']:
            report += f"""

📄 **配置文件**
{chr(10).join(f'• {file}' for file in analysis['config_files'])}"""

        # 开发建议
        suggestions = []
        
        if analysis['language'] == 'JavaScript/TypeScript':
            suggestions.append("💡 运行 `npm install` 或 `yarn install` 安装依赖")
            if 'package.json' in analysis['config_files']:
                suggestions.append("📋 查看 package.json 中的 scripts 了解可用命令")
        
        elif analysis['language'] == 'Python':
            suggestions.append("🐍 建议创建虚拟环境:`python -m venv venv`")
            if 'requirements.txt' in analysis['config_files']:
                suggestions.append("📦 安装依赖:`pip install -r requirements.txt`")
            elif 'pyproject.toml' in analysis['config_files']:
                suggestions.append("📦 使用Poetry安装:`poetry install`")
        
        elif analysis['language'] == 'Java':
            if 'Maven' in analysis['package_managers']:
                suggestions.append("☕ 运行 `mvn install` 编译项目")
            elif 'Gradle' in analysis['package_managers']:
                suggestions.append("☕ 运行 `./gradlew build` 编译项目")
        
        if not analysis['has_tests']:
            suggestions.append("🧪 建议添加自动化测试提高代码质量")
        
        if not analysis['readme_exists']:
            suggestions.append("📝 建议添加README.md文档")
        
        if suggestions:
            report += f"""

💡 **开发建议**
{chr(10).join(suggestions)}"""

        return report
    
    async def smart_open_in_vscode(self, arguments):
        """智能VS Code启动器"""
        project_path = arguments["project_path"]
        prefer_insiders = arguments.get("prefer_insiders", False)
        
        try:
            # 验证路径存在
            path_obj = Path(project_path)
            if not path_obj.exists():
                return [TextContent(
                    type="text",
                    text=f"❌ 路径不存在:{project_path}"
                )]
            
            # 检测操作系统
            system = platform.system().lower()
            
            # VS Code可执行文件检测
            vscode_commands = []
            
            if prefer_insiders:
                if system == "windows":
                    vscode_commands = ["code-insiders", "code"]
                elif system == "darwin":  # macOS
                    vscode_commands = ["code-insiders", "code"]
                else:  # Linux
                    vscode_commands = ["code-insiders", "code"]
            else:
                if system == "windows":
                    vscode_commands = ["code", "code-insiders"]
                elif system == "darwin":  # macOS
                    vscode_commands = ["code", "code-insiders"]
                else:  # Linux
                    vscode_commands = ["code", "code-insiders"]
            
            # 尝试启动VS Code
            for cmd in vscode_commands:
                try:
                    process = await asyncio.create_subprocess_exec(
                        cmd, str(project_path),
                        stdout=asyncio.subprocess.DEVNULL,
                        stderr=asyncio.subprocess.DEVNULL
                    )
                    
                    # 等待进程启动
                    await asyncio.sleep(1)
                    
                    if process.returncode is None:  # 进程正在运行
                        return [TextContent(
                            type="text",
                            text=f"✅ 已使用 {cmd} 打开项目:{project_path}"
                        )]
                        
                except FileNotFoundError:
                    continue  # 尝试下一个命令
                except Exception as e:
                    continue  # 尝试下一个命令
            
            # 所有尝试都失败
            return [TextContent(
                type="text",
                text=f"""❌ 无法启动VS Code

🔧 **解决方案:**
1. 确保VS Code已安装
2. 将VS Code添加到系统PATH中
3. 手动打开:{project_path}

💡 **下载链接:** https://code.visualstudio.com/"""
            )]
            
        except Exception as e:
            return [TextContent(
                type="text",
                text=f"❌ VS Code启动异常:{str(e)}"
            )]
    
    async def manage_workspace(self, arguments):
        """工作区管理器"""
        action = arguments["action"]
        filter_pattern = arguments.get("filter_pattern", "*")
        
        if action == "list":
            return await self.list_workspace_projects()
        elif action == "analyze":
            return await self.analyze_workspace()
        elif action == "clean":
            return await self.clean_workspace(filter_pattern)
        elif action == "organize":
            return await self.organize_workspace()
        else:
            return [TextContent(
                type="text",
                text="❌ 不支持的操作类型"
            )]
    
    async def list_workspace_projects(self):
        """列出工作区项目"""
        try:
            projects = []
            
            for item in self.workspace_root.iterdir():
                if item.is_dir() and not item.name.startswith('.'):
                    # 快速项目类型检测
                    project_type = "Unknown"
                    main_language = "Unknown"
                    
                    if (item / "package.json").exists():
                        project_type = "Node.js"
                        main_language = "JavaScript/TypeScript"
                    elif (item / "requirements.txt").exists() or (item / "pyproject.toml").exists():
                        project_type = "Python"
                        main_language = "Python"
                    elif (item / "pom.xml").exists():
                        project_type = "Java (Maven)"
                        main_language = "Java"
                    elif (item / "Cargo.toml").exists():
                        project_type = "Rust"
                        main_language = "Rust"
                    elif (item / "go.mod").exists():
                        project_type = "Go"
                        main_language = "Go"
                    
                    # 计算项目大小
                    try:
                        total_size = sum(
                            f.stat().st_size for f in item.rglob('*') 
                            if f.is_file()
                        )
                        size_mb = round(total_size / (1024 * 1024), 2)
                    except:
                        size_mb = 0
                    
                    projects.append({
                        "name": item.name,
                        "type": project_type,
                        "language": main_language,
                        "size_mb": size_mb,
                        "path": str(item),
                        "last_modified": item.stat().st_mtime
                    })
            
            if not projects:
                return [TextContent(
                    type="text",
                    text=f"""📁 **工作区为空**

工作区路径:`{self.workspace_root}`

💡 使用 `intelligent_clone_repository` 克隆第一个项目吧!"""
                )]
            
            # 按最后修改时间排序
            projects.sort(key=lambda x: x["last_modified"], reverse=True)
            
            # 生成项目列表报告
            report = f"""📁 **MCP工作区项目列表**

📍 工作区路径:`{self.workspace_root}`
📊 项目总数:{len(projects)}

"""
            
            for i, project in enumerate(projects, 1):
                report += f"""**{i}. {project['name']}**
   • 类型:{project['type']}
   • 语言:{project['language']}
   • 大小:{project['size_mb']} MB
   • 路径:`{project['path']}`

"""
            
            return [TextContent(type="text", text=report.strip())]
            
        except Exception as e:
            return [TextContent(
                type="text",
                text=f"❌ 列出项目失败:{str(e)}"
            )]
    
    async def analyze_workspace(self):
        """分析工作区统计信息"""
        try:
            stats = {
                "total_projects": 0,
                "total_size_mb": 0,
                "languages": {},
                "project_types": {},
                "largest_project": None,
                "newest_project": None
            }
            
            newest_time = 0
            largest_size = 0
            
            for item in self.workspace_root.iterdir():
                if item.is_dir() and not item.name.startswith('.'):
                    stats["total_projects"] += 1
                    
                    # 项目大小
                    try:
                        total_size = sum(
                            f.stat().st_size for f in item.rglob('*') 
                            if f.is_file()
                        )
                        size_mb = round(total_size / (1024 * 1024), 2)
                        stats["total_size_mb"] += size_mb
                        
                        # 记录最大项目
                        if size_mb > largest_size:
                            largest_size = size_mb
                            stats["largest_project"] = {
                                "name": item.name,
                                "size_mb": size_mb
                            }
                    except:
                        size_mb = 0
                    
                    # 最新项目
                    try:
                        mtime = item.stat().st_mtime
                        if mtime > newest_time:
                            newest_time = mtime
                            stats["newest_project"] = item.name
                    except:
                        pass
                    
                    # 语言统计
                    if (item / "package.json").exists():
                        stats["languages"]["JavaScript/TypeScript"] = stats["languages"].get("JavaScript/TypeScript", 0) + 1
                        stats["project_types"]["Node.js"] = stats["project_types"].get("Node.js", 0) + 1
                    elif (item / "requirements.txt").exists() or (item / "pyproject.toml").exists():
                        stats["languages"]["Python"] = stats["languages"].get("Python", 0) + 1
                        stats["project_types"]["Python"] = stats["project_types"].get("Python", 0) + 1
                    elif (item / "pom.xml").exists():
                        stats["languages"]["Java"] = stats["languages"].get("Java", 0) + 1
                        stats["project_types"]["Java (Maven)"] = stats["project_types"].get("Java (Maven)", 0) + 1
                    else:
                        stats["languages"]["Unknown"] = stats["languages"].get("Unknown", 0) + 1
                        stats["project_types"]["Unknown"] = stats["project_types"].get("Unknown", 0) + 1
            
            # 生成分析报告
            report = f"""📊 **工作区分析报告**

📈 **总体统计**
• 项目总数:{stats['total_projects']}
• 占用空间:{round(stats['total_size_mb'], 2)} MB
• 平均项目大小:{round(stats['total_size_mb'] / max(stats['total_projects'], 1), 2)} MB

📝 **语言分布**
"""
            
            for lang, count in sorted(stats["languages"].items(), key=lambda x: x[1], reverse=True):
                percentage = round(count / stats["total_projects"] * 100, 1)
                report += f"• {lang}{count} 项目 ({percentage}%)\n"
            
            report += f"""
🏗️ **项目类型分布**
"""
            
            for ptype, count in sorted(stats["project_types"].items(), key=lambda x: x[1], reverse=True):
                percentage = round(count / stats["total_projects"] * 100, 1)
                report += f"• {ptype}{count} 项目 ({percentage}%)\n"
            
            if stats["largest_project"]:
                report += f"""
🏆 **最大项目**:{stats['largest_project']['name']} ({stats['largest_project']['size_mb']} MB)"""
            
            if stats["newest_project"]:
                report += f"""
🆕 **最新项目**:{stats['newest_project']}"""
            
            return [TextContent(type="text", text=report)]
            
        except Exception as e:
            return [TextContent(
                type="text",
                text=f"❌ 工作区分析失败:{str(e)}"
            )]
    
    async def run(self):
        """启动MCP服务器"""
        print(f"🚀 GitHub Clone MCP服务器启动")
        print(f"📁 工作区目录:{self.workspace_root}")
        print(f"🔧 可用工具:intelligent_clone_repository, smart_open_in_vscode, manage_workspace")
        await self.server.run()

# 主程序入口
async def main():
    server = GitHubCloneMCPServer()
    await server.run()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n👋 MCP服务器已停止")
    except Exception as e:
        print(f"❌ 服务器启动失败:{e}")

第三步:GitHub Copilot Agent Mode集成

现在让我们配置GitHub Copilot使用我们的自定义MCP服务器:

1. VS Code配置文件设置

在项目根目录创建 .vscode/settings.json

json
{
  "github.copilot.chat.experimentalMcp": {
    "mcpServers": {
      "github-clone-assistant": {
        "command": "python",
        "args": ["github_clone_mcp_server.py"],
        "description": "GitHub仓库智能克隆和VS Code集成助手",
        "enabled": true
      }
    }
  },
  "python.defaultInterpreterPath": "./venv/bin/python"
}

2. Agent Builder配置

在AI Toolkit的Agent Builder中:

  1. 创建新代理

    • 名称:GitHubDeveloperAssistant
    • 模型:Claude 3.5 Sonnet
    • 描述:专业的GitHub开发工作流自动化助手
  2. 添加MCP服务器

    • 服务器类型:自定义MCP服务器
    • 服务器路径:./github_clone_mcp_server.py
    • 传输方式:stdio
  3. 配置提示词模板

markdown
你是一个专业的GitHub开发工作流助手,具备以下核心能力:

## 🎯 核心功能
- **智能仓库克隆**:快速克隆GitHub仓库并自动在VS Code中打开
- **项目分析**:深度分析项目结构、语言、框架和依赖
- **工作区管理**:组织和管理本地开发项目
- **开发建议**:基于项目特征提供个性化的开发建议

## 🔧 可用工具
1. `intelligent_clone_repository` - 智能克隆GitHub仓库
2. `smart_open_in_vscode` - 跨平台VS Code启动器  
3. `manage_workspace` - 工作区项目管理

## 💡 使用示例
- "帮我克隆 https://github.com/microsoft/vscode 这个仓库"
- "列出我的所有项目"
- "分析我的工作区统计信息"
- "在VS Code中打开项目 /path/to/project"

始终提供详细、准确和有用的响应,并在适当时候给出开发建议。

第四步:实际使用和测试

在GitHub Copilot Chat中使用

打开GitHub Copilot Chat,尝试以下对话:

👤 User: 帮我克隆这个仓库:https://github.com/microsoft/vscode-ai-toolkit

🤖 Assistant: 我来帮你克隆Microsoft的VS Code AI Toolkit仓库!

[调用 intelligent_clone_repository 工具]
{
  "repo_url": "https://github.com/microsoft/vscode-ai-toolkit",
  "open_in_vscode": true,
  "shallow_clone": true
}

🎉 GitHub仓库克隆成功!

📦 仓库信息
• 仓库:microsoft/vscode-ai-toolkit
• 本地路径:/Users/yourname/mcp-workspace/vscode-ai-toolkit
• 描述:AI Toolkit for Visual Studio Code
• 主要语言:TypeScript
• ⭐ Stars:1,245 | 🔄 Forks:156

🔍 项目分析
• 项目类型:TypeScript项目
• 主要语言:JavaScript/TypeScript
• 包管理器:npm
• 框架/工具:Webpack, ESLint
• 文件数量:847
• 项目大小:12.5 MB

📋 项目特征
✅ 包含README文档
✅ 包含开源许可证
✅ 包含测试代码
✅ 包含项目文档

💡 开发建议
• 运行 `npm install` 安装依赖
• 查看 package.json 中的 scripts 了解可用命令
• 🚀 已在VS Code中打开项目!

高级使用场景

👤 User: 我想要一个专门的工作区来存放AI相关的项目,帮我克隆几个知名的AI项目

🤖 Assistant: 很好的想法!我来帮你创建一个AI项目专用工作区,并克隆一些优秀的AI项目。

[依次调用 intelligent_clone_repository 工具克隆多个仓库]

1. Transformers (Hugging Face)
2. LangChain
3. OpenAI Cookbook
4. Stable Diffusion WebUI

所有项目已成功克隆到你的工作区!每个项目都已经过智能分析,你可以立即开始开发。需要我打开特定的项目吗?

🧪 测试和验证

功能测试清单

创建测试脚本 test_github_mcp_server.py

python
#!/usr/bin/env python3
"""
GitHub Clone MCP Server 测试套件
"""

import asyncio
import json
import tempfile
from pathlib import Path
from github_clone_mcp_server import GitHubCloneMCPServer

class MCPServerTester:
    def __init__(self):
        self.server = GitHubCloneMCPServer()
        self.test_results = []
    
    async def run_all_tests(self):
        """运行所有测试用例"""
        print("🧪 开始MCP服务器功能测试...\n")
        
        tests = [
            ("URL验证测试", self.test_url_validation),
            ("仓库克隆测试", self.test_repository_cloning),
            ("项目分析测试", self.test_project_analysis),
            ("工作区管理测试", self.test_workspace_management),
            ("VS Code集成测试", self.test_vscode_integration)
        ]
        
        for test_name, test_func in tests:
            print(f"🔍 {test_name}...")
            try:
                result = await test_func()
                self.test_results.append((test_name, "✅", result))
                print(f"   ✅ {result}\n")
            except Exception as e:
                self.test_results.append((test_name, "❌", str(e)))
                print(f"   ❌ {str(e)}\n")
        
        # 输出测试总结
        self.print_test_summary()
    
    async def test_url_validation(self):
        """测试URL验证功能"""
        test_urls = [
            ("https://github.com/microsoft/vscode", True),
            ("https://github.com/user/repo.git", True),
            ("https://gitlab.com/user/repo", False),
            ("invalid-url", False)
        ]
        
        for url, expected_valid in test_urls:
            result = await self.server.validate_and_parse_repo_url(url)
            if result["valid"] != expected_valid:
                raise AssertionError(f"URL验证失败:{url}")
        
        return "所有URL验证测试通过"
    
    async def test_repository_cloning(self):
        """测试仓库克隆功能(使用小型测试仓库)"""
        # 使用临时目录
        with tempfile.TemporaryDirectory() as temp_dir:
            self.server.workspace_root = Path(temp_dir)
            
            # 克隆一个小型公开仓库
            result = await self.server.intelligent_clone_repository({
                "repo_url": "https://github.com/octocat/Hello-World",
                "open_in_vscode": False,
                "shallow_clone": True
            })
            
            if not result or "成功" not in result[0].text:
                raise AssertionError("仓库克隆失败")
        
        return "仓库克隆功能正常"
    
    async def test_project_analysis(self):
        """测试项目分析功能"""
        # 创建模拟项目结构
        with tempfile.TemporaryDirectory() as temp_dir:
            test_project = Path(temp_dir) / "test-project"
            test_project.mkdir()
            
            # 创建package.json
            (test_project / "package.json").write_text(json.dumps({
                "name": "test-project",
                "dependencies": {"react": "^18.0.0"}
            }))
            
            # 创建其他文件
            (test_project / "README.md").write_text("# Test Project")
            (test_project / "LICENSE").write_text("MIT License")
            
            analysis = await self.server.analyze_cloned_project(test_project)
            
            if analysis["language"] != "JavaScript/TypeScript":
                raise AssertionError("项目语言检测错误")
            
            if "React" not in analysis["frameworks"]:
                raise AssertionError("框架检测失败")
        
        return "项目分析功能正常"
    
    async def test_workspace_management(self):
        """测试工作区管理功能"""
        with tempfile.TemporaryDirectory() as temp_dir:
            self.server.workspace_root = Path(temp_dir)
            
            # 创建测试项目
            (Path(temp_dir) / "project1").mkdir()
            (Path(temp_dir) / "project2").mkdir()
            
            result = await self.server.manage_workspace({"action": "list"})
            
            if not result or "project1" not in result[0].text:
                raise AssertionError("工作区列表功能失败")
        
        return "工作区管理功能正常"
    
    async def test_vscode_integration(self):
        """测试VS Code集成功能"""
        with tempfile.TemporaryDirectory() as temp_dir:
            result = await self.server.smart_open_in_vscode({
                "project_path": temp_dir
            })
            
            # 检查是否返回了合理的响应(可能成功或失败都是正常的)
            if not result:
                raise AssertionError("VS Code集成功能无响应")
        
        return "VS Code集成功能响应正常"
    
    def print_test_summary(self):
        """打印测试总结"""
        passed = sum(1 for _, status, _ in self.test_results if status == "✅")
        total = len(self.test_results)
        
        print("=" * 60)
        print(f"📊 测试总结:{passed}/{total} 通过")
        print("=" * 60)
        
        for test_name, status, result in self.test_results:
            print(f"{status} {test_name}: {result}")
        
        print(f"\n🎯 整体测试成功率:{passed/total*100:.1f}%")

async def main():
    tester = MCPServerTester()
    await tester.run_all_tests()

if __name__ == "__main__":
    asyncio.run(main())

🎖️ 学习成果验证

💪 技能掌握检查清单

完成这个实验后,检验你是否掌握了以下技能:

🎯 基础技能 (必需掌握)

  • [ ] 能够独立设计和实现自定义MCP服务器
  • [ ] 理解并正确使用MCP Python SDK的最新API
  • [ ] 实现跨平台兼容的系统集成功能
  • [ ] 编写完善的错误处理和用户反馈机制

🚀 进阶技能 (熟练掌握)

  • [ ] 能够集成GitHub API进行仓库验证
  • [ ] 实现智能项目分析和自动化建议
  • [ ] 构建生产级的异步处理流程
  • [ ] 在GitHub Copilot中流畅使用自定义工具

🏆 专家技能 (精通掌握)

  • [ ] 能够优化MCP服务器的性能和稳定性
  • [ ] 设计可扩展的工具架构和插件系统
  • [ ] 实现企业级的安全性和权限控制
  • [ ] 构建完整的测试和监控体系

🎯 挑战任务

选择以下挑战来进一步提升你的技能:

🥉 初级挑战:功能扩展

为GitHub Clone服务器添加以下功能:

  • 支持克隆指定的Git分支或标签
  • 添加克隆进度显示
  • 实现项目模板应用功能

🥈 中级挑战:工作流集成

构建一个完整的开发工作流自动化:

  • 自动设置开发环境(安装依赖、配置工具)
  • 集成代码质量检查工具
  • 实现自动化的项目文档生成

🥇 高级挑战:企业级扩展

开发企业级的GitHub管理平台:

  • 支持多Git平台(GitHub、GitLab、Bitbucket)
  • 实现团队项目管理和权限控制
  • 构建项目分析和报告仪表板

📚 深入学习资源

🔗 相关文档和教程

🎥 推荐视频教程

  • "构建自定义MCP服务器" - MCP官方教程系列
  • "GitHub API最佳实践" - GitHub开发者大会
  • "Python异步编程深度解析" - PyCon技术分享

🌐 社区和支持

🎉 实验总结

恭喜你完成了MCP学习之旅的终极实验!🏆

通过这个实验,你不仅学会了如何构建生产级的MCP服务器,更重要的是掌握了AI原生开发的核心思维模式。你现在拥有的能力包括:

🌟 核心成就

  1. 架构设计能力:能够设计可扩展、可维护的MCP服务器架构
  2. 工程实践经验:掌握了错误处理、测试、文档等工程最佳实践
  3. AI集成专家:深度理解AI工具与传统开发工具的无缝集成
  4. 跨平台开发技能:编写兼容多种操作系统的高质量代码
  5. 用户体验意识:始终从用户角度思考工具的易用性和实用性

🚀 下一步建议

  1. 实际部署:将你的MCP服务器部署到真实的开发环境中
  2. 持续改进:根据使用反馈不断优化和扩展功能
  3. 社区贡献:将你的经验分享给MCP开发者社区
  4. 技术分享:撰写技术博客或制作教程视频

💡 最后的话

记住,最好的工具是那些能够真正解决问题、提高效率的工具。你构建的GitHub Clone MCP服务器虽然看起来简单,但它体现了优秀软件的核心特质:简单、可靠、实用

现在,拿着你的"神兵利器",去征服更广阔的AI开发世界吧!🌟✨


🎯 准备好成为MCP生态系统的贡献者了吗?

你的下一个MCP服务器将改变什么样的开发工作流呢?我们期待看到你的创新!💫