CodeGym /행동 /ChatGPT Apps /MCP 검사와 디버깅: MCP Jam, Inspector, 로그

MCP 검사와 디버깅: MCP Jam, Inspector, 로그

ChatGPT Apps
레벨 6 , 레슨 4
사용 가능

1. 왜 MCP‑인스펙터가 필요한가

프런트엔드를 디버깅하는데 DevTools를 열지 못하게 했다면 어떤 느낌일까요? MCP‑인스펙터 없이 일하는 삶이 정확히 그렇습니다. MCP‑프로토콜은 ChatGPT와 Apps SDK의 보닛 안에서 동작합니다. 채팅 답변만 보고 “왜 내 도구를 못 보지?”라고 생각한다면, 사실상 허공에 총을 쏘는 셈입니다.

MCP Inspector(공식)나 MCP Jam 같은 인스펙터는 개발자를 위한 전용 MCP 클라이언트입니다. 이들은 다음을 할 수 있습니다:

  • 여러분의 MCP 서버에 ChatGPT와 동일한 방식으로 연결;
  • handshake / capabilities를 수행;
  • tools/resources/prompts 목록을 요청;
  • 아무 tool이나 임의의 인자로 수동 호출;
  • 원본 JSON 메시지(requests / replies / errors) 표시.

본질적으로 “두뇌를 장착한 MCP용 Postman”입니다. 일반 REST 클라이언트와 달리 인스펙터는 MCP의 특성을 압니다: tools/list, tools/call을 이해하고, 인자 스키마를 보여 주며, 때로는 보호된 서버를 위한 OAuth 플로우까지 지원합니다.

인스펙터가 없으면 디버깅은 이렇게 됩니다. ChatGPT를 띄우고 App을 호출해 보다가 “Error talking to app”이 뜨거나 툴이 아예 호출되지 않는 것을 보고, 모델이 도구를 부르지 않은 건지, MCP가 뜨지 않은 건지, 아니면 JSON 오류인지 추측합니다. 인스펙터가 있으면 각 레이어를 개별적으로 확인할 수 있습니다: 먼저 MCP 서버와 인스펙터를 1:1로 점검하고, 그다음 ChatGPT ↔ MCP를 묶어 봅니다.

2. 인스펙터 미니 리뷰: MCP Inspector, Jam 등

실무에서는 MCP용 인스펙터를 보통 두 종류로 사용하게 됩니다.

첫째, Model Context Protocol 리포지토리의 공식 MCP Inspector입니다. 보통 React 기반의 웹 앱(SPA)으로, 로컬에서 띄우거나 npx/Docker로 실행해 HTTP/SSE로 여러분의 MCP 서버에 연결할 수 있습니다.

둘째, MCP Jam 유형의 인스펙터로, OAuth 편의 기능을 제공하는 경우가 많습니다. 이들은 .well-known/oauth-protected-resource를 읽어 authorization_endpointtoken_endpoint를 추출하고, PKCE 플로우를 수행한 뒤 인증된 상태로 MCP에 접근합니다.

MCP Jam은 MCP Inspector를 기반으로 개발자들이 만든 도구입니다. MCP Inspector가 최소한의 디버깅 도구를 제공한다면, MCP Jam은 개발자가 MCP로 일상적으로 작업할 때 필요한 모든 기능을 제공합니다. 개인적으로는 처음부터 MCP Jam을 사용하는 것을 추천합니다. 나중에 다시 익힐 필요가 없습니다.

우리 과정의 관점에서 차이는 다음과 같습니다:

  • 기본 Inspector는 항상 필요합니다. 가장 단순한 비보호 MCP 서버에도 마찬가지입니다;
  • MCP Jam(또는 유사 도구)은 인증/인가 모듈을 다룰 때 유용해집니다.

하지만 핵심은 같습니다. ChatGPT가 “조용히” 수행하는 일을 보기 좋게 보여 주는, 평범하지만 개발자 친화적인 MCP 클라이언트입니다.

3. MCP 인스펙터 사용의 표준 시나리오

여러분이 MCP 서버에 새 tool을 만들었고 실제로 동작하는지 확인하고 싶다고 가정해 봅시다.

지난 강의에서 최소 MCP 서버를 이미 띄웠습니다. 이제 검증을 위한 시스템적 접근을 더해, 전체 사이클 “서버 → 인스펙터 → JSON 로직”을 단계별로 실행해 보겠습니다.

단계 1 — MCP 서버 실행

이전 강의에서 이미 해 본 작업입니다. 예를 들어 npm run mcp-dev 스크립트가 있다고 합시다:

# MCP 서버 실행 예
npm run mcp-dev
# 내부적으로는 대략: ts-node src/mcp-server.ts

서버가 선택한 트랜스포트를 수신 중인지가 중요합니다. 이 과정에서는 보통 HTTP endpoint /mcp를 임의의 포트에서 사용합니다. 예: http://localhost:4001/mcp.

단계 2 — MCP Jam 실행

두 번째 터미널:

# MCP Jam 실행 방법 예
npx @mcpjam/inspector@latest
# 필요하면 --port 4002 등을 추가

그 후 인스펙터는 보통 http://localhost:6274 같은 포트에서 브라우저로 열립니다.

MCP Jam 시작 화면에서 MCP 서버의 URL을 입력하라고 합니다. 다음을 입력합니다:

http://localhost:4001/mcp

또는 이미 ngrok 등을 통해 터널링 중이라면 해당 URL을 사용합니다.

단계 3 — handshake / capabilities

MCP Jam이 연결되면 ChatGPT와 동일한 일을 자동으로 수행합니다:

  1. 클라이언트 정보를 담아 초기 요청(initialize)을 전송합니다.
  2. 프로토콜 버전과 서버의 capabilities를 응답으로 받습니다.
  3. capabilities에 따라 서버가 tools, resources, prompts 등 기능을 지원하는지 파악합니다.

UI에서는 보통 다음과 비슷하게 표시됩니다:

Connected
Protocol: mcp/2025-06-18
Capabilities:
- tools: list, call
- resources: list, read
- prompts: list, get

이 단계에서 인스펙터가 연결하지 못한다면(connection refused, CORS, 500 등), 즉시 오류를 확인하고 다음을 이해할 수 있습니다: 문제는 확실히 모델이나 ChatGPT가 아니라, 여러분의 서버 측이나 네트워크에 있습니다.

단계 4 — discovery: tools/resources/prompts 확인

핸드셰이크가 성공하면 인스펙터는 보통 tools/list, resources/list, prompts/list 같은 메서드를 스스로 호출해 사이드바를 채웁니다. 그러면 다음을 보게 됩니다:

  • 설명과 입력 인자 JSON Schema가 포함된 도구 목록;
  • 컬렉션/경로 기준으로 그룹화된 리소스 목록;
  • 간단한 설명이 있는 프롬프트 목록.

방금 새 tool을 추가했는데 목록에 없다면, 서버에서 등록이 잘못되었거나 최신 코드로 서버가 뜨지 않은 것입니다. 이 지점에서 문제를 확인하는 것이, ChatGPT가 “도구를 부르지 않는다”는 이유를 추측하는 것보다 훨씬 쉽습니다.

4. MCP Jam으로 tools 수동 호출

MCP Jam의 가장 유용한 기능은 도구 수동 호출입니다. 즉, tools/call을 위한 개인용 UI입니다.

도구를 선택하고 인자 채우기

이전 모듈에서 suggest_gifts라는 tool을 작성했다고 가정해 봅시다:

// src/mcp/tools/suggestGifts.ts 어딘가
export const suggestGiftsTool = {
  name: "suggest_gifts",
  description: "나이, 예산, 관심사에 맞는 선물 아이디어를 추천합니다",
  inputSchema: {
    type: "object",
    properties: {
      age: { type: "number" },
      budget: { type: "number" },
      interests: {
        type: "array",
        items: { type: "string" }
      }
    },
    required: ["age", "budget"]
  },
  // handler는 별도로 정의
};

MCP Jam에서 suggest_gifts를 클릭합니다. 그러면 inputSchema를 기반으로 생성된 폼이 오른쪽에 열립니다. 그곳에 다음과 같이 입력합니다:

{
  "age": 30,
  "budget": 100,
  "interests": ["게임", "책"]
}

그리고 “Call” 버튼을 누릅니다.

인스펙터는 MCP 요청 tools/call을 전송하고, 곧바로 다음을 확인할 수 있습니다:

  • 서버로 전송되는 원본 JSON 데이터(요청 내용);
  • 원본 JSON 응답 데이터(result 또는 error);
  • 가능하다면 보기 좋은 결과 프리뷰.

인스펙터에서 JSON 로그 읽기

보통 인스펙터는 다음과 비슷하게 보여 줍니다:

// Request
{
  "id": "1",
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "suggest_gifts",
    "arguments": {
      "age": 30,
      "budget": 100,
      "interests": ["게임", "책"]
    }
  }
}
// Reply
{
  "id": "1",
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "1) 보드게임 ... 2) 서점 기프트 카드 ..."
      }
    ]
  }
}

handler가 예외로 실패하면 JSON‑RPC 스타일의 error를 보게 됩니다:

{
  "id": "1",
  "jsonrpc": "2.0",
  "error": {
    "code": -32603,
    "message": "Internal error",
    "data": "TypeError: Cannot read properties of undefined ..."
  }
}

매우 중요합니다. 바로 여기서 프로토콜 레벨을 볼 수 있습니다. Apps SDK/ChatGPT가 기대하는 형식과 응답이 다르면, “GPT 버그”를 의심하기 전에 이를 미리 파악할 수 있습니다.

5. 리소스와 프롬프트 디버깅

MCP가 할 수 있는 일은 도구만이 아닙니다. 이미 resourcesprompts도 있다는 걸 알고 있지요.

인스펙터를 통해 다음을 할 수 있습니다:

  • 리소스 목록(resources/list)을 열고 메타데이터를 확인;
  • 특정 리소스를 읽기(resources/read) 및 반환 데이터의 적합성 확인;
  • 리소스 검색 기능을 구현했다면 검색 수행;
  • 준비된 프롬프트와 그 텍스트 확인.

예를 들어 gift_catalog 리소스가 있다면:

// 리소스 등록 의사코드
registerResource({
  uri: "resource://giftgenius/catalog",
  name: "선물 카탈로그",
  mimeType: "application/json",
  handler: async () => {
    return JSON.stringify(giftCatalogData);
  }
});

인스펙터에서 이 리소스를 확인하고 클릭하면 즉시 JSON을 볼 수 있습니다. JSON이 유효하지 않거나 MIME 타입이 이상하다면, ChatGPT가 이를 읽거나 위젯에 삽입하다가 넘어지기 전에 문제를 잡을 수 있습니다.

6. MCP 서버 로그: 무엇을 어디에 어떻게

MCP Jam은 훌륭하지만 충분하지 않습니다. MCP 서버 자체 로그가 필요합니다. 로그 없이는 어떤 프로덕션도 복불복이 됩니다.

무엇을 로그로 남길까

유용한 최소 구성:

  • 모든 수신 MCP 메시지(request/notification)와 함께:
    • 시간;
    • 메서드(tools/call, tools/list 등);
    • 도구 이름(있다면);
    • 민감 정보가 제거된 축약 인자;
  • 모든 송신 응답:
    • 상태(성공/오류);
    • 실행 시간;
    • 축약된 결과 또는 최소한 타입;
  • 기술적 오류:
    • JSON 파싱;
    • handler에서의 예상치 못한 예외.

동시에 PII나 시크릿을 그대로 로그하지 않는 것이 매우 중요합니다. 토큰, 비밀번호, 기밀 요청 본문 전체 등은 금물입니다. 프로덕션 로깅 권장사항에서는 보통 PII를 축약한 형태로 기록하라고 명시합니다.

로그는 어디로: stdout / stderr

MCP에는 중요한 요구사항이 있습니다. JSON 메시지는 “올바른 채널”로, 디버그 로그는 다른 채널로 보내야 합니다. 예를 들어 stdout/stderr 기반 트랜스포트를 사용 중이라면:

  • JSON‑RPC 메시지는 stdout으로;
  • console.log, console.error 등은 모두 stderr로 보내야 합니다.

JSON과 텍스트 로그를 하나의 스트림에 섞으면, 클라이언트(MCP Jam 또는 ChatGPT)는 메시지를 파싱하지 못합니다. JSON 사이에 갑자기 Server started at http://localhost:4001 같은 문자열이 끼어들 수 있기 때문입니다. MCP 서버에서 흔한 실수입니다.

HTTP 시나리오에서는 문제가 좀 더 단순하지만 원칙은 같습니다. HTTP 응답에는 순수 JSON만 있어야 하며, 모든 로그는 콘솔/파일 등으로 보내고 응답 본문에는 넣지 마세요.

TypeScript MCP 서버를 위한 간단한 로거

가상의 MCP 서버에 작은 로거를 추가해 봅시다:

// src/logger.ts
export function logRequest(method: string, details: unknown) {
  console.error(
    JSON.stringify({
      level: "info",
      type: "request",
      method,
      details,
      ts: new Date().toISOString(),
    })
  );
}

export function logError(method: string, error: unknown) {
  console.error(
    JSON.stringify({
      level: "error",
      type: "error",
      method,
      error: String(error),
      ts: new Date().toISOString(),
    })
  );
}

그리고 tools 핸들러에서:

// src/mcp-server.ts (발췌)
server.setRequestHandler("tools/call", async (req) => {
  logRequest("tools/call", {
    name: req.params?.name,
    // 여기서는 전체 payload를 넣기보다 안전한 필드만 넣는 것이 좋습니다
  });

  try {
    const result = await handleToolCall(req);
    return result;
  } catch (e) {
    logError("tools/call", e);
    throw e;
  }
});

이렇게 하면 콘솔에서 구조화된 JSON 로그를 볼 수 있고, ts나 추가적인 requestId로 서로 쉽게 매칭할 수 있습니다.

7. 조합: MCP Jam + 로그

올바른 MCP 디버깅 전략은 거의 항상 다음과 같습니다:

  1. 인스펙터에서 문제를 재현합니다. tools/list가 빈 목록을 반환하거나 tools/call이 실패하고, JSON 응답이 이상한지 등을 확인합니다.
  2. 동시에 MCP 서버 로그를 확인합니다. 시작 시 무엇을 기록하는지, 각 메시지마다 어떤 오류를 내보내는지, stack trace가 있는지 확인합니다.
  3. 로그의 id, method, ts와 인스펙터에서 보이는 내용을 대조합니다.

예를 들어 인스펙터에서는 다음을 보았고:

{
  "error": {
    "code": -32603,
    "message": "Internal error"
  }
}

로그에서는 다음을 보았다면:

{
  "level": "error",
  "type": "error",
  "method": "tools/call",
  "error": "TypeError: Cannot read properties of undefined (reading 'age')",
  "ts": "2025-11-21T10:15:12.345Z"
}

원인은 명확합니다. handler 어딘가에서 age를 기대하지만 스키마/인자가 다릅니다.

8. 미니 체크리스트 “MCP 서버가 App 통합 준비가 되었는가”

MCP 서버를 실제 ChatGPT App에 연결하기 전에 인스펙터로 짧은 체크리스트를 점검하면 좋습니다.

첫째, handshake와 capabilities가 오류 없이 통과해야 합니다. MCP Jam은 서버가 여러분에게 필요한 엔터티를 지원한다고 보여야 합니다. 최소한 tools와, 사용하는 경우 resources / prompts가 필요합니다.

둘째, 인스펙터의 tools/resources/prompts 목록이 여러분이 구현했다고 생각하는 도구, 리소스, 프롬프트 집합과 일치해야 합니다. name 오타, 등록 누락 등은 이 단계에서 즉시 잡힙니다.

셋째, 유효한 인자로 호출했을 때 도구는 안정적으로 올바른 result를 반환해야 합니다. 프로덕션에서 실제로 기대하는 전형적인 케이스 몇 가지를 시도해 보세요.

넷째, 유효하지 않은 인자로 호출했을 때는 명확한 error 응답(JSON‑RPC 스타일)을 반환해야 하며, 500으로 단순히 떨어지면 안 됩니다. 필수 파라미터가 빠진 경우 등에는 구조화된 오류를 반환해, 이후 ChatGPT가 사용자에게 이해 가능한 메시지로 바꿀 수 있도록 하는 것이 좋습니다.

다섯째, 이때 서버 로그가 매번 엄청난 stack trace로 콘솔을 도배해서는 안 됩니다. 오류는 구조화되어야 하고, 민감한 데이터는 적절히 필터링되어야 합니다.

이 모든 항목을 인스펙터에서 만족한다면, 훨씬 더 안심하고 MCP 서버를 Apps SDK에 연결해 Dev Mode에서 위젯을 실험할 수 있습니다.

9. 자주 발생하는 MCP 서버 버그와 인스펙터로 잡는 법

이제 가장 중요한 부분입니다. 무엇이 자주 망가지고, 이를 어떻게 발견할 수 있는지 살펴봅시다.

구성 및 연결

“서버가 작동하지 않는다”고 느끼지만, 실제 문제는 해당 포트나 endpoint를 전혀 수신하지 않는 경우가 있습니다. 이럴 때 인스펙터는 connection refused라고 솔직히 알려 주거나, 아예 연결에 실패합니다. 흔한 원인: 잘못된 URL(예: /mcp 대신 /api/mcp), 포트가 다른 프로세스에 점유됨, 터널 미기동, CORS에 의한 차단 등.

잘못된 JSON / 로그와 프로토콜 혼합

가장 아픈 사례 중 하나는 console.log("Server started")를 stdout에 출력하면서, 동시에 JSON‑RPC 메시지도 그 위로 전송하는 경우입니다. 클라이언트는 순수 JSON을 기대하지만, 텍스트 + JSON을 받아 파싱에 실패합니다.

해결책은 간단합니다. 프로토콜 스트림(stdout 또는 HTTP 응답 본문)으로 가는 것과, 로그(stderr 또는 별도 로그 파일)로 가는 것을 엄격히 분리하세요.

스키마와 도구 구현 불일치

또 다른 흔한 실수는 inputSchema에는 한 가지를 선언해 놓고, 코드에서는 다른 것을 기대하는 경우입니다. 예를 들어 스키마는 age는 숫자, interests는 선택적 문자열 배열이라고 했는데, 코드는 arguments.interests.toLowerCase()를 시도합니다. 모델(과 인스펙터)은 정직하게 interestsnull로 보내거나 아예 필드를 보내지 않을 수 있으며, 이때 코드가 실패합니다.

인스펙터는 tools/call로 실제 어떤 JSON이 전달되는지를 명시적으로 보여 주므로, 이를 코드와 대조할 수 있습니다.

잘못된 tools/resources 이름

capabilities / tools/list에서 tool을 suggest_gifts_v2로 내보내는데, Apps 매니페스트나 위젯에서는 suggest_gifts를 기대한다면, “도구를 찾을 수 없음”은 프로젝트 끝까지 따라다닐 것입니다. 인스펙터에서는 tools 목록과 각 name 필드를 통해 즉시 확인할 수 있습니다. GPT의 생각을 추측할 필요가 없습니다.

느리거나 멈추는 tools

인스펙터에서 도구 호출이 30초씩 걸리다가 타임아웃으로 실패한다면, ChatGPT가 더 잘 반응하리라 기대하지 마세요. MCP 인스펙터는 정확히 어느 단계에서 느린지(네트워크 호출, DB, 외부 API)를 파악하는 데 도움을 줍니다. 각 요청 처리의 시작/종료 시간을 로그에 남겨 outlier를 바로 볼 수 있도록 하는 것이 좋습니다.

10. 인스펙션/디버깅 시 흔한 실수

실수 1: MCP를 ChatGPT만으로 디버깅하려 한다.
많은 개발자가 먼저 MCP를 App에 연결하고, “무언가 작동하지 않는다”는 것을 보고 프롬프트, 도구 설명, 심지어 모델 버전까지 바꿉니다. 그런데 MCP 서버가 아예 뜨지 않았거나 tools/list가 비어 있는 경우가 있습니다. 항상 인스펙터부터 시작하세요. 거기서 문제가 보이면, 모델 탓이 아닙니다.

실수 2: JSON‑RPC와 로그를 한 스트림에 섞는다.
MCP 클라이언트가 순수 JSON을 기대하는데 stdout에 디버그 문자열을 찍어 버리면 결과는 예측 가능합니다. 파서가 깨지고, Inspector는 이상한 오류를 보여 줍니다. 로그는 별도로(stderr, 파일, 외부 로깅 시스템), 프로토콜 메시지는 자기 채널로만 보내야 합니다.

실수 3: capabilities와 tools 목록을 보지 않는다.
도구가 “사라지는” 이유는 종종 등록을 깜박했거나 해당 capability를 켜지 않았기 때문입니다. 인스펙터에서 capabilitiestools/list를 확인하지 않으면, 모델 탓으로 돌리기 쉽지만, 실제로는 등록 코드 문제일 수 있습니다.

실수 4: 스키마 오류와 JSON 불일치를 무시한다.
inputSchema와 실제 JSON이 어긋나면, 모델과 인스펙터는 당연히 이상하게 동작합니다. 인스펙터에서 원본 JSON 메시지를 확인하지 않고 스키마 유효성 검사를 하지 않으면, 오류는 가장 예상치 못한 곳에서 터집니다.

실수 5: PII와 토큰까지 모조리 로그로 남긴다.
디버깅 열정에 휩쓸려 전체 request body를 로그로 찍기 쉽습니다. 그 안에는 개인 정보나 시크릿이 포함될 수 있습니다. 프로덕션에서는 시한폭탄이 됩니다. 유출, 컴플라이언스 문제 등으로 이어지죠. 진단에 필요한 것만, 축약/익명화된 데이터로 기록하세요.

실수 6: 최소 케이스로 문제를 재현하지 않는다.
때로는 ChatGPT의 복잡한 대화 속에서만 버그가 나타나고, 개발자가 그 상태 그대로 디버깅을 시도합니다. 훨씬 효과적인 방법은 동일한 시나리오를 인스펙터에서 한두 개의 MCP 요청으로 재현하여 프롬프트, 대화 이력, 모델의 “기분” 영향을 제거하는 것입니다.

1
설문조사/퀴즈
MCP 프로토콜, 레벨 6, 레슨 4
사용 불가능
MCP 프로토콜
MCP: 프로토콜, 서버, 그리고 인스펙션
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION