- 홈
- Documentation
- MCP
- MCP 서버 및 도구 작성
MCP 서버 및 도구 작성
이 문서는 MCP 서버 정의가 코딩 에이전트에서 호출 가능한 mcp_* 도구로 변환되는 방식과, 설정이 유효하지 않거나, 중복되거나, 비활성화되거나, 인증이 필요한 경우 운영자가 예상해야 할 사항을 설명합니다.
아키텍처 개요
섹션 제목: “아키텍처 개요”Config sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.) -> discovery providers normalize to canonical MCPServer -> capability loader dedupes by server name (higher provider priority wins) -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false -> MCPManager connects/listTools (with auth/header/env resolution) -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool> -> AgentSession.refreshMCPTools replaces live MCP tools immediately1) 서버 설정 모델 및 유효성 검사
섹션 제목: “1) 서버 설정 모델 및 유효성 검사”src/mcp/types.ts는 MCP 설정 작성자와 런타임에서 사용하는 작성 형식을 정의합니다:
stdio(type이 누락된 경우 기본값):command필수,args,env,cwd선택http:url필수,headers선택sse:url필수,headers선택 (호환성을 위해 유지)- 공유 필드:
enabled,timeout,auth
validateServerConfig() (src/mcp/config.ts)는 전송 계층 기본 사항을 강제합니다:
command와url을 동시에 설정한 설정을 거부합니다- stdio에는
command필수 - http/sse에는
url필수 - 알 수 없는
type을 거부합니다
config-writer.ts는 추가/업데이트 작업에 이 유효성 검사를 적용하며 서버 이름도 검증합니다:
- 비어 있지 않아야 함
- 최대 100자
[a-zA-Z0-9_.-]만 허용
전송 계층 주의사항
섹션 제목: “전송 계층 주의사항”type이 생략되면 stdio를 의미합니다. HTTP/SSE를 의도했지만type을 생략한 경우command가 필수가 됩니다.sse는 여전히 허용되지만 내부적으로는 HTTP 전송(createHttpTransport)으로 처리됩니다.- 유효성 검사는 구조적이며 연결 가능성을 검사하지 않습니다: 구문적으로 유효한 URL도 연결 시점에 실패할 수 있습니다.
2) 검색, 정규화 및 우선순위
섹션 제목: “2) 검색, 정규화 및 우선순위”기능 기반 검색
섹션 제목: “기능 기반 검색”loadAllMCPConfigs() (src/mcp/config.ts)는 loadCapability(mcpCapability.id)를 통해 정규 MCPServer 항목을 로드합니다.
기능 계층(src/capability/index.ts)은 다음을 수행합니다:
- 우선순위 순서로 프로바이더를 로드합니다
server.name기준으로 중복을 제거합니다 (먼저 발견된 것이 승리 = 가장 높은 우선순위)- 중복 제거된 항목을 검증합니다
결과: 여러 소스에 걸친 중복 서버 이름은 병합되지 않습니다. 하나의 정의만 적용되며, 낮은 우선순위의 중복은 가려집니다.
.mcp.json 및 관련 파일
섹션 제목: “.mcp.json 및 관련 파일”src/discovery/mcp-json.ts의 전용 폴백 프로바이더는 프로젝트 루트의 mcp.json 및 .mcp.json을 읽습니다 (낮은 우선순위).
실제로 MCP 서버는 더 높은 우선순위의 프로바이더에서도 제공됩니다 (예: 네이티브 .xcsh/... 및 도구별 설정 디렉터리). 작성 지침:
- 명시적 제어를 위해
.xcsh/mcp.json(프로젝트) 또는~/.xcsh/mcp.json(사용자)를 선호하세요. - 폴백 호환성이 필요할 때 루트
mcp.json/.mcp.json을 사용하세요. - 여러 소스에서 동일한 서버 이름을 재사용하면 병합이 아닌 우선순위에 의한 가려짐이 발생합니다.
정규화 동작
섹션 제목: “정규화 동작”convertToLegacyConfig() (src/mcp/config.ts)는 정규 MCPServer를 런타임 MCPServerConfig로 매핑합니다.
주요 동작:
- 전송 계층은
server.transport ?? (command ? "stdio" : url ? "http" : "stdio")로 추론됩니다 - 비활성화된 서버(
enabled === false)는 연결 전에 제거됩니다 - 선택적 필드는 존재하는 경우 보존됩니다
검색 중 환경 변수 확장
섹션 제목: “검색 중 환경 변수 확장”mcp-json.ts는 expandEnvVarsDeep()를 사용하여 문자열 필드의 환경 변수 플레이스홀더를 확장합니다:
${VAR}및${VAR:-default}를 지원합니다- 해석되지 않은 값은 리터럴
${VAR}문자열로 유지됩니다
mcp-json.ts는 또한 사용자 JSON에 대해 런타임 타입 검사를 수행하며, 전체 파일을 실패 처리하는 대신 유효하지 않은 enabled/timeout 값에 대해 경고를 로그합니다.
3) 인증 및 런타임 값 해석
섹션 제목: “3) 인증 및 런타임 값 해석”MCPManager.prepareConfig()/#resolveAuthConfig() (src/mcp/manager.ts)는 연결 전 최종 처리 단계입니다.
OAuth 자격 증명 주입
섹션 제목: “OAuth 자격 증명 주입”설정에 다음이 있는 경우:
auth: { type: "oauth", credentialId: "..." }인증 저장소에 자격 증명이 존재하면:
http/sse:Authorization: Bearer <access_token>헤더를 주입합니다stdio:OAUTH_ACCESS_TOKEN환경 변수를 주입합니다
자격 증명 조회가 실패하면 매니저는 경고를 로그하고 해석되지 않은 인증 상태로 계속 진행합니다.
헤더/환경 변수 값 해석
섹션 제목: “헤더/환경 변수 값 해석”연결 전에 매니저는 resolveConfigValue() (src/config/resolve-config-value.ts)를 통해 각 헤더/환경 변수 값을 해석합니다:
!로 시작하는 값 => 셸 명령을 실행하고, 트리밍된 stdout을 사용합니다 (캐시됨)- 그 외에는 값을 환경 변수 이름으로 먼저 처리하고 (
process.env[name]), 리터럴 값으로 폴백합니다 - 해석되지 않은 명령/환경 변수 값은 최종 헤더/환경 변수 맵에서 제외됩니다
운영상 주의사항: 잘못 입력된 시크릿 명령/환경 변수 키가 해당 헤더/환경 변수 항목을 조용히 제거하여 다운스트림에서 401/403 또는 서버 시작 실패를 유발할 수 있습니다.
4) 도구 브릿지: MCP -> 에이전트 호출 가능 도구
섹션 제목: “4) 도구 브릿지: MCP -> 에이전트 호출 가능 도구”src/mcp/tool-bridge.ts는 MCP 도구 정의를 CustomTool로 변환합니다.
이름 지정 및 충돌 도메인
섹션 제목: “이름 지정 및 충돌 도메인”도구 이름은 다음과 같이 생성됩니다:
mcp_<sanitized_server_name>_<sanitized_tool_name>규칙:
- 소문자로 변환
[a-z_]가 아닌 문자는_로 변환- 반복되는 밑줄은 하나로 축약
- 도구 이름에서 중복되는
<server>_접두사는 한 번 제거
이것은 많은 충돌을 방지하지만 전부는 아닙니다. 서로 다른 원본 이름이 동일한 식별자로 정제될 수 있으며 (예: my-server와 my.server는 유사하게 정제됨), 레지스트리 삽입은 마지막 쓰기가 우선합니다.
스키마 매핑
섹션 제목: “스키마 매핑”convertSchema()는 MCP JSON Schema를 대부분 그대로 유지하지만, properties가 누락된 객체 스키마에 프로바이더 호환성을 위해 {}를 패치합니다.
실행 매핑
섹션 제목: “실행 매핑”MCPTool.execute() / DeferredMCPTool.execute():
- MCP
tools/call을 호출합니다 - MCP 콘텐츠를 표시 가능한 텍스트로 평탄화합니다
- 구조화된 상세 정보를 반환합니다 (
serverName,mcpToolName, 프로바이더 메타데이터) - 서버가 보고한
isError를Error: ...텍스트 결과로 매핑합니다 - 발생한 전송/런타임 실패를
MCP error: ...로 매핑합니다 - AbortError를
ToolAbortError로 변환하여 중단 시맨틱을 보존합니다
5) 운영자 라이프사이클: 추가/편집/제거 및 실시간 업데이트
섹션 제목: “5) 운영자 라이프사이클: 추가/편집/제거 및 실시간 업데이트”인터랙티브 모드는 src/modes/controllers/mcp-command-controller.ts에서 /mcp를 노출합니다.
지원되는 작업:
add(마법사 또는 빠른 추가)remove/rmenable/disabletestreauth/unauthreload
설정 쓰기는 원자적입니다 (writeMCPConfigFile: 임시 파일 + 이름 변경).
변경 후 컨트롤러는 #reloadMCP()를 호출합니다:
mcpManager.disconnectAll()mcpManager.discoverAndConnect()session.refreshMCPTools(mcpManager.getTools())
refreshMCPTools()는 모든 mcp_ 레지스트리 항목을 교체하고 최신 MCP 도구 세트를 즉시 다시 활성화하므로, 세션을 재시작하지 않고도 변경 사항이 적용됩니다.
모드 차이점
섹션 제목: “모드 차이점”- 인터랙티브/TUI 모드:
/mcp는 앱 내 UX를 제공합니다 (마법사, OAuth 흐름, 연결 상태 텍스트, 즉시 런타임 재바인딩). - SDK/헤드리스 통합:
discoverAndLoadMCPTools()(src/mcp/loader.ts)는 로드된 도구 + 서버별 오류를 반환하며,/mcp명령 UX는 없습니다.
6) 사용자에게 표시되는 오류 화면
섹션 제목: “6) 사용자에게 표시되는 오류 화면”사용자/운영자가 보게 되는 일반적인 오류 문자열:
- 추가/업데이트 유효성 검사 실패:
Invalid server config: ...Server "<name>" already exists in <path>
- 빠른 추가 인수 문제:
Use either --url or -- <command...>, not both.--token requires --url (HTTP/SSE transport).
- 연결/테스트 실패:
Failed to connect to "<name>": <message>- 타임아웃 도움말 텍스트는 타임아웃 증가를 제안합니다
401/403에 대한 인증 도움말 텍스트
- 인증/OAuth 흐름:
Authentication required ... OAuth endpoints could not be discoveredOAuth flow timed out. Please try again.OAuth authentication failed: ...
- 비활성화된 서버 사용:
Server "<name>" is disabled. Run /mcp enable <name> first.
검색 중 잘못된 소스 JSON은 일반적으로 경고/로그로 처리됩니다; config-writer 경로는 명시적 오류를 던집니다.
7) 실용적 작성 지침
섹션 제목: “7) 실용적 작성 지침”이 코드베이스에서 견고한 MCP 작성을 위해:
- 모든 MCP 지원 설정 소스에서 서버 이름을 전역적으로 고유하게 유지하세요.
- 생성된
mcp_*도구 이름에서 정제된 이름 충돌을 방지하기 위해 영숫자/밑줄 이름을 선호하세요. - 우발적인 stdio 기본값을 방지하기 위해 명시적
type을 사용하세요. enabled: false는 완전 비활성화로 처리하세요: 서버가 런타임 연결 세트에서 제외됩니다.- OAuth 설정의 경우 유효한
credentialId를 저장하세요; 그렇지 않으면 인증 주입이 건너뛰어집니다. - 명령 기반 시크릿 해석(
!cmd)을 사용하는 경우 명령 출력이 안정적이고 비어 있지 않은지 확인하세요.