저장소 문서를 다시 추적하고 계약 예제를 복원한다
All checks were successful
Deploy Gateway / deploy (push) Successful in 2m52s
All checks were successful
Deploy Gateway / deploy (push) Successful in 2m52s
계약 테스트는 docs/contracts 아래 예제를 golden example로 읽는다.
docs/와 README.md가 ignore되어 있어 예제 파일이 사라졌고 9건이
실패하고 있었다. 문서가 온전했던 마지막 상태(3de052a)에서 복원하고
.gitignore에서 두 항목을 제거한다. 에이전트 산출물인
docs/superpowers/ 제외는 유지한다.
initialize 응답의 capabilities.tools.listChanged를 문서는 true로
적고 있었으나 InitializeHandler는 false를 낸다. 현재 HTTP 단발 응답
transport가 notification을 push할 수 없으므로 false가 맞다. 예제와
architecture.md를 코드에 맞추고, ToolListChangedEvent가 발행되지만
아직 소비되지 않는다는 점과 SSE 도입 시 true로 바꾼다는 조건을
남긴다.
169개 테스트 전부 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
170
README.md
Normal file
170
README.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# 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 선택적 도입 설계](docs/mcp-java-sdk-adoption.md)를 따른다.
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
필수 조건은 JDK 21이다. 기본 profile은 `local`이며 Redis 없이 Tool Service 매니페스트를 먼저 조회하고, 최초 조회 실패 시 `config/local-core-tools-manifest-sample-v1.json`을 fallback으로 사용한다.
|
||||
|
||||
```powershell
|
||||
.\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`는 검사만 하고 고쳐 주지 않는다.
|
||||
|
||||
```powershell
|
||||
$env:IDEA_FORMATTER='C:/Program Files/JetBrains/IntelliJ IDEA 2026.1.4/bin/format.bat'
|
||||
.\gradlew.bat ideaFormat
|
||||
```
|
||||
|
||||
줄 폭 160자는 코드 스타일의 권장값이며 기존 코드에 소급 적용되지 않는다. 강제 대상이 아니다.
|
||||
|
||||
local Tool 파일을 바꾸려면 다음 환경변수에 Spring resource 경로를 지정한다.
|
||||
|
||||
```powershell
|
||||
$env:MCP_LOCAL_TOOL_REGISTRY_FILE='file:C:/path/local-tools.json'
|
||||
```
|
||||
|
||||
## 공개 계약
|
||||
|
||||
- 공개 endpoint: `POST https://{global.mcpHost}{deployments.<key>.publicPath}`
|
||||
- 예: `https://mcp-dev.apps.example.internal/mcp/processing-critical`
|
||||
- OpenShift Route는 공개 path로 MCP Service만 선택하고, 컨테이너가 같은 path를 직접 처리
|
||||
- Method: `initialize`, `notifications/initialized`, `tools/list`, `tools/call`
|
||||
- Response: 항상 단일 `application/json` JSON-RPC response
|
||||
- `Accept: application/json, text/event-stream`: 호환 목적으로 수용하지만 SSE 경로는 제공하지 않음
|
||||
- 공개 endpoint의 `GET`: `405 Method Not Allowed`
|
||||
- `MCP-Protocol-Version`: `initialize` 이후 필수, 현재 `2025-11-25`
|
||||
- `Mcp-Session-Id`: initialize lifecycle 추적용 correlation 값이며 서버 세션이 아님
|
||||
|
||||
정확한 요청·응답과 오류 의미는 [Agent Builder-MCP 현재 계약 v0.3](docs/contracts/agent-builder-mcp/protocol-v0.3-streaming-policy.md)이 정본이다.
|
||||
Agent Builder는 공개 URL마다 별도 MCP로 등록하고 initialize한다. URL 사이에 lifecycle correlation이나 Tool 목록을 공유하지 않는다.
|
||||
|
||||
## Tool metadata와 실행
|
||||
|
||||
| 환경 | Tool 원천 | Redis |
|
||||
|---|---|---|
|
||||
| `local` | local JSON fixture | 사용 안 함 |
|
||||
| 운영(`prod`) | 이 배포가 보는 Tool Service 매니페스트를 주기적으로 pull | 성공 snapshot 공유와 warm start에만 사용 |
|
||||
|
||||
요청 경로의 `tools/list`와 `tools/call`은 in-memory snapshot만 읽는다. 운영 refresh는 bundle별 last-good을 유지하고, 모든 bundle에 사용 가능한 성공본이 있을 때만 aggregate를 교체한다. 조회 실패만으로 Tool을 제거하지 않으며 정상 매니페스트에서 삭제가 확인될 때만 반영한다. 코드는 bundle N개 병합을 지원하지만 **운영 배포의 bundle은 항상 하나다**([ADR-0007](docs/decisions/ADR-0007-one-mcp-per-tool-service.md)).
|
||||
|
||||
Tool 실행 주소는 local `_meta.endpoint` 또는 운영 `baseEndpoint` 설정에서만 정한다. Agent Builder의 `arguments`와 Tool Service 매니페스트는 호출 대상을 바꿀 수 없다.
|
||||
|
||||
운영 매니페스트와 장애 처리의 wire 계약은 [Tool Service-MCP bundle 조회 계약 v0.2](docs/contracts/tool-service-mcp/protocol-v0.2-bundle-discovery.md)를 따른다.
|
||||
|
||||
## Correlation과 로그
|
||||
|
||||
Agent Builder가 보낼 수 있는 표준 헤더는 12개다. MCP는 권한 판정이나 헤더 업무 검증을 하지 않고, 표준 헤더를 필수로 요구하지 않는다. 값이 없으면 없는 상태로 두며, 이 표준 헤더 때문에 요청을 차단하지 않는다.
|
||||
|
||||
| 헤더 | 기대 형식 | MCP 동작 |
|
||||
|---|---|---|
|
||||
| `X-Guid` | UUID V4 | 있으면 end-to-end 상관 값으로 응답과 Tool Service 호출에 이어서 사용하고, 없으면 생략 |
|
||||
| `X-Praf-No` | 숫자 8자리 | 있으면 Tool Service로 전달. 권한 판단은 하지 않음 |
|
||||
| `X-Request-Id` | UUID V4 | 있으면 Agent→MCP 요청 식별자로 사용. Tool Service 호출 시에는 새 UUID V4로 재채번 |
|
||||
| `X-Request-Time` | ISO-8601 offset date-time | 있으면 수신 context에 보관. Tool Service 호출 시에는 재채번 시각으로 교체 |
|
||||
| `X-Vrtl-Praf-No` | 숫자 8자리 | 있으면 Tool Service로 전달 |
|
||||
| `X-App-Code` | 대문자 3자리 | 있으면 Tool Service로 전달 |
|
||||
| `X-Project-Code` | 대문자 5자리 | 있으면 Tool Service로 전달 |
|
||||
| `X-User-Ip` | 사용자 단말 IP | 있으면 Tool Service로 전달 |
|
||||
| `X-Caller-IP` | 호출 서버 IP | 있으면 수신 context에 보관. Tool Service 호출 시 MCP 서버 IP로 교체 |
|
||||
| `X-Caller-Host` | 호출 서버 host name | 있으면 수신 context에 보관. Tool Service 호출 시 MCP 서버 host로 교체 |
|
||||
| `X-Channel` | 채널 코드 | 있으면 수신 context에 보관. Tool Service 호출 시 `MCP`로 교체 |
|
||||
| `X-Agent-Id` | Agent 식별자 | 있으면 Tool Service로 전달 |
|
||||
|
||||
MCP는 사번·가상사번·사용자 IP를 로그에 남기지 않는다. MDC는 사용하지 않으며 로그에는 `X-Guid`와 MCP가 받은 `X-Request-Id`만 남긴다. request/response body와 credential도 기본 로그에 남기지 않는다.
|
||||
|
||||
`Authorization`은 `mcp.tool-client.forward-authorization` 설정이 켜진 경우에만 전달한다. MCP는 이 값을 해석하지 않는다.
|
||||
|
||||
## 인증과 권한
|
||||
|
||||
**이 서버는 인증도 인가도 하지 않는다.** 요청자 신원을 검증하지 않고, Tool 실행 권한을 판단하지 않으며, 사원 식별자를 복호화하지 않는다. 결정과 근거는 [ADR-0006](docs/decisions/ADR-0006-no-authentication-in-mcp.md)이다.
|
||||
|
||||
| 책임 | 주체 |
|
||||
|---|---|
|
||||
| 외부 호출자를 Agent Builder로 제한 | OpenShift Route IP allowlist |
|
||||
| MCP Pod 직접 접근 제한 | 플랫폼 NetworkPolicy |
|
||||
| 사용자 인증과 Tool 실행 권한 | Agent Builder |
|
||||
| 사원 식별자 복호화(KMS)와 업무 권한 | Tool Service |
|
||||
|
||||
⚠️ **Route IP allowlist와 NetworkPolicy는 선택 사항이 아니다.** 외부 요청은 Route가 Agent Builder의 고정 egress CIDR만 받고, backend 요청은 ingress controller 또는 허용된 Agent Builder namespace에서만 MCP Pod에 도달한다. 실제 CIDR을 넣지 않은 배포는 운영에 사용할 수 없다. `HelmDeploymentContractTest`가 두 경계가 Chart에서 빠지지 않도록 고정한다.
|
||||
|
||||
## 운영 설정
|
||||
|
||||
운영 설정은 Helm Chart가 만드는 ConfigMap이 담당한다. `identity`와 bundle 설정을 환경변수로 나열하지 않는 이유는 항목이 흩어질수록 인덱스 실수가 조용한 오라우팅이 되기 때문이다.
|
||||
|
||||
`identity`는 `{배포 이름}-{global.env}`로 template이 조립한다. 현재 Redis cache 구현이 이 값을 사용하지만, Redis key namespace와 공유 정책은 아직 확정되지 않았으므로 [extension-points.md](docs/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이 있으면 서비스를 유지하고, 아무 성공본도 없으면 트래픽을 받지 않는다.
|
||||
|
||||
**MCP 배포 하나는 Tool Service 하나만 본다**([ADR-0007](docs/decisions/ADR-0007-one-mcp-per-tool-service.md)). 대상을 늘리는 방법은 bundle 목록을 늘리는 것이 아니라 배포를 하나 더 만드는 것이다. 외부에서는 같은 host의 고유 path로 각 배포를 노출하고 컨테이너가 그 path를 그대로 처리한다([ADR-0009](docs/decisions/ADR-0009-container-handles-public-mcp-path.md)). 배포는 업무 × 중요도 등급으로 나뉘며, 등급이 replica 수와 PodDisruptionBudget을 정한다.
|
||||
|
||||
배포 정의는 [Helm Chart](deploy/helm/mcp-server/) 하나뿐이다. 배포 토폴로지는 `values.yaml`이, 환경 차이는 `values-{dev,test,prod}.yaml`이 소유한다. 설치할 배포 하나는 `--set`으로 고른다.
|
||||
|
||||
```bash
|
||||
helm upgrade --install processing-critical-mcp deploy/helm/mcp-server -f deploy/helm/mcp-server/values-dev.yaml --set deploymentKey=processing-critical -n <namespace>
|
||||
```
|
||||
|
||||
**MCP Server와 Tool Service는 같은 namespace에 배포한다.** 그래서 values에는 Tool Service의 이름만 적고 주소는 template이 조립한다. 환경마다 URL을 반복해 적지 않으므로 오타로 엉뚱한 곳을 호출할 수 없다.
|
||||
|
||||
```yaml
|
||||
deployments:
|
||||
processing-critical:
|
||||
name: processing-critical-mcp
|
||||
service: processing-critical-tools # ← 이름만. 주소는 template이 만든다
|
||||
namePrefix: "processing." # ← 업무 단위. 등급을 넣지 않는다
|
||||
tier: critical
|
||||
publicPath: /mcp/processing-critical # ← 같은 환경 host 안에서 유일
|
||||
```
|
||||
|
||||
배포가 10개든 20개든 파일 수는 늘지 않는다. 자세한 사용법은 [deploy/README.md](deploy/README.md)에 있다.
|
||||
|
||||
평문 manifest가 필요하면 `helm template`으로 만든다. 별도 YAML을 저장소에 두지 않는다 — 두 벌은 반드시 어긋난다.
|
||||
|
||||
빌드·이미지·배포 실행 방식은 사내 표준 CI/CD가 담당하며 이 저장소가 정하지 않는다. 배포 시 알아야 할 앱 제약은 [deploy/README.md](deploy/README.md)에 정리했다.
|
||||
|
||||
## 문서 R&R
|
||||
|
||||
| 문서 | 책임 |
|
||||
|---|---|
|
||||
| [README](README.md) | 프로젝트 진입점과 실행 방법 |
|
||||
| [architecture.md](docs/architecture.md) | 현재 코드 구조, 요청 흐름, 내부 책임과 장애 동작 |
|
||||
| [Agent Builder-MCP contracts](docs/contracts/agent-builder-mcp/README.md) | Agent Builder와의 HTTP/JSON-RPC wire 계약 |
|
||||
| [Tool Service-MCP contracts](docs/contracts/tool-service-mcp/README.md) | 매니페스트와 Tool 실행 wire 계약 |
|
||||
| [decisions](docs/decisions/README.md) | 결정 이유와 대안 이력 |
|
||||
| [extension-points.md](docs/extension-points.md) | 아직 미합의인 항목과 운영 보완 작업 |
|
||||
| [codex-workflow.md](docs/codex-workflow.md) | 저장소 작업 규칙과 공개 정책 |
|
||||
|
||||
Superseded/Rejected 문서는 이력일 뿐 현재 구현 근거가 아니다. 코드나 공개 계약을 변경할 때는 가까운 테스트와 해당 현재 계약을 함께 수정한다. 변경을 마치기 전에 실행할 검증 명령은 [AGENTS.md](AGENTS.md)의 완료 기준이 정본이다.
|
||||
|
||||
Reference in New Issue
Block a user