Skip to Content

Frame

한 줄 소개: KSME 회계 기준 준수 금융관리 SaaS. Postgres schema-per-entity 격리 + ACID double-entry + append-only audit log. Blueprint 에이전트가 MCP ~44개 도구로 접근 (조회 14 + 작업 30+).

기술 스택

항목
언어Python 3.12+
프레임워크FastMCP (stdio + HTTP/SSE)
DBPostgreSQL 16
ORMSQLAlchemy 2.0 Core (declarative 아님) + psycopg v3
MigrationAlembic dual-env (shared + entity)
인증PyJWT (HS256 frame JWT + RS256 Microsoft JWKS)
LLMAnthropic Claude (file format 분석, 분류)
HTTP serverStarlette + uvicorn

포트

포트용도
3700PostgreSQL 16 (frame-postgres)
3710MCP HTTP blue (active)
3711MCP HTTP green (passive)
3712axe-frame-proxy (Caddy, blue/green selector)

4 가지 surface

  1. MCP (HTTP/SSE) — Claude Agent SDK 가 도구로 호출
  2. CLI (frame ...) — 운영자 명령 (migrate, register-entity, mcp-token, …)
  3. DB 직접 접근 — psql 으로 정합성 검사, 운영자만
  4. Export — 회계 사무소용 CSV/JSON

MCP Tools (~54개)

src/frame/mcp/server.py@mcp.tool() 데코레이터 기준. 핵심 도구:

ToolScope 요구설명
query_balanceread계정 잔액 조회
query_trial_balanceread시산표
list_journalsread분개장
get_journalread단일 분개 상세
list_raw_transactionsread입출금 표
search_evidenceread증거 전문 검색
list_open_itemsread미결 항목
list_periodsread회계 기간 조회
post_journalwrite분개 등록 (5계층 검증)
reverse_journalwrite분개 역분
flag_uncertaintywrite미결 생성
resolve_open_itemwrite미결 해결
ingest_file_blobwrite파일 추출
reconcile_and_proposewrite대사 제안
closing_previewclose마감 미리보기
closing_commitclose마감 확정
register_resolution, list_resolutions, link_journal_to_resolutionwrite결의서 관리
compose_kb_mmf_q_journals, compose_kbsc_q_journalswrite분기 마감 자동 분개
register_evidence_blob, promote_source_filewrite증거·파일 관리
transition_periodclose기간 상태 전이
register_entity, list_sub_entitiesadminentity 등록 + 자회사/펀드 등 sub-entity 조회 (D-ops-30 Phase 2)
list_scrape_schedules, get_scrape_scheduleread포털 스크래퍼 주기 조회 (D-frame-scrape-schedule)
set_scrape_schedulewrite스크래퍼 주기 설정 (raw cron 또는 daily/weekly/monthly 프리셋, KST)
기타: B/S·I/S statements, fixed asset, export, snapshot 등각각정합성·내보내기

전체 정확한 카운트는 grep -c '@mcp.tool' /Users/axe/frame/src/frame/mcp/server.py 로 확인 (현재 54).

Schema discovery — GET /frame/schemas

Blueprint 의 artifact + PARA 지식 레이어 (D-bp-artifact-1) 가 fact 의 typed schema 권위를 MCP 에 위임. frame 은 자기가 생산하는 fact kind 를 /schemas 엔드포인트로 노출 — Blueprint 가 fetch + cache + version (B-bp-artifact-schema-discovery).

curl -H "Authorization: Bearer $FRAME_MCP_TOKEN" \ https://axe.axelabs.ai/frame/schemas

응답 envelope (안정 형식):

{ "version": "1.0", "service": "frame", "schemas": { "[email protected]": { "description": "단일 계정의 시점 잔액 (debit/credit 합계 + 순 balance).", "produced_by": ["query_balance"], "key_fields": ["entity_id", "account_code", "as_of_date", "debit", "credit", "balance", "policy_id"] }, "[email protected]": { ... }, "[email protected]": { ... } } }

13 schemas 현재 노출 (balance / trial_balance / journal / journal_list / raw_transaction / open_item / resolution / fixed_asset_book_value / income_statement / balance_sheet / cash_flow_statement / equity_changes_statement / general_ledger). 추가 schema 는 src/frame/mcp/schemas.py (SOT) 에 등재.

cash_flow_statement 활동 분류 (간접법, src/frame/ops/statements.py): 명시적 dict (OPERATING_ASSETS/OPERATING_LIABILITIES/INVESTING_ACCOUNTS/FINANCING_ACCOUNTS) 우선, dict 에 없는 신규 계정만 type+prefix fallback (D-frame-3): asset 且 code prefix 13투자활동 (1300번대 투자자산 전체, 신계정 자동 포함); liability 且 차입금 코드(BORROWING_LIABILITIES=2105) → 재무활동. ⚠️ 2101 미지급금/2102 미지급세금 등 영업성 부채는 영업활동 유지 (차입금 코드만 명시 — 미지급금 재무 누출 차단). 분류 완전 시 reconciliation_diff=0·verified=true, 누락 계정은 unclassified_accounts 로 노출.

  • 인증: required (JWTAuthMiddleware 적용 — _PUBLIC_PATHS 에 미포함). MCP 클라이언트의 동일 토큰 재사용
  • versioning: schema_id 의 @{version} suffix (예: [email protected]@2.0 incompatible 시), 환경 envelope version 은 응답 모양 변경 시에만 bump
  • 소스 권위: src/frame/mcp/schemas.py 가 SOT — 외부 schema registry 없음

데이터 모델

Shared schema

테이블역할
entity등록된 entity 메타 (corp + fund 평등 평면). 컬럼: id PK · legal_name (영문 사업자등록 SoT) · display_name (한글 표시명, NULL→legal_name 폴백, 0010_shared) · biz_no · schema_name · accounting_standard · fiscal_year_start_month · entity_kind (corporate/kip/kvf, D-ops-22) · fund_meta JSONB (펀드 결성·약정·LP 등, D-ops-22) · closed_at (청산일, D-ops-22)
entity_relationshipentity 간 관계 (PR #26 + D-ops-22). entity_a (holder) / entity_b (target) / kind 8 종 (parent_subsidiary · sister_company · common_controlling_party · common_director · related_party · joint_venture · gp_managed_fund · lp_invested_fund) / ownership_numerator/denominator/unit (amount_krw/shares/units — % 는 반올림 오차로 폐기) / effective_from·until / evidence_ids[]
cross_journal_link (D-ops-22)cross-entity mirror 분개 pair. link_kind (gp_commitment / mgmt_fee / carried_interest / capital_call / distribution / other) + (left_entity_id, left_journal_id) ↔ (right_entity_id, right_journal_id). UNIQUE 양쪽. schema 격리 환경 무결성
format_profile파일 포맷 캐시 (vendor + column_signature)
format_profile_clarificationclarify Q&A 누적
account_template표준 계정과목 (KSME/KGAAP/KIFRS)
idempotency_record멱등성 키 (cross-entity write dedup)
oauth_authorization_codesOAuth proxy 잔재 (현재 dormant)
scrape_schedule (D-frame-scrape-schedule)포털 스크래퍼 주기. source_key · entity_id · enabled · cron (KST) · window_days · last_run_at/last_status/next_due_at. MCP set_scrape_schedule 로 조정, 호스트 frame scrape-due 가 실행

Per-entity schema (axec, axev, …)

테이블역할
account계정 (chart-of-accounts)
journal분개 헤더
journal_line분개 라인 (debit/credit)
raw_transaction원천 거래 (은행 입출금 등)
bank_account계좌 메타
source_file업로드된 파일 (content_hash, portable storage_url)
fiscal_period회계 기간 (open/soft_closed/hard_closed)
open_item미결 (severity, resolution_note)
evidence증거 (kind, content_hash, portable storage_url)
audit_log쓰기 추적 (모든 INSERT/UPDATE/DELETE)
entity_meta사업자등록번호, 법인명
fixed_asset고정자산
security_holder주주 (PII 암호화)
commitment_ledger펀드 약정·납입 ledger (kip/kvf 전용 — D-ops-30 Phase 3)
lp_masterLP master (external_entity_id FK → shared.entity, D-ops-30 Phase 3)

accounting_policy.standard CHECK (D-frame-fund-ksme-policy-check, 2026-05-22): 0003 의 CHECK 가 fund kind 의 fund_ksme 미허용하여 KIP/KVF entity 등록 시 zombie schema (0 tables). 0003 코드 패치 + 0018 migration 으로 ('ksme','kgaap','kifrs','fund_ksme') 확장. fund_ksme chart_template (D-ops-30 Migration #2) 가 fund entity bootstrap 시 자동 적용.

evidence / source_file blob 저장 (D-frame-2, 2026-06-04): 모든 evidence·source_file blob 은 content-addressed 로 .local/files/<hash[:2]>/<hash>/<filename> 에 복사 (Postgres 에 bytea 없음 — 큰 파일은 file store). storage_urlportable = relative <hash[:2]>/<hash>/<filename>, read 시 frame.ingest.pipeline.resolve_storage_path()FRAME_FILE_STORAGE_PATH (.local/files, 컨테이너 /app/.local/files) 기준 absolute 로 매핑. local://<host 경로> 또는 CWD 의존 absolute 경로 금지 — 컨테이너가 host 경로 read 불가 + 원본 rename/move 시 깨짐 (host /Users/.../.local/files ↔ 컨테이너 /app/.local/files 불일치). resolver 는 legacy 형식 (portable / /app/... / /Users/... / local://<abs>) 을 모두 tolerant. migration 0020_normalize_evidence_storage_url 가 기존 row 를 portable 로 정규화 (blob 실존 시만, 부재 row 미변경). 무결성 probe: GET /frame/health/storage.

디렉토리 구조

src/frame/ ├── auth.py TokenClaims, encode/decode_token ├── auth_oidc.py Microsoft id_token 검증 ├── config.py customers.yaml 로더 + accessor ├── cli.py 37+ click commands ├── errors.py FrameError 계층 ├── observability.py @instrument, Prometheus ├── db/ │ ├── engine.py async pool, context manager │ ├── shared.py shared 테이블 정의 │ ├── entity.py entity 메타 factory (35개 테이블) │ └── migrations.py upgrade_all(), register_entity() ├── ingest/ 파일 → raw_transaction │ ├── reader.py xls/xlsx/csv parser │ ├── llm.py Claude format 분석 │ ├── profile.py format_profile 캐시 │ ├── hometax.py 홈택스 vendor │ ├── kb_fund.py KB 펀드 │ └── card.py 카드사 ├── accounting/ M2+ 회계 계층 │ ├── chart.py chart-of-accounts │ ├── journal.py 분개 모델 │ ├── classify.py raw → journal_line │ ├── balance.py trial_balance, B/S │ └── reconcile.py 대사 ├── ops/ MCP tool 로직 │ ├── queries.py │ ├── write.py post_journal (5계층 검증) │ ├── period.py list_periods, transition_period │ ├── workflow.py reconcile_and_propose, closing_* │ ├── open_item.py │ ├── quarterly_close.py │ └── snapshot.py ├── mcp/ MCP 서버 │ ├── server.py FastMCP instance, 44 tools 등록 │ ├── http_server.py Starlette, OAuth middleware │ ├── prompts.py 4 prompts (reconcile_walkthrough 등) │ ├── oauth.py OAuth proxy (D-ops-15, dormant) │ └── tools/ 각 tool 구현 (13 .py) ├── security/ │ └── pii.py pgcrypto pgp_sym_encrypt alembic/ ├── versions/ shared (0001~0007) └── entity_versions/ per-entity (0001~0016) docs/ops/ 운영 정책 6 파일 ├── naming-conventions.md ├── access-control.md ├── jwt-issuance.md ├── mcp-client-config.md ├── onboarding-operator.md └── onboarding-employee.md

CLI 명령어

# DB frame migrate # shared + 모든 entity frame register-entity --id axec --legal-name "에이엑스이 코퍼레이션" # fund (D-ops-22, 2026-05-22) — KIP / KVF 등록 frame register-entity --id axev_kvf_001 \ --legal-name "AXE 시드 1호 벤처투자조합" \ --kind kvf \ --fund-meta '{"founded_at":"2024-03-15","commitment_total_krw":5000000000,"term_years":7}' frame list-entities # 기간 frame open-period --entity axec --year 2026 frame close-period --entity axec --period-id 7 --status soft_closed frame list-periods --entity axec # 추출 frame seed-profile &lt;spec.json&gt; frame ingest --entity axec --file &lt;path&gt; frame list-issues --entity axec # 스크래핑 (외부 포털 → ingest, host-only / launchd) frame scrape lottecard # 롯데 법인카드 승인내역(개인정보 미포함) → axec 적재 frame scrape lottecard --entity axtest --since 2026-04-01 --until 2026-04-30 # 헤드풀 Playwright (EIWAF 가 headless 차단). 법인전체 = 법인의 모든 카드. # 자격증명: FRAME_LOTTE_PORTAL_USERNAME / _PASSWORD (vault → .env.local). frame scrape-due [--dry-run] # launchd tick: shared.scrape_schedule 중 due 인 것만 실행 # 주기는 DB(MCP set_scrape_schedule)로 조정. com.frame.scrape-tick 이 08–21시 매시 호출. # 회계 frame post-journal --entity axec --spec-path &lt;json&gt; frame classify --entity axec frame trial-balance --entity axec --as-of 2026-05-31 frame integrity-check --entity axec # 토큰 발급 frame mcp-token --sub [email protected] --customer axe \ --entity axec:read,write,close \ --entity axev:read,write,close \ --ttl 2592000 # 서버 frame mcp-serve # stdio frame mcp-health &lt;token&gt;

OAuth-RP 흐름

Claude Code/claude.ai → POST /frame/mcp (no auth) frame → 401 + WWW-Authenticate: Bearer resource_metadata="..." client → GET /.well-known/oauth-protected-resource frame → {resource: "https://axe.axelabs.ai/frame/mcp", authorization_servers: [Microsoft]} client → Microsoft OAuth flow (PKCE) Microsoft → access_token (aud = Application ID URI) client → POST /frame/mcp Authorization: Bearer &lt;access_token&gt; frame middleware → JWKS RS256 verify + aud + iss + email extract → entity 매핑 frame → MCP session 생성 → tool 호출 가능

상세: 인증 · 권한.

Custom Connector 등록 (신규 사용자)

운영자: axe secret send AZURE_FRAME_MCP_CLIENT_SECRET --service frame --to <local-part> → URL 발급 → /api/admin/broadcast-dm 자동 Teams DM. 전체 흐름은 /architecture/secrets § 사람에게 전달. 수신자 화면 절차는 /onboard/claude-frame-setup (5분 가이드).

환경 변수

D-ops-18 (2026-05-21): 비밀 vars (FRAME_DB_PASSWORD, FRAME_JWT_SECRET, FRAME_SECRET_KEY, AZURE_FRAME_MCP_CLIENT_SECRET, FRAME_PII_PASSPHRASE_*, CLAUDE_CODE_OAUTH_TOKEN) 는 모두 env_file .env.local 에서 직접 dump. docker-compose.ymlenvironment: 블록에 default-empty 변수 치환 ($VAR 형식) 으로 중복 정의 금지 — Compose precedence 가 env_file 의 vault 값을 shadow 했던 trap. 환경변수 = vault SoT 단일 출처 (axe secret pull frame 이 vault → .env.local atomic write).

# DB FRAME_DB_HOST=postgres FRAME_DB_PORT=5432 FRAME_DB_USER=frame FRAME_DB_PASSWORD= FRAME_DB_NAME=frame # JWT FRAME_JWT_SECRET= # ≥32 bytes AZURE_FRAME_MCP_CLIENT_SECRET= # OAuth confidential client # PII FRAME_PII_PASSPHRASE_AXEC= FRAME_PII_PASSPHRASE_AXEV= FRAME_PII_PASSPHRASE_REALCHOICE= FRAME_PII_SALT_DIR=/root/.frame # MCP HTTP FRAME_MCP_HOST=0.0.0.0 FRAME_MCP_PORT=3710 FRAME_MCP_LOG_LEVEL=INFO FRAME_MCP_ALLOWED_HOSTS=frame-mcp:*,localhost:*,... FRAME_MCP_ALLOWED_ORIGINS=https://axe.axelabs.ai,... # Multi-tenant FRAME_CUSTOMERS_YAML=/etc/axe/customers.yaml FRAME_CUSTOMER_ID=axe FRAME_DEPLOY_COLOR=blue # Blueprint platform identity (D-axe-idp-1) — 전부 빈 기본값 = 비활성(non-breaking). # BLUEPRINT_ISSUER set 시 Blueprint 발급 platform 토큰을 신뢰. 상세: architecture/platform-identity. BLUEPRINT_ISSUER= # 미설정=무시(현재 동작). set 시 활성 BLUEPRINT_JWKS_URL= # 미설정 시 issuer 에서 유도 BLUEPRINT_AUDIENCE=https://axe.axelabs.ai # 롯데카드 스크래퍼 (host-only — MCP 컨테이너엔 없음, launchd 만 사용) FRAME_LOTTE_PORTAL_USERNAME= # 법인관리자 로그인 ID FRAME_LOTTE_PORTAL_PASSWORD= # vault frame/axe/lotte-portal-password # Storage (host bind mount) FRAME_STORAGE_HOST_DIR=${HOME}/frame/.local # default — host 의 home 기준. /Users/axe/... hardcode 금지

⚠️ /Users/axe/ 절대경로 hardcode 금지: FRAME_STORAGE_HOST_DIR (그리고 docker-compose 의 volume mount default) 는 반드시 ${HOME}/... 패턴을 사용. customer macmini 의 운영 user 가 /Users/<other>/ 인 경우 hardcode 된 /Users/axe/... 는 path 부재 → volume bind fail 또는 frame storage 무효 (2026-05-25 트루비아 측 발견 — R1 fix wrapper .env.local${HOME} 명시, B-frame-keychain-to-vault 후속 fix 후 compose default 도 ${HOME} 으로 자동 일반화).

운영 노트

  • Blue/green deploy 지원 (port 3710/3711 동시 실행, alias swap)
  • Append-only: raw_transaction, journal_line 의 DELETE/UPDATE 권한이 REVOKE
  • PII: pgp_sym_encrypt + entity 별 passphrase (분실 = 영구 손실, 의도)
  • 정합성: frame integrity-check 5가지 검사 (journal_balance, audit_completeness, period_consistency, source_link_coverage, evidence_storage_presence — 증빙 bytes 가 디스크에 실존하는지 orphan 탐지). nightly com.frame.integrity-check, error/blocker → open_item 자동 등록.
  • LLM 비용 추적: llm_call 테이블에 모든 호출 기록
  • 포털 스크래퍼 스케줄·SLA (D-frame-scrape-schedule): 주기는 shared.scrape_schedule 의 KST cron (MCP set_scrape_schedule 로 조정), 호스트 com.frame.scrape-tick 이 08–21시 매시 due 만 실행. SLA catch-up — 다운타임 중 놓친 실행은 복구 후 첫 in-window 틱에서 1회 실행하며, look-back 을 now−last_run(상한 365일)까지 넓혀 그 기간 전체를 backfill (row_hash 중복제거, 데이터 손실 0). 부팅/wake 시 RunAtLoad 로 즉시 catch-up, get_scrape_scheduleoverdue 로 가시화.

신규 customer 1 차 사이클 runbook (D-day ~ D+30)

신규 customer (예: realchoice) 가 6/1 D-day 이후 첫 한 달 동안 frame 내부에 회계 데이터를 적재하고 1 차 사이클을 닫는 권장 순서. B-frame-lpm-classify (자동 분류) 가 완성 (ETA D+90, 아래) 되기 전 시점이라 분개 일부는 수작업 분개로 보완합니다.

시점작업명령 / 도구
D-day (6/1)frame stack 부팅 (운영자 측)axe customers ingest <id> ... --apply + axe onboard <id> --apply
D+1chart of accounts auto-seed — entity 등록 시점에 44-account KSME 자동 복사 (D-ops-21)frame register-entity --id <id> --legal-name "..." --kind corporate --accounting-standard ksme
D+2 ~ D+7원천 데이터 ingest — KB/거래은행 거래내역 CSV 업로드. LLM 이 포맷 자동 인식 → raw_transaction 적재ingest_file_blob MCP tool (또는 frame ingest <file> CLI) → analyze_file_format 자동 호출
D+8 ~ D+14trial balance / 결산 잔액 등록 — D2 결정의 “결산 시점 trial balance” 적재. 외부 회계법인 검수본 기준register_resolution + post_journal (개시 분개)
D+15 ~ D+21payroll 매칭 — Hive Phase 1 활성 후 pending_payroll INSERT → frame match_pending_sweep 자동 매칭 → 잔여 수동 매칭list_pending_payroll / match_pending_to_raw_tx / match_pending_sweep
D+22 ~ D+28수작업 분개 보완analyze_file_format 가 인식한 raw_transaction 중 자동 매칭 못 한 것은 운영자 또는 customer 측 회계 담당자가 post_journal 로 수작업 분개. LLM proposal 보조는 가능하나 commit 은 사람 confirmpost_journal + query_trial_balance 으로 매일 진척 확인
D+301 차 사이클 회고 — 자동 매칭률 / 수작업 분개 시간 / 실패 패턴 측정 → B-frame-lpm-classify (M2 top) 의 prompt 학습 데이터로 활용query_trial_balance / income_statement / balance_sheet

B-frame-lpm-classify ETA (자동 분류 pipeline)

ETA도달 수준회신 시점
D+90 (2026-09-01 목표)minimum viable — LLM 이 raw_transaction 보고 journal_line proposal 생성, 운영자/회계 담당자 confirm 후 commit. 정확도 60~80% (axe 백테스트 기준)2026-08 중 베타 → 9 월 production
D+120 (2026-10-01)rule-based pre-classifier 결합 — 반복 거래는 정규식 룰, 1 회 거래는 LLM. 정확도 80%+2026-10
2026-12-012027-01-01 cutover 직전 stable — axec/axev 1 년치 backtest 완료 + 회귀 test greencutover 2 주 전

D+30 ~ D+90 동안의 Truvia 측 분개 부담은 **위 표의 D+8D+28 의 수작업 + 매칭 보조** 로 흡수. 1 차 사이클 = 한 달 raw_transaction + payroll + 결산 잔액 = 추정 200500 건 분개. 자동 매칭 0% 가정 시 회계 담당자 1 인 × 2~5 일/월 effort.

Frame 내부 운영의 SSOT

  • 본 페이지 = 운영자 측 SSOT (signed by 액스코퍼레이션 주식회사)
  • /Users/axe/frame/DECISIONS.md = frame 내부 결정 누적
  • /services/hive 의 payroll 흐름 = frame pending_payroll 의 입력 채널
  • 외부 회계법인 cross-check workflow = B-frame-cross-check-workflow (별도 backlog, Truvia 측 외부 회계법인 회신 시 등재)

관련 문서

Last updated on