MCP 서버
f385262 기준입니다.
github-mcp-server는 GitHub API를 MCP 도구로 감싸서 AI 클라이언트에 제공합니다. 이 저장소에서 배울 핵심은 외부 API를 AI가 호출할 수 있는 작고 검증 가능한 작업으로 바꾸는 방법입니다. 언어 모델이 GitHub의 HTTP 주소와 응답 형식을 매번 직접 조합하는 대신, 서버가 준비한 도구를 선택하도록 만드는 것입니다.
예를 들어 사용자가 “내 GitHub 계정을 알려줘”라고 말하면 AI 호스트는 get_me를 선택해 호출할 수 있습니다.
pkg/github/context_tools.go의 GetMe는 인증된 GitHub 클라이언트를 받아
client.Users.Get(ctx, "")를 실행하고, 응답을 MinimalUser로 옮깁니다.
자연어를 이해하는 쪽은 호스트의 모델이고, 이 Go 서버는 정해진 작업을 수행하는 쪽입니다.
직원은 “이 이슈를 찾아 주세요”라고 요청합니다. 창구에는 신청서 이름과 필수 칸이 정해져 있습니다. 도구 이름이 신청서 제목이고, JSON Schema가 신청서 양식이며, 토큰은 업무를 맡길 수 있는 출입증입니다.
창구는 출입증으로 가능한 업무를 확인하고 GitHub에 요청한 뒤 결과를 정리합니다. 신청서가 있다고 모든 일을 할 수 있는 것은 아닙니다. 실제로 허용되는 범위는 GitHub의 권한 검사까지 거쳐 결정됩니다.
| MCP 요소 | 쉬운 설명 | 이 저장소의 예 |
|---|---|---|
| 도구 | 입력을 받아 작업을 실행하는 함수 | get_me, search_repositories, issue_write |
| 리소스 | 주소로 읽는 자료 | 저장소 파일 리소스와 MCP App UI HTML |
| 프롬프트 | 재사용할 수 있는 작업 안내 | pkg/github/prompts.go, pkg/github/workflow_prompts.go |
GitHub 쪽 기능은 저장소·이슈·PR·Actions·보안 경고·Projects 등으로 넓어집니다.
모든 기능을 한꺼번에 이해할 필요는 없습니다. 먼저 get_me의 입력·클라이언트·출력 세 부분을 찾으면,
다른 도구도 비슷한 틀로 읽을 수 있습니다.
“어떤 JSON을 받는가 → 어느 함수가 실행되는가 → 어떤 권한과 API를 쓰는가 → 무엇을 돌려주는가”를 연결해 보세요. 파일 수보다 요청의 흐름을 이해하는 것이 먼저입니다.
2026-10-23 GitHub Trending Go Weekly #10 선정 프로젝트이며, GitHub 기준 스타는 33,883개입니다. 저장소와 코드 검색에 머물지 않고 이슈 처리·PR 관리·CI 확인까지 연결한다는 점이 학습 대상으로 매력적입니다. 주목할 설계적 장점은 GitHub 작업을 여러 AI 호스트에서 재사용할 수 있는 공통 도구로 만든다는 데 있습니다.
이는 코드와 기능 구성에서 읽어낸 관심 요인입니다. 주간 순위의 정확한 상승 원인이나 스타 증가량은 별도 시계열 자료가 필요합니다.
최신 릴리스가 v2.0.3(2026-10-21)이고 최근 push가 2026-10-22라는 사실만으로,
특정 기능 때문에 순위가 올랐다고 단정할 수는 없습니다.
| 방식 | 강점 | 학습·운영 시 고려할 점 |
|---|---|---|
| REST / GraphQL 직접 호출 | 요청과 응답을 세밀하게 제어 | 인증·입력 검사·모델용 설명·오류 변환을 직접 구성 |
GitHub CLI gh | 사람의 터미널 작업과 명령 자동화에 적합 | 에이전트와 연결할 때 명령 선택과 출력 처리 방식을 별도로 설계 |
| 호스트별 GitHub 연동 | 특정 제품의 사용 흐름에 맞게 제공 가능 | 공개되는 기능과 설정 방법이 호스트마다 달라질 수 있음 |
| GitHub 공식 MCP 서버 | 도구 설명·스키마·권한 필터를 서버에서 묶어 제공 | MCP 지원 호스트가 필요하며, 토큰·조직 정책의 제약을 받음 |
이 표는 접근 방식의 차이입니다. 응답 속도나 안정성 우열을 벤치마크한 결과는 아닙니다. 이 저장소의 교육적 가치는 API 호출 한 줄 주변에 필요한 제품 설계를 한 번에 볼 수 있다는 점입니다.
README는 GitHub가 호스팅하는 원격 서버 주소를 https://api.githubcopilot.com/mcp/로 안내합니다.
로컬에서는 실행 파일이나 Docker를 이용해 stdio 방식으로 연결할 수 있습니다.
현재 소스에는 자체 호스팅을 위한 http 하위 명령도 있습니다.
원격 서버는 이미 운영 중인 업무 창구를 이용하는 방식이고, 로컬 서버는 내 환경에서 창구 프로그램을 띄우는 방식입니다. 두 경우 모두 업무 원장은 GitHub에 있으므로, 로컬 실행이어도 실제 작업에는 GitHub와의 통신이 필요합니다.
공개 소스의 pkg/github/tools.go에는 원격 전용 도구 모음도 문서화되어 있습니다.
copilot_spaces와 github_support_docs_search는 원격 전용으로 분리됩니다.
따라서 로컬 실행과 GitHub의 운영 서비스가 정확히 같은 기능을 제공한다고 가정하면 안 됩니다.
go.mod의 모듈 경로는 github.com/github/github-mcp-server/v2이고,
Go 지시문은 go 1.27.3입니다. 아래 버전은 커밋 f385262의 직접 의존성 선언입니다.
| 기술 | 실제 버전 | 역할과 읽을 파일 |
|---|---|---|
| Go | 1.27.3 | 서버·도구·인증 구현. go.mod |
| MCP Go SDK | v1.8.0 | 프로토콜·도구 등록·전송. pkg/github/server.go |
| go-github / v92 | v92.0.0 | GitHub REST API 클라이언트 |
| githubv4 | v0.0.0-20260209031235-2402fdf4a9ed | GraphQL 요청. pkg/github/issues.go |
| google/jsonschema-go | v0.4.3 | 입력·출력 스키마와 검증 |
| chi / v5 | v5.3.2 | HTTP 라우터. pkg/http/server.go |
| Cobra / Viper | v1.10.2 / v1.21.0 | CLI 명령·플래그·환경 변수. cmd/github-mcp-server/main.go |
| bluemonday | v1.0.27 | HTML 정제 정책. pkg/sanitize/sanitize.go |
| testify | v1.12.1 | 테스트 단언. pkg/github/context_tools_test.go |
githubv4의 긴 버전 문자열은 커밋을 식별하는 Go 모듈 버전입니다.
날짜 부분과 커밋 식별자가 들어 있습니다. 정식 태그 이름이 없어도 어떤 소스를 쓰는지 고정할 수 있습니다.ui/package.json은 @github/mcp-server-ui를 private: true로 선언합니다.
서버 릴리스 v2.0.3과 UI 패키지 자체 버전 1.0.0은 서로 다른 번호입니다.
아래 표는 선언 범위와 ui/package-lock.json의 고정 버전을 구분합니다.
| 기술 | package.json → 잠금 버전 | 역할 |
|---|---|---|
| Node.js | ^26.0.0 (engines) | UI 빌드·UI 단위 테스트 환경 |
| React / React DOM | ^19.3.0 → 19.3.0 | 화면과 상태 관리 |
| Primer React | ^38.40.1 → 38.40.1 | GitHub 디자인 시스템의 컴포넌트 |
| MCP Apps 확장 | ^2.0.3 → 2.0.3 | 호스트와 화면의 도구 호출·결과 통신 |
| MCP JS 클라이언트 | ^2.3.1 → 2.3.1 | 클라이언트 타입·프로토콜 연동 |
| TypeScript | ^7.0.2 → 7.0.2 | 타입 검사. ui/tsconfig.json의 strict: true |
| Vite | ^8.3.2 → 8.3.2 | 앱별 번들 생성 |
| vite-plugin-singlefile | 선언 ^2.3.3 | 화면의 JS·CSS를 HTML에 합치는 빌드 플러그인 |
ui/scripts/build.mjs는 get-me, issue-write, pr-write,
pr-edit 네 앱을 순서대로 빌드합니다. ui/vite.config.ts가 결과를
pkg/github/ui_dist/로 보내고, pkg/github/ui_embed.go의 go:embed가 Go 실행 파일에 담습니다.
이 프론트엔드는 별도 웹사이트를 운영하는 구조보다, Go 서버가 AI 호스트에 건네줄 화면을 미리 포장하는 구조에 가깝습니다. 저장소의 UI에는 실행용 코드가 있지만, MCP App을 지원하는 호스트에서 리소스로 제공되는 방식입니다.
| 근거 파일 | 확인한 설정 | 배울 점 |
|---|---|---|
Dockerfile | node:26-alpine → golang:1.27.3-alpine → distroless/base-debian12 | 빌드 도구와 실행 환경을 단계별로 분리 |
Dockerfile | CGO_ENABLED=0, 기본 CMD ["stdio"] | Go 바이너리 배포와 기본 실행 방식 |
.goreleaser.yaml | 설정 형식 version: 2, Linux·Windows·Darwin | 운영체제별 실행 파일과 압축 형식 자동화 |
.github/workflows/go.yml | Ubuntu·Windows·macOS, go mod tidy -diff, script/test | 환경 차이와 의존성 변경을 검사 |
.github/workflows/lint.yml | golangci-lint v2.14.0 | 정적 검사 규칙을 일정하게 유지 |
.github/workflows/docker-publish.yml | linux/amd64,linux/arm64, GHCR | 서로 다른 CPU용 이미지 배포 |
세 베이스 이미지는 SHA-256 digest로 고정되어 있습니다.
.goreleaser.yaml의 숫자 2는 도구 설정 형식이며, GoReleaser 실행 파일의 버전 번호를 뜻하지 않습니다.
설정을 읽을 때 이름이 같은 숫자라도 무엇의 버전인지 먼저 확인해야 합니다.
cmd/github-mcp-server/main.go는 실행 모드를 고르는 출입구입니다.
stdioCmd는 ghmcp.RunStdioServer로,
httpCmd는 ghhttp.RunHTTPServer로 연결됩니다.
두 경로는 도구 정의와 github.NewMCPServer를 공유하면서, 인증 정보의 수명은 다르게 관리합니다.
아래는 이미 MCP 연결 초기화를 마쳤다고 가정한 도구 호출 메시지 예시입니다. 실제 전송에는 선택한 방식의 연결 절차와 헤더가 더 필요합니다.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_me",
"arguments": {}
}
}
AllTools에 포함된 GetMe 정의가 Inventory의 후보가 됩니다.context.Context에 의존성과 요청 상태를 넣습니다.GetMeInput을 핸들러에 전달합니다.deps.GetClient(ctx)로 클라이언트를 얻고 Users.Get(ctx, "")를 호출합니다.MinimalUser를 구성해 텍스트 결과와 타입 기반 출력을 반환합니다.pkg/github/context_tools.go에서 흐름을 확인할 핵심 구문은 다음과 같습니다.
전체 함수에서 오류 처리와 출력 필드 일부를 생략한 발췌입니다.
// GetMe 안에서 의존성 획득 → REST 요청 → 결과 생성으로 이어집니다.
client, err := deps.GetClient(ctx)
user, res, err := client.Users.Get(ctx, "")
// 실제 함수는 이 사이에서 오류를 검사하고 MinimalUser를 구성합니다.
result := MarshalledTextResult(minimalUser)
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelGetMe())
return result, minimalUser, nil
""는 이 호출에서 인증된 사용자 조회에 쓰입니다.
MinimalUser라는 이름만 보고 모든 정보가 공개 프로필이라고 생각하면 안 됩니다.
이 함수의 Details에는 비공개 저장소 수 같은 필드도 포함될 수 있습니다.
어댑터는 서로 다른 인터페이스를 연결하는 변환기입니다.
pkg/github/dependencies.go의 NewTool[In, Out]이 타입을 가진 핸들러와
도구 메타데이터를 묶고, pkg/inventory/server_tool.go가 SDK 등록을 맡습니다.
GetMe는 GetMeInput → MinimalUser,
SearchRepositories는 SearchRepositoriesInput → *SearchRepositoriesOutput의 형태입니다.
후자는 pkg/github/search.go에서 client.Search.Repositories를 호출합니다.
API 응답 전체를 그대로 넘길지, 모델에 필요한 필드만 정리할지가 이 계층의 중요한 결정입니다.
SearchRepositories의 minimal_output 기본값은 true입니다.
설명에는 최신 클라이언트에서 false를 주어도 선별된 상세 필드를 반환하며,
예전 클라이언트와 동작이 다를 수 있다고 명시됩니다. 입력 옵션 이름만으로 출력 계약을 추측하지 않는 연습이 됩니다.
pkg/github/inventory.go의 NewInventory는 도구·리소스·프롬프트를 모읍니다.
pkg/inventory/builder.go의 Builder는 설정을 누적하고,
Build가 최종 Inventory를 만듭니다.
// internal/ghmcp/server.go의 구성 방식에서 핵심만 발췌
inventoryBuilder := github.NewInventory(cfg.Translator, github.WithHost(hostType)).
WithDeprecatedAliases(github.DeprecatedToolAliases).
WithReadOnly(cfg.ReadOnly).
WithToolsets(github.ResolvedEnabledToolsets(cfg.EnabledToolsets, cfg.EnabledTools)).
WithTools(github.CleanTools(cfg.EnabledTools)).
WithExcludeTools(cfg.ExcludeTools).
WithServerInstructions().
WithFeatureChecker(featureChecker)
| 입력 상태 | 실제 의미 | 소스 근거 |
|---|---|---|
nil 도구 모음 | 기본 도구 모음 사용 | Builder.WithToolsets |
| 빈 슬라이스 | 도구 모음에서 아무것도 선택하지 않음 | processToolsets |
| 개별 도구만 지정 | 기본 모음을 추가하지 않고 해당 도구 선택 | ResolvedEnabledToolsets |
| 제외 도구 지정 | 명시적으로 추가했어도 제외 필터 적용 | WithExcludeTools, isToolEnabled |
기본 모음은 context, repos, issues, pull_requests,
users, copilot로 표시됩니다. 실제 도구는 추가 조건을 더 통과해야 합니다.
모르는 개별 도구 이름은 Build에서 오류가 되지만, 모르는 모음은 경고 대상으로 기록됩니다.
도구를 정의하는 코드와 그 도구를 누구에게 공개할지 결정하는 코드를 분리하면, 기능을 늘려도 정책을 한곳에서 검토할 수 있습니다.
ToolDependencies는 REST·GraphQL·Raw 클라이언트, 로깅, 기능 설정 등을 얻는 인터페이스입니다.
BaseDeps는 로컬 서버에서 미리 만든 클라이언트를 보관합니다.
RequestDeps.GetClient(ctx)는 HTTP 요청의 토큰 정보를 꺼내 해당 인증 정보로 클라이언트를 만듭니다.
도구는 “배송 담당자를 주세요”라는 창구만 압니다. 개인용 창구에서는 같은 담당자를 계속 쓰고, 여러 사용자가 오는 창구에서는 그 요청의 출입증에 맞는 담당자를 배정합니다. 도구 함수가 인증 정보를 전역 변수에서 가져올 필요가 없어집니다.
InjectDepsMiddleware와 ContextWithDeps가 의존성을 전달하고,
NewToolWithSchemaOptions의 래퍼가 MustDepsFromContext로 꺼냅니다.
테스트에서는 이 인터페이스의 구현을 바꿔 네트워크 없이 성공·실패 응답을 제공할 수 있습니다.
들어오는 HTTP 요청에는 Handler.RegisterMiddleware가 본문 크기 제한 → 토큰 추출 → 요청 설정 →
MCP 메시지 파싱 → PAT scope 처리를 순서대로 붙입니다. 개별 도구마다 같은 코드를 반복하지 않도록 만드는 구조입니다.
밖으로 나가는 요청에는 http.RoundTripper를 감싼 전송 계층이 쓰입니다.
로컬 REST 경로는 BearerAuthTransport → UserAgentTransport → ETagTransport → http.DefaultTransport로 이어집니다.
인증 헤더가 설정된 뒤 캐시 키를 계산해야 사용자별 응답을 구분할 수 있으므로 순서가 중요합니다.
pkg/http/handler.go의 ServeHTTP는 요청마다 MCP 서버를 만들고
mcp.NewStreamableHTTPHandler에 Stateless: true를 전달합니다.
대신 mcp.SchemaCache는 Handler에서 공유해 요청마다 반복되는 스키마 처리 비용을 줄입니다.
변하지 않는 스키마는 재사용하고, 사용자의 토큰과 요청 설정은 요청 문맥에서 읽습니다. “모두 공유”와 “모두 새로 생성” 사이에서 데이터의 성격에 맞게 수명을 선택하는 사례입니다.
위 트리는 학습에 필요한 부분을 추린 것입니다. internal은 Go의 가져오기 제한을 받는 내부 구현이고,
pkg는 공개 패키지를 모으는 관례입니다. pkg라는 이름 자체가 API 안정성을 보장하지는 않습니다.
| 순서 | 파일 | 읽으면서 답할 질문 |
|---|---|---|
| 1 | cmd/github-mcp-server/main.go | 입력 설정은 무엇이며 어떤 실행 모드로 전달되는가? |
| 2 | pkg/github/context_tools.go | get_me는 무엇을 받아 무엇을 돌려주는가? |
| 3 | pkg/github/dependencies.go | 실제 API 클라이언트는 어디에서 오는가? |
| 4 | pkg/inventory/filters.go | 정의된 도구가 왜 목록에서 빠지는가? |
| 5 | pkg/github/context_tools_test.go | 성공과 실패를 어떻게 재현하는가? |
| 6 | pkg/http/handler.go | 여러 사용자의 HTTP 요청에는 무엇이 더 필요한가? |
| 분류 | 파일 수 | 물리적 줄 수 |
|---|---|---|
| Go 구현 | 157 | 57,163 |
| Go 테스트 | 142 | 82,998 |
| Go 합계 | 299 | 140,161 |
| TypeScript / TSX | 6 / 7 | 329 / 4,519 |
| JavaScript / MJS | 0 / 1 | 0 / 14 |
| CSS / HTML | 1 / 6 | 148 / 165 |
| YAML / Markdown | 19 / 46 | 1,135 / 8,109 |
커밋 f385262의 파일을 대상으로 빈 줄과 주석을 포함해 집계했습니다.
.git, node_modules, vendor, third-party, ui_dist는 제외했습니다.
JSON 잠금 파일과 스냅샷 파일도 위 언어별 합계에 넣지 않았습니다.
Go 테스트가 전체 Go 줄 수의 약 59.2%입니다. 이 비율은 테스트 코드가 많다는 뜻이며,
실행 경로의 59.2%가 검증됐다는 커버리지 수치와는 다릅니다.
구현을 읽다 막히면 대응하는 *_test.go 파일의 입력 사례로 돌아가 보세요.
ToolDependencies는 “어떤 객체인가”보다 “무엇을 제공하는가”로 도구를 연결합니다.
NewTool[In, Out]의 타입 매개변수는 도구별 입력·출력을 표현합니다.
context.Context는 취소 신호와 함께 요청의 인증·설정 정보를 전달하는 통로로 쓰입니다.
// pkg/github/dependencies.go의 메서드 선언 중 일부
GetClient(ctx context.Context) (*gogithub.Client, error)
GetGQLClient(ctx context.Context) (*githubv4.Client, error)
// 같은 파일의 컴파일 시 인터페이스 구현 확인
var _ ToolDependencies = (*BaseDeps)(nil)
해볼 일: BaseDeps.GetClient와 RequestDeps.GetClient를 나란히 적고,
반환 타입은 같지만 토큰을 얻는 위치가 어떻게 다른지 표시하세요.
이어서 nil 슬라이스와 빈 슬라이스가 도구 선택에 서로 다른 뜻을 갖는 이유를 설명해 보세요.
GetMe와 SearchRepositories는 REST 클라이언트를 사용합니다.
반면 pkg/github/issues.go의 fetchIssueIDs는 GraphQL로 이슈의 내부 ID를 조회합니다.
중복 이슈가 주어지면 별칭 duplicateIssue를 사용해 두 이슈의 ID를 한 질의에 담습니다.
해볼 일: fetchIssueIDs의 duplicateOf == 0 분기와 나머지 분기를 비교하세요.
필요한 필드가 ID뿐일 때, 이슈 전체 본문을 요청하는 것과 어떤 차이가 있는지 써 보세요.
pkg/inventory/typed_output.go의 wrapTypedHandler는 기본값 적용과 스키마 검증을 거쳐
입력 타입으로 디코딩합니다. 디코더에는 DontMatchCaseInsensitiveStructFields를 설정합니다.
필드 이름 대소문자까지 계약의 일부로 취급하는 사례입니다.
GetMe의 GitHub API 실패는 NewGitHubAPIErrorResponse를 통해
도구 오류 결과로 변환될 수 있습니다. Go의 error가 nil이라고 해서 업무가 성공한 것은 아닙니다.
pkg/github/context_tools_test.go는 result.IsError도 따로 검사합니다.
해볼 일: “클라이언트 획득 실패”, “GitHub 응답 실패”, “정상 조회”의 세 경로를 표로 만드세요. 각 경로의 Go 오류와 MCP 결과 오류를 별도 열로 기록하면, 호출 경계마다 오류 표현이 바뀐다는 점이 보입니다.
| 질문 | 소스의 장치 | 기억할 경계 |
|---|---|---|
| 어떤 도구를 공개할까? | WithReadOnly, 도구 선택·제외 필터 | 공개 범위 제한. 토큰의 GitHub 권한 자체를 바꾸지는 않음 |
| 실행할 권한이 있을까? | pkg/scopes, 인증 토큰, GitHub API | 토큰 종류·저장소 권한·조직 정책에 따라 결과가 달라짐 |
| 반환할 내용을 어떻게 다룰까? | lockdown, sanitize, IFC 라벨 | 콘텐츠 필터·정제·분류는 서로 다른 역할 |
ServerTool.IsReadOnly는 Tool.Annotations.ReadOnlyHint를 확인하고,
Inventory의 읽기 전용 모드는 이를 기준으로 쓰기 도구를 걸러냅니다.
따라서 도구를 새로 추가하는 개발자는 이 표시를 정확히 지정해야 합니다.
pkg/http/transport/bearer.go의 AllowedHosts는 목록이 비어 있지 않을 때 토큰을 붙일 호스트와 포트를 제한합니다.
리다이렉트 이후에도 전송 계층이 인증 헤더를 다시 붙일 수 있으므로, 표준 HTTP 클라이언트의 기본 동작만 믿지 않는 설계입니다.
RepoAccessCache.IsSafeContent는 비공개 저장소, push 권한이 있는 작성자, 신뢰하는 봇,
조회자 본인의 콘텐츠 등을 구분합니다. 이는 콘텐츠 작성자의 신뢰도를 판단하는 정책이며,
모든 문장의 의미를 분석해 악성 지시를 알아내는 장치는 아닙니다.
pkg/ifc/ifc.go는 정보의 신뢰도와 공개 범위를 나타내는 라벨 형식을 정의합니다.
실제 강제 엔진은 별도 서비스에 있다고 명시합니다.
pkg/github/ifc_labels.go의 라벨 부착도 기능 플래그가 켜지고 결과가 성공일 때 적용됩니다.
보안 코드를 공부할 때는 장치의 이름보다, 무엇을 검사하고 어디까지 효력이 있는지부터 확인해야 합니다.
ui/src/hooks/useMcpApp.ts의 useMcpApp는 호스트에서 도구 입력과 결과를 받아 상태에 저장합니다.
app.ontoolinput에서 setToolResult(null)을 호출해 이전 실행의 성공 화면이
새 입력 폼 위에 남는 문제를 막습니다. 작은 상태 초기화가 실제 사용 흐름을 바꿉니다.
callTool은 app.callServerTool로, setModelContext는
app.updateModelContext로 이어집니다. 화면에서 보이는 상태와 모델에게 전달되는 문맥을 구분해서 읽어 보세요.
pkg/github/ui_capability.go의 shouldDeferToForm은 UI 지원 여부와 제출 상태 등을 함께 판단합니다.
해볼 일: 입력 A → 결과 A → 입력 B → 결과 B 순서로 상태 표를 그리세요. 입력 B 시점에 결과 A를 남겨두면 어떤 오해가 생기는지 설명하면 React 상태 관리의 목적을 이해한 것입니다.
Test_GetMe는 스키마 스냅샷, 읽기 전용 표시, 테이블 기반 행동 테스트를 함께 확인합니다.
REST는 MockHTTPClientWithHandlers, GraphQL은 internal/githubv4mock로 대체합니다.
script/test에 적힌 실제 명령은 go test -race ./...입니다.
internal/toolsnaps/toolsnaps.go의 Test는 JSON 스키마와 기준 파일을 비교합니다.
CI에서 기준 파일이 없으면 실패하지만, 로컬에서 없으면 파일을 만들 수 있습니다.
테스트도 파일을 쓸 수 있으므로 명령의 이름만 보고 부작용이 없다고 생각하지 않는 습관이 중요합니다.
해볼 일: 필수 입력 하나를 추가했다고 가정하고, 구현·스키마·스냅샷·문서 중 무엇을 함께 검토해야 하는지 적으세요.
.github/workflows/docs-check.yml이 문서를 생성한 뒤 README 차이를 검사하는 이유도 연결해 보세요.
이 서버의 자원 계획은 모델 추론보다 GitHub API 통신·동시 요청·응답 크기를 중심으로 세워야 합니다. 이 저장소의 서버는 GitHub 연결을 담당합니다. 모델을 실행하는 AI 호스트의 메모리와 GPU 요구는 별도로 계산해야 합니다.
| 항목 | 소스·문서에서 확인한 내용 | 초보자를 위한 해석 |
|---|---|---|
| 실행 운영체제 | GoReleaser 대상 Linux·Windows·macOS | 운영체제와 CPU가 맞는 배포물을 선택 |
| 컨테이너 CPU | 배포 워크플로는 amd64·arm64 빌드 | Intel/AMD와 ARM 환경을 구분 |
| Go 소스 빌드 | go.mod와 Dockerfile은 1.27.3 | 현재 소스의 도구 체인 요구를 먼저 확인 |
| UI 소스 빌드 | Node.js ^26.0.0, npm 의존성 | Go 컴파일과 UI 빌드는 별도 단계 |
| 배포 실행 | 최종 이미지는 Go 바이너리를 복사 | 서버 실행용 이미지에 Node·Go 컴파일러를 함께 넣지 않음 |
| 네트워크 | GitHub REST·GraphQL·Raw 요청 | 인터넷 또는 해당 Enterprise 인스턴스 접근 필요 |
| 인증 | stdio: PAT·OAuth·GitHub App 경로, HTTP: 요청 Bearer 토큰 | 실행 방식에 따라 자격 증명 전달 위치가 달라짐 |
| 공식 최소 RAM·CPU | README·빌드 설정에 수치 명시 없음 | 아래 계획은 성능 보장이 아닌 실습용 제안 |
코드 열람은 일반 노트북으로 시작할 수 있습니다. 직접 빌드까지 공부한다면 메모리 8GB 이상, 2~4코어급 개발 환경을 출발점으로 고려할 수 있습니다. 이는 편의를 위한 제안이며, 측정한 최소 사양이 아닙니다. Docker·편집기·AI 호스트를 함께 띄우면 필요한 메모리가 늘어납니다.
실행 서버의 메모리는 동시 요청 수와 결과 크기를 정한 뒤 측정해야 합니다. 의존성 다운로드, UI 번들, Go 빌드 캐시에도 디스크 공간이 들어갑니다. 바이너리 크기·실사용 RSS·처리량은 실제 실행 환경에서 측정해야 구체적인 운영 용량을 정할 수 있습니다.
| 설정 | 값 | 무엇을 제한하는가 |
|---|---|---|
| HTTP 포트 | 8082 | cmd/github-mcp-server/main.go의 기본값. stdio 통신에는 이 수신 포트가 필요하지 않음 |
| HTTP 본문 | 5 << 20 = 5 MiB | pkg/http/middleware/body_limit.go의 요청 본문 크기 기본 제한 |
| 로컬 REST ETag 캐시 | 512개 / 항목 1 MiB / 본문 합계 32 MiB | pkg/http/transport/etag.go의 기본 한도. 전체 프로세스 메모리 한도는 아님 |
| Actions 로그 반환 줄 수 상한 | 5000줄 | content-window-size. pkg/github/actions.go에서 tail_lines와 함께 제한 |
| 저장소 접근 캐시 TTL | CLI 5분 / 패키지 기본 20분 | CLI가 값을 전달하는 경로와 직접 패키지를 쓰는 경로를 구분 |
cmd/github-mcp-server/main.go의 도움말에는 0s to disable이라고 적혀 있습니다.
그러나 pkg/lockdown/lockdown.go의 WithTTL은 0 이하를 만료 없음으로 설명하고,
조회 경로도 계속 캐시를 사용합니다. 이 커밋에서는 캐시 사용 중단과 만료 중단을 구분해서 읽어야 합니다.
또한 HTTP의 --listen-host 기본값은 빈 문자열이며 모든 인터페이스에 바인딩합니다.
개인 실습에서 로컬 연결만 필요하다면 127.0.0.1을 지정하는 설정을 검토하세요.
Dockerfile의 EXPOSE 8082가 기본 모드가 HTTP라는 뜻은 아닙니다. 실제 기본 명령은 stdio입니다.
실습의 완료 기준은 명령을 실행했다는 사실보다, 입력 하나가 어떤 판단을 거쳐 어떤 결과가 되는지 설명할 수 있는가입니다. 1·2번은 토큰이나 네트워크 없이 파일만 읽어도 끝낼 수 있습니다. 3~5번의 구현은 별도 연습용 프로젝트에서 가짜 응답으로 시작하세요.
목표: 도구 이름에서 API 호출과 출력 타입까지 이어지는 경로를 찾습니다.
방법: pkg/github/tools.go의 GetMe 등록,
pkg/github/context_tools.go의 정의, pkg/github/dependencies.go의 클라이언트 획득,
pkg/github/context_tools_test.go의 검증을 순서대로 읽습니다.
제출물: 함수·입력·외부 호출·출력의 네 칸 표와 연결 화살표를 만듭니다.
빈 문자열 인자와 ReadOnlyHint: true의 의미를 각각 한 문장으로 설명하면 완료입니다.
# 저장소 루트에서 내용을 찾기 위한 읽기 전용 명령 예시
rg -n 'GetMe\(|Users.Get|MinimalUser' pkg/github/context_tools.go
rg -n 'GetMe\(' pkg/github/tools.go
rg -n 'Test_GetMe|ReadOnlyHint|IsError' pkg/github/context_tools_test.go
목표: 도구를 명시적으로 선택해도 읽기 전용과 제외 설정을 우회할 수 없음을 이해합니다.
방법: ResolvedEnabledToolsets, Builder.Build,
Inventory.isToolEnabled를 읽습니다. 기능 플래그와 추가 권한 조건은 같다고 가정하고 아래 표를 채웁니다.
제출물: 각 행에 남는 도구와 그 결정을 내리는 조건문을 적습니다.
기본 모음을 뜻하는 nil과 모음을 선택하지 않는 빈 슬라이스도 구분하세요.
| 선택 | 추가 조건 | 예상 결과 |
|---|---|---|
get_me | 읽기 전용 | get_me 유지 |
get_me,issue_write | 읽기 전용 | 쓰기 도구가 빠져 get_me만 유지 |
get_me | 제외 목록에도 get_me | 해당 도구 제외 |
| 존재하지 않는 개별 이름 | WithTools로 지정 | ErrUnknownTools를 포함한 빌드 오류 |
목표: 테스트 대역을 통해 API 호출과 오류 표현을 검증합니다.
방법: Test_GetMe를 참고해 연습용 조회 함수를 만들고,
정상 프로필·클라이언트 생성 오류·API 오류를 가짜 응답으로 돌려줍니다.
실제 토큰이나 실제 GitHub 계정은 사용하지 않아도 됩니다.
제출물: 세 경우의 입력, 가짜 응답, 결과 오류 여부, 반환 필드를 비교한 테스트 표입니다. Go 오류만 검사하면 놓칠 수 있는 실패를 한 가지 설명하세요.
확장: 가짜 API 응답에 필요 없는 필드를 추가해도 모델용 출력이 바뀌지 않는지 확인합니다. API 응답 타입과 공개 출력 타입을 분리하는 이유를 체감할 수 있습니다.
목표: 비동기 알림을 React 상태로 옮길 때의 화면 오류를 이해합니다.
방법: ui/src/hooks/useMcpApp.ts의 ontoolinput과 ontoolresult를 읽고,
새 입력에서 이전 결과를 지우는 경우와 남기는 경우를 네 단계 그림으로 비교합니다.
제출물: 입력 A·결과 A·입력 B·결과 B마다 toolInput과
toolResult 값을 기록한 표입니다. “새 요청 대기 중”과 “이전 요청 성공”이 섞이는 시점을 찾으세요.
확장: shouldDeferToForm의 네 조건을 읽고,
UI를 지원하지 않거나 이미 제출된 요청에서 실행을 폼 입력 단계로 다시 미루면 안 되는 이유를 적습니다.
목표: 응답 캐시가 인증 정보를 고려해야 하는 이유를 코드로 설명합니다.
방법: pkg/http/transport/etag.go의 cacheKey와 RoundTrip,
pkg/http/transport/etag_test.go를 읽습니다. 같은 URL에 서로 다른 가짜 토큰 A·B를 사용하는 흐름을 설계하세요.
제출물: 첫 200 응답 → 같은 사용자의 재요청 → 304 응답 → 저장된 본문 재사용의 시퀀스와, 다른 사용자의 본문을 재사용하지 않는 테스트 조건입니다.
확장: 캐시 키에서 인증 정보를 뺐을 때 생기는 문제를 설명하고,
허용되지 않은 호스트로 이동하면 BearerAuthTransport가 어떤 헤더를 지우는지 추가합니다.
조건문을 읽기 전에 예상 결과를 적고, 그 뒤에 실제 분기를 대조하세요. 예상과 다른 줄이 가장 좋은 학습 포인트입니다. 테스트를 추가할 때도 그 차이를 재현하는 입력부터 고르면 됩니다.
Go 문법이 처음이라면 1주차를 더 길게 잡아도 괜찮습니다. 한 번에 저장소 전체를 읽기보다, 매주 설명 가능한 요청 경로를 하나씩 늘리는 방식이 학습 진도를 확인하기 좋습니다. 시간은 학습 계획용 제안이며, 선수 지식에 따라 달라집니다.
| 주차 | 중심 기술·읽을 소스 | 완료 기준 |
|---|---|---|
| 1주차 | Go 구조체·포인터·인터페이스·오류pkg/github/context_tools.go, pkg/github/dependencies.go | GetMe의 입력부터 출력까지 설명하고 과제 1 완성 |
| 2주차 | MCP·JSON-RPC·JSON Schemapkg/github/server.go, pkg/inventory/server_tool.go | 도구·리소스·프롬프트의 차이와 도구 호출 JSON 작성 |
| 3주차 | 빌더·필터·scopepkg/inventory/builder.go, pkg/inventory/filters.go, pkg/scopes/ | 읽기 전용·명시 선택·제외 설정의 조합을 예측해 과제 2 완성 |
| 4주차 | HTTP·REST·GraphQL·테스트 대역pkg/http/handler.go, pkg/github/context_tools_test.go | 인증 문맥과 API 오류 경로를 설명하고 과제 3 완성 |
| 5주차 | React·TypeScript·MCP Appsui/src/hooks/useMcpApp.ts, pkg/github/ui_capability.go | 새 입력과 이전 결과의 관계를 상태 표로 만들어 과제 4 완성 |
| 6주차 | 캐시·동시성·컨테이너·CIpkg/http/transport/etag.go, Dockerfile, 워크플로 | 과제 5와 함께 빌드부터 실행까지의 의존 관계를 설명 |
fetchIssueIDs에서 시작해 REST와 GraphQL을 섞는 도구 설계를 읽습니다.ToolsetMetadata, 도구 설명, 최소 출력이 모델의 도구 선택에 주는 영향을 실험 설계로 정리합니다.ui/src/apps/issue-write/App.tsx의 입력 폼과 호스트 알림을 연결해 봅니다.건물 전체의 배관도를 외우기보다, 수도꼭지 하나에서 물이 어디로 흐르는지 따라가는 방식입니다. 첫 경로를 이해한 다음 옆 수도꼭지를 보면, 공통 배관과 다른 부분이 눈에 들어옵니다.
| 용어 | 쉬운 설명 |
|---|---|
| MCP | AI 앱이 외부 도구·자료와 통신하는 공통 규약입니다. |
| 호스트 / 클라이언트 | 호스트는 AI가 동작하는 앱, 클라이언트는 그 안에서 MCP 서버와 연결하는 부분입니다. |
| 도구 · tool | 정해진 입력을 받아 작업하는 함수입니다. get_me가 한 예입니다. |
| 리소스 · resource | 주소로 읽는 자료입니다. 저장소 파일이나 UI HTML이 해당합니다. |
| JSON-RPC | 메서드 이름·인자·요청 ID를 JSON에 담아 호출하고 응답받는 방식입니다. |
| JSON Schema | JSON 값이 지켜야 할 필드·타입·필수 조건을 적은 계약입니다. |
| stdio | 표준 입력과 표준 출력으로 통신하는 방식입니다. 로그 출력과 프로토콜 출력을 구분해야 합니다. |
| Streamable HTTP | HTTP를 통해 MCP 요청과 응답을 주고받는 전송 방식입니다. |
| Inventory / 도구 모음 | Inventory는 공개할 기능 목록을 관리하고, 도구 모음은 관련 기능을 묶는 분류입니다. |
| 의존성 주입 · DI | 함수가 필요한 클라이언트나 로거를 밖에서 받도록 만드는 설계입니다. |
| 어댑터 | 다른 두 인터페이스를 연결하는 변환기입니다. 여기서는 GitHub API와 MCP 도구를 잇습니다. |
| 미들웨어 | 핵심 처리 전후에 인증·검사 같은 공통 동작을 붙이는 함수입니다. |
| context.Context | Go에서 요청의 취소 신호와 요청 범위 정보를 전달하는 값입니다. |
| PAT / Bearer 토큰 | PAT는 개인 액세스 토큰이고, Bearer는 토큰을 인증 헤더로 전달하는 방식입니다. |
| OAuth / scope | OAuth는 권한 위임을 위한 흐름, scope는 요청하거나 부여받는 권한 범위입니다. |
| GraphQL 별칭 | 같은 필드를 다른 인자로 여러 번 조회할 때 응답 이름을 나누는 기능입니다. |
| ETag / 304 | 서버가 주는 버전 표식과 “내용이 바뀌지 않음” 응답입니다. 저장한 본문을 다시 활용할 수 있습니다. |
| TTL / LRU | TTL은 캐시 항목의 수명, LRU는 오래 사용하지 않은 항목부터 내보내는 방식입니다. |
| ReadOnlyHint | 도구가 읽기 전용임을 나타내는 표시입니다. 이 서버의 읽기 전용 필터가 이를 사용합니다. |
| IFC | 정보의 신뢰도·공개 범위를 분류해 흐름을 통제하는 접근입니다. 이 저장소는 라벨 형식을 제공합니다. |
| MCP Apps | 도구와 연결된 화면을 지원 호스트 안에서 표시하고 상호작용하도록 하는 확장입니다. |
| go:embed | 빌드할 때 파일을 Go 실행 파일 안에 포함하는 기능입니다. |
| 스냅샷 테스트 | 현재 결과나 구조를 저장한 기준 데이터와 비교해 변경을 발견하는 검사입니다. |
| 테스트 대역 · mock | 실제 외부 시스템 대신 준비한 응답을 돌려주는 구성 요소입니다. |
| Distroless / 다단계 빌드 | 실행에 필요한 구성 중심의 이미지와, 빌드 환경을 여러 단계로 나누는 컨테이너 기법입니다. |
| 항목 | 확인 내용 |
|---|---|
| 선정 | GitHub Trending Go Weekly #10 · 2026-10-23 분석 |
| 스타 / 포크 | 33,883 / 5,219 · 2026-10-23 GitHub 기준 |
| 생성일 | 2025-03-04 |
| 주 언어 | Go |
| 공식 설명 | GitHub 공식 MCP 서버 |
| 최신 릴리스 | v2.0.3 · 2026-10-21 |
| 최근 push | 2026-10-22 · GitHub 기준 |
| 분석한 최근 커밋 | f385262 · 2026-10-22 · UI의 @types/node를 26.6.4에서 26.6.5로 올린 변경 (#3516) |
| Go 코드 규모 | 299개 파일 / 140,161줄 · 구현 57,163줄 + 테스트 82,998줄 |
| UI 코드 규모 | TypeScript·TSX 4,848줄 + MJS 14줄 + CSS 148줄 · 빌드 산출물 제외 |
| 도구 체인 | Go 1.27.3 · UI Node.js ^26.0.0 · React 잠금 버전 19.3.0 |
| 라이선스 | MIT · 루트 LICENSE의 Copyright (c) 2025 GitHub |
활동 수치는 분석일의 GitHub 기준이며, 코드 규모와 구현 설명은 커밋 f385262 기준입니다.
최신 릴리스와 분석한 HEAD는 별개입니다. 이 글에서 본 기능·의존성이 모두 v2.0.3 배포물과 같다고 보장하지 않습니다.
구현 설명만으로 설치 성공이나 실행 성능을 보장할 수는 없습니다. 전체 커밋 수와 기여자 비중에는 전체 이력이 필요하며, 트렌딩 상승 원인을 판단하려면 별도의 시계열 자료가 필요합니다.
README의 Go 모듈 안내에는 v2.0.0 태그의 모듈 경로 문제와 후속 태그가 필요하다는 문장이 남아 있습니다.
분석일의 최신 릴리스 번호와 시간 맥락이 다르므로, 의존성으로 사용할 때는 선택한 태그의 go.mod를 확인해야 합니다.
README의 간단한 Go 빌드 안내만으로는 완성된 React 화면까지 보장되지 않습니다.
소스의 UIAssetsAvailable은 pkg/github/ui_dist/get-me.html을 확인하며, 빌드 전 placeholder만 있으면 UI 등록을 건너뜁니다.
원격 전용 도구와 호스트의 MCP Apps 지원 범위도 별도로 확인해야 합니다.
이슈·PR·저장소를 실제로 바꾸는 기능은 토큰과 조직 정책의 영향을 받습니다. 읽기 전용 설정은 도구 공개를 제한하는 장치이며, GitHub 권한 검사를 대체하지 않습니다. 캐시 TTL 도움말과 구현의 차이, IFC 강제 엔진이 별도라는 점도 함께 기억하세요.