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)