목차
기존 API를 MCP로 연결하려면 외부에 보여줄 도구와 자료, 호출에 필요한 권한부터 나눠 설계해야 합니다. 특정 인프라나 구현 프레임워크가 아니라, 서버에 무엇을 공개하고 어떻게 제한할지에 초점을 둡니다.
MCP 서버에서 노출할 기능 구분
MCP 서버에서는 Tools, Resources, Prompts를 서로 다른 역할로 구분할 수 있습니다. 그중 Tools와 Resources를 중심으로 서버에 공개할 기능을 살펴봅니다.
무엇이든 Tool로 만들면 호출 대상이 늘어납니다. 정적인 데이터나 애플리케이션이 제공할 컨텍스트는 Resource로, 모델이 호출해 작업을 수행해야 하는 기능은 Tool로 분리하면 AI 클라이언트가 사용할 기능을 명확하게 구성할 수 있습니다. 조회 기능이라도 모델이 능동적으로 호출해야 한다면 Tool로 설계할 수 있습니다.
Tool 설계와 호출 흐름

Tool의 입력 스키마는 필수 인자, 허용되는 값, 문자열 형식과 작업 범위를 명확히 담아야 합니다. 반환 데이터도 AI가 해석하기 쉬운 구조로 구성하고, 성공 결과와 오류 결과의 형식을 구분하면 후속 자동화가 안정적으로 이어집니다.
Tool을 사용하는 기본 흐름은 연결, 사용 가능한 Tool 확인, Tool 호출, 결과 반환 순서로 진행됩니다. AI 클라이언트는 먼저 사용할 수 있는 기능을 확인한 뒤 인자를 전달하고, MCP 서버는 기존 API나 도구를 실행해 결과 또는 오류를 돌려줍니다. 인자가 잘못되면 실행 전에 검증하고, 외부 API가 실패하면 원인과 재시도 가능 여부를 구분해 결과에 담습니다.
작업 범위는 작게 유지하는 편이 안전합니다. 한 Tool이 여러 시스템을 한꺼번에 수정하지 않도록 나누고, 되돌릴 수 있는 작업과 삭제처럼 되돌리기 어려운 작업은 별도로 설계합니다.
실전 코드 예시
FastMCP(Python)로 위 원칙을 실제 코드에 적용하면 다음과 같습니다. 아래 예시는 파일을 조회하는 Tool 하나를 구현합니다 — 입력 스키마를 명확히 하고, 성공과 오류 결과를 구분해서 돌려줍니다.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("file-tools")
@mcp.tool()
def read_file_summary(path: str, max_lines: int = 50) -> dict:
"""지정한 경로의 텍스트 파일에서 앞부분 일부를 읽어 반환합니다."""
if not isinstance(path, str) or not path:
return {"ok": False, "error": "path는 비어 있지 않은 문자열이어야 합니다."}
if max_lines <= 0 or max_lines > 500:
return {"ok": False, "error": "max_lines는 1~500 사이여야 합니다."}
try:
with open(path, "r", encoding="utf-8") as f:
lines = []
for _ in range(max_lines):
line = f.readline()
if not line:
break
lines.append(line)
except FileNotFoundError:
return {"ok": False, "error": f"파일을 찾을 수 없습니다: {path}"}
return {"ok": True, "path": path, "lines": lines}
이 예시는 앞서 설명한 세 가지를 그대로 반영합니다. 입력 스키마는 path(문자열, 필수)와 max_lines(정수, 기본값 50)로 명확히 제한했고, 잘못된 인자는 실행 전에 걸러 ok: False로 돌려줍니다. 파일이 없는 경우처럼 실행 중 실패도 예외를 그대로 노출하지 않고 같은 형식(ok, error)으로 감싸 AI 클라이언트가 성공과 실패를 일관되게 해석할 수 있게 했습니다. 이 Tool은 읽기 전용이지만, 예시 그대로라면 프로세스가 읽을 수 있는 임의 경로를 열 수 있어 비밀 파일도 노출할 수 있습니다. 신뢰할 수 없는 클라이언트에 그대로 공개하지 말고 인증·인가와 접근 가능한 디렉터리를 제한해야 합니다. 경로 제한을 구현할 때는 상대 경로와 심볼릭 링크가 허용 범위 밖을 가리키는 경우도 확인하세요. 파일을 쓰거나 지우는 Tool은 권한과 승인 절차를 별도로 설계해야 합니다. 예시는 파일 없음만 처리하므로 권한 오류와 잘못된 인코딩 등도 실제 서비스에서 처리해야 합니다.
경로 문자열과 실제 경로의 차이
네트워크 없는 격리 Python에서 가상 허용 폴더 allowed와 형제 폴더 allowed-other/file.txt를 비교했습니다. 단순 문자열 startswith는 이름이 비슷해서 True였지만, 정규화한 경로의 is_relative_to는 False였습니다. 예시 코드는 다음과 같습니다.
from pathlib import Path
base = Path("/tmp/allowed").resolve()
candidate = Path("/tmp/allowed-other/file.txt").resolve()
print(str(candidate).startswith(str(base))) # True
print(candidate.is_relative_to(base)) # False
Python 경로 문서의 resolve()는 ..과 심볼릭 링크를 정리합니다. 이 확인만으로 안전한 파일 열기가 보장되는 것은 아닙니다. 검사와 열기 사이에 경로가 바뀔 위험도 있으므로 파일 접근 권한과 격리 등 별도 보호가 필요합니다. 위 실험은 가상 경로 비교이며 MCP 서버를 외부에 공개하거나 보안을 검증한 결과가 아닙니다.
권한·안전성·자동화 연결
권한은 MCP 서버가 접근할 파일, 데이터베이스, API 범위를 기준으로 제한합니다. 읽기 전용 작업과 수정 작업을 분리하고, 삭제·결제·대량 변경처럼 민감한 작업은 별도 승인이나 추가 제한의 대상으로 둡니다. MCP 서버 자체가 권한을 자동으로 안전하게 만들어 주는 것은 아니므로, 인증·인가와 감사 기록은 연결된 시스템에서도 확인해야 합니다.
자동화 시스템과 연결할 때는 Tool이 너무 많아 선택 오류가 생기거나, 같은 작업이 중복 호출될 수 있습니다. 긴 작업은 상태 조회용 Tool을 따로 두고 중복 방지 키를 이용할 수 있습니다. 다만 요청 식별자를 기록하는 것만으로 중복 실행이 자동으로 막히지는 않습니다. 수신 시스템이 같은 작업의 키와 입력·처리 상태를 함께 확인하고 중복 요청을 안전하게 처리해야 합니다. Stripe의 중복 방지 요청 안내는 같은 키의 결과 보관, 다른 입력으로 재사용할 때의 오류, 키 보관 기간을 설명합니다. 외부 API도 중복 방지를 지원하는지와 보관 기간·실패 후 상태 확인 방법을 별도로 점검하세요.
구조는 MCP 서버, AI 클라이언트, 기존 API·도구, 자동화 시스템의 네 계층으로 나눌 수 있습니다. 먼저 읽기 전용 Tool로 흐름을 검증한 뒤 수정 기능을 추가하면, 잘못된 인자나 외부 API 실패가 실제 데이터 변경으로 이어지는 범위를 줄일 수 있습니다.
결론
MCP 서버 설계의 핵심은 기능을 많이 공개하는 것이 아니라, Resource와 Tool을 구분하고 작은 작업 단위·명확한 스키마·제한된 권한으로 호출 흐름을 통제하는 것입니다.