Files
dap-was-dapms/docs/contracts/agent-builder-mcp/protocol-v0.3-streaming-policy.md
koseokmin cb29b192b4 docs를 저장소로 되돌리고 계약 예제를 복원한다
5cfb8a1이 .gitignore에 docs/를 넣고 68개 파일을 지웠다. 그런데
AgentBuilderContractExampleTest, ToolBundleContractExampleTest,
ArchitectureDocumentContractTest는 docs/ 아래 계약 예제와 architecture
문서를 입력으로 직접 읽는다. 그 결과 clean clone에서 테스트 10건이
입력을 찾지 못해 실패했다.

제외 범위를 원래 의도대로 좁힌다. 에이전트 산출물(AGENTS.md, .agents/,
.codex/, docs/superpowers/)은 계속 제외하고 저장소 문서는 추적한다.

문서는 삭제 직전 상태(3de052a)를 기준으로 복원하고, 그 위에 main 코드와
대조해 어긋난 부분을 고쳤다.

- ADR-0007을 Superseded로 바꾸고 ADR-0013을 새로 쓴다. route당 Tool
  Service N개가 최종안이며, PortalToolRegistryClient가 이미 route별로
  N개를 유지하고 있는데 ADR-0007은 "bundles는 항상 한 항목"을 Accepted
  상태로 주장하고 있었다. ADR-0009 결정 4도 부분 대체한다.
- ADR-0008 파일 헤더가 Accepted였으나 ADR-0009가 이미 대체한 상태였다.
- 6078852의 endpoint 소유권 반전이 반영되지 않은 서술을 계약 v0.2,
  bundle 설정 예제, Tool 적재 안내에서 고친다.
- Portal registry 계약 v0.1과 route key 규약을 새로 문서화한다. 둘 다
  구현은 있는데 계약 문서가 없었다.
- MCP SDK 2.0.0 SBOM을 추가한다.

번호 주석: ADR-0011과 0012는 feature/mcp-integration이 Tool inputSchema
정책에 쓰고 있어 비워 둔다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:27:56 +09:00

11 KiB

Agent Builder-MCP 동기 JSON 계약 v0.3

  • 상태: Implemented
  • 기준일: 2026-07-16
  • 공개 endpoint: POST https://{mcpHost}{publicPath}
  • 컨테이너 endpoint: 공개 URL과 동일한 POST {publicPath}
  • JSON-RPC: 2.0
  • protocolVersion: 2025-11-25

이 계약의 현재 구현은 stateless MCP 실행 계층의 transport를 동기 JSON으로 고정한다. 현재 in-memory snapshot의 표준 Tool name metadata를 조회해 확정된 endpoint로 POST하며, Mcp-Session-Id는 lifecycle correlation 값일 뿐 서버는 initialize 성공 시 이를 발급하지만 대화·readiness 상태를 저장하지 않는다.

한 환경은 공개 host를 공유하지만 path마다 독립된 MCP Deployment와 Tool Service에 연결된다. Agent Builder는 각 공개 URL을 별도 MCP로 등록하고 initialize한다. URL 사이에는 session ID, Tool 목록, lifecycle 상태를 공유하지 않는다. Route는 path를 바꾸지 않으며 컨테이너가 같은 path를 처리한다. 이 매핑은 ADR-0009이 정본이며 JSON-RPC payload에는 영향을 주지 않는다.

공개 URL과 route key

배포는 두 형태 중 하나다. 어느 쪽이든 publicPath가 Agent Builder 등록 URL의 정본이고, JSON-RPC payload는 route 선택에 관여하지 않는다.

형태 mcp.endpoint-path 호출 URL route 결정
고정 endpoint /mcp/core처럼 route 포함 POST {publicPath} 설정된 path 자체가 route다
동적 route /mcp POST /mcp/{routeKey} URL path segment가 route다

동적 route 배포에서 routeKey는 Portal registry가 선언한 route를 가리킨다. 다음 경우는 controller에 닿기 전에 JSON-RPC error(-32600 Invalid Request)로 거부한다.

상황 message
Portal 모드인데 /mcp처럼 route가 없음 route key is required
route에 /가 있거나 [A-Za-z0-9._-]{1,64}를 벗어남 route key is invalid
형식은 맞지만 registry snapshot에 없는 route route key is not registered
고정 endpoint 배포인데 뒤에 segment를 더 붙임 route key is not allowed for fixed endpoint path

미등록 route 검사는 in-memory snapshot만 조회하며 요청 경로에서 Portal이나 Redis를 새로 호출하지 않는다. 따라서 Portal 장애 중에도 이미 확보한 route는 계속 응답하고, 존재하지 않는 route는 Tool 실행 계층에 닿지 않는다.

mcp.portal.route-key 설정은 선언되어 있으나 현재 구현이 읽지 않는다. route는 URL에서만 결정되며, 설정 기본값으로 보정하면 잘못된 단일 진입점 호출이 조용히 성공하기 때문이다.

HTTP 선택 정책

  • Agent Builder는 Accept: application/json, text/event-stream을 보낸다.
  • 서버는 항상 Content-Type: application/json과 단일 JSON-RPC response를 반환한다.
  • Accept는 수용 가능 형식의 선언이며, text/event-stream이 포함되어도 응답 transport를 바꾸지 않는다.
  • 독립적인 server-push SSE channel은 제공하지 않으므로 공개 endpoint의 GET405 Method Not Allowed다.
  • initialize 요청에는 MCP-Protocol-Version header를 요구하지 않는다.
  • initialize 이후 notifications/initialized, tools/list, tools/call 요청에는 정확히 MCP-Protocol-Version: 2025-11-25이 필수다. version 등 임의 header는 대체하지 않는다. header가 없거나 지원하지 않는 값이면 server는 JSON-RPC body 대신 HTTP 400 Bad Requesterror, message, supportedVersions, guid를 가진 JSON 오류 body를 반환한다.

호출자 식별 header

MCP-Protocol-Version 외에 Agent Builder가 보내는 header는 다섯 개이며 모두 선택값이다.

header 형식 서버 동작
guid UUID 없으면 서버가 생성한다. 응답 header와 오류 body에 되돌려준다
x-request-id 안전 문자 1~128자 없으면 서버가 생성한다. 응답 header에 되돌려준다
mcp-session-id 안전 문자 1~128자 initialize lifecycle 상관 값. 서버는 저장하지 않는다
employee-no 암호화된 사원번호 해석하지 않는다
virtual-employee-no 암호화된 가상사원번호 해석하지 않는다

사원 식별자 둘은 불투명 값이다. MCP는 복호화·검증·저장하지 않고 Tool Service로 그대로 전달한다. 값의 의미는 보지 않되, 개행이나 공백이 섞여 downstream 요청 header를 조작하는 것은 거부한다 (출력 가능 문자 1~2048자가 아니면 -32600).

암호화된 값이라도 로그에 남기지 않는다. 로그에 나가는 상관 값은 guidx-request-id뿐이다.

initialize와 notification

initializev0.2 요청 예시를 그대로 사용하며, 응답은 v0.3 응답 예시처럼 원 요청 id, protocolVersion: 2025-11-25, serverInfo(name/title/version), capabilities.tools.listChanged: false를 반환한다. HTTP response header에는 새 UUID Mcp-Session-Id가 포함된다. Agent Builder는 응답 version을 이후 모든 HTTP 요청의 MCP-Protocol-Version header에 사용하고, session ID를 notifications/initialized 및 이후 Tool 요청의 correlation header로 보낸다. MCP 2025-11-25 lifecycle에 따라 Agent Builder는 notifications/initialized를 반드시 보내고 두 header를 포함한다. 서버는 notification을 HTTP 202 Accepted와 빈 body로 수용하되 stateless 원칙상 수신 여부를 저장하거나 이후 요청을 차단하는 readiness gate로 사용하지 않는다.

tools/list

tools/listresult.tools에 현재 snapshot의 공개 Tool 필드(name, title, description, inputSchema, outputSchema, annotations)를 반환한다. _meta의 version, endpoint, HTTP method, timeout, cache 설정은 실행·운영 metadata이므로 MCP 공개 응답에 포함하지 않는다.

현재 tools/callstructuredContent를 반환하거나 Tool 응답을 outputSchema로 검증하지 않는다. 따라서 outputSchema를 가진 Tool 정의를 그대로 노출하는 동작은 현재 코드의 사실이지만 MCP 2025-11-25의 구조화 출력 계약을 완전히 충족하지 않는다. 운영 Tool은 구조화 출력 지원이 도입되기 전까지 outputSchema를 생략해야 한다.

원천은 profile이 정한다. local은 Tool Service 매니페스트를 먼저 조회하고 최초 실패 시 config/local-core-tools-manifest-sample-v1.json fallback을 사용한다(파일이 곧 목록이므로 여기에 Tool 이름을 옮겨 적지 않는다). 운영은 설정된 Tool Service 매니페스트뿐이다.

동기 Tool 호출

기본 Tool 호출은 요청 예시처럼 params.name과 object params.arguments를 사용한다. name은 tools/list와 실행 사이의 유일한 식별자다. MCP는 snapshot metadata에서 endpoint를 확정하고 arguments 전체를 JSON body로 전달한다. 성공 및 Tool 실행 실패는 각각 성공 응답, 실행 실패 응답처럼 application/json JSON-RPC response로 반환한다.

성공 응답은 Tool의 plain text를 result.content[0].text, 소요 시간(ms)을 result.content[0]._meta.searchTime, 성공 여부를 result.isError: false에 넣는다. JSON object/array 응답은 compact JSON 문자열로 text에 보존하며, outer JSON serializer가 올바른 quote escaping을 수행한다. 실행·timeout·권한 실패는 result.isError: true이며, JSON-RPC envelope/params/method 오류는 기존 JSON-RPC error다. Registry의 inputSchema는 모든 tools/call에서 Tool 호출 전에 검증한다.

tools/call 성공·오류 응답 기준

Agent Builder는 HTTP 상태만으로 성공 여부를 판단하지 않고 JSON-RPC body의 최상위 result 또는 error를 확인해야 한다. 일반적인 JSON-RPC 요청 오류는 HTTP 200 OK와 함께 최상위 error로 반환될 수 있다. -32602error.messageInvalid params: <상세 원인> 형식이며, 예를 들어 필수 query가 없으면 Invalid params: 'query' is required를 반환한다. 선택적인 error.data에는 guid와 상세 원인을 추가로 담을 수 있다. 단, MCP-Protocol-Version 누락·미지원처럼 HTTP transport 단계에서 거부된 요청은 HTTP 400 Bad Request다.

상황 HTTP 상태 JSON-RPC body isError 현재 구현의 처리 주체
Tool 정상 완료 200 result.content 반드시 false ToolsCallHandler
Tool Service timeout, upstream 4xx/5xx, downstream 권한 거부 200 result.content 반드시 true ToolsCallHandler
Tool이 실행된 뒤 업무 검증·업무 규칙으로 실패 200 result.content 반드시 true Tool Service 또는 실행 계층
JSON 문법 오류 200 최상위 error (-32700) 없음 McpExceptionHandler
JSON-RPC envelope 오류 200 최상위 error (-32600) 없음 JsonRpcRequestParser
알 수 없는 MCP method 또는 Tool 200 최상위 error (-32601 또는 Tool 조회 오류) 없음 method/registry 계층
params.name 누락, params.arguments 형식 오류, 공개된 inputSchema의 필수 값 누락 200 최상위 error (-32602) 없음 Adapter/parameter/schema validator
서버 설정·Registry 장애 등 서버가 Tool 호출을 시작할 수 없는 경우 200 최상위 error (-32603 또는 서버 정의 오류) 없음 transport/execute 계층
MCP-Protocol-Version 누락 또는 미지원 400 transport 오류 body 없음 McpProtocolVersionValidator

모든 routing은 공통 name/arguments 형식과 선택된 Tool의 inputSchema를 실행 전에 검증한다. Tool Service가 반환한 HTTP 400은 검증을 통과해 Tool 실행을 시작한 뒤의 실패이므로 result.isError: true로 반환한다.

실행 가능한 응답 형태는 성공 예시, Tool 실행 실패 예시, 잘못된 인자 예시를 따른다.

호환성 메모

  • v0.2의 일반 JSON 요청·응답 형식은 그대로 호환된다.
  • Agent Builder의 기존 Accept: application/json, text/event-stream header는 계속 수용한다.