Skip to content

미들웨어 & 에러 레퍼런스

AirMiddleware

typescript
interface AirMiddleware {
  name: string;
  before?: (ctx: MiddlewareContext) => Promise<MiddlewareResult | void>;
  after?: (ctx: MiddlewareContext & { result: any; duration: number }) => Promise<void>;
  onError?: (ctx: MiddlewareContext, error: Error) => Promise<any>;
}

세 가지 훅 모두 선택. 필요한 것만 구현.

MiddlewareContext

typescript
interface MiddlewareContext {
  tool: AirToolDef;              // 호출되는 도구
  params: Record<string, any>;   // 요청 파라미터 (before에서 수정 가능)
  requestId: string;             // UUID
  serverName: string;
  startedAt: number;             // Date.now()
  meta: Record<string, any>;     // 미들웨어 간 공유 데이터
}

MiddlewareResult (before 반환값)

typescript
interface MiddlewareResult {
  params?: Record<string, any>;  // 변경된 파라미터
  abort?: boolean;               // true → 나머지 + 핸들러 건너뛰기
  abortResponse?: any;           // 중단 시 응답 (normalizeResult 거침)
  meta?: Record<string, any>;    // 메타데이터 추가
}

사용 예제

typescript
// 파라미터 수정
const limiter: AirMiddleware = {
  name: 'limiter',
  before: async (ctx) => ({
    params: { ...ctx.params, limit: Math.min(ctx.params.limit || 100, 100) },
  }),
};

// 조기 중단
const blocker: AirMiddleware = {
  name: 'blocker',
  before: async (ctx) => {
    if (ctx.params.blocked) return { abort: true, abortResponse: 'Blocked' };
  },
};

// 에러 복구
const graceful: AirMiddleware = {
  name: 'graceful',
  onError: async (ctx, error) => {
    if (error.message.includes('ECONNREFUSED'))
      return 'Service unavailable';
    return undefined;  // 다음 핸들러로 전달
  },
};

체인 실행 흐름

before 미들웨어 (순서대로)
  ↓ abort → normalizeResult(abortResponse) 반환
  ↓ params 수정 → 이후 미들웨어/핸들러에 적용
handler()
  ↓ 에러 → onError 미들웨어 순서대로 (값 반환 시 정상 응답으로 전환)
after 미들웨어 (순서대로, 에러 시 무시)

normalizeResult(result)

AirError

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

  constructor(message: string, code?: number, details?: Record<string, any>);
}
typescript
import { AirError } from '@airmcp-dev/core';

throw new AirError('크레딧 부족', -32010, { required: 100, available: 42 });

McpErrors

표준 에러 팩토리:

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

McpErrors.toolNotFound(name: string): AirError;
// code: -32601, "Tool "name" not found"

McpErrors.invalidParams(message: string, params?: Record<string, any>): AirError;
// code: -32602, "Invalid params: message"

McpErrors.internal(message: string, cause?: Error): AirError;
// code: -32603, "Internal error: message"

McpErrors.forbidden(message: string): AirError;
// code: -32000, details: { type: 'forbidden' }

McpErrors.rateLimited(tool: string, retryAfterMs?: number): AirError;
// code: -32001, details: { tool, retryAfterMs }

McpErrors.timeout(tool: string, timeoutMs: number): AirError;
// code: -32003, details: { tool, timeoutMs }

MCP 에러 코드 표

코드팩토리의미
-32601toolNotFound도구 없음
-32602invalidParams파라미터 검증 실패
-32603internal내부 에러
-32000forbidden접근 거부
-32001rateLimited레이트 리밋 초과
-32003timeout타임아웃

에러 응답 형식

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

내장 미들웨어

errorBoundaryMiddleware

모든 에러를 포착 → MCP 형식 변환. stderr로 에러 로그.

validationMiddleware

params 정의 → Zod 스키마 변환 → 매 호출 검증. 성공 시 result.data로 params 교체 (.passthrough() 적용). 실패 시 필드별 상세 에러:

[Validation] Invalid parameters for "search":
  - query: Expected string, received number (expected: string, got: number)
Expected schema:
  - query: string
  - limit: number (optional)

Released under the Apache-2.0 License.