Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ __pycache__/
.worktrees/

# Internal Planning
docs/planning/
docs/planning/

# Go build artifacts
bin/
*.test
coverage.out
59 changes: 46 additions & 13 deletions cmd/rune-mcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,69 @@
// Tools: 8 MCP tools (capture, recall, batch_capture, capture_history,
// delete_capture, vault_status, diagnostics, reload_pipelines).
//
// Phase A (current): MCP handshake + tools/list only. All 8 handlers return
// "not yet implemented" CallToolResult. RunBootLoop · Vault · envector ·
// embedder are not wired. Phase 4-5 brings real adapters + service logic.
//
// Python reference: mcp/server/server.py (2002 LoC)
package main

import (
"context"
"log"
"errors"
"log/slog"
"os"
"os/signal"
"syscall"

sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"

"github.com/envector/rune-go/internal/mcp"
Comment thread
esifea marked this conversation as resolved.
)

// version is the rune-mcp protocol version surfaced in MCP `initialize`.
// Phase A is "0.4.0-alpha" until adapters are wired.
const version = "0.4.0-alpha"

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// TODO Phase 1: config.Load("~/.rune/config.json") — 3-section schema
// TODO Phase 2: lifecycle.RunBootLoop(ctx, deps) — Vault.GetPublicKey retry
// TODO Phase 3: mcp.NewServer + RegisterTools + Serve(stdio)
// TODO Phase 4: graceful shutdown (30s) on stdin EOF / SIGTERM
//
// Python reference: server.py:main() + RunMCPServer lifecycle.

// SIGINT / SIGTERM → cancel ctx → srv.Run unblocks.
// stdin EOF (Claude window closed) also unblocks Run via the StdioTransport.
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
select {
case <-sigCh:
cancel()
case <-ctx.Done():
}
}()

log.Println("rune-mcp skeleton — not yet implemented")
// Phase A: empty Deps. RunBootLoop / config.Load / adapter wiring deferred.
deps := &mcp.Deps{}

select {
case <-ctx.Done():
case <-sigCh:
cancel()
srv := sdkmcp.NewServer(&sdkmcp.Implementation{
Name: "rune-mcp",
Version: version,
}, nil)

if err := mcp.Register(srv, deps); err != nil {
slog.Error("rune-mcp register failed", "err", err)
os.Exit(1)
}

if err := srv.Run(ctx, &sdkmcp.StdioTransport{}); err != nil && !isNormalShutdown(err) {
slog.Error("rune-mcp serve error", "err", err)
os.Exit(1)
}
}

// isNormalShutdown reports whether err corresponds to expected stdio teardown.
// The SDK's `Connection.Wait` filters io.EOF to nil before returning, so on
// stdin EOF Run returns nil. The only other expected exit is ctx cancel from
// SIGINT/SIGTERM, which surfaces as context.Canceled.
func isNormalShutdown(err error) bool {
return err == nil || errors.Is(err, context.Canceled)
}
11 changes: 8 additions & 3 deletions docs/v04/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ docs/v04/
│ │ └── envector.md # envector-go SDK
│ └── python-mapping.md # Python 파일/LoC → Go 구조 매핑
└── notes/ # 내부 작업 노트 (참고용)
├── verification-matrix.md # Python↔Go bit-identical 대조 검증 로그
└── implementability-report.md # Go 개발자 진입 가능성 검증 리포트
├── notes/ # 내부 작업 노트 (참고용)
│ ├── verification-matrix.md # Python↔Go bit-identical 대조 검증 로그
│ ├── implementability-report.md # Go 개발자 진입 가능성 검증 리포트
│ └── flow-matrix.md # 10 flow × 파일 매트릭스 + Tier S/A/B 공통 모듈
└── progress/ # 실제 개발 진행 추적 (vertical slice 단위)
├── README.md # 인덱스 + 마일스톤별 상태표
└── phase-a-mcp-boot.md # MCP handshake + tools/list (`19b7bf6`)
```

## 읽는 순서
Expand Down
2 changes: 2 additions & 0 deletions docs/v04/notes/flow-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
| 11 | `adapters/logio/capture_log.go::Append` | 3/10 | write tool 3개 (CAP·BAT·DEL) |

> **TM** = teammate scope. 타입 시그니처는 팀원 확정에 의존하지만, 내 구현 코드의 거의 모든 경로가 이 에러 타입을 참조한다.
>
> **현재 상태 (2026-04-25, Phase A 합격 후)**: Tier S 1·2·4 (`Deps`, `obs/slog`, `lifecycle/boot`)는 본 매트릭스가 "stdlib-only로 즉시 시작 가능"이라 분류했지만, 실제로는 Phase A가 vertical slice 우선 (SDK 연결)로 진행되며 **셋 다 still skeleton 상태**다. `Deps`는 빈 struct, `obs/slog`은 `NewRequestID() == ""` stub, `lifecycle/boot.go::RunBootLoop`은 `_ = ctx`. Phase A.5 또는 Phase 4 진입 전에 보강 필요. 자세한 phase별 상태는 [`docs/v04/progress/`](../progress/).

---

Expand Down
46 changes: 46 additions & 0 deletions docs/v04/progress/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# `progress/` — 실제 개발 진행 추적

본 디렉토리는 **`docs/v04/`의 spec(How)·overview(Why)와 별개로**, 실제 구현이 어디까지 진행됐는지·어떤 단면이 동작하는지·어떻게 검증할 수 있는지를 시간순으로 기록한다.

- **spec/** — "어떻게 만들어야 하는가" (변하지 않는 계약)
- **overview/** — "왜 이렇게 만드는가" (결정·근거)
- **notes/** — bit-identical 검증 로그 등 일회성 작업 노트
- **progress/** ← **여기**: "지금 어디까지 동작하는가 + 어떻게 직접 확인하는가"

## 진행 추적 단위

README의 7-Phase 로드맵(Phase 1 외부 deps → Phase 7 검증)이 **horizontal slice**라면, progress 문서는 **vertical slice** 단위로도 작성될 수 있다. 예를 들어 "MCP handshake만 통과시키는 Phase A"는 7-Phase 어디에도 정확히 매핑되지 않지만, end-to-end 단면이 동작하는 **첫 마일스톤**으로서 별도 문서를 갖는다.

## 문서 명명 규칙

- 하나의 마일스톤 = 하나의 파일 = `<phase-id>-<짧은 설명>.md`
- `phase-a-mcp-boot.md` (handshake + tools/list)
- `phase-1-deps.md` (외부 deps 추가)
- `phase-4a-vault-client.md` (Phase 4 중 Vault 부분)
- 각 문서 상단에 **관련 커밋 SHA · 브랜치 · PR 링크** 명시
- "기능" + "확인법(여러 레벨)" 두 섹션은 필수
- "한계" + "Troubleshooting"은 권장

## 현재 인덱스

| 마일스톤 | 상태 | 문서 | 관련 커밋 |
|---|---|---|---|
| Phase A — MCP boot (handshake + tools/list) | ✅ 합격 | [phase-a-mcp-boot.md](phase-a-mcp-boot.md) | `19b7bf6` (브랜치 `yg/first-mcp-boot`) |
| Phase A.5 — smoke test 추가 (CI 회귀 방지) | ⏳ 예정 | — | — |
| Phase B — `rune_diagnostics` environment 섹션 진짜 응답 (stdlib only) | ⏳ 예정 | — | — |
| Phase 1 — `go.mod` 외부 deps 본격 추가 (gRPC · envector SDK · embedder proto) | ⏳ 예정 | — | — |
| Phase 2 — `internal/domain` + `internal/policy` 순수 로직 (TM scope) | ⏳ 예정 | — | — |
| Phase 3 — `record_builder` 703 LoC + `payload_text` 364 LoC 포팅 (TM scope) | ⏳ 예정 | — | — |
| Phase 4a — Vault 클라이언트 + 부팅 시퀀스 연결 | ⏳ 예정 | — | — |
| Phase 4b — envector SDK 연결 (Q4 PR 머지 후) | ⏳ 예정 | — | — |
| Phase 4c — embedder 클라이언트 | ⏳ 예정 | — | — |
| Phase 5 — service 레이어 오케스트레이션 (`stubHandler` → 실제 service 호출) | ⏳ 예정 | — | — |
| Phase 7 — golden fixture 기반 bit-identical 검증 | ⏳ 예정 | — | — |

> Phase 6 (MCP wiring)은 Phase A에서 부분 선행됐으므로 별도 마일스톤으로 빼지 않음. Phase 5의 service 호출 교체에 흡수됨.

## 사용 방식

- **개발자**: 새 vertical slice를 시작할 때 이 디렉토리에 새 파일을 만들고, 합격 기준을 명시한 뒤 그 기준에 맞춰 작업한다. 파일은 PR과 함께 같은 커밋에 묶는다.
- **리뷰어**: PR을 받았을 때 이 문서의 "확인법"을 그대로 따라 해보면 변경사항이 제대로 동작하는지 검증할 수 있다.
- **다른 팀원**: 어디까지 동작하고 어디부터 미구현인지 한 곳에서 본다 (spec과 다름 — spec은 "최종 모양", progress는 "지금 모양").
182 changes: 182 additions & 0 deletions docs/v04/progress/phase-a-mcp-boot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Phase A — MCP boot

> ✅ 통과 (2026-04-25) · 커밋 `19b7bf6` + `e80d6ea` · 브랜치 `yg/first-mcp-boot`
> 핵심 파일 2개: `cmd/rune-mcp/main.go` (74줄) · `internal/mcp/tools.go` (134줄)

## 1. 한 줄 요약

`rune-mcp` Go 바이너리가 **MCP 프로토콜 표면**(handshake + tools/list + tools/call)만 살아있는 상태. 외부 deps 0, 비즈니스 로직 0, 8개 tool 모두 stub.

**가치** — Claude Code가 이 바이너리를 spawn 하면 8 tool 카탈로그를 정상 인식. 이후 phase는 stub 본체만 채우면 끝. 검증 회로는 매 phase 재사용.

## 2. 동작하는 것 / 안 하는 것

**동작**

- `go build` 한 번에 8.3 MB 정적 바이너리 (Go 1.25+)
- MCP `initialize` handshake — `serverInfo: rune-mcp 0.4.0-alpha`
- `tools/list` — 8 tool 광고 (input/output schema는 Go struct에서 자동 추론)
- `tools/call` — 모두 `isError:true` + "not yet implemented" 응답 (JSON-RPC 자체는 valid)
- 종료: stdin EOF · SIGINT · SIGTERM 모두 exit 0

**안 함 (의도된 한계)**

| 영역 | 가능 시점 |
|---|---|
| 비즈니스 로직 (capture/recall 등) | Phase 5 |
| Vault / envector / embedder adapter | Phase 4 |
| `lifecycle.Manager` 상태 머신 | Phase 4 |
| `config.json` 로딩, `capture_log.jsonl` IO | Phase 4 |
| `request_id` 로깅, `SensitiveFilter` redaction | Phase 4 |

→ Phase A의 정확한 범위는 **"MCP 프로토콜 표면이 정상 동작한다"** 까지.

## 3. 8 tool 카탈로그

```
rune_batch_capture, rune_capture, rune_capture_history, rune_delete_capture,
rune_diagnostics, rune_recall, rune_reload_pipelines, rune_vault_status
```

(SDK가 알파벳순 정렬해 광고 — Python 원본과 bit-identical한 8 이름)

각 tool의 input/output schema는 `internal/domain/*` · `internal/service/*` 의 Go struct에서 SDK가 자동 추론. **Go 타입 = MCP API 계약**, 별도 IDL 없음.

## 4. 검증 — 5분 컷

### 4.1. 빌드 & 헬스 체크

```bash
cd <repo-root>
go build -o bin/rune-mcp ./cmd/rune-mcp
./bin/rune-mcp < /dev/null; echo "exit=$?" # → exit=0
```

### 4.2. MCP 시퀀스 — `tools/list` 까지

순서: `initialize` → `notifications/initialized` → `tools/list`

```bash
{
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
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
sleep 0.1
printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
sleep 0.5
} | ./bin/rune-mcp 2>/dev/null | jq -r 'select(.id==2) | .result.tools[].name'
```

**기대**: 위 §3의 8 이름.

### 4.3. tool 한 개 호출 (stub 응답)

```bash
{
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
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
sleep 0.1
printf '%s\n' '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rune_diagnostics","arguments":{}}}'
sleep 0.5
} | ./bin/rune-mcp 2>/dev/null | jq 'select(.id==3).result | {isError, text:.content[0].text}'
```

**기대**: `isError:true`, text는 `"rune_diagnostics is not yet implemented..."`.

> **MCP framing 핵심 3개**
> ① `initialize` → `notifications/initialized` → `tools/*` **순서 필수**
> ② 각 메시지는 `\n` 종결 (LSP의 Content-Length 미사용)
> ③ 마지막 `sleep 0.3~0.5` 없으면 EOF로 응답 끊김

### 4.4. Claude Code 등록 (선택)

`~/.claude/mcp.json`:

```json
{
"mcpServers": {
"rune-go-dev": {
"command": "<repo-root>/bin/rune-mcp"
}
}
}
```

> `<repo-root>` 는 본인 체크아웃 경로의 **절대 경로**로 치환 (`~`/상대경로 미지원).

Claude 재시작 후 `/mcp` 에서 `rune-go-dev` 가 connected 표시되면 합격. tool 호출하면 빨간 "not implemented" 응답 — **이게 정상**.

> ⚠️ 기존 Python `envector` MCP가 같은 8 이름 광고. namespace는 `mcp__rune-go-dev__*` vs `mcp__envector__*` 로 분리돼 충돌은 없지만 카탈로그가 중복 보임.

### 4.5. MCP Inspector (시각적, 선택)

```bash
npx -y @modelcontextprotocol/inspector ./bin/rune-mcp
```

브라우저(`localhost:6274`)에 8 tool 리스트 + schema + raw JSON-RPC.

## 5. 8 tool 한 번씩 호출 — `mcp_call` 헬퍼

현재 셸에 paste:

```bash
mcp_call() {
local tool="$1"; local args="$2"; [ -z "$args" ] && args='{}'
{
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
printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}'
sleep 0.1
printf '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"%s","arguments":%s}}\n' "$tool" "$args"
sleep 0.5
} | ./bin/rune-mcp 2>/dev/null | jq -c 'select(.id==2)'
}
```

> ⚠️ default value를 `${2:-{}}` 로 쓰면 bash parameter expansion이 깨짐. 위 `[ -z ... ] && args='{}'` 패턴 필수.

```bash
# 인자 없는 4개
mcp_call rune_diagnostics
mcp_call rune_vault_status
mcp_call rune_reload_pipelines
mcp_call rune_capture_history

# 인자 필수 4개
mcp_call rune_recall '{"query":"hello"}'
mcp_call rune_capture '{"text":"hi","source":"test","extracted":{}}'
mcp_call rune_delete_capture '{"record_id":"dec_test"}'
mcp_call rune_batch_capture '{"items":"[]"}'
```

8개 모두 동일한 stub 응답.

## 6. Troubleshooting

| 증상 | 해결 |
|---|---|
| `go build`: `go >= 1.25.0 required` | `go install golang.org/dl/go1.25@latest && go1.25 download` 또는 brew 업그레이드 |
| `go build`: `missing go.sum entry` | `go mod tidy` 후 재빌드 |
| 응답이 안 옴 / 끊김 | 마지막 `sleep` 을 0.5+ 로 |
| Claude Code 에서 tool 미인식 | `cat ~/.claude/mcp.json \| jq .` 로 JSON 검증 + `chmod +x bin/rune-mcp` + 절대 경로 |
| coproc syntax error (macOS bash 3.2) | `brew install bash` 또는 zsh 사용 |

## 7. 다음 마일스톤

**가벼운 후속**

- **Phase A.5 (smoke test)** — `internal/mcp/register_test.go` 에 `mcp.NewInMemoryTransports()` 로 in-memory 서버 띄워 tools/list 8개 회귀 가드. ~50 LoC, CI에서 `AddTool` schema-inference 회귀 자동 감지
- **Phase B** — `rune_diagnostics` environment 섹션 stdlib 응답 (`runtime.GOOS` · `runtime.Version` · `os.Getwd`). 첫 진짜 응답 흐름, 2-3시간 PR

**7-Phase 로드맵 본격 진입** (서로 병렬 가능)

- **Phase 1** — 외부 deps (gRPC · protobuf · envector-go SDK · embedder proto stub)
- **Phase 2** — `internal/domain` + `internal/policy` 순수 로직 (TM scope, 외부 deps 0)
- **Phase 3** — `record_builder` 703 LoC + `payload_text` 364 LoC 라인 단위 포팅
- **Phase 4a/b/c** — Vault / envector / embedder adapter (Phase 1 머지 후)
- **Phase 5** — service 오케스트레이션. `stubHandler` → `service.X.Handle` 교체
- **Phase 7** — 검증 (golden fixture byte-identical · bufconn · Python↔Go shadow run)

→ 의존성 그래프는 `flow-matrix.md §5-d`. Phase 6은 본 문서가 부분 선행이라 Phase 5에 흡수.
25 changes: 18 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
module github.com/envector/rune-go

go 1.24
go 1.25.9

// External dependencies to be added as implementation progresses:
// External dependencies, in implementation order:
//
// github.com/modelcontextprotocol/go-sdk v1.5.0 — MCP protocol (D2)
// google.golang.org/grpc v1.65.0 — Vault / envector / embedder clients
// google.golang.org/protobuf v1.34.0 — generated stubs
// github.com/CryptoLabInc/envector-go-sdk — envector FHE client (Q4 PR pending)
// github.com/modelcontextprotocol/go-sdk v1.5.0 — MCP protocol (D2) ✅ Phase A
// google.golang.org/grpc v1.65.0 — Vault / envector / embedder clients (Phase 4)
// google.golang.org/protobuf v1.34.0 — generated stubs (Phase 4)
// github.com/CryptoLabInc/envector-go-sdk — envector FHE client (Q4 PR pending)
//
// Skeleton stage: stdlib only. No external imports yet.
// go 1.25.0 + toolchain pin required by the MCP SDK.

require github.com/modelcontextprotocol/go-sdk v1.5.0

require (
github.com/google/jsonschema-go v0.4.2 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sys v0.41.0 // indirect
)
Loading
Loading