v0.5.1: MCP tool 출력 강타입화 (PATCH)#17
Merged
Merged
Conversation
callable Discriminator + Tag 유니온의 자동 schema 가 oneOf 11 변형으로 펼쳐지는데, UnknownBlock 의 `kind: str` + `extra: allow` 가 known kind 인스턴스 (예: 빈 ParagraphBlock) 와도 매칭 → jsonschema strict oneOf exactly-one 위반 → fastmcp Client 가 RuntimeError. 결과적으로 v0.5.1 typed output 의 wire format 호환이 client side 에서 깨짐. Field(json_schema_extra=callable) 로 schema 에 not.enum: sorted(_KNOWN_KINDS) 삽입. callable 분리는 _KNOWN_KINDS 가 모듈 정의 순서상 뒤에 위치하는 NameError 회피. 런타임 동작 0 변경 — model_validate / round-trip / forward-compat 라우팅 모두 보존, schema export 만 strict 화. packaged hwp_ir_v1.json 도 자동 재생성. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dict[str, Any] / list[dict[str, Any]] 반환을 Pydantic V2 모델로 전환: - get_ir(path) -> HwpDocument - iter_blocks(path, ...) -> list[Block] - chunks(path, ...) -> list[ChunkRecord] ChunkRecord 신규 BaseModel — page_content: str + metadata: dict[str, Any]. metadata 는 mode × block kind 동적 키 집합 (HwpLoader SSOT) 이라 자유 dict 유지 — mode 별 분기 모델 (3-11 배 schema 비대) 거부. 호출 시그니처 / 입력 schema / wire format (result.structured_content) 모두 v0.5.0 과 byte-equal — fastmcp 자동 직렬화에 위임 (model_dump(mode="json") 수동 호출 제거). outputSchema 만 약타입 → 강타입으로 강화 (LLM 응답 정확도). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 신규 테스트 클래스 + 기존 dict access 검증 typed model 전환:
- TestTypedSignatures (AC-1, AC-2, AC-3, AC-7) — 정적 반환 어노테이션 +
ChunkRecord.metadata 자유 dict
- TestTypedOutputSchema (AC-4) — fastmcp 자동 outputSchema 가 HwpDocument
defs / Block oneOf 11 변형 / ChunkRecord page_content+metadata 노출
- TestBackwardsCompat (AC-5) — fastmcp Client in-process 의 byte-equal
회귀 가드. wrap 분기 정확화: HwpDocument = inline / list[T] = {"result":...}
- TestTypedClientData (AC-6) — result.data 의 typed-or-dict 분기. fastmcp v3
의 oneOf deserialization 한계로 iter_blocks list element 는 dict 폴백,
v0.5.0 access 패턴 그대로 동작
- TestTypedModelRoundTrip — Pydantic dump → load → equality
기존 TestGetIr / TestIterBlocks / TestChunks 의 dict 인덱싱 검증을
typed attribute access 로 교체.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- roadmap/v0.5.1/mcp-typed-output.md (Draft) — 결정 8 항목 + AC-1~AC-10.
결정 8 (UnknownBlock.kind not.enum), § wire format byte-equal 의 wrap
분기 표 (BaseModel = inline / list[T] = {"result": ...}), AC-5/AC-6 의
fastmcp v3 한계 반영
- design/v0.5.1/mcp-typed-output-research.md (Draft) — 결정 매트릭스 row 5
+ § 5 (UnknownBlock not.enum 옵션 비교 + 검증자 반박 + 1차 소스). § 2
검증자 반박에 fastmcp client deserialization 한계 추가
- implementation/v0.5.1/migration.md (Frozen) — 본 PATCH 의 산출물 / 결정
매핑 / 호환성 / 검증 / AC↔테스트 매핑 / fastmcp v3 한계 / GA 절차 인계
- README.md MCP 도구 표 출력 컬럼 갱신 + v0.5.1 마이그 노트
- roadmap/README.md v0.5.1 row Draft 인덱스 등록
- traces/coverage.md 16 v0.5.1 AC mappings 자동 갱신
Cargo.toml bump / CHANGELOG [0.5.1] / spec Draft→Frozen flip / git tag 는
별도 GA 절차 step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cargo.toml version bump 0.5.0 → 0.5.1 (pyproject.toml 의 dynamic = ["version"] 가 본 SSOT 를 읽음 — publish.yml::verify-version 이 git tag 와 일치 가드). CHANGELOG.md [0.5.1] — 2026-05-07 섹션 신설: - Added: ChunkRecord BaseModel (page_content + metadata: dict[str, Any]) - Changed: get_ir / iter_blocks / chunks 출력 강타입화, README 도구 표 + 마이그 - Fixed: UnknownBlock.kind 의 JSON Schema not.enum constraint (fastmcp strict oneOf 호환) - Build: submodule pin 62a458a (v0.7.10) 유지, extras / 의존성 변경 없음 - Notes: spec / ADR / migration 위치, fastmcp v3 한계 2 가지 언급 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
frontmatter flip: - roadmap/v0.5.1/mcp-typed-output.md: status: Draft → Frozen, target → ga - design/v0.5.1/mcp-typed-output-research.md: 동일 - implementation/v0.5.1/migration.md: target → ga (status 는 이미 Frozen — pre-GA stage 면제 그대로 적용) roadmap/README.md: - 현재 상태 v0.5.1 row: Draft → Frozen GA (2026-05-07) - 활성 spec 인덱스 row: Draft → Frozen - v0.6.0+ narrative + quoted note 의 v0.5.1 표기: Draft → Frozen - 구현/검증 로그 표에 v0.5.1 → implementation/v0.5.1/migration.md row 추가 Living-policy schema migration — CONVENTIONS § Frozen 면제 조항 정합 (GA 시점 frontmatter flip 은 허용된 in-place 갱신). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v0.5.0 GA 한
rhwp-mcp의 7 도구 중 약타입 (dict[str, Any]/list[dict[str, Any]]) 으로 반환하던 3 도구 (get_ir/iter_blocks/chunks) 의 출력을 Pydantic V2 모델 (HwpDocument/list[Block]/list[ChunkRecord]) 로 강타입화. fastmcp v3 의 자동 outputSchema 가 약타입 (additionalProperties: true만) → 강타입 (필드별 type / Discriminator + Tag 11 변형 / required 명시) 으로 강화 — LLM 응답 정확도 향상. wire format byte-equal — 외부 클라이언트 (Claude Desktop / Cline 등) 영향 0.변경 요약
python/rhwp/mcp/tools.pyChunkRecord신규 BaseModelpython/rhwp/ir/nodes.pyUnknownBlock.kindJSON Schemanot.enumconstraint — fastmcp strictoneOf호환. 런타임 동작 0 변경, schema export 만 strict 화python/rhwp/ir/schema/hwp_ir_v1.jsontests/test_mcp_server.pyresult.data/ Pydantic round-tripCargo.toml+CHANGELOG.md[0.5.1]섹션README.md작업 중 표면화된 fastmcp v3 한계 (spec 본문 정확화)
result.structured_contentwrap 분기 — BaseModel = inline (no wrap) /list[T]={"result": [...]}envelope. spec § wire format byte-equal 의 wrap 분기 표 추가.oneOf11 변형을 dynamic 모델로 변환 못 해 list element dict 폴백. server side 의 typed 출력은 AC-2 가 cover, wire format byte-equal 은 AC-5 가 cover. v0.5.0 dict access 패턴 그대로 backwards-compat.oneOfvalidation 충돌 — ParagraphBlock + UnknownBlock 양쪽 valid 인스턴스에서RuntimeError: Invalid structured content. 결정 8 (UnknownBlock.kindnot.enum) 추가로 해결.Why
v0.5.0 S3 의
code-reviewer가 LOW-2 로 권고: "iter_blocks / get_ir / chunks 가 같은 패턴 (list[dict[str, Any]]) 이라 일괄 후속 polish 처리". v0.5.0 GA (2026-05-06) 직후 후속 polish 시점이 v0.5.1 PATCH. outputSchema 본문이 48 bytes (additionalProperties: trueonly) → 약 28-32 KB (Discriminator + Tag 11 변형,$defs약 20 종) 로 확장 — LLM 의 응답 해석 / 후속 도구 호출 정확도에 측정 가능한 개선.Related Issues
없음 —
code-reviewerLOW-2 후속 polish.검증
uv run pytest tests/ -m "not slow"— 585 passed, 2 skipped (pre-existing aift fixture), 6 deselecteduv run pyright python/rhwp/ tests/test_mcp_server.py— 0 errors (변경 파일) + 4 intentionaltests/type_check_errors.py보존uv run ruff check(변경 파일) — cleanuv run python scripts/lint_docs.py— exit 0uv run python scripts/generate_spec_trace.py --check— up to date (16 v0.5.1 mappings)Spec / ADR / Migration
Commit history
머지 후 GitHub Release
v0.5.1published 로 publish.yml 트리거 (Trusted Publisher OIDC → PyPI 자동 배포).