feat: deploy jarca ledger network mcp service
- add Docker deployment for independent jarca-ledger container - enable FastMCP streamable-http runtime configuration - document dcy-013, REQ-019, and BAT-015 verification evidence - update structure/runtime baselines and deployment runbook
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
.git
|
||||
.venv
|
||||
.pytest_cache
|
||||
.ruff_cache
|
||||
__pycache__
|
||||
*.py[cod]
|
||||
*.egg-info
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.hermes
|
||||
.worktrees
|
||||
*.sqlite3
|
||||
*.sqlite
|
||||
*.db
|
||||
*.db-*
|
||||
*.pdf
|
||||
*.xls
|
||||
*.xlsx
|
||||
*.csv
|
||||
tmp_*
|
||||
docs
|
||||
scripts
|
||||
tests
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
JARCA_LEDGER_DB_PATH=/data/ledger.sqlite3 \
|
||||
JARCA_MCP_TRANSPORT=streamable-http \
|
||||
JARCA_MCP_HOST=0.0.0.0 \
|
||||
JARCA_MCP_PORT=8000 \
|
||||
JARCA_MCP_STREAMABLE_HTTP_PATH=/mcp
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml README.md ./
|
||||
COPY jarca_ledger_mcp ./jarca_ledger_mcp
|
||||
COPY dashboard ./dashboard
|
||||
|
||||
RUN python -m pip install --upgrade pip \
|
||||
&& python -m pip install '.[dashboard]'
|
||||
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 8000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD python -c "import os, socket; s=socket.create_connection(('127.0.0.1', int(os.getenv('JARCA_MCP_PORT', '8000'))), timeout=3); s.close()"
|
||||
|
||||
CMD ["jarca-ledger-mcp"]
|
||||
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
jarca-ledger:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: jarca-ledger:latest
|
||||
container_name: jarca-ledger
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
JARCA_LEDGER_DB_PATH: /data/ledger.sqlite3
|
||||
JARCA_MCP_TRANSPORT: streamable-http
|
||||
JARCA_MCP_HOST: 0.0.0.0
|
||||
JARCA_MCP_PORT: "8000"
|
||||
JARCA_MCP_STREAMABLE_HTTP_PATH: /mcp
|
||||
PYTHONUNBUFFERED: "1"
|
||||
volumes:
|
||||
- /volume1/docker/jarca-ledger/data:/data
|
||||
@@ -0,0 +1,44 @@
|
||||
# BAT-015 Design
|
||||
|
||||
## Architecture Decision
|
||||
|
||||
`jarca-ledger` is deployed as an independent Docker service that directly serves FastMCP `streamable-http` transport on port 8000. `genichin-agent` is no longer the process launcher for Jarca Ledger MCP. It may remain a client, but the Jarca Ledger service lifecycle belongs to Docker.
|
||||
|
||||
## Runtime topology
|
||||
|
||||
```text
|
||||
finance-agent / Hermes client
|
||||
│
|
||||
│ Streamable HTTP MCP
|
||||
▼
|
||||
http://192.168.55.253:8000/mcp
|
||||
│
|
||||
▼
|
||||
jarca-ledger container
|
||||
├─ jarca-ledger-mcp FastMCP server
|
||||
├─ /data/ledger.sqlite3
|
||||
└─ Docker healthcheck on 127.0.0.1:8000
|
||||
│
|
||||
▼
|
||||
Synology host /volume1/docker/jarca-ledger/data
|
||||
```
|
||||
|
||||
## Transport contract
|
||||
|
||||
- Default remains `stdio` when no env is set.
|
||||
- `JARCA_MCP_TRANSPORT=streamable-http` runs Uvicorn/FastMCP network transport.
|
||||
- `JARCA_MCP_HOST=0.0.0.0`, `JARCA_MCP_PORT=8000`, `JARCA_MCP_STREAMABLE_HTTP_PATH=/mcp` define the Docker service endpoint.
|
||||
- Tool names and tool payload contracts remain unchanged.
|
||||
|
||||
## Data migration design
|
||||
|
||||
- Source: `jarca:/root/.local/share/jarca/ledger.sqlite3`
|
||||
- Target: `jarca-ledger:/data/ledger.sqlite3`
|
||||
- Snapshot method: SQLite `Connection.backup()` to avoid copying a live DB mid-write.
|
||||
- Pre-replacement safety: back up the new empty `/data/ledger.sqlite3` under `/data/backups/pre_migration_empty_<timestamp>.sqlite3`.
|
||||
- Verification: sha256, `PRAGMA integrity_check`, table counts, MCP `get_ledger_db_status`.
|
||||
|
||||
## Security and exposure boundary
|
||||
|
||||
- This batch exposes MCP only on the Synology host/internal network port mapping `8000:8000`.
|
||||
- Internet exposure, reverse proxy, TLS, and auth policy are not included and must be handled as a separate approved change before external access.
|
||||
@@ -0,0 +1,41 @@
|
||||
# BAT-015 Implementation
|
||||
|
||||
## Code changes
|
||||
|
||||
- `jarca_ledger_mcp/adapters/mcp/server.py`
|
||||
- Added `McpRuntimeConfig` dataclass.
|
||||
- Added env parsing for `JARCA_MCP_TRANSPORT`, `JARCA_MCP_HOST`, `JARCA_MCP_PORT`, `JARCA_MCP_STREAMABLE_HTTP_PATH`, `JARCA_MCP_SSE_PATH`, `JARCA_MCP_MESSAGE_PATH`, `JARCA_MCP_STATELESS_HTTP`.
|
||||
- Instantiates FastMCP with configured host/port/path values.
|
||||
- Calls `mcp.run(transport=_runtime_config.transport)`.
|
||||
- `tests/test_mcp_http_runtime.py`
|
||||
- Added config tests for default stdio, streamable-http env, and invalid transport rejection.
|
||||
|
||||
## Deployment files
|
||||
|
||||
- `Dockerfile`
|
||||
- Builds `jarca-ledger:latest` from `python:3.12-slim`.
|
||||
- Installs `.[dashboard]` dependencies.
|
||||
- Exposes port 8000 and runs `jarca-ledger-mcp`.
|
||||
- Adds TCP healthcheck against `127.0.0.1:${JARCA_MCP_PORT}`.
|
||||
- `.dockerignore`
|
||||
- Excludes `.env`, DBs, financial source files, tests/docs/scripts not needed in image, and local artifacts.
|
||||
- `docker-compose.yml`
|
||||
- Defines `container_name: jarca-ledger`.
|
||||
- Maps `8000:8000`.
|
||||
- Mounts `/volume1/docker/jarca-ledger/data:/data`.
|
||||
- Sets streamable-http MCP env.
|
||||
- Uses `restart: unless-stopped`.
|
||||
|
||||
## Data migration implementation
|
||||
|
||||
1. Created a consistent source snapshot inside the old `jarca` container using SQLite backup API.
|
||||
2. Copied snapshot to the host temp path.
|
||||
3. Backed up the initial empty `jarca-ledger` DB under `/data/backups/pre_migration_empty_20260705152129.sqlite3`.
|
||||
4. Stopped `jarca-ledger`, removed WAL/SHM sidecars, copied the snapshot to `/data/ledger.sqlite3`, normalized permissions, and restarted.
|
||||
5. Waited for Docker health to become `healthy`.
|
||||
|
||||
## Documentation changes
|
||||
|
||||
- Added `docs/runbooks/docker-network-mcp-deploy.md`.
|
||||
- Added this StagePilot post-implementation trace: dcy-013, REQ-019, BAT-015.
|
||||
- Updated `docs/project-structure.md` and `docs/runtime-flows.md` to include Docker/network MCP runtime.
|
||||
@@ -0,0 +1,20 @@
|
||||
# BAT-015 Docker network MCP deployment
|
||||
|
||||
- Profile: standard
|
||||
- Status: release-candidate
|
||||
- Included REQ: REQ-019
|
||||
- Discovery: dcy-013
|
||||
- Owner: 렛저장인
|
||||
- Date (KST): 2026-07-07
|
||||
|
||||
## 목표
|
||||
|
||||
REQ-019에 따라 `jarca-ledger`를 `genichin-agent` 생명주기에서 분리된 독립 Docker service로 배포하고, streamable-http MCP endpoint와 Synology volume 기반 운영 DB를 검증한다.
|
||||
|
||||
## 산출물
|
||||
|
||||
- FastMCP runtime transport 설정화
|
||||
- Dockerfile / `.dockerignore` / `docker-compose.yml`
|
||||
- Docker network MCP 배포 runbook
|
||||
- 기존 `jarca` 운영 DB 이전
|
||||
- pytest, Docker health, MCP smoke, DB integrity/table-count verification
|
||||
@@ -0,0 +1,24 @@
|
||||
# BAT-015 Planning
|
||||
|
||||
## Scope
|
||||
|
||||
- Implement and verify REQ-019 Docker network MCP deployment.
|
||||
- Preserve existing stdio MCP compatibility while enabling `streamable-http` runtime mode.
|
||||
- Deploy `jarca-ledger` as an independent Docker service with Synology bind volume.
|
||||
- Migrate the existing operating DB from `jarca` to `jarca-ledger` with backup/snapshot evidence.
|
||||
- Refresh deployment runbook and baseline docs.
|
||||
|
||||
## Design Gate
|
||||
|
||||
- Required: yes
|
||||
- Rationale: this batch changes runtime topology, deployment ownership, exposed MCP transport, Docker health behavior, and operating DB location. A design/evidence trail is required even though the code diff is small.
|
||||
|
||||
## Work plan
|
||||
|
||||
1. Add MCP runtime config helpers for stdio/sse/streamable-http transport selection.
|
||||
2. Add tests for default stdio, streamable-http env config, and invalid transport rejection.
|
||||
3. Add Dockerfile, `.dockerignore`, compose service, healthcheck, and runbook.
|
||||
4. Build and run `jarca-ledger` with `/volume1/docker/jarca-ledger/data:/data`.
|
||||
5. Verify network MCP using a streamable-http client against `http://192.168.55.253:8000/mcp`.
|
||||
6. Snapshot existing `jarca` DB with SQLite backup API, back up the new empty DB, replace `/data/ledger.sqlite3`, restart, and verify table counts/integrity.
|
||||
7. Update StagePilot Discovery/SRS/BAT/baseline docs.
|
||||
@@ -0,0 +1,141 @@
|
||||
# BAT-015 Verification
|
||||
|
||||
## Verification summary
|
||||
|
||||
Status: PASS
|
||||
|
||||
The Docker network MCP deployment is implemented and running. `jarca-ledger` is independent of `genichin-agent`, exposes streamable-http MCP on port 8000, uses `/volume1/docker/jarca-ledger/data` as the operating volume, and serves the migrated operating ledger DB.
|
||||
|
||||
## Acceptance criteria evidence
|
||||
|
||||
### AC-1. Runtime config tests pass
|
||||
|
||||
- Command: `python -m pytest tests/test_mcp_http_runtime.py -q`
|
||||
- Result: PASS
|
||||
- Output: `... [100%]`
|
||||
|
||||
### AC-2. Full regression suite passes
|
||||
|
||||
- Command: `python -m pytest -q`
|
||||
- Result: PASS
|
||||
- Output: `........................ss..................ss... [100%]`
|
||||
|
||||
### AC-3. Docker build and independent service start succeed
|
||||
|
||||
- Command: `docker compose build`
|
||||
- Result: PASS, image `jarca-ledger:latest` built.
|
||||
- Command: `docker compose up -d --no-build`
|
||||
- Result: container `jarca-ledger` created/started.
|
||||
|
||||
Docker state evidence:
|
||||
|
||||
```text
|
||||
state=running health=healthy restart=unless-stopped
|
||||
```
|
||||
|
||||
Mount evidence:
|
||||
|
||||
```text
|
||||
/volume1/docker/jarca-ledger/data -> /data (bind)
|
||||
```
|
||||
|
||||
Log evidence:
|
||||
|
||||
```text
|
||||
StreamableHTTP session manager started
|
||||
Application startup complete.
|
||||
Uvicorn running on http://0.0.0.0:8000
|
||||
```
|
||||
|
||||
### AC-4. Network MCP smoke passes
|
||||
|
||||
Endpoint used from `genichin-agent` container:
|
||||
|
||||
```text
|
||||
http://192.168.55.253:8000/mcp
|
||||
```
|
||||
|
||||
Smoke evidence:
|
||||
|
||||
```text
|
||||
tool_count 56
|
||||
ledger_health_check success true
|
||||
db_path /data/ledger.sqlite3
|
||||
schema_version 10
|
||||
active_transactions 6225
|
||||
```
|
||||
|
||||
### AC-5. Operating DB migration preserved data
|
||||
|
||||
Source DB before migration:
|
||||
|
||||
```text
|
||||
jarca:/root/.local/share/jarca/ledger.sqlite3
|
||||
size 8343552
|
||||
sha256 f43532a41db3fd64edfe26d3dcc6f54ab8cf34bf79018fed9e53324996e4413b
|
||||
accounts 8
|
||||
transactions 6312
|
||||
import_batches 13
|
||||
category_rules 46
|
||||
ledger_categories 62
|
||||
```
|
||||
|
||||
Migration snapshot evidence:
|
||||
|
||||
```text
|
||||
snapshot_integrity ok
|
||||
snapshot_size 8343552
|
||||
snapshot_sha256 e940a237e25e71561fe1af332f29cb11e92f02612b4ff89a7ab562585f82f278
|
||||
```
|
||||
|
||||
Post-migration target evidence:
|
||||
|
||||
```text
|
||||
path /data/ledger.sqlite3
|
||||
size 8343552
|
||||
sha256 e940a237e25e71561fe1af332f29cb11e92f02612b4ff89a7ab562585f82f278
|
||||
integrity ok
|
||||
accounts 8
|
||||
transactions 6312
|
||||
budgets 0
|
||||
import_batches 13
|
||||
category_rules 46
|
||||
ledger_categories 62
|
||||
manual_assets 0
|
||||
asset_snapshots 0
|
||||
```
|
||||
|
||||
MCP `get_ledger_db_status` evidence:
|
||||
|
||||
```text
|
||||
integrity_check ok
|
||||
table_counts.accounts 8
|
||||
table_counts.transactions 6312
|
||||
table_counts.import_batches 13
|
||||
table_counts.category_rules 46
|
||||
table_counts.ledger_categories 62
|
||||
```
|
||||
|
||||
### AC-6. Documentation and baselines updated
|
||||
|
||||
- `docs/runbooks/docker-network-mcp-deploy.md` added.
|
||||
- `docs/project-structure.md` updated with Dockerfile/compose/network MCP service and deployment boundaries.
|
||||
- `docs/runtime-flows.md` updated with Docker network MCP and DB migration flows.
|
||||
- StagePilot trace added: `dcy-013`, `REQ-019`, `BAT-015`.
|
||||
|
||||
## Safety boundary
|
||||
|
||||
- No raw financial statement source files were copied into the image.
|
||||
- `.dockerignore` excludes DB/source financial files from image build context.
|
||||
- Existing empty target DB was backed up before replacement.
|
||||
- Existing source DB was not modified; migration used a snapshot copy.
|
||||
|
||||
## Blocking Issues
|
||||
|
||||
- None for internal Synology network operation.
|
||||
|
||||
## Follow-up candidates
|
||||
|
||||
- Add remote MCP endpoint registration to the finance-agent profile.
|
||||
- Consider a separate auth/TLS/reverse-proxy Discovery before any external network exposure.
|
||||
- Consider an automated backup scheduler as a separate operations requirement.
|
||||
@@ -22,3 +22,4 @@
|
||||
| BAT-012 | standard | release-candidate | REQ-016 | dcy-011 | [bat-012_20260703_import-pipeline-split](bat-012_20260703_import-pipeline-split/) |
|
||||
| BAT-013 | standard | release-candidate | REQ-017 | dcy-011 | [bat-013_20260703_advanced-workflow-service-split](bat-013_20260703_advanced-workflow-service-split/) |
|
||||
| BAT-014 | standard | release-candidate | REQ-018 | dcy-011 | [bat-014_20260703_adapter-surface-narrowing-baseline-refresh](bat-014_20260703_adapter-surface-narrowing-baseline-refresh/) |
|
||||
| BAT-015 | standard | release-candidate | REQ-019 | dcy-013 | [bat-015_20260707_docker-network-mcp-deployment](bat-015_20260707_docker-network-mcp-deployment/) |
|
||||
|
||||
+2
-2
@@ -5,9 +5,9 @@
|
||||
- 문서 ID: dcy-011_20260703_backend-refactor-phase4-write-import-workflow-adapter-narrowing
|
||||
- 이슈명: Jarca Ledger 백엔드 리팩터링 Phase 4+ 후속 주기 (write service 분리 → import pipeline 분리 → advanced finance workflow 분리 → MCP/Streamlit adapter 표면 축소)
|
||||
- 작성 시각(KST): 2026-07-03 07:10
|
||||
- 마지막 갱신 시각(KST): 2026-07-03 08:23
|
||||
- 마지막 갱신 시각(KST): 2026-07-07 22:23
|
||||
- 대체됨: 없음
|
||||
- 후속 Discovery 참조: 없음
|
||||
- 후속 Discovery 참조: `docs/discovery/dcy-013_20260707_docker-network-mcp-deployment.md`
|
||||
- 생성된 REQ 참조: `REQ-015`, `REQ-016`, `REQ-017`, `REQ-018`
|
||||
|
||||
# 1. 계획 상태 요약
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
# 0. 문서 상태
|
||||
이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.
|
||||
|
||||
- 상태: confirmed
|
||||
- 문서 ID: dcy-013_20260707_docker-network-mcp-deployment
|
||||
- 이슈명: Jarca Ledger 독립 Docker 배포 및 network MCP endpoint 제공
|
||||
- 작성 시각(KST): 2026-07-07 22:23 KST
|
||||
- 마지막 갱신 시각(KST): 2026-07-07 22:23 KST
|
||||
- 대체됨: 없음
|
||||
- 후속 Discovery 참조: 없음
|
||||
- 생성된 REQ 참조: `REQ-019`
|
||||
|
||||
# 1. 계획 상태 요약
|
||||
|
||||
- 연계 문서 현황:
|
||||
- 선행 Discovery: `docs/discovery/dcy-011_20260703_backend-refactor-phase4-write-import-workflow-adapter-narrowing.md` — Phase 8 optional HTTP adapter를 Deferred 후보로 남겼다.
|
||||
- 관련 REQ/BAT: `REQ-018`/`BAT-014`는 MCP/Streamlit adapter surface narrowing과 baseline refresh를 완료했으나, network MCP 배포 자체는 Out of Scope였다.
|
||||
- 관련 baseline 문서: `docs/project-structure.md`, `docs/runtime-flows.md`
|
||||
- 관련 runbook: `docs/runbooks/docker-network-mcp-deploy.md`
|
||||
- 해석:
|
||||
- 재무관리 에이전트가 `genichin-agent`의 stdio `docker exec` 실행에 의존하지 않고 Jarca Ledger를 사용하려면, Jarca Ledger 자체가 Docker daemon 아래 독립 서비스로 실행되고 network MCP endpoint를 직접 제공해야 한다.
|
||||
- 이번 문서는 이미 수행된 긴급 배포 작업을 StagePilot 추적성 안으로 편입해, Dockerfile/compose/transport 설정/DB 이전/검증 evidence를 REQ/BAT 기준으로 남기는 post-implementation SDLC 보정 문서다.
|
||||
|
||||
# 2. 요구사항 판정 결과
|
||||
|
||||
- 판정: 신규 후속 주기
|
||||
- 근거
|
||||
- dcy-011은 Phase 8 optional HTTP adapter를 구현하지 않고 Deferred로 남겼으므로, network MCP endpoint와 Docker service 배포는 dcy-011의 하위 구현이 아니라 별도 deployment/interface 반복이다.
|
||||
- 기존 stdio MCP는 클라이언트가 프로세스를 실행하는 모델이어서 `genichin-agent` 실행 환경에 의존한다. 이번 요청은 `jarca-ledger` 컨테이너가 자체 생명주기와 endpoint를 갖도록 하는 운영 구조 변경이다.
|
||||
|
||||
# 3. 문제점의 요약
|
||||
|
||||
- 현재 상태
|
||||
- 기존 `jarca` 컨테이너에는 `/root/.local/share/jarca/ledger.sqlite3` 운영 DB가 존재했고, `accounts=8`, `transactions=6312`, `import_batches=13`, `category_rules=46`, `ledger_categories=62`가 들어 있었다.
|
||||
- 새 `jarca-ledger` 컨테이너 초기 배포 직후 `/data/ledger.sqlite3`는 빈 schema 상태였고, 운영 DB 이전이 누락되어 있었다.
|
||||
- 기존 MCP entrypoint는 FastMCP 기본 stdio 실행만 사용했으며, network MCP transport 설정과 Docker 배포 정의가 StagePilot 문서에 반영되어 있지 않았다.
|
||||
- `docs/project-structure.md`와 `docs/runtime-flows.md`는 MCP stdio/local dashboard 중심 baseline을 유지했고, network MCP/Docker service flow를 아직 반영하지 않았다.
|
||||
- 기대 상태
|
||||
- `jarca-ledger` 컨테이너는 `genichin-agent`와 독립적으로 `restart: unless-stopped` 정책 아래 실행된다.
|
||||
- 컨테이너는 `streamable-http` MCP endpoint `http://<synology-host>:8000/mcp`를 직접 제공한다.
|
||||
- 운영 DB는 `/volume1/docker/jarca-ledger/data/ledger.sqlite3`에 영속화되며, 기존 `jarca` 운영 DB 데이터가 일관성 snapshot 방식으로 이전된다.
|
||||
- 재무관리 에이전트는 Docker exec/attach가 아니라 network MCP endpoint를 소비한다.
|
||||
- 이해관계자
|
||||
- 가계 재무관리 에이전트 — Jarca Ledger MCP tools를 안정적으로 호출하는 소비자
|
||||
- 가웨인/dev-butler — 배포 구조와 기술 기준을 관리하는 개발 책임자
|
||||
- 주인님/운영자 — Synology Docker volume과 운영 DB의 실제 소유자
|
||||
- 주요 사용자 시나리오
|
||||
- 재무관리 에이전트가 `http://192.168.55.253:8000/mcp`에 접속해 `ledger_health_check`, `get_ledger_db_status`, 거래/계좌/리포트 도구를 호출한다.
|
||||
- `genichin-agent` 컨테이너가 재시작되거나 중단되어도 `jarca-ledger` 컨테이너와 DB는 계속 유지된다.
|
||||
- 운영자가 Docker 상태, healthcheck, bind mount, DB integrity/hash/table counts를 evidence로 확인한다.
|
||||
|
||||
# 4. 이번에 정의할 변경
|
||||
|
||||
- FastMCP runtime transport 설정화: `JARCA_MCP_TRANSPORT`, `JARCA_MCP_HOST`, `JARCA_MCP_PORT`, `JARCA_MCP_STREAMABLE_HTTP_PATH`로 stdio와 streamable-http 실행을 선택할 수 있게 한다.
|
||||
- 독립 Docker service 배포: `Dockerfile`, `.dockerignore`, `docker-compose.yml`을 추가하고 `container_name=jarca-ledger`, volume `/volume1/docker/jarca-ledger/data:/data`, port `8000:8000`, restart `unless-stopped`를 고정한다.
|
||||
- 운영 DB 이전 및 검증: 기존 `jarca` 컨테이너 DB를 SQLite backup API로 snapshot한 뒤 새 volume으로 이전하고, MCP smoke와 DB status로 동일 데이터를 검증한다.
|
||||
- baseline 문서 갱신: `docs/project-structure.md`, `docs/runtime-flows.md`, deployment runbook이 network MCP 배포 구조를 반영해야 한다.
|
||||
|
||||
## 영향 범위
|
||||
|
||||
- 코드 영향:
|
||||
- `jarca_ledger_mcp/adapters/mcp/server.py`
|
||||
- `tests/test_mcp_http_runtime.py`
|
||||
- 배포 영향:
|
||||
- `Dockerfile`, `.dockerignore`, `docker-compose.yml`
|
||||
- Synology host path `/volume1/docker/jarca-ledger/data`
|
||||
- 문서 영향:
|
||||
- `docs/runbooks/docker-network-mcp-deploy.md`
|
||||
- `docs/project-structure.md`, `docs/runtime-flows.md`
|
||||
- StagePilot Discovery/SRS/Batch register와 verification evidence
|
||||
|
||||
# 5. 요구사항 목록
|
||||
|
||||
## 기능 요구사항
|
||||
- FR-1: MCP server는 기존 stdio 기본값을 유지하면서 환경변수로 `streamable-http` transport를 활성화할 수 있어야 한다.
|
||||
- FR-2: Docker 배포는 `jarca-ledger` 컨테이너가 독립 실행되고 `/volume1/docker/jarca-ledger/data`를 `/data`로 bind mount해야 한다.
|
||||
- FR-3: network MCP endpoint는 `http://<synology-host>:8000/mcp`에서 tool discovery와 `ledger_health_check`를 성공시켜야 한다.
|
||||
- FR-4: 기존 `jarca` 운영 DB는 일관성 snapshot으로 이전되어 주요 table counts와 integrity가 보존되어야 한다.
|
||||
- FR-5: baseline docs와 runbook은 Docker network MCP 운영 흐름과 `genichin-agent` 비의존 원칙을 반영해야 한다.
|
||||
|
||||
## 비기능 요구사항
|
||||
- NFR-1 (operational independence): `jarca-ledger` 컨테이너 생명주기는 `genichin-agent`가 아니라 Docker daemon/restart policy에 의해 관리되어야 한다.
|
||||
- NFR-2 (financial data safety): 운영 DB 이전 전 새 빈 DB 백업을 남기고, 기존 DB는 SQLite backup API로 snapshot해 복사해야 한다.
|
||||
- NFR-3 (contract preservation): 기존 MCP tool 이름과 주요 응답 shape는 network transport 도입 후에도 유지되어야 한다.
|
||||
|
||||
## 범위 경계
|
||||
|
||||
- In Scope
|
||||
- FastMCP streamable-http transport 설정화
|
||||
- Docker image/compose 배포
|
||||
- Synology volume 기반 운영 DB 이전
|
||||
- MCP network smoke 및 DB integrity verification
|
||||
- StagePilot/baseline/runbook 문서 반영
|
||||
- Out of Scope
|
||||
- 별도 FastAPI/REST business API 구현
|
||||
- 외부 공개용 reverse proxy/TLS/auth 설계
|
||||
- 자동 백업 스케줄러
|
||||
- dcy-012 review-decision/workbench 기능 구현
|
||||
|
||||
# 6. 리스크/가정 목록
|
||||
|
||||
## 리스크
|
||||
- R-1 (High): 운영 DB 이전 누락 또는 잘못된 DB 교체 시 재무관리 에이전트가 빈 ledger를 source of truth로 오인할 수 있다.
|
||||
- 완화 방안: 이전 전후 table counts, sha256, integrity_check, MCP `get_ledger_db_status`를 verification evidence로 남긴다.
|
||||
- R-2 (Medium): `127.0.0.1:8000`은 `genichin-agent` 내부에서는 agent 컨테이너 자신을 의미하므로 smoke endpoint를 잘못 잡을 수 있다.
|
||||
- 완화 방안: agent 컨테이너에서 검증할 때는 Synology host IP `192.168.55.253:8000` 또는 공유 Docker network DNS를 사용한다고 runbook에 명시한다.
|
||||
- R-3 (Medium): network MCP endpoint가 내부망에 열리므로 접근제어/TLS가 없는 상태에서 외부 노출되면 금융 tool surface가 위험해질 수 있다.
|
||||
- 완화 방안: 현재 범위는 내부망 배포로 제한하고, 외부 공개/reverse proxy/auth는 별도 후속 Discovery로 분리한다.
|
||||
|
||||
## 가정
|
||||
- A-1: Synology Docker daemon은 `/volume1/docker/jarca-ledger/data` bind mount를 생성/사용할 수 있다.
|
||||
- A-2: 재무관리 에이전트는 streamable-http MCP client 또는 Hermes remote MCP 설정을 통해 endpoint를 소비할 수 있다.
|
||||
- A-3: 기존 `jarca` 컨테이너의 `/root/.local/share/jarca/ledger.sqlite3`가 현 시점 운영 ledger source였다.
|
||||
|
||||
## 오픈 질문
|
||||
- 없음 — 이번 배포 범위와 endpoint/volume/컨테이너 이름은 사용자 지시와 실제 배포 evidence로 확정되었다.
|
||||
|
||||
# 7. 초기 성공 기준
|
||||
|
||||
- S-1: `docker inspect jarca-ledger`에서 `state=running`, `health=healthy`, `restart=unless-stopped`, mount `/volume1/docker/jarca-ledger/data -> /data`가 확인된다.
|
||||
- S-2: MCP streamable-http client가 `http://192.168.55.253:8000/mcp`에서 56개 tool을 발견하고 `ledger_health_check`에 성공한다.
|
||||
- S-3: `get_ledger_db_status`가 `/data/ledger.sqlite3`, `integrity_check=ok`, `accounts=8`, `transactions=6312`, `import_batches=13`, `category_rules=46`, `ledger_categories=62`를 보고한다.
|
||||
- S-4: `python -m pytest -q`와 신규 runtime config 테스트가 통과한다.
|
||||
- S-5: baseline docs와 runbook이 network MCP 배포와 운영 DB 이전 흐름을 설명한다.
|
||||
|
||||
## 측정 방식 및 데이터 출처
|
||||
|
||||
- S-1: `docker inspect`, `docker logs`, Docker healthcheck
|
||||
- S-2: Python `mcp.client.streamable_http.streamablehttp_client` smoke
|
||||
- S-3: MCP `get_ledger_db_status` 및 SQLite integrity/table count probe
|
||||
- S-4: pytest output
|
||||
- S-5: `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/runbooks/docker-network-mcp-deploy.md`, BAT-015 verification
|
||||
|
||||
# 8. REQ로 넘기기 전 확인 체크
|
||||
|
||||
- [x] 컨테이너명/volume/endpoint 확정
|
||||
- [x] 기존 DB source와 이전 대상 확인
|
||||
- [x] network MCP transport 지원 가능성 확인
|
||||
- [x] 배포 및 smoke evidence 확보
|
||||
- [x] baseline 문서 갱신 요구 명시
|
||||
|
||||
# 9. 파일 처리 결과
|
||||
|
||||
- 처리 결과: 생성
|
||||
- 생성 경로: `docs/discovery/dcy-013_20260707_docker-network-mcp-deployment.md`
|
||||
- 참조 문서:
|
||||
- `docs/discovery/dcy-011_20260703_backend-refactor-phase4-write-import-workflow-adapter-narrowing.md`
|
||||
- `docs/srs/Interface/req-018_adapter-surface-narrowing-baseline-refresh.md`
|
||||
- `docs/project-structure.md`
|
||||
- `docs/runtime-flows.md`
|
||||
- `docs/runbooks/docker-network-mcp-deploy.md`
|
||||
- `stage-pilot/templates/project-structure.md`
|
||||
- `stage-pilot/templates/runtime-flows.md`
|
||||
|
||||
# 10. 사용자 결정 필요 항목 요약
|
||||
|
||||
## DECIDE
|
||||
- 없음
|
||||
|
||||
## CONFIRM
|
||||
- 없음
|
||||
|
||||
## DATA
|
||||
- 없음
|
||||
|
||||
# 11. Discovery Freeze
|
||||
|
||||
## 섹션 참조
|
||||
- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록)
|
||||
- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록)
|
||||
- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준)
|
||||
|
||||
## Freeze 플래그
|
||||
- Handoff Decision: confirmed
|
||||
- Handoff Rationale: 사용자 지시에 따라 Docker network MCP 배포와 운영 DB 이전이 이미 완료되었고, 검증 evidence가 확보되어 REQ/BAT post-implementation 문서화로 인계한다.
|
||||
- Ready for REQ Drafting: true
|
||||
- Confirmed By: 진환 고
|
||||
- Confirmed At (KST): 2026-07-07 22:23 KST
|
||||
@@ -18,3 +18,4 @@
|
||||
| dcy-010_20260702_backend-core-layering-mcp-adapter-realignment | 20260702 | Jarca Ledger 백엔드 코어 분리 및 layered 아키텍처 재편, MCP adapter 재정렬 | [Discovery](./dcy-010_20260702_backend-core-layering-mcp-adapter-realignment.md) | confirmed |
|
||||
| dcy-011_20260703_backend-refactor-phase4-write-import-workflow-adapter-narrowing | 20260703 | Jarca Ledger 백엔드 리팩터링 Phase 4+ 후속 주기 (write service 분리 → import pipeline 분리 → advanced finance workflow 분리 → MCP/Streamlit adapter 표면 축소) | [Discovery](./dcy-011_20260703_backend-refactor-phase4-write-import-workflow-adapter-narrowing.md) | confirmed |
|
||||
| dcy-012_20260703_review-queue-reconciliation-workbench-persistence | 20260703 | 확인필요 큐 / 잔액대조 워크벤치 영속화 및 운영 모델 정립 | [Discovery](./dcy-012_20260703_review-queue-reconciliation-workbench-persistence.md) | draft |
|
||||
| dcy-013_20260707_docker-network-mcp-deployment | 20260707 | Jarca Ledger 독립 Docker 배포 및 network MCP endpoint 제공 | [Discovery](./dcy-013_20260707_docker-network-mcp-deployment.md) | confirmed |
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
- Baseline Seed: `.stage-pilot/bootstrap/baseline.yaml`
|
||||
- Status: draft
|
||||
- Owner: Jarca / project maintainer
|
||||
- Last Updated (KST): 2026-07-03
|
||||
- Source Discovery / Batch: bootstrap-baseline, BAT-006, BAT-007, BAT-010, BAT-012, BAT-013, BAT-014
|
||||
- Last Updated (KST): 2026-07-07
|
||||
- Source Discovery / Batch: bootstrap-baseline, BAT-006, BAT-007, BAT-010, BAT-012, BAT-013, BAT-014, dcy-013/BAT-015
|
||||
|
||||
## Project Summary
|
||||
- Jarca 가계부 전용 MCP 서버와 Streamlit 대시보드로 계좌/카드 거래, 예산, 분류 규칙, import, 자산/스냅샷, 현금흐름 분석을 SQLite에 저장하고 운영한다.
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
## Tech Stack
|
||||
- Python >=3.11
|
||||
- FastMCP / MCP server
|
||||
- FastMCP / MCP server (stdio and streamable-http)
|
||||
- SQLite
|
||||
- Streamlit
|
||||
- pandas / plotly
|
||||
@@ -26,7 +26,8 @@
|
||||
## Primary Runtime
|
||||
- Type: mixed
|
||||
- Planned Entry Points:
|
||||
- `jarca-ledger-mcp` :: MCP stdio server for Hermes Agent tools.
|
||||
- `jarca-ledger-mcp` :: MCP stdio server for local Hermes Agent tools and streamable-http server when configured by env.
|
||||
- `jarca-ledger` Docker container :: independent Streamable HTTP MCP service on `:8000/mcp`.
|
||||
- `dashboard/app.py` :: Streamlit dashboard for local ledger operations.
|
||||
- `pytest` :: Regression test entry point.
|
||||
|
||||
@@ -43,6 +44,9 @@
|
||||
```text
|
||||
jarca-ledger-mcp/
|
||||
├── README.md
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
├── .dockerignore
|
||||
├── pyproject.toml
|
||||
├── uv.lock
|
||||
├── .env.example
|
||||
@@ -104,6 +108,7 @@ jarca-ledger-mcp/
|
||||
│ ├── contract_snapshots/
|
||||
│ │ └── response_contract_v1_0.json
|
||||
│ ├── test_contract_regression.py
|
||||
│ ├── test_mcp_http_runtime.py
|
||||
│ ├── test_dashboard_app.py
|
||||
│ ├── test_service.py
|
||||
│ ├── test_asset_snapshots.py
|
||||
@@ -125,6 +130,7 @@ jarca-ledger-mcp/
|
||||
│ ├── srs/
|
||||
│ ├── batches/
|
||||
│ ├── runbooks/
|
||||
│ │ └── docker-network-mcp-deploy.md
|
||||
│ ├── releases/
|
||||
│ ├── project-structure.md
|
||||
│ ├── runtime-flows.md
|
||||
@@ -174,7 +180,7 @@ jarca-ledger-mcp/
|
||||
- `jarca_ledger_mcp/adapters/mcp/tool_contracts.py`
|
||||
- Narrow MCP adapter contract surface that re-exports only the public tool callables consumed by the FastMCP server.
|
||||
- `jarca_ledger_mcp/adapters/mcp/server.py`
|
||||
- FastMCP tool registration layer that depends on `tool_contracts.py` instead of broad direct service imports.
|
||||
- FastMCP tool registration layer that depends on `tool_contracts.py` instead of broad direct service imports. Runtime transport is configurable: stdio by default, or streamable-http through `JARCA_MCP_TRANSPORT=streamable-http` with host/port/path env settings.
|
||||
- `jarca_ledger_mcp/adapters/streamlit/service_gateway.py`
|
||||
- Compatibility aggregator over page/domain gateway modules.
|
||||
- `jarca_ledger_mcp/adapters/streamlit/*_gateway.py`
|
||||
@@ -197,6 +203,10 @@ jarca-ledger-mcp/
|
||||
- Local browser evidence helper using synthetic/validation DBs only.
|
||||
- `scripts/ledger_restore_smoke.py`
|
||||
- Script-only restore smoke helper; restore remains outside the MCP tool surface.
|
||||
- `Dockerfile` / `docker-compose.yml`
|
||||
- Build and run the independent `jarca-ledger` Docker service. Compose fixes `container_name=jarca-ledger`, `restart=unless-stopped`, port `8000:8000`, and bind mount `/volume1/docker/jarca-ledger/data:/data`.
|
||||
- `docs/runbooks/docker-network-mcp-deploy.md`
|
||||
- Operator runbook for building, starting, and verifying the Docker Streamable HTTP MCP deployment and DB path.
|
||||
|
||||
## Dependency Rules
|
||||
- `adapters/mcp/server.py` should remain a thin tool adapter and delegate business logic to `service.py` facade/application modules.
|
||||
@@ -216,7 +226,7 @@ jarca-ledger-mcp/
|
||||
## Current Gaps / Planned Changes
|
||||
- Application modules are now separated by phase-0~3 domain seams, but deeper repository/use-case split remains future Phase 4+ work.
|
||||
- Streamlit now uses page/domain gateways; deeper presenter/view-model extraction remains future work.
|
||||
- HTTP adapter and remote ops re-architecture remain out of scope.
|
||||
- Network MCP over FastMCP streamable-http is now available for internal Synology operation; broader HTTP/REST business API, external exposure, reverse proxy/TLS/auth, and remote ops re-architecture remain future work.
|
||||
|
||||
## Update Triggers
|
||||
- Update this document when package boundaries change.
|
||||
@@ -231,3 +241,4 @@ jarca-ledger-mcp/
|
||||
- 2026-06-30 KST: REQ-007/REQ-008/BAT-007 기준으로 MCP response contract snapshot/test 위치와 required-check job 계약 추가.
|
||||
- 2026-07-03 KST: REQ-013/REQ-014/BAT-010 기준으로 application / infrastructure / adapter layering, compatibility facade/shim, Streamlit gateway 구조를 반영했다.
|
||||
- 2026-07-03 KST: REQ-016/REQ-017/REQ-018 및 BAT-012/BAT-013/BAT-014 기준으로 import/advanced owning services, parser seam, MCP contract helper, page/domain Streamlit gateways를 반영했다.
|
||||
- 2026-07-07 KST: REQ-019/BAT-015 기준으로 Dockerfile/compose, `jarca-ledger` independent container, streamable-http MCP endpoint, Synology volume `/volume1/docker/jarca-ledger/data`, and Docker deployment runbook을 반영했다.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Jarca Ledger Docker Network MCP 배포 Runbook
|
||||
|
||||
## 목적
|
||||
|
||||
`jarca-ledger`를 `genichin-agent` 생명주기와 분리된 독립 Docker 서비스로 배포하고, stdio `docker exec`가 아니라 HTTP 기반 Streamable MCP endpoint를 직접 제공한다.
|
||||
|
||||
## 운영 고정값
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 컨테이너 이름 | `jarca-ledger` |
|
||||
| 이미지 | `jarca-ledger:latest` |
|
||||
| Host data path | `/volume1/docker/jarca-ledger/data` |
|
||||
| Container data path | `/data` |
|
||||
| SQLite DB | `/data/ledger.sqlite3` |
|
||||
| MCP transport | `streamable-http` |
|
||||
| MCP listen | `0.0.0.0:8000` |
|
||||
| MCP path | `/mcp` |
|
||||
|
||||
## 배포
|
||||
|
||||
```bash
|
||||
cd /home/ubuntu/workspaces/jarca-ledger-mcp
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
## 검증
|
||||
|
||||
```bash
|
||||
docker ps --filter name=jarca-ledger
|
||||
docker inspect jarca-ledger --format='MOUNTS: see HostConfig.Binds and Mounts in docker inspect output'
|
||||
docker inspect jarca-ledger | python3 -c "import json,sys; data=json.load(sys.stdin)[0]; [print(f'{m.get(\"Source\")} -> {m.get(\"Destination\")} ({m.get(\"Type\")})') for m in data.get('Mounts', [])]"
|
||||
docker logs --tail 80 jarca-ledger
|
||||
```
|
||||
|
||||
Python MCP client smoke:
|
||||
|
||||
```bash
|
||||
python - <<'PY'
|
||||
import anyio
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
|
||||
async def main():
|
||||
async with streamablehttp_client('http://127.0.0.1:8000/mcp') as (read, write, _):
|
||||
async with ClientSession(read, write) as session:
|
||||
await session.initialize()
|
||||
tools = await session.list_tools()
|
||||
print('tool_count', len(tools.tools))
|
||||
result = await session.call_tool('ledger_health_check', {})
|
||||
print(result.content[0].text)
|
||||
|
||||
anyio.run(main)
|
||||
PY
|
||||
```
|
||||
|
||||
## 운영 원칙
|
||||
|
||||
- `jarca-ledger` 컨테이너는 Docker daemon의 `restart: unless-stopped` 정책으로 독립 실행된다.
|
||||
- `genichin-agent`는 이 서비스를 배포하거나 실행시키는 필수 구성요소가 아니다.
|
||||
- 재무관리 에이전트는 `http://<synology-host>:8000/mcp` 또는 같은 Docker network에서 `http://jarca-ledger:8000/mcp`로 접속한다.
|
||||
- 운영 DB와 import 원본/민감자료는 이미지에 포함하지 않고 `/volume1/docker/jarca-ledger/data`에만 둔다.
|
||||
+23
-5
@@ -4,8 +4,8 @@
|
||||
- Baseline Seed: `.stage-pilot/bootstrap/baseline.yaml`
|
||||
- Status: draft
|
||||
- Owner: Jarca / project maintainer
|
||||
- Last Updated (KST): 2026-07-03
|
||||
- Source Discovery / Batch: bootstrap-baseline, BAT-006, BAT-007, BAT-008, BAT-010, BAT-012, BAT-013, BAT-014
|
||||
- Last Updated (KST): 2026-07-07
|
||||
- Source Discovery / Batch: bootstrap-baseline, BAT-006, BAT-007, BAT-008, BAT-010, BAT-012, BAT-013, BAT-014, dcy-013/BAT-015
|
||||
- Primary Runtime: mixed
|
||||
|
||||
## Project Summary
|
||||
@@ -20,19 +20,36 @@
|
||||
- Does not duplicate full acceptance criteria or low-level implementation details.
|
||||
|
||||
## Covered Entry Points
|
||||
- `jarca-ledger-mcp` :: MCP stdio server for Hermes tool calls.
|
||||
- `jarca-ledger-mcp` :: MCP stdio server for local Hermes tool calls, or streamable-http server when configured by env.
|
||||
- `jarca-ledger` Docker service :: Streamable HTTP MCP endpoint at `http://<synology-host>:8000/mcp`.
|
||||
- `dashboard/app.py` :: Streamlit local dashboard.
|
||||
- `pytest` :: regression test runner.
|
||||
- `.github/workflows/ci.yml` :: GitHub Actions CI gate runner for PR/push/manual checks.
|
||||
|
||||
## Shared Components
|
||||
- SQLite database at `~/.local/share/jarca/ledger.sqlite3` by default.
|
||||
- SQLite database at `~/.local/share/jarca/ledger.sqlite3` by default for local runs; Docker service uses `JARCA_LEDGER_DB_PATH=/data/ledger.sqlite3` backed by `/volume1/docker/jarca-ledger/data`.
|
||||
- `jarca_ledger_mcp/service.py` compatibility facade over application modules.
|
||||
- `jarca_ledger_mcp/infrastructure/sqlite/*` for DB path resolution, schema, migration, and connection lifecycle.
|
||||
- `jarca_ledger_mcp/adapters/mcp/tool_contracts.py` + `adapters/mcp/server.py` for MCP contract and registration seams.
|
||||
- `jarca_ledger_mcp/adapters/streamlit/*_gateway.py` + `service_gateway.py` compatibility aggregator for dashboard-facing service calls.
|
||||
- `jarca_ledger_mcp/infrastructure/parsers/*` plus legacy parser modules for KB/Toss/SeoulPay sources.
|
||||
|
||||
|
||||
## Flow: `Docker network MCP service`
|
||||
1. Docker daemon starts the `jarca-ledger` container from `jarca-ledger:latest` with `restart: unless-stopped`.
|
||||
2. Compose mounts `/volume1/docker/jarca-ledger/data` to `/data` and sets `JARCA_LEDGER_DB_PATH=/data/ledger.sqlite3`.
|
||||
3. `jarca-ledger-mcp` starts FastMCP with `JARCA_MCP_TRANSPORT=streamable-http`, `JARCA_MCP_HOST=0.0.0.0`, `JARCA_MCP_PORT=8000`, and path `/mcp`.
|
||||
4. Finance agents connect to `http://<synology-host>:8000/mcp` or a shared Docker network DNS endpoint.
|
||||
5. The MCP adapter routes tool calls through `adapters/mcp/tool_contracts.py` and existing application/persistence services.
|
||||
6. Docker healthcheck verifies TCP readiness on `127.0.0.1:8000`; functional readiness is verified by MCP tool discovery and `ledger_health_check`.
|
||||
|
||||
## Flow: `Operating DB migration to jarca-ledger container`
|
||||
1. Identify source DB in the legacy `jarca` container: `/root/.local/share/jarca/ledger.sqlite3`.
|
||||
2. Create a consistent source snapshot using SQLite `Connection.backup()` rather than copying a live DB file directly.
|
||||
3. Back up the current target `/data/ledger.sqlite3` under `/data/backups/` before replacement.
|
||||
4. Stop `jarca-ledger`, remove target WAL/SHM sidecar files, copy the snapshot to `/data/ledger.sqlite3`, and restart.
|
||||
5. Verify Docker health, SQLite `PRAGMA integrity_check`, table counts, sha256, and MCP `get_ledger_db_status`.
|
||||
|
||||
## Flow: `MCP server tool call`
|
||||
1. Hermes MCP client invokes a tool exposed by `jarca_ledger_mcp.server` / `jarca_ledger_mcp.adapters.mcp.server`.
|
||||
2. MCP adapter forwards arguments to the matching callable exposed by `adapters/mcp/tool_contracts.py`.
|
||||
@@ -121,7 +138,7 @@
|
||||
## Current Gaps / Planned Changes
|
||||
- Browser-level evidence exists locally and remains outside required CI gates by design.
|
||||
- Post-Phase-7 adapter narrowing is complete for the current compatibility architecture; deeper repository/use-case extraction and richer presenter/view-model layers remain future work.
|
||||
- HTTP adapter and remote ops re-architecture remain deferred.
|
||||
- Streamable HTTP MCP is now deployed for internal Synology operation. External exposure, reverse proxy/TLS/auth, and broader HTTP/REST adapter work remain deferred.
|
||||
|
||||
## Update Triggers
|
||||
- Update this document when a command entry point changes.
|
||||
@@ -138,3 +155,4 @@
|
||||
- 2026-06-30 KST: REQ-007/REQ-008/BAT-007 기준으로 contract-regression/lint CI required-check flow 추가.
|
||||
- 2026-07-03 KST: REQ-013/REQ-014/BAT-010 기준으로 service facade → application, db shim → infrastructure/sqlite, MCP/Streamlit adapter seam flow를 반영했다.
|
||||
- 2026-07-03 KST: REQ-016/REQ-017/REQ-018 및 BAT-012/BAT-013/BAT-014 기준으로 imports/advanced owning services, parser seam, MCP tool_contracts, Streamlit page/domain gateway flow를 반영했다.
|
||||
- 2026-07-07 KST: REQ-019/BAT-015 기준으로 Docker network MCP service flow와 legacy `jarca` DB → `jarca-ledger` volume migration flow를 추가했다.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# REQ-019 Docker network MCP deployment and operating DB migration
|
||||
|
||||
- Status: Implemented
|
||||
- Type: Deployment
|
||||
- Priority: High
|
||||
- Owner: 렛저장인
|
||||
- Source Discovery: `docs/discovery/dcy-013_20260707_docker-network-mcp-deployment.md`
|
||||
- Batch: BAT-015
|
||||
- Created At (KST): 2026-07-07 22:23 KST
|
||||
- Approved At (KST): 2026-07-07 22:23 KST
|
||||
- Implemented At (KST): 2026-07-07 22:23 KST
|
||||
|
||||
## 1. 배경
|
||||
|
||||
재무관리 에이전트가 Jarca Ledger를 사용하려면 `genichin-agent`가 stdio 프로세스를 실행해 주는 구조가 아니라, `jarca-ledger` 자체가 독립 Docker 서비스로 실행되고 network MCP endpoint를 직접 제공해야 한다. 또한 기존 `jarca` 컨테이너의 운영 DB가 새 volume으로 이전되어야 빈 ledger를 운영 source로 오인하지 않는다.
|
||||
|
||||
## 2. Intent
|
||||
|
||||
`jarca-ledger` 컨테이너를 Synology Docker daemon 아래 독립 실행 서비스로 배포하고, `streamable-http` MCP endpoint와 `/volume1/docker/jarca-ledger/data` 영속 volume을 운영 기준으로 고정한다. 기존 `jarca` 운영 DB를 안전하게 이전하고, MCP smoke와 DB status evidence로 재무관리 에이전트 투입 가능 상태를 검증한다.
|
||||
|
||||
## 3. 범위
|
||||
|
||||
### In Scope
|
||||
- FastMCP runtime transport 설정화: stdio 기본값 유지 + `streamable-http` 선택 가능
|
||||
- Dockerfile, `.dockerignore`, `docker-compose.yml` 추가
|
||||
- `container_name=jarca-ledger`, `restart=unless-stopped`, port `8000:8000`, volume `/volume1/docker/jarca-ledger/data:/data`
|
||||
- 기존 `jarca` DB snapshot 및 새 volume 교체
|
||||
- network MCP tool discovery / health / DB status smoke
|
||||
- Docker deployment runbook 및 baseline docs 갱신
|
||||
|
||||
### Out of Scope
|
||||
- public internet exposure, TLS, reverse proxy, auth policy
|
||||
- 별도 REST business API
|
||||
- dcy-012 review-decision persistence 기능
|
||||
- 자동 backup scheduler
|
||||
|
||||
## 4. Requirement
|
||||
|
||||
### FR-1. MCP runtime transport configuration
|
||||
`jarca-ledger-mcp`는 기본 stdio 실행을 보존하면서 환경변수 `JARCA_MCP_TRANSPORT=streamable-http`, `JARCA_MCP_HOST`, `JARCA_MCP_PORT`, `JARCA_MCP_STREAMABLE_HTTP_PATH`로 network MCP endpoint를 제공할 수 있어야 한다.
|
||||
|
||||
### FR-2. Independent Docker deployment
|
||||
Repository는 `jarca-ledger:latest` image를 빌드하고 `jarca-ledger` 컨테이너를 `restart: unless-stopped`로 실행하는 Docker compose 정의를 제공해야 한다.
|
||||
|
||||
### FR-3. Synology volume and DB path
|
||||
컨테이너는 host `/volume1/docker/jarca-ledger/data`를 `/data`로 mount하고 `JARCA_LEDGER_DB_PATH=/data/ledger.sqlite3`를 사용해야 한다.
|
||||
|
||||
### FR-4. Operating DB migration
|
||||
기존 `jarca` 컨테이너의 `/root/.local/share/jarca/ledger.sqlite3` 운영 DB를 SQLite backup snapshot으로 복사해 새 `/data/ledger.sqlite3`로 이전해야 하며, 이전 전 빈 DB 백업을 남겨야 한다.
|
||||
|
||||
### FR-5. Deployment documentation and baselines
|
||||
`docs/runbooks/docker-network-mcp-deploy.md`, `docs/project-structure.md`, `docs/runtime-flows.md`는 network MCP endpoint, Docker service, volume, 운영 검증 절차를 설명해야 한다.
|
||||
|
||||
## 5. Acceptance Criteria
|
||||
|
||||
- AC-1: `tests/test_mcp_http_runtime.py`가 stdio default, streamable-http env, invalid transport rejection을 검증하고 통과한다.
|
||||
- AC-2: `docker compose build`와 `docker compose up -d --no-build` 후 `jarca-ledger`가 `running`, `healthy`, `restart=unless-stopped` 상태다.
|
||||
- AC-3: Docker inspect에서 `/volume1/docker/jarca-ledger/data -> /data` bind mount가 확인된다.
|
||||
- AC-4: MCP streamable-http smoke가 `http://192.168.55.253:8000/mcp`에서 56개 tool discovery와 `ledger_health_check` 성공을 확인한다.
|
||||
- AC-5: `get_ledger_db_status`가 `/data/ledger.sqlite3`, `integrity_check=ok`, `accounts=8`, `transactions=6312`, `import_batches=13`, `category_rules=46`, `ledger_categories=62`를 보고한다.
|
||||
- AC-6: 전체 `python -m pytest -q`가 통과한다.
|
||||
- AC-7: 배포 runbook과 baseline docs가 network MCP 운영 구조를 반영한다.
|
||||
|
||||
## 6. Impacted Area
|
||||
|
||||
- `jarca_ledger_mcp/adapters/mcp/server.py`
|
||||
- `tests/test_mcp_http_runtime.py`
|
||||
- `Dockerfile`
|
||||
- `.dockerignore`
|
||||
- `docker-compose.yml`
|
||||
- `docs/runbooks/docker-network-mcp-deploy.md`
|
||||
- `docs/project-structure.md`
|
||||
- `docs/runtime-flows.md`
|
||||
- Synology host path `/volume1/docker/jarca-ledger/data`
|
||||
|
||||
## 7. Verification Evidence Summary
|
||||
|
||||
- Full pytest: `python -m pytest -q` → PASS (`........................ss..................ss... [100%]`)
|
||||
- Runtime config tests: `python -m pytest tests/test_mcp_http_runtime.py -q` → PASS (`... [100%]`)
|
||||
- Docker image build: `docker compose build` → PASS, image `jarca-ledger:latest` built
|
||||
- Container start: `docker compose up -d --no-build` → container `jarca-ledger` started
|
||||
- Health: `state=running health=healthy restart=unless-stopped`
|
||||
- MCP smoke: `tool_count 56`, `ledger_health_check success true`, `db_path /data/ledger.sqlite3`
|
||||
- DB status after migration: `integrity_check ok`, `accounts=8`, `transactions=6312`, `import_batches=13`, `category_rules=46`, `ledger_categories=62`
|
||||
|
||||
## 8. Change Log
|
||||
|
||||
- 2026-07-07 22:23 KST: dcy-013 범위를 바탕으로 REQ-019를 생성하고, 이미 수행된 Docker network MCP 구현/배포/DB 이전 evidence를 근거로 Implemented 상태로 기록했다.
|
||||
+3
-1
@@ -33,7 +33,7 @@
|
||||
|
||||
## Next Requirement ID
|
||||
|
||||
- Current: `REQ-019`
|
||||
- Current: `REQ-020`
|
||||
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
|
||||
|
||||
## Requirement Register
|
||||
@@ -58,6 +58,7 @@
|
||||
| REQ-016 | Import pipeline application/infrastructure split and evidence-preserving parser relocation | Non-Functional | Implemented | High | 렛저장인 | [req-016_import-pipeline-application-infrastructure-split.md](Non-Functional/req-016_import-pipeline-application-infrastructure-split.md) |
|
||||
| REQ-017 | Advanced finance workflow service split and compatibility-preserving domain isolation | Non-Functional | Implemented | High | 렛저장인 | [req-017_advanced-finance-workflow-service-split.md](Non-Functional/req-017_advanced-finance-workflow-service-split.md) |
|
||||
| REQ-018 | MCP/Streamlit adapter surface narrowing and baseline refresh for dcy-011 completion | Interface | Implemented | High | 렛저장인 | [req-018_adapter-surface-narrowing-baseline-refresh.md](Interface/req-018_adapter-surface-narrowing-baseline-refresh.md) |
|
||||
| REQ-019 | Docker network MCP deployment and operating DB migration | Deployment | Implemented | High | 렛저장인 | [req-019_docker-network-mcp-deployment.md](Deployment/req-019_docker-network-mcp-deployment.md) |
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
@@ -92,3 +93,4 @@
|
||||
- 2026-07-03 08:01 KST: BAT-013 advanced workflow split 구현과 full pytest/MCP discovery evidence를 근거로 `REQ-017`을 Implemented로 전환했다.
|
||||
- 2026-07-03 08:03 KST: dcy-011 Phase 7 adapter narrowing + baseline refresh 범위를 `REQ-018`로 생성하고 BAT-014 입력으로 Approved 상태를 확정했다.
|
||||
- 2026-07-03 08:23 KST: BAT-014 adapter narrowing + baseline refresh 구현과 AppTest/full pytest/MCP discovery evidence를 근거로 `REQ-018`을 Implemented로 전환했다.
|
||||
- 2026-07-07 22:23 KST: dcy-013 Docker network MCP 배포 범위로 REQ-019를 생성하고, BAT-015 구현·검증 evidence 및 운영 DB 이전 완료를 근거로 Implemented 상태로 등록했다.
|
||||
|
||||
@@ -1,13 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from . import tool_contracts as contracts
|
||||
|
||||
load_dotenv(Path(__file__).resolve().parents[3] / ".env")
|
||||
mcp = FastMCP("jarca-ledger-mcp")
|
||||
|
||||
McpTransport = Literal["stdio", "sse", "streamable-http"]
|
||||
_ALLOWED_TRANSPORTS: set[str] = {"stdio", "sse", "streamable-http"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class McpRuntimeConfig:
|
||||
transport: McpTransport = "stdio"
|
||||
host: str = "127.0.0.1"
|
||||
port: int = 8000
|
||||
streamable_http_path: str = "/mcp"
|
||||
sse_path: str = "/sse"
|
||||
message_path: str = "/messages/"
|
||||
stateless_http: bool = False
|
||||
|
||||
|
||||
def _env_bool(name: str, default: bool = False) -> bool:
|
||||
raw = os.getenv(name)
|
||||
if raw is None or raw == "":
|
||||
return default
|
||||
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _env_int(name: str, default: int) -> int:
|
||||
raw = os.getenv(name)
|
||||
if raw is None or raw == "":
|
||||
return default
|
||||
try:
|
||||
return int(raw)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"{name} must be an integer, got {raw!r}") from exc
|
||||
|
||||
|
||||
def _env_path(name: str, default: str) -> str:
|
||||
raw = os.getenv(name, default).strip() or default
|
||||
return raw if raw.startswith("/") else f"/{raw}"
|
||||
|
||||
|
||||
def get_mcp_runtime_config() -> McpRuntimeConfig:
|
||||
transport = os.getenv("JARCA_MCP_TRANSPORT", "stdio").strip() or "stdio"
|
||||
if transport not in _ALLOWED_TRANSPORTS:
|
||||
allowed = ", ".join(sorted(_ALLOWED_TRANSPORTS))
|
||||
raise ValueError(f"JARCA_MCP_TRANSPORT must be one of: {allowed}; got {transport!r}")
|
||||
return McpRuntimeConfig(
|
||||
transport=transport, # type: ignore[arg-type]
|
||||
host=os.getenv("JARCA_MCP_HOST", "127.0.0.1").strip() or "127.0.0.1",
|
||||
port=_env_int("JARCA_MCP_PORT", 8000),
|
||||
streamable_http_path=_env_path("JARCA_MCP_STREAMABLE_HTTP_PATH", "/mcp"),
|
||||
sse_path=_env_path("JARCA_MCP_SSE_PATH", "/sse"),
|
||||
message_path=_env_path("JARCA_MCP_MESSAGE_PATH", "/messages/"),
|
||||
stateless_http=_env_bool("JARCA_MCP_STATELESS_HTTP", False),
|
||||
)
|
||||
|
||||
|
||||
_runtime_config = get_mcp_runtime_config()
|
||||
mcp = FastMCP(
|
||||
"jarca-ledger-mcp",
|
||||
host=_runtime_config.host,
|
||||
port=_runtime_config.port,
|
||||
streamable_http_path=_runtime_config.streamable_http_path,
|
||||
sse_path=_runtime_config.sse_path,
|
||||
message_path=_runtime_config.message_path,
|
||||
stateless_http=_runtime_config.stateless_http,
|
||||
)
|
||||
|
||||
@mcp.tool()
|
||||
def ledger_health_check() -> dict:
|
||||
@@ -290,7 +357,7 @@ def delete_ledger_category(category: str, subcategory: str = "", hard_delete: bo
|
||||
return contracts.delete_ledger_category(category, subcategory, hard_delete, force)
|
||||
|
||||
def main() -> None:
|
||||
mcp.run()
|
||||
mcp.run(transport=_runtime_config.transport)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
|
||||
|
||||
def test_mcp_runtime_config_defaults_to_stdio(monkeypatch):
|
||||
monkeypatch.delenv("JARCA_MCP_TRANSPORT", raising=False)
|
||||
monkeypatch.delenv("JARCA_MCP_HOST", raising=False)
|
||||
monkeypatch.delenv("JARCA_MCP_PORT", raising=False)
|
||||
monkeypatch.delenv("JARCA_MCP_STREAMABLE_HTTP_PATH", raising=False)
|
||||
|
||||
server = importlib.import_module("jarca_ledger_mcp.adapters.mcp.server")
|
||||
config = server.get_mcp_runtime_config()
|
||||
|
||||
assert config.transport == "stdio"
|
||||
assert config.host == "127.0.0.1"
|
||||
assert config.port == 8000
|
||||
assert config.streamable_http_path == "/mcp"
|
||||
|
||||
|
||||
def test_mcp_runtime_config_accepts_streamable_http_env(monkeypatch):
|
||||
monkeypatch.setenv("JARCA_MCP_TRANSPORT", "streamable-http")
|
||||
monkeypatch.setenv("JARCA_MCP_HOST", "0.0.0.0")
|
||||
monkeypatch.setenv("JARCA_MCP_PORT", "8765")
|
||||
monkeypatch.setenv("JARCA_MCP_STREAMABLE_HTTP_PATH", "/ledger-mcp")
|
||||
|
||||
server = importlib.import_module("jarca_ledger_mcp.adapters.mcp.server")
|
||||
config = server.get_mcp_runtime_config()
|
||||
|
||||
assert config.transport == "streamable-http"
|
||||
assert config.host == "0.0.0.0"
|
||||
assert config.port == 8765
|
||||
assert config.streamable_http_path == "/ledger-mcp"
|
||||
|
||||
|
||||
def test_mcp_runtime_config_rejects_invalid_transport(monkeypatch):
|
||||
monkeypatch.setenv("JARCA_MCP_TRANSPORT", "websocket")
|
||||
|
||||
server = importlib.import_module("jarca_ledger_mcp.adapters.mcp.server")
|
||||
|
||||
try:
|
||||
server.get_mcp_runtime_config()
|
||||
except ValueError as exc:
|
||||
assert "JARCA_MCP_TRANSPORT" in str(exc)
|
||||
else:
|
||||
raise AssertionError("invalid MCP transport should be rejected")
|
||||
Reference in New Issue
Block a user