"""
GitCode MCP服务器,提供GitCode API功能的MCP接口
"""
import json
import logging
import os
import sys
from typing import Optional
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
from gitcode_mcp.api.base.client import GitCodeAPI
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stderr),
logging.FileHandler("gitcode_mcp.log")
]
)
logger = logging.getLogger("gitcode_mcp")
logger.info("GitCode MCP服务器模块初始化...")
load_dotenv()
logger.info("环境变量已加载")
mcp = FastMCP("GitCode MCP服务器")
logger.info(f"FastMCP实例已创建,名称: GitCode MCP服务器")
api_client: Optional[GitCodeAPI] = None
def init_api_client():
"""初始化API客户端"""
global api_client
if api_client is not None:
return api_client
token = os.environ.get("GITCODE_TOKEN")
if not token:
logger.warning("未设置GITCODE_TOKEN环境变量,API访问可能受限")
proxy = os.environ.get("HTTP_PROXY") or os.environ.get("HTTPS_PROXY")
if proxy:
logger.info(f"检测到代理配置: {proxy}")
api_client = GitCodeAPI(token=token, proxy=proxy)
logger.info("GitCode API客户端已初始化")
return api_client
async def safe_api_call(func, *args, **kwargs):
"""安全调用API,统一处理错误和日志"""
client = init_api_client()
try:
return await func(*args, **kwargs)
except Exception as e:
error_message = f"API调用失败: {func.__name__}({args}, {kwargs}) - {str(e)}"
logger.error(error_message)
raise ValueError(error_message) from e
@mcp.tool()
async def list_repositories() -> str:
"""列出当前用户的仓库"""
client = init_api_client()
repos = await safe_api_call(client.repos.list_user_repos)
return json.dumps(repos, ensure_ascii=False)
@mcp.tool()
async def get_repository(owner: str, repo: str) -> str:
"""获取特定仓库的详细信息"""
client = init_api_client()
repo_info = await safe_api_call(client.repos.get_repo, owner=owner, repo=repo)
return json.dumps(repo_info, ensure_ascii=False)
@mcp.tool()
async def create_repository(name: str, description: str = "", private: bool = False) -> str:
"""创建新仓库"""
client = init_api_client()
repo = await safe_api_call(client.repos.create_repo, name=name, description=description, private=private)
return json.dumps(repo, ensure_ascii=False)
@mcp.tool()
async def list_branches(owner: str, repo: str) -> str:
"""列出仓库的分支"""
client = init_api_client()
branches = await safe_api_call(client.branches.list_branches, owner=owner, repo=repo)
return json.dumps(branches, ensure_ascii=False)
@mcp.tool()
async def get_branch(owner: str, repo: str, branch: str) -> str:
"""获取特定分支的详细信息"""
client = init_api_client()
branch_info = await safe_api_call(client.branches.get_branch, owner=owner, repo=repo, branch=branch)
return json.dumps(branch_info, ensure_ascii=False)
@mcp.tool()
async def create_branch(owner: str, repo: str, branch: str, ref: str) -> str:
"""创建新分支"""
client = init_api_client()
branch_info = await safe_api_call(client.branches.create_branch, owner=owner, repo=repo, branch=branch, ref=ref)
return json.dumps(branch_info, ensure_ascii=False)
@mcp.tool()
async def list_issues(owner: str, repo: str) -> str:
"""列出仓库的Issues"""
client = init_api_client()
issues = await safe_api_call(client.issues.list_issues, owner=owner, repo=repo)
return json.dumps(issues, ensure_ascii=False)
@mcp.tool()
async def get_issue(owner: str, repo: str, issue_number: int) -> str:
"""获取特定Issue的详细信息"""
client = init_api_client()
issue = await safe_api_call(client.issues.get_issue, owner=owner, repo=repo, issue_number=issue_number)
return json.dumps(issue, ensure_ascii=False)
@mcp.tool()
async def create_issue(owner: str, repo: str, title: str, body: str = "") -> str:
"""创建新Issue"""
client = init_api_client()
issue = await safe_api_call(client.issues.create_issue, owner=owner, repo=repo, title=title, body=body)
return json.dumps(issue, ensure_ascii=False)
@mcp.tool()
async def list_issue_comments(owner: str, repo: str, issue_number: int) -> str:
"""列出Issue的评论列表"""
client = init_api_client()
comments = await safe_api_call(
client.issues.list_comments,
owner=owner,
repo=repo,
issue_number=issue_number
)
return json.dumps(comments, ensure_ascii=False)
@mcp.tool()
async def create_issue_comment(owner: str, repo: str, issue_number: int, body: str) -> str:
"""创建Issue评论"""
client = init_api_client()
comment = await safe_api_call(
client.issues.add_comment,
owner=owner,
repo=repo,
issue_number=issue_number,
body=body
)
return json.dumps(comment, ensure_ascii=False)
@mcp.tool()
async def list_pull_requests(owner: str, repo: str) -> str:
"""列出仓库的Pull Requests"""
client = init_api_client()
prs = await safe_api_call(client.pulls.list_pull_requests, owner=owner, repo=repo)
return json.dumps(prs, ensure_ascii=False)
@mcp.tool()
async def get_pull_request(owner: str, repo: str, pull_number: int) -> str:
"""获取特定Pull Request的详细信息"""
client = init_api_client()
pr = await safe_api_call(client.pulls.get_pull_request, owner=owner, repo=repo, pr_number=pull_number)
return json.dumps(pr, ensure_ascii=False)
@mcp.tool()
async def create_pull_request(owner: str, repo: str, title: str, head: str, base: str, body: str = "") -> str:
"""创建新Pull Request"""
client = init_api_client()
pr = await safe_api_call(
client.pulls.create_pull_request,
owner=owner,
repo=repo,
title=title,
head=head,
base=base,
body=body
)
return json.dumps(pr, ensure_ascii=False)
@mcp.tool()
async def update_pull_request(owner: str, repo: str, pull_number: int, title: str = None, body: str = None, state: str = None) -> str:
"""更新Pull Request"""
client = init_api_client()
pr = await safe_api_call(
client.pulls.update_pull_request,
owner=owner,
repo=repo,
pr_number=pull_number,
title=title,
body=body,
state=state
)
return json.dumps(pr, ensure_ascii=False)
@mcp.tool()
async def merge_pull_request(owner: str, repo: str, pull_number: int, commit_title: str = None, commit_message: str = None, merge_method: str = None) -> str:
"""合并Pull Request"""
client = init_api_client()
result = await safe_api_call(
client.pulls.merge_pull_request,
owner=owner,
repo=repo,
pr_number=pull_number,
commit_title=commit_title,
commit_message=commit_message,
merge_method=merge_method
)
return json.dumps(result, ensure_ascii=False)
@mcp.tool()
async def list_pull_request_comments(
owner: str,
repo: str,
pull_number: int,
direction: str = None,
comment_type: str = None,
) -> str:
"""列出Pull Request的评论列表。
direction: 可选,排序方向,asc 升序 或 desc 降序。
comment_type: 可选,筛选评论类型:diff_comment(代码行/diff 评论)、pr_comment(PR 普通评论)。
"""
client = init_api_client()
comments = await safe_api_call(
client.pulls.list_pull_request_comments,
owner=owner,
repo=repo,
pr_number=pull_number,
direction=direction,
comment_type=comment_type,
)
return json.dumps(comments, ensure_ascii=False)
@mcp.tool()
async def create_pull_request_comment(owner: str, repo: str, pull_number: int, body: str, path: str = None, position: int = None) -> str:
"""在Pull Request添加评论(可选path和position用于代码行评论)"""
client = init_api_client()
comment = await safe_api_call(
client.pulls.create_pull_request_comment,
owner=owner,
repo=repo,
pr_number=pull_number,
body=body,
path=path,
position=position
)
return json.dumps(comment, ensure_ascii=False)
@mcp.tool()
async def get_pull_request_comment(owner: str, repo: str, comment_id: int) -> str:
"""获取 Pull Request 单条评论详情(含 discussion_id 等字段,便于回复讨论线程)。"""
client = init_api_client()
comment = await safe_api_call(
client.pulls.get_pull_request_comment,
owner=owner,
repo=repo,
comment_id=comment_id,
)
return json.dumps(comment, ensure_ascii=False)
@mcp.tool()
async def reply_to_pull_request_comment(
owner: str,
repo: str,
pull_number: int,
discussion_id: str,
body: str,
) -> str:
"""在指定 PR 的讨论线程下回复评论(discussion_id 可由 get_pull_request_comment 等接口获得)。"""
client = init_api_client()
result = await safe_api_call(
client.pulls.reply_to_pull_request_comment,
owner=owner,
repo=repo,
pr_number=pull_number,
discussion_id=discussion_id,
body=body,
)
return json.dumps(result, ensure_ascii=False)
@mcp.tool()
async def search_repositories(query: str) -> str:
"""搜索仓库"""
client = init_api_client()
results = await safe_api_call(client.search.search_repositories, query=query)
return json.dumps(results, ensure_ascii=False)
@mcp.tool()
async def search_issues(query: str) -> str:
"""搜索Issues"""
client = init_api_client()
results = await safe_api_call(client.search.search_issues, query=query)
return json.dumps(results, ensure_ascii=False)
@mcp.tool()
async def search_users(query: str) -> str:
"""搜索用户"""
client = init_api_client()
results = await safe_api_call(client.search.search_users, query=query)
return json.dumps(results, ensure_ascii=False)
@mcp.resource("repo://{owner}/{name}")
async def get_repository_resource(owner: str, name: str) -> str:
"""获取仓库资源"""
client = init_api_client()
try:
repo_data = await safe_api_call(client.repos.get_repo, owner=owner, repo=name)
formatted_content = (
f"# {repo_data.get('name')}\n\n"
f"{repo_data.get('description', '')}\n\n"
f"- 所有者: {owner}\n"
f"- 星标数: {repo_data.get('stargazers_count', 0)}\n"
f"- Fork数: {repo_data.get('forks_count', 0)}\n"
f"- 默认分支: {repo_data.get('default_branch', 'main')}\n"
f"- 创建时间: {repo_data.get('created_at', '')}\n"
f"- 最后更新: {repo_data.get('updated_at', '')}\n"
)
return formatted_content
except Exception as e:
logger.error(f"读取资源时出错: {owner}/{name} - {e}")
return f"Error: {str(e)}"
@mcp.prompt()
def create_issue_prompt(owner: str, repo: str, title: str, body: str = "") -> str:
"""创建Issue的提示模板"""
return f"""在 {owner}/{repo} 仓库中创建一个新Issue:
标题:{title}
内容:{body}"""
@mcp.prompt()
def create_pull_request_prompt(owner: str, repo: str, title: str, head: str, base: str, body: str = "") -> str:
"""创建Pull Request的提示模板"""
return f"""在 {owner}/{repo} 仓库中创建一个新Pull Request:
标题:{title}
从分支 {head} 到 {base}
描述:{body}"""
def main():
"""主函数,启动MCP服务器"""
logger.info("正在启动GitCode MCP服务器...")
try:
init_api_client()
logger.info("API客户端已初始化,准备启动服务器...")
mcp.run()
except KeyboardInterrupt:
logger.info("服务器被用户中断")
except Exception as e:
logger.error(f"服务器运行错误: {e}", exc_info=True)
raise
finally:
import asyncio
if api_client:
try:
asyncio.run(api_client.close())
logger.info("API客户端已关闭")
except Exception as e:
logger.error(f"关闭API客户端时出错: {e}")
if __name__ == "__main__":
main()