12 KiB
설계서: PoC_4 MCP Discovery UI — KB VPD Streamable HTTP 연동 정비 (#620)
상태: Draft 작성: [AI] Architect · 최종수정: 2026-07-09 추적성 — Redmine: #620 · 관련 ADR: 없음 · 구현 파일:
apps/poc4/mcp_discovery_ui.py,config/mcp_servers.json,config/mcp_servers.sample.json, VPD Backoffice의/mcpendpoint · 테스트: 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_python8500 RAG MCP의 변경 - VPD 권한 규칙, Select AI SQL 생성 규칙, ORDS 비즈니스 로직 변경
- OAuth Authorization Server, 사용자 로그인/토큰 발급 UI 재설계
- VPD 토큰 원문을 환경변수·레지스트리에 저장하는 방식
- 기존
3. 인수조건 (Acceptance Criteria)
- KB MCP URL은
KB_MCP_BASE_URL하나에서만 해석되고/mcppath가 안전하게 결합된다. 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, 유효 토큰의 권한 부족은 HTTP403으로 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-25MCP 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 선언은 다음 계약을 따른다. 값은 예시이며, 토큰 값은 넣지 않는다.
{
"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."
}
환경 변수는 아래 두 값만 필요하다.
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 요청
모든 요청은 다음 헤더를 사용한다.
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는 아래와 같이 제한한다.
{
"name": "ords.query.kb_select_ai_vpd",
"arguments": {
"prompt": "담당 고객의 보험료 상세를 보여줘",
"limit": 50
}
}
경계 검증 규칙:
- tool name은 정확히 allowlist 값 하나와 일치해야 한다.
prompt는 문자열이며 서버와 동일한 최대 길이를 적용한다.limit은 정수1..100으로 clamp한다.- 토큰은 공백·
Bearerprefix를 정규화한 뒤 헤더에만 넣는다. - 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. 흐름 / 알고리즘
- UI는
KB_MCP_BASE_URL을 읽고/mcp만 결합한다. registry의 임의endpoint_url은 KB 서버에 허용하지 않는다. - 사용자가 VPD token preset 또는 일회성 Bearer를 선택한다. 토큰은 현재 실행 변수에만 유지한다.
- client는
initialize를 보내고 서버가 반환한 protocol version과 선택 가능한 session ID를 검증한다. notifications/initialized를 보낸 뒤tools/list를 실행한다.- 응답 목록이 정확히 허용 도구를 포함하는지, 해당 input schema가
prompt,limit계약에 맞는지 확인한다. - KB 서버는 단일 도구이므로 router model을 호출하지 않고
ords.query.kb_select_ai_vpd를 직접 선택한다. tools/call은 prompt·clamp된 limit만 body에 넣고 VPD Bearer는 Authorization에만 넣는다.- 결과는 화면용 안전 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 행/컬럼 결과가 서로 다른지 확인
- 허용된 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에 민감 데이터 보존 기간과 삭제 정책을 별도로 정해야 한다.