# 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](../../decisions/ADR-0009-container-handles-public-mcp-path.md)이 정본이며 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의 `GET`은 `405 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 Request`와 `error`, `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`). 암호화된 값이라도 **로그에 남기지 않는다.** 로그에 나가는 상관 값은 `guid`와 `x-request-id`뿐이다. ## initialize와 notification `initialize`는 [v0.2 요청 예시](examples/agentbuilder-v0.2/initialize-request.json)를 그대로 사용하며, 응답은 [v0.3 응답 예시](examples/agentbuilder-v0.3/initialize-response.json)처럼 원 요청 `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/list`는 `result.tools`에 현재 snapshot의 공개 Tool 필드(`name`, `title`, `description`, `inputSchema`, `outputSchema`, `annotations`)를 반환한다. `_meta`의 version, endpoint, HTTP method, timeout, cache 설정은 실행·운영 metadata이므로 MCP 공개 응답에 포함하지 않는다. 현재 `tools/call`은 `structuredContent`를 반환하거나 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 호출은 [요청 예시](examples/agentbuilder-v0.3/tools-call-request.json)처럼 `params.name`과 object `params.arguments`를 사용한다. name은 `tools/list`와 실행 사이의 유일한 식별자다. MCP는 snapshot metadata에서 endpoint를 확정하고 arguments 전체를 JSON body로 전달한다. 성공 및 Tool 실행 실패는 각각 [성공 응답](examples/agentbuilder-v0.3/tools-call-success-response.json), [실행 실패 응답](examples/agentbuilder-v0.3/tools-call-execution-error-response.json)처럼 `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`로 반환될 수 있다. `-32602`의 `error.message`는 `Invalid 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`로 반환한다. 실행 가능한 응답 형태는 [성공 예시](examples/agentbuilder-v0.3/tools-call-success-response.json), [Tool 실행 실패 예시](examples/agentbuilder-v0.3/tools-call-execution-error-response.json), [잘못된 인자 예시](examples/agentbuilder-v0.3/tools-call-invalid-params-response.json)를 따른다. ## 호환성 메모 - v0.2의 일반 JSON 요청·응답 형식은 그대로 호환된다. - Agent Builder의 기존 `Accept: application/json, text/event-stream` header는 계속 수용한다.