Skip to content

Commit b5e2c9b

Browse files
redcourageclaude
andcommitted
docs(v04): phase-a §4 명령어 cookbook 추가
기존 §3 (Level 1·2·3 합격 시퀀스)는 한 번 통과를 위한 step. 그 위에 §4를 신설해 반복 작업 시 그대로 복붙해 쓰는 명령어 모음을 정리. Phase B / Phase 4 / Phase 5 에서도 같은 회로 재사용 가능. §4 구성: - 4.1 서버 실행 4가지 변형 (foreground · stdin EOF · stderr 분리 · Inspector) - 4.2 단발 JSON-RPC 요청 패턴 + MCP framing 3 규칙 (initialize → notifications/initialized → tools/* 순서, newline framing, 마지막 sleep 0.3-0.5s 권장) - 4.3 8 tool 각각 minimum 인자 호출 cookbook - 4.4 mcp_call bash 헬퍼 (8 tool 검증 후 채택). default `{}` expansion 함정 (`${2:-{}}`)도 명시 - 4.5 jq 응답 분석 패턴 7가지 (이름 / schema / required 매트릭스 / text 등) - 4.6 디버깅 (stderr 분리, raw dump, tee로 input/output 동시 저장) - 4.7 양방향 stateful 세션 (bash coproc) — 옵션 - 4.8 ~/.claude/mcp.json 등록 후 검증 명령어 §0 메타데이터 박스에 §4 navigation 한 줄 추가. 번호 시프트: 기존 §4 Troubleshooting → §5, §5 코드 변경 → §6, §6 다음 마일스톤 → §7. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cebe38e commit b5e2c9b

1 file changed

Lines changed: 187 additions & 3 deletions

File tree

docs/v04/progress/phase-a-mcp-boot.md

Lines changed: 187 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
> **관련 커밋**: [`19b7bf6`](../../../)`feat(go): first MCP boot — handshake + tools/list (Phase A)`
55
> **브랜치**: `yg/first-mcp-boot` (origin/`yg/first-mcp-boot`)
66
> **수정 파일 5개**: `cmd/rune-mcp/main.go` · `internal/mcp/tools.go` · `go.mod` · `go.sum` · `.gitignore`
7+
> **빠른 실행**: §3 (검증 절차) · **§4 (명령어 cookbook — 8 tool 호출, bash 헬퍼, jq 패턴 등)**
78
89
## 목적
910

@@ -320,7 +321,190 @@ npx -y @modelcontextprotocol/inspector ./bin/rune-mcp
320321

321322
---
322323

323-
## 4. Troubleshooting
324+
## 4. 명령어 cookbook (재사용 가능한 패턴)
325+
326+
§3는 "한 번 합격하기 위한" 시퀀스고, 이 절은 **반복 작업 시 그냥 복사해서 쓰는 명령어 모음**. Phase A뿐 아니라 Phase B/4/5에서도 같은 회로로 재사용된다.
327+
328+
### 4.1. 서버 실행 — 4가지 변형
329+
330+
```bash
331+
cd /Users/redcourage/cryptolab/rune-project/rune
332+
go build -o bin/rune-mcp ./cmd/rune-mcp # 빌드 (Go 1.25+ 필요)
333+
```
334+
335+
| 시나리오 | 명령어 |
336+
|---|---|
337+
| **foreground 단순 실행** (stdin 대화 흐름은 안 됨, EOF로 종료) | `./bin/rune-mcp` |
338+
| **stdin EOF 즉시 종료** | `./bin/rune-mcp < /dev/null` |
339+
| **stderr 분리** (디버그 로그 따로 저장) | `./bin/rune-mcp 2>/tmp/rune-stderr.log` |
340+
| **log 두 곳 동시** | `./bin/rune-mcp 2> >(tee /tmp/rune-stderr.log >&2)` |
341+
| **백그라운드 + named pipe로 양방향** | 아래 §4.6 참고 |
342+
| **Inspector(GUI) 띄우기** | `npx -y @modelcontextprotocol/inspector ./bin/rune-mcp` |
343+
344+
### 4.2. 단발 JSON-RPC 요청 — 가장 짧은 형태
345+
346+
`initialize` 응답만 받기:
347+
348+
```bash
349+
{ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"0.0.1"}}}'; sleep 0.3; } | ./bin/rune-mcp 2>/dev/null | jq .
350+
```
351+
352+
`tools/list`까지 받기 (initialize → notifications/initialized → tools/list 순서 필수):
353+
354+
```bash
355+
{
356+
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"0.0.1"}}}'
357+
sleep 0.3
358+
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
359+
sleep 0.1
360+
printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
361+
sleep 0.5
362+
} | ./bin/rune-mcp 2>/dev/null | jq -r 'select(.id==2) | .result.tools[].name'
363+
```
364+
365+
> **MCP framing 핵심 3개**:
366+
> 1. **순서 필수**: `initialize``notifications/initialized``tools/*`. 순서 어기면 SDK가 거절
367+
> 2. **줄바꿈 framing**: 각 메시지는 `\n` 으로 끝나야 함 (LSP의 Content-Length는 미사용)
368+
> 3. **stdin 종료 = 세션 종료**: 마지막 메시지 후 sleep 없으면 응답 받기 전에 EOF로 닫힘. **0.3~0.5초** 정도 마지막 sleep 권장
369+
370+
### 4.3. 8 tool 각각 호출 — minimum 인자
371+
372+
각 tool의 **input schema에서 필수 필드만 채운** 최소 호출. 응답은 모두 Phase A에서는 `isError=true` + "not yet implemented".
373+
374+
```bash
375+
# rune_diagnostics — 인자 없음
376+
mcp_call rune_diagnostics
377+
378+
# rune_vault_status — 인자 없음
379+
mcp_call rune_vault_status
380+
381+
# rune_reload_pipelines — 인자 없음
382+
mcp_call rune_reload_pipelines
383+
384+
# rune_capture_history — 모두 optional
385+
mcp_call rune_capture_history
386+
387+
# rune_recall — query 필수
388+
mcp_call rune_recall '{"query":"hello"}'
389+
390+
# rune_capture — text + source + extracted 필수
391+
mcp_call rune_capture '{"text":"hi","source":"test","extracted":{}}'
392+
393+
# rune_delete_capture — record_id 필수
394+
mcp_call rune_delete_capture '{"record_id":"dec_test"}'
395+
396+
# rune_batch_capture — items (string) 필수
397+
mcp_call rune_batch_capture '{"items":"[]"}'
398+
```
399+
400+
`mcp_call` 헬퍼는 §4.4에 정의. 위 8줄을 그대로 붙여넣으면 8개 모두 동일한 stub 응답이 나오는 것을 확인할 수 있다.
401+
402+
### 4.4. `mcp_call` bash 헬퍼 함수
403+
404+
다음 블록을 `~/.zshrc` / `~/.bashrc` 또는 현재 셸에 그대로 붙여넣으면 `mcp_call` 명령어 사용 가능:
405+
406+
```bash
407+
mcp_call() {
408+
local tool="$1"
409+
local args="$2"
410+
[ -z "$args" ] && args='{}'
411+
{
412+
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"0.0.1"}}}'
413+
sleep 0.3
414+
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
415+
sleep 0.1
416+
printf '%s\n' "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"$tool\",\"arguments\":$args}}"
417+
sleep 0.5
418+
} | ./bin/rune-mcp 2>/dev/null | jq -c 'select(.id==2)'
419+
}
420+
```
421+
422+
> ⚠️ **함정 주의**: `local args="${2:-{}}"` 처럼 default value에 `}` 를 쓰면 bash parameter expansion이 깨져서 닫는 brace가 한 개 추가됨 (`{"query":"hello"}}` 가 됨). 위 코드처럼 `[ -z ... ] && args='{}'` 패턴으로 우회하는 게 안전.
423+
424+
사용 예 (§4.3과 동일):
425+
426+
```bash
427+
cd /Users/redcourage/cryptolab/rune-project/rune
428+
mcp_call rune_recall '{"query":"hello world"}'
429+
# {"jsonrpc":"2.0","id":2,"result":{"content":[...],"isError":true,...}}
430+
```
431+
432+
### 4.5. 응답 분석 — `jq` 패턴 모음
433+
434+
| 목적 | 명령어 (헬퍼 출력 또는 raw 출력에 파이프) |
435+
|---|---|
436+
| 8개 tool 이름만 보기 | `jq -r 'select(.id==2) \| .result.tools[].name'` |
437+
| 특정 tool의 input schema | `jq 'select(.id==2) \| .result.tools[] \| select(.name=="rune_recall") \| .inputSchema'` |
438+
| 특정 tool의 output schema | `jq 'select(.id==2) \| .result.tools[] \| select(.name=="rune_recall") \| .outputSchema'` |
439+
| 모든 tool의 required 필드 매트릭스 | `jq -r 'select(.id==2) \| .result.tools[] \| "\(.name): \(.inputSchema.required \| join(","))"'` |
440+
| `tools/call` 응답에서 텍스트 메시지만 | `jq -r 'select(.id==2) \| .result.content[0].text'` |
441+
| `tools/call` 응답의 isError + structuredContent | `jq 'select(.id==2) \| {isError: .result.isError, structured: .result.structuredContent}'` |
442+
| serverInfo만 | `jq 'select(.id==1) \| .result.serverInfo'` |
443+
444+
### 4.6. 디버깅 — stderr 분리, raw 메시지 dump
445+
446+
```bash
447+
# stderr만 따로 보기 (정상이면 비어 있어야 함)
448+
{
449+
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"0.0.1"}}}'
450+
sleep 0.5
451+
} | ./bin/rune-mcp 2>/tmp/rune-stderr.log >/dev/null
452+
cat /tmp/rune-stderr.log
453+
454+
# JSON 응답을 한 줄씩 raw로 보기 (jq 없이)
455+
{ ... } | ./bin/rune-mcp 2>/dev/null
456+
# → 줄별 valid JSON. 각 줄을 jq에 따로 파이프해도 됨
457+
458+
# 메시지 시퀀스 검증 (input과 output 비교)
459+
{ ... } | tee /tmp/rune-input.log | ./bin/rune-mcp 2>/dev/null | tee /tmp/rune-output.log | jq .
460+
# /tmp/rune-input.log : 보낸 메시지
461+
# /tmp/rune-output.log: 받은 응답
462+
```
463+
464+
### 4.7. 양방향 stateful 세션 (named pipe / coproc)
465+
466+
위 모든 명령어는 **단방향** (입력 한꺼번에 보내고 응답 모아서 종료). MCP는 양방향 stateful이라, 진짜 클라이언트처럼 동작하려면 다음이 가능:
467+
468+
**bash coproc**:
469+
```bash
470+
coproc RUNE { ./bin/rune-mcp 2>/dev/null; }
471+
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"0.0.1"}}}' >&${RUNE[1]}
472+
read -r -u ${RUNE[0]} line; echo "$line" | jq .
473+
echo '{"jsonrpc":"2.0","method":"notifications/initialized"}' >&${RUNE[1]}
474+
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' >&${RUNE[1]}
475+
read -r -u ${RUNE[0]} line; echo "$line" | jq -r '.result.tools[].name'
476+
exec {RUNE[1]}>&- # stdin 닫음 → server 종료
477+
wait $COPROC_PID
478+
```
479+
480+
대부분의 검증에는 §4.2~4.4의 단방향 패턴으로 충분. coproc은 응답 후 다음 요청을 동적으로 결정해야 할 때만 사용.
481+
482+
### 4.8. Claude Code 등록 후 검증
483+
484+
`~/.claude/mcp.json` 등록(§3 Level 2)이 끝난 뒤:
485+
486+
```bash
487+
# JSON 문법 검증 (등록 직후 필수)
488+
cat ~/.claude/mcp.json | jq .
489+
490+
# rune-go-dev entry 존재 확인
491+
jq '.mcpServers["rune-go-dev"]' ~/.claude/mcp.json
492+
493+
# 바이너리 권한 확인
494+
ls -la /Users/redcourage/cryptolab/rune-project/rune/bin/rune-mcp
495+
# → -rwxr-xr-x ... 실행 권한 있어야 함
496+
497+
# 바이너리 직접 실행해서 응답 오는지 1회 검증 (§3 1.3과 동일)
498+
cd /Users/redcourage/cryptolab/rune-project/rune && ./bin/rune-mcp < /dev/null; echo "exit=$?"
499+
500+
# Claude Code 재시작 후 (수동), 새 세션에서:
501+
# /mcp ← 등록된 서버 목록 표시
502+
# "rune_diagnostics 호출해" ← Claude가 tool 인식하는지
503+
```
504+
505+
---
506+
507+
## 5. Troubleshooting
324508
325509
| 증상 | 가능한 원인 | 해결 |
326510
|---|---|---|
@@ -334,7 +518,7 @@ npx -y @modelcontextprotocol/inspector ./bin/rune-mcp
334518
335519
---
336520
337-
## 5. 코드 변경 요약
521+
## 6. 코드 변경 요약
338522
339523
### `cmd/rune-mcp/main.go` (rewrite, 80줄)
340524
@@ -373,7 +557,7 @@ coverage.out
373557
374558
---
375559
376-
## 6. 다음 마일스톤
560+
## 7. 다음 마일스톤
377561
378562
이 문서가 통과되면 다음 둘 중 선택:
379563

0 commit comments

Comments
 (0)