Codex 확장은 두 갈래다 — MCP = “지금 이 대화에 도구를 꽂는 케이블”, Plugin = “스킬·앱·MCP·훅을 매니페스트 하나로 묶어 배포하는 봉지”. AI에게 손발을 달고 팀에 배포하는 표준 방법이라, 이 두 단어 차이만 잡아도 확장 설계의 8할이 풀린다.
그림
flowchart TD
C[config.toml 의 mcp_servers] --> MGR[McpManager 카탈로그 합치기]
P[플러그인 안 .mcp.json] --> MGR
E[확장 기여자 extension] --> MGR
MGR --> CAT[MCP 서버 카탈로그]
CAT --> CLI["RmcpClient 서버 켜기<br/>stdio · HTTP+OAuth · 같은프로세스"]
CLI --> TOOLS["도구목록 받기 tools/list"]
TOOLS --> EXP["노출 결정<br/>바로보임 vs 검색뒤로미룸 + 가시성"]
EXP --> SPEC[모델에게 mcp__서버__도구 형태로 보임]
SPEC --> MODEL[모델]
MODEL -->|도구 호출| CALL[승인 받고 서버로 중계]
CALL --> CLI
PLG[플러그인이 멘션됨] --> INJ[플러그인 안내문 주입] --> MODEL
그림 읽는 법
왼쪽 세 갈래(config·플러그인·확장)가 하나의 카탈로그로 합쳐지고, 그 카탈로그가 서버를 켜서(RmcpClient) 도구 목록을 받아온 뒤, 도구들이 모델에게 보이는 카드(mcp__서버__도구)로 바뀐다. 모델이 도구를 부르면 다시 케이블을 타고 서버로 중계된다.
쉽게 풀기
MCP = 도구를 꽂는 케이블
USB 케이블을 떠올리면 된다. Codex 본체는 그대로 두고, 외부 프로그램(MCP 서버)을 꽂으면 그 프로그램이 “내가 할 줄 아는 일 목록”을 건넨다. Codex는 이걸 도구 카드로 바꿔 대화에 올려두고, 모델이 “이 도구 써줘”라 하면 케이블을 타고 서버에 일을 시켜 결과만 받아온다.
꽂는 방식은 둘뿐이다.
stdio — Codex가 그 프로그램을 자식 프로세스로 켜서 표준 입출력으로 대화 (내 컴퓨터 프로그램)
streamable-HTTP — 원격 서버에 인터넷 접속, 인증(베어러 토큰/OAuth)이 붙음 (남이 운영하는 서버)
flowchart LR
M["모델: 도구 써줘"] --> CX[Codex]
CX -->|"stdio: 자식프로세스"| L[내 컴퓨터 MCP 프로그램]
CX -->|"HTTP+인증"| R[원격 MCP 서버]
L --> RES[결과]
R --> RES
RES --> M
Plugin = 봉지
플러그인은 케이블 하나가 아니라 여러 물건을 담은 장바구니 봉지다. 안에 스킬(skills) + 앱(apps) + MCP 서버 정의(mcp) + 훅(hooks)이 들어가고, 이 모두를 **매니페스트 하나(plugin.json)**가 묶는다. 마켓플레이스나 깃에서 봉지째 설치·캐시·활성화하며, 안의 MCP 서버는 켜지는 순간 위 MCP 케이블 경로를 그대로 탄다.
도구가 카탈로그에 합쳐지고 → 서버가 켜지고 → 노출 방식이 정해지고 → 모델 호출이 중계되는 생명주기다.
sequenceDiagram
participant Mgr as McpManager
participant Cli as RmcpClient
participant Exp as 노출판정
participant Mod as 모델
Mgr->>Mgr: config+plugin+extension 병합<br/>(apps_enabled면 codex_apps 등록)
Mgr->>Cli: 서버 연결 (stdio/HTTP+OAuth/in-proc)
Cli->>Cli: initialize 핸드셰이크<br/>(404면 자동 재초기화)
Cli->>Exp: tools/list
Exp->>Mod: direct vs deferred 결정<br/>(도구 100개↑면 자동 deferred)
Mod->>Cli: mcp__서버__도구 호출
Cli->>Cli: 인자파싱→승인→call_tool→결과정제
Cli->>Mod: emit
펼쳐보기: 단계별 상세 + 근거 라인
카탈로그: McpManager(core/src/mcp.rs:23)가 config·plugin·extension 세 출처를 합치고, apps_enabled면 codex_apps 호환 서버를 자동 등록 (mcp.rs:70).
노출: build_mcp_tool_exposure(mcp_tool_exposure.rs:20)가 direct/deferred를 정함. 도구 100개(DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD) 이상이면 자동 deferred (:35).
실행: 모델이 mcp__서버__도구를 부르면 handle_mcp_tool_call(mcp_tool_call.rs:108)이 인자 파싱 → 메타 조회 → 승인 결정 → call_tool 중계 → 결과 정제 후 emit.
플러그인 주입: 멘션 시 build_plugin_injections(plugins/injection.rs:14)가 안내문(developer hint)을 만들어 모델에 넣고, 실제 문구는 render_explicit_plugin_instructions(render.rs:12)가 생성:
Capabilities from the `<displayName>` plugin:
- Skills from this plugin are prefixed with `<displayName>:`.
- MCP servers from this plugin available in this session: `<server>`.
- Apps from this plugin available in this session: `<app>`.
Use these plugin-associated capabilities to help solve the task.
요약 & 셀프체크
세 줄 요약:
MCP는 런타임에 외부 도구를 꽂는 케이블, 전송은 stdio(내 프로그램) 또는 streamable-HTTP(원격+인증) 둘 중 하나.
Plugin은 스킬·앱·MCP·훅을 plugin.json(JSON) 하나로 묶어 배포하는 봉지, 안의 MCP는 켜지면 MCP 경로를 그대로 탄다.
도구는 모델에게 mcp__서버__도구로 보이며, 100개를 넘으면 자동으로 검색 뒤로 미뤄진다(deferred).
스스로 답해보기:
내 서버가 stdio인지 streamable_http인지는 어떤 필드 유무로 결정되나?
config와 플러그인 매니페스트의 파일 형식은 각각 무엇이고, 왜 헷갈리나?
도구를 50개만 노출하게 설계했는데 합쳐서 120개가 됐다. 모델 입장에서 뭐가 달라지나?