Files
vpd-permission-poc/docs/design/620-poc4-mcp-discovery-streamable-http/README.md

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의 /mcp endpoint · 테스트: PoC_4 단위 테스트 및 실제 MCP HTTP smoke test

1. 목적 (Why)

PoC_4의 MCP Discovery UI가 KB VPD MCP를 사용자별 Bearer 토큰으로 안전하게 호출하면서도, 환경별 URL·전송 방식·오류 처리를 하나의 명확한 계약으로 관리한다.

현재 UI의 기본 호출 흐름은 동작한다. 다만 kb_mcp 설정에는 endpoint_urlbase_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_mcpcustom_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 하나이며, 인자는 promptlimit만 허용된다.
  • 단일 도구 구성에서는 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 선언은 다음 계약을 따른다. 값은 예시이며, 토큰 값은 넣지 않는다.

{
  "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한다.
  • 토큰은 공백·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/listtools/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에 민감 데이터 보존 기간과 삭제 정책을 별도로 정해야 한다.