CLI 클라이언트

CLI 클라이언트 (CLI Client)

출처: MCP 공식 문서 — CLI client

CLI 실행은 서버에 연결해서 --method로 지정한 하나의 요청만 호출하고, 결과를 출력한 뒤 종료해요. 그래서 CI 파이프라인, 셸 원라이너, 서버 변경을 즉시 검증하려는 코딩 에이전트에 잘 맞아요.

npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list

아래 예제는 설치된 mcp-inspector 바이너리를 기준으로 해요. 전역 설치가 없다면 위처럼 각 명령 앞에 npx @modelcontextprotocol/inspector를 붙이면 돼요.

서버 고르기

CLI는 위치 인자 명령(stdio), --server-url(HTTP/SSE), 또는 카탈로그·설정 파일 속 이름 있는 서버를 받아요.

# stdio: 위치 인자 전부가 띄울 명령
mcp-inspector --cli node build/index.js --method tools/list

# HTTP
mcp-inspector --cli https://api.example.com/mcp --transport http --method tools/list

# 파일에서
mcp-inspector --cli --config ./mcp.json --server myserver --method tools/list

서버가 파일에서 오면 해당 서버별 설정(헤더, 타임아웃, OAuth, 프로토콜 에라, 루트)이 TUI와 웹 클라이언트가 해석하는 것과 똑같이 연결에 적용돼요. --header 플래그는 그 실행에서 파일의 헤더를 덮어쓰되 타임아웃과 OAuth는 그대로 둬요.

이후 예제는 어떤 형태를 쓰든 --transport 또는 --config/--server 플래그와 함께 <server>로 줄여 쓰겠어요.

설정 파일이 실행에 roots를 주는 유일한 지속적인 방법이에요. roots 플래그는 없고, --method roots/set은 그 하나의 짧은 연결에만 적용돼요. 서버에 설정된 roots는 연결 시 광고되므로, roots/list를 호출하는 서버(@modelcontextprotocol/server-filesystem이 허용 디렉터리를 알기 위해 그렇게 해요)는 이를 받게 돼요.

--catalog vs --config, -- 구분자, 공용 서버 선택 플래그는 Configuration and flags에서 볼 수 있어요.

메서드

--method 필요한 동반 플래그 비고
initialize 없음 연결만 하는 프로브: {serverInfo, protocolVersion, capabilities, instructions}.
tools/list 없음
tools/call --tool-name, + --tool-arg / --tool-args-json
resources/list 없음
resources/read --uri
resources/templates/list 없음
prompts/list 없음
prompts/get --prompt-name, --prompt-args
logging/setLevel --log-level legacy 에라 전용; modern 서버는 대신 요청마다 옵트인해요.
servers/list, servers/show 없음 연결 없이 카탈로그를 읽어요.

스트림 또는 세션 전용 메서드(logging/tail 등)는 거부돼요. 종료하는 프로세스가 스트림을 열어 둘 수 없기 때문이에요.

인자 전달

--tool-argkey=value를 받아서 값을 JSON 파싱으로 강제 변환해요. 그래서 count=1은 숫자가 되고 "012"12가 돼요.

mcp-inspector --cli <server> --method tools/call --tool-name mytool \
  --tool-arg key=value --tool-arg count=1 --tool-arg 'options={"format":"json"}'

--tool-args-json은 인자 객체 전체를 한 번에 받아 있는 그대로(verbatim) 전달해요. 강제 변환이 없으므로 "012"는 문자열 012로 남아요. 둘은 상호 배타적이에요.

mcp-inspector --cli <server> --method tools/call --tool-name mytool \
  --tool-args-json '{"zip":"10001"}'

출력

--format text(기본)는 사람이 읽기 좋게 예쁘게 출력하고, --format json은 배너 없이 단일 JSON 객체를 stdout에 내보내서 전체 출력이 깔끔하게 파이프돼요.

mcp-inspector --cli <server> --method tools/list --format json | jq '.result.tools[].name'

MCP Apps 프로빙

--app-info는 도구가 MCP App UI(ui:// 리소스, CSP, 권한)를 제공하는지 도구를 호출하지 않고 보고해서, 파이프라인이 뭔가를 호출하기 전에 브라우저가 필요한지 판단하게 해줘요.

# 도구 하나 -> JSON 한 줄
mcp-inspector --cli <server> --method tools/call --tool-name my_tool --app-info
# {"hasApp":true,"toolName":"my_tool","resourceUri":"ui://...","csp":{...},"permissions":{...}}

# 모든 도구 -> 단일 연결 위 NDJSON, 도구마다 한 줄
mcp-inspector --cli <server> --method tools/list --app-info | jq -c 'select(.hasApp)'

종료 코드가 결과를 구분해요. 앱이 있는 도구는 0, 앱이 없으면 2, 도구가 없으면 5로 종료돼서, 오타를 '앱 없음'으로 오인하지 않아요. 프로브 실패(읽을 수 없는 UI 리소스, 잘못된 resourceUri)는 중단 대신 resourceError 필드로 보고되므로, 나쁜 도구 하나가 전체 목록을 죽이지 않아요.

tools/list --app-info--format과 무관하게 항상 NDJSON(도구당 한 줄)을 내보내요. --format jsontools/call --app-info의 단일 도구 출력만 모양을 바꿔요.

종료 코드와 오류 봉투

0이 아닌 모든 종료는 안정적인 실패 클래스에 매핑되므로, 호출자가 산문을 긁지 않고 실패했는지로 분기할 수 있어요.

코드 의미
0 성공.
1 사용법 또는 예상 밖 오류(포괄).
2 도구에 MCP App이 없음(--app-info 프로브).
3 서버가 인증 필요(401/403, WWW-Authenticate, OAuth).
4 서버에 도달 불가(DNS, 연결 거부, 타임아웃, fetch failed).
5 도구 오류: tools/callisError: true를 반환했거나 도구를 찾지 못함.

0이 아닌 종료 시 CLI는 단일 JSON 한 줄을 stderr에 써요.

{
  "error": {
    "code": "auth_required",
    "message": "Unauthorized",
    "status": 401,
    "url": "https://api.example/mcp"
  }
}

한 줄이므로 호출자는 2>&1 | tail -1 | jq .error로 파싱할 수 있어요.

isError: true를 반환하는 tools/call도 페이로드는 출력하지만 5로 종료되므로, && 체인이 실패한 호출에서는 계속 진행되지 않아요.

스크립트에서의 인가

기본적으로 CLI는 TUI와 같은 루프백 OAuth 흐름을 실행해요. 브라우저를 열고 CI 작업이 완료할 수 없는 localhost 콜백을 기다리죠. 두 플래그가 비대화형 실행을 예측 가능하게 만들어요.

  • --stored-auth-only: 대화형 OAuth나 step-up을 절대 시작하지 않고 브라우저를 자동으로 열지도 않아요. 공유 저장소의 토큰이 있으면 쓰고, 없으면 auth_required로 즉시 실패해요. CI가 원하는 플래그가 바로 이거예요.
  • --use-stored-auth: 이 머신에서 웹 Inspector가 이미 얻은 토큰을 재사용하고, 리프레시 토큰이 저장돼 있으면 먼저 갱신해요.

둘 다 없고 stdin·stderr에 TTY도 없다면, CLI는 아무도 완료하지 않을 콜백을 15분 동안 기다리는 대신 auth_required로 빠르게 실패해요.

전체 흐름, 웹→CLI 핸드오프, --print-handoffAuthorization에서 볼 수 있어요.

레시피

CI에서 서버 검증

set -euo pipefail

# 서버에 도달할 수 없거나 도구를 노출하지 않으면 빌드 실패
mcp-inspector --cli --config ./ci-servers.json --server my-server \
  --stored-auth-only --method tools/list --format json \
  | jq -e '.result.tools | map(.name) | index("get_weather")' > /dev/null

실패 클래스로 분기

if out=$(mcp-inspector --cli "$URL" --transport http --method tools/list 2>err.json); then
  echo "$out"
else
  case $? in
    3) echo "needs auth: run the web inspector once to sign in" ;;
    4) echo "server unreachable" ;;
    *) jq .error < err.json ;;
  esac
fi

UI가 있는 모든 도구 스모크 테스트

mcp-inspector --cli "$URL" --transport http --method tools/list --app-info \
  | jq -r 'select(.hasApp) | .toolName'

연결 없이 카탈로그 검사

mcp-inspector --cli --catalog ~/.mcp-inspector/mcp.json --method servers/list
mcp-inspector --cli --catalog ~/.mcp-inspector/mcp.json --method servers/show --server my-server

servers/show는 비밀을 담는 필드(env 값, 민감한 헤더, OAuth 클라이언트 시크릿)를 가리지만, 서버 url(userinfo나 쿼리 토큰)이나 stdio args에 박힌 자격 증명은 지우지 않아요. 이슈에 붙여 넣기 전에 원시 URL과 detail 필드를 민감하게 다뤄야 해요.

프록시

원격 HTTP/SSE 서버로의 연결은 관례적인 프록시 변수를 존중해요. HTTPS_PROXY / HTTP_PROXY(소문자 형태도)는 프록시를 정하고, NO_PROXY는 호스트를 면제해요. Inspector 전용 플래그는 필요 없고, 프록시 에이전트는 느리게 로드되므로 프록시가 없는 실행은 비용이 없어요. 웹 클라이언트의 백엔드에도 똑같이 적용돼요.

더 알아보기 (Learn more)