DEV Community

Cover image for AI 에이전트 API 테스트 능력 부여하는 MCP 서버 구축 방법
Rihpig
Rihpig

Posted on • Originally published at apidog.com

AI 에이전트 API 테스트 능력 부여하는 MCP 서버 구축 방법

요약 (TL;DR)

run_test, validate_schema, list_environments 세 가지 도구를 노출하는 TypeScript MCP 서버를 빠르게 구축하세요. Claude Code는 ~/.claude/settings.json, Cursor는 .cursor/mcp.json에 MCP 서버를 등록합니다. 그러면 AI 에이전트가 채팅 인터페이스 내에서 Apidog 테스트 실행, OpenAPI 스키마 검증, 환경 목록 조회를 자동화할 수 있습니다. 전체 소스 코드는 약 150줄이며 @modelcontextprotocol/sdk 패키지를 활용합니다.

Apidog을 지금 사용해보세요

Claude Code, Cursor 및 기타 AI 에이전트가 채팅 인터페이스를 벗어나지 않고 Apidog API 테스트를 실행하고, 스키마를 검증하며, 응답을 비교할 수 있도록 MCP 서버를 직접 구축해보세요.

💡 실전 배경

코딩 세션 중에 AI 에이전트가 API 엔드포인트를 완성했다면, 복사-붙여넣기 없이 채팅에서 바로 Apidog 테스트를 실행하고 결과를 즉시 받아보고 싶을 것입니다.

이것이 바로 Model Context Protocol (MCP)의 역할입니다. MCP는 AI 에이전트가 표준 인터페이스로 외부 도구에 접근할 수 있도록 하며, Apidog MCP 서버를 사용하면 컨텍스트 전환 없이 테스트 실행, 스키마 검증, 환경 조회가 가능합니다.

MCP란 무엇인가요?

MCP(Model Context Protocol)는 AI 에이전트가 외부 도구 및 데이터 소스에 접근할 수 있도록 하는 표준 프로토콜입니다. Claude Code, Cursor 등 MCP 호환 클라이언트에서 동작하는 플러그인 시스템과 유사합니다.

MCP 서버는 도구(에이전트가 호출할 수 있는 함수)와 리소스(에이전트가 읽을 수 있는 데이터)를 노출합니다. 여기서는 API 테스트를 위한 Apidog MCP 서버의 도구 구현에 집중합니다.

┌─────────────────┐         ┌──────────────────┐         ┌─────────────┐
│  AI 에이전트    │         │  MCP 서버        │         │  Apidog     │
│  (Claude Code)  │◄───────►│  (사용자 코드)   │◄───────►│  API        │
└─────────────────┘   JSON  └──────────────────┘  HTTP   └─────────────┘
Enter fullscreen mode Exit fullscreen mode

1단계: 프로젝트 설정

새 TypeScript 프로젝트를 만듭니다.

mkdir apidog-mcp-server
cd apidog-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
Enter fullscreen mode Exit fullscreen mode

tsconfig.json 파일을 아래와 같이 생성:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
Enter fullscreen mode Exit fullscreen mode

package.json에 빌드 및 실행 스크립트 추가:

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

2단계: MCP 서버 스켈레톤 생성

src/index.ts 파일을 생성 후 기본 MCP 서버를 초기화합니다.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "apidog",
  version: "1.0.0",
  description: "AI 에이전트를 위한 Apidog API 테스트 도구"
});

// 도구는 아래에 정의합니다

const transport = new StdioServerTransport();
await server.connect(transport);
Enter fullscreen mode Exit fullscreen mode

이 코드는 MCP 서버를 생성하고 stdio를 통해 에이전트와 통신할 준비를 마칩니다.

3단계: run_test 도구 정의

src/index.ts에 API 테스트 실행용 도구를 추가합니다.

server.tool(
  "run_test",
  {
    projectId: z.string().describe("Apidog 프로젝트 ID (프로젝트 URL에서 찾을 수 있음)"),
    environmentId: z.string().optional().describe("선택적 환경 ID"),
    testSuiteId: z.string().optional().describe("선택적 테스트 스위트 ID")
  },
  async ({ projectId, environmentId, testSuiteId }) => {
    const apiKey = process.env.APIDOG_API_KEY;
    if (!apiKey) {
      return {
        content: [{ type: "text", text: "오류: APIDOG_API_KEY 환경 변수가 설정되지 않았습니다" }]
      };
    }

    let url = `https://api.apidog.com/v1/projects/${projectId}/tests/run`;
    const params = new URLSearchParams();
    if (environmentId) params.append("environmentId", environmentId);
    if (testSuiteId) params.append("testSuiteId", testSuiteId);
    if (params.toString()) url += `?${params.toString()}`;

    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${apiKey}`,
          "Content-Type": "application/json"
        }
      });

      if (!response.ok) {
        const error = await response.text();
        return {
          content: [{ type: "text", text: `API 오류: ${response.status} ${error}` }]
        };
      }

      const results = await response.json();
      return {
        content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `요청 실패: ${error instanceof Error ? error.message : String(error)}` }]
      };
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

도구 정의 구성

  1. 이름: run_test – 에이전트가 호출할 때 사용할 명확한 이름
  2. 파라미터 스키마: Zod로 유효성 검증. 설명 추가 권장
  3. 핸들러: Apidog API 호출 및 결과 반환

4단계: validate_schema 도구 추가

OpenAPI 3.x 스키마의 유효성을 검증하는 도구를 추가하세요.

server.tool(
  "validate_schema",
  {
    schema: z.object({}).describe("유효성 검사할 OpenAPI 3.x 스키마 객체"),
    strict: z.boolean().optional().default(false).describe("엄격 모드 사용 여부")
  },
  async ({ schema, strict }) => {
    const apiKey = process.env.APIDOG_API_KEY;
    if (!apiKey) {
      return {
        content: [{ type: "text", text: "오류: APIDOG_API_KEY 환경 변수가 설정되지 않았습니다" }]
      };
    }

    try {
      const response = await fetch("https://api.apidog.com/v1/schemas/validate", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${apiKey}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({ schema, strict })
      });

      const result = await response.json();

      if (!response.ok) {
        return {
          content: [{ type: "text", text: `유효성 검사 실패: ${JSON.stringify(result.errors, null, 2)}` }]
        };
      }

      return {
        content: [{
          type: "text",
          text: result.valid
            ? "스키마가 유효한 OpenAPI 3.x입니다"
            : `경고: ${JSON.stringify(result.warnings, null, 2)}`
        }]
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `유효성 검사 실패: ${error instanceof Error ? error.message : String(error)}` }]
      };
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

5단계: list_environments 도구 추가

프로젝트의 테스트 환경 목록을 조회하는 도구를 구현합니다.

server.tool(
  "list_environments",
  {
    projectId: z.string().describe("Apidog 프로젝트 ID")
  },
  async ({ projectId }) => {
    const apiKey = process.env.APIDOG_API_KEY;
    if (!apiKey) {
      return {
        content: [{ type: "text", text: "오류: APIDOG_API_KEY 환경 변수가 설정되지 않았습니다" }]
      };
    }

    try {
      const response = await fetch(
        `https://api.apidog.com/v1/projects/${projectId}/environments`,
        {
          headers: {
            "Authorization": `Bearer ${apiKey}`
          }
        }
      );

      if (!response.ok) {
        const error = await response.text();
        return {
          content: [{ type: "text", text: `API 오류: ${response.status} ${error}` }]
        };
      }

      const environments = await response.json();
      return {
        content: [{
          type: "text",
          text: environments.length === 0
            ? "이 프로젝트에 대한 환경을 찾을 수 없습니다"
            : environments.map((e: any) =>
                `- ${e.name} (ID: ${e.id})${e.isDefault ? " [기본값]" : ""}`
              ).join("\n")
        }]
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `요청 실패: ${error instanceof Error ? error.message : String(error)}` }]
      };
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

6단계: 빌드 및 테스트

서버 빌드:

npm run build
Enter fullscreen mode Exit fullscreen mode

간단한 MCP 클라이언트로 동작을 확인하세요. test-client.js 예시:

import { spawn } from "child_process";

const server = spawn("node", ["dist/index.js"], {
  env: { ...process.env, APIDOG_API_KEY: "your-api-key" }
});

server.stdout.on("data", (data) => {
  console.log(`서버 출력: ${data}`);
});

server.stderr.on("data", (data) => {
  console.error(`서버 오류: ${data}`);
});

const message = {
  jsonrpc: "2.0",
  id: 1,
  method: "initialize",
  params: {
    protocolVersion: "2024-11-05",
    capabilities: {},
    clientInfo: { name: "test-client", version: "1.0.0" }
  }
};

server.stdin.write(JSON.stringify(message) + "\n");
Enter fullscreen mode Exit fullscreen mode

7단계: Claude Code 구성

MCP 서버를 Claude Code에 등록하세요.

~/.claude/settings.json 예시:

{
  "mcpServers": {
    "apidog": {
      "command": "node",
      "args": ["/absolute/path/to/apidog-mcp-server/dist/index.js"],
      "env": {
        "APIDOG_API_KEY": "your-api-key-here"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Claude Code를 재시작한 뒤, "API 테스트 지원"을 요청하면 Apidog 도구가 나타나야 합니다.

예시 사용법 (Claude Code):

run_test 도구를 사용하여 Apidog 프로젝트에서 테스트 실행
프로젝트 ID: proj_12345
환경: staging
Enter fullscreen mode Exit fullscreen mode
이 OpenAPI 스키마를 Apidog 규칙에 따라 검증해줘
[스키마 붙여넣기]
Enter fullscreen mode Exit fullscreen mode
proj_12345 프로젝트의 모든 환경을 나열해줘
Enter fullscreen mode Exit fullscreen mode

8단계: Cursor 구성

Cursor는 .cursor/mcp.json으로 MCP 서버를 연결합니다.

{
  "mcpServers": {
    "apidog": {
      "command": "node",
      "args": ["/absolute/path/to/apidog-mcp-server/dist/index.js"],
      "env": {
        "APIDOG_API_KEY": "your-api-key-here"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

예시 사용법 (Cursor):

@apidog run_test projectId="proj_12345" environmentId="staging"
Enter fullscreen mode Exit fullscreen mode

완전한 소스 코드

src/index.ts 전체 예시:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "apidog",
  version: "1.0.0",
  description: "AI 에이전트를 위한 Apidog API 테스트 도구"
});

// run_test 도구
server.tool(
  "run_test",
  {
    projectId: z.string().describe("Apidog 프로젝트 ID"),
    environmentId: z.string().optional().describe("환경 ID"),
    testSuiteId: z.string().optional().describe("테스트 스위트 ID")
  },
  async ({ projectId, environmentId, testSuiteId }) => {
    const apiKey = process.env.APIDOG_API_KEY;
    if (!apiKey) {
      return {
        content: [{ type: "text", text: "오류: APIDOG_API_KEY가 설정되지 않았습니다" }]
      };
    }

    let url = `https://api.apidog.com/v1/projects/${projectId}/tests/run`;
    const params = new URLSearchParams();
    if (environmentId) params.append("environmentId", environmentId);
    if (testSuiteId) params.append("testSuiteId", testSuiteId);
    if (params.toString()) url += `?${params.toString()}`;

    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${apiKey}`,
          "Content-Type": "application/json"
        }
      });

      const results = await response.json();
      return {
        content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `요청 실패: ${error instanceof Error ? error.message : String(error)}` }]
      };
    }
  }
);

// validate_schema 도구
server.tool(
  "validate_schema",
  {
    schema: z.object({}).describe("OpenAPI 스키마"),
    strict: z.boolean().optional().default(false)
  },
  async ({ schema, strict }) => {
    const apiKey = process.env.APIDOG_API_KEY;
    if (!apiKey) {
      return {
        content: [{ type: "text", text: "오류: APIDOG_API_KEY가 설정되지 않았습니다" }]
      };
    }

    const response = await fetch("https://api.apidog.com/v1/schemas/validate", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ schema, strict })
    });

    const result = await response.json();
    return {
      content: [{
        type: "text",
        text: result.valid
          ? "스키마가 유효합니다"
          : `문제: ${JSON.stringify(result.errors || result.warnings, null, 2)}`
      }]
    };
  }
);

// list_environments 도구
server.tool(
  "list_environments",
  {
    projectId: z.string().describe("Apidog 프로젝트 ID")
  },
  async ({ projectId }) => {
    const apiKey = process.env.APIDOG_API_KEY;
    if (!apiKey) {
      return {
        content: [{ type: "text", text: "오류: APIDOG_API_KEY가 설정되지 않았습니다" }]
      };
    }

    const response = await fetch(
      `https://api.apidog.com/v1/projects/${projectId}/environments`,
      {
        headers: { "Authorization": `Bearer ${apiKey}` }
      }
    );

    const environments = await response.json();
    return {
      content: [{
        type: "text",
        text: environments.map((e: any) =>
          `- ${e.name} (${e.id})${e.isDefault ? " [기본값]" : ""}`
        ).join("\n")
      }]
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
Enter fullscreen mode Exit fullscreen mode

구축한 것

구성 요소 목적
MCP 서버 AI 에이전트를 Apidog API에 연결
run_test 테스트 컬렉션을 프로그램적으로 실행
validate_schema 배포 전 OpenAPI 오류 포착
list_environments 사용 가능한 테스트 환경 검색
Zod 유효성 검사 타입 안정성 파라미터 처리
Stdio 트랜스포트 Claude Code, Cursor, 모든 MCP 클라이언트와 호환

다음 단계

서버 확장:

  • 환경 간 테스트 결과 비교용 compare_responses 도구 추가
  • 과거 실행 내역 조회용 get_test_history 도구 추가
  • 모의 엔드포인트 관리용 trigger_mock_server 도구 추가

운영 고려 사항:

  • 네트워크 불안정 시 재시도 로직 추가
  • API 스로틀링 방지를 위한 속도 제한 적용
  • 실패한 호출에 대한 로깅 추가
  • API 키를 안전하게 저장

팀과 공유:

  • @your-org/apidog-mcp-server로 npm에 배포
  • 환경 변수 및 구성법 문서화
  • 일반 MCP 클라이언트 예시 포함

일반적인 문제 해결

Claude Code에서 MCP 서버가 로드되지 않는 경우

  • ~/.claude/settings.json의 경로가 절대 경로인지 확인
  • node가 PATH에 있는지 (which node)
  • 빌드된 dist/index.js가 존재하는지 (ls -la dist/)
  • Claude Code의 MCP 로그에서 오류 확인

구성 후 도구가 나타나지 않음

  • Claude Code 완전 재시작
  • npm run build로 TypeScript 컴파일
  • 세 도구가 server.connect() 전에 정의되어 있는지 확인
  • 서버가 오류 없이 시작되는지 (node dist/index.js)

API 요청 401 오류

  • APIDOG_API_KEY 환경 변수 설정 확인
  • 공백, 따옴표 등 오타 없는지 확인
  • Apidog 계정에서 API 접근 권한 확인
  • 키를 curl로 직접 테스트
  curl -H "Authorization: Bearer $APIDOG_API_KEY" https://api.apidog.com/v1/user
Enter fullscreen mode Exit fullscreen mode

Zod 유효성 검사 오류

  • 파라미터 이름과 스키마 일치 확인
  • 필수 필드 누락 없는지 확인
  • 선택 필드는 .optional() 사용
  • Zod 오류 메시지를 꼼꼼히 확인

TypeScript 컴파일 오류

  • 모든 의존성 설치 (npm install)
  • TypeScript 5.x 버전 확인 (npx tsc --version)
  • dist 폴더 삭제 후 재빌드 (rm -rf dist && npm run build)
  • 타입 불일치시 as 어설션 활용

MCP 서버 로컬에서 테스트하기

운영 배포 전, MCP 서버를 수동으로 stdio를 통해 검증하세요.

# 서버 시작
node dist/index.js

# 다른 터미널에서 도구 목록 요청
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node dist/index.js
Enter fullscreen mode Exit fullscreen mode

예상 출력:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      { "name": "run_test", "description": "...", "inputSchema": {...} },
      { "name": "validate_schema", "description": "...", "inputSchema": {...} },
      { "name": "list_environments", "description": "...", "inputSchema": {...} }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

도구 호출 테스트:

echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_environments","arguments":{"projectId":"your-project-id"}}}' | node dist/index.js
Enter fullscreen mode Exit fullscreen mode

이제 AI 에이전트가 Apidog의 테스트 기능에 직접 접근할 수 있습니다.

브라우저 전환과 복사-붙여넣기 없이, 채팅에서 명령만 입력하면 결과를 받을 수 있습니다.

이것이 바로 MCP의 힘입니다.

AI 에이전트를 도메인별 도구로 확장해, 개발자의 반복 작업을 자동화하세요.

핵심 요약

  • MCP 서버는 AI 에이전트를 외부 API에 연결

    한 번 구축하면 Claude Code, Cursor, 기타 MCP 호환 클라이언트에서 사용 가능

  • 세 가지 도구로 대부분의 API 테스트 요구 충족

    실행(run_test), OpenAPI 유효성 검증(validate_schema), 환경 조회(list_environments)

  • Zod 유효성 검증으로 잘못된 파라미터 사전 차단

    타입 안정성으로 API 호출 전에 오류 포착

  • 도구별 구성

    Claude Code는 ~/.claude/settings.json, Cursor는 .cursor/mcp.json 사용

  • 운영 환경에선 오류 처리 필수

    재시도, 속도 제한, 안전한 키 저장소 등 보강 필요

자주 묻는 질문 (FAQ)

Q. AI에서 MCP란 무엇인가요?

A. MCP(Model Context Protocol)는 AI 에이전트가 외부 도구/데이터 소스에 접근할 수 있도록 하는 표준 프로토콜입니다. 플러그인 시스템과 유사합니다.

Q. Apidog용 MCP 서버는 어떻게 생성하나요?

A. @modelcontextprotocol/sdk 설치 → Zod로 도구 파라미터 정의 → Apidog API 호출하는 핸들러 작성 → StdioServerTransport로 연결

Q. Cursor와 함께 사용할 수 있나요?

A. 네, 프로젝트 루트의 .cursor/mcp.json에 MCP 서버 구성을 추가하면 Claude Code, Cursor 등에서 동일하게 동작합니다.

Q. 어떤 도구를 노출해야 하나요?

A. 최소한 run_test(테스트 실행), validate_schema(OpenAPI 검증), list_environments(환경 조회)를 구현하세요.

Q. Apidog MCP 서버는 운영 환경에 준비되었나요?

A. 튜토리얼 코드는 시작점입니다. 운영에서는 재시도, 속도 제한, 오류 처리, 안전한 API 키 저장을 반드시 추가하세요.

Q. Apidog API 키가 필요한가요?

A. 네. APIDOG_API_KEY를 환경 변수로 지정해야 하며, 서버는 이를 사용해 Apidog API를 인증합니다.

Q. 이 MCP 서버를 팀과 공유할 수 있나요?

A. 네. npm에 비공개 패키지로 배포하고, 환경 변수 및 구성 예시를 문서화하세요.

Top comments (0)