1. 服务器核心模块 (internal/ghmcp)
1.1 模块架构图
classDiagram
class MCPServerConfig {
+Version: string
+Host: string
+Token: string
+EnabledToolsets: []string
+DynamicToolsets: bool
+ReadOnly: bool
+Translator: TranslationHelperFunc
+ContentWindowSize: int
}
class StdioServerConfig {
+Version: string
+Host: string
+Token: string
+EnabledToolsets: []string
+DynamicToolsets: bool
+ReadOnly: bool
+ExportTranslations: bool
+EnableCommandLogging: bool
+LogFilePath: string
+ContentWindowSize: int
}
class APIHost {
+baseRESTURL: *url.URL
+graphqlURL: *url.URL
+uploadURL: *url.URL
+rawURL: *url.URL
}
class GitHubClients {
+restClient: *gogithub.Client
+gqlClient: *githubv4.Client
+rawClient: *raw.Client
}
class MCPServerCore {
+NewMCPServer(config)
+RunStdioServer(config)
+parseAPIHost(host)
+setupGitHubClients()
}
MCPServerConfig --> MCPServerCore
StdioServerConfig --> MCPServerCore
MCPServerCore --> APIHost
MCPServerCore --> GitHubClients
APIHost --> GitHubClients
1.2 核心函数分析
NewMCPServer - 服务器创建函数
// 位置: internal/ghmcp/server.go:57-160
func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
// 1. API主机解析 - 支持多种GitHub部署环境
apiHost, err := parseAPIHost(cfg.Host)
if err != nil {
return nil, fmt.Errorf("failed to parse API host: %w", err)
}
// 2. REST客户端配置
restClient := gogithub.NewClient(nil).WithAuthToken(cfg.Token)
restClient.UserAgent = fmt.Sprintf("github-mcp-server/%s", cfg.Version)
restClient.BaseURL = apiHost.baseRESTURL
restClient.UploadURL = apiHost.uploadURL
// 3. GraphQL客户端配置
gqlHTTPClient := &http.Client{
Transport: &bearerAuthTransport{
transport: http.DefaultTransport,
token: cfg.Token,
},
}
gqlClient := githubv4.NewEnterpriseClient(apiHost.graphqlURL.String(), gqlHTTPClient)
// 4. 初始化时的用户代理更新钩子
beforeInit := func(_ context.Context, _ any, message *mcp.InitializeRequest) {
userAgent := fmt.Sprintf(
"github-mcp-server/%s (%s/%s)",
cfg.Version,
message.Params.ClientInfo.Name,
message.Params.ClientInfo.Version,
)
restClient.UserAgent = userAgent
gqlHTTPClient.Transport = &userAgentTransport{
transport: gqlHTTPClient.Transport,
agent: userAgent,
}
}
// 5. 服务器钩子配置
hooks := &server.Hooks{
OnBeforeInitialize: []server.OnBeforeInitializeFunc{beforeInit},
OnBeforeAny: []server.BeforeAnyHookFunc{
func(ctx context.Context, _ any, _ mcp.MCPMethod, _ any) {
// 确保错误上下文清理
errors.ContextWithGitHubErrors(ctx)
},
},
}
// 6. 工具集管理和注册
enabledToolsets := cfg.EnabledToolsets
if cfg.DynamicToolsets {
// 动态工具集模式下过滤"all"
enabledToolsets = filterToolsets(cfg.EnabledToolsets)
}
// 7. GitHub服务器实例创建
instructions := github.GenerateInstructions(enabledToolsets)
ghServer := github.NewServer(cfg.Version,
server.WithInstructions(instructions),
server.WithHooks(hooks),
)
// 8. 客户端工厂函数设置
getClient := func(_ context.Context) (*gogithub.Client, error) {
return restClient, nil
}
getGQLClient := func(_ context.Context) (*githubv4.Client, error) {
return gqlClient, nil
}
getRawClient := func(ctx context.Context) (*raw.Client, error) {
client, err := getClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
}
return raw.NewClient(client, apiHost.rawURL), nil
}
// 9. 默认工具集创建和启用
tsg := github.DefaultToolsetGroup(cfg.ReadOnly, getClient, getGQLClient, getRawClient, cfg.Translator, cfg.ContentWindowSize)
err = tsg.EnableToolsets(enabledToolsets)
if err != nil {
return nil, fmt.Errorf("failed to enable toolsets: %w", err)
}
// 10. 工具集注册到服务器
tsg.RegisterAll(ghServer)
// 11. 动态工具集支持
if cfg.DynamicToolsets {
dynamic := github.InitDynamicToolset(ghServer, tsg, cfg.Translator)
dynamic.RegisterTools(ghServer)
}
return ghServer, nil
}
parseAPIHost - 多环境API主机解析
// 位置: internal/ghmcp/server.go:384-407
func parseAPIHost(s string) (apiHost, error) {
if s == "" {
return newDotcomHost() // 默认GitHub.com
}
u, err := url.Parse(s)
if err != nil {
return apiHost{}, fmt.Errorf("could not parse host as URL: %s", s)
}
if u.Scheme == "" {
return apiHost{}, fmt.Errorf("host must have a scheme (http or https): %s", s)
}
// 环境检测逻辑
switch {
case strings.HasSuffix(u.Hostname(), "github.com"):
return newDotcomHost() // GitHub.com
case strings.HasSuffix(u.Hostname(), "ghe.com"):
return newGHECHost(s) // GitHub Enterprise Cloud
default:
return newGHESHost(s) // GitHub Enterprise Server
}
}
// 不同环境的API端点配置
func newDotcomHost() (apiHost, error) {
baseRestURL, _ := url.Parse("https://api.github.com/")
gqlURL, _ := url.Parse("https://api.github.com/graphql")
uploadURL, _ := url.Parse("https://uploads.github.com")
rawURL, _ := url.Parse("https://raw.githubusercontent.com/")
return apiHost{
baseRESTURL: baseRestURL,
graphqlURL: gqlURL,
uploadURL: uploadURL,
rawURL: rawURL,
}, nil
}
func newGHESHost(hostname string) (apiHost, error) {
u, err := url.Parse(hostname)
if err != nil {
return apiHost{}, fmt.Errorf("failed to parse GHES URL: %w", err)
}
// GHES API端点路径
restURL, _ := url.Parse(fmt.Sprintf("%s://%s/api/v3/", u.Scheme, u.Hostname()))
gqlURL, _ := url.Parse(fmt.Sprintf("%s://%s/api/graphql", u.Scheme, u.Hostname()))
uploadURL, _ := url.Parse(fmt.Sprintf("%s://%s/api/uploads/", u.Scheme, u.Hostname()))
rawURL, _ := url.Parse(fmt.Sprintf("%s://%s/raw/", u.Scheme, u.Hostname()))
return apiHost{
baseRESTURL: restURL,
graphqlURL: gqlURL,
uploadURL: uploadURL,
rawURL: rawURL,
}, nil
}
1.3 传输层优化
HTTP传输层增强
// 用户代理传输层 - 动态更新用户代理
type userAgentTransport struct {
transport http.RoundTripper
agent string
}
func (t *userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
req.Header.Set("User-Agent", t.agent)
return t.transport.RoundTrip(req)
}
// Bearer认证传输层 - GraphQL API认证
type bearerAuthTransport struct {
transport http.RoundTripper
token string
}
func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
req.Header.Set("Authorization", "Bearer "+t.token)
return t.transport.RoundTrip(req)
}
2. 工具集管理模块 (pkg/toolsets)
2.1 工具集架构设计
classDiagram
class ToolsetGroup {
+toolsets: map[string]*Toolset
+everythingOn: bool
+readOnly: bool
+AddToolset(toolset)
+EnableToolset(name) error
+EnableToolsets(names) error
+RegisterAll(server)
+GetToolset(name) (*Toolset, error)
}
class Toolset {
+name: string
+description: string
+enabled: bool
+readOnly: bool
+readTools: []ServerTool
+writeTools: []ServerTool
+resourceTemplates: []ServerResourceTemplate
+prompts: []ServerPrompt
+AddReadTools(tools) *Toolset
+AddWriteTools(tools) *Toolset
+GetActiveTools() []ServerTool
+RegisterTools(server)
}
class ServerTool {
+Tool: mcp.Tool
+Handler: server.ToolHandlerFunc
}
class ServerResourceTemplate {
+Template: mcp.ResourceTemplate
+Handler: server.ResourceTemplateHandlerFunc
}
class ServerPrompt {
+Prompt: mcp.Prompt
+Handler: server.PromptHandlerFunc
}
ToolsetGroup ||--o{ Toolset
Toolset ||--o{ ServerTool
Toolset ||--o{ ServerResourceTemplate
Toolset ||--o{ ServerPrompt
2.2 核心工具集管理逻辑
工具集启用机制
// 位置: pkg/toolsets/toolsets.go:206-228
func (tsg *ToolsetGroup) EnableToolsets(names []string) error {
// 特殊处理"all"关键词
for _, name := range names {
if name == "all" {
tsg.everythingOn = true
break
}
err := tsg.EnableToolset(name)
if err != nil {
return err
}
}
// 如果启用了"all",则启用所有已注册的工具集
if tsg.everythingOn {
for name := range tsg.Toolsets {
err := tsg.EnableToolset(name)
if err != nil {
return err
}
}
}
return nil
}
// 单个工具集启用
func (tsg *ToolsetGroup) EnableToolset(name string) error {
toolset, exists := tsg.Toolsets[name]
if !exists {
return NewToolsetDoesNotExistError(name)
}
toolset.Enabled = true
tsg.Toolsets[name] = toolset
return nil
}
读写分离机制
// 位置: pkg/toolsets/toolsets.go:65-80
func (t *Toolset) GetActiveTools() []ServerTool {
if t.Enabled {
if t.readOnly {
return t.readTools // 只读模式只返回读工具
}
return append(t.readTools, t.writeTools...) // 完整模式返回所有工具
}
return nil
}
// 写工具添加验证
func (t *Toolset) AddWriteTools(tools ...ServerTool) *Toolset {
for _, tool := range tools {
if *tool.Tool.Annotations.ReadOnlyHint {
panic(fmt.Sprintf("tool (%s) is incorrectly annotated as read-only", tool.Tool.Name))
}
}
if !t.readOnly {
t.writeTools = append(t.writeTools, tools...)
}
return t
}
// 读工具添加验证
func (t *Toolset) AddReadTools(tools ...ServerTool) *Toolset {
for _, tool := range tools {
if !*tool.Tool.Annotations.ReadOnlyHint {
panic(fmt.Sprintf("tool (%s) must be annotated as read-only", tool.Tool.Name))
}
}
t.readTools = append(t.readTools, tools...)
return t
}
2.3 工具集注册流程
sequenceDiagram
participant System as System Init
participant ToolsetGroup as ToolsetGroup
participant Toolset as Individual Toolset
participant MCPServer as MCP Server
Note over System, MCPServer: 工具集注册完整流程
System->>ToolsetGroup: NewToolsetGroup(readOnly)
ToolsetGroup->>ToolsetGroup: Initialize with empty toolsets map
Note over System: 创建各个工具集
loop For each toolset type
System->>Toolset: NewToolset(name, description)
Toolset->>Toolset: AddReadTools(readOnlyTools...)
alt Not read-only mode
Toolset->>Toolset: AddWriteTools(writeTools...)
end
Toolset->>Toolset: AddResourceTemplates(resources...)
Toolset->>Toolset: AddPrompts(prompts...)
System->>ToolsetGroup: AddToolset(toolset)
end
Note over System: 启用指定工具集
System->>ToolsetGroup: EnableToolsets(["repos", "issues", "actions"])
loop For each toolset name
ToolsetGroup->>ToolsetGroup: EnableToolset(name)
ToolsetGroup->>Toolset: toolset.Enabled = true
end
Note over System: 注册到MCP服务器
System->>ToolsetGroup: RegisterAll(mcpServer)
loop For each enabled toolset
ToolsetGroup->>Toolset: RegisterTools(mcpServer)
Toolset->>MCPServer: AddTool(tool, handler)
ToolsetGroup->>Toolset: RegisterResourcesTemplates(mcpServer)
Toolset->>MCPServer: AddResourceTemplate(template, handler)
ToolsetGroup->>Toolset: RegisterPrompts(mcpServer)
Toolset->>MCPServer: AddPrompt(prompt, handler)
end
MCPServer-->>System: All toolsets registered and ready
3. GitHub客户端管理模块
3.1 多客户端架构
classDiagram
class ClientManager {
+getClient(ctx) (*github.Client, error)
+getGQLClient(ctx) (*githubv4.Client, error)
+getRawClient(ctx) (*raw.Client, error)
}
class RESTClient {
+client: *github.Client
+baseURL: *url.URL
+uploadURL: *url.URL
+userAgent: string
+token: string
}
class GraphQLClient {
+client: *githubv4.Client
+endpoint: string
+httpClient: *http.Client
+transport: bearerAuthTransport
}
class RawClient {
+client: *raw.Client
+baseURL: *url.URL
+githubClient: *github.Client
}
ClientManager --> RESTClient
ClientManager --> GraphQLClient
ClientManager --> RawClient
RESTClient --> GitHubRESTAPI
GraphQLClient --> GitHubGraphQLAPI
RawClient --> GitHubRawAPI
class GitHubRESTAPI {
+Issues: IssuesService
+PullRequests: PullRequestsService
+Repositories: RepositoriesService
+Actions: ActionsService
}
class GitHubGraphQLAPI {
+Query(ctx, query, variables)
+Mutate(ctx, mutation, input, variables)
}
class GitHubRawAPI {
+GetRawContent(ctx, owner, repo, path, opts)
+DownloadFile(url) ([]byte, error)
}
3.2 API选择策略
智能API路由机制
// API选择策略示例
func chooseAPIStrategy(operation string, dataSize int, complexity string) APIType {
switch {
case operation == "list_issues" && complexity == "with_labels":
return GraphQLAPI // GraphQL更适合复杂查询
case operation == "get_file_contents" && dataSize > 1024*1024:
return RawAPI // Raw API更适合大文件下载
case strings.HasPrefix(operation, "create_") || strings.HasPrefix(operation, "update_"):
return RESTAPI // REST API更适合CRUD操作
case operation == "batch_operations":
return GraphQLAPI // GraphQL支持批量查询
default:
return RESTAPI // 默认使用REST API
}
}
// 实际使用示例 - GetFileContents中的API选择
func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) {
// ...
// 对于文件,优先使用Raw API获取内容
if path != "" && !strings.HasSuffix(path, "/") {
// 1. 先用REST API获取文件元信息
fileContent, _, respContents, err := client.Repositories.GetContents(ctx, owner, repo, path, opts)
// 2. 再用Raw API下载文件内容(更高效)
rawClient, err := getRawClient(ctx)
resp, err := rawClient.GetRawContent(ctx, owner, repo, path, rawOpts)
// Raw API成功则直接返回,失败则回退到REST API
if resp.StatusCode == http.StatusOK {
// 使用Raw API结果
return processRawContent(resp)
}
}
// 回退到REST API
return useRESTAPIFallback(client, owner, repo, path)
}
3.3 客户端配置优化
连接池和超时配置
// 优化的HTTP客户端配置
func createOptimizedHTTPClient() *http.Client {
transport := &http.Transport{
MaxIdleConns: 100, // 最大空闲连接数
MaxIdleConnsPerHost: 10, // 每个主机最大空闲连接数
IdleConnTimeout: 90 * time.Second, // 空闲连接超时
TLSHandshakeTimeout: 10 * time.Second, // TLS握手超时
ResponseHeaderTimeout: 10 * time.Second, // 响应头超时
}
return &http.Client{
Transport: transport,
Timeout: 30 * time.Second, // 总请求超时
}
}
// GitHub企业版SSL配置
func configureEnterpriseSSL(client *http.Client, host string) {
if isEnterpriseHost(host) {
// 企业版可能有自签名证书,需要特殊处理
transport := client.Transport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: false, // 生产环境应该验证证书
ServerName: extractHostname(host),
}
}
}
4. 错误处理模块 (pkg/errors)
4.1 错误处理架构
classDiagram
class ErrorHandler {
+NewGitHubAPIErrorResponse(ctx, message, resp, err)
+NewGitHubGraphQLErrorResponse(ctx, message, err)
+ContextWithGitHubErrors(ctx) context.Context
}
class GitHubAPIError {
+Message: string
+StatusCode: int
+RateLimit: GitHubRateLimit
+Response: *github.Response
+Error: string
+Timestamp: time.Time
}
class GitHubGraphQLError {
+Message: string
+Error: string
+Timestamp: time.Time
}
class GitHubRateLimit {
+Limit: int
+Remaining: int
+Reset: time.Time
}
class GitHubCtxErrors {
+api: []*GitHubAPIError
+graphQL: []*GitHubGraphQLError
+AddError(error)
+GetErrors() []error
}
ErrorHandler --> GitHubAPIError
ErrorHandler --> GitHubGraphQLError
GitHubAPIError --> GitHubRateLimit
GitHubCtxErrors --> GitHubAPIError
GitHubCtxErrors --> GitHubGraphQLError
4.2 用户友好错误映射
// 位置: pkg/errors/error.go
func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github.Response, err error) *mcp.CallToolResult {
// 1. 记录详细错误信息到上下文
NewGitHubAPIErrorToCtx(ctx, message, resp, err)
// 2. HTTP状态码到用户友好消息的映射
if resp != nil {
switch resp.StatusCode {
case 401:
return mcp.NewToolResultError("身份验证失败。请检查您的GitHub令牌是否正确。")
case 403:
if strings.Contains(resp.Status, "rate limit") {
resetTime := resp.Rate.Reset.Format("15:04:05")
return mcp.NewToolResultError(fmt.Sprintf("API速率限制已达上限。请在 %s 之后重试。剩余请求数:%d/%d",
resetTime, resp.Rate.Remaining, resp.Rate.Limit))
}
return mcp.NewToolResultError("访问被禁止。请检查仓库权限或令牌scope设置。")
case 404:
return mcp.NewToolResultError("未找到资源。请验证仓库名称、分支名或文件路径是否正确。")
case 422:
return mcp.NewToolResultError("请求参数无效。请检查输入参数的格式和值。")
case 500, 502, 503, 504:
return mcp.NewToolResultError("GitHub服务暂时不可用。请稍后重试。")
default:
return mcp.NewToolResultError(fmt.Sprintf("%s: HTTP %d - %s", message, resp.StatusCode, resp.Status))
}
}
// 3. 网络错误等其他错误
if err != nil {
if strings.Contains(err.Error(), "timeout") {
return mcp.NewToolResultError("请求超时。请检查网络连接或稍后重试。")
}
if strings.Contains(err.Error(), "connection refused") {
return mcp.NewToolResultError("无法连接到GitHub。请检查网络设置。")
}
}
return mcp.NewToolResultError(fmt.Sprintf("%s: %v", message, err))
}
4.3 错误上下文收集机制
// 错误上下文管理
func ContextWithGitHubErrors(ctx context.Context) context.Context {
if ctx == nil {
ctx = context.Background()
}
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
// 如果上下文已有错误收集器,清空以开始新的请求
val.api = []*GitHubAPIError{}
val.graphQL = []*GitHubGraphQLError{}
} else {
// 创建新的错误收集器
ctx = context.WithValue(ctx, GitHubErrorKey{}, &GitHubCtxErrors{
api: []*GitHubAPIError{},
graphQL: []*GitHubGraphQLError{},
})
}
return ctx
}
// 添加API错误到上下文
func NewGitHubAPIErrorToCtx(ctx context.Context, message string, resp *github.Response, err error) {
if ctx == nil {
return
}
errorInfo := GitHubError{
Message: message,
StatusCode: 0,
Timestamp: time.Now(),
}
if resp != nil {
errorInfo.StatusCode = resp.StatusCode
errorInfo.RateLimit = GitHubRateLimit{
Limit: resp.Rate.Limit,
Remaining: resp.Rate.Remaining,
Reset: resp.Rate.Reset.Time,
}
}
if err != nil {
errorInfo.Error = err.Error()
}
// 将错误添加到上下文收集器
if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok {
apiErr := newGitHubAPIError(message, resp, err)
val.api = append(val.api, apiErr)
}
}
5. 数据类型优化模块 (pkg/github/minimal_types)
5.1 最小化数据结构设计
设计原则
- 减少响应大小:只包含必要字段,减少网络传输和内存使用
- 保持兼容性:与GitHub API响应结构保持足够兼容
- 类型安全:使用强类型而非map[string]interface{}
- 易于序列化:支持JSON序列化,便于前端使用
核心数据类型
// 最小化仓库结构 - 只保留关键信息
type MinimalRepository struct {
ID int64 `json:"id"`
Name string `json:"name"`
FullName string `json:"full_name"`
Description string `json:"description,omitempty"`
HTMLURL string `json:"html_url"`
Language string `json:"language,omitempty"`
Stars int `json:"stargazers_count"`
Forks int `json:"forks_count"`
OpenIssues int `json:"open_issues_count"`
UpdatedAt string `json:"updated_at,omitempty"`
Topics []string `json:"topics,omitempty"`
Private bool `json:"private"`
Fork bool `json:"fork"`
Archived bool `json:"archived"`
DefaultBranch string `json:"default_branch,omitempty"`
}
// 最小化提交结构 - 支持可选的diff信息
type MinimalCommit struct {
SHA string `json:"sha"`
HTMLURL string `json:"html_url"`
Commit *MinimalCommitInfo `json:"commit,omitempty"`
Author *MinimalUser `json:"author,omitempty"`
Committer *MinimalUser `json:"committer,omitempty"`
Stats *MinimalCommitStats `json:"stats,omitempty"` // 只在需要时包含
Files []MinimalCommitFile `json:"files,omitempty"` // 只在需要时包含
}
// 通用最小响应 - 用于CRUD操作
type MinimalResponse struct {
ID string `json:"id"` // 资源标识符
URL string `json:"url"` // 资源访问URL
}
5.2 转换函数设计
智能字段包含策略
// 提交转换函数 - 支持按需包含diff信息
func convertToMinimalCommit(commit *github.RepositoryCommit, includeDiffs bool) MinimalCommit {
minimalCommit := MinimalCommit{
SHA: commit.GetSHA(),
HTMLURL: commit.GetHTMLURL(),
}
// 基本提交信息(总是包含)
if commit.Commit != nil {
minimalCommit.Commit = &MinimalCommitInfo{
Message: commit.Commit.GetMessage(),
}
// 作者信息
if commit.Commit.Author != nil {
minimalCommit.Commit.Author = &MinimalCommitAuthor{
Name: commit.Commit.Author.GetName(),
Email: commit.Commit.Author.GetEmail(),
Date: formatTime(commit.Commit.Author.Date),
}
}
}
// 用户信息(GitHub用户,可能为空)
if commit.Author != nil {
minimalCommit.Author = &MinimalUser{
Login: commit.Author.GetLogin(),
ID: commit.Author.GetID(),
ProfileURL: commit.Author.GetHTMLURL(),
AvatarURL: commit.Author.GetAvatarURL(),
}
}
// Diff信息(按需包含,避免不必要的数据传输)
if includeDiffs {
if commit.Stats != nil {
minimalCommit.Stats = &MinimalCommitStats{
Additions: commit.Stats.GetAdditions(),
Deletions: commit.Stats.GetDeletions(),
Total: commit.Stats.GetTotal(),
}
}
if len(commit.Files) > 0 {
minimalCommit.Files = make([]MinimalCommitFile, 0, len(commit.Files))
for _, file := range commit.Files {
minimalFile := MinimalCommitFile{
Filename: file.GetFilename(),
Status: file.GetStatus(),
Additions: file.GetAdditions(),
Deletions: file.GetDeletions(),
Changes: file.GetChanges(),
}
minimalCommit.Files = append(minimalCommit.Files, minimalFile)
}
}
}
return minimalCommit
}
5.3 性能优化效果
数据大小对比
数据类型 | GitHub API原始大小 | MinimalType大小 | 压缩率 |
---|---|---|---|
Repository | ~2KB | ~0.5KB | 75% |
Commit (无diff) | ~1KB | ~0.3KB | 70% |
Commit (含diff) | ~10KB | ~3KB | 70% |
User | ~1KB | ~0.2KB | 80% |
Issue | ~3KB | ~0.8KB | 73% |
内存使用优化
// 示例:搜索仓库结果的内存优化
func convertSearchResults(githubResult *github.RepositoriesSearchResult) MinimalSearchRepositoriesResult {
minimal := MinimalSearchRepositoriesResult{
TotalCount: githubResult.GetTotal(),
IncompleteResults: githubResult.GetIncompleteResults(),
Items: make([]MinimalRepository, 0, len(githubResult.Repositories)),
}
// 只转换必要字段,大幅减少内存使用
for _, repo := range githubResult.Repositories {
minimalRepo := MinimalRepository{
ID: repo.GetID(),
Name: repo.GetName(),
FullName: repo.GetFullName(),
Description: repo.GetDescription(),
HTMLURL: repo.GetHTMLURL(),
Language: repo.GetLanguage(),
Stars: repo.GetStargazersCount(),
Forks: repo.GetForksCount(),
OpenIssues: repo.GetOpenIssuesCount(),
Private: repo.GetPrivate(),
Fork: repo.GetFork(),
Archived: repo.GetArchived(),
DefaultBranch: repo.GetDefaultBranch(),
}
// 时间字段格式化
if repo.UpdatedAt != nil {
minimalRepo.UpdatedAt = repo.UpdatedAt.Format("2006-01-02T15:04:05Z")
}
minimal.Items = append(minimal.Items, minimalRepo)
}
return minimal
}
6. 模块间协作机制
6.1 依赖注入模式
graph TD
A[Main Application] --> B[Server Core]
B --> C[Toolset Group]
C --> D[Individual Toolsets]
D --> E[GitHub Clients]
D --> F[Error Handler]
D --> G[Translator]
E --> H[REST Client]
E --> I[GraphQL Client]
E --> J[Raw Client]
F --> K[Context Error Collector]
F --> L[User-Friendly Mapper]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff3e0
6.2 接口抽象层
// 客户端工厂接口
type GetClientFn func(context.Context) (*github.Client, error)
type GetGQLClientFn func(context.Context) (*githubv4.Client, error)
type GetRawClientFn func(context.Context) (*raw.Client, error)
// 翻译函数接口
type TranslationHelperFunc func(key string, defaultValue string) string
// 工具创建接口
type ToolCreatorFunc func(GetClientFn, TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc)
// 这种设计的优势:
// 1. 测试友好 - 可以轻松mock客户端
// 2. 延迟初始化 - 只在需要时创建客户端
// 3. 错误处理 - 统一的错误处理策略
// 4. 配置灵活 - 支持不同环境的客户端配置
6.3 生命周期管理
sequenceDiagram
participant App as Application
participant Core as Server Core
participant Toolsets as Toolsets
participant Clients as GitHub Clients
participant Resources as Resources
Note over App, Resources: 完整的生命周期管理
App->>Core: Initialize with config
Core->>Clients: Create client factories
Core->>Toolsets: Create toolset group
Note over Core: Configuration Phase
Core->>Toolsets: Add all toolsets (disabled)
Core->>Toolsets: Enable specified toolsets
Note over Core: Registration Phase
Core->>Toolsets: RegisterAll(mcpServer)
loop For each enabled toolset
Toolsets->>Resources: Register tools
Toolsets->>Resources: Register resources
Toolsets->>Resources: Register prompts
end
Note over Core: Runtime Phase
Core->>Core: Start stdio server
Core->>Core: Handle MCP requests
Note over Core: Request Processing
Core->>Toolsets: Route tool calls
Toolsets->>Clients: Make API calls
Clients-->>Toolsets: API responses
Toolsets-->>Core: Tool results
Note over Core: Shutdown Phase
App->>Core: Shutdown signal
Core->>Clients: Close connections
Core->>Resources: Cleanup resources
Core-->>App: Shutdown complete
7. 总结
GitHub MCP Server 的核心模块设计体现了以下特点:
设计优势
- 模块化架构:清晰的模块边界,便于维护和扩展
- 依赖注入:使用工厂模式和接口抽象,提高测试性
- 错误处理:分层错误处理,用户友好的错误消息
- 性能优化:最小化数据结构,减少内存和网络开销
- 多环境支持:统一的客户端管理支持各种GitHub部署
扩展能力
- 新工具集:可以轻松添加新的功能模块
- 新API类型:支持扩展到其他类型的API调用
- 新错误类型:可以添加更多的错误处理策略
- 新数据格式:支持添加更多的优化数据结构
运维友好
- 可观测性:内置错误收集和性能分析
- 配置灵活:支持环境变量和配置文件
- 部署简单:支持容器化和多种部署方式
- 监控完善:详细的日志记录和指标收集
这种设计使得 GitHub MCP Server 能够作为一个稳定、高效、可扩展的中间件服务,为各种AI工具提供强大的GitHub平台集成能力。