아키텍처
air의 내부 구조와 실행 흐름을 설명합니다.
전체 구조
mermaid
graph TB
Client[MCP 클라이언트<br/>Claude Desktop / Cursor / VS Code]
Transport[트랜스포트<br/>stdio | SSE | HTTP]
Server[AirServer<br/>defineServer]
Chain[미들웨어 체인]
Plugins[플러그인 매니저]
Tools[도구 레지스트리]
Resources[리소스 레지스트리]
Prompts[프롬프트 레지스트리]
Storage[스토리지<br/>MemoryStore | FileStore]
Meter[Meter 미들웨어<br/>7계층 분류]
Client --> Transport
Transport --> Server
Server --> Chain
Server --> Plugins
Server --> Tools
Server --> Resources
Server --> Prompts
Plugins --> Chain
Chain --> Tools
Server --> Storage
Server --> Meter도구 호출 흐름
클라이언트가 도구를 호출하면 다음 순서로 처리됩니다:
mermaid
sequenceDiagram
participant C as Client
participant T as Transport
participant V as Validation MW
participant M as Meter MW
participant P as Plugin MW
participant U as User MW
participant H as Handler
participant E as Error Boundary
C->>T: tools/call { name, arguments }
T->>V: 1. 파라미터 검증 (Zod)
V->>M: 2. 계층 분류 + 시작 시간
M->>P: 3. 플러그인 before (배열 순서)
P->>U: 4. 사용자 before
U->>H: 5. 핸들러 실행
H-->>U: 반환값
U-->>P: 6. 사용자 after
P-->>M: 7. 플러그인 after
M-->>V: 8. Meter 기록
V-->>T: 9. normalizeResult → MCP content
T-->>C: { content: [...] }
Note over E: 어디서든 에러 발생 시
H--xE: throw Error
E-->>T: { content: [{ text: "[-32603] ..." }], isError: true }미들웨어 체인
미들웨어는 양파(Onion) 모델로 실행됩니다:
→ errorBoundary.before
→ validation.before (Zod 검증)
→ meter.before (계층 분류)
→ plugin[0].before (timeout)
→ plugin[1].before (retry)
→ plugin[2].before (cache — 히트 시 abort)
→ user[0].before
→ handler()
← user[0].after
← plugin[2].after (cache — 결과 저장)
← plugin[1].after
← plugin[0].after (timeout — 경고 체크)
← meter.after (호출 기록)
← validation.after
← errorBoundary.afterbefore 훅의 반환값 효과
typescript
return undefined; // 다음 미들웨어로 진행
return { params: { ... } }; // 파라미터 교체 후 진행
return { abort: true, abortResponse: '...' }; // 체인 중단, 즉시 응답
return { meta: { key: 'value' } }; // 메타 데이터 추가에러 처리 흐름
핸들러에서 throw
→ plugin onError 미들웨어 (역순)
→ 값 반환 시 → 정상 응답으로 전환
→ undefined 반환 시 → 다음으로 전달
→ 사용자 onError 미들웨어
→ errorBoundaryMiddleware (최종 포착)
→ AirError → MCP 에러 코드 변환
→ 일반 Error → -32603 Internal Error플러그인 라이프사이클
mermaid
graph LR
R[register] --> V[validatePlugin]
V --> I[onInit]
I --> S[onStart]
S --> TR[onToolRegister<br/>도구마다 1회]
S --> RUN[서버 실행 중<br/>middleware 활성]
RUN --> ST[onStop]
ST --> END[종료]실행 순서
- register:
use배열 순서대로 플러그인 등록.meta.name검증 - onInit: DB 연결, 리소스 로드.
ctx.state에 공유 객체 등록 - onStart: 워커 시작, 주기적 작업 등록
- onToolRegister: 각 도구 등록 시 호출. 도구 메타데이터 수정 가능 (동기)
- middleware: 매 도구 호출마다 실행 (before → handler → after)
- onStop: DB 닫기, 타이머 정리
트랜스포트 레이어
mermaid
graph TB
subgraph stdio
STDIN[stdin] --> P[MCP 프로토콜 파서]
P --> S[AirServer]
S --> SER[MCP 직렬화]
SER --> STDOUT[stdout]
LOG[로그] --> STDERR[stderr]
end
subgraph "SSE / HTTP"
HTTP[HTTP 요청] --> R[라우터]
R --> |POST /messages| S2[AirServer]
R --> |GET /sse| SSE[SSE 스트림]
R --> |GET /| STATUS[상태 JSON]
S2 --> SSE
end자동 감지 (type: 'auto')
MCP_TRANSPORT 환경변수?
├─ 있음 → 해당 타입 사용
└─ 없음 → process.stdin.isTTY?
├─ false (파이프) → stdio (클라이언트가 spawn)
└─ true (터미널) → http (개발자 직접 실행)스토리지 레이어
createStorage({ type })
├─ 'memory' → MemoryStore
│ └─ Map<namespace, Map<key, { value, expireAt }>>
│
└─ 'file' → FileStore
├─ .air/data/{namespace}.json (key-value, JSON)
├─ .air/data/{namespace}.log.jsonl (append-only, JSONL)
├─ 메모리 캐시 (네임스페이스별)
├─ Dirty 트래킹
└─ 5초 주기 flush (dirty만)Gateway 아키텍처
mermaid
graph TB
C[클라이언트] --> G[Gateway :4000]
G --> R[Request Router]
R --> TI[Tool Index]
R --> LB[Load Balancer]
LB --> S1[Server A :3510<br/>SSE]
LB --> S2[Server B :3511<br/>SSE]
LB --> S3[Server C<br/>stdio]
HC[Health Checker<br/>15초 주기] --> S1
HC --> S2
HC --> S3
G --> SR[Server Registry]
SR --> S1
SR --> S2
SR --> S3요청 라우팅 흐름
- 클라이언트가 도구 호출 → Gateway 수신
- Tool Index에서 해당 도구를 제공하는 서버 검색
- 같은 이름의 서버가 여러 개면 Load Balancer가 선택
- 선택된 서버로 프록시 → 결과 반환
- 서버 다운 → Health Checker 감지 → 라우팅 풀에서 제외