Ahrefs MCP Server: Claude, Codex 및 기타 도구를 위한 설정
요약
Ahrefs의 MCP 서버를 Claude 및 기타 AI 클라이언트에 올바르게 연결하는 방법을 설명합니다. API v3 키와 MCP 스코프 키의 차이점을 명확히 하여 설정 오류를 방지하는 가이드를 제공합니다.
핵심 포인트
- Ahrefs 원격 MCP 서버는 반드시 MCP 스코프 키를 사용해야 함
- 기존 API v3 키를 사용하면 연결 시 오류 없이 작동하지 않음
- 원격 서버는 Streamable HTTP 방식을 사용하여 MCP 사양을 준수함
- 사용 시 API 유닛 예산이 소모되며 호출 방식에 따라 과금됨
Ahrefs를 AI 클라이언트(AI client)에 연결할 때 가장 먼저 일어나는 일은 아무 일도 일어나지 않는다는 것입니다. 오류도 없고, 도구도 나타나지 않으며, 그저 연결된 것처럼 보이는 서버만 그 자리에 있을 뿐입니다. 저의 경우 원인은 아주 사소했습니다. Ahrefs는 두 가지 서로 다른 MCP 서버와 두 가지 종류의 API 키를 제공하는데, 가능한 네 가지 조합 중 오늘 당신이 원하는 것은 단 하나뿐입니다. 그런데 아무도 당신이 어떤 것을 선택했는지 알려주지 않습니다.
이 가이드가 존재하는 이유에 대한 짧은 버전입니다. 더 긴 버전은 제가 구독 주기 동안 이 시스템을 통해 약 1,100회의 로그 호출(logged calls)을 실행하며 시간을 허비했다는 것인데, 제 시간을 가장 많이 뺏은 것은 SEO 분석이 아니라 바로 배관 작업(plumbing)이었습니다.
실제로 당신이 연결하고 있는 것
Ahrefs는 https://api.ahrefs.com/mcp/mcp에서 호스팅된 MCP 서버를 운영합니다. 이 서버는 Streamable HTTP를 사용하는데, 이는 현재 Model Context Protocol (MCP) 사양의 전송 방식이며 모든 진지한 클라이언트가 지원하는 방식입니다. SSE는 지원이 중단(deprecated)되었으므로 이를 기반으로 새로운 것을 구축해서는 안 됩니다.
해당 엔드포인트 뒤에는 평소 Ahrefs 웹 앱에서 클릭하며 사용하던 기능 대부분이 자리 잡고 있습니다. 백링크와 유기적 키워드(organic keywords)를 위한 Site Explorer, 검색량과 난이도를 위한 Keywords Explorer, Rank Tracker, Site Audit, 그리고 계정을 연결했다면 Google Search Console 연동 기능 등이 포함됩니다. 제 사례의 경우 호출 가능한 도구가 130개에 달했는데, 이는 서버가 계속 성장하고 있기 때문에 마케팅 페이지에서 주장하는 것보다 더 많은 수치입니다.
연결을 설정하기 전에 알아두어야 할 두 가지 사항이 있습니다. 액세스는 Lite 플랜부터 시작되므로, 무료 체험 계정으로는 접속할 수 없습니다. 또한, 모든 유료 호출은 일반적인 API v3 사용과 동일한 월간 API 유닛 예산에서 차감됩니다. 즉, 당신의 채팅 어시스턴트와 크론 잡(cron jobs)이 하나의 접시에서 음식을 나누어 먹는 것과 같습니다. 과금 방식은 세 가지로 나뉩니다. 상당수의 엔드포인트는 비용이 들지 않고, 일부는 요청당 고정 요금을 부과하며, 나머지는 행(row)당 요금을 부과합니다. 마지막 섹션에서는 이들을 구분하는 방법에 대해 다룹니다.
두 개의 서버, 두 가지 키 유형, 하나의 침묵하는 실패
이 부분이 저의 첫 저녁 시간을 낭비하게 만든 대목입니다.
npm에 @ahrefs/mcp로 게시되어 있고 GitHub의 ahrefs/ahrefs-mcp-server에 호스팅되어 있는 오래된 로컬 서버가 있습니다. 해당 저장소는 현재 아카이브(archived)되었으며, 그 README에는 두 번 읽어볼 만한 가치가 있는 문장이 적혀 있습니다: 이 서버는 API v3 키로만 작동하며, MCP 키로는 작동하지 않는다는 내용입니다.
호스팅된 원격(remote) 서버는 그 반대입니다. 이 서버는 Ahrefs 계정에서 별도로 생성해야 하는 MCP 스코프(scope)를 가진 키를 요구합니다. Ahrefs는 API 키와 MCP 키가 서로 호환되지 않는다고 명확히 밝히고 있습니다.
따라서 매트릭스(matrix)는 다음과 같습니다. 두 개의 셀이 작동하지만, 오늘날 합리적인 선택지는 단 하나뿐입니다:
| API v3 키 | MCP 스코프 키 | |
|---|---|---|
로컬 @ahrefs/mcp | 작동함, 현재 저장소 아카이브됨 | 실패 |
원격 /mcp/mcp | 실패 | 정답 |
아카이브된 조합은 고장 난 것이라기보다 버려진 것에 가깝습니다. 이미 가지고 있다면 여전히 실행되지만, 유지보수가 이루어지지 않으며 Ahrefs는 대신 원격 서버를 사용하도록 안내합니다.
실패 모드(failure modes)는 조용하게 발생합니다. REST API를 향한 MCP 스코프 키는 Unauthorized를 반환하며, 이는 최소한 무엇이 잘못되었는지 알려줍니다. 하지만 핸드셰이크(handshake)를 완료할 수 없는 클라이언트는 종종 도구가 0개인 서버를 보여줄 뿐이며, 사용자는 존재하지도 않는 설정 오타를 찾아 헤매게 됩니다.
오늘 설정을 진행 중이라면, 원격 서버를 사용하고 MCP 스코프 키를 생성하세요. 무엇인가를 npm install 하라는 모든 튜토리얼은 무시하십시오.
인증: OAuth는 공식적인 경로이고, Bearer는 유용한 경로이다
Ahrefs는 OAuth를 진입 방법으로 문서화하고 있습니다. 클라이언트가 브라우저 창을 열면 사용자가 로그인하고, 클라이언트가 자격 증명(credentials)을 캐싱하는 방식입니다. 대화형(interactive) 작업에는 이 방식이 괜찮으며, 실제로 가장 번거롭지 않은 옵션입니다.
일요일 오전 7시에 데이터를 가져오는 예약된 작업(scheduled job)을 실행하려고 하는 순간 상황이 까다로워집니다. OAuth는 초기 인증(authorisation)을 위해 브라우저를 사용하는 사람이 필요하며, 그 이후에는 사람의 개입 없이 계속 작동해야 하는 토큰 갱신(token refresh)을 관리해야 합니다. 따라서 헤드리스(headless) 환경의 모든 작업에 대해서는 대신 베어러 토큰(bearer token)으로 인증하며, Authorization 헤더에 MCP 키를 직접 전달합니다. 엔드포인트(endpoint)와 도구(tools)는 동일하지만, 브라우저가 필요 없고 갱신할 것도 없습니다.
제가 정한 실질적인 규칙은 다음과 같습니다: 제가 없어도 유지되어야 하는 모든 것에는 베어러(bearer)를, 제가 앞에 앉아 있는 노트북에는 OAuth를 사용합니다. 만약 이 기능을 채팅에서만 사용한다면, OAuth 프롬프트를 따르고 다음 몇 섹션은 건너뛰셔도 됩니다.
Claude Code
명령어 하나로, --scope 플래그를 통해 서버를 현재 프로젝트에 둘지 또는 사용자 설정(user config)에 둘지 결정할 수 있습니다.
claude mcp add --transport http ahrefs https://api.ahrefs.com/mcp/mcp \
--header "Authorization: Bearer $AHREFS_API_KEY" -s project
프로젝트 범위(Project scope)는 코드 옆의 .mcp.json 파일에 기록되며, 이는 키가 특정 클라이언트나 특정 사이트에 속해 있을 때 올바른 선택입니다. 헤더가 평문(plain text)으로 저장되므로, 키를 붙여넣기 전에 해당 파일을 .gitignore에 추가하십시오.
여기서 제 시간을 낭비하게 만든 두 가지 사항이 있습니다. 첫째, 도구들이 ahrefs.<toolname>이 아니라 mcp__ahrefs__<toolname> 형태로 나타나는데, 이는 도구 이름을 명시적으로 지정하는 프롬프트를 작성할 때 중요합니다. 둘째, 실행 중인 세션은 시작 시에만 MCP 서버를 로드하므로, 방금 추가한 연결은 현재 세션이 아닌 다음 세션부터 나타납니다. 저는 설정이 잘못되었다고 확신하며 세 번이나 재시작했습니다.
Claude Desktop
설정 파일이 필요하지 않습니다. Settings, Connectors, Add custom connector 순으로 이동한 다음 엔드포인트 URL을 붙여넣으세요. 서버에 필요한 경우 OAuth 클라이언트 ID(client ID)와 비밀번호(secret)는 Advanced settings 항목에 입력합니다.
여기에는 사람들을 놀라게 하고 연결할 수 있는 대상을 변화시키는 한 가지 아키텍처(architectural) 세부 사항이 있습니다. Claude Desktop은 사용자의 로컬 머신에서 MCP 서버로 직접 연결되는 것이 아닙니다. Anthropic의 클라우드 인프라(cloud infrastructure)에서 서버로 연결됩니다. Ahrefs와 같은 호스팅 서비스(hosted service)의 경우에는 전혀 문제가 되지 않습니다. 하지만 자신의 노트북이나 회사 VPN 뒤에서 실행되는 서버의 경우에는 모든 것이 달라집니다. 왜냐하면 해당 서버가 작동하려면 반드시 공용 인터넷(public internet)에서 접근 가능해야 하기 때문입니다.
무료 계정은 커스텀 커넥터(custom connector)가 한 개로 제한됩니다. 유료 티어(Paid tiers)는 제한이 없습니다.
Codex
Codex는 전역적으로 ~/.codex/config.toml을 읽거나, 신뢰할 수 있다고 표시한 프로젝트 디렉토리 내의 .codex/config.toml을 읽습니다. 서버당 하나의 TOML 테이블을 사용하며, 전송 방식(transport)은 설정된 키에 따라 추론됩니다. command 키는 stdio를 의미하고, url 키는 Streamable HTTP를 의미합니다.
[mcp_servers.ahrefs]
url = "https://api.ahrefs.com/mcp/mcp"
bearer_token_env_var = "AHREFS_API_KEY"
bearer_token_env_var가 무엇을 받는지 주의하십시오. 이는 토큰 자체가 아니라 환경 변수(environment variable)의 이름입니다. 여기에 키를 직접 작성하면 비밀 정보(secret)로 가득 찬 설정 파일과 아무 내용도 없는 서버를 갖게 될 것입니다.
codex mcp add 하위 명령(subcommand)이 존재하지만, 이는 stdio 서버를 중심으로 설계되었습니다. 따라서 원격 엔드포인트(remote endpoint)의 경우 TOML을 직접 편집하는 것이 더 빠르고 버전 관리(version control)에 넣기도 더 쉽습니다. codex mcp list로 확인하십시오.
Cursor, VS Code 및 Windsurf
세 도구 모두 동일한 JSON 방언(dialect)을 사용하지만, 한 가지 짜증 나는 차이점이 있습니다. 바로 URL을 담는 키(key) 이름입니다. Cursor는 이를 url이라고 부릅니다. VS Code는 Claude Code가 사용하는 것과 동일한 형태인 명시적인 "type": "http"와 함께 url을 요구합니다. Windsurf는 이를 serverUrl이라고 부릅니다. 블록의 다른 모든 부분은 변경 없이 그대로 복사되므로, 한 에디터에서 작동하는 설정은 이름만 바꾸면 30초 안에 다음 에디터에서도 작동하게 만들 수 있습니다.
VS Code는 1.99 버전부터 Copilot Chat을 통해 네이티브 MCP 지원을 제공해 왔습니다. Windsurf는 올해 초에 이를 추가했습니다. 만약 팀원들이 서로 다른 에디터를 사용하고 있다면, 블록을 한 번만 작성한 뒤 세 가지 변형을 스니펫(snippet) 어딘가에 보관해 두세요. 조만간 다시 필요하게 될 것이기 때문입니다.
클라이언트가 전혀 없는 헤드리스 (Headless) 방식
MCP는 단순한 REST 호출이 아니라 세션 프로토콜 (session protocol)입니다. 초기화(initialize)를 수행하고, 초기화 알림 (initialized notification)을 보낸 후에야 비로소 도구 (tool)를 호출할 수 있습니다. 핸드셰이크 (handshake) 이후의 모든 요청에는 반환받은 세션 ID (session id)가 포함되어야 합니다.
curl -sD hdr -X POST "$AHREFS_MCP_URL" \
-H "Authorization: Bearer $AHREFS_API_KEY" \
-H "Content-Type: application/json" \
...
이 과정에서 실수하기 쉬운 세 가지가 있습니다. 두 번째 호출을 건너뛰지 마세요. 세션 ID만으로는 핸드셰이크가 완료되지 않으며, 초기화 알림을 받지 못한 서버는 도구 호출을 거부할 것입니다. Accept 헤더에는 스트림 (stream)을 읽을 의도가 없더라도 두 가지 콘텐츠 타입 (content types)이 모두 포함되어야 합니다. 그리고 2025-06-18 수정 버전부터는 초기화 이후의 모든 요청에 MCP-Protocol-Version 헤더를 반드시 포함해야 하므로, mcp-session-id와 함께 알림 및 이후의 모든 도구 호출에 포함되어야 합니다.
이를 작은 셸 헬퍼 (shell helper)로 감싸는 데 20분을 투자할 가치가 있습니다. 그렇게 하면 채팅 인터페이스가 전혀 없는 크론 잡 (cron jobs)이나 에이전트 (agents)에서도 동일한 데이터를 사용할 수 있기 때문입니다. 경험에서 우러나온 한 가지 경고를 드리자면, 만약 JSON 인자에 대해 ${ARG:-{}}와 같은 bash 기본값을 사용하여 헬퍼를 만든다면, 기본값 내부의 중괄호 매칭 문제로 인해 잘못된 닫는 중괄호가 몰래 추가되어 형식이 잘못된 JSON (malformed JSON)이 생성될 수 있습니다. 이 경우 호출은 아무런 출력이나 에러 없이 실패하게 됩니다. 변수 기본값 설정은 별도의 라인에서 수행하세요.
요금제에 따라 행 제한(Row Cap)이 결정되며, 이것이 비용이 많이 드는 부분입니다
이 섹션은 제가 가장 먼저 읽었어야 했던 부분입니다. 단 한 번의 아침 만에 한 달 치 예산을 날리게 만든 실수를 설명하고 있기 때문입니다.
Ahrefs는 구독 티어 (subscription tier)에 따라 두 가지를 제한합니다. 월별로 받을 수 있는 유닛 (units)의 수와 단일 요청이 반환할 수 있는 행 (rows)의 수입니다.
| 플랜 (Plan) | 월별 유닛 (Units per month) | 요청당 최대 행 (Max rows per request) |
|---|---|---|
| Lite | 100,000 | 100 |
| ... |
이 수치들은 2026년 4월 28일에 변경되었으며, 매우 크게 바뀌었습니다. Lite 플랜은 25,000 유닛에서 100,000 유닛으로, 10행에서 100행으로 늘어났습니다. Standard 플랜은 150,000 유닛에서 400,000 유닛으로, 25행에서 250행으로 변경되었습니다. Advanced 플랜은 유닛 수가 두 배로 늘어났으며, 100행에서 500행으로 증가했습니다.
이제 이 수치들을 가격 책정 방식과 대조해 보십시오. 호출(call) 한 번에는 최소 50 유닛이 소모되며, 그 이상의 비용은 요청한 열(column)의 수에 행(row) 수를 곱하여 지불합니다. volume, keyword_difficulty, traffic_domain과 같은 프리미엄 열(Premium columns)은 행당 각각 약 10 유닛씩을 추가로 부과합니다.
이 두 가지 사실을 결합하면 함정(trap)이 드러납니다. 제 전체 로그에서 가장 비용이 많이 들었던 호출은 250행의 제한(limit)으로 실행되었습니다. 이는 제가 고민 끝에 선택한 숫자가 아닙니다. 제가 사용 중이던 플랜의 행 제한(row cap) 수치였으며, 사용 가능한 최대치였기에 선택하게 된 것입니다. 해당 호출들은 각각 5,250 유닛의 비용이 들었습니다. 동일한 쿼리를 50행으로 실행했다면 1,050 유닛만 소모되었을 것이며, 51행부터 250행까지는 제가 전혀 사용하지 않는 롱테일 노이즈(long-tail noise)였기 때문에 동일한 정보를 제공했을 것입니다.
행 제한(row cap)은 권장 사항이 아닙니다. 그것은 천장(ceiling)이며, 4월 이후 그 천장은 이전보다 최대 10배 더 높게 설정되었습니다. 만약 당신의 코드가 명시적인 제한(explicit limit)을 전달한다면 이러한 변화는 무해합니다. 50은 여전히 50을 의미하기 때문입니다. 하지만 다음 세 가지 구체적인 상황에서는 치명적입니다. 제한을 전혀 전달하지 않을 때, 코드가 현재 가능한 최대치를 요청할 때, 그리고 이전의 제한 수치에 의해 잘렸던 쿼리가 이제는 10배 더 많은 행을 반환할 때입니다. 이 세 가지 상황은 모두 봄 이전에 작성된 스크립트에서 흔히 나타나며, 코드상으로는 전혀 차이가 없어 보입니다.
실제로 읽을 데이터만큼만 제한을 설정하십시오. 키워드 확장(keyword expansion)의 경우, 저는 이제 50에서 시작하며, 경계에서 결과가 눈에 띄게 잘리고 추가된 행이 중요할 때만 더 높게 설정합니다.
실행할 때마다 비용을 치르게 만든 10가지 함정
이 중 그 어떤 것도 문제를 맞닥뜨리기 전에는 문서에서 찾아볼 수 있는 방식으로 설명되어 있지 않습니다. 이 모든 것들은 제가 직접 해독해야 하는 에러를 발생시키거나, 더 나쁜 경우에는 마치 유의미한 결과인 것처럼 보이는 빈 결과(empty result)를 생성했습니다.
where는 JSON이며, 절대 문자열 표현식(string expression)이 아닙니다. "position>3 and position<15"라고 작성하면 bad where: invalid JSON syntax라는 에러가 반환됩니다. JSON 형식의 동일한 필터는 조건당 하나의 절(clause)이 필요합니다:
{"and":[{"field":"position","is":["gt",3]},
{"field":"position","is":["lt",15]}]}
연산자는 gt, gte, lt, lte, eq입니다.
select는 도구에 따라 타입이 변경됩니다. Site Explorer와 Keywords Explorer는 쉼표로 구분된 문자열(comma-separated string)을 요구합니다. 반면 batch-analysis는 배열(array)을 요구합니다. 이를 혼동하면 한쪽에서는 column '["domain"' not found 에러가 발생하고, 다른 쪽에서는 expected array but got string 에러가 발생합니다.
움라우트(umlauts)를 음차(transliterate)하지 마세요. 키워드 매칭은 리터럴(literal)하게 이루어집니다. ü 대신 ue로 작성된 독일어 용어는 결과 행이 0개로 반환되며, 마치 죽은 키워드처럼 보입니다. 스페인어의 틸데(tildes)도 마찬가지입니다.
국가 필터(country filter)는 조용히 진실을 삭제할 수 있습니다. 유기적 키워드(organic keywords)를 국가별로 필터링했을 때, 분명히 순위가 있는 도메인임에도 불구하고 결과가 0개로 나왔습니다. 해당 도메인의 순위가 다른 국가들에 걸쳐 있었기 때문입니다. 저는 빈 답변에 50 유닛(units)을 지불했고, 해당 도메인이 아무런 키워드로도 순위가 없다고 결론 내릴 뻔했습니다. 필터를 사용하지 말고 대신 keyword_country를 컬럼(column)으로 가져오세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기