Initial commit
This commit is contained in:
16
docs/contracts/agent-builder-mcp/README.md
Normal file
16
docs/contracts/agent-builder-mcp/README.md
Normal 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가 아니다.
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"protocolVersion": "2025-06-18",
|
||||
"serverInfo": {},
|
||||
"capabilities": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/initialized",
|
||||
"params": {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "customer.search@1.0.0: timed out"
|
||||
}
|
||||
],
|
||||
"isError": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "customer.search",
|
||||
"arguments": {
|
||||
"customerNo": "1234567890"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "{\"customerName\":\"Hong\"}"
|
||||
}
|
||||
],
|
||||
"structuredContent": {
|
||||
"customerName": "Hong"
|
||||
},
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "customer.search@1.0.0: timed out"
|
||||
}
|
||||
],
|
||||
"isError": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"error": {
|
||||
"code": -32602,
|
||||
"message": "Invalid params: 'query' is required"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "processing",
|
||||
"arguments": {
|
||||
"query": "processing system information inquiry",
|
||||
"category": "processing"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "processing complete",
|
||||
"_meta": {
|
||||
"searchTime": 976.1
|
||||
}
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
@@ -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": "테스트 요청 식별자입니다."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/initialized"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/initialized"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/initialized"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||
}
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "youtube_search",
|
||||
"arguments": {
|
||||
"q": "EBS 공식 유튜브 채널",
|
||||
"gl": "KR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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\>"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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]>
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/initialized"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "list_collections",
|
||||
"arguments": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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로 취급하지 않는다.
|
||||
@@ -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`을 실행 식별자로 사용한다.
|
||||
@@ -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는 계속 수용한다.
|
||||
@@ -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. 코드와 테스트 변경을 별도 작업으로 수행한다.
|
||||
39
docs/contracts/tool-service-mcp/README.md
Normal file
39
docs/contracts/tool-service-mcp/README.md
Normal 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)을 따른다.
|
||||
227
docs/contracts/tool-service-mcp/TEMP-tool-list-loading-guide.md
Normal file
227
docs/contracts/tool-service-mcp/TEMP-tool-list-loading-guide.md
Normal 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)을 참고한다.
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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`: 불필요한 내부 주소 노출 방지
|
||||
Reference in New Issue
Block a user