MCP 서버 개발 가이드라인
GitLab v19.2이 페이지에는 GitLab MCP 서버를 개발하고 사용하는 방법에 대한 정보가 포함되어 있습니다. 개발 환경을 설정하려면 다음을 수행합니다: GDK에서 HTTPS를 활성화하고 구성합니다. node를 설치하고 mcp-remote를 전역으로 설치합니다.
이 페이지에는 GitLab MCP 서버를 개발하고 사용하는 방법에 대한 정보가 포함되어 있습니다.
개발 환경 설정#
개발 환경을 설정하려면 다음을 수행합니다:
-
node를 설치하고mcp-remote를 전역으로 설치합니다. GDK에는 Node.js가 포함되어 있지만, 설치된 AI 어시스턴트는 GDK 버전을 사용할 수 없습니다.
디버깅 및 문제 해결#
Cursor 디버깅#
더 자세한 로깅을 위해 mcp-remote 명령에 --debug를 추가합니다. Output을 열고 MCP:SERVERNAME을 선택하여 MCP 서버 로그를 확인합니다. 아래 예시의 경우 MCP:user-GitLab-GDK가 됩니다.
{
"mcpServers": {
"GitLab-GDK": {
"command": "npx",
"args": [
"mcp-remote",
"https://gdk.test:3443/api/v4/mcp",
"--debug"
],
"env": {
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}
}
}
Claude Desktop 디버깅#
Node.js 버전 확인#
Claude Desktop은 지원되지 않는 버전의 Node.js를 사용합니다. 특정 버전을 사용하는 사용자 지정 래퍼 스크립트를 생성합니다:
#!/bin/bash
# Force use of your Node.js version
NODE_BIN="/PATH_TO_NODE_INSTALL/node/22.17.0/bin/node"
MCP_REMOTE_BIN="/PATH_TO_NODE_INSTALL/node/22.17.0/bin/mcp-remote"
# Run mcp-remote with your Node.js
exec "$NODE_BIN" "$MCP_REMOTE_BIN" "$@"
Claude Desktop 구성에서 래퍼 스크립트를 사용합니다.
{
"mcpServers": {
"GitLab-GDK": {
"command": "/PATH_TO_REMOTE_WRAPPER_SCRIPT/mcp-remote-wrapper",
"args": [
"https://gdk.test:3443/api/v4/mcp",
"--debug"
],
"env": {
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}
}
}
mcp-remote 디버깅#
AI 어시스턴트 외부에서 mcp-remote에서 GDK로의 인증을 테스트합니다:
NODE_TLS_REJECT_UNAUTHORIZED=0 npx mcp-remote https://gdk.test:3443/api/v4/mcp --debug
브랜치를 전환하면 인증 문제가 발생할 수 있으며, 여기에는 로그에 나타나는 UNABLE_TO_VERIFY_LEAF_SIGNATURE 오류가 포함될 수 있습니다. 체인 내 신뢰할 수 없는 인증서 오류는 TLS를 사용하는 GDK 인스턴스에 특유한 것입니다. 이 오류는 Node.js에서 사용되는 https 클라이언트와 npx를 통한 mcp-remote 라이브러리로 인해 발생합니다. https 클라이언트는 번들로 제공되는 인증 기관 목록 외부에서 서명된 인증서를 신뢰하지 않습니다.
인증 문제가 발생하는 경우, 최후의 수단으로 ~/.mcp-auth 디렉터리를 삭제하면 mcp-remote에 저장된 자격 증명이 재설정됩니다. AI 어시스턴트가 MCP 서버에 다시 연결되면 인증을 요청하는 브라우저 창이 열립니다.
rm -rf ~/.mcp-auth
MCP Inspector로 디버깅#
MCP Inspector는 MCP 서버를 테스트하고 디버깅하기 위한 대화형 개발자 도구입니다.
다음 명령은 서버에 연결하고 MCP 도구를 나열하고 실행할 수 있는 직관적인 웹 UI를 엽니다:
npx -y @modelcontextprotocol/inspector npx
개발 워크플로#
유용한 링크#
새 도구 추가#
도구 제안 프로세스#
현재의 개발 가이드라인은 아직 초기 개발 단계에 있습니다. 특히 사용자 지정 도구와 집계 도구에 대한 도구 개발 표준을 계속 확립해 나가면서, 제안된 도구를 구현 전에 평가하고 새로운 MCP 도구를 계획하는 팀을 안내하기 위해 임시 mcp-tool-review-board 위원회를 만들었습니다.
새 도구를 추가하려면 MCP 도구 제안 이슈를 생성하고 템플릿 지침을 따르십시오.
도구 구현 위치는 GitLab 리소스와의 상호 작용 방식에 따라 달라집니다:
-
GitLab 리소스와 상호 작용하는 도구는 궁극적으로 MCP 서버에 있어야 하지만, 단기적이거나 긴급한 필요에 따라 Agent Platform에서 구현될 수 있습니다.
-
GitLab 리소스와 상호 작용하지 않는 도구는 Agent Platform에서 구현되어야 합니다.
MCP 서버 기능을 Agent Platform에 통합하기 위해 작업하고 있습니다. 이 이슈를 통해 진행 상황을 추적할 수 있습니다.
모든 엔지니어가 도구 제안 프로세스를 따르고 사용 사례에 대한 명확한 설명을 제공할 것을 강력히 권장합니다.
REST API 라우트에서 도구 구현#
이 병합 요청은 API 라우트에서 MCP 도구를 생성하는 프로세스를 정의합니다.
API 라우트 정의에 다음 route_setting을 추가합니다:
route_setting :mcp, tool_name: :get_merge_request, params: [:id, :merge_request_iid], resource_name: "merge request"
-
get_merge_request도구를 도구 목록에 추가하고 그 실행을 활성화합니다 -
도구 및 매개변수 설명은 OpenAPI 라우트 정의에서 가져옵니다
-
허용되는 매개변수는
params인수로 필터링됩니다. 예를 들어,id와merge_request_iid만 노출되고 허용됩니다 -
도구가 호출되면 전달된 매개변수로 라우트 코드가 직접 실행됩니다
-
선택적
resource_name필드는 리소스별 404 오류 메시지를 제공합니다(예: 일반적인"404 Not Found"대신"404 Merge request Not Found")."issue"또는"merge request"와 같은 소문자 문자열을 사용합니다. 렌더링된 메시지에서 첫 글자는 대문자로 표시됩니다.
이 병합 요청은 더 많은 예시를 제공합니다.
집계 REST API 도구 구현#
집계 API 도구는 여러 관련 API 도구를 하나의 통합 인터페이스로 결합하여 도구 수를 줄이고 사용자 경험을 개선합니다. 검색 도구는 전역, 그룹, 프로젝트 검색을 하나의 도구로 통합하여 이 패턴을 보여줍니다.
집계 도구를 사용해야 하는 경우:
유사한 목적을 수행하지만 서로 다른 범위(전역, 그룹, 프로젝트)에서 작동하는 여러 API 엔드포인트가 있는 경우 집계 도구를 사용합니다. 이렇게 하면 세 개 대신 하나의 도구를 제시하여 LLM의 인지 부하를 줄입니다.
구현 단계:
Mcp::Tools::AggregatedService를 상속하는 집계 서비스 클래스를 생성합니다:
module Mcp
module Tools
class ExampleAggregatedService < AggregatedService
include Gitlab::Utils::StrongMemoize
extend ::Gitlab::Utils::Override
register_version '0.1.0', {
description: 'My example aggregated tool',
input_schema: {
type: 'object',
properties: {},
required: []
}
}
override :tool_name
def self.tool_name
'new_tool'
end
override :select_tool
def select_tool(args)
tool_name = if args[:group_id]
:example_tool_for_group
elsif args[:project_id]
:example_tool_for_project
end
tools.find { |tool| tool.name.to_sym == tool_name }
end
override :transform_arguments
def transform_arguments(args)
if args[:group_id]
args.merge(id: args[:group_id])
elsif args[:project_id]
args.merge(id: args[:project_id])
else
args
end
end
end
end
end
- 라우트 정의에서 기본 API 도구를 집계기에 등록합니다:
route_setting :mcp, tool_name: :example_tool_for_group, params: [:id], aggregators: [::Mcp::Tools::ExampleAggregatedService]
route_setting :mcp, tool_name: :example_tool_for_project, params: [:id], aggregators: [::Mcp::Tools::ExampleAggregatedService]
Mcp::Tools::Manager는aggregators가 지정된 라우트를 스캔하여 집계 도구를 자동으로 검색하고, 수집된 도구로 집계기 클래스를 인스턴스화합니다.
GraphQL 기반 도구 구현#
GitLab GraphQL API를 사용하는 MCP 도구의 경우 GraphQL 통합 가이드라인을 참조하십시오.
사용자 지정 도구 구현#
API 노출과 분리되어야 하는 고유한 기능을 가진 도구의 경우, 독립형 클래스를 정의할 수 있습니다(참고로 이 예시를 참조하십시오).
AI 에이전트 아키텍처에서 도구 확산의 위험#
너무 많은 도구를 추가할 때의 주요 문제#
-
성능 저하
-
컨텍스트 팽창: 모든 도구 정의(이름, 설명, JSON 스키마)가 모델의 프롬프트 컨텍스트에 추가되어 입력 토큰 수가 증가합니다
-
지연 시간 증가: 프롬프트가 커질수록 응답 시간이 느려집니다
-
비용 증가: 토큰이 많을수록 요청당 API 비용이 높아집니다
-
정확도 저하: 연구에 따르면 컨텍스트가 많아지고 도구가 많아질수록 에이전트 성능이 저하됩니다
-
궤적 저하: 더 긴 추론 경로가 필요한 에이전트는 도구 확산에 따라 더 빠르게 저하됩니다
-
도구 선택 혼란
-
의사 결정 복잡성: 도구가 많을수록 모델이 올바른 도구를 선택하기가 더 어려워집니다
-
매개변수 설계 문제: LLM이 학습한 공개용 형식(프로젝트 경로, IID) 대신 내부 ID(예: GitLab 전역 ID)를 사용하면 도구 사용 효율성이 떨어집니다
-
유사한 도구 간의 모호성: 모델은 유사한 목적을 가진 도구(예: 로컬 파일 작업 대 원격 파일 작업)를 구별하는 데 어려움을 겪습니다
-
사용자 경험에 미치는 영향
-
도구 체이닝 노이즈: 도구가 많을수록 단일 요청에서 더 많은 도구 호출이 발생하여 노이즈와 산만함을 만듭니다
-
응답 속도 저하: 처리 시간이 증가하면 사용자 만족도에 영향을 미칩니다
-
잘못된 도구 선택: 잘못된 도구 선택은 부정확하거나 관련 없는 응답으로 이어집니다
도구를 추가하기 전에 개발자가 답해야 할 질문#
-
필요성: 이것이 정말로 새로운 기능인가, 아니면 매개변수 조정을 통해 기존 도구로 처리할 수 있는가?
-
통합 가능성: 이 기능을 열거형(enum)이나 매개변수를 사용하여 기존 도구와 병합할 수 있는가?
-
매개변수 설계: 모델의 학습 데이터와 일치하는 공개적으로 인식 가능한 식별자를 사용하고 있는가?
-
평가 전략: 이 도구가 전체 에이전트 성능을 개선하는지 아니면 저하시키는지 어떻게 측정하는가?
-
컨텍스트 관리: 이 도구의 정의가 실제 추론에 사용 가능한 컨텍스트 윈도우에 어떤 영향을 미치는가?
-
도구 라우팅: 이 도구가 메인 에이전트의 도구 세트가 아니라 전문화된 하위 에이전트의 일부가 되어야 하는가?
-
권한 모델: 이 도구가 권한/가격 모델과 어떻게 상호 작용하는가? 복잡성을 만들지는 않는가?
-
의미적 구별성: 이 도구가 도구 세트의 다른 도구와 명확하게 구별되는가?
더 많은 도구가 항상 더 나은 것은 아닙니다. 연구에 따르면 컨텍스트 크기와 도구 수 모두 수확 체감이 있으며 결국 성능 저하로 이어집니다. 도구 세트를 계속 확장하는 대신 도구 통합, 전문화된 하위 에이전트, 또는 동적 도구 라우팅을 고려하십시오.
기존 도구 수정#
MCP 도구는 소비자에게 호환성이 깨지는 변경을 방지하기 위해 시맨틱 버전 관리를 사용합니다. 도구를 수정할 때는 이 병합 요청에서 도입된 버전 관리 시스템을 사용합니다.
버전 관리가 중요한 이유:
LLM과 AI 에이전트는 도구 스키마를 캐시하고 특정 도구 동작을 중심으로 워크플로를 구축합니다. 도구 매개변수, 설명 또는 출력 형식의 변경은 기존 통합을 깨뜨릴 수 있습니다. 버전 관리를 통해 하위 호환성을 유지하면서 안전하게 발전시킬 수 있습니다.
버전 등록 패턴:
집계 API, 사용자 지정, GraphQL 도구의 경우 register_version을 사용하여 버전을 등록합니다:
module Mcp
module Tools
class GetServerVersionService < CustomService
register_version '0.1.0', {
description: 'Get the current version of MCP server.',
input_schema: {
type: 'object',
properties: {},
required: []
}
}
def perform_0_1_0(_arguments = {})
data = { version: Gitlab::VERSION, revision: Gitlab.revision }
formatted_content = [{ type: 'text', text: data[:version] }]
::Mcp::Tools::Response.success(formatted_content, data)
end
override :perform_default
def perform_default(arguments = {})
perform_0_1_0(arguments)
end
end
end
end
새 버전 추가:
도구의 동작을 수정해야 하는 경우:
- 업데이트된 메타데이터로 새 버전을 등록합니다:
register_version '0.2.0', {
description: 'Get version with additional metadata.',
input_schema: {
type: 'object',
properties: {
include_metadata: {
type: 'boolean',
description: 'Include additional metadata'
}
},
required: []
}
}
- 버전별 메서드를 구현합니다:
def perform_0_2_0(arguments = {})
data = {
version: Gitlab::VERSION,
revision: Gitlab.revision
}
if arguments[:include_metadata]
data[:metadata] = { build_date: Time.current }
end
formatted_content = [{ type: 'text', text: data[:version] }]
::Mcp::Tools::Response.success(formatted_content, data)
end
- 최신 버전을 사용하도록
perform_default를 업데이트합니다:
override :perform_default
def perform_default(arguments = {})
perform_0_2_0(arguments)
end
API 도구의 경우:
API 도구는 자동으로 버전 0.1.0으로 기본 설정됩니다. 필요한 경우 라우트 설정에서 버전을 지정할 수 있습니다:
route_setting :mcp, tool_name: :get_merge_request,
params: [:id, :merge_request_iid],
version: '1.0.0'
라우트에서 가져온 API 도구는 도구당 단일 버전을 사용합니다. 여러 버전이 필요한 도구의 경우 대신 사용자 지정 도구로 구현하는 것을 고려하십시오.
버전 지원 정책:
프레임워크는 버전이 지정되지 않은 경우 자동으로 최신 버전을 사용합니다. 소비자는 도구 호출 중에 특정 버전을 요청할 수 있습니다. 버전을 사용 중단할 때는 다중 버전 호환성 가이드라인을 따르십시오.
도구 이름 변경#
도구 이름을 변경하려면 하위 호환성을 유지하기 위해 도구 별칭을 사용해야 합니다. 연결된 클라이언트는 도구 이름을 캐시하며 도구 이름이 변경되어도 자동으로 새로 고치지 않습니다. 이 병합 요청에서 도입된 별칭 시스템을 사용하면 기존 통합을 깨뜨리지 않고 원활하게 이름을 변경할 수 있습니다.
별칭이 필요한 이유:
MCP 클라이언트는 tools/list에서 도구 목록을 캐시하며 도구가 변경되어도 자동으로 다시 가져오지 않습니다. 도구 이름을 변경하면 클라이언트가 존재하지 않는 도구 이름을 호출하게 되어 오류가 발생하거나 무한정 멈추게 됩니다. MCP 사양은 클라이언트에 변경 사항을 알리기 위한 notifications/tools/list_changed를 지원하지만, GitLab MCP 서버는 이를 구현하지 않습니다(이 이슈에서 추적됨).
구현 단계:
- 도구 클래스에서
tool_aliases를 재정의하여 이전 이름을 포함합니다:
module Mcp
module Tools
class RenamedService < AggregatedService
override :tool_name
def self.tool_name
'new_name'
end
override :tool_aliases
def self.tool_aliases
['old_name']
end
end
end
end
-
모든 참조를 새 도구 이름을 사용하도록 업데이트합니다:
tool_name:이 포함된 라우트 설정 -
테스트 파일
-
문서
-
하드코딩된 모든 도구 이름 참조
-
Mcp::Tools::Manager는get_tool호출 중에 별칭을 자동으로 확인하므로, 이전 이름을 사용하는 클라이언트는 계속 작동합니다.
중요 참고 사항:
-
list_tools는 별칭이 아닌 정식 도구 이름만 반환합니다 -
별칭은 사용자 지정, GraphQL, API, 집계 도구 등 모든 도구 유형에 대해 작동합니다
-
별칭 확인은 모든 도구 레지스트리를 확인하는
Manager#resolve_alias에서 발생합니다 -
클라이언트가 업데이트할 충분한 시간이 지난 후 향후 릴리스에서 별칭을 제거할 계획을 세웁니다
사용 중단 타임라인:
릴리스 M: 별칭 추가 및 도구 이름 변경 릴리스 M+1: 별칭 제거(클라이언트가 도구 목록을 새로 고칠 시간이 지난 후)
이 접근 방식은 도구 이름 변경 중에 연결된 클라이언트의 다운타임을 제로로 보장합니다.