Skip to content

에러 처리

air는 모든 에러를 MCP 프로토콜 형식으로 자동 변환합니다. 특정 코드를 가진 커스텀 에러를 던질 수도 있습니다.

에러 흐름

도구 핸들러에서 throw → onError 미들웨어 실행 → MCP 에러 응답 반환

내장 errorBoundaryMiddleware가 모든 미처리 에러를 잡아 MCP 형식으로 변환합니다:

json
{
  "content": [{ "type": "text", "text": "[-32603] Internal error: something broke" }],
  "isError": true
}

MCP 에러 코드

코드상수상황
-32601toolNotFound도구 이름이 존재하지 않음
-32602invalidParams파라미터 검증 실패 (Zod)
-32603internal핸들러에서 미처리 에러
-32000forbidden접근 거부
-32001rateLimited레이트 리밋 초과
-32002threatDetected위협 패턴 매칭
-32003timeout도구 실행 시간 초과

핸들러에서 에러 던지기

단순 에러

typescript
defineTool('divide', {
  params: { a: 'number', b: 'number' },
  handler: async ({ a, b }) => {
    if (b === 0) throw new Error('0으로 나눌 수 없습니다');
    return a / b;
  },
});

코드가 있는 AirError

typescript
import { AirError } from '@airmcp-dev/core';

defineTool('admin-action', {
  handler: async (params, context) => {
    if (!context.state.isAdmin) {
      throw new AirError('관리자 권한이 필요합니다', -32000, { role: 'user' });
    }
  },
});

McpErrors 팩토리

typescript
import { McpErrors } from '@airmcp-dev/core';

throw McpErrors.toolNotFound('missing-tool');
throw McpErrors.invalidParams('이메일 형식이 올바르지 않습니다');
throw McpErrors.internal('데이터베이스 연결 실패');
throw McpErrors.forbidden('정책에 의해 접근이 거부되었습니다');
throw McpErrors.rateLimited('search', 30000);
throw McpErrors.threatDetected('prompt_injection', 'high');
throw McpErrors.timeout('slow-tool', 10000);

AirError 클래스

typescript
class AirError extends Error {
  code: number;                        // MCP 에러 코드
  details?: Record<string, any>;       // 추가 메타데이터

  constructor(message: string, code?: number, details?: Record<string, any>);
}

예제

typescript
// 비즈니스 로직 에러
throw new AirError('크레딧이 부족합니다', -32010, {
  required: 100,
  available: 42,
});

// 상세 검증 에러
throw new AirError('날짜 범위가 올바르지 않습니다', -32602, {
  field: 'startDate',
  reason: 'startDate는 endDate보다 이전이어야 합니다',
});

커스텀 에러 미들웨어

typescript
const errorReporter: AirMiddleware = {
  name: 'error-reporter',
  onError: async (ctx, error) => {
    // 에러 트래킹 서비스로 전송
    await reportToSentry({
      error: error.message,
      tool: ctx.tool.name,
      params: ctx.params,
      requestId: ctx.requestId,
    });

    // undefined 반환 → 다음 에러 미들웨어가 처리
    // 값 반환 → 그 값이 응답이 됨
    return undefined;
  },
};

defineServer({
  middleware: [errorReporter],
});

에러 처리 순서

핸들러에서 throw
  → 플러그인 onError 미들웨어 (역순)
  → 사용자 onError 미들웨어
  → errorBoundaryMiddleware (항상 마지막, 모든 에러 포착)

onError 미들웨어가 값을 반환하면 그 값이 응답이 되고, 남은 에러 미들웨어는 건너뜁니다.

Released under the Apache-2.0 License.