Metadata-Version: 2.4
Name: dtxwiki
Version: 0.0.1
Summary: Local-first MCP-native Markdown wiki for AI tools.
Author: dtxWiki contributors
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/dtxSuite/dtxWiki
Project-URL: Repository, https://github.com/dtxSuite/dtxWiki
Project-URL: Issues, https://github.com/dtxSuite/dtxWiki/issues
Project-URL: Changelog, https://github.com/dtxSuite/dtxWiki/blob/main/CHANGELOG.md
Keywords: ai,mcp,markdown,wiki,knowledge-base
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Documentation
Classifier: Topic :: Software Development :: Documentation
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: alembic>=1.14
Requires-Dist: argon2-cffi>=23.1
Requires-Dist: cryptography>=42
Requires-Dist: fastapi>=0.115
Requires-Dist: httpx>=0.27
Requires-Dist: mcp==1.27.1
Requires-Dist: pydantic>=2.8
Requires-Dist: pydantic-settings>=2.4
Requires-Dist: pg8000>=1.31
Requires-Dist: python-multipart>=0.0.20
Requires-Dist: PyJWT>=2.8
Requires-Dist: sqlalchemy>=2.0
Requires-Dist: tomlkit>=0.13
Requires-Dist: typer>=0.12
Requires-Dist: uvicorn[standard]>=0.30
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: pytest>=8.3; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Requires-Dist: twine>=6.0; extra == "dev"
Dynamic: license-file

# dtxWiki

dtxWiki는 로컬 우선 MCP-native Markdown wiki 구현체입니다. 웹 UI는 Ask, Library, Context, Agent, Activity 5개 primary surface를 제공하고, REST, MCP stdio server, HTTP MCP endpoint는 Markdown page, ContextPage, AgentPage, source, search, ChangeSet, Review, Audit 흐름을 같은 backend contract 위에서 다룹니다.

현재 범위는 SQLite 저장소, bundled React UI, CodeMirror Markdown editor, deterministic context-first Ask surface, default-off LLM provider 추상화, session/PAT 기반 인증, workspace membership RBAC, 12개 P0 MCP tool, Sprint_0/Sprint_1 legacy Pages 호환 tool, MCP stdio/HTTP onboarding, OAuth 2.1-style DCR/PKCE/JWT flow, MCP client 등록 CLI, background worker CLI까지입니다.

## 주요 기능

- **Primary surface**: Ask / Library / Context / Agent / Activity 5개 sidebar surface와 TreeRail, Breadcrumbs, StatusPill 기반 공통 navigation
- **Markdown page 호환**: Sprint_0/Sprint_1 Pages CRUD route와 `list_pages`, `read_page`, `write_page` MCP tool을 legacy 호환 surface로 유지
- **Markdown editor와 preview**: CodeMirror 6 editor, `react-markdown`, `remark-gfm`, `rehype-sanitize` 기반 preview, canonical `content_hash` stale conflict 감지
- **Context workflow**: ContextPage draft, update, publish, citation, feedback, review decision, Ask turn에서 ContextIngressProposal 생성과 accept/reject
- **Agent workflow**: AgentPage draft와 activation, owner/reviewer role guard, rule-based AgentInputBundle 생성, TTL/version pinning, omission reason 기록
- **Tree placement**: workspace tree, primary placement uniqueness, move/rename, cycle prevention, `target`/`expanded` URL state
- **Source pipeline**: source discovery, extraction job, inline/file storage ref, `DTXWIKI_LIBRARY_FILE_ROOTS` 기반 root-restricted `file://` read
- **Search와 Ask**: Context, source, artifact, feedback, change, AgentPage 대상 keyword search와 deterministic context-first Ask. Anthropic/OpenAI LLM provider 추상화는 default off로 제공되며, LLM 기반 Ask answer 생성과 semantic/hybrid retrieval은 Epic_1 이후 범위입니다.
- **Change, Review, Audit**: ChangeSet, ChangeOperation, ReviewItem, FeedbackItem, MCPToolCall, AuditEvent를 write workflow에서 atomic하게 기록
- **인증과 RBAC**: browser session, personal API token, workspace membership role(owner/editor/viewer), REST/MCP access gate
- **MCP stdio/HTTP**: P0 spec tool 12개, `list_my_workspaces`, legacy Pages tool 3개를 stdio와 HTTP endpoint로 제공
- **OAuth MCP onboarding**: OAuth protected resource metadata, authorization server metadata, dynamic client registration, browser consent, S256 PKCE, RS256 JWT access token, rotating refresh token
- **REST API**: `/api/v1` 아래 OpenAPI artifact 기준 65개 operation, 공통 error envelope, cursor pagination, validation envelope
- **Worker runtime**: source discovery, extraction, feedback resolution, bundle expiration job을 별도 worker CLI로 처리
- **단일 사용자 패키징**: local wheel build/install, `dtxwiki bootstrap`, bundled frontend serving, Claude/Cursor/Codex MCP client 등록

## 요구 사항

dtxWiki 는 **SQLite 전용**으로 동작합니다 — 별도 DB 서버가 필요 없습니다.

- **solo / 평가** (단일 사용자, 로컬): Python 3.12+
- **production self-host — systemd**: Ubuntu 22.04+ (root), 공개 도메인 1개, 80/443 개방
- **production self-host — Docker**: Docker Engine + Docker Compose v2, 공개 도메인 1개, 80/443 개방

소스를 직접 빌드/수정하려는 개발자(Node.js 20+, npm, 로컬 인프라 compose 등)는
[DEVELOPMENT.md](DEVELOPMENT.md)를 참고하세요.

## 설치

설치 경로는 용도에 따라 셋입니다. **모두 SQLite를 사용**합니다.

> PyPI 첫 릴리스 전에는 소스 체크아웃에서 `make build-wheel` 로
> wheel(`dist/dtxwiki-<version>-py3-none-any.whl`)을 만들어 아래
> `pip install dtxwiki` / `--wheel` 자리에 그 wheel 경로를 사용하세요. PyPI 게시 후에는
> 같은 명령에서 패키지명 `dtxwiki` 를 그대로 사용할 수 있습니다. (Docker 경로는 소스에서
> 빌드하므로 PyPI 게시 여부와 무관합니다.)

### 1) solo / 개인·평가 (단일 사용자, 인터넷 노출 없음)

SQLite DB 가 `~/.dtxwiki/data/dtxwiki.sqlite` 에 자동 생성됩니다.

```bash
# PyPI 게시 후:
pipx install dtxwiki
dtxwiki

# 첫 릴리스 전(소스에서):
make build-wheel
pipx install dist/dtxwiki-*.whl
dtxwiki
```

`dtxwiki` 는 migration/seed 후 backend 를 띄우고 브라우저(`http://127.0.0.1:21080/`)를
엽니다. dtxWiki 는 LLM 없이도 완전히 동작하므로(LLM provider 기본 off, Ask 는
deterministic) 아래 MCP 등록은 **선택사항**입니다.

아래 명령은 **dtxWiki 와 같은 머신에 설치된** AI 클라이언트(Claude Desktop/Claude Code/
Cursor/Codex)에 로컬 **stdio** MCP 서버(`dtxwiki-mcp`)를 등록합니다. 설치된 클라이언트가
없으면 `mcp register` 는 `No MCP clients detected.` 로 그냥 넘어갑니다.

```bash
dtxwiki claude install
dtxwiki cursor install
dtxwiki codex install
dtxwiki mcp register --all --yes
```

> **AI 클라이언트가 dtxWiki 와 다른 머신에 있다면** (예: 원격 서버의 dtxWiki + 내 노트북의
> Claude Code) 위 stdio 등록은 쓰지 마세요. 대신 서버의 `/mcp` 엔드포인트를 PAT/OAuth 로
> 등록합니다 — [원격 서버 배포와 팀 MCP 연결](#원격-서버-배포와-팀-mcp-연결) 참고:
> ```bash
> claude mcp add --transport http dtxwiki https://wiki.example.com/mcp \
>   --header "Authorization: Bearer dtxw_..."
> ```

### 2) production self-host — systemd (한 명령)

맨 Ubuntu 서버에 **도메인 + 이메일 2개**만으로 설치합니다. SSL 은 Caddy 가 Let's Encrypt
에서 자동 발급합니다(전제: 도메인 DNS A 레코드 → 서버 공인 IP, 80/443 개방).

```bash
curl -fsSL https://<host>/install.sh | sudo bash -s -- \
    --domain wiki.example.com --email admin@example.com
# 첫 PyPI 릴리스 전에는 빌드한 wheel 을 함께 전달:
#   sudo bash install.sh --domain ... --email ... --wheel /path/dtxwiki-<version>.whl
```

상세 절차와 Day-2 재설정(`dtxwiki config set`)은 아래
[원격 서버 배포와 팀 MCP 연결](#원격-서버-배포와-팀-mcp-연결) 및
[DEPLOYMENT.md](DEPLOYMENT.md) 를 참고하세요.

### 3) production self-host — Docker Compose

레포를 clone 한 뒤 **도메인 + 이메일 2개**만 정하면 됩니다. `init`(keygen + secret +
bootstrap) → `app` + `worker` → `caddy`(80/443, auto-TLS) 스택이 뜨고, **SQLite 는 named
volume 에 영속**됩니다(JWT 키·session secret 포함, 재시작해도 토큰·세션 유지).

```bash
cd infra/prod
cp .env.docker.example .env      # DTXWIKI_DOMAIN + DTXWIKI_ADMIN_EMAIL
docker compose up -d --build
```

이미지는 멀티스테이지 [Dockerfile](Dockerfile)(프론트 빌드 → SPA 를 `backend/static` 에
번들 → `pip install`)에서 소스로 빌드됩니다. 자세한 구성은
[DEPLOYMENT.md](DEPLOYMENT.md) 를 참고하세요.

> 개발 checkout 셋업·테스트·로컬 인프라 compose 는 [DEVELOPMENT.md](DEVELOPMENT.md) 를 보세요.

## 웹 UI 사용법

1. `http://127.0.0.1:21173/` 또는 packaged server의 `http://127.0.0.1:21080/`에 접속합니다.
2. `/`는 Sprint_0/Sprint_1 legacy Pages landing입니다. 페이지가 있으면 첫 page reader로 이동하고, 없으면 `New page` action이 표시됩니다.
3. Sidebar의 primary surface는 Ask, Library, Context, Agent, Activity입니다.
4. Library(`/library`)는 source 목록, artifact/version detail, tree selection을 보여줍니다. legacy Pages CRUD는 `/pages/*` route에서 계속 동작합니다.
5. Context(`/context`)는 ContextPage tree, version/change history, draft/update/publish 상태를 보여줍니다.
   기존 북마크 호환을 위해 legacy alias `/context-map`은 `/context`로 redirect됩니다.
6. Agent(`/agent`)는 AgentPage category, draft, activation, AgentInputBundle preview를 다룹니다.
7. Ask(`/ask`)는 session/turn 기반 deterministic answer를 만들고, assistant turn을 ContextIngressProposal로 저장할 수 있습니다.
8. Activity(`/activity`)는 ChangeSet, ReviewItem, AuditEvent, MCPToolCall, job 상태를 기간 filter와 lane으로 보여줍니다.
9. Search, Review, Lint, Setup, Settings는 topbar action 또는 route(`/search`, `/review`, `/lint`, `/setup`, `/settings`)로 접근합니다.

현재 frontend는 session cookie를 기본 인증 경로로 사용합니다. 개발 서버에서는 `VITE_API_BASE_URL` 기본값으로 `http://127.0.0.1:21080`을 사용하고, backend가 번들된 frontend를 serve하는 packaged build에서는 same-origin `/api/v1`로 요청합니다. CI/headless 호출은 Settings > API tokens 또는 `dtxwiki users add`로 계정을 만든 뒤 PAT를 발급해 `Authorization: Bearer dtxw_...` 형식으로 사용할 수 있습니다.

## 환경 변수

| 변수 | 기본값 | 용도 |
| --- | --- | --- |
| `DTXWIKI_APP_ENV` | `development` | Backend 실행 환경. `production`/`prod`에서는 CORS startup guard가 강화됩니다. |
| `DTXWIKI_DATA_DIR` | `~/.dtxwiki` | 기본 SQLite DB, logs, local source root의 기준 디렉터리 |
| `DTXWIKI_DATABASE_URL` | `sqlite+pysqlite:///~/.dtxwiki/data/dtxwiki.sqlite` | Backend DB URL. dtxWiki 는 SQLite 전용으로 동작합니다. |
| `DTXWIKI_SESSION_SECRET` | unset | Session cookie signing secret. unset이면 local development fallback을 사용합니다. |
| `DTXWIKI_SESSION_TTL_SECONDS` | `43200` | Session cookie TTL |
| `DTXWIKI_SESSION_COOKIE_NAME` | `dtxwiki_session` | Session cookie 이름 |
| `DTXWIKI_SESSION_COOKIE_SECURE` | derived | HTTPS/public base URL이면 secure cookie 사용 |
| `DTXWIKI_OAUTH_ALLOW_DCR` | `true` | OAuth dynamic client registration 허용 여부 |
| `DTXWIKI_OAUTH_ACCESS_TOKEN_TTL_SECONDS` | `3600` | OAuth RS256 access token TTL |
| `DTXWIKI_OAUTH_REFRESH_TOKEN_TTL_SECONDS` | `2592000` | OAuth refresh token TTL |
| `DTXWIKI_OAUTH_JWT_PRIVATE_KEY_PEM` | unset | OAuth access token signing private key PEM. unset이면 process-local 개발 key를 생성합니다. |
| `DTXWIKI_WORKSPACE_SLUG` | `default` | 기본 workspace slug |
| `DTXWIKI_BACKEND_PORT` | `21080` | CLI/package backend bind port. `--port` CLI 옵션이 있으면 옵션이 우선합니다. |
| `DTXWIKI_CORS_ORIGINS` | derived local dev origins | Backend CORS 허용 origin. comma-separated 또는 JSON list를 지원합니다. unset이면 backend/frontend port에서 개발용 localhost origin을 파생합니다. `*`와 empty string은 credentials 사용 때문에 거부되며 production에서는 명시적인 non-local origin 목록을 반드시 설정해야 합니다. |
| `DTXWIKI_AUTO_BOOTSTRAP` | `true` | CLI/package startup에서 migration과 seed를 자동 수행할지 제어 |
| `DTXWIKI_PUBLIC_BASE_URL` | request-derived | OAuth issuer/resource URL, secure-cookie derivation, external client metadata에 사용할 public base URL |
| `DTXWIKI_LIBRARY_FILE_ROOTS` | `~/.dtxwiki/library` | `file://` source storage ref 허용 root. 여러 root는 OS path separator로 구분합니다. |
| `DTXWIKI_LLM_PROVIDER` | `none` | Optional LLM provider selector. 지원값은 `none`, `anthropic`, `openai`입니다. 현재 Ask answer 생성 경로는 deterministic default를 유지합니다. |
| `DTXWIKI_EMBEDDING_PROVIDER` | `none` | Embedding provider selector placeholder. Embedding retrieval은 Epic_1 이후 범위입니다. |
| `DTXWIKI_ANTHROPIC_API_KEY` | unset | `DTXWIKI_LLM_PROVIDER=anthropic` 사용 시 필요한 API key |
| `DTXWIKI_OPENAI_API_KEY` | unset | `DTXWIKI_LLM_PROVIDER=openai` 사용 시 필요한 API key |
| `DTXWIKI_MCP_CLIENT_ID` | client별 등록값 또는 `internal-agent` | MCP actor 식별자 |
| `DTXWIKI_MCP_LOG_LEVEL` | `INFO` | MCP stdio server logging level |
| `VITE_API_BASE_URL` | dev server: `http://127.0.0.1:21080`, packaged build: same-origin | Frontend가 호출할 backend base URL |
| `VITE_DTXWIKI_API_TOKEN` | unset | 선택적 PAT bearer token. 일반 browser session에서는 비워 둡니다. |

AgentInputBundle TTL은 환경변수가 아니라 workspace policy(`bundle_ttl_default_seconds`, `bundle_ttl_max_seconds`)로 관리됩니다. 현재 코드에 `DTXWIKI_OBJECT_STORE_*`나 `DTXWIKI_JOB_QUEUE_*` 환경변수는 없습니다.

## REST API 사용 예시

REST API는 session cookie 또는 PAT bearer token을 요구합니다.

```bash
export DTXWIKI_API=http://127.0.0.1:21080/api/v1
export DTXWIKI_TOKEN=dtxw_...
```

Page 생성:

```bash
curl -sS -X POST "$DTXWIKI_API/pages" \
  -H "Authorization: Bearer $DTXWIKI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_slug": "default",
    "slug": "hello-page",
    "title": "Hello page",
    "body_md": "# Hello page\n\nThis page was created through REST."
  }'
```

Page 목록 조회:

```bash
curl -sS "$DTXWIKI_API/pages?workspace=default" \
  -H "Authorization: Bearer $DTXWIKI_TOKEN"
```

Pagination:

```bash
curl -sS "$DTXWIKI_API/pages?workspace=default&limit=10&cursor=0" \
  -H "Authorization: Bearer $DTXWIKI_TOKEN"
```

`limit`은 `1..100`입니다. `cursor`는 응답의 `next_cursor`로 받은 opaque base64 token을 그대로 전달합니다. 기존 numeric offset cursor는 Epic_0 호환 입력으로만 허용되며 `0..10000` 범위를 넘으면 거부됩니다.

Page 읽기:

```bash
curl -sS "$DTXWIKI_API/pages/<page_id>" \
  -H "Authorization: Bearer $DTXWIKI_TOKEN"
```

Page 수정은 stale write 방지를 위해 `base_version_id`와 `base_content_hash`를 함께 보내야 합니다.

```bash
curl -sS -X PATCH "$DTXWIKI_API/pages/<page_id>" \
  -H "Authorization: Bearer $DTXWIKI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Updated title",
    "body_md": "# Updated title\n\nNew body.",
    "base_version_id": "<current_version_id>",
    "base_content_hash": "<current_content_hash>"
  }'
```

Stale update는 HTTP `409`와 함께 server의 최신 version/hash 정보를 반환합니다.

Workspace 조회와 search:

```bash
curl -sS "$DTXWIKI_API/workspaces" \
  -H "Authorization: Bearer $DTXWIKI_TOKEN"

curl -sS -X POST "$DTXWIKI_API/workspaces/<workspace_id>/search" \
  -H "Authorization: Bearer $DTXWIKI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "context policy",
    "scope": "all",
    "retrieval_mode": "keyword",
    "limit": 10
  }'
```

전체 REST surface는 [shared/schemas/rest/v1/openapi.json](shared/schemas/rest/v1/openapi.json)을 기준으로 확인하세요.

REST error는 공통 envelope 형태를 사용합니다. FastAPI validation error도 `detail` 배열 대신 아래 shape로 반환됩니다.

```json
{
  "error_code": "validation_failed",
  "message": "body.slug: Field required",
  "request_id": "00000000-0000-0000-0000-000000000000",
  "warnings": []
}
```

## MCP stdio server

`dtxwiki-mcp`와 `python -m backend.mcp.stdio_server`는 표준 MCP JSON-RPC 2.0 stdio transport를 사용합니다. 로컬 단일 사용자 등록은 `dtxwiki claude install`, `dtxwiki cursor install`, `dtxwiki codex install`, `dtxwiki mcp register --all --yes` 명령으로 처리합니다. 원격 HTTP MCP 등록은 아래 [원격 서버 배포와 팀 MCP 연결](#원격-서버-배포와-팀-mcp-연결) 섹션을 참고하세요.

stdio MCP server 실행:

```bash
# 기본 ~/.dtxwiki/data/dtxwiki.sqlite 를 사용하거나, 같은 DTXWIKI_DATABASE_URL 을
# backend 와 공유하도록 설정한 뒤 실행합니다.
.venv/bin/dtxwiki-mcp
```

P0 spec MCP tool:

| 그룹 | Tool |
| --- | --- |
| Read/context | `guide`, `search`, `read`, `list_changes` |
| Context write | `write_draft`, `update_draft`, `cite`, `submit_feedback` |
| Agent | `list_agent_categories`, `read_agent_page`, `write_agent_page_draft`, `build_agent_input_bundle` |

Legacy Pages 호환 tool:

```text
list_pages, read_page, write_page
```

Legacy Pages tool은 `tools/list` description에 `[DEPRECATED in Epic_1/Sprint_1]`가 표시되고, 호출 결과 envelope의 `warnings`에 `deprecated_tool`을 포함합니다. 신규 MCP client는 `guide`, `search`, `read`, `write_draft`, `update_draft`, `list_changes` 등 Context/Agent 표준 tool을 사용하세요.

> 2026-05 Sprint_3 fix: 호출 결과 envelope 의 `warnings` 항목이 단순 문자열 코드에서
> `{code, message}` 객체 배열로 변경되었습니다. 기존 client 가
> `warnings[0] == "deprecated_tool"` 으로 비교한다면
> `warnings[0].code == "deprecated_tool"` 로 마이그레이션이 필요합니다.
>
> 2026-05 Sprint_3 Hotfix-2: 위 warning 객체의 JSON Schema title이
> `Warning`에서 `EnvelopeWarning`으로 rename되었습니다. Wire shape
> (`{code, message}`)은 동일하므로 `warnings[i].code` 접근 코드는 변경
> 불필요합니다. 단, schema로부터 자동 생성된 client type을 사용하는 경우
> 재생성이 필요합니다.

`write_page`, `update_draft` 계열 write는 stale update 감지를 위해 `base_version_id`와 `base_content_hash`를 사용합니다. 권한 또는 workflow 위반은 MCP tool result 안의 dtxWiki envelope로 `scope_denied`, `rejected_by_policy`, `bundle_expired`, `validation_failed`, `unsupported_retrieval_mode` 같은 error code를 반환합니다.

Unknown tool name 예시:

```json
{
  "envelope": {
    "result_status": "failed",
    "error_code": "unknown_tool"
  }
}
```

## Worker runtime

Source discovery, extraction, feedback-resolution, bundle-expiration job 은 backend 와
별도의 worker process 가 처리합니다. production self-host 에서는 worker 가 자동 실행됩니다 —
systemd 경로는 `dtxwiki-worker.service`, Docker 경로는 `worker` 컨테이너이며, 둘 다
backend 와 같은 SQLite DB 를 공유합니다. claim 로직은 database-backed queue
(`SELECT FOR UPDATE SKIP LOCKED` 지원 시)와 `--poll-interval-seconds`, `--max-jobs`,
`--idle-exit-after` 폴링 옵션을 사용합니다.

개발 checkout 에서 worker 를 직접 실행하는 방법은 [DEVELOPMENT.md](DEVELOPMENT.md) 를 보세요.

## 원격 서버 배포와 팀 MCP 연결

팀이 공유하는 dtxWiki 인스턴스를 원격 Ubuntu 서버에 self-host 로 올리고, 각 팀원의 PC 에서 웹 UI 와 MCP client 로 접속하는 표준 절차입니다. 이 섹션은 단일 사용자 packaged 실행과 달리 **외부에서 도메인으로 접근하는 다중 사용자 시나리오**를 다룹니다. 본 README 의 명령은 [DEPLOYMENT.md](DEPLOYMENT.md) 와 [infra/prod/.env.example](infra/prod/.env.example) 을 기반으로 합니다.

서버 설치·배포의 전체 절차(권장 한 명령 `install.sh` / Docker, 수동·디버그 Step, reverse
proxy 옵션, 백업, Day-2 재설정)는 위 [설치](#설치) 와 [DEPLOYMENT.md](DEPLOYMENT.md) 에
있습니다. 아래는 배포 토폴로지 개요와, 팀원 PC 에서 서버 `/mcp` 에 붙는 기초 가이드입니다.

### 새 서버 설치 빠른 가이드 (SQLite)

아래 절차는 새 Ubuntu 서버에 dtxWiki 를 SQLite DB 로 설치하는 최소 운영 경로입니다. 예시는 `wiki.example.com` 도메인을 사용하며, 실제 설치 시 본인 도메인으로 바꾸세요. Backend 는 `127.0.0.1:21080` 에만 bind 하고, 외부 접속은 Caddy/nginx reverse proxy 가 HTTPS 로 처리합니다.

1. DNS 와 방화벽을 준비합니다.

```bash
# DNS: wiki.example.com A/AAAA 레코드가 서버 public IP 를 가리키도록 설정
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```

2. 전용 계정과 데이터 디렉터리를 만듭니다.

```bash
sudo useradd --system --home /opt/dtxwiki --shell /usr/sbin/nologin dtxwiki
sudo mkdir -p /opt/dtxwiki /etc/dtxwiki /var/lib/dtxwiki /var/backups/dtxwiki
sudo chown -R dtxwiki:dtxwiki /opt/dtxwiki /var/lib/dtxwiki /var/backups/dtxwiki
sudo chown root:dtxwiki /etc/dtxwiki && sudo chmod 750 /etc/dtxwiki
```

3. Python 3.12 venv 에 dtxWiki 를 설치합니다.

```bash
sudo apt-get update
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:deadsnakes/ppa   # Ubuntu 22.04 에서 Python 3.12 이 필요할 때
sudo apt-get update && sudo apt-get install -y python3.12 python3.12-venv sqlite3

sudo -u dtxwiki python3.12 -m venv /opt/dtxwiki/.venv
sudo -u dtxwiki /opt/dtxwiki/.venv/bin/pip install --upgrade pip
sudo -u dtxwiki /opt/dtxwiki/.venv/bin/pip install dtxwiki
```

source checkout 에서 직접 wheel 을 만들어 설치하는 경우에는 마지막 줄을 다음처럼 바꿉니다.

```bash
# 개발 PC 또는 CI 에서 wheel 생성 후 서버로 복사
make test-packaged
sudo -u dtxwiki /opt/dtxwiki/.venv/bin/pip install /path/to/dtxwiki-<version>.whl
```

4. OAuth/JWT signing key 와 환경 파일을 준비합니다.

```bash
sudo install -o dtxwiki -g dtxwiki -m 700 -d /var/lib/dtxwiki/private
sudo -u dtxwiki /opt/dtxwiki/.venv/bin/dtxwiki keygen \
  --output /var/lib/dtxwiki/private/jwt.pem
sudo install -o root -g dtxwiki -m 0640 \
  /var/lib/dtxwiki/private/jwt.pem /etc/dtxwiki/jwt.pem
sudo rm /var/lib/dtxwiki/private/jwt.pem

sudo cp /opt/dtxwiki/.venv/share/dtxwiki/infra/prod/.env.example /etc/dtxwiki/dtxwiki.env
sudo chown root:dtxwiki /etc/dtxwiki/dtxwiki.env
sudo chmod 640 /etc/dtxwiki/dtxwiki.env
sudo nano /etc/dtxwiki/dtxwiki.env
```

`/etc/dtxwiki/dtxwiki.env` 에서 최소한 아래 값을 확인합니다.

```dotenv
DTXWIKI_APP_ENV=production
DTXWIKI_APP_MODE=team
DTXWIKI_PUBLIC_BASE_URL=https://wiki.example.com
DTXWIKI_CORS_ORIGINS=https://wiki.example.com
DTXWIKI_BEHIND_PROXY=true
DTXWIKI_FORWARDED_ALLOW_IPS=127.0.0.1
DTXWIKI_SESSION_SECRET=<python -c "import secrets; print(secrets.token_urlsafe(64))" 로 생성>
DTXWIKI_OAUTH_JWT_PRIVATE_KEY_PATH=/etc/dtxwiki/jwt.pem
DTXWIKI_DATABASE_URL=sqlite+pysqlite:////var/lib/dtxwiki/dtxwiki.sqlite
DTXWIKI_AUTO_BOOTSTRAP=false
DTXWIKI_MCP_HTTP_MODE=streamable
```

5. 최초 1회 bootstrap 을 실행합니다.

```bash
sudo -u dtxwiki bash -c 'set -a; source /etc/dtxwiki/dtxwiki.env; \
  /opt/dtxwiki/.venv/bin/dtxwiki bootstrap'
```

6. systemd service 를 등록하고 시작합니다.

```bash
sudo cp /opt/dtxwiki/.venv/share/dtxwiki/infra/prod/dtxwiki.service /etc/systemd/system/
sudo cp /opt/dtxwiki/.venv/share/dtxwiki/infra/prod/dtxwiki-worker.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now dtxwiki dtxwiki-worker
sudo systemctl status dtxwiki dtxwiki-worker
```

7. Caddy 로 HTTPS reverse proxy 를 설정합니다.

```bash
sudo apt-get install -y caddy
sudo cp /opt/dtxwiki/.venv/share/dtxwiki/infra/prod/Caddyfile.example /etc/caddy/Caddyfile
sudo sed -i 's/wiki.example.com/<your-domain>/g' /etc/caddy/Caddyfile
sudo systemctl reload-or-restart caddy
```

nginx 를 선호하면 아래 상세 Step 7 의 nginx + certbot 절차를 사용하세요.

8. 관리자 계정을 만들고 smoke check 를 수행합니다.

```bash
sudo -u dtxwiki bash -c 'set -a; source /etc/dtxwiki/dtxwiki.env; \
  /opt/dtxwiki/.venv/bin/dtxwiki users add admin@example.com --role owner'

curl -fsS https://wiki.example.com/health/live
curl -fsS https://wiki.example.com/health/ready
curl -i https://wiki.example.com/mcp
```

`/health/ready` 가 200 이고 `/mcp` 가 401 + `WWW-Authenticate` 헤더를 반환하면 기본 노출이 정상입니다. 브라우저에서 `https://wiki.example.com/` 로 접속해 admin 계정으로 로그인하세요.

SQLite 운영 시에는 DB 파일과 signing key 를 함께 백업합니다.

```bash
sudo -u dtxwiki sqlite3 /var/lib/dtxwiki/dtxwiki.sqlite \
  ".backup '/var/backups/dtxwiki/dtxwiki-$(date +%F).sqlite'"
sudo cp /etc/dtxwiki/dtxwiki.env /var/backups/dtxwiki/dtxwiki.env.$(date +%F)
sudo cp /etc/dtxwiki/jwt.pem /var/backups/dtxwiki/jwt.pem.$(date +%F)
sudo chmod 600 /var/backups/dtxwiki/*
```

### 아키텍처 개요

```text
                ┌────────────────────────────────────────────────┐
                │  Remote Ubuntu Server (wiki.example.com)       │
                │                                                │
   443/TLS ────► │  Caddy / nginx (TLS termination, security      │
   80  (ACME)    │  headers, X-Forwarded-* 주입)                  │
                │            │                                   │
                │            ▼ 127.0.0.1:21080                   │
                │  dtxwiki.service                                │
                │    └─ uvicorn + bundled React UI + /api/v1 +    │
                │       /mcp (Streamable HTTP) + /oauth/*         │
                │                                                │
                │  dtxwiki-worker.service                         │
                │    └─ source/feedback/bundle jobs               │
                │                                                │
                │  /var/lib/dtxwiki/dtxwiki.sqlite (DB)           │
                │  /etc/dtxwiki/jwt.pem (OAuth RS256 signing key) │
                └────────────────────────────────────────────────┘
                         ▲                       ▲
                         │ HTTPS (browser)       │ HTTPS (/mcp + Bearer)
   ┌─────────────────────┴─────────┐   ┌─────────┴─────────────────────┐
   │ 팀원 A: 웹 UI (브라우저)         │   │ 팀원 B: Claude Code / Codex CLI │
   │ Session cookie + RBAC          │   │ PAT bearer 또는 dtxWiki OAuth   │
   └────────────────────────────────┘   └────────────────────────────────┘
```

핵심 포인트:

- backend uvicorn 은 항상 `127.0.0.1:21080` 에만 bind 합니다. 외부 노출은 **반드시 reverse proxy(Caddy 또는 nginx) 가 TLS 를 종료**하면서 수행합니다. uvicorn 을 `0.0.0.0` 으로 직접 외부 노출하지 마세요(`DTXWIKI_BEHIND_PROXY=true` 가드와 production CORS 가드와 충돌함).
- `/api/v1` 과 `/mcp` 는 동일 origin 으로 serve 됩니다. 따라서 frontend 는 same-origin 요청만 사용하고 CORS preflight 가 발생하지 않습니다. 외부 CORS 는 다른 도메인에서 호출하는 client(SPA, third-party)만 필요로 합니다.
- bundled frontend 는 backend wheel 안에 포함되어 같은 process 가 `/` 에서 SPA 를 serve 합니다. 별도 Vite dev server 를 production 에서 띄울 필요는 없습니다.
- SQLite DB 는 `/var/lib/dtxwiki/` 에 두고 backend·worker 가 공유합니다 (production 은 SQLite 전용).

### 팀원 PC 에서 MCP client 연결

원격 dtxWiki 가 동작 중이면 팀원들은 자기 PC 의 Claude Code / Codex CLI / Cursor 에서 `https://wiki.example.com/mcp` 를 등록해 그대로 사용할 수 있습니다. dtxWiki Python 패키지를 팀원 PC 에 설치할 필요는 없습니다(설치한 경우 helper CLI 가 추가로 동작).

#### 인증 방식 선택

| 방식 | 사용 시나리오 | 만료/회전 |
| --- | --- | --- |
| **PAT (Personal Access Token)** bearer | 모든 HTTP MCP client (Claude Code, Codex CLI, Cursor, 사내 자동화) | `--expires-at` 명시 시 만료, 미명시 시 무기한. `dtxwiki users revoke-pat` 로 즉시 폐기 |
| **dtxWiki OAuth 2.1** (DCR + PKCE) | OAuth-aware client (현재 Claude Code 가 가장 깔끔). 사용자별 consent 와 refresh token rotation 자동 | Access token TTL `DTXWIKI_OAUTH_ACCESS_TOKEN_TTL_SECONDS`(기본 3600s), refresh token `DTXWIKI_OAUTH_REFRESH_TOKEN_TTL_SECONDS`(기본 30d) |

팀 운영에는 다음을 권장합니다.

- **개별 사용자별로 별도 PAT 발급** (공유 금지). PAT 한 줄에 user identity 가 매핑되므로 audit 와 폐기가 쉽습니다.
- OAuth 가 자동 동작하는 Claude Code 사용자는 OAuth 경로를 우선 시도하고, 그 외 client 는 PAT.

#### OAuth 동작 방식 (OAuth-aware client)

Bearer 없이 `/mcp` 를 호출하면 `WWW-Authenticate` 의 `resource_metadata` 가
`/.well-known/oauth-protected-resource` 를 가리킵니다. OAuth-aware client 는 이 metadata 로
authorization server 를 찾아 `/oauth/register`(DCR) → `/oauth/authorize`(브라우저 consent) →
`/oauth/token`(S256 PKCE) 순서로 RS256 JWT access token 을 얻고, refresh token 은
`/oauth/token` 의 `refresh_token` grant 로 rotate 됩니다. OAuth 를 지원하지 않는 client 는
아래 PAT 경로를 사용하세요.

#### PAT 발급 (서버 관리자가 remote 에서 실행)

```bash
# remote 서버에서:
sudo -u dtxwiki bash -c 'set -a; source /etc/dtxwiki/dtxwiki.env; \
    /opt/dtxwiki/.venv/bin/dtxwiki users issue-pat \
      --user alice@example.com \
      --name alice-laptop-mcp \
      --scope mcp:read,mcp:write,mcp:agent \
      --expires-at 2026-12-31T23:59:59Z'
```

명령은 다음 형태로 토큰을 한 번만 출력합니다.

```text
PAT issued (copy now; this is the only time it is shown):

    dtxw_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

token_id: <uuid>
scopes: mcp:read,mcp:write,mcp:agent
```

이 값을 안전한 채널(1Password / 사내 secrets manager / 직접 전달) 로 해당 팀원에게 전달합니다. **Slack DM, 평문 이메일, 공용 채팅에 붙여넣지 마세요.**

대안으로 팀원이 직접 웹 UI 에 로그인한 뒤 `Settings > API tokens` 에서 본인 PAT 를 발급해도 됩니다.

#### Claude Code (macOS / Linux)

```bash
claude mcp add --transport http dtxwiki https://wiki.example.com/mcp \
  --header "Authorization: Bearer dtxw_..."

# 또는 OAuth 자동 등록 (브라우저 consent 가 뜨고 token 이 자동 저장됨)
claude mcp add --transport http dtxwiki https://wiki.example.com/mcp --oauth
```

위 명령은 `~/.claude.json` 의 `mcpServers.dtxwiki` 항목을 작성합니다. 등록 후 `claude` 를 재시작하면 dtxWiki tool (`guide`, `search`, `read`, `write_draft`, ...) 이 노출됩니다.

#### Claude Code (Windows)

```powershell
claude mcp add --transport http dtxwiki https://wiki.example.com/mcp `
  --header "Authorization: Bearer dtxw_..."
```

config 파일 경로는 `%USERPROFILE%\.claude.json` 입니다. Windows 의 cmd.exe 에서는 backtick 대신 `^` 또는 한 줄로 입력하세요.

#### Codex CLI

Codex CLI 는 보안상 config 파일에 bearer 를 평문으로 저장하지 않고 환경변수로 분리합니다.

```bash
# bash/zsh — ~/.bashrc 또는 ~/.zshrc 에 영구 등록
export DTXWIKI_MCP_PAT=dtxw_...
codex mcp add dtxwiki --url https://wiki.example.com/mcp \
  --bearer-token-env-var DTXWIKI_MCP_PAT
```

```powershell
# Windows PowerShell — $PROFILE 에 영구 등록 권장
$env:DTXWIKI_MCP_PAT = "dtxw_..."
codex mcp add dtxwiki --url https://wiki.example.com/mcp `
  --bearer-token-env-var DTXWIKI_MCP_PAT
```

config 파일 경로는 macOS/Linux 가 `~/.codex/config.toml`, Windows 가 `%APPDATA%\codex\config.toml` 입니다.

#### Cursor 및 기타 HTTP MCP client

수동으로 `mcpServers` JSON 을 추가합니다.

```json
{
  "mcpServers": {
    "dtxwiki": {
      "url": "https://wiki.example.com/mcp",
      "transport": "http",
      "headers": {
        "Authorization": "Bearer dtxw_..."
      }
    }
  }
}
```

#### 헬퍼 CLI (선택: 팀원이 dtxWiki venv 를 가진 경우)

팀원이 자기 PC 에 dtxWiki Python 패키지를 설치했다면 native CLI 대신 helper 를 사용할 수 있습니다.

```bash
pipx install dtxwiki    # 또는 python -m venv + pip install dtxwiki

dtxwiki mcp register-http \
  --url https://wiki.example.com/mcp \
  --client claude-code \
  --token dtxw_...

dtxwiki mcp doctor \
  --http https://wiki.example.com/mcp \
  --token dtxw_...
```

`mcp doctor` 는 `/mcp` 401 challenge, `WWW-Authenticate` resource metadata, `/oauth/*` reachability, PAT 가 주어진 경우 `tools/list` round-trip 까지 검증합니다.

각 client 별 상세 설정과 OS 경로 차이(Windows `%APPDATA%` 등)는 [doc/setup/remote-mcp-clients.md](doc/setup/remote-mcp-clients.md) 를 참고하세요.

### 트러블슈팅

| 증상 | 원인 | 조치 |
| --- | --- | --- |
| `systemctl start dtxwiki` 가 `validate_for_startup` 으로 즉시 종료 | production guard 9개 중 하나 실패 | `journalctl -u dtxwiki -n 80 --no-pager` 의 hint 메시지대로 `/etc/dtxwiki/dtxwiki.env` 수정 후 `systemctl restart dtxwiki` |
| 브라우저 접속 시 CORS error | `DTXWIKI_CORS_ORIGINS` 가 실제 접속 도메인과 불일치 | env 파일에 정확한 origin (`https://wiki.example.com`) 추가, scheme 까지 일치해야 함 |
| `/mcp` 가 HTML SPA 응답 반환 | reverse proxy 가 `/mcp` 도 SPA fallback 으로 처리 중 | Caddy/nginx example 그대로 사용. dtxWiki 자체 SPA fallback 은 `/mcp` 를 면제(Task 4.11) |
| OAuth 토큰이 process 재시작마다 invalidate | `DTXWIKI_OAUTH_JWT_PRIVATE_KEY_PATH` 미설정으로 ephemeral key 사용 | `dtxwiki keygen` 으로 키 생성 후 env 에 stable path 고정 ([DEPLOYMENT.md](DEPLOYMENT.md)) |
| `/health/ready` 503 `oauth_jwk=ephemeral_unsafe` | 같은 원인 | 위와 동일 |
| `/health/ready` 503 `app_version=unsynced` | bootstrap 미수행 또는 migration 적용 안 됨 | `dtxwiki bootstrap` 1회 수동 실행 |
| Claude Code 가 `tools/list` 에서 401 | PAT scope 누락 또는 만료 | `mcp:read,mcp:write,mcp:agent` 모두 포함된 새 PAT 발급 |
| Codex CLI 가 `DTXWIKI_MCP_PAT` 를 못 찾음 | shell rc 가 reload 안 됨 / GUI launcher 가 env 미상속 | 새 shell 에서 `echo $DTXWIKI_MCP_PAT` 로 확인, GUI 사용 시 OS 사용자 env 로 등록 |
| reverse proxy 가 502 | uvicorn 미기동 또는 port mismatch | `systemctl status dtxwiki`, `curl http://127.0.0.1:21080/health/live` 확인 |
| Worker unit 즉시 종료 | `--idle-exit-after 0` 사용 | `dtxwiki-worker.service` 의 ExecStart 점검 |

전체 production 운영 절차와 health endpoint 분리(`/health/live`, `/health/ready`, deprecated `/api/v1/health`) 는 [DEPLOYMENT.md](DEPLOYMENT.md) 를 참고하세요.

## 개발 / 기여

소스 빌드·개발 서버·테스트(`make test` / `make lint` / Playwright E2E)·스키마 생성·
로컬 인프라 compose 등 개발 워크플로 전체는 [DEVELOPMENT.md](DEVELOPMENT.md) 에 있습니다.

## 프로젝트 구조

```text
backend/                         FastAPI, SQLAlchemy, Alembic, service/repository/MCP runtime
backend/worker/                  background worker CLI and dispatcher
backend/services/                application services for page/context/agent/source/search/audit flows
backend/migrations/versions/     Sprint_0~Sprint_2 Alembic migrations
frontend/                        Vite React TypeScript web UI
frontend/src/features/           ask, library/source, context, agent, activity, review, search, lint, setup screens
shared/                          content hash fixtures, REST/MCP schema artifacts, version/error metadata
infra/local/                     개발용 로컬 인프라 compose (DEVELOPMENT.md 참고)
infra/prod/                      production self-host: install.sh, systemd unit, Docker compose, Caddyfile
tests/                           cross-stack acceptance, static, browser E2E tests
doc/plan/                        local Epic_0 sprint plans, reviews, completion summaries
```

## 현재 제한 사항

Epic_0 MVP는 local-first 사용에 맞춘 구현입니다. 다음 기능은 아직 포함하지 않습니다.

- multi-workspace UI
- upstream OIDC/SSO federation, MFA, token introspection/revocation endpoint, org-wide admin UI
- LLM-driven Ask answer generation, embedding/reranking/VLM provider implementations, and provider secret storage
- background source sync loop
- real lint analysis engine beyond manual run/finding/decision state
- embedding/semantic·hybrid retrieval (현재 keyword search 만; Epic_1 이후)
- object storage integration in the packaged local path
- `uv tool install` acceptance smoke
- Windows native smoke; Windows 지원은 예정되어 있지만 현재 PoC 검증은 Linux 기준입니다.

## 설계 문서

구현 기준 문서는 별도 design repository에 있습니다.

- Design repository: `/home/rony/workspace_dtx/dtxwiki_design`
- Required design branch: `main` 또는 active `p0/*`
- Software design: `/home/rony/workspace_dtx/dtxwiki_design/doc/sw-design/`
- Implementation plan: `/home/rony/workspace_dtx/dtxwiki_design/doc/sw-implementation-plan/`
- Prism UI reference: `/home/rony/workspace_dtx/dtxwiki_design/design/260507-prism/`

이 checkout의 Epic_0 진행 문서:

- [doc/plan/Epic-0/sprint-roadmap.md](doc/plan/Epic-0/sprint-roadmap.md)
- [doc/plan/Epic-0/Sprint-1/](doc/plan/Epic-0/Sprint-1/)
- [doc/plan/Epic-0/Sprint-2/](doc/plan/Epic-0/Sprint-2/)
