“문서 서버와 일정 서버에 모두 search가 있다. 두 서버를 연결하자 엉뚱한 검색이 실행되는데, 도구 설명을 고치면 해결될까?” 먼저 호스트가 두 도구를 서로 다른 실행 대상으로 보관하는지 확인한다. 모델이 고르는 이름, 연결을 식별하는 키, 실제 서버로 보내는 원래 도구 이름이 한 쌍으로 이어져야 한다. 설명을 길게 쓰는 것만으로 잘못된 연결 경로를 바로잡을 수는 없다.
적용 기준은 2026년 10월 13일 KST에 확인한 MCP revision 2026-07-28이다. 공식 버전 안내는 이를 현재 revision으로 명시한다. 여러 MCP 서버를 합치는 호스트·프록시 개발자를 위한 진단이며, 특정 제품의 설정 키나 SDK API를 설명하지 않는다. 아래 연결 이름과 검색 자료는 모두 가상이다.
같은 이름이 항상 오류인 것은 아니다
공식 Tool Names 설명에서 도구 이름의 유일성 범위는 서버 하나다. 서로 다른 서버가 각각 search를 제공할 수 있다. 여러 서버의 도구를 합치는 클라이언트나 프록시는 접두어 같은 구별 방식을 두도록 권고된다. 서버가 알려주는 serverInfo.name 역시 서버 간에 유일하다고 보장되지 않는다.
따라서 화면에 보이는 서버 이름을 붙였다는 사실만으로 충돌 방지가 끝나지는 않는다. 이 글에서는 호스트가 관리하는 연결별 고유 키와 원래 도구 이름을 함께 저장하는 설계를 제안한다. 이 키와 별칭의 구체적인 형식은 MCP 표준 필드가 아니다.
증상으로 고장 난 지점을 좁힌다
| 관측한 증상 | 먼저 비교할 것 | 아직 단정하면 안 되는 것 |
|---|---|---|
| 각 서버에는 search가 있는데 합친 목록에는 하나뿐이다 | 목록을 합칠 때 이름을 키로 덮어쓰는지 | 모델이 도구를 일부러 무시했다고 단정 |
| 별칭은 두 개인데 둘 다 같은 서버로 간다 | 별칭 → 연결 키 → 원래 이름의 매핑 | 접두어만 붙이면 라우팅도 자동으로 달라진다고 가정 |
| 원하는 서버에 도착했지만 Unknown tool이 난다 | 실제 요청의 params.name | 호스트 별칭을 원래 서버도 안다고 가정 |
| 경로가 맞는데 검색 자료가 다르다 | arguments와 해당 연결의 허용 범위 | 이름 충돌이 모든 오답의 원인이라고 단정 |
진단 순서: 원본 두 개와 합친 목록을 대조한다
1. 서버별 원본을 따로 읽는다. 각 연결에서 얻은 도구의 name, 설명, inputSchema를 나란히 적는다. 첫 페이지만 받아 놓고 도구가 사라졌다고 판단하지 않는다. 목록 수집 문제는 nextCursor 페이지네이션 점검을 먼저 따른다.
2. 호스트가 모델에 보여주는 목록을 읽는다. 같은 이름이 몇 개인지보다 각 항목을 어느 연결로 돌려보낼 수 있는지가 중요하다. 서로 다른 설명이 남아 있어도 내부 딕셔너리에서 실행 함수가 덮어써질 수 있다. 이는 점검할 설계 위험이지 여기서 발견한 실제 결함은 아니다.
3. 한 번의 호출 경로를 끝까지 따라간다. 모델이 선택한 별칭, 호스트가 선택한 연결 키, 서버로 내보낸 원래 도구 이름을 대조한다. MCP 요청과 응답의 id는 요청을 응답과 연결하는 값이다. 같은 id가 보인다는 사실만으로 올바른 서버를 선택했다는 증거가 되지는 않는다.
4. 안전한 자료로 다시 확인한다. 승인된 읽기 전용 시험 자료를 연결마다 하나씩 준비한다. 한쪽에는 문서 표식, 다른 쪽에는 일정 표식을 넣고 결과가 섞이지 않는지 관찰한다. 실서비스 고객 자료나 인증값을 진단 로그에 복사하지 않는다. 읽기 전용이라는 이름만 믿지 말고 실제 동작과 권한도 확인한다.
복사해서 검토할 수 있는 매핑 예시
다음 JSON은 호스트 내부 설계용 가상 자료다. 실제 MCP 설정 파일에 그대로 넣는 형식이 아니며, docs_01과 calendar_02는 미리 승인된 연결을 가리키는 내부 키다. 주소나 인증정보를 모델이 임의로 지정하게 하지 않는다.
{
"docs_search": {
"connection_key": "docs_01",
"upstream_name": "search"
},
"calendar_search": {
"connection_key": "calendar_02",
"upstream_name": "search"
}
}
호스트가 docs_search를 받았다면 매핑된 문서 연결에 search를 보낸다. 도구 이름 앞의 접두어를 문자열 자르기로 떼는 대신, 등록 때 만든 정확한 매핑을 찾는다. 단, 상위 프록시 자체가 별칭 이름으로 도구를 공개하는 구조라면 그 프록시와의 계약을 따라야 한다.
def resolve_tool(alias, routes):
if alias not in routes:
raise ValueError("Unknown tool alias")
route = routes[alias]
return route["connection_key"], route["upstream_name"]
routes = {
"docs_search": {"connection_key": "docs_01", "upstream_name": "search"},
"calendar_search": {"connection_key": "calendar_02", "upstream_name": "search"},
}
assert resolve_tool("docs_search", routes) == ("docs_01", "search")
assert resolve_tool("calendar_search", routes) == ("calendar_02", "search")
이 함수는 별칭 조회만 보여준다. 네트워크 호출, 인자 스키마 검사, 인증, 권한 검증과 중복 등록 차단은 구현하지 않았다. 알 수 없는 별칭을 첫 번째 서버로 보내는 기본 동작도 두지 않았다. 등록 단계에서는 이미 쓰는 별칭을 새 도구가 덮어쓰지 않도록 별도로 거부해야 한다.
이름을 고친 뒤에도 확인할 조건
별칭 변경은 기존 대화에 저장된 도구 호출, 캐시된 목록, 호출 허용 목록과 함께 검토한다. 어떤 호스트는 새 대화나 목록 갱신이 필요할 수 있지만, 이 글은 특정 제품에 그런 동작이 있다고 보증하지 않는다. 대상 호스트에서 이전 별칭이 남는 위치를 실제로 관찰해야 한다.
권한은 별칭과 분리한다. 익숙한 이름이 붙어 있어도 다른 계정의 연결로 바뀌었다면 같은 작업이라고 볼 수 없다. 서버의 표시 이름을 흉내 낸 연결을 믿거나, 이름을 고치기 위해 접근 범위를 넓혀서는 안 된다. 도구 annotations의 읽기 전용 힌트도 실제 권한 검증을 대신하지 않는다.
무엇을 확인했고, 무엇은 남아 있는가
이 글의 공식 규격 설명은 위 문서를 직접 대조했다. 매핑 예시는 Python 3.12.14의 표준 실행 환경에서 두 별칭의 연결 분리와 알 수 없는 별칭 거부를 확인했다. 이는 로컬 함수 검사이며 MCP 서버 연결이나 특정 호스트의 장애 재현·수정 성공을 뜻하지 않는다. 실제 사용 환경에서는 서버별 목록 수집, 합친 목록 보존, 최종 요청 경로와 결과 자료까지 각각 확인해야 한다.
완료 기준은 “이름이 두 개 보인다”가 아니다. 두 별칭이 각각 의도한 연결과 원래 이름으로 전달되고, 허용된 시험 자료의 결과가 서로 섞이지 않는 것이다. 이 증거가 없으면 이름 변경은 수정 후보로 남겨 둔다.