216 lines
14 KiB
Markdown
216 lines
14 KiB
Markdown
# 설계서: PoC_4 MCP Discovery UI — KB VPD Streamable HTTP 연동 정비 (#620)
|
|
|
|
> **상태**: Draft · source snapshot imported
|
|
> **작성**: [AI] Architect · **최종수정**: 2026-07-14
|
|
> **추적성** — Redmine: #620 · 관련 ADR: 없음
|
|
> · 스냅샷: `poc4_active_source_20260714/` · 원본 archive: `poc4_active_source_20260714/poc4_active_source_20260714.tar.gz` · 목표 구현 파일: `apps/poc4/mcp_discovery_ui.py`, `config/mcp_servers.json`, VPD Backoffice의 `/mcp` endpoint · 테스트: PoC_4 단위 테스트 및 실제 MCP HTTP smoke test
|
|
|
|
## 1. 목적 (Why)
|
|
|
|
PoC_4의 MCP Discovery UI가 KB VPD MCP를 사용자별 Bearer 토큰으로 안전하게 호출하면서도, 환경별 URL·전송 방식·오류 처리를 하나의 명확한 계약으로 관리한다.
|
|
|
|
현재 UI의 기본 호출 흐름은 동작한다. 다만 `kb_mcp` 설정에는 `endpoint_url`과 `base_url_env`가 함께 있고, UI는 `endpoint_url`을 우선 사용한다. 따라서 `KB_MCP_BASE_URL`을 바꿔도 실제 호출 대상이 바뀌지 않는다. 또한 `custom_python`은 로컬 8500 MCP용 이름이므로 외부 KB VPD MCP의 통신 계약을 설명하지 못한다.
|
|
|
|
## 2. 범위 (Scope)
|
|
|
|
- **포함**:
|
|
- `apps/poc4/mcp_discovery_ui.py`의 KB MCP endpoint, 인증 헤더, JSON-RPC, 오류 처리 정비
|
|
- `config/mcp_servers.json` 및 sample의 KB MCP 선언 정비
|
|
- KB MCP의 단일 도구 `ords.query.kb_select_ai_vpd` 호출 계약 문서화
|
|
- VPD Backoffice `/mcp`과의 HTTP 상태·프로토콜 버전 호환성 점검 및 필요한 최소 보완
|
|
- **제외 (out of scope)**:
|
|
- 기존 `kb_vector_mcp` 및 `custom_python` 8500 RAG MCP의 변경
|
|
- VPD 권한 규칙, Select AI SQL 생성 규칙, ORDS 비즈니스 로직 변경
|
|
- OAuth Authorization Server, 사용자 로그인/토큰 발급 UI 재설계
|
|
- VPD 토큰 원문을 환경변수·레지스트리에 저장하는 방식
|
|
|
|
## 3. 인수조건 (Acceptance Criteria)
|
|
|
|
- [ ] KB MCP URL은 `KB_MCP_BASE_URL` 하나에서만 해석되고 `/mcp` path가 안전하게 결합된다.
|
|
- [ ] `initialize`, `notifications/initialized`, `tools/list`, `tools/call`의 모든 HTTP 요청에 현재 선택된 사용자의 `Authorization: Bearer <VPD token>`만 전송된다.
|
|
- [ ] Bearer 토큰은 JSON-RPC arguments, SQLite 대화 이력, UI 결과, 애플리케이션 로그에 저장·출력되지 않는다.
|
|
- [ ] 호출 가능한 도구는 `ords.query.kb_select_ai_vpd` 하나이며, 인자는 `prompt`와 `limit`만 허용된다.
|
|
- [ ] 단일 도구 구성에서는 LLM router를 호출하지 않고 허용 도구를 직접 선택한다.
|
|
- [ ] 토큰 없음/위조/만료는 HTTP `401`, 유효 토큰의 권한 부족은 HTTP `403`으로 UI에 구분 표시된다.
|
|
- [ ] 잘못된 Origin의 브라우저 요청은 MCP 서버에서 거부되며, 허용 Origin 및 Origin 없는 네이티브 MCP client 정책이 문서화된다.
|
|
- [ ] 기존 `kb_vector_mcp` 및 로컬 8500 MCP 회귀 테스트가 통과한다.
|
|
|
|
## 4. 컨텍스트 & 제약
|
|
|
|
- KB VPD MCP endpoint: `https://kb.cloud-handson.com/mcp`
|
|
- 보호 대상: `ords.query.kb_select_ai_vpd`는 ORDS를 거쳐 VPD 컨텍스트가 적용된 Select AI 조회를 실행한다.
|
|
- 토큰 주체: VPD 권한은 정적 서비스 계정이 아니라 현재 선택된 `KB_STAKEHOLDERS` 사용자 토큰에 의해 결정된다.
|
|
- UI의 VPD token preset 파일은 데모 편의 기능일 뿐이다. 운영에서는 OS 소유자 전용 권한(`0600`)으로 관리하고 형상관리·로그·SQLite에서 제외한다.
|
|
- UI가 현재 사용하는 `2025-11-25` MCP protocol version과 서버의 지원 버전은 handshake에서 협상해야 한다. 지원하지 않는 버전을 무조건 강제하지 않는다.
|
|
- 현재 서버는 stateless JSON-RPC POST 호출로도 동작한다. 서버가 `Mcp-Session-Id`를 발급하면 client는 이후 요청에만 그 값을 포함한다.
|
|
|
|
## 5. 아키텍처 개요
|
|
|
|
I/O는 Discovery UI의 HTTP transport와 VPD Backoffice `/mcp`에 한정한다. URL 결합, 허용 도구 검증, 요청·응답 검증, 안전한 오류 변환은 순수 함수로 분리해 네트워크 없이 테스트한다.
|
|
|
|
```
|
|
VPD 사용자 선택 / 토큰 입력
|
|
│ (원문은 요청 메모리에만 존재)
|
|
▼
|
|
PoC_4 MCP Discovery UI
|
|
├─ KB_MCP_BASE_URL + "/mcp"
|
|
├─ tool allowlist 검증
|
|
└─ Authorization: Bearer <current VPD token>
|
|
│
|
|
▼ HTTPS JSON-RPC / Streamable HTTP
|
|
VPD Backoffice MCP (/mcp)
|
|
├─ Origin·토큰 검증
|
|
├─ tools/list: metadata only
|
|
└─ tools/call: ords.query.kb_select_ai_vpd
|
|
│
|
|
▼
|
|
ORDS Select AI API → VPD context → Oracle ADB
|
|
```
|
|
|
|
## 6. 데이터 모델
|
|
|
|
### 6.1 KB MCP registry 선언
|
|
|
|
`config/mcp_servers.json`의 KB 선언은 다음 계약을 따른다. 값은 예시이며, 토큰 값은 넣지 않는다.
|
|
|
|
```json
|
|
{
|
|
"id": "kb_mcp",
|
|
"enabled": true,
|
|
"provider": "kb_vpd_streamable_http",
|
|
"transport": "streamable_http",
|
|
"base_url_env": "KB_MCP_BASE_URL",
|
|
"endpoint_path": "/mcp",
|
|
"auth_delivery": "per_request_vpd_bearer",
|
|
"timeout_seconds_env": "POC3_MCP_TIMEOUT_SECONDS",
|
|
"default_tool": "ords.query.kb_select_ai_vpd",
|
|
"tool_allowlist": ["ords.query.kb_select_ai_vpd"],
|
|
"router_mode": "direct",
|
|
"description": "KB VPD Select AI MCP; the current user's VPD bearer is sent only in the Authorization header."
|
|
}
|
|
```
|
|
|
|
환경 변수는 아래 두 값만 필요하다.
|
|
|
|
```dotenv
|
|
KB_MCP_BASE_URL=https://kb.cloud-handson.com
|
|
POC3_MCP_TIMEOUT_SECONDS=90
|
|
```
|
|
|
|
`endpoint_url`, `token_env`, `POC3_MCP_TOKEN`, `BACKOFFICE_MCP_ACCESS_TOKEN`은 KB MCP 선언에 두지 않는다. URL은 registry에 하드코딩하지 않고, VPD 토큰은 사용자별 요청에서만 받는다.
|
|
|
|
### 6.2 MCP 요청
|
|
|
|
모든 요청은 다음 헤더를 사용한다.
|
|
|
|
```http
|
|
Accept: application/json, text/event-stream
|
|
Content-Type: application/json
|
|
MCP-Protocol-Version: <negotiated version>
|
|
Authorization: Bearer <current-user-vpd-token>
|
|
```
|
|
|
|
`tools/call` body의 `arguments`는 아래와 같이 제한한다.
|
|
|
|
```json
|
|
{
|
|
"name": "ords.query.kb_select_ai_vpd",
|
|
"arguments": {
|
|
"prompt": "담당 고객의 보험료 상세를 보여줘",
|
|
"limit": 50
|
|
}
|
|
}
|
|
```
|
|
|
|
경계 검증 규칙:
|
|
|
|
- tool name은 정확히 allowlist 값 하나와 일치해야 한다.
|
|
- `prompt`는 문자열이며 서버와 동일한 최대 길이를 적용한다.
|
|
- `limit`은 정수 `1..100`으로 clamp한다.
|
|
- 토큰은 공백·`Bearer ` prefix를 정규화한 뒤 헤더에만 넣는다.
|
|
- redirect는 허용하지 않는다. 다른 origin으로 Authorization이 전달되어서는 안 된다.
|
|
|
|
## 7. 함수 명세 (Function Specs)
|
|
|
|
| 함수 | 책임(1줄) | 시그니처(잠정) | 입력 | 출력 | 에러/실패 | 복잡? |
|
|
|------|-----------|----------------|------|------|-----------|-------|
|
|
| `resolve_kb_mcp_endpoint` | base URL과 고정 path를 안전하게 결합 | `(server, environ) -> str` | registry, env | HTTPS MCP URL | 누락/비정상 URL | 단순 |
|
|
| `validate_kb_mcp_server` | KB 선언의 provider·transport·allowlist를 검증 | `(mapping) -> McpServer` | registry row | typed server | 계약 위반 | 단순 |
|
|
| `mcp_headers` | 요청별 VPD bearer 헤더 생성 | `(token, version, session_id) -> dict` | 사용자 토큰 | 안전한 headers | 토큰 형식 오류 | 단순 |
|
|
| `discover_kb_tools` | initialize 및 tools/list 후 단일 도구를 검증 | `(server, token) -> McpDiscoveryResult` | endpoint, token | tool descriptor | auth/protocol 오류 | **복잡** |
|
|
| `call_kb_select_ai` | 고정 도구에 prompt·limit을 전달 | `(server, token, prompt, limit) -> result` | 사용자 요청 | tool result | auth/timeout/JSON-RPC 오류 | **복잡** |
|
|
| `to_public_mcp_error` | HTTP/JSON-RPC 오류를 안전한 UI 메시지로 변환 | `(exception) -> PublicMcpError` | 내부 오류 | 사용자 메시지 | 원문 노출 금지 | 단순 |
|
|
|
|
## 8. 흐름 / 알고리즘
|
|
|
|
1. UI는 `KB_MCP_BASE_URL`을 읽고 `/mcp`만 결합한다. registry의 임의 `endpoint_url`은 KB 서버에 허용하지 않는다.
|
|
2. 사용자가 VPD token preset 또는 일회성 Bearer를 선택한다. 토큰은 현재 실행 변수에만 유지한다.
|
|
3. client는 `initialize`를 보내고 서버가 반환한 protocol version과 선택 가능한 session ID를 검증한다.
|
|
4. `notifications/initialized`를 보낸 뒤 `tools/list`를 실행한다.
|
|
5. 응답 목록이 정확히 허용 도구를 포함하는지, 해당 input schema가 `prompt`, `limit` 계약에 맞는지 확인한다.
|
|
6. KB 서버는 단일 도구이므로 router model을 호출하지 않고 `ords.query.kb_select_ai_vpd`를 직접 선택한다.
|
|
7. `tools/call`은 prompt·clamp된 limit만 body에 넣고 VPD Bearer는 Authorization에만 넣는다.
|
|
8. 결과는 화면용 안전 projection만 SQLite에 저장한다. Authorization 헤더와 원문 token은 저장하지 않으며, 진단이 필요하면 단방향 token fingerprint만 별도 보존할 수 있다.
|
|
|
|
## 9. 엣지케이스 & 에러 처리
|
|
|
|
| 상황 | client 처리 | 서버 기대 동작 |
|
|
|------|-------------|----------------|
|
|
| 토큰 없음 | 호출 전 안내, 네트워크 요청 없음 | 해당 없음 |
|
|
| 토큰 위조·만료 | `401` → “토큰이 유효하지 않거나 만료됨” | `WWW-Authenticate` 포함 가능 |
|
|
| 유효하지만 권한 없음 | `403` → “이 사용자에게 조회 권한 없음” | VPD fail-closed 유지 |
|
|
| allowlist 밖 도구 | 호출 전 차단 | tools/call에서도 차단 |
|
|
| 429 | 안전하게 재시도하지 않고 잠시 후 재시도 안내 | rate limit 정책 적용 |
|
|
| timeout | tools/call 자동 재시도 금지 | request ID 기반 감사 추적 |
|
|
| session ID 미발급 | stateless POST로 진행 | session을 요구하지 않음 |
|
|
| session ID 발급 | 이후 요청에 `Mcp-Session-Id` 포함 | 세션 소유·만료 검증 |
|
|
| redirect | 즉시 실패 | Authorization 전달 금지 |
|
|
| Origin 불일치 | 브라우저 UI에 일반 오류 표시 | `403`으로 거부 |
|
|
|
|
## 10. 테스트 계획
|
|
|
|
- registry 단위 테스트
|
|
- `KB_MCP_BASE_URL`만으로 endpoint가 `https://kb.cloud-handson.com/mcp`가 되는지 검증
|
|
- KB registry에 `endpoint_url`, `token_env`, `custom_python`이 있으면 fail-closed 되는지 검증
|
|
- vector MCP 설정은 기존 형식으로 계속 로드되는지 검증
|
|
- HTTP transport 단위 테스트
|
|
- initialize/tools/list/tools/call 모두 Authorization header가 있고 JSON body에는 token key가 없는지 검증
|
|
- `401`, `403`, `429`, timeout, redirect, malformed JSON-RPC 응답을 안전한 메시지로 변환하는지 검증
|
|
- session header 반환/재전송 및 stateless fallback을 검증
|
|
- 통합 smoke test
|
|
- 허용된 VPD 사용자 토큰으로 `tools/list`와 `tools/call` 성공
|
|
- 잘못된 토큰은 `401`, 타 사용자 권한은 `403`
|
|
- 설계사와 지점장 토큰으로 동일 질문을 실행해 VPD 행/컬럼 결과가 서로 다른지 확인
|
|
- 비밀정보 점검
|
|
- chat SQLite, Streamlit log, 예외 메시지에서 토큰 원문 검색 결과 0건
|
|
|
|
## 11. 리스크 & 대안 검토
|
|
|
|
- **선택**: KB MCP 전용 `kb_vpd_streamable_http` 선언을 도입하고, 로컬 8500용 `custom_python`과 분리한다. 외부 HTTPS/VPD Bearer 계약을 코드와 운영 화면에서 명확히 할 수 있다.
|
|
- **대안 1 — 기존 `custom_python` 재사용**: 동작은 시킬 수 있으나 provider 이름과 endpoint 제약이 실제 KB 서버와 맞지 않아 로컬 MCP와 외부 VPD MCP가 섞인다.
|
|
- **대안 2 — 고정 MCP access token 사용**: 구현은 간단하지만 모든 사용자가 동일 VPD 주체가 되어 데이터 권한 분리가 무너진다. 채택하지 않는다.
|
|
- **대안 3 — 즉시 OAuth 2.1 전환**: 표준 상호운용성에는 유리하지만 현재 데모의 VPD token 발급·검증 체계를 대체하므로 별도 인증 서버 설계가 필요하다.
|
|
- 롤백: 새 registry 선언을 비활성화하고 기존 KB 선언을 복원한다. DB VPD 정책·ORDS endpoint·토큰 데이터는 변경하지 않는다.
|
|
|
|
## 12. 미해결 질문 (Open Questions)
|
|
|
|
- VPD Backoffice MCP endpoint가 현재 지원할 MCP protocol version을 어떤 값으로 공식 고정할지 결정이 필요하다.
|
|
- Streamable HTTP의 GET/SSE 및 `Mcp-Session-Id`를 완전 지원할지, stateless POST profile로 운영할지 결정이 필요하다.
|
|
- 데모 이후 사용자 VPD bearer를 OAuth 2.1 access token으로 전환할지, 현 토큰을 resource-server token으로 계속 운영할지 결정이 필요하다.
|
|
- chat 대화 이력의 `basis_json`/`details_json`에 민감 데이터 보존 기간과 삭제 정책을 별도로 정해야 한다.
|
|
|
|
## 13. 2026-07-14 소스 스냅샷 반입
|
|
|
|
배포 서버의 PoC4 활성 화면 소스를 현재 저장소에 별도 폴더로 반입했다.
|
|
|
|
- 원격 실제 위치: `/home/opc/poc_4/poc4_active_source_20260714.tar.gz`
|
|
- 사용자 제시 경로 `/home/opc/poc_4/poc4_active_source_20260714/poc4_active_source_20260714.tar.gz`에는 파일이 없었고, 실제 archive는 `/home/opc/poc_4/` 바로 아래에 있었다.
|
|
- 저장소 위치: `poc4_active_source_20260714/`
|
|
- 포함 파일: Streamlit UI, MCP router, OCI GenAI client, 모델 profile, token preset sample, 기동/status script, requirements
|
|
- 보안 확인: 실제 `.env`, 실제 VPD token 원문, 대화 SQLite DB는 포함하지 않았다. `vpd_token_presets.json`에는 placeholder만 있다.
|
|
- DB 참조 경로: VPD 개발본 배포 서버에서는 `/home/opc/kbmcp/.env`의 접속 정보를 사용하고, wallet directory는 `/home/opc/wallet/kbaipoc`를 사용한다. 두 경로의 파일 내용은 저장소에 포함하지 않는다.
|
|
- 런타임 전제: Python 3.11 이상. 현재 개발 서버 기본 `python3`는 3.6.8이므로 이 소스의 문법 검증에는 맞지 않는다.
|
|
- 검증: 배포 서버 원본 경로에서 PoC4 전용 Python `3.11.15`로 `py_compile` 통과. 기존 Java 백오피스 `mvn -q test` 통과.
|
|
|
|
주의: 이 반입은 소스 스냅샷 보관이다. 아직 본 설계서의 목표 계약인 `kb_vpd_streamable_http`, `KB_MCP_BASE_URL` 단일 endpoint 해석, 단일 도구 direct routing을 구현 완료했다는 의미는 아니다.
|