Initial commit

This commit is contained in:
2026-08-05 14:20:01 +09:00
commit 71523c98e1
178 changed files with 12481 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
# Agent Builder-MCP 계약 문서
이 디렉터리는 현재 구현 계약과 목표 합의 기준선을 분리해 관리한다.
| 문서 | 상태 | 용도 |
|---|---|---|
| [protocol-v0.3-streaming-policy.md](protocol-v0.3-streaming-policy.md) | Implemented | `text/event-stream` Accept를 수용하는 동기 JSON 현재 계약 |
| [protocol-v0.2-agentbuilder.md](protocol-v0.2-agentbuilder.md) | Superseded | streaming 정책 도입 전 non-streaming 계약 |
| [protocol-v1-agreement-baseline.md](protocol-v1-agreement-baseline.md) | Partial Agreement | 2026-07-10 협의에서 확정된 목표 원칙만 기록 |
`protocol-v1-agreement-baseline.md`는 아직 실행 가능한 전체 wire contract가 아니다. Agent Builder의 전체 JSON 샘플과 미확정 항목이 승인되기 전까지 현재 구현을 변경하는 직접 근거로 사용하지 않는다.
현재 구현 예시는 [examples/agentbuilder-v0.3](examples/agentbuilder-v0.3/)에서 관리한다. `initialize` 이후 요청에는 `MCP-Protocol-Version` HTTP header가 필요하다.
현재 공개 주소는 환경별 host와 배포별 path를 합친 `https://{mcpHost}{publicPath}`이며, 각 URL을 독립 MCP로 등록한다. 컨테이너가 공개 path를 직접 처리하는 기준은 [ADR-0009](../../decisions/ADR-0009-container-handles-public-mcp-path.md)이 정본이다.
Agent Builder가 실제 Tool을 호출하며 남긴 관찰용 JSON-RPC 로그는 [observed-samples/2026-07-13](observed-samples/2026-07-13/)에 분리해 보관한다. 이 로그는 구현 계약이나 자동화 테스트 fixture가 아니다.

View File

@@ -0,0 +1,13 @@
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"serverInfo": {},
"capabilities": {}
}
}

View File

@@ -0,0 +1,5 @@
{
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {}
}

View File

@@ -0,0 +1,13 @@
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "customer.search@1.0.0: timed out"
}
],
"isError": true
}
}

View File

@@ -0,0 +1,11 @@
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "customer.search",
"arguments": {
"customerNo": "1234567890"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"customerName\":\"Hong\"}"
}
],
"structuredContent": {
"customerName": "Hong"
},
"isError": false
}
}

View File

@@ -0,0 +1,17 @@
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": false
}
},
"serverInfo": {
"name": "shl-axhub-mcp-server",
"title": "SHL AX HUB MCP Server",
"version": "1.0.0"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "customer.search@1.0.0: timed out"
}
],
"isError": true
}
}

View File

@@ -0,0 +1,8 @@
{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32602,
"message": "Invalid params: 'query' is required"
}
}

View File

@@ -0,0 +1,12 @@
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "processing",
"arguments": {
"query": "processing system information inquiry",
"category": "processing"
}
}
}

View File

@@ -0,0 +1,16 @@
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "processing complete",
"_meta": {
"searchTime": 976.1
}
}
],
"isError": false
}
}

View File

@@ -0,0 +1,67 @@
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "insurance.processing.test",
"title": "처리계 연계 점검",
"description": "처리계 연계 경로와 요청·응답 형식을 점검하는 테스트 전용 도구입니다. 실제 보험 업무 데이터는 처리하지 않습니다.",
"inputSchema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"description": "테스트 요청 식별자입니다."
}
}
},
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}
},
{
"name": "insurance.corebanking.test",
"description": "계정계 연계 경로와 요청·응답 형식을 점검하는 테스트 전용 도구입니다. 실제 계약·수납·지급 처리는 수행하지 않습니다.",
"inputSchema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"description": "테스트 요청 식별자입니다."
}
}
}
},
{
"name": "insurance.information.test",
"description": "정보계 연계 경로와 요청·응답 형식을 점검하는 테스트 전용 도구입니다. 실제 고객·계약 정보를 조회하지 않습니다.",
"inputSchema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"description": "테스트 요청 식별자입니다."
}
}
}
},
{
"name": "insurance.channel.test",
"description": "채널계 연계 경로와 요청·응답 형식을 점검하는 테스트 전용 도구입니다. 실제 고객 채널 업무를 수행하지 않습니다.",
"inputSchema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"description": "테스트 요청 식별자입니다."
}
}
}
}
]
}
}

View File

@@ -0,0 +1,14 @@
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}

View File

@@ -0,0 +1,34 @@
{
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"prompts": {
"listChanged": true
},
"resources": {
"listChanged": true
}
},
"serverInfo": {
"name": "exa-search-server",
"title": "Exa",
"version": "3.2.1",
"websiteUrl": "<https://exa.ai>",
"icons": [
{
"src": "<https://exa.ai/images/favicon-32x32.png>",
"mimeType": "image/png",
"sizes": [
"32x32"
]
}
]
}
},
"jsonrpc": "2.0",
"id": 1
}

View File

@@ -0,0 +1,5 @@
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}

View File

@@ -0,0 +1,13 @@
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "web_search_exa",
"arguments": {
"query": "most popular useless github repository stars useless repo",
"numResults": 10
}
}
}

View File

@@ -0,0 +1,183 @@
# 관찰 로그 01 - exa / `web_search_exa`
- 출처: `MCP_툴_호출_통신_로그_(JSON-RPC) (1).pdf`
- 원본 생성 시각: 2026-07-13 17:10:33 UTC
- 성격: Agent Builder 솔루션에서 수행한 관찰용 실행 로그. 본 문서는 구현 계약이나 테스트 fixture가 아니다.
## 헤더 및 실행 메타데이터
| 항목 | 원본 기록 값 |
|---|---|
| transport type | `mcp-http` |
| request_id | `045992d3-9683-4409-9613-e355a3eb05e1` |
| tool_uid | `f44207bd-9afb-4bbe-88d7-1cadf9e2779b` |
| Tool version | `2.0` |
| 실행 시각 | 2026-07-13 16:53:16 UTC |
| 결과 | ok · 1896ms · truncated=False |
| JSON-RPC 프레임 수 | 5 |
| lifecycle | `initialize → notifications/initialized → tools/call` |
### HTTP 헤더 기록 범위
원본 PDF에는 개별 HTTP request/response header 값이 기록되어 있지 않다. 원본 표의 `request_id`, `tool_uid`, `version`은 실행 메타데이터이며 HTTP header라고 단정하지 않는다.
원본 공통 안내에는 시크릿 및 인증 header가 로깅 전에 `[REDACTED]`로 마스킹되었다고 명시되어 있다. 따라서 Authorization, `Mcp-Session-Id` 등 실제 HTTP header 값은 이 문서에 포함하지 않는다.
## JSON-RPC 통신 전문
아래는 원본 PDF의 JSON-RPC 프레임을 순서대로 옮긴 전사본이다. 긴 `result.content[].text` 문자열은 PDF의 시각적 줄바꿈을 보존했으므로, 이 블록 전체를 기계 실행용 단일 JSON payload로 사용하지 않는다.
```text
16:53:16 요청 → initialize
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}
16:53:16 ← 응답 initialize
{
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"prompts": {
"listChanged": true
},
"resources": {
"listChanged": true
}
},
"serverInfo": {
"name": "exa-search-server",
"title": "Exa",
"version": "3.2.1",
"websiteUrl": "<https://exa.ai>",
"icons": [
{
"src": "<https://exa.ai/images/favicon-32x32.png>",
"mimeType": "image/png",
"sizes": [
"32x32"
]
}
]
}
},
"jsonrpc": "2.0",
"id": 1
}
16:53:16 요청 → notifications/initialized
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
16:53:17 요청 → tools/call
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "web_search_exa",
"arguments": {
"query": "most popular useless github repository stars useless repo",
"numResults": 10.0
}
}
}
16:53:18 ← 응답 tools/call
{
"result": {
"content": [
{
"type": "text",
"text": "Title: btahir/uselesshooks\nURL: <https://github.com/btahir/uselesshooks\nPublished:
> N/A\nAuthor: N/A\nHighlights:\n# btahir/us\n...\n- Stars: 125\n- Forks: 9\n- Watchers: 125\n- Open
issues: 3\n- License: MIT License\n- Default branch: main\n- Created: 2022-12-18T03:03:19Z\n\n---\n\n
Title: Useless-Garbage-Institute/useless-garbage\nURL: <https://github.com/Useless-Garbage-Institute/
useless-garbage\nPublished:> 2015-08-27T22:14:06.000Z\nAuthor: N/A\nHighlights:\n# Repository: Useles
s-Garbage-Institute/useless-garbage\n...\nThe most profoundly useless javascript library ever invente
d.\n...\n- Stars: 6\n- Forks: 1\n- Watchers: 6\n- Open issues: 1\n- Primary language: JavaScript\n- L
anguages: JavaScript\n- Default branch: master\n- Created: 2015-08-27T22:14:06Z\n- Last push: 2015-09
-01T01:14:37Z\n- Contributors: 2 (top: kriztynna, cgalbiati)\n...\nThe useless-garbage library is the
only npm module that is guaranteed to have no redeemable functionality whatsoever. There are plenty o
f helpful JavaScript libraries out there, and some that are of questionable utility, but none that ar
e share our commitment to utter uselessness.\n\n---\n\nTitle: tomekw/whatever\nURL: <https://github.c
om/tomekw/whatever\nPublished:> N/A\nAuthor: N/A\nHighlights:\n- Stars: 281\n- Forks: 115\n- Watcher
s: 281\n- Open issues: 2\n- Default branch: master\n- Created: 2014-10-10T15:51:31Z\n\n---\n\nTitle:
thecodersroom/the-button-that-does-nothing\nURL: <https://github.com/thecodersroom/the-button-that-do
es-nothing\nPublished:> 2025-10-06T18:39:22.000Z\nAuthor: N/A\nHighlights:\n# Repository: thecodersro
om/the-button-that-does-nothing\n...\nthat looks important\n...\nliterally does nothing\n...\n- Star
s: 21\n- Forks: 67\n- Watchers: 0\n- Open issues: 24\n- Primary language: JavaScript\n- Languages: Ja
vaScript (45.4%), CSS (41.9%), HTML (12.7%)\n- License: MIT License (MIT)\n- Topics: css, hacktoberfe
st, hacktoberfest-accepted, html, javascript\n- Default branch: main\n- Homepage: <https://thecodersr
oom.github.io/the-button-that-does-nothing/\n-> Created: 2025-10-06T18:39:22Z\n- Last push: 2025-11-0
4T09:02:47Z\n- Contributors: 60 (top: AbdulKhadhar, AdZard69, Moksh-Mutreja, AshaSaini-033, akshith28
55, kronpatel, ahmedrazabaloch, AhishRagav, Nl-T-lN, vanshikap21)\n...\nbuilt entirely with **\n\n---
\n\nTitle: GitHub - niltok64/the-useless-collection: A collection of software that serve no purpose o
ther than waste your time. Forking is encouraged!\nURL: <https://github.com/niltok64/the-useless-coll
ection\nPublished:> 2021-11-30T09:35:25.000Z\nAuthor: niltok64\nHighlights:\n64/the\n...\nuseless-col
lection\n...\n[\nStar\n1\n](<https://github.com/login?return_to=/niltok64/the-useless-collection>)\n*
\nA collection of software that serve no purpose other than waste your time. Forking is encouraged!\n
[niltok64.github.io/the-useless-collection/](<https://niltok64.github.io/the-useless-collection/>)\n#
## License\n...\nstar\n](<https://github.com/niltok64/the-useless-collection/stargazers>) [\n0
\n...\n://github.com/niltok64/the-useless-collection/forks)\n...\n://github.com/niltok64/the-useless-
collection/branches) [\n...\n://github.com/niltok64/the-useless-collection/tags) [\nActivity
\n...\n://github.\n...\n/nilt\n...\nthe-useless-collection\n...\n[\n**1**\nstar\n](<https://github.co
m/niltok64/the-useless-collection/stargazers>)\n...\n### Watchers\n\n---\n\nTitle: GitHub - needless-
org/needless: The JavaScript library you never asked for, never needed, and probably shouldn't use. B
ut here we are!\nURL: <https://github.com/needless-org/needless\nPublished:> 2023-09-05T14:04:16.000Z
\nAuthor: needless-org\nHighlights:\nGitHub - needless-org/needless: The JavaScript library you never
asked for, never needed, and probably shouldn't use. But here we are!\n...\n[Skip to\n...\nneedless-o
rg/needless)\n...\nhttps://github\n...\ncom/login?return_to=/needless-org/needless)\n...\n[\nStar\n1
\n](<https://github.com/login?return_to=/needless-org/needless>)\n*\nThe JavaScript library you never
asked for, never needed, and probably shouldn't use. But here we are!\n...\n[\n1\nstar\n](<https://gi
thub.com/needless-org/needless/stargazers>) [\n0\nforks\n](<https://github.com/needless-org/needless/
forks>) [\nBranches\n](<https://github.com/needless-org/needless/branches>) [\nTags\n](<https://githu
b.com/needless-org/needless/tags>) [\nActivity\n](<https://github.com/needless-org/needless/activity
>)\n[\n...\nneedless-\n...\nneedless)\n...\nThe JavaScript library you never asked for, never needed,
and probably shouldn't use. But here we are!\n...\n\"My project is running too\n...\nwish there was
\n...\nadd some completely unnecessary\n...\nno further!\n...\nIntroducing `needless-js`, the world's
first and foremost pointless library. We bring the \"Why?\" into \"Why is this even a thing?\".\n## F
eatures\n...\n1. **Absolutely Useless**: Doesn't fix any problems because it doesn't know what proble
ms are.\n2. **Hilariously Redundant**: Why have one function when you can have ten doing the same thi
ng?\n3. **Zero Dependencies**: Mainly because no other library wants to be associated with us.\n4. **
Eco-friendly**: Uses 0% CPU because it does absolutely nothing.\n## Installation\n...\n[\n**1**\nstar
\n...\n](<https://github.com/needless-org/needless/stargazers>)\n\n---\n\nTitle: GitHub - terremoth/a
wesome-hilarious-repos\nURL: <https://github.com/terremoth/awesome-hilarious-repos\nPublished:> N/A\n
Author: N/A\nHighlights:\nAwesome Hilarious Github Repositories. Awesome license. GitHub contributor
s. List of all (?) available and funniest Github repos. Contribute if you know others!\n\n---\n\nTitl
e: SuavePirate/Xamarin.Yeet\nURL: <https://github.com/SuavePirate/Xamarin.Yeet\nPublished:> 2020-02-2
2T21:16:23.000Z\nAuthor: N/A\nHighlights:\n# Repository: SuavePirate/Xamarin.Yeet\n...\nAn absolutely
useless tool to yeet your components off the screen.\n...\n- Stars: 2\n- Forks: 1\n- Watchers: 1\n- O
pen issues: 0\n- Primary language: C#\n- Languages: C#\n- License: MIT License (MIT)\n- Default branc
h: master\n- Created: 2020-02-22T21:16:23Z\n- Last push: 2020-02-22T21:31:11Z\n- Contributors: 1 (to
p: SuavePirate)\n...\nAn absolutely useless tool to yeet your components off the screen.\n\n---\n\nTi
tle: nico-garnier/useless-repo\nURL: <https://github.com/nico-garnier/useless-repo\nPublished:> 2020-
12-03T11:49:46.000Z\nAuthor: N/A\nHighlights:\n# Repository: nico-garnier/useless-repo\n...\n- Stars:
1\n- Forks: 0\n- Watchers: 2\n- Open issues: 0\n- Default branch: main\n- Created: 2020-12-03T11:49:4
6Z\n- Last push: 2020-12-03T14:16:57Z\n- Contributors: 2 (top: nico-garnier, eilenilec)\n...\n# usele
ss-repo\n...\nFor github stat on top collaborators only.\n\n---\n\nTitle: elierotenberg/useless | Git
Hub | Open Source Insights\nURL: <https://deps.dev/project/github/elierotenberg%2fuseless\nPublished:
> N/A\nAuthor: N/A\nHighlights:\nelierotenberg/useless | GitHub | Open Source Insights\n...\n# eliero
tenberg/useless\n...\n33 stars",
"_meta": {
"searchTime": 1071.9
}
}
]
},
"jsonrpc": "2.0",
"id": 3
}
```

View File

@@ -0,0 +1,14 @@
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}

View File

@@ -0,0 +1,34 @@
{
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"prompts": {
"listChanged": true
},
"resources": {
"listChanged": true
}
},
"serverInfo": {
"name": "exa-search-server",
"title": "Exa",
"version": "3.2.1",
"websiteUrl": "<https://exa.ai>",
"icons": [
{
"src": "<https://exa.ai/images/favicon-32x32.png>",
"mimeType": "image/png",
"sizes": [
"32x32"
]
}
]
}
},
"jsonrpc": "2.0",
"id": 1
}

View File

@@ -0,0 +1,5 @@
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}

View File

@@ -0,0 +1,13 @@
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "web_search_exa",
"arguments": {
"query": "site:github.com \"does nothing\" stars",
"numResults": 10
}
}
}

View File

@@ -0,0 +1,176 @@
# 관찰 로그 02 - exa / `web_search_exa`
- 출처: `MCP_툴_호출_통신_로그_(JSON-RPC) (1).pdf`
- 원본 생성 시각: 2026-07-13 17:10:33 UTC
- 성격: Agent Builder 솔루션에서 수행한 관찰용 실행 로그. 본 문서는 구현 계약이나 테스트 fixture가 아니다.
## 헤더 및 실행 메타데이터
| 항목 | 원본 기록 값 |
|---|---|
| transport type | `mcp-http` |
| request_id | `536d9a7e-be3e-4847-8878-f71ae1d690ba` |
| tool_uid | `f44207bd-9afb-4bbe-88d7-1cadf9e2779b` |
| Tool version | `2.0` |
| 실행 시각 | 2026-07-13 16:53:24 UTC |
| 결과 | ok · 1768ms · truncated=False |
| JSON-RPC 프레임 수 | 5 |
| lifecycle | `initialize → notifications/initialized → tools/call` |
### HTTP 헤더 기록 범위
원본 PDF에는 개별 HTTP request/response header 값이 기록되어 있지 않다. 원본 표의 `request_id`, `tool_uid`, `version`은 실행 메타데이터이며 HTTP header라고 단정하지 않는다.
원본 공통 안내에는 시크릿 및 인증 header가 로깅 전에 `[REDACTED]`로 마스킹되었다고 명시되어 있다. 따라서 Authorization, `Mcp-Session-Id` 등 실제 HTTP header 값은 이 문서에 포함하지 않는다.
## JSON-RPC 통신 전문
아래는 원본 PDF의 JSON-RPC 프레임을 순서대로 옮긴 전사본이다. 긴 `result.content[].text` 문자열은 PDF의 시각적 줄바꿈을 보존했으므로, 이 블록 전체를 기계 실행용 단일 JSON payload로 사용하지 않는다.
```text
16:53:24 요청 → initialize
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}
16:53:24 ← 응답 initialize
{
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"prompts": {
"listChanged": true
},
"resources": {
"listChanged": true
}
},
"serverInfo": {
"name": "exa-search-server",
"title": "Exa",
"version": "3.2.1",
"websiteUrl": "<https://exa.ai>",
"icons": [
{
"src": "<https://exa.ai/images/favicon-32x32.png>",
"mimeType": "image/png",
"sizes": [
"32x32"
]
}
]
}
},
"jsonrpc": "2.0",
"id": 1
}
16:53:24 요청 → notifications/initialized
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
16:53:24 요청 → tools/call
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "web_search_exa",
"arguments": {
"query": "site:github.com \"does nothing\" stars",
"numResults": 10.0
}
}
}
16:53:25 ← 응답 tools/call
{
"result": {
"content": [
{
"type": "text",
"text": "Title: michaelb/do-nothing.vim\nURL: <https://github.com/michaelb/do-nothing.vim\nPu
blished:> N/A\nAuthor: N/A\nHighlights:\nA vim plugin that does nothing. Because why not\n...\n- Star
s: 121\n- Forks: 4\n- Watchers: 121\n- Open issues: 1\n- License: MIT License\n- Default branch: main
\n- Created: 2021-07-06T07:35:05Z\n...\nThis plugin doesn't do anything.\n\n---\n\nTitle: silicakes/n
ada-js\nURL: <https://github.com/silicakes/nada-js\nPublished:> N/A\nAuthor: N/A\nHighlights:\nA Libr
ary that does nothing\n...\n- Stars: 32\n- Forks: 0\n- Watchers: 32\n- Open issues: 3\n- License: MIT
License\n- Default branch: main\n- Created: 2019-05-29T22:52:59Z\n...\n### A Library that does nothin
g in or for your project\n...\nNadaJS gives a single guarantee: Aside from taking space, it will do a
bsolutely nothing.\nNo matter what kind of project, architecture or constraints you might have, nadaJ
S always gets nothing done.\n\n---\n\nTitle: GitHub - azr/donothing: does nothing !\nURL: <https://gi
thub.com/azr/donothing\nPublished:> 2012-10-25T16:10:49.000Z\nAuthor: azr\nHighlights:\nGitHub - az
\n...\ndonothing: does nothing !\n...\n[\nStar\n1\n](<https://github.com/login?return_to=/azr/donothi
ng>)\n...\n*\ndoes nothing !\n### License\n...\n[\n1\n...\nstar\n](<https://github.com/azr/donothing/
stargazers>) [\n0\n...\n://github.com/azr/donothing/forks) [\n...\n://github.com/azr/donothing/branch
es\n...\nTags\n](\n...\n://github.com/azr/donothing/tags\n...\n://github.com/azr/donothing/activity)
\n...\n://github.com/\n...\n=/azr/donothing)\n...\ndoes nothing !\n...\n[\n**1**\nstar\n](<https://gi
thub.com/azr/donothing/stargazers>)\n\n---\n\nTitle: imjakechapman/TheNothingApp\nURL: <https://githu
b.com/imjakechapman/TheNothingApp\nPublished:> 2014-06-19T22:35:01.000Z\nAuthor: N/A\nHighlights:\nTh
e app that does literally nothing.\n...\n- Stars: 19\n- Forks: 2\n- Watchers: 19\n- Open issues: 2\n-
Primary language: Swift\n- Languages: Swift (71.3%), Java (28.7%)\n- Default branch: master\n- Homepa
ge: thenothingapp.com\n- Created: 2014-06-19T22:35:01Z\n- Last push: 2015-06-22T22:07:41Z\n- Contribu
tors: 2 (top: imjakechapman, thiagokimo)\n...\nThe app that does literally nothing, hackernews/design
ernews/twitter approved best application for doing jack-diddley-squat.\n\n---\n\nTitle: thecodersroo
m/the-button-that-does-nothing\nURL: <https://github.com/thecodersroom/the-button-that-does-nothing\n
Published:> 2025-10-06T18:39:22.000Z\nAuthor: N/A\nHighlights:\n- Stars: 21\n- Forks: 67\n- Watchers:
0\n- Open issues: 24\n- Primary language: JavaScript\n- Languages: JavaScript (45.4%), CSS (41.9%), H
TML (12.7%)\n- License: MIT License (MIT)\n- Topics: css, hacktoberfest, hacktoberfest-accepted, htm
l, javascript\n- Default branch: main\n- Homepage: <https://thecodersroom.github.io/the-button-that-d
oes-nothing/\n-> Created: 2025-10-06T18:39:22Z\n- Last push: 2025-11-04T09:02:47Z\n- Contributors: 60
(top: AbdulKhadhar, AdZard69, Moksh-Mutreja, AshaSaini-033, akshith2855, kronpatel, ahmedrazabaloch,
AhishRagav, Nl-T-lN, vanshikap21)\n\n---\n\nTitle: seeschloss/nothing-to-see-here\nURL: <https://gith
ub.com/seeschloss/nothing-to-see-here\nPublished:> N/A\nAuthor: N/A\nHighlights:\nAn NPM package whic
h does nothing\n...\n- Stars: 1\n- Forks: 0\n- Watchers: 1\n- Open issues: 0\n- License: MIT License
\n- Default branch: master\n- Created: 2015-01-21T08:32:44Z\n...\n## Usage ##\n\n var nothing = requi
re('nothing-to-see-here');\n // Do nothing with nothing, since it does nothing at all.\n\n---\n\nTitl
e: 10xly/do-nothing\nURL: <https://github.com/10xly/do-nothing\nPublished:> N/A\nAuthor: N/A\nHighlig
hts:\n- Stars: 1\n- Forks: 0\n- Watchers: 1\n- Open issues: 0\n- Default branch: main\n- Created: 202
4-09-06T19:20:09Z\n- Fork: yes\n...\nDo nothing.\n...\nIt's just another noop\n\n---\n\nTitle: Search
code, repositories, users, issues, pull requests...\nURL: <https://github.com/techsiddhi/does-nothing
\nPublished:> 2025-07-22T16:46:01.000Z\nAuthor: techsiddhi\nHighlights:\n.com/\n...\n](https://
\n...\n* [Notifications](<https://github.com/login?return_to=/techsiddhi/does-nothing>)You must be si
gned in to change notification settings\n* [Fork0](<https://github.com/login?return_to=/techsiddhi/do
es-nothing>)\n* [Star1](<https://github.com/login?return_to=/techsiddhi/does-nothing>)\n...\n[1star]
(<https://github.com/techsiddhi/does-nothing/stargazers>)[0forks](<https://github.com/techsiddhi/does
-nothing/forks>)[Branches](<https://github.com/techsiddhi/does-nothing/branches>)[Tags](<https://gith
ub.com/techsiddhi/does-nothing/tags>)[Activity](<https://github.com/techsiddhi/does-nothing/activity
>)\n...\n[Star](<https://github.com/login?return_to=/techsiddhi/does-nothing>)\n...\n/activity)
\n...\n[**1**star](<https://github.com/techsiddhi/does-nothing/stargazers>)\n### Watchers\n[**0**watc
hing](<https://github.com/techsiddhi/does-nothing/watchers>)\n\n---\n\nTitle: NicusorN5/DoNothing\nUR
L: <https://github.com/NicusorN5/DoNothing\nPublished:> 2023-08-19T15:20:52.000Z\nAuthor: N/A\nHighli
ghts:\nThis does nothing. Literally nothing.\n...\n- Stars: 0\n- Forks: 0\n- Watchers: 0\n- Open issu
es: 0\n- Primary language: Assembly\n- Languages: Assembly\n- License: MIT License (MIT)\n- Topics: a
ssembly, nothing\n- Default branch: main\n- Created: 2023-08-19T15:20:52Z\n- Last push: 2023-08-19T1
5:29:03Z\n- Contributors: 1 (top: NicusorN5)\n- Releases: 1\n- Latest release: release (2023-08-19T1
5:29:03Z)\n- Archived: true\n...\nThis does nothing. Literally nothing.\n\n---\n\nTitle: README.md\nU
RL: <https://github.com/torahappy/does_nothing/blob/master/README.md\nPublished:> N/A\nAuthor: N/A\nH
ighlights:\n# does_nothing : A library that does nothing\n...\n- `function a()`\n - Description: Does
some meaningless caluculation.\n - Input: nothing.\n - Output: nothing.\n...\n- `function b()`\n - De
scription: Does some meaningless caluculation.\n - Input: nothing.\n - Output: nothing.\n...\n- `func
tion c()`\n - Description: Does some meaningless caluculation.\n - Input: nothing.\n - Output: nothin
g.\n...\nThough functions in this library are completely harmless and consume little cpu resource, no
t telling users that the program sometimes does a meaningless calculation or computes inefficiently m
ay result in a criminal prosecution as the program may be against users' intention. (in Japan)",
"_meta": {
"searchTime": 976.1
}
}
]
},
"jsonrpc": "2.0",
"id": 3
}
```

View File

@@ -0,0 +1,14 @@
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}

View File

@@ -0,0 +1,34 @@
{
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"prompts": {
"listChanged": true
},
"resources": {
"listChanged": true
}
},
"serverInfo": {
"name": "exa-search-server",
"title": "Exa",
"version": "3.2.1",
"websiteUrl": "<https://exa.ai>",
"icons": [
{
"src": "<https://exa.ai/images/favicon-32x32.png>",
"mimeType": "image/png",
"sizes": [
"32x32"
]
}
]
}
},
"jsonrpc": "2.0",
"id": 1
}

View File

@@ -0,0 +1,5 @@
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}

View File

@@ -0,0 +1,13 @@
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "web_search_exa",
"arguments": {
"query": "site:github.com \"useless\" \"stars\" \"forks\" repository useless",
"numResults": 10
}
}
}

View File

@@ -0,0 +1,178 @@
# 관찰 로그 03 - exa / `web_search_exa`
- 출처: `MCP_툴_호출_통신_로그_(JSON-RPC) (1).pdf`
- 원본 생성 시각: 2026-07-13 17:10:33 UTC
- 성격: Agent Builder 솔루션에서 수행한 관찰용 실행 로그. 본 문서는 구현 계약이나 테스트 fixture가 아니다.
## 헤더 및 실행 메타데이터
| 항목 | 원본 기록 값 |
|---|---|
| transport type | `mcp-http` |
| request_id | `5720da6c-68cc-44f3-8a1f-73088bddc23d` |
| tool_uid | `f44207bd-9afb-4bbe-88d7-1cadf9e2779b` |
| Tool version | `2.0` |
| 실행 시각 | 2026-07-13 16:53:31 UTC |
| 결과 | ok · 2070ms · truncated=False |
| JSON-RPC 프레임 수 | 5 |
| lifecycle | `initialize → notifications/initialized → tools/call` |
### HTTP 헤더 기록 범위
원본 PDF에는 개별 HTTP request/response header 값이 기록되어 있지 않다. 원본 표의 `request_id`, `tool_uid`, `version`은 실행 메타데이터이며 HTTP header라고 단정하지 않는다.
원본 공통 안내에는 시크릿 및 인증 header가 로깅 전에 `[REDACTED]`로 마스킹되었다고 명시되어 있다. 따라서 Authorization, `Mcp-Session-Id` 등 실제 HTTP header 값은 이 문서에 포함하지 않는다.
## JSON-RPC 통신 전문
아래는 원본 PDF의 JSON-RPC 프레임을 순서대로 옮긴 전사본이다. 긴 `result.content[].text` 문자열은 PDF의 시각적 줄바꿈을 보존했으므로, 이 블록 전체를 기계 실행용 단일 JSON payload로 사용하지 않는다.
```text
16:53:31 요청 → initialize
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}
16:53:31 ← 응답 initialize
{
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"prompts": {
"listChanged": true
},
"resources": {
"listChanged": true
}
},
"serverInfo": {
"name": "exa-search-server",
"title": "Exa",
"version": "3.2.1",
"websiteUrl": "<https://exa.ai>",
"icons": [
{
"src": "<https://exa.ai/images/favicon-32x32.png>",
"mimeType": "image/png",
"sizes": [
"32x32"
]
}
]
}
},
"jsonrpc": "2.0",
"id": 1
}
16:53:31 요청 → notifications/initialized
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
16:53:31 요청 → tools/call
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "web_search_exa",
"arguments": {
"query": "site:github.com \"useless\" \"stars\" \"forks\" repository useless",
"numResults": 10.0
}
}
}
16:53:33 ← 응답 tools/call
{
"result": {
"content": [
{
"type": "text",
"text": "Title: gianmarco-mameli/uselessrepo\nURL: <https://github.com/gianmarco-mameli/usele
ssrepo\nPublished:> 2023-04-17T12:26:41.000Z\nAuthor: N/A\nHighlights:\n# Repository: gianmarco-mamel
i/uselessrepo\n...\nThis useless repo is created for versioning a version\n...\n- Stars: 3\n- Forks:
2\n- Watchers: 1\n- Open issues: 0\n- Default branch: main\n- Created: 2023-04-17T12:26:41Z\n- Last p
ush: 2025-05-09T07:38:26Z\n- Contributors: 3 (top: gianmarco-mameli, s0ys4uc3, CrownKingClown)\n...\n
# uselessrepo\n...\nThis useless repo is created for versioning a version\n\n---\n\nTitle: tinkerhub/
useless_project_temp\nURL: <https://github.com/tinkerhub/useless_project_temp\nPublished:> N/A\nAutho
r: N/A\nHighlights:\n# tinkerhub/useless_project_temp\n...\n- Stars: 15\n- Forks: 854\n- Watchers: 15
\n- Open issues: 42\n- Default branch: main\n- Created: 2024-10-23T10:26:22Z\n\n---\n\nTitle: btahir/
uselesshooks\nURL: <https://github.com/btahir/uselesshooks\nPublished:> N/A\nAuthor: N/A\nHighlight
s:\n# btahir/uselesshooks\n...\nA Collection of U\n...\ness React Hooks to impress your coworkers
\n...\n- Stars: 125\n- Forks: 9\n- Watchers: 125\n- Open issues: 3\n- License: MIT License\n- Default
branch: main\n- Created: 2022-12-18T03:03:19Z\n\n---\n\nTitle: dorktoast/turd\nURL: <https://github.c
om/dorktoast/turd\nPublished:> N/A\nAuthor: N/A\nHighlights:\nThe Useless Repo (Duh)\n...\n- Stars: 2
\n- Forks: 0\n- Watchers: 2\n- Open issues: 0\n- Default branch: master\n- Created: 2023-08-22T08:54:
42Z\n\n##\n...\n# TURD: The Useless Repo (Duh)\n...\nThis is the most useless repo on github. Inside
this repo you will find scripts that make your soul hurt.\n\n---\n\nTitle: elierotenberg/useless\nUR
L: <https://github.com/elierotenberg/useless\nPublished:> 2020-05-10T09:53:12.000Z\nAuthor: N/A\nHigh
lights:\n# Repository: elierotenberg/useless\n...\nUseless React hooks\n...\n- Stars: 33\n- Forks: 1
\n- Watchers: 1\n- Open issues: 6\n- Primary language: TypeScript\n- Languages: TypeScript (68.7%), J
avaScript (31.3%)\n- Default branch: master\n- Created: 2020-05-10T09:53:12Z\n- Last push: 2022-03-26
T16:38:59Z\n- Contributors: 1 (top: elierotenberg)\n...\nThis is a library of useless hooks for commo
n non-use-cases.\n\n---\n\nTitle: GitHub - niltok64/the-useless-collection: A collection of software
that serve no purpose other than waste your time. Forking is encouraged!\nURL: <https://github.com/ni
ltok64/the-useless-collection\nPublished:> 2021-11-30T09:35:25.000Z\nAuthor: niltok64\nHighlights:\nG
itHub - niltok64/the-useless-collection: A collection of software that serve no purpose other than wa
ste your time. Forking is encouraged!\n[Skip to content](#start-of-content)\n## Navigation Menu\nTogg
le navigation\n...\n](<https://github>.\n...\n/\n**\n[the-useless-collection](<https://github.com/nil
tok64/the-useless-collection>)\n**\nPublic\n* [Notifications\n](<https://github.com/login?return_to=/
niltok64/the-useless-collection>) You must be signed in to change notification settings\n* [Fork\n0
\n...\n](<https://github.com/login?return_to=/niltok64/the-useless-collection>)\n*\n...\n[\nStar\n1
\n](<https://github.com/login?return_to=/niltok64/the-useless-collection>)\n*\nA collection of softwa
re that serve no purpose other than waste your time. Forking is encouraged!\n[niltok64.github.io/the-
useless-collection/](<https://niltok64.github.io/the-useless-collection/>)\n### License\n...\nstar\n]
(<https://github.com/niltok64/the-useless-collection/stargazers>) [\n0\n...\nforks\n](<https://githu
b.com/niltok64/the-useless-collection/forks>) [\nBranches\n](<https://github.com/niltok64/the-useless
-collection/branches>) [\nTags\n](<https://github.com/niltok64/the-useless-collection/tags>) [\nActiv
ity\n](<https://github.com/niltok64/the-useless-collection/activity>)\n[\n...\nA collection of softwa
re that serve no purpose other than waste your time. Forking is encouraged!\n...\nRequires Python 3.9
for building.\n## Usage\n...\nTo use on Unix-based systems run \"pyinstaller main.\n...\n--onefile
\"\n...\nline in any of the folders. You can also use the executables in\n...\n`automated-build/` fol
der to build all the programs at once.\n## About\nA collection of software that serve no purpose othe
r than waste your time. Forking is encouraged!\n[niltok64.github.io/the-useless-collection/](<http
s://niltok64.github.io/the-useless-collection/>)\n...\nuseless-collection/activity)\n...\n[\n**1**\ns
tar\n](<https://github.com/niltok64/the-useless-collection/stargazers>)\n### Watchers\n...\n[\n**1**
\n...\ncom/niltok64/the-useless-collection/watchers)\n...\n[\n**0**\nforks\n](<https://github.com/nil
tok64/the-useless-collection/forks>)\n[\n\n---\n\nTitle: erikvorhes/Useless-JS\nURL: <https://github.
com/erikvorhes/Useless-JS\nPublished:> N/A\nAuthor: N/A\nHighlights:\n# erikvorhes/Useless-JS\n...\nS
cripts that do nothing or overcomplicate things.\n...\n- Stars: 8\n- Forks: 3\n- Watchers: 8\n- Open
issues: 1\n- License: Do What The F*ck You Want To Public License\n- Default branch: master\n- Create
d: 2011-08-19T14:09:26Z\n\n##\n...\n# Useless JS\n...\nThis is a collection of scripts that don't rea
lly do anything useful.\n...\nI've created a couple script files to get us started. Please add your o
wn!\n\n---\n\nTitle: stac47/libuseless\nURL: <https://github.com/stac47/libuseless\nPublished:> N/A\n
Author: stac47\nHighlights:\n# stac47/libuseless\n...\nThe most useless C++ library in the world
\n...\n- Stars: 0\n- Forks: 0\n- Watchers: 0\n- Open issues: 0\n- License: GNU General Public License
v3.0\n- Default branch: main\n- Created: 2021-01-29T13:34:01Z\n...\nThe most useless C++ library in t
he world\n\n---\n\nTitle: jeku/useless\nURL: <https://github.com/jeku/useless\nPublished:> 2012-08-02
T14:05:01.000Z\nAuthor: jeku\nHighlights:\n# Repository: jeku/useless\n\nquite useless\n...\n- Stars:
1\n- Forks: 0\n- Watchers: 1\n- Open issues: 0\n- Default branch: master\n- Created: 2012-08-02T14:0
5:01Z\n- Last push: 2012-08-02T14:05:01Z\n- Contributors: 1 (top: jeku)\n\n---\n\nTitle: barelyhuman/
useless\nURL: <https://github.com/barelyhuman/useless\nPublished:> N/A\nAuthor: N/A\nHighlights:\n# b
arelyhuman/useless\n...\nA set of useless utilities for javascript\n...\n- Stars: 6\n- Forks: 0\n- Wa
tchers: 6\n- Open issues: 0\n- License: MIT License\n- Default branch: dev\n- Created: 2022-04-14T08:
52:31Z\n\n## Languages\n...\n## README\n\n \n \n A set of useless utilities for javascript \n\n## Doc
umentation\n...\nalready exist somewhere\n...\nand I'm just writing\n...\nto feel good about having m
y own set of utilties.",
"_meta": {
"searchTime": 1216.7
}
}
]
},
"jsonrpc": "2.0",
"id": 3
}
```

View File

@@ -0,0 +1,14 @@
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}

View File

@@ -0,0 +1,27 @@
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"prompts": {
"listChanged": true
},
"resources": {
"listChanged": true
},
"logging": {}
},
"serverInfo": {
"name": "searchapi",
"title": "SearchAPI MCP Server",
"version": "1.0.0",
"websiteUrl": "<https://www.searchapi.io>"
},
"instructions": "Use the available tools to get real-time search results. Each tool corresponds to a specific use case, which may be a full search engine or a specialized feature of an engine."
}
}

View File

@@ -0,0 +1,13 @@
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "youtube_search",
"arguments": {
"q": "EBS 공식 유튜브 채널",
"gl": "KR"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"search_metadata\": {\n \"id\": \"search_Z18NJlvnOYI636jLXk3BOm7g\",\n \"status\": \"Success\",\n \"created_at\": \"2026-07-13T17:02:43Z\",\n \"request_time_taken\": 1.12,\n \"parsing_time_taken\": 0.04,\n \"total_time_taken\": 1.15,\n \"request_url\": \"<https://www.youtube.com/results?search_query=EBS+%EA%B3%B5%EC%8B%9D+%EC%9C%A0%ED%8A%9C%EB%B8%8C+%EC%B1%84%EB%84%90&gl=KR&hl=en\>"
}
]
}
}

View File

@@ -0,0 +1,174 @@
# 관찰 로그 04 - searchapi / `youtube_search`
- 출처: `MCP_툴_호출_통신_로그_(JSON-RPC) (1).pdf`
- 원본 생성 시각: 2026-07-13 17:10:33 UTC
- 성격: Agent Builder 솔루션에서 수행한 관찰용 실행 로그. 본 문서는 구현 계약이나 테스트 fixture가 아니다.
## 헤더 및 실행 메타데이터
| 항목 | 원본 기록 값 |
|---|---|
| transport type | `mcp-http` |
| request_id | `2ab618c9-8f2f-4163-ad11-d7118bc4d3ae` |
| tool_uid | `28553975-d471-4b67-a210-61cc636d537d` |
| Tool version | `2.0` |
| 실행 시각 | 2026-07-13 17:02:42 UTC |
| 결과 | ok · 2200ms · truncated=False |
| JSON-RPC 프레임 수 | 4 |
| lifecycle | `initialize → tools/call` |
### HTTP 헤더 기록 범위
원본 PDF에는 개별 HTTP request/response header 값이 기록되어 있지 않다. 원본 표의 `request_id`, `tool_uid`, `version`은 실행 메타데이터이며 HTTP header라고 단정하지 않는다.
원본 공통 안내에는 시크릿 및 인증 header가 로깅 전에 `[REDACTED]`로 마스킹되었다고 명시되어 있다. 따라서 Authorization, `Mcp-Session-Id` 등 실제 HTTP header 값은 이 문서에 포함하지 않는다.
## JSON-RPC 통신 전문
아래는 원본 PDF의 JSON-RPC 프레임을 순서대로 옮긴 전사본이다. 긴 `result.content[].text` 문자열은 PDF의 시각적 줄바꿈을 보존했으므로, 이 블록 전체를 기계 실행용 단일 JSON payload로 사용하지 않는다.
> 원본 PDF 자체가 `tools/call` 성공 응답의 끝부분을 `[+22537B]>`로 접어 표시한다. 따라서 이 문서는 PDF에 표시된 전문을 보존한 것이며, 접힌 22,537바이트의 원본 응답 데이터를 복원한 것은 아니다.
```text
17:02:42 요청 → initialize
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}
17:02:42 ← 응답 initialize
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"prompts": {
"listChanged": true
},
"resources": {
"listChanged": true
},
"logging": {}
},
"serverInfo": {
"name": "searchapi",
"title": "SearchAPI MCP Server",
"version": "1.0.0",
"websiteUrl": "<https://www.searchapi.io>"
},
"instructions": "Use the available tools to get real-time search results. Each tool corresponds t
o a specific use case, which may be a full search engine or a specialized feature of an engine."
}
}
17:02:42 요청 → tools/call
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "youtube_search",
"arguments": {
"q": "EBS 공식 유튜브 채널",
"gl": "KR"
}
}
}
17:02:44 ← 응답 tools/call
{"jsonrpc": "2.0", "id": 3, "result": {"content": [{"type": "text", "text": "{\n \"search_metadata
\": {\n \"id\": \"search_Z18NJlvnOYI636jLXk3BOm7g\",\n \"status\": \"Success\",\n \"created_
at\": \"2026-07-13T17:02:43Z\",\n \"request_time_taken\": 1.12,\n \"parsing_time_taken\": 0.0
4,\n \"total_time_taken\": 1.15,\n \"request_url\": \"<https://www.youtube.com/results?search_q
uery=EBS+%EA%B3%B5%EC%8B%9D+%EC%9C%A0%ED%8A%9C%EB%B8%8C+%EC%B1%84%EB%84%90&gl=KR&hl=en\>",\n \"htm
l_url\": \"<https://www.searchapi.io/api/v1/searches/search_Z18NJlvnOYI636jLXk3BOm7g.html\>",\n
\"json_url\": \"<https://www.searchapi.io/api/v1/searches/search_Z18NJlvnOYI636jLXk3BOm7g\>"\n },\n
\"search_parameters\": {\n \"engine\": \"youtube\",\n \"q\": \"EBS 공식 유튜브 채널\",\n \"hl
\": \"en\",\n \"gl\": \"KR\"\n },\n \"search_information\": {\n \"total_results\": 6938629\n
},\n \"channels\": [\n {\n \"position\": 1,\n \"id\": \"UCFCtZJTuJhE18k8IXwmXTYQ\",\n
\"title\": \"EBS Documentary\",\n \"link\": \"<https://www.youtube.com/@EBSDocumentary\>",\n
\"description\": \"EBS offers a wide range of high-quality documentaries\\ndealing with subjects, suc
h as knowledge, science, culture and others.\",\n \"is_verified\": true,\n \"subscribers\":
5410000.0,\n \"thumbnail\": {\n \"static\": \"<https://yt3.ggpht.com/JCoc_tttfLn5nzycdjiS
Fwv0gg-AKMIU2pyLw_13CzftUAchPNt4pJ8sHymyp_PcpF9KqSH-yIQ=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"r
ich\": \"<https://yt3.ggpht.com/JCoc_tttfLn5nzycdjiSFwv0gg-AKMIU2pyLw_13CzftUAchPNt4pJ8sHymyp_PcpF9Kq
SH-yIQ=s176-c-k-c0x00ffffff-no-rj-mo\>"\n }\n },\n {\n \"position\": 2,\n \"id
\": \"UC-swf20n5xdKW0waeLpXPFQ\",\n \"title\": \"EBS\",\n \"link\": \"<https://www.youtube.
com/@EBS_story\>",\n \"description\": \"EBS 공식 유튜브 채널 EBS 입니다. 언제나 여러분 곁엔 EBS가 그리
고 언제나 EBS 곁엔 여러분이!\",\n \"subscribers\": 698000,\n \"thumbnail\": {\n \"stati
c\": \"<https://yt3.ggpht.com/k1Z1iQQcdGXiByVcoCCGEwQtx803bSIMOhDOEmLwAexwVmkd6EeZNg__BcDIZ8B_5-jURP_
9bg=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"rich\": \"<https://yt3.ggpht.com/k1Z1iQQcdGXiByVcoCCG
EwQtx803bSIMOhDOEmLwAexwVmkd6EeZNg__BcDIZ8B_5-jURP_9bg=s176-c-k-c0x00ffffff-no-rj-mo\>"\n }\n
},\n {\n \"position\": 4,\n \"id\": \"UCNjQBiTSdoj2tCQLBGXFksw\",\n \"title\": \"EB
S 라디오 공식 채널\",\n \"link\": \"<https://www.youtube.com/@ebsradio\>",\n \"description\":
\"책 읽어주는 라디오 + 외국어 라디오 (서울 수도권 104.5MHz) 한국교육방송 EBS 라디오 공식 운영 채널 입니다.\",\n
\"subscribers\": 208000,\n \"thumbnail\": {\n \"static\": \"<https://yt3.googleuserconten
t.com/ytc/AIdro_kWu2E5kc1Es_euuO3CNfx6R3-JBcB-eLaX1tAEd20yGew=s88-c-k-c0x00ffffff-no-rj-mo\>",\n
\"rich\": \"<https://yt3.googleusercontent.com/ytc/AIdro_kWu2E5kc1Es_euuO3CNfx6R3-JBcB-eLaX1tAEd20yGe
w=s176-c-k-c0x00ffffff-no-rj-mo\>"\n }\n },\n {\n \"position\": 6,\n \"id\": \"U
CL44YGs2BXxtM4GZRH-wnrQ\",\n \"title\": \"EBS 지식채널e\",\n \"link\": \"<https://www.youtub
e.com/@ebs_jisike\>",\n \"description\": \"우리 삶 속에서 잠깐, 5분 의미있는 순간으로 만드는 채널 공유하
고 쌓여가는 지식 지식채널e 공식홈페이지 ...\",\n \"subscribers\": 147000,\n \"thumbnail\": {\n
\"static\": \"<https://yt3.ggpht.com/_nK3ujZE56iSp3jf35MUMgCXIlFY9Gshi7VdHxNBoUNvT-8Jv1IgsGBlf3yNfR7J
Qr-TxBScfg=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"rich\": \"<https://yt3.ggpht.com/_nK3ujZE56iSp
3jf35MUMgCXIlFY9Gshi7VdHxNBoUNvT-8Jv1IgsGBlf3yNfR7JQr-TxBScfg=s176-c-k-c0x00ffffff-no-rj-mo\>"\n
}\n },\n {\n \"position\": 8,\n \"id\": \"UCiFYUP4_TI70yCkkVJAlxoA\",\n \"title
\": \"EBS Collection - Nature\",\n \"link\": \"<https://www.youtube.com/@ebs.science\>",\n
\"description\": \"Chosen among more than 310,000 digitalized sources from EBS archive, EBS Collectio
n gives you exclusive access to intriguing ...\",\n \"subscribers\": 1340000.0,\n \"thumbna
il\": {\n \"static\": \"<https://yt3.googleusercontent.com/ytc/AIdro_m_xD2UFIA7qn07UeYtPAly1eP
mRdspeKkwIqhgdLjflwM=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"rich\": \"<https://yt3.googleusercon
tent.com/ytc/AIdro_m_xD2UFIA7qn07UeYtPAly1ePmRdspeKkwIqhgdLjflwM=s176-c-k-c0x00ffffff-no-rj-mo\>"\n
}\n },\n {\n \"position\": 9,\n \"id\": \"UCl_tB4AqPkkxuYcJQHz6dMw\",\n \"title
\": \"EBSCulture (EBS 교양)\",\n \"link\": \"<https://www.youtube.com/@EBSCulture\>",\n \"d
escription\": \"Knowledge and information are becoming more important in all areas of today's societ
y. EBS provides the information to make ...\",\n \"is_verified\": true,\n \"subscribers\":
3020000.0,\n \"thumbnail\": {\n \"static\": \"<https://yt3.googleusercontent.com/ytc/AIdr
o_nRo84wHIX9NhlqcW4f8Z4XptXjuHQD-xp3U_Dibgvxpn4=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"rich\":
\"<https://yt3.googleusercontent.com/ytc/AIdro_nRo84wHIX9NhlqcW4f8Z4XptXjuHQD-xp3U_Dibgvxpn4=s176-c-k
-c0x00ffffff-no-rj-mo\>"\n }\n },\n {\n \"position\": 10,\n \"id\": \"UCbeZPOz8u
aHstEIbkqBOnGg\",\n \"title\": \"EBS 세계테마기행\",\n \"link\": \"<https://www.youtube.com/@
ebstheme\>",\n \"description\": \"EBS 세계테마기행 공식 유튜브 채널 : <https://bit.ly/WorldTravel_EB
S> (구독하기) ⛵세계를 여행하며 만나는 다양한 문화와 풍경!\",\n \"subscribers\": 162000,\n \"thumb
nail\": {\n \"static\": \"<https://yt3.ggpht.com/s9JbLvTmFQuY-jP9R_2uRdkWz_YgEK3LYGLa5mdgldqhW
npGrLJMVJCgQOL_VCfBqb-T7_IWgA=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"rich\": \"<https://yt3.ggph
t.com/s9JbLvTmFQuY-jP9R_2uRdkWz_YgEK3LYGLa5mdgldqhWnpGrLJMVJCgQOL_VCfBqb-T7_IWgA=s176-c-k-c0x00ffffff
-no-rj-mo\>"\n }\n },\n {\n \"position\": 11,\n \"id\": \"UCuuA38hvvuipqXFIP3BfL
EA\",\n \"title\": \"EBS 지식\",\n \"link\": \"<https://www.youtube.com/@EBSKnowledge\>",\n
\"description\": \"일상 속 지식 한 스푼! 각 분야의 전문가로 구성된 연사들의 강연까지! EBS의 고품격 지식·강연 프로
그램 속 다양한 지식들을 큐레이팅 ...\",\n \"subscribers\": 298000,\n \"thumbnail\": {\n
\"static\": \"<https://yt3.ggpht.com/E2hBI06f7Y9cWzG5EcQOzPGtLmhj_2IH3AGcqdTj4OI7Jiz8dUwtQOZyHA5kqIbG
cxw9hr08dQ=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"rich\": \"<https://yt3.ggpht.com/E2hBI06f7Y9cW
zG5EcQOzPGtLmhj_2IH3AGcqdTj4OI7Jiz8dUwtQOZyHA5kqIbGcxw9hr08dQ=s176-c-k-c0x00ffffff-no-rj-mo\>"\n
}\n },\n {\n \"position\": 13,\n \"id\": \"UC2dDb6up1sIc-5geTvNtgHA\",\n \"title
\": \"EBS 최고의 요리비결\",\n \"link\": \"<https://www.youtube.com/@EBS_best.cooking.secrets
\>",\n \"description\": \"이 채널은 \\\"EBS 최고의 요리비결\\\" 공식 유튜브 채널입니다. EBS 최고의 요리비
결은 지난 20년 동안 요리의 대가들이 출연하여 누구나 ...\",\n \"subscribers\": 104000,\n \"thumbnai
l\": {\n \"static\": \"<https://yt3.googleusercontent.com/ytc/AIdro_mpiFqITW0McZ_0lY7r80ltEfM3
hz5srioAQjdI0Jxizw=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"rich\": \"<https://yt3.googleuserconte
nt.com/ytc/AIdro_mpiFqITW0McZ_0lY7r80ltEfM3hz5srioAQjdI0Jxizw=s176-c-k-c0x00ffffff-no-rj-mo\>"\n
}\n },\n {\n \"position\": 14,\n \"id\": \"UC2DBKKki_gtyrrgT7HYU1CA\",\n \"title
\": \"EBS 국제다큐영화제 공식 채널\",\n \"link\": \"<https://www.youtube.com/@EIDFOFFICIAL\>",\n
\"description\": \"EBS국제다큐영화제 EIDF 공식 Youtube 채널입니다. This is the official YouTube EIDF Chan
nel.\",\n \"subscribers\": 10100.0,\n \"thumbnail\": {\n \"static\": \"<https://yt3.
googleusercontent.com/ytc/AIdro_nglXasX2K52bUhcEmGgttfBh7no_eQcDperw-a5E8sJ6U=s88-c-k-c0x00ffffff-no-
rj-mo\>",\n \"rich\": \"<https://yt3.googleusercontent.com/ytc/AIdro_nglXasX2K52bUhcEmGgttfBh7
no_eQcDperw-a5E8sJ6U=s176-c-k-c0x00ffffff-no-rj-mo\>"\n }\n },\n {\n \"position\": 1
5,\n \"id\": \"UCN3RfsR18gsH8PLI6R6PYSQ\",\n \"title\": \"EBS뉴스\",\n \"link\": \"<ht
tps://www.youtube.com/@EBSNewsG\>",\n \"description\": \"한국교육방송공사 EBS 뉴스 채널입니다. EBS 뉴
스의 TV 방송시간은 월~금 낮 12시, 저녁 6시 10분입니다. Republic of Korea's ...\",\n \"subscribers\": 5
9900.0,\n \"thumbnail\": {\n \"static\": \"<https://yt3.ggpht.com/J5dpRSp4ROHG5T0zUdZPvl2
SrNXUyObe68rfiVid5i6XnN7zF43M36qekBESigoXqhN-hoA9dA=s88-c-k-c0x00ffffff-no-rj-mo\>",\n \"rich
\": \"<https://yt3.ggpht.com/J5dpRSp4ROHG5T0zUdZPvl2SrNXUyObe68rfiVid5i6XnN7zF43M36qekBESigoXqhN-ho…
[+22537B]>
```

View File

@@ -0,0 +1,14 @@
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}

View File

@@ -0,0 +1,30 @@
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"experimental": {},
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": false,
"listChanged": true
},
"tools": {
"listChanged": true
},
"extensions": {
"io.modelcontextprotocol/ui": {}
}
},
"serverInfo": {
"name": "langconnect-rag-mcp",
"version": "3.3.1"
},
"instructions": "This server provides RAG tools over document collections. Call list_collections() first: it returns collections you can access plus per-collection role and allowed_tools (derived from RBAC scopes). Use search_documents/search_documents_batch/search_documents_with_context for retrieval. Use list_documents to inspect documents. Use add_document_text to add text. Use delete_document/delete_collection only when allowed_tools includes them. Call get_user_info() to get the current user's email and display name."
}
}

View File

@@ -0,0 +1,5 @@
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}

View File

@@ -0,0 +1,10 @@
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "list_collections",
"arguments": {}
}
}

View File

@@ -0,0 +1,40 @@
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"data\":[{\"name\":\"test\",\"id\":\"1caf198a-444b-4a89-a235-23e82bc3d68a\",\"metadata\":{\"description\":\"col\"},\"role\":0,\"allowed_tools\":[\"add_document_text\",\"delete_collection\",\"delete_document\",\"list_documents\",\"search_documents\",\"search_documents_batch\",\"search_documents_with_context\"],\"document_count\":2,\"chunk_count\":119}],\"success\":true,\"error\":null,\"request_id\":\"b3fa0787-dce3-449b-aff0-51a8d882e81d\"}"
}
],
"structuredContent": {
"data": [
{
"name": "test",
"id": "1caf198a-444b-4a89-a235-23e82bc3d68a",
"metadata": {
"description": "col"
},
"role": 0,
"allowed_tools": [
"add_document_text",
"delete_collection",
"delete_document",
"list_documents",
"search_documents",
"search_documents_batch",
"search_documents_with_context"
],
"document_count": 2,
"chunk_count": 119
}
],
"success": true,
"error": null,
"request_id": "b3fa0787-dce3-449b-aff0-51a8d882e81d"
},
"isError": false
}
}

View File

@@ -0,0 +1,140 @@
# 관찰 로그 05 - langconnect / `list_collections`
- 출처: `MCP_툴_호출_통신_로그_(JSON-RPC) (1).pdf`
- 원본 생성 시각: 2026-07-13 17:10:33 UTC
- 성격: Agent Builder 솔루션에서 수행한 관찰용 실행 로그. 본 문서는 구현 계약이나 테스트 fixture가 아니다.
## 헤더 및 실행 메타데이터
| 항목 | 원본 기록 값 |
|---|---|
| transport type | `mcp-http` |
| request_id | `ac3d3c06-882e-4416-be34-a20c5f91662f` |
| tool_uid | `636e8cd0-3114-467f-b305-14e2c9cda744` |
| Tool version | `1.0` |
| 실행 시각 | 2026-07-13 17:08:46 UTC |
| 결과 | ok · 346ms · truncated=False |
| JSON-RPC 프레임 수 | 5 |
| lifecycle | `initialize → notifications/initialized → tools/call` |
### HTTP 헤더 기록 범위
원본 PDF에는 개별 HTTP request/response header 값이 기록되어 있지 않다. 원본 표의 `request_id`, `tool_uid`, `version`은 실행 메타데이터이며 HTTP header라고 단정하지 않는다.
원본 공통 안내에는 시크릿 및 인증 header가 로깅 전에 `[REDACTED]`로 마스킹되었다고 명시되어 있다. 따라서 Authorization, `Mcp-Session-Id` 등 실제 HTTP header 값은 이 문서에 포함하지 않는다.
## JSON-RPC 통신 전문
아래는 원본 PDF의 JSON-RPC 프레임을 순서대로 옮긴 전사본이다. 긴 `result.content[].text` 문자열은 PDF의 시각적 줄바꿈을 보존했으므로, 이 블록 전체를 기계 실행용 단일 JSON payload로 사용하지 않는다.
```text
17:08:46 요청 → initialize
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "toolbox-executor",
"version": "0.1.0"
}
}
}
17:08:46 ← 응답 initialize
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"experimental": {},
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": false,
"listChanged": true
},
"tools": {
"listChanged": true
},
"extensions": {
"io.modelcontextprotocol/ui": {}
}
},
"serverInfo": {
"name": "langconnect-rag-mcp",
"version": "3.3.1"
},
"instructions": "This server provides RAG tools over document collections. Call list_collections
() first: it returns collections you can access plus per-collection role and allowed_tools (derived f
rom RBAC scopes). Use search_documents/search_documents_batch/search_documents_with_context for retri
eval. Use list_documents to inspect documents. Use add_document_text to add text. Use delete_documen
t/delete_collection only when allowed_tools includes them. Call get_user_info() to get the current us
er's email and display name."
}
}
17:08:46 요청 → notifications/initialized
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
17:08:46 요청 → tools/call
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "list_collections",
"arguments": {}
}
}
17:08:47 ← 응답 tools/call
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"data\":[{\"name\":\"test\",\"id\":\"1caf198a-444b-4a89-a235-23e82bc3d68a\",\"meta
data\":{\"description\":\"col\"},\"role\":0,\"allowed_tools\":[\"add_document_text\",\"delete_collect
ion\",\"delete_document\",\"list_documents\",\"search_documents\",\"search_documents_batch\",\"search
_documents_with_context\"],\"document_count\":2,\"chunk_count\":119}],\"success\":true,\"error\":nul
l,\"request_id\":\"b3fa0787-dce3-449b-aff0-51a8d882e81d\"}"
}
],
"structuredContent": {
"data": [
{
"name": "test",
"id": "1caf198a-444b-4a89-a235-23e82bc3d68a",
"metadata": {
"description": "col"
},
"role": 0,
"allowed_tools": [
"add_document_text",
"delete_collection",
"delete_document",
"list_documents",
"search_documents",
"search_documents_batch",
"search_documents_with_context"
],
"document_count": 2,
"chunk_count": 119
}
],
"success": true,
"error": null,
"request_id": "b3fa0787-dce3-449b-aff0-51a8d882e81d"
},
"isError": false
}
}
```

View File

@@ -0,0 +1,30 @@
# Agent Builder Tool 호출 관찰 로그
- 출처: `MCP_툴_호출_통신_로그_(JSON-RPC) (1).pdf`
- 원본 생성 시각: 2026-07-13 17:10:33 UTC
- 목적: Agent Builder 솔루션이 실제 Tool을 호출하며 남긴 JSON-RPC 흐름을 샘플별로 보존
- 성격: 관찰 기록. 현재 구현 계약, 목표 계약 또는 자동화 테스트 fixture로 사용하지 않는다.
원본 PDF는 시크릿 및 인증 header를 `[REDACTED]` 처리했다고 명시한다. 개별 HTTP request/response header 자체는 제공하지 않으므로, 각 문서에는 원본에 기록된 실행 메타데이터와 header 기록 범위를 분리해 표시한다.
| # | 서버 | Tool | 시각(UTC) | 프레임 | 결과 | 파일 |
|---:|---|---|---|---:|---|---|
| 1 | exa | `web_search_exa` | 16:53:16 | 5 | ok · 1896ms | [01](01-exa-web-search-exa-165316.md) |
| 2 | exa | `web_search_exa` | 16:53:24 | 5 | ok · 1768ms | [02](02-exa-web-search-exa-165324.md) |
| 3 | exa | `web_search_exa` | 16:53:31 | 5 | ok · 2070ms | [03](03-exa-web-search-exa-165331.md) |
| 4 | searchapi | `youtube_search` | 17:02:42 | 4 | ok · 2200ms | [04](04-searchapi-youtube-search.md) |
| 5 | langconnect | `list_collections` | 17:08:46 | 5 | ok · 346ms | [05](05-langconnect-list-collections.md) |
## JSON 프레임 파일
각 요청과 응답은 아래의 독립 JSON 파일로 분리했다. PDF의 시각적 줄바꿈을 제거한 뒤 JSON 문법 검증을 통과한 프레임만 원래 JSON-RPC 구조로 저장했다.
- [01 Exa](01-exa-web-search-exa-165316-initialize-request.json): `initialize` 요청·응답, `notifications/initialized`, `tools/call` 요청·응답
- [02 Exa](02-exa-web-search-exa-165324-initialize-request.json): `initialize` 요청·응답, `notifications/initialized`, `tools/call` 요청·응답
- [03 Exa](03-exa-web-search-exa-165331-initialize-request.json): `initialize` 요청·응답, `notifications/initialized`, `tools/call` 요청·응답
- [04 SearchAPI](04-searchapi-youtube-search-initialize-request.json): `initialize` 요청·응답, `tools/call` 요청, `tools/call` 응답의 PDF 표시 전사본
- [05 LangConnect](05-langconnect-list-collections-initialize-request.json): `initialize` 요청·응답, `notifications/initialized`, `tools/call` 요청·응답
SearchAPI의 `tools/call` 응답은 원본 PDF가 마지막 22,537바이트를 접어 표시하므로,
[visible transcript](04-searchapi-youtube-search-tools-call-response-visible-transcript.txt)로 저장했다.
끝부분이 없는 전사본이므로 실행 가능한 JSON-RPC 응답이나 `.json` fixture로 취급하지 않는다.

View File

@@ -0,0 +1,44 @@
# Agent Builder-MCP 이전 non-streaming 계약 v0.2
> 상태: v0.3으로 대체됨. 이 문서는 streaming 정책 도입 전 계약 기록이며, 신규 연동에는 사용하지 않는다.
- 상태: Superseded
- 기준일: 2026-07-16
- 구현 endpoint: `POST /mcp`
- JSON-RPC: `2.0`
- protocolVersion: `2025-06-18`
> 이 문서는 교체된 고정 endpoint 시점의 이력이다. 현재 공개 URL과 path 처리는 [v0.3](protocol-v0.3-streaming-policy.md)과 [ADR-0009](../../decisions/ADR-0009-container-handles-public-mcp-path.md)을 따른다.
이 계약은 Agent Builder에서 받은 JSON-RPC/MCP 호출·응답 예시를 현재 stateless MCP 실행 계층에 반영한 범위다. 서버는 `mcp-session-id`를 요청 correlation에만 사용하며 `Mcp-Session-Id`를 발급하거나 세션 상태를 저장하지 않는다.
## 범위와 HTTP 정책
- 이 버전은 non-streaming 요청에 한정한다. 호출 클라이언트는 `Accept: application/json`을 사용한다.
- 기존 NDJSON streaming 구현과 progress frame은 변경하지 않았다. streaming의 SSE/재개/cancel 계약은 별도 승인 후 반영한다.
- `Content-Type: application/json` 또는 `application/json-rpc`을 사용한다.
- `notifications/initialized`는 Agent Builder가 stateless 서버에 대해 생략할 수 있다. 전송될 경우 표준 lifecycle을 수용하고 HTTP `202 Accepted`와 빈 body를 반환한다.
## initialize
Agent Builder는 연결 초기화 시 [요청 예시](examples/agentbuilder-v0.2/initialize-request.json)를 전송한다. 응답은 [응답 예시](examples/agentbuilder-v0.2/initialize-response.json)처럼 `jsonrpc`, `id`, `result.protocolVersion`만 의미 있는 값을 가진다. `serverInfo``capabilities`는 빈 객체다.
서버는 protocol version으로 `2025-06-18`을 반환한다. 현재 `MCP-Protocol-Version` HTTP 헤더의 수신·검증은 범위 밖이다.
## notifications/initialized
초기화 완료 notification의 body는 [예시](examples/agentbuilder-v0.2/initialized-notification.json)와 같다. JSON-RPC notification에는 `id`가 없으며, 서버는 실행 결과 JSON-RPC body를 만들지 않는다. Agent Builder가 stateless 정책으로 notification을 보내지 않아도 Tool 호출은 가능하다.
## tools/list
`tools/list` handler는 계속 제공한다. Agent Builder가 사전 등록한 Tool만 사용할 때는 이 호출을 생략할 수 있다. 현재 pagination과 `listChanged` notification은 제공하지 않는다.
## tools/call
호출 request는 [예시](examples/agentbuilder-v0.2/tools-call-request.json)처럼 `params.name`과 object 형식의 `params.arguments`를 사용한다. version은 Agent Builder가 보내지 않는다. 서버는 Registry에서 같은 name의 활성 version이 정확히 하나일 때만 이를 해소해 실행한다. 두 개 이상이면 임의 version을 선택하지 않고 오류로 처리한다.
성공 응답은 [예시](examples/agentbuilder-v0.2/tools-call-success-response.json)처럼 `content`, 선택적 `structuredContent`, `isError: false`를 반환한다. Tool endpoint 실행·timeout·권한 오류는 [예시](examples/agentbuilder-v0.2/tools-call-execution-error-response.json)처럼 HTTP/JSON-RPC transport error 대신 `result.isError: true`로 반환한다. 잘못된 JSON-RPC envelope, 알 수 없는 method, 잘못된 name/arguments는 기존 JSON-RPC `error`를 사용한다.
## 호환성 메모
- UID execution key 검토안은 [ADR-0005](../../decisions/ADR-0005-standard-tool-name.md)에서 폐기되었다. 신규 계약은 표준 MCP `name`을 실행 식별자로 사용한다.

View File

@@ -0,0 +1,82 @@
# 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-06-18`
이 계약의 현재 구현은 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에는 영향을 주지 않는다.
## 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-06-18`이 필수다. `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-06-18`, `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-06-18 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-06-18의 구조화 출력 계약을 완전히 충족하지 않는다. 운영 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는 계속 수용한다.

View File

@@ -0,0 +1,56 @@
# Agent Builder-MCP 목표 계약 v1 합의 기준선
- 상태: Partial Agreement
- 합의 기준일: 2026-07-10
- 목적: 확정된 목표 제약만 기록하고 미확정 payload의 추측 구현을 방지
## Normative 합의 사항
### 책임 경계
- Agent Builder가 Tool을 선택한다.
- MCP Server는 요청에 명시된 Tool만 검증·실행한다.
- MCP Server는 stateless하며 `mcp-session-id`를 대화 상태로 사용하지 않는다.
### Tool 노출과 호출 단위
- Agent Builder가 LLM에 노출하는 Tool은 최대 50개다.
- Agent Builder-MCP 목표 호출은 요청 하나당 Tool 하나다.
### Tool 식별
- 표준 MCP Tool `name``tools/list``tools/call` 사이의 시스템 간 실행 key로 사용한다.
- Tool Service가 전체 MCP 범위에서 고유한 namespaced name을 선언하고 MCP Server는 이를 재작성하지 않는다.
- Agent Builder UID는 Agent Builder 내부 관리에만 사용하고 MCP wire contract에 포함하지 않는다.
### 실행 가드레일
- 전체 상호작용 상한은 300초다.
- MCP Server는 3만 자 기준으로 원문 응답을 임의 절단하지 않는다.
- idempotency가 확인되지 않은 write/update Tool은 자동 retry하지 않는다.
## 아직 Normative가 아닌 항목
다음 항목은 목표 v1의 일부로 구현하면 안 된다.
- 최종 method 이름 (`tool/call`, `tools/call` 또는 별도 vendor method)
- 최종 Tool Call parameter와 result field 이름
- `initialize` 전체 request/response
- Tool name 변경·폐기 시 rolling 호환 기간과 Agent Builder 내부 mapping lifecycle
- 사원 식별자 header를 언젠가 필수로 승격할지 여부 (현재 계약에서는 선택값이며 MCP는 검증하지 않는다)
- HTTP status와 JSON-RPC/업무 error code 매핑
- read retry 조건과 idempotency key
- 300초 timeout budget과 cancellation protocol
- 대용량 response의 최대 크기와 continuation 방식
- streaming media type과 framing
## 요청·응답 규격 처리 원칙
목표 v1의 확정 JSON 예시는 아직 제공하지 않는다. 미확정 field를 임의로 채운 JSON은 사실상 새로운 계약 결정이 되기 때문이다.
Agent Builder의 전체 payload와 위 미확정 항목이 승인되면 다음 순서로 갱신한다.
1. 이 문서 상태를 `Approved`로 변경한다.
2. 목표 v1 JSON example과 schema를 추가한다.
3. 관련 ADR을 추가하거나 기존 ADR의 구현 보류 조건을 해제한다.
4. 코드와 테스트 변경을 별도 작업으로 수행한다.

View File

@@ -0,0 +1,39 @@
# Tool Service-MCP 계약 문서
이 디렉터리는 Tool Service와 MCP Server 사이의 metadata 조회·실행 계약을 관리한다.
```text
Agent Builder ──[agent-builder-mcp 계약]──▶ MCP Server ──[tool-service-mcp 계약]──▶ Tool Service
```
| 문서 | 상태 | 용도 |
|---|---|---|
| [protocol-v0.2-bundle-discovery.md](protocol-v0.2-bundle-discovery.md) | Implemented | Tool Service Bundle의 매니페스트 조회·실행 계약. 구현은 N개 Bundle을 지원하지만 운영 배포는 1개로 고정 |
| [TEMP-tool-list-loading-guide.md](TEMP-tool-list-loading-guide.md) | Temporary | Tool 개발 파트가 현재 최초 적재·memory snapshot·`tools/list` 변환 흐름을 이해하기 위한 안내 |
push 등록 방식(v0.1)은 채택하지 않았다. 그 이유는
[v0.2 §2](protocol-v0.2-bundle-discovery.md#2-왜-조회-방식인가-왜-기동-시-1회가-아닌가)에 있다.
## 현재 원칙
- 운영 Tool metadata의 유일한 원천은 각 Tool Service의 매니페스트다.
- `local` profile은 Tool Service 매니페스트를 먼저 조회하고, 최초 실패 시 `config/local-core-tools-manifest-sample-v1.json` fallback을 사용한다.
- 표준 MCP `name``tools/list``tools/call`의 실행 식별자다. Agent Builder UID는 이 계약에 포함하지 않는다.
- Tool Service는 표준 MCP `name`을 선언한다. MCP는 자기 Bundle 안에서 형식·접두사·중복을 검증하며, 서로 다른 MCP 배포 간 전역 유일성은 Tool Service·플랫폼의 변경 절차로 보장한다.
- MCP는 요청 경로에서 in-memory snapshot만 읽는다. Redis는 선택적인 공유 last-good cache다.
- 조회 실패는 Tool 삭제가 아니다. 성공한 매니페스트가 Tool을 제외했을 때만 삭제를 반영한다.
- 불완전한 aggregate, 중복 name, 총량 상한 초과는 현재 snapshot을 교체하지 않는다.
## 예제와 검증
[examples/bundle-v0.2](examples/bundle-v0.2/)의 매니페스트, MCP 설정, Actuator 상태 응답을 계약 테스트가 직접 읽는다.
예제와 구현은 같은 변경에서 함께 수정한다.
운영 적용 전에 Tool 개발 파트와 다음 항목을 확정한다.
1. MCP → Tool 방향 NetworkPolicy와 매니페스트 인증 방식
2. Tool name 변경·폐기 시 rolling 호환 기간
3. `namePrefix`, Tool 수, 매니페스트 크기 상한
4. Tool Service별 timeout과 권한 scope
상세 필드와 장애 처리는 [v0.2 계약](protocol-v0.2-bundle-discovery.md)을 따른다.

View File

@@ -0,0 +1,227 @@
# 임시 안내: Tool 목록 최초 적재와 `tools/list` 노출 흐름
> 상태: **임시 학습 문서** · 기준: 현재 MCP 서버 구현 · 대상: Tool Service 개발 파트
>
> 이 문서는 현재 동작을 이해하기 위한 안내다. 외부 wire 계약의 정본은
> [Tool Service-MCP Bundle 조회 계약 v0.2](protocol-v0.2-bundle-discovery.md)다.
## 먼저 구분할 것
Tool Service가 MCP 표준 `tools/list`를 직접 구현하는 구조가 아니다. Tool Service는 아래의 내부
**매니페스트 endpoint**를 제공하고, MCP Server가 이를 읽어 Agent Builder용 표준 `tools/list` 응답으로
변환한다.
```text
Tool Service -- GET /tool-manifest --> MCP Server -- JSON-RPC tools/list --> Agent Builder
```
현재 운영 배포는 MCP 하나가 Tool Service Bundle 하나를 본다. 구현은 호환 목적으로 여러 Bundle의
병합도 지원하지만, Tool 개발 파트는 자기 Bundle 하나의 매니페스트만 제공하면 된다.
## 1. 최초 적재는 구현되어 있는가?
**구현되어 있다.** Spring 애플리케이션이 준비되면 `ToolRegistryRefreshScheduler.preload()`가 실행된다.
```text
ApplicationReadyEvent
-> 선택 Redis snapshot warm start (있으면 memory에 임시 적재)
-> Tool Service manifest 즉시 조회
-> 검증 성공한 전체 Tool 목록으로 memory snapshot 교체
-> 선택 Redis cache 저장
-> readiness 판단 가능
```
Redis는 선택 cache일 뿐이다. Redis가 없거나 실패해도 Tool Service 매니페스트 조회가 성공하면 정상
기동한다. 반대로 최초 조회와 선택 cache 모두 실패하면 애플리케이션 프로세스는 살아 있어도 usable
Tool 목록이 없으므로 readiness는 DOWN이다. 다음 주기 조회에서 자동 재시도한다.
`tools/list` 요청이 기동 preload보다 먼저 들어와 memory snapshot이 비어 있으면, 요청 경로도 원천을
한 번 직접 조회해 cold start 공백을 메운다.
## 2. Tool Service에서 memory까지의 처리 순서
```text
ToolBundleRegistryClient.fetchTools()
-> ToolBundleDiscovery.discoverAll()
-> GET {manifestUrl}
-> bundleId / tools[] / Tool 필수 필드 검증
-> ToolMetadata 생성 (endpoint는 MCP 배포 설정의 baseEndpoint 사용)
-> enabled=false Tool 제외
-> immutable List<ToolMetadata>를 AtomicReference snapshot에 저장
```
실제 memory 저장소는 `ToolRegistryService``AtomicReference<List<ToolMetadata>>`다.
- 매니페스트 조회·검증에 **성공했을 때만** 새 immutable 목록으로 통째로 교체한다.
- HTTP 오류, timeout, JSON 오류, 필수 필드 누락, 이름 규칙 위반은 기존 snapshot을 비우지 않는다.
- Tool 하나만 걸러서 부분 반영하지 않는다. 매니페스트 하나가 잘못되면 해당 Bundle 전체를 거부한다.
- 현재 운영은 Bundle 하나지만, 구현상 여러 Bundle이면 모두 사용 가능한 성공본이 있을 때만 하나의 snapshot을 교체한다.
- 주기 refresh가 겹치면 single-flight로 하나의 원천 조회를 공유한다.
## 3. Agent Builder의 `tools/list` 요청은 어떻게 처리되는가?
Agent Builder는 공개 `POST https://{mcpHost}{publicPath}`로 JSON-RPC 요청을 보낸다. OpenShift Route는
해당 path의 MCP Service만 선택하고, 컨테이너가 같은 `POST {publicPath}`를 직접 처리한다.
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}
```
처리 경로는 다음과 같다.
```text
McpController
-> McpMethodHandlerRegistry
-> ToolsListHandler
-> ToolRegistryService.listTools()
-> in-memory snapshot 읽기
-> MCP SDK ListToolsResult 변환
-> JSON-RPC result.tools 반환
```
memory snapshot이 이미 있으면 `tools/list`는 Tool Service나 Redis를 호출하지 않는다. 따라서 Tool
Service가 잠시 느리거나 Redis가 장애여도 이미 적재한 목록은 바로 반환한다.
`ToolsListHandler`는 매니페스트 Tool 정의의 공개 필드만 MCP Tool로 만든다. `_meta` 안의
`version`, `timeoutMillis`, `enabled`와 MCP 내부의 `endpoint`는 절대 Agent Builder에 노출하지 않는다.
```json
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "processing.contract.inquiry",
"title": "계약 조회",
"description": "계약번호로 계약의 기본 정보를 조회합니다.",
"inputSchema": {
"type": "object",
"properties": {
"contractNo": { "type": "string" }
},
"required": ["contractNo"]
},
"annotations": {
"readOnlyHint": true
}
}
]
}
}
```
정확한 Agent Builder 응답 fixture는
[tools-list-response.json](../agent-builder-mcp/examples/agentbuilder-v0.3/tools-list-response.json)을 따른다.
## 4. Tool Service가 구현할 매니페스트 endpoint
Tool Service는 MCP 배포 설정에 등록된 `manifestUrl`에 대해 다음을 반환한다.
```text
GET /tool-manifest
Accept: application/json
200 OK
Content-Type: application/json
```
이 요청은 사용자 Tool 실행이 아니라 MCP의 배경 metadata 갱신이다. 따라서 `guid`, 사원 식별자,
`Mcp-Session-Id` 같은 요청 상관·사용자 header를 기대하면 안 된다.
현재 구현은 conditional GET을 보내지 않으므로 Tool Service는 우선 항상 `200 OK`와 전체 JSON을
반환하면 된다. `304 Not Modified`와 ETag는 계약상 선택 사항이지만 현재 MCP 구현 범위가 아니다.
### 응답 규칙
```json
{
"bundleId": "insurance-processing",
"revision": "2026-08-03T01",
"tools": [
{
"name": "processing.contract.inquiry",
"title": "계약 조회",
"description": "계약번호로 계약의 기본 정보를 조회합니다.",
"inputSchema": {
"type": "object",
"properties": {
"contractNo": {
"type": "string",
"description": "조회할 계약번호입니다.",
"minLength": 1
}
},
"required": ["contractNo"],
"additionalProperties": false
},
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
},
"_meta": {
"version": "1.0.0",
"timeoutMillis": 3000,
"enabled": true
}
}
]
}
```
| 항목 | Tool Service 규칙 | MCP 처리 |
|---|---|---|
| `bundleId` | 필수. MCP 배포 설정의 Bundle id와 정확히 일치 | 다르면 Bundle 전체 거부 |
| `revision` | 선택. 변경 식별·운영 진단용 | 현재 호출 대상이나 공개 응답에는 사용하지 않음 |
| `tools` | 필수. 이 Bundle의 **전체 상태**를 배열로 반환 | 정상 빈 배열은 “노출 Tool 없음”으로 채택 |
| `name` | 필수. `[A-Za-z0-9_./-]{1,64}` 및 설정 `namePrefix`로 시작 | 위반 시 Bundle 전체 거부 |
| `description` | 필수. Agent Builder가 Tool 선택에 사용할 설명 | 그대로 `tools/list`에 공개 |
| `inputSchema` | 필수 JSON Schema object | 그대로 공개하고 `tools/call` 전에 검증 |
| `title`, `annotations` | 선택 공개 정보 | 있으면 `tools/list`에 공개 |
| `_meta.version` | 필수 | 내부 metadata로만 사용, 공개하지 않음 |
| `_meta.timeoutMillis` | 선택 | 설정 상한 이하로 제한, 공개하지 않음 |
| `_meta.enabled` | 선택, 기본 `true` | `false`면 memory snapshot과 `tools/list`에서 제외 |
| `outputSchema` | 현재 운영에서는 생략 | `structuredContent` 미지원 상태라 선언하지 않음 |
`baseEndpoint`, Tool 실행 URL, credential은 매니페스트에 넣지 않는다. MCP가 실제 호출할 주소는
배포 설정의 `baseEndpoint`에서만 결정한다. 매니페스트 안의 `endpoint` 성격 필드는 있어도 읽지 않는다.
## 5. Tool Service가 알아야 할 실패 동작
| Tool Service 매니페스트 결과 | MCP 동작 |
|---|---|
| `200` + 전체 검증 통과 | 새 목록을 memory에 교체하고 다음 `tools/list`부터 노출 |
| `200` + JSON/필수 필드/이름 오류 | 직전 성공 목록 유지. 첫 기동이면 목록을 만들지 못함 |
| timeout, 연결 실패, 4xx/5xx | 직전 성공 목록 유지. 첫 기동이면 readiness DOWN |
| 정상 `tools: []` | 빈 목록을 정상 전체 상태로 채택 |
| Tool 하나만 제거한 정상 전체 manifest | 다음 갱신에 그 Tool도 목록에서 제거 |
따라서 Tool Service는 manifest 응답을 부분 목록이나 증분 변경으로 보내면 안 된다. 한 번의 `200` 응답은
그 시점에 노출할 Tool의 완전한 목록이어야 한다.
## Tool Service 구현 체크리스트
1. `GET /tool-manifest`를 MCP Server namespace에서만 접근 가능하게 제공한다.
2. `bundleId`가 배포 설정의 Bundle id와 정확히 일치하는지 배포 전에 함께 확인한다.
3. 모든 Tool에 고유한 표준 `name`, 비어 있지 않은 `description`, object 형태의 `inputSchema`, `_meta.version`을 넣는다.
4. Tool을 숨기려면 `_meta.enabled: false`를 쓰거나 정상 전체 목록에서 제거한다. 둘의 변경 반영 시점은 다음 refresh다.
5. 실행 주소·credential·개인정보·업무 payload를 매니페스트에 넣지 않는다.
6. Tool 자체 실행 endpoint는 별도로 `POST {baseEndpoint}/{toolName}`을 구현한다. manifest endpoint는 실행 endpoint가 아니다.
## 확인한 구현·테스트
- 최초 preload·주기 refresh: `ToolRegistryRefreshScheduler`
- in-memory snapshot·실패 fallback: `ToolRegistryService`
- HTTP 매니페스트 조회·필드 검증: `ToolBundleDiscovery`
- `tools/list` 공개 필드 변환·`_meta` 제거: `ToolsListHandler`
- 회귀 테스트: `ToolRegistryServiceTest`, `ToolBundleDiscoveryTest`, `ToolsListHandlerTest`
자세한 field 정의와 실행 계약은 [v0.2 계약](protocol-v0.2-bundle-discovery.md), 실제 manifest 전체 예시는
[manifest-response.json](examples/bundle-v0.2/manifest-response.json)을 참고한다.

View File

@@ -0,0 +1,54 @@
{
"bundles": [
{
"bundleId": "insurance-processing",
"enabled": true,
"status": "healthy",
"revision": "sha256:9f2c4a17b83e5d06c1f9a2e7b45d8c30ff1a6b92e4c7d5083a1b6e9f2c4d7a850",
"toolCount": 2,
"consecutiveFailures": 0,
"lastSuccessAt": "2026-07-29T02:29:45Z",
"lastFailureReason": null
},
{
"bundleId": "insurance-corebanking",
"enabled": true,
"status": "degraded",
"revision": "sha256:1d70e6b4c2a89f35e0b7d4816c3a92f5088b1e7d6a4c93520fb8e1d7a6c40395",
"toolCount": 5,
"consecutiveFailures": 1,
"lastSuccessAt": "2026-07-29T02:29:15Z",
"lastFailureReason": "ResourceAccessException"
},
{
"bundleId": "insurance-payment",
"enabled": true,
"status": "degraded",
"revision": "sha256:7e4c81b0f90f4a2c31e7d1086aa9cd31b2f14403e1f0c6633ca2b424e6d4a812",
"toolCount": 3,
"consecutiveFailures": 4,
"lastSuccessAt": "2026-07-29T02:10:00Z",
"lastFailureReason": "ResourceAccessException"
},
{
"bundleId": "insurance-claim",
"enabled": true,
"status": "unreachable",
"revision": null,
"toolCount": 0,
"consecutiveFailures": 2,
"lastSuccessAt": null,
"lastFailureReason": "IllegalStateException"
},
{
"bundleId": "insurance-channel",
"enabled": false,
"status": "disabled",
"revision": null,
"toolCount": 0,
"consecutiveFailures": 0,
"lastSuccessAt": null,
"lastFailureReason": null
}
]
}

View File

@@ -0,0 +1,94 @@
{
"bundleId": "insurance-processing",
"revision": "sha256:9f2c4a17b83e5d06c1f9a2e7b45d8c30ff1a6b92e4c7d5083a1b6e9f2c4d7a850",
"tools": [
{
"name": "processing.contract.inquiry",
"title": "계약 조회",
"description": "계약번호로 계약의 기본 정보를 조회합니다. 사용자가 특정 계약의 상태, 보험료, 계약일을 물어볼 때 사용합니다. 테스트 전용이며 실제 고객 계약 데이터는 처리하지 않습니다.",
"inputSchema": {
"type": "object",
"properties": {
"contractNo": {
"type": "string",
"description": "조회할 계약번호입니다.",
"minLength": 1
}
},
"required": ["contractNo"],
"additionalProperties": false
},
"annotations": {
"title": "계약 조회",
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
},
"_meta": {
"version": "1.2.0",
"timeoutMillis": 3000,
"enabled": true
}
},
{
"name": "processing.payment.history",
"title": "수납 이력 조회",
"description": "계약번호로 수납 이력을 조회합니다. 사용자가 납입 내역이나 미납 여부를 물어볼 때 사용합니다.",
"inputSchema": {
"type": "object",
"properties": {
"contractNo": {
"type": "string",
"description": "조회할 계약번호입니다.",
"minLength": 1
},
"months": {
"type": "integer",
"description": "조회할 최근 개월 수입니다.",
"minimum": 1,
"maximum": 36
}
},
"required": ["contractNo"],
"additionalProperties": false
},
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
},
"_meta": {
"version": "1.0.1",
"timeoutMillis": 5000,
"enabled": true
}
},
{
"name": "processing.notice.send",
"title": "안내 발송",
"description": "계약자에게 안내 메시지를 발송합니다. 사용자가 명시적으로 발송을 요청한 경우에만 사용합니다.",
"inputSchema": {
"type": "object",
"properties": {
"contractNo": { "type": "string", "minLength": 1 },
"template": { "type": "string", "enum": ["PAYMENT_DUE", "CONTRACT_EXPIRY"] }
},
"required": ["contractNo", "template"],
"additionalProperties": false
},
"annotations": {
"readOnlyHint": false,
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": false
},
"_meta": {
"version": "0.9.0",
"timeoutMillis": 10000,
"enabled": false
}
}
]
}

View File

@@ -0,0 +1,49 @@
# MCP Server의 bundle 조회 설정 예시 (protocol-v0.2-bundle-discovery.md 3절)
#
# 이 파일은 계약 예시이며 실제 적용 설정이 아니다.
# 운영에서는 ConfigMap으로 주입하고 MCP Server마다 다른 bundle 목록을 갖는다.
#
# 키 이름은 구현된 McpProperties와 1:1로 맞춰 두었다. Spring relaxed binding이
# camelCase와 kebab-case를 모두 받으므로 이 문서는 읽기 쉬운 camelCase를 쓴다.
mcp:
# 이 MCP Server의 식별자. Redis key namespace에 사용한다.
identity: mcp-insurance-core
registry:
# 조회 주기와 첫 scheduled refresh 쏠림을 줄이는 지연 jitter. 기동 preload는 즉시 실행한다.
refreshIntervalSeconds: 30
refreshJitterSeconds: 5
discovery:
# 운영 profile에서는 true이고 아래 Tool Service 매니페스트만 원천으로 사용한다.
# false는 local profile의 테스트 JSON에만 사용한다.
enabled: true
connectTimeoutMillis: 1000
readTimeoutMillis: 3000
# 상한. 초과 시 처리는 계약 7절을 따른다.
maxToolsPerBundle: 100
maxToolsTotal: 200
maxManifestBytes: 1048576
# 매니페스트가 선언한 Tool timeout의 상한. 초과분은 절삭한다.
# Tool이 과도한 timeout을 선언해 MCP 스레드를 점유하는 것을 막는다.
maxToolTimeoutMillis: 30000
# 스키마는 N개를 허용하지만 운영 배포에서는 항상 한 항목이다.
# MCP 배포 하나가 Tool Service 하나만 보기 때문이다(ADR-0007).
# 대상을 늘리려면 이 목록이 아니라 MCP 배포를 하나 더 만든다.
bundles:
- id: insurance-processing
# 매니페스트 조회 주소 (MCP -> Tool)
manifestUrl: http://tool-processing.ax-hub.svc.cluster.local:8080/tool-manifest
# Tool 실행 주소. Pod IP가 아니라 Service URL을 사용한다.
# 매니페스트가 이 값을 바꿀 수 없다. 이것이 조회 방식의 보안 기반이다.
baseEndpoint: http://tool-processing.ax-hub.svc.cluster.local:8080/mcp
# Tool Service가 선언한 표준 MCP name이 따라야 할 접두사.
# 업무 단위이며 중요도 등급을 넣지 않는다. 등급이 이름에 들어가면
# Tool 재분류가 Tool name 변경이 되어 Agent Builder 재등록을 부른다.
namePrefix: "processing."
enabled: true

View File

@@ -0,0 +1,349 @@
# Tool Service-MCP Bundle 조회 계약 v0.2
- 상태: **Implemented** (MCP 서버 측 구현 완료, Tool Service 측 합의 대기)
- 기준일: 2026-07-30
- 대체 대상: push 등록 방식(v0.1). 채택하지 않은 이유는 §2
- 조회 endpoint: `GET {manifestUrl}` — Tool Service가 제공
- 실행 endpoint: `POST {baseEndpoint}/{toolName}` — 현재 구현
## 1. 계약 범위와 원칙
Tool Service는 여러 Tool을 함께 배포하는 하나의 프로젝트이며, 이 계약에서 **bundle**이라 부른다.
MCP Server는 자기 설정에 선언된 bundle의 매니페스트를 **주기적으로 조회**해 Tool 목록을 구성한다.
| 원칙 | 내용 |
|---|---|
| MCP가 가져온다 | Tool Service는 매니페스트를 제공만 한다. MCP에 등록 요청을 보내지 않는다 |
| 조회 대상은 설정이 정한다 | 어떤 bundle이 이 MCP에 속하는지는 배포 시점 YAML로 확정된다 |
| **라우팅 주소는 설정이 소유한다** | 호출 대상 주소는 MCP 설정에서만 온다. 매니페스트가 바꿀 수 없다 |
| 매니페스트는 전체 상태 | 응답은 그 bundle의 Tool 전체 목록이다. 증분 없음 |
| 조회 성공이 생존 신호 | 별도 heartbeat·TTL 장치가 없다 |
| bundle 단위 조회 격리 | 한 bundle의 조회 실패가 다른 bundle의 조회를 중단시키지 않는다 |
| aggregate는 전부 아니면 전무 | 단, 직전 성공본조차 없는 bundle이 하나라도 있으면 카탈로그 전체를 교체하지 않는다 |
세 번째 원칙이 이 계약의 보안 기반이다. 매니페스트는 **무엇을 노출하는가**만 말하고
**어디로 호출할 것인가**는 말하지 않는다. Tool Service가 임의의 주소를 MCP에 주입할 수 없다.
마지막 두 원칙은 층이 다르다. **조회**는 bundle마다 독립이고 실패해도 직전 성공본이 남으므로
평소에는 한 bundle의 장애가 다른 bundle을 건드리지 않는다. 그러나 **카탈로그 교체**는 전부 아니면
전무다. 한 번도 성공한 적 없는 bundle이 남아 있으면 그 상태로 목록을 확정하지 않는다.
일부만 담긴 목록은 "필요한 Tool이 조용히 사라진 상태"를 만들기 때문이다(§7, §11 W11).
> **운영 배포에서 bundle은 항상 하나다.** MCP 배포 하나가 Tool Service 하나만 보기로 했기 때문이다
> ([ADR-0007](../../decisions/ADR-0007-one-mcp-per-tool-service.md)). 따라서 여러 bundle을 전제로 한
> 규칙(§7의 4·6번, `maxToolsTotal`)은 운영에서 발동하지 않는다. 계약과 구현은 N개를 계속 지원하지만
> 배포 정의가 1개로 잠그며, 그 사실은 `HelmDeploymentContractTest`가 검사한다.
## 2. 왜 조회 방식인가, 왜 기동 시 1회가 아닌가
### push를 채택하지 않은 이유
Tool Service가 MCP로 등록을 보내는 방식은 **MCP Server가 재기동되면 카탈로그를 복구할 방법이 없다.**
Tool Service는 이미 등록을 마쳤으므로 다시 보내지 않고, MCP는 빈 상태로 서비스한다.
재기동 빈도는 오히려 MCP 쪽이 높다(배포·스케일·노드 이동).
조회 방식은 MCP가 스스로 물어보므로 이 문제가 성립하지 않는다.
또한 MCP에 쓰기 endpoint를 열지 않아도 된다.
### 기동 시 1회로 끝내지 않는 이유
조회 방식이라도 기동 시 1회만 하면 아래를 따라가지 못한다.
| 상황 | 기동 시 1회만 | 주기적 조회 |
|---|---|---|
| MCP 재기동 | ✅ 다시 조회하므로 복구 | ✅ |
| Tool이 Tool 목록·schema 변경 | ❌ MCP 재기동 전까지 모름 | ✅ 다음 주기 반영 |
| Tool Service 장애 | ❌ 계속 노출 | ✅ 직전 성공본 유지, 정상 응답에서 삭제 확인 시 제거 |
| MCP 기동 시점에 Tool이 배포 중이라 응답 실패 | ❌ **영구 누락** | ✅ 다음 주기 복구 |
마지막 항목이 가장 위험하다. 조회는 반드시 주기적이어야 한다.
## 3. MCP 설정 (YAML)
조회 대상과 라우팅 주소를 선언한다. 예시는
[mcp-bundle-config.yaml](examples/bundle-v0.2/mcp-bundle-config.yaml)에 있다.
```yaml
mcp:
identity: mcp-insurance-core
registry:
refreshIntervalSeconds: 30
discovery:
enabled: true
connectTimeoutMillis: 1000
readTimeoutMillis: 3000
maxToolsPerBundle: 100
maxToolsTotal: 200
maxManifestBytes: 1048576
maxToolTimeoutMillis: 30000
bundles:
# 운영 배포에서 이 목록은 항상 한 항목이다(ADR-0007). 스키마는 N개를 허용한다.
- id: insurance-processing
manifestUrl: http://tool-processing.ax-hub.svc.cluster.local:8080/tool-manifest
baseEndpoint: http://tool-processing.ax-hub.svc.cluster.local:8080/mcp
namePrefix: "processing."
# local 검증에서만 사용. 최초 원격 조회 실패 때만 읽으며 운영 Helm에는 넣지 않는다.
fallbackManifestFile: file:./config/local-process-tools-manifest-sample-v1.json
enabled: true
```
| 항목 | 설명 |
|---|---|
| `discovery.enabled` | 운영에서는 `true`이며 bundle 매니페스트를 원천으로 사용한다. `false`는 legacy local JSON fixture에만 사용한다 |
| `manifestUrl` | 매니페스트 조회 주소 |
| `baseEndpoint` | **Tool 실행 주소.** Pod IP가 아니라 Service URL을 사용한다 |
| `namePrefix` | 이 bundle이 사용할 수 있는 Tool 이름 접두사 |
| `fallbackManifestFile` | 선택. 최초 원격 조회 실패 때만 읽을 local manifest 파일. 운영 Helm에는 설정하지 않는다 |
| `enabled` | `false`면 조회하지 않는다. Actuator 상태에는 `status: "disabled"`로 나타난다 |
`manifestUrl``baseEndpoint`를 나눈 이유는 매니페스트 제공 경로와 실행 경로가 다를 수 있기 때문이다.
같아도 무방하다.
원격 매니페스트와 legacy local JSON fixture는 **배타적**이다. `ToolRegistryClient` 구현은
`discovery.enabled`로 선택된다. 다만 local profile에서 원격 조회를 켠 경우에는 bundle별
`fallbackManifestFile`을 둘 수 있다. 이는 **최초 원격 조회가 실패했을 때만** 읽는 같은 매니페스트 형식의
cold-start fallback이며, 원격 정상 목록이나 직전 성공본을 덮어쓰지 않는다.
| profile | `discovery.enabled` | 등록되는 원천 | 결과 |
|---|:---:|---|---|
| `local` | `false` | `LocalFileToolRegistryClient` | legacy JSON fixture만 사용 |
| `local` | `true` | `ToolBundleRegistryClient` | 원격 우선, 설정 시 local manifest fallback |
| `local` 아님(`ocp` 등) | `true` | `ToolBundleRegistryClient` | 정상. 운영 |
| `local` 아님 | `false` | 없음 | **기동 실패** |
원천이 하나도 없으면 `ToolRegistryService`가 주입받을 bean이 없어 기동 단계에서 멈춘다.
빈 Tool 목록으로 조용히 뜨는 것보다 낫지만, 오류 메시지가 Spring의 bean 해석 실패이므로
원인을 바로 알기 어렵다. 두 profile YAML이 이미 올바른 값을 고정하고 있으므로
(`application-local.yml``application-ocp.yml``true`; legacy local fixture만 쓸 때에만 `false`)
새 profile을 추가할 때만 주의하면 된다.
### 조회 주기와 jitter
주기는 기존 `mcp.registry.refreshIntervalSeconds`를 사용한다. replica가 동시에 기동할 때
조회 쏠림을 줄이기 위해 첫 **scheduled refresh**에만 `refreshJitterSeconds` 범위의 bounded jitter를 더한다.
ApplicationReady 직후 warm start와 원천 preload는 빈 목록 구간을 줄이기 위해 jitter 없이 즉시 실행한다.
### 기동 시 검증
아래를 위반하면 **기동에 실패한다.** 잘못된 설정이 운영 중 엉뚱한 라우팅으로 나타나는 것보다 낫다.
| 규칙 | 이유 |
|---|---|
| `discovery.enabled=true`이면 `bundles`가 비어 있을 수 없다 | 이 상태로 뜨면 `tools/list`가 영구히 빈다 |
| `id`는 중복될 수 없다 | 상태 추적 단위가 겹친다 |
| `namePrefix`는 중복될 수 없고 다른 prefix의 접두사도 될 수 없다 | `a.``a.b.`가 함께 있으면 `a.b.search`의 소속이 확정되지 않는다 |
## 4. Tool Service가 제공할 endpoint
```text
GET {manifestUrl}
Accept: application/json
If-None-Match: "<직전 revision>" # 선택
```
매니페스트 조회는 사용자 요청이 아니라 **배경 갱신**이다. 특정 호출자의 요청 context가 없으므로
correlation·사원 식별자 header를 붙이지 않는다.
응답:
```text
200 OK
Content-Type: application/json
ETag: "sha256:9f2c..." # 선택. revision과 같은 값
```
응답 예시는 [manifest-response.json](examples/bundle-v0.2/manifest-response.json)을 따른다.
`If-None-Match`가 현재 `revision`과 같으면 `304 Not Modified`를 본문 없이 반환해도 된다.
MCP는 이 경우 직전 매니페스트를 그대로 유지한다. **선택 기능이며 구현하지 않아도 계약을 만족한다.**
이 endpoint는 인증을 요구하지 않아도 되지만, **NetworkPolicy로 MCP Server에서만 접근 가능하도록
제한한다.** Tool 이름·설명·schema는 내부 시스템 구조를 드러내므로 클러스터 전체에 공개하지 않는다.
## 5. 매니페스트 스키마
### 최상위 필드
| 필드 | 필수 | 설명 |
|---|:---:|---|
| `bundleId` | 예 | MCP 설정의 `id`와 일치해야 한다. 다르면 그 응답을 버린다 |
| `revision` | 아니오 | 매니페스트 버전. 변경 감지·로그·ETag에만 쓰인다 |
| `tools` | 예 | 이 bundle이 노출하는 Tool 전체. 빈 배열은 "노출할 Tool 없음"이다 |
`baseEndpoint`**매니페스트에 넣지 않는다.** 넣어도 MCP는 무시한다(§1 세 번째 원칙).
### `tools[]` 필드
| 필드 | 필수 | 설명 |
|---|:---:|---|
| `name` | 예 | MCP 표준에 맞춘 `[A-Za-z0-9_./-]{1,64}`이며 bundle의 `namePrefix`로 시작해야 한다 |
| `title` | 아니오 | 표시용 이름 |
| `description` | 예 | 에이전트가 Tool 선택에 사용한다. 언제 쓰는 도구인지 명확히 쓴다 |
| `inputSchema` | 예 | JSON Schema 2020-12 |
| `outputSchema` | 아니오 | `structuredContent` 응답 구조. 현재 MCP는 구조화 출력을 만들지 않으므로 운영에서는 사용하지 않는다 |
| `annotations` | 아니오 | `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` |
| `_meta.version` | 예 | Tool 버전 |
| `_meta.timeoutMillis` | 아니오 | MCP 설정의 `maxToolTimeoutMillis`로 상한을 건다 |
| `_meta.enabled` | 아니오 | 기본 `true`. `false``tools/list`에 노출하지 않는다 |
`name`, `title`, `description`, `inputSchema`, `outputSchema`, `annotations`는 MCP가 `tools/list`
그대로 공개한다. `_meta`는 공개하지 않는다.
현재 MCP의 `tools/call``content[0].text`만 반환하고 `structuredContent` 생성·응답 schema 검증은 하지 않는다.
MCP 2025-06-18에서 `outputSchema`를 선언한 서버는 이에 맞는 구조화 결과를 제공해야 하므로, Tool Service는
구조화 출력 지원이 별도 계약으로 반영되기 전까지 운영 매니페스트에서 `outputSchema`를 생략한다.
## 6. MCP의 조회 동작
| 항목 | 권장값 | 근거 |
|---|---|---|
| 주기 | `30초` | 변경 반영 지연의 상한 |
| 첫 scheduled refresh jitter | `0~5초` | 반복 조회 주기가 replica마다 같은 시점에 고정되는 것을 방지 |
| 연결 timeout | `1초` | |
| 읽기 timeout | `3초` | |
| 기동 시 | **즉시 1회 조회하되 기동을 막지 않는다** | Tool 장애가 MCP 기동 실패로 번지지 않게 |
| readiness | **첫 조회 시도 완료 + usable snapshot이면 ready** | 원천 또는 Redis last-good이 있어 실제 요청을 처리할 수 있을 때만 트래픽을 받는다 |
usable snapshot은 원천 조회 성공본, 최초 원격 조회 실패 때 채택한 local fallback, 또는 Redis에서 채택한 last-good이다. 정상 매니페스트가 반환한 빈 Tool
목록도 유효한 전체 상태다. 반대로 첫 조회가 끝났더라도 memory와 Redis에 성공본이 하나도 없으면 readiness는
DOWN을 유지하고 다음 주기 조회를 기다린다.
### 동시 조회
bundle N개를 **동시에** 조회한다. 순차 조회하면 소요 시간이 합산되어 기동과 갱신이 지연된다.
개별 조회 실패는 **예외가 아니라 결과값**으로 다룬다. 하나의 실패가 전체 조회를 중단시키면
나머지 성공분까지 버려진다.
### 실패 판정과 유예
| 조회 결과 | 처리 |
|---|---|
| 성공 + 검증 통과 | 새 매니페스트 채택 |
| 성공 + 검증 실패 | 직전 성공본 유지. 실패 횟수 증가 |
| timeout / 연결 실패 / 5xx | 직전 성공본 유지. 실패 횟수 증가 |
| `304 Not Modified` | 직전 성공본 유지. 실패 횟수 **초기화** |
`304`**아직 구현하지 않았다.** §4에서 선택 기능으로 둔 항목이므로 `If-None-Match`를 보내지 않고,
Tool Service가 `304`를 반환할 일도 없다. 매번 전체 매니페스트를 받아 채택한다.
구현상 실패는 **예외가 아니라 결과값**이다. 조회 작업이 예외를 그대로 올리면 `Future` 하나가 깨지면서
나머지 bundle의 성공분까지 함께 버려지기 때문이다. bundle별 작업이 자기 예외를 잡아 실패 결과로 바꾸고,
그 바깥에 예상 밖의 오류까지 흡수하는 2차 방어선을 둔다.
## 7. 병합 규칙
1. **이름 검증**`namePrefix`로 시작하지 않는 Tool은 그 bundle 전체를 거부한다
2. **`enabled: false` 제외** — 등록은 하되 `tools/list`에 노출하지 않는다
3. **상한 검사**`maxToolsPerBundle` 초과 시 그 bundle 거부, `maxToolsTotal` 초과 시 전체 aggregate 거부
4. **정렬**`(bundleId, name)` 오름차순으로 정렬한다
5. **`timeoutMillis` 상한** — `maxToolTimeoutMillis`를 넘는 값은 상한으로 절삭한다
6. **bundle 간 이름 충돌** — 어느 Tool도 임의 선택하지 않고 전체 aggregate를 거부한다
1번은 Tool 하나가 규칙을 어겨도 **bundle 전체를 거부**한다는 뜻이다. 필수 필드 누락, `bundleId` 불일치,
매니페스트 내부 이름 중복도 같다. 일부만 반영된 카탈로그는 "필요한 Tool이 조용히 사라진 상태"를 만들어,
직전 성공본을 유지하는 것보다 나쁘다.
6번을 정렬 **뒤에** 두는 이유는 1번과 같다. 정렬 전에 처리하면 어느 쪽이 살아남는지가
동시 조회의 응답 순서에 좌우되어 replica마다 달라진다.
정렬이 없으면 동시 조회 응답 순서에 따라 `tools/list` 순서가 매번 달라진다.
Agent Builder 쪽 프롬프트가 매 호출 달라져 캐시 적중률이 떨어지므로 반드시 정렬한다.
`maxToolsTotal`은 [ADR-0002](../../decisions/ADR-0002-tool-exposure-and-single-call.md)의
Tool 노출 상한과 함께 검토한다. 운영 배포는 bundle이 하나이므로(ADR-0007) 이 상한은
`maxToolsPerBundle`과 같은 층에서 동작하며, 50개 노출 상한은 한 Agent가 **여러 MCP에서 가져온
Tool의 합계**에 적용된다. MCP를 나눈다고 상한이 늘지 않는다.
## 8. Tool을 찾지 못했을 때의 재확인
`tools/call` 요청의 Tool이 현재 스냅샷에 없으면, **해당 bundle을 즉시 1회 재조회한 뒤**
그래도 없으면 `-32001 Tool not found`로 응답한다.
MCP replica마다 조회 시점이 달라 스냅샷이 일시적으로 어긋날 수 있기 때문이다(§11 W2).
구현은 해당 bundle 하나가 아니라 **전체를 한 번 재조회**한다. 재조회 대상은 병렬이고 timeout이 짧아
비용 차이가 작은 반면, "어느 bundle에 속한 Tool인가"를 이름만으로 되짚는 경로를 따로 두지 않아도 된다.
계약이 요구하는 것(한 번 더 확인한 뒤 판정)은 그대로 만족한다.
## 9. 운영 상태 조회
```text
GET /actuator/toolBundles
```
management port(운영 기본 9090)에서 MCP가 알고 있는 bundle의 조회 상태를 반환한다. 외부 ingress에는
노출하지 않는다. 응답 예시는
[bundle-status-response.json](examples/bundle-v0.2/bundle-status-response.json)에 있다.
설정에 선언되어 있으나 한 번도 조회에 성공하지 못한 bundle도 반환한다.
**설정에 기대값이 있으므로 누락 감지가 가능하다.**
| `status` | 의미 |
|---|---|
| `healthy` | 마지막 조회 성공 |
| `fallback` | 최초 원격 조회에 실패해 local manifest sample을 사용 중 |
| `degraded` | 최근 조회는 실패했지만 직전 성공본을 계속 노출 중 |
| `unreachable` | 켜져 있으나 한 번도 성공한 적 없음 |
| `disabled` | 설정에서 `enabled: false` |
응답에 **`manifestUrl``namePrefix`는 넣지 않는다.** 진단에 꼭 필요하지 않은데 내부 주소 체계를 더 드러낸다.
`lastFailureReason`도 메시지가 아니라 **예외 타입 이름만** 담는다. 메시지에는 URL이나 응답 조각이 섞일 수 있다.
읽기 전용이며 상태를 바꾸지 않는다. 그러나 내부 구조를 노출하므로 외부에 공개하지 않는다.
이 endpoint는 Spring Boot Actuator가 제공하므로 `/mcp`의 JSON-RPC 예외 처리 경계를 통과하지 않는다.
## 10. 실행 경로 (MCP → Tool, 현재 구현)
이미 구현되어 있는 계약이다. Tool Service는 아래를 받을 수 있어야 한다.
```text
POST {baseEndpoint}/{toolName}
Content-Type: application/json
guid, x-request-id, mcp-session-id, employee-no, virtual-employee-no
Authorization: <설정에 따라 전달>
<tools/call의 arguments 객체 원본>
```
- 호출자 header 다섯 개는 **이름과 값을 바꾸지 않고 그대로 bypass**한다. 값이 없는 header는 보내지 않는다.
- `employee-no`·`virtual-employee-no`는 호출자가 암호화한 값이다. **복호화는 Tool Service 몫이며
사내 KMS에서 발급받은 키를 사용한다.** MCP는 키를 갖지 않으므로 값을 읽지도, 로그에 남기지도 못한다.
MCP가 인증을 하지 않으므로([ADR-0006](../../decisions/ADR-0006-no-authentication-in-mcp.md))
이 값의 신뢰 여부는 Tool Service가 판단한다. 두 header가 모두 없을 수 있다는 점도 함께 고려한다.
- 발송·등록·변경 Tool의 중복 실행 방지는 Tool Service 책임이다. retry가 같은 `guid`를 재사용할지와
이를 멱등성 키로 사용할지는 아직 합의되지 않았으므로 현재 wire 계약으로 가정하지 않는다.
합의 대상은 [extension-points.md](../../extension-points.md)에 한 번만 관리한다.
- 요청 body는 에이전트가 보낸 `arguments` 객체 **그대로**다. MCP는 이름·값을 바꾸지 않는다.
- 응답이 JSON object/array면 MCP가 compact JSON 문자열로 `result.content[0].text`에 담는다.
- Tool이 반환한 HTTP 4xx/5xx와 timeout은 `result.isError: true`로 변환한다.
- 호출 소요 시간은 `result.content[0]._meta.searchTime`(ms)로 반환한다.
향후 Tool Service를 MCP 서버로 만들면 매니페스트 조회를 표준 `tools/list`로, 실행을 표준
`tools/call`로 대체할 수 있다. 이 경우 §4·§5는 MCP 표준으로 흡수된다. 전환 여부는 합의 항목이다.
## 11. 트레이드오프와 확장 경계
현재 선택은 **Tool Service 원천 + 주기 pull + in-memory last-good + 선택 Redis 공유 cache**다.
Redis의 key 형식, TTL, 공유 범위와 운영 정책은 이 Tool Service wire 계약의 범위가 아니며
[extension-points.md](../../extension-points.md#운영-적용-전-필수-보완)에서 합의한다.
| 트레이드오프 | 현재 선택 |
|---|---|
| replica snapshot 차이 | 일시 허용. 성공본만 교체하고 Tool miss 시 원천을 한 번 재확인 |
| 변경 반영 지연 | 기본 한 주기 허용. 즉시 알림·ETag는 아직 도입하지 않음 |
| 조회 부하 | replica별 조회 허용. 규모가 커질 때만 leader election 검토 |
| 기동 중 원천 장애 | Redis warm start 후 즉시 preload. local profile에 fallback 파일이 있으면 최초 실패 때만 채택하고, 없으면 다음 조회까지 Registry unavailable |
| stale Tool | 조회 실패만으로 삭제하지 않음. 성공한 매니페스트에서 빠진 경우에만 삭제 |
| Redis 장애 | cache miss로 격리. 요청 경로는 in-memory만 조회 |
NetworkPolicy·egress, 관측 지표, retry/idempotency, outputSchema 등 아직 합의하거나 보완할 내용은
[extension-points.md](../../extension-points.md)에서만 관리한다. 구현 목록은 코드와 테스트가 정본이며 이 계약에 다시 나열하지 않는다.
## 12. 의도적으로 넣지 않은 기능
- `If-None-Match` / `304`: 매니페스트가 작아 현재 효용이 없음
- 즉시 refresh 알림 endpoint: 주기 반영으로 부족하다는 운영 근거가 생길 때 검토
- leader election: replica 조회 부하가 실제 병목이 될 때 검토
- 상태 응답의 `manifestUrl`·`namePrefix`: 불필요한 내부 주소 노출 방지