GitOps 저장소도 ArgoCD Application도 아직 없어, 그때까지 이 저장소가 push 방식 파이프라인(.gitea/workflows/)을 임시로 소유한다. 무엇을 포기하는지와 넘길 때 할 일은 deploy/README.md에 적었다. Chart는 portal과 bundles 두 배포 모델을 모두 렌더링한다. ADR-0013이 ADR-0007을 대체했으므로 운영은 portal이 기준이지만, bundles 경로를 언제 삭제할지는 아직 정하지 않았다. - .gitea/workflows/ci.yaml, deploy-openshift.yaml - deploy/ci/render-manifests.sh, deploy/examples/ - Chart: mode 분기, selectedDeployment/tier helper, imagePullSecrets, toolService.apiKeySecret 참조 - extension-points.md에 미결 항목 9~12 추가 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
AX HUB MCP Server
Agent Builder와 Tool Service 사이의 stateless MCP 실행 계층이다. Agent Builder가 tools/call에 명시한 단일 Tool을 JSON-RPC 2.0과 inputSchema로 검증하고, 서버가 관리하는 metadata에 따라 Tool
Service를 호출한다.
이 서버는 Tool을 추천하거나 사용자 의도를 판단하지 않는다. 업무 규칙은 Tool Service가, Tool 선택과 사용자·Agent별 노출 정책은 Agent Builder가 소유한다.
기술 기준
- Java 21, Spring Boot 3.5.11, Spring MVC
- MCP Java SDK 2.0.0의
mcp-json-jackson2: protocol 상수·표준 result 모델·JSON Schema 검증에만 사용 - Spring Data Redis: 선택적 공유 cache
- Spring AI MCP Starter/transport: 사용하지 않음
SDK 적용 경계는 MCP Java SDK 선택적 도입 설계를 따른다.
빠른 시작
필수 조건은 JDK 21이다. 기본 profile은 local이며 Redis 없이 Tool Service 매니페스트를 먼저 조회하고, 최초 조회 실패 시 config/local-core-tools-manifest-sample-v1.json을 fallback으로 사용한다.
.\gradlew.bat check
.\gradlew.bat bootRun
빌드는 외부 저장소에서 코드 스타일 도구를 내려받지 않는다. 폐쇄망에서 검사 하나 때문에 빌드 전체가 시작되지 못하는 상황을 만들지 않기 위해서다. 서식 검사는 저장소 안의 테스트가 소유한다.
Java 포맷은 .idea/codeStyles/Project.xml의 IntelliJ IDEA 코드 스타일로 고정한다. 이 파일은 저장소에 포함되어 있어 IDE에서 자동으로 적용된다. Java 소스의 줄바꿈은 운영체제와 무관하게 LF이며 .gitattributes가 commit
시점에 이를 강제한다.
두 가지가 보장하는 범위가 다르다.
| 무엇이 | 보장하는 것 | 조건 |
|---|---|---|
CodeStyleContractTest |
LF 줄바꿈, 탭 없음, 후행 공백 없음, 파일 끝 개행, 미사용 import 없음 | 항상 (test에 포함) |
| IntelliJ formatter | 4칸 들여쓰기, 줄바꿈 스타일, 단순 lambda·다중 표현식 분리 | IDEA_FORMATTER 설정 시에만 |
IDEA_FORMATTER가 없으면 IntelliJ formatter 단계는 경고를 남기고 건너뛴다. IntelliJ가 없는 CI나 폐쇄망 빌드에서 빌드가 깨지지 않게 하기 위한 것이며, 그 환경에서는 들여쓰기와 줄바꿈이 검증되지 않는다는 뜻이다. 도구 없이 판정할 수
있는 규칙은 그때도 계속 검사된다.
포맷터로 코드를 실제로 정리하려면 경로를 지정하고 ideaFormat을 실행한다. CodeStyleContractTest는 검사만 하고 고쳐 주지 않는다.
$env:IDEA_FORMATTER='C:/Program Files/JetBrains/IntelliJ IDEA 2026.1.4/bin/format.bat'
.\gradlew.bat ideaFormat
줄 폭 160자는 코드 스타일의 권장값이며 기존 코드에 소급 적용되지 않는다. 강제 대상이 아니다.
local Tool 파일을 바꾸려면 다음 환경변수에 Spring resource 경로를 지정한다.
$env:MCP_LOCAL_TOOL_REGISTRY_FILE='file:C:/path/local-tools.json'
공개 계약
- 공개 endpoint:
POST https://{global.mcpHost}/mcp/{routeKey} - 예:
https://mcp-dev.apps.example.internal/mcp/cus - route key는 URI에서만 결정된다. route 없는
/mcp호출은 거부한다 - OpenShift Route는 공개 path로 MCP Service만 선택하고, 컨테이너가 같은 path를 직접 처리
- Method:
initialize,notifications/initialized,tools/list,tools/call - Response: 항상 단일
application/jsonJSON-RPC response Accept: application/json, text/event-stream: 호환 목적으로 수용하지만 SSE 경로는 제공하지 않음- 공개 endpoint의
GET:405 Method Not Allowed MCP-Protocol-Version:initialize이후 필수, 현재2025-11-25Mcp-Session-Id: initialize lifecycle 추적용 correlation 값이며 서버 세션이 아님
정확한 요청·응답과 오류 의미는 Agent Builder-MCP 현재 계약 v0.3이 정본이다. Agent Builder는 공개 URL마다 별도 MCP로 등록하고 initialize한다. URL 사이에 lifecycle correlation이나 Tool 목록을 공유하지 않는다.
Tool metadata와 실행
| 환경 | Tool 원천 | Redis |
|---|---|---|
local |
local JSON fixture | 사용 안 함 |
운영(ocp) |
Portal registry가 알려 준 route별 Tool Service 매니페스트를 주기적으로 pull | 성공 snapshot 공유와 Portal registry fallback에 사용 |
요청 경로의 tools/list와 tools/call은 in-memory snapshot만 읽는다. 운영 refresh는 bundle별 last-good을 유지하고, 그 route의 모든 bundle에 사용 가능한 성공본이 있을 때만 route의 aggregate를 교체한다. 조회 실패만으로 Tool을 제거하지 않으며 정상 매니페스트에서 삭제가 확인될 때만 반영한다. 한 route에는 Tool Service가 여럿 붙을 수 있고, 병합 단위는 route다(ADR-0013).
Tool 실행 주소는 local _meta.endpoint 또는 운영 baseEndpoint 설정에서만 정한다. Agent Builder의 arguments와 Tool Service 매니페스트는 호출 대상을 바꿀 수 없다.
운영 매니페스트와 장애 처리의 wire 계약은 Tool Service-MCP bundle 조회 계약 v0.2를 따른다.
Correlation과 로그
호출자가 보내는 헤더는 다섯 개이며 모두 선택값이다. 값은 그대로 Tool Service 요청 헤더로 bypass한다.
| 헤더 | 의미 | 없을 때 |
|---|---|---|
guid |
요청 하나를 끝까지 따라가는 상관 값(UUID) | 서버가 생성 |
x-request-id |
개별 HTTP 요청 ID | 서버가 생성 |
mcp-session-id |
initialize lifecycle 상관 값 | 전달하지 않음 |
employee-no |
사원번호 | 전달하지 않음 |
virtual-employee-no |
가상사원번호(상담사 등 비사원) | 전달하지 않음 |
employee-no와 virtual-employee-no는 불투명 값이다. 형식이나 의미를 해석하지 않고, 개행이 섞여 downstream 헤더가 조작되는 것만 막은 뒤 그대로 전달한다.
MDC는 사용하지 않는다. 로그에는 guid와 x-request-id만 남기며 사원 식별자는 기록하지 않는다. request/response body와 credential도 남기지 않는다.
Authorization은 mcp.tool-client.forward-authorization 설정이 켜진 경우에만 전달한다. MCP는 이 값을 해석하지 않는다.
인증과 권한
이 서버는 인증도 인가도 하지 않는다. 요청자 신원을 검증하지 않고, Tool 실행 권한을 판단하지 않으며, 사원 식별자를 복호화하지 않는다. 결정과 근거는 ADR-0006이다.
| 책임 | 주체 |
|---|---|
| 외부 호출자를 Agent Builder로 제한 | OpenShift Route IP allowlist |
| MCP Pod 직접 접근 제한 | 플랫폼 NetworkPolicy |
| 사용자 인증과 Tool 실행 권한 | Agent Builder |
| 업무 권한 | Tool Service |
⚠️ Route IP allowlist와 NetworkPolicy는 선택 사항이 아니다. 외부 요청은 Route가 Agent Builder의 고정 egress CIDR만 받고, backend 요청은 ingress controller 또는 허용된 Agent Builder namespace에서만 MCP Pod에 도달한다. 실제 CIDR을 넣지 않은 배포는 운영에 사용할 수 없다.
운영 설정
아래 내용은 아직 미정으로 내부 CI/CD 정책에 따라 변경된다. (참고 용도로만 확인)
운영 설정은 Helm Chart가 만드는 ConfigMap이 담당한다. identity와 bundle 설정을 환경변수로 나열하지 않는 이유는 항목이 흩어질수록 인덱스 실수가 조용한 오라우팅이 되기 때문이다.
identity는 {배포 이름}-{global.env}로 template이 조립한다. 현재 Redis cache 구현이 이 값을 사용하지만, Redis key namespace와 공유 정책은 아직 확정되지
않았으므로 extension-points.md에서 합의한다.
- 업무 포트:
SERVER_PORT(기본 8080) - management 포트:
MANAGEMENT_SERVER_PORT(운영 기본 9090) - 상태:
/actuator/health/liveness,/actuator/health/readiness - bundle 진단: management 포트의
/actuator/toolBundles
readiness는 첫 Tool discovery 시도가 끝나고 usable in-memory snapshot이 있을 때만 UP이다. 원천 장애 중에도 기존 memory 또는 Redis last-good이 있으면 서비스를 유지하고, 아무 성공본도 없으면 트래픽을 받지 않는다.
route↔Tool Service 매핑의 원천은 Portal이다(ADR-0013). 배포 하나가 N개 route를 서비스하고, route key는 /mcp/{routeKey} URI에서만 결정된다. 매핑이 바뀌어도 재배포하지 않는다. 외부에서는 같은 host의 path로 route를 구분하고 컨테이너가 그 path를 그대로 처리한다(ADR-0009).
배포 정의는 Helm Chart 하나뿐이다. Chart는 배포 모델 둘을 mode로 고른다. portal이 현재 애플리케이션이 실제로 도는 경로이고, bundles는 ADR-0013이 대체한 1:1 구성(ADR-0007)이다.
helm upgrade --install axhub-mcp deploy/helm/mcp-server -f deploy/helm/mcp-server/values-dev.yaml -n <namespace>
배포 토폴로지는 values.yaml이, 환경 차이는 values-{dev,test,prod}.yaml이 소유한다. 두 모드의 차이, 등급별 가용성, 확정 전 임시값은 deploy/README.md가 정본이다.
평문 manifest가 필요하면 deploy/ci/render-manifests.sh가 helm template으로 만든다. 별도 YAML을 저장소에 두지 않는다 — 두 벌은 반드시 어긋난다.
빌드·이미지·배포 실행 방식은 원래 사내 표준 CI/CD가 담당한다. GitOps 저장소가 준비되기 전까지만 .gitea/workflows/가 임시로 그 역할을 하며, 그 방식이 무엇을 포기하는지와 넘길 때 할 일은 deploy/README.md에 적었다. 배포 시 알아야 할 앱 제약도 같은 문서에 정리했다.
문서 R&R
| 문서 | 책임 |
|---|---|
| README | 프로젝트 진입점과 실행 방법 |
| architecture.md | 현재 코드 구조, 요청 흐름, 내부 책임과 장애 동작 |
| Agent Builder-MCP contracts | Agent Builder와의 HTTP/JSON-RPC wire 계약 |
| Tool Service-MCP contracts | 매니페스트와 Tool 실행 wire 계약 |
| Portal-MCP contracts | Portal registry의 Tool Server endpoint 목록 조회 wire 계약 |
| decisions | 결정 이유와 대안 이력 |
| extension-points.md | 아직 미합의인 항목과 운영 보완 작업 |
| codex-workflow.md | 저장소 작업 규칙과 공개 정책 |
Superseded/Rejected 문서는 이력일 뿐 현재 구현 근거가 아니다. 코드나 공개 계약을 변경할 때는 가까운 테스트와 해당 현재 계약을 함께 수정한다. 변경을 마치기 전에 실행할 검증 명령은 AGENTS.md의 완료 기준이 정본이다.