한 문장으로 이 프로젝트가 뭘 하는지부터.
Codex with ChatGPT = "생각은 이미 돈 낸 ChatGPT 웹에 시키고, 손은 Codex가 쓰게 만드는 중계 다리." 코딩 에이전트를 돌리면 계획 세우기·코드 리뷰 같은 생각에도 실행과 똑같은 토큰이 나간다. 그런데 매달 결제 중인 ChatGPT 웹 구독은 대개 남아돈다. 이 프로젝트는 그 남는 쪽으로 생각을 옮긴다 — ChatGPT가 계획을 세우고 결과를 검수하고, Codex는 파일을 고치고 테스트를 돌린다. 둘을 잇는 다리는 읽기만 되고, 쓰기·실행 도구는 아예 만들어 두지 않았다.
보통은 코드를 복사해 ChatGPT 창에 붙여 넣는다. 붙여 넣는 순간 내 저장소 사본이 대화창으로 넘어가고, 길이 제한 때문에 필요한 부분만 잘라 보내니 설계자는 늘 조각난 정보로 판단한다.
C2C는 반대다. 코드를 보내지 않고 열람 권한만 준다. ChatGPT는 "그 파일 42~90행 보여 줘", "지금 git diff 뭐야"를 스스로 물어 가져간다. 대화창에 오가는 건 1KB도 안 되는 상태 쪽지뿐이고, 실제 내용은 별도 통로로만 흐른다. 열람증에는 복사기 사용 권한이 없다 — 읽을 수는 있어도 고칠 수는 없다.
만든 사람은 XiaoDuoYa, 라이선스는 MIT, 버전은 0.1.0이다. 별은 1.3천 개, 포크 141개(2026년 8월 30일 기준). 소스는 TypeScript 29개 파일 5,071줄에 테스트 12개 파일 1,806줄로, 규모로 보면 작은 편인데 보안 설계 문서만 별도로 한 장을 차지한다. README가 중국어·영어 병기이고, 설치조차 "이 문단을 Codex에게 그대로 복사해 주세요"로 시작하는 게 이 프로젝트의 성격을 잘 보여 준다.
비슷한 "ChatGPT 연동" 도구가 널린 판에서 무엇이 달랐나.
"AI 비용 절감"을 내건 도구는 많지만 대개 캐시나 모델 다운그레이드 얘기다. 이 프로젝트는 작업을 종류별로 갈라 붙인다. 계획 수립·아키텍처 판단·코드 리뷰는 긴 맥락과 좋은 추론이 필요한데 출력량은 적다. 반대로 실제 편집은 출력이 많지만 판단은 덜 필요하다. 앞쪽을 정액제 웹 구독으로, 뒤쪽을 종량제 하네스로 보내는 배분이다.
많은 도구가 "쓰기는 확인 후에만"이라고 약속한다. 약속은 프롬프트 인젝션 한 방에 깨질 수 있다. C2C는 서버에 쓰기·삭제·셸 실행 도구를 코드로 만들어 두지 않았다. 보안 문서의 표현이 직설적이다 — "이 도구들은 서버에 존재하지 않으므로, 어떤 프롬프트 인젝션이나 스코프 버그나 UI 혼동으로도 활성화할 수 없다." 없는 기능은 뚫리지 않는다.
에이전트 협업에서 흔한 실패가 "테스트 다 통과했습니다"라는 실행자의 자기 보고를 검수자가 그대로 믿는 것이다. C2C는 Codex가 작업을 끝내면 EXECUTED 쪽지에 "바뀐 파일 4개, 테스트 27개 통과" 같은 숫자만 적어 보내고, ChatGPT는 그걸 믿는 대신 MCP로 실제 git diff와 실행 기록을 직접 당겨 확인한다. 프로토콜 문서가 이 절차를 명시적으로 요구한다.
동봉된 Codex 스킬 문서(skill/SKILL.md, 555줄)의 황금률 2번은 "사용자에게 MCP·OAuth·PKCE·터널·포트·localhost를 보여 주지 말 것"이다. 4번은 "실패하면 c2c doctor를 돌려 조용히 고치고, 로그인·캡차·2단계 인증처럼 사람이 꼭 해야 하는 일만 그것도 한 번에 하나씩 요청할 것". 설치 안내가 "터미널을 여세요"가 아니라 "이 문단을 에이전트에게 붙여 넣으세요"인 것도 같은 방향이다.
docs/에 아키텍처·프로토콜·보안·문제해결 4종 487줄이 있고, 특히 보안 문서는 위협 13가지를 표로 늘어놓고 각각의 대응을 적었다. 개인 프로젝트 규모에서 위협 모델을 이 정도로 쓰는 경우는 드물다.
이 구조는 에이전트가 사용자의 ChatGPT 웹 세션을 자동 조작하는 것을 전제로 한다. 편의는 크지만 OpenAI 이용약관과의 관계는 사용자가 스스로 판단할 몫이고, 계정 단위 제재 위험을 프로젝트가 대신 져 주지 않는다. 업무용 계정으로 붙이기 전에 이 점을 먼저 저울질하는 게 맞다.
무엇으로 만들어졌나 — 의존성이 다섯 개뿐이다.
| 항목 | 값 | 왜 이걸 골랐나 |
|---|---|---|
| TypeScript 5.9 | 소스 전량 | MCP 도구 스키마·OAuth 상태 머신처럼 모양이 틀리면 바로 사고인 부분이라 타입이 값을 한다 |
| Node.js ≥ 20 | 필수 조건 | ESM 기본("type":"module"), node:crypto의 timingSafeEqual 등 표준 API를 그대로 씀 |
| pnpm 11.24 | 패키지 매니저 | packageManager 필드로 고정 — 설치 안내가 corepack pnpm인 이유 |
| 패키지 | 역할 |
|---|---|
@modelcontextprotocol/sdk | MCP 서버 구현. 도구 등록과 Streamable HTTP 전송을 담당 |
express ^5.1 | 브리지 HTTP 서버. /mcp·OAuth 엔드포인트·/health·관리 API를 얹는다 |
zod ^3.25 | 도구 입력 스키마. 외부에서 들어오는 값의 1차 방어선 |
ignore ^7.0 | gitignore 문법 매칭기. 민감 파일 차단·잡음 폴더 제외·.c2cignore가 전부 이걸 씀 |
commander ^14 | c2c CLI. 명령 20개 남짓과 --json 출력 |
눈여겨볼 건 없는 것들이다. 데이터베이스가 없다(상태는 OS 앱 디렉터리의 JSON 파일). 인증 라이브러리가 없다(OAuth 2.1 인가 서버를 src/auth/ 721줄로 직접 구현했다). 프런트엔드 프레임워크가 없다(페어링 화면은 서버가 뱉는 HTML 한 장). 외부에 열리는 서비스를 만들면서 의존성을 다섯 개로 묶어 둔 것 자체가 공격 표면 관리다.
S256 방식만 받고 약한 plain은 거부한다. DCR(동적 클라이언트 등록)은 ChatGPT가 사전 협의 없이 스스로 클라이언트로 등록하는 절차다 — 사람이 키를 복사해 붙일 일이 없어지는 이유.| 항목 | 내용 |
|---|---|
vitest ^3.2 | 테스트 12개 파일, describe 27개 · it 119개. 경로 탈출·OAuth·페어링·MCP 왕복이 전부 대상 |
tsx | 빌드 없이 개발 실행 (pnpm dev) |
cloudflared | 외부 의존 바이너리. 공개 주소를 여는 터널 (npm 패키지가 아니라 별도 설치) |
ripgrep | 있으면 검색에 쓰고 없으면 Node 구현으로 자동 대체 — 필수가 아니다 |
📌 README는 "vitest: 76 tests"라고 적었지만, 실제 테스트 파일을 열어 세면 it( 블록이 119개다. 문서가 쓰인 뒤 테스트가 늘어난 것으로 보인다 — 레포를 평가할 때 README 숫자보다 소스를 직접 세는 편이 안전하다는 흔한 사례다.
이 프로젝트의 진짜 아이디어는 "두 개의 평면을 절대 섞지 않는다"이다.
프로토콜 문서 첫 세 줄이 설계 전부를 요약한다.
// docs/protocol.md 첫머리 Control plane: Computer Use (tiny structured messages typed into the ChatGPT UI). Data plane: MCP (ChatGPT pulls files, diffs, search results itself). Never mix the two: control messages carry state, never content.
제어 평면은 ChatGPT 대화창에 타이핑되는 짧은 쪽지다. 여기엔 "지금 어느 단계인가, 몇 번째 반복인가, 목표가 뭔가"만 들어가고 1KB를 넘지 않는다. 데이터 평면은 MCP 연결이고, 파일 내용·diff·검색 결과는 오직 이쪽으로만 흐른다. 왜 이렇게 나눴는지는 섞였을 때를 생각하면 분명하다 — 대화창에 diff를 붙이기 시작하면 맥락 창이 금세 차고, 잘라 붙이는 과정에서 정보가 왜곡되고, 무엇보다 대화 기록에 코드 사본이 남는다.
두 에이전트가 주고받는 쪽지는 [C2C]로 시작하고 헤더에 상태를 적는다. 흐름은 이렇다.
| 상태 | 보내는 쪽 | 뜻 |
|---|---|---|
INIT | Codex | 새 작업. "연결된 작업 폴더를 살펴보고 계획을 세워 달라" |
PLAN | ChatGPT | 다음 회차에 실행할 계획. 근거·행동·건드릴 파일·테스트·성공 기준 |
EXECUTING | Codex | (선택) 실행 중 |
EXECUTED | Codex | 회차 종료. 메타데이터만 — 바뀐 파일 수, 테스트 결과 |
REVIEW | ChatGPT | (암묵) MCP로 직접 들여다보는 중 |
DONE / BLOCKED | ChatGPT | 성공 기준 충족 / 진행 불가(사유 포함) |
ERROR | 양쪽 | 프로토콜·인프라 실패 |
HANDOFF | Codex | 대화가 길어졌을 때 후속 대화로 넘기는 인수인계 브리핑 |
프로토콜 문서가 계획에 대고 못을 박은 문장이 하나 있다 — "계획은 유한하고 구체적이며 실행 가능해야 한다. 40단계짜리 대서사시가 아니라." 리뷰 단계에서 계획으로 되돌아가는 고리가 있으니, 한 회차를 작게 유지하는 게 이 구조의 전제다.
공개 주소가 열리는 구조라 인증이 핵심인데, 설계 목표가 뚜렷하다. 모델은 장기 자격증명을 절대 보지 않는다. 사람과 모델이 다루는 비밀은 1회용 페어링 코드 하나뿐이고, 진짜 토큰은 ChatGPT 클라이언트와 브리지의 OAuth 엔드포인트 사이에서만 오간다.
| 장치 | 실제 값 |
|---|---|
| 페어링 코드 | 혼동 글자를 뺀 31자 알파벳(ABCDEFGHJKMNPQRSTUVWXYZ23456789)에서 8자 — 약 40비트 |
| 코드 방어 | 유효 5분 · 시도 5회 · IP당 분당 10회 · 1회 쓰면 폐기 · 한도 초과 시 세션 파기 |
| 인가 코드 | 5분, 1회용, 클라이언트+리다이렉트 URI에 묶임 |
| 액세스 토큰 | 1시간 |
| 리프레시 토큰 | 30일, 쓸 때마다 회전 — 옛 것 재사용은 실패 |
| 저장 방식 | 토큰은 원문이 아니라 SHA-256 해시만 저장. 상태 파일을 훔쳐도 쓸 수 있는 토큰이 안 나온다 |
여기에 균등 분포까지 신경 썼다. 코드 생성기가 난수 바이트를 % 31로 접지 않고, 256을 31로 나눈 배수를 넘는 바이트는 버리고 다시 뽑는다. 나머지 연산만 쓰면 앞쪽 글자가 미세하게 자주 나오는 편향이 생기는데, 그걸 차단한 것이다. 40비트짜리 5분 코드에 이 정도까지 하는 건 과할 수도 있지만 암호학 코드를 대하는 태도는 드러난다.
브리지는 48765 포트를 선호하고 127.0.0.1에만 바인딩한다(0.0.0.0은 거부). 포트가 이미 쓰이고 있으면 /health를 찔러 그게 같은 작업 폴더의 c2c 브리지인지 확인하고, 맞으면 재사용하고 아니면 임의 포트로 물러선다. 공개 주소는 Cloudflare 임시 터널이 기본인데 재시작마다 주소가 바뀌므로, c2c doctor가 그 사실을 감지해 ChatGPT 커넥터를 지웠다 다시 만들라고 스킬에 지시한다. 도메인이 있는 사용자는 고정 호스트명을 한 번 정해 이 반복을 없앨 수 있다.
tunnel/은 TunnelProvider 인터페이스를 두고 Cloudflare 임시·고정 두 구현을 붙였다. 아키텍처 문서의 표현으로는 "비즈니스 로직은 벤더 중립"이다. Cloudflare에 묶인 프로젝트가 되기 쉬운데 교체 지점을 미리 파 둔 것이고, 실제로 고정 터널 준비가 실패하면 임시 터널로 자동 강등되는 폴백도 들어 있다.
폴더 이름이 곧 책임이다 — 12개 모듈, 각각 하는 일이 하나씩.
줄 수 분포가 이 프로젝트의 무게 중심을 드러낸다. 가장 큰 모듈이 CLI(1,057줄)이고 그 다음이 workspace(1,003줄)다. 즉 코드의 절반 가까이가 "모델에게 무엇을 어떻게 보여 줄 것인가"와 "사람이 어떻게 조작하는가"에 들어갔다. 정작 MCP 도구 구현은 318줄로 가장 얇은 축에 속한다 — 도구가 얇을 수 있는 이유는 위험한 판단을 전부 workspace 층에서 끝내 놓기 때문이다.
파일 하나를 읽는 요청이 여기서 세 개의 관문을 지난다.
| 관문 | 하는 일 |
|---|---|
① 경로 봉쇄manager.ts | 존재하는 가장 깊은 조상까지 realpath로 정규화한 뒤 작업 폴더 안인지 검사. 심볼릭 링크·..·절대경로·역슬래시·널 바이트가 전부 막히고, macOS/Windows에선 대소문자 무시로 비교한다 |
② 민감 파일ignore.ts | 기본 거부 목록에 걸리면 ACCESS_DENIED_SENSITIVE_FILE. 읽기·목록·검색이 같은 관문을 지난다 |
| ③ 분량 상한 | 읽기는 기본 400줄·최대 2,000줄, 기본 256KB·최대 1MB. diff는 바이트 오프셋 페이지네이션에 총량 64MB 상한 |
거부 목록이 구체적이다 — .env와 .env.*(단 .env.example은 예외로 허용), *.pem·*.key·*.p12·*.pfx·*.jks·*.keystore, SSH 키 4종(id_rsa·id_ed25519·id_ecdsa·id_dsa)과 그 변형, .ssh/·.aws/·.gnupg/·.cloudflared/, .npmrc·.netrc·.git-credentials, 키체인 파일, credentials.json·service-account*.json·secrets.json, 그리고 브라우저 쿠키 DB까지. 사용자는 .c2cignore로 자기 규칙을 더할 수 있지만 기본 목록을 뺄 수는 없다.
도서관에 열람증을 준다고 금고실 열쇠까지 주지는 않는 것과 같다. 그리고 이 금고실 목록은 사서가 임의로 줄일 수 없게 건물 설계에 박혀 있다. 이용자가 "여기도 잠가 주세요"라고 더할 수는 있어도, "여긴 열어 주세요"는 안 된다.
목록 하나가 더 있다. NOISE_PATTERNS는 node_modules/·dist/·.next/·target/·__pycache__/·락파일처럼 양만 많고 의미는 없는 경로를 목록·검색에서 뺀다. 민감 파일과 달리 이건 오류가 아니라 그냥 안 보이는 처리다. 둘을 다른 개념으로 나눠 둔 게 정확하다 — 하나는 보안이고 하나는 신호 대 잡음이다.
이 레포에서 훔쳐 갈 만한 설계 습관 여섯 가지.
가장 배울 만한 대목이다. AI에게 파일 접근을 줄 때 보통은 쓰기 도구를 만들어 놓고 조건을 건다 — "이 폴더만", "확인받고 나서". 그 조건은 코드고, 코드에는 버그가 있고, 모델은 설득당한다. C2C는 쓰기 도구 자체를 구현하지 않는 쪽을 골랐다. 대신 쓰기가 필요한 일은 권한을 이미 가진 Codex가 한다. 능력을 나눠 갖되 겹치지 않게 자르는 이 발상은 다른 에이전트 시스템에도 그대로 옮겨진다.
readOnlyHint: trueMCP에는 도구의 성격을 알리는 주석(annotations)이 있다. C2C의 도구 8개는 전부 readOnlyHint: true를 달았다. 클라이언트가 "이건 읽기만 하는 도구"라고 판단해 확인 절차를 줄일 수 있게 하는 신호다. 목록은 이렇다.
| 도구 | 하는 일 | 스코프 |
|---|---|---|
workspace_info | 작업 폴더 개요 | workspace.read |
list_directory | 폴더 목록 (잡음 폴더 제외) | workspace.read |
read_file | 파일 읽기 (줄·바이트 상한) | workspace.read |
search_workspace | ripgrep 검색, 없으면 Node 대체 | workspace.search |
git_status | 브랜치·스테이징·미추적 파일 | git.read |
git_diff | diff (바이트 오프셋 페이지네이션) | git.read |
test_status | 기록된 테스트 결과 | execution.read |
execution_summary | 회차별 실행 기록 | execution.read |
스코프가 도구마다 개별 검사된다는 점도 눈여겨볼 만하다(부족하면 INSUFFICIENT_SCOPE). 토큰 하나로 다 되는 게 아니라, 읽기·검색·git·실행기록이 각각 다른 권한이다.
MCP 전송은 무상태(stateless) Streamable HTTP다. 아키텍처 문서에 "요청마다 새 서버 인스턴스, JSON 응답"이라고 적혀 있다. 세션을 붙들지 않으니 요청 사이에 상태가 새지 않고, 브리지를 재시작해도 클라이언트 쪽이 깨지지 않는다. 대신 매 요청이 인증을 통과해야 한다 — 편의를 버리고 예측 가능성을 산 교환이다.
"작업 폴더 밖으로 못 나가게" 만드는 건 쉬워 보이는데 실제론 함정이 많다. C2C의 처리를 그대로 배울 만하다.
| 함정 | 대응 |
|---|---|
| 심볼릭 링크로 밖을 가리키기 | realpath로 링크를 푼 뒤 포함 검사. 파일 링크·디렉터리 링크 둘 다 테스트에 있다 |
아직 없는 경로라 realpath가 실패 | 존재하는 가장 깊은 조상까지만 정규화하고 나머지를 이어 붙인다 |
| macOS·Windows의 대소문자 | 그 두 플랫폼에서만 소문자로 낮춰 비교 (리눅스에선 구분 유지) |
| 역슬래시·널 바이트·절대경로 | 전부 거부, 각각 테스트 있음 |
logger/는 86줄짜리 작은 모듈인데 하는 일이 분명하다 — 토큰 앞자리, Bearer 헤더, 토큰처럼 생긴 파라미터, 페어링 코드 모양의 문자열을 기록 전에 지운다. 디버그 로그가 자격증명 유출 경로가 되는 사고는 실제로 흔하다. 작은 모듈 하나로 그 계열을 통째로 닫아 둔 셈이다.
관리 API는 루프백에서만 받는데, 거기서 한 걸음 더 간다. cf-connecting-ip·x-forwarded-for 같은 프록시 헤더가 붙은 요청은 거부한다 — 터널을 통해 들어온 요청이 루프백인 척하는 걸 막는 장치다. 게다가 인증 없는 탐색에는 401이 아니라 404를 돌려준다. "여기 뭔가 있다"는 사실 자체를 안 알려 주는 것이다.
/health가 작업 폴더 경로 대신 소금 친 해시를 내놓는다포트 충돌을 판별하려면 "이 브리지가 어느 폴더 것인가"를 알아야 하는데, 경로를 그대로 노출하면 공개 엔드포인트에서 내 PC의 디렉터리 구조가 새어 나간다. 그래서 솔트를 섞은 해시만 준다. 같은 폴더인지 비교는 되지만 원래 경로는 복원되지 않는다.
모델을 돌리는 게 아니라 중계만 하므로 요구사항이 거의 없다.
| 항목 | 요구 | 비고 |
|---|---|---|
| Node.js | 20 이상 | 필수. package.json의 engines에 명시 |
| git | 필수 | git_status·git_diff가 실제 git을 부른다 |
cloudflared | 공개 연결에 필요 | 자동 감지. 스킬이 대신 설치해 준다 |
| ripgrep | 선택 | 없으면 Node 구현으로 자동 대체. C2C_DISABLE_RG=1로 강제 비활성화도 가능 |
| GPU | 불필요 | 추론은 전부 ChatGPT 서버와 Codex가 한다 |
| 메모리·CPU | 사실상 무시 가능 | Express 서버 하나 + cloudflared 프로세스 |
| OS | macOS · Windows · Linux | 대소문자·상태 디렉터리 분기가 코드에 들어 있다 |
| 네트워크 | 아웃바운드만 | 터널이 바깥으로 연결을 건다 — 방화벽에 포트를 열 필요가 없다 |
진짜 비용은 하드웨어가 아니라 계정이다. ChatGPT 유료 구독(계획·리뷰용)과 Codex를 돌릴 수단이 둘 다 있어야 하고, 고정 주소를 원하면 Cloudflare 계정과 그 위에 올린 도메인이 추가로 필요하다. 상태 파일은 OS 규약 디렉터리(macOS 기준 ~/Library/Application Support/codex-with-chatgpt)에 디렉터리 0700 · 파일 0600으로 저장되고, 프로젝트 폴더 안에는 아무것도 남기지 않는다.
보안 문서가 스스로 적어 둔 한계다. 원문 토큰은 어디에도 안 쓰이고 해시만 남지만, 클라이언트 등록 정보와 토큰 해시가 파일 기반이라는 점은 키체인 저장보다 약하다. 키체인 연동은 v2 과제로 남아 있다. 한계를 문서에 먼저 적어 둔 것 자체는 신뢰 요소이지만, 공유 PC에서 쓸 계획이라면 이 줄을 반드시 읽고 넘어가야 한다.
읽기만 해서는 안 남는다 — 손으로 확인하는 순서.
테스트 폴더에 .env(가짜 값)와 .env.example을 나란히 두고 붙인 뒤, ChatGPT에게 두 파일을 각각 읽어 달라고 한다. .env는 ACCESS_DENIED_SENSITIVE_FILE로 거부되고 .env.example은 읽혀야 한다.
search_workspace로는 내용이 새는 도구가 세상에 꽤 있다.실제 작업을 한 회차 돌린 뒤 ChatGPT 대화창에 쌓인 [C2C] 메시지를 복사해 글자 수를 센다. docs/protocol.md의 예시 형식과 비교한다.
tests/workspace.test.ts(it 20개)를 정독한 뒤, 저자가 안 막았을 법한 경로를 하나 상상해 테스트로 추가한다. 유니코드 정규화, 윈도우 8.3 단축 이름, 하드링크 같은 것들.
/mcp를 찔러 보기터널 주소를 알아낸 상태에서 curl로 /mcp에 요청한다. 401이 나오고 WWW-Authenticate 헤더가 리소스 메타데이터 주소를 가리키는지 본다. 다른 작업 폴더의 토큰으로 찌르면 403이 나와야 한다.
skill/SKILL.md는 Codex 전용으로 쓰였다. 이 555줄을 다른 코딩 에이전트의 규약으로 옮기고, [C2C] 상태 머신을 그대로 태워 본다. 브리지는 손댈 필요가 없어야 정상이다 — 하네스 의존이 스킬 문서에만 있다면.
이 레포를 이해하려고 파고들면 자연히 닿는 것들.
먼저 MCP 사양을 읽고 도구·리소스·프롬프트의 차이를 잡는다. 그다음 도구 주석(readOnlyHint·destructiveHint·idempotentHint)이 왜 있는지 본다. C2C처럼 읽기 전용 서버를 하나 직접 만들어 보면 "도구를 안 만드는 것이 곧 권한 통제"라는 감각이 손에 붙는다.
C2C가 구현한 건 MCP Authorization 흐름 그대로다. 관련 RFC를 순서대로 읽으면 코드가 훨씬 잘 읽힌다 — RFC 8414(인가 서버 메타데이터) · RFC 9728(보호 자원 메타데이터) · RFC 7591(동적 클라이언트 등록) · RFC 7636(PKCE) · RFC 7009(토큰 폐기). src/auth/oauth.ts 363줄이 이 목록의 구현체다.
프롬프트 인젝션 문헌과 OWASP LLM Top 10을 읽고, C2C의 위협 표 13줄과 대조해 본다. "완화했다"와 "구조적으로 불가능하게 했다"의 차이를 사례로 구분할 수 있게 되는 게 목표다.
[C2C]는 사람이 읽을 수 있는 텍스트 상태 머신이다. 같은 문제를 JSON 스키마나 함수 호출로 푸는 방식과 비교해 보면 트레이드오프가 보인다 — 텍스트는 웹 UI에 타이핑할 수 있고 사람이 감사하기 쉽지만, 파싱이 느슨하고 검증이 약하다. 전송 수단이 대화창일 때 왜 텍스트가 이기는지가 이 프로젝트의 답이다.
Cloudflare 임시 터널 · 고정 호스트명 터널 · ngrok · SSH 역터널을 비교한다. 공통 질문 하나로 정리된다 — "주소를 아는 것만으로 뭘 할 수 있는가?" C2C의 답은 "아무것도"이고, 그 답을 만드는 게 OAuth 층이다.
문서와 코드에서 반복해 나오는 말들.
| 키워드 | 뜻 |
|---|---|
| C2C | Codex to ChatGPT. 이 프로젝트의 약칭이자 CLI 명령 이름(c2c), 그리고 제어 메시지의 머리표([C2C]) |
| 브리지 (Bridge) | 내 PC에서 도는 작은 서버. MCP 서버 + OAuth 인가 서버 + 페어링 + 터널 관리자를 한 프로세스에 담았다 |
| 제어 평면 / 데이터 평면 | 네트워크 장비 설계에서 온 말. 여기선 상태만 나르는 통로(대화창)와 내용을 나르는 통로(MCP)를 가리킨다 |
| 워크스페이스 (Workspace) | 작업 폴더. 이 프로젝트에선 권한의 최소 단위다 — 브리지 하나 = 폴더 하나 = 토큰 하나 |
| 페어링 코드 | 브라우저에 딱 한 번 입력하는 8자리 임시 코드. 사람과 모델이 만지는 유일한 비밀 |
| PKCE (S256) | 인가 코드를 가로채도 못 쓰게 하는 장치. 약한 plain 방식은 거부된다 |
| DCR | 동적 클라이언트 등록. 클라이언트가 사전 협의 없이 스스로 등록하는 절차 (RFC 7591) |
| 스코프 (Scope) | 권한 딱지. workspace.read·workspace.search·git.read·execution.read·offline_access 다섯 종 |
| 경로 봉쇄 (Containment) | 요청 경로가 작업 폴더 안에 있는지 정규화 후 검사하는 것. 심볼릭 링크를 푼 다음 비교하는 게 핵심 |
.c2cignore | 사용자가 추가로 가릴 파일을 적는 파일. gitignore 문법. 기본 차단 목록을 해제할 수는 없다 |
| Quick Tunnel / Named Tunnel | Cloudflare 터널 두 종류. 임시(주소가 매번 바뀜) vs 고정 호스트명(도메인 필요) |
c2c doctor | 진단·자가 수리 명령. 터널 주소가 바뀌었으면 커넥터를 다시 만들라고 스킬에 알린다 |
| HANDOFF | 대화가 길어졌을 때 후속 대화로 넘기는 인수인계 상태. 맥락 창 한계를 프로토콜 안에서 다룬 장치 |
| long-chat / project | 대화 재사용 모드 두 가지. 폴더당 대화 하나(구방식) vs 폴더당 ChatGPT 프로젝트 하나(신규 기본값) |
| 실행 기록 (Execution record) | c2c record가 JSONL로 남기는 회차 결과. ChatGPT가 execution_summary·test_status로 읽어 자기 보고를 검증한다 |
원문과, 옆에 두고 볼 것들.
| 구분 | 링크 | 왜 보는가 |
|---|---|---|
| 원본 레포 | XiaoDuoYa/codex-with-chatgpt | 별 1.3천 · 포크 141 · MIT · v0.1.0 |
| 보안 문서 | docs/security.md | 이 레포에서 가장 먼저 읽을 문서. 위협 13가지와 대응이 표로 정리돼 있다 |
| 프로토콜 문서 | docs/protocol.md | [C2C] 메시지 형식과 상태 머신 전문 |
| 아키텍처 문서 | docs/architecture.md | 모듈별 책임과 요청 생애주기(MCP 호출·인가·포트·터널) |
| 스킬 문서 | skill/SKILL.md | 555줄. 에이전트에게 주는 운영 매뉴얼을 어떻게 쓰는지의 좋은 표본 |
| 중국어 README | README.zh-CN.md | 원저자의 설명이 더 자세한 대목이 있다 |
| MCP 사양 | modelcontextprotocol.io | 도구 주석과 인가 흐름의 원문 규격 |
| MCP TypeScript SDK | modelcontextprotocol/typescript-sdk | src/mcp/server.ts 318줄을 읽기 전에 훑어 둘 것 |
| Cloudflare Tunnel | Cloudflare 문서 | 임시 터널과 고정 호스트명의 차이 |
ignore 패키지 | kaelzhang/node-ignore | 민감 파일 정책이 통째로 이 라이브러리 위에 서 있다 |
이 레포의 값어치는 "ChatGPT 구독을 재활용한다"는 영리한 요령에만 있지 않다. 진짜 배울 것은 두 AI에게 능력을 어떻게 나눠 줄 것인가에 대한 답이다 — 하나에게는 판단을, 다른 하나에게는 실행을 주고, 판단하는 쪽에는 실행 도구를 애초에 만들어 주지 않는다. 코드 5천 줄 중 절반이 "무엇을 못 하게 할 것인가"에 쓰였다는 사실이 이 프로젝트의 성격을 요약한다. 다만 자동화 대상이 내 ChatGPT 계정이라는 점은 도입 전에 스스로 판단해야 할 몫으로 남는다.