본문 바로가기
Come on IT/참고용

MCP와 API 의 차이점 (feat. function calling)

by JONGSKY 2026. 2. 27.
728x90
반응형
SMALL

1. 작성하게 된 이유

 

요즘 LLM으로 무엇인가 만들어보면 외부 도구를 연결해야 하는 경우가 이제는 필수적으로 필요하게 된다.

과거 API를 사용했던 경우들 그리고 Fucntion calling 그리고 MCP 등등 많은 키워드들이 나오고 있는데

 

이를 확인해 보면 설명이 추상적이어서 이해가 되지 않거나

표준 프로토콜, USB-C 타입 등 다양한 키워드들이 나오게 되는데

개인적인 생각으로는 코드로 한 번 구현해 보는 것이 더 빠르게 배울 수 있다고 생각이 든다.

 

그래서 Python 코드 기준으로 API → Function Calling → MCP가 뭐가 다른 건지 나름대로 정리해 봤다.

 

혹시 정리 중에 잘못된 내용이 있다면 알려주시면 감사드리겠으며

이 글을 읽으면서 함께 도움이 되었으면 좋겠다는 생각으로 작성하게 되었습니다.

 

 

2. 우리가 이미 아는 것부터

 

 

개발자라면 API는 이미 익숙할 거다. REST API든 GraphQL이든, 핵심은 간단하다.

클라이언트가 요청 보내면, 서버가 응답 돌려주는 거.

 
import requests

# 날씨 API 호출 — 개발자가 직접 코드를 작성
response = requests.get("https://api.weather.com/v1/current", params={
    "city": "Seoul",
    "api_key": "my-secret-key"
})
weather = response.json()
print(f"서울 기온: {weather['temperature']}°C")
 
 

여기서 중요한 건, 내가 언제 어떤 API를 호출할지 결정한다는 거다.

if문을 쓰든, 버튼 클릭 이벤트에 연결하든, 호출의 주체는 항상 코드 작성한 사람이라는 것이다.

근데 LLM이 등장하면서 상황이 좀 달라졌다.

 

3. Function Calling — "LLM이 어떤 함수를 부를지 스스로 고른다"

 

ChatGPT나 Claude 같은 LLM한테 "서울 날씨 알려줘"라고 말하면, 모델이 알아서 적절한 함수를 골라 호출하는 것.

이게 Function Calling이다. (Anthropic에서는 Tool Use라고도 부른다)

 

OpenAI 스타일로 보면 이런 흐름이다

import openai

# 1단계: 모델에게 "쓸 수 있는 함수 목록"을 알려준다
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "특정 도시의 현재 날씨를 조회합니다",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "도시 이름"}
                },
                "required": ["city"]
            }
        }
    }
]

# 2단계: 사용자 메시지와 함께 모델에게 보낸다
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "서울 날씨 어때?"}],
    tools=tools
)

# 3단계: 모델이 "get_weather를 city='Seoul'로 호출하라"고 응답
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.function.name)       # "get_weather"
print(tool_call.function.arguments)  # '{"city": "Seoul"}'

# 4단계: 개발자가 실제 함수를 실행하고, 결과를 다시 모델에게 전달
# (이 부분은 우리가 직접 구현해야 한다)

 

 

Anthropic(Claude)에서는 같은 개념인데, 문법이 약간 다르다:

 
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=[
        {
            "name": "get_weather",
            "description": "특정 도시의 현재 날씨를 조회합니다",
            "input_schema": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "도시 이름"}
                },
                "required": ["city"]
            }
        }
    ],
    messages=[{"role": "user", "content": "서울 날씨 어때?"}]
)

 

 

Function Calling의 한계

이 방식, 잘 동작한다. 근데 프로젝트가 커지면 슬슬 문제가 생긴다.

 

첫째, 벤더 종속(Vendor Lock-in).

OpenAI의 tools 스키마랑 Anthropic의 tools 스키마가 미묘하게 다르다.

모델 바꾸면 함수 정의를 전부 다시 작성해야 된다.

 

둘째, 도구가 앱에 묶여 있다.

날씨 함수, DB 쿼리 함수, 슬랙 메시지 함수... 이걸 전부 하나의 앱 코드 안에서 정의하고 관리해야 한다.

도구가 10개, 20개로 늘어나면 JSON 스키마 관리만으로도 좀 고통스럽다.

 

셋째, 재사용이 어렵다.

내가 만든 날씨 함수를 다른 팀의 다른 AI 앱에서도 쓰고 싶으면?

코드를 복사-붙여 넣기 하거나, 별도의 라이브러리를 만들어야 한다. 번거로운 단점이 있다.

 

그림으로 보면 이런 모양이다

[Function Calling 방식 — N×M 문제]

  AI 앱 A ──┬── 날씨 API 연동 코드
            ├── DB 쿼리 연동 코드
            └── Slack 연동 코드

  AI 앱 B ──┬── 날씨 API 연동 코드  ← 또 작성!
            ├── DB 쿼리 연동 코드   ← 또 작성!
            └── GitHub 연동 코드

  AI 앱 C ──┬── Slack 연동 코드    ← 또 작성!
            └── GitHub 연동 코드   ← 또 작성!

 

앱이 N개, 서비스가 M개면, 최악의 경우 N×M개의 연동 코드가 필요하다.

 

 

4. MCP 등장 — "USB-C 같은 표준 프로토콜"

 

MCP(Model Context Protocol)는 Anthropic이 2024년 11월에 발표한 오픈 프로토콜이다.

한 마디로 요약하면, LLM 앱과 외부 도구/데이터를 연결하는 표준 규격이다.

 

USB-C 비유가 너무 많긴 하지만 이게 제일 직관적이긴 하다.

USB-C 나오기 전에는 폰마다 8핀 등등 충전 케이블이 달랐는데 USB-C라는 표준이 생기면서, 하나의 케이블로 거의 모든 기기를 충전할 수 있게 됐다. MCP가 AI 세계에서 하려는 게 딱 이거다.

 

[MCP 방식 — N+M 구조]

  AI 앱 A ──┐
  AI 앱 B ──┼── MCP 프로토콜 ──┬── 날씨 MCP 서버
  AI 앱 C ──┘                  ├── DB MCP 서버
                               ├── Slack MCP 서버
                               └── GitHub MCP 서버

 

 

각 서비스가 MCP 서버를 한 번만 만들면, 어떤 AI 앱이든 표준 프로토콜로 연결할 수 있다.

 

MCP의 아키텍처

MCP는 세 가지 역할로 구성된다:

  • Host: 사용자가 직접 쓰는 AI 앱 (Claude Desktop, VS Code Copilot, 직접 만든 챗봇 등)
  • Client: Host 안에 내장된 MCP 통신 모듈. 서버와의 1:1 연결을 관리
  • Server: 실제 도구/데이터를 제공하는 프로그램 (날씨 서버, DB 서버, GitHub 서버 등)

그리고 Server가 제공할 수 있는 건 세 가지다:

  • Tools: LLM이 실행할 수 있는 액션 (파일 생성, 이슈 등록, 쿼리 실행 등)
  • Resources: LLM이 읽을 수 있는 데이터 (파일 내용, DB 레코드, 설정값 등)
  • Prompts: 특정 작업에 최적화된 프롬프트 템플릿

 

5. 코드로 보는 차이 — 같은 작업, 다른 접근

 

"GitHub에 이슈를 생성하는" 작업을 세 가지 방식으로 비교해 본다면

 

방식 1: 전통적 API 호출 (개발자가 직접)

 
import requests

def create_github_issue(repo: str, title: str, body: str):
    """개발자가 직접 호출 시점을 결정"""
    response = requests.post(
        f"https://api.github.com/repos/{repo}/issues",
        headers={"Authorization": f"token {GITHUB_TOKEN}"},
        json={"title": title, "body": body}
    )
    return response.json()

# 내가 코드에서 직접 호출
issue = create_github_issue("myorg/myrepo", "버그 수정", "로그인 버튼 안 됨")

 

호출 시점, 파라미터, 에러 처리 등 모든 걸 내가 제어한다. 우리가 아는 그 방식이다.

 

 

방식 2: Function Calling (LLM이 결정, 내가 실행)

 
import anthropic

client = anthropic.Anthropic()

# 도구 정의를 앱 코드에 포함
tools = [{
    "name": "create_github_issue",
    "description": "GitHub 저장소에 새 이슈를 생성합니다",
    "input_schema": {
        "type": "object",
        "properties": {
            "repo":  {"type": "string", "description": "owner/repo 형식"},
            "title": {"type": "string", "description": "이슈 제목"},
            "body":  {"type": "string", "description": "이슈 본문"}
        },
        "required": ["repo", "title", "body"]
    }
}]

# 모델이 사용자의 자연어를 해석해서 도구 호출을 결정
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=[{
        "role": "user",
        "content": "myorg/myrepo에 '로그인 버그' 이슈 만들어줘"
    }]
)

# 모델이 tool_use 블록을 반환하면, 내가 직접 실행
for block in response.content:
    if block.type == "tool_use":
        # 여기서 실제 GitHub API를 호출하는 건 내 코드의 몫
        result = create_github_issue(**json.loads(block.input))

 

 

LLM이 "무엇을 호출할지" 결정하지만, 도구 정의와 실행 로직은 여전히 내 앱 안에 있다.

 

방식 3: MCP (도구가 독립 서버로 분리)

여기서부터가 좀 다르다. GitHub MCP 서버가 별도 프로세스로 실행된다.

Python의 mcp 라이브러리(FastMCP)를 사용해 보자

 

# github_mcp_server.py — 별도의 프로세스로 실행되는 MCP 서버

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("github-tools")

@mcp.tool()
def create_github_issue(repo: str, title: str, body: str) -> dict:
    """GitHub 저장소에 새 이슈를 생성합니다.

    Args:
        repo: owner/repo 형식의 저장소 이름
        title: 이슈 제목
        body: 이슈 본문
    """
    import requests
    response = requests.post(
        f"https://api.github.com/repos/{repo}/issues",
        headers={"Authorization": f"token {GITHUB_TOKEN}"},
        json={"title": title, "body": body}
    )
    return response.json()

@mcp.resource("github://{repo}/issues")
def list_issues(repo: str) -> str:
    """저장소의 이슈 목록을 반환합니다"""
    import requests
    response = requests.get(
        f"https://api.github.com/repos/{repo}/issues",
        headers={"Authorization": f"token {GITHUB_TOKEN}"}
    )
    issues = response.json()
    return "\n".join(f"#{i['number']}: {i['title']}" for i in issues)

if __name__ == "__main__":
    mcp.run(transport="stdio")

 

그리고 AI 앱(Host)은 이 서버에 그냥 연결하기만 하면 된다

 

# my_ai_app.py — MCP 클라이언트를 사용하는 AI 앱

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    # MCP 서버에 연결
    server_params = StdioServerParameters(
        command="python",
        args=["github_mcp_server.py"]
    )

    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # 서버가 어떤 도구를 제공하는지 자동으로 발견!
            tools = await session.list_tools()
            print(f"사용 가능한 도구: {[t.name for t in tools.tools]}")

            # 도구 호출
            result = await session.call_tool(
                "create_github_issue",
                arguments={
                    "repo": "myorg/myrepo",
                    "title": "로그인 버그",
                    "body": "로그인 버튼 클릭 시 404 에러 발생"
                }
            )
            print(result)
 

 

핵심적인 차이는

 

 

  • 도구 정의와 실행 로직이 내 앱 밖에 있다. MCP 서버라는 독립 프로세스에 살고 있다.
  • 도구 발견(Discovery)이 자동이다. list_tools() 호출하면, 서버가 "나는 이런 도구를 제공해~"라고 알려준다. JSON 스키마를 앱 코드에 하드코딩할 필요가 없다..!
  • 서버를 바꿔도 앱 코드는 그대로다. 동일한 tool contract(이름, 파라미터, 반환 구조)를 유지한다면 서버를 교체하더라도 앱 코드 수정 없이 동작할 수 있다.

 

6. 비교표 정리

 

구분 전통적 API  Function Calling MCP
호출 주제 개발자 코드 LLM이 결정, 개발자가 실행 LLM이 결정, MCP 서버가 실행
도구 정의 위치 해당 없음 앱 코드 안 (JSON 스키마) MCP 서버 (독립 프로세스)
도구 발견 개발자가 문서 읽고 직접 연동 모델에게 스키마를 매번 전달 list_tools()로 자동 발견
재사용성 라이브러리/SDK 수준 앱마다 재정의 필요 한 번 만든 서버를 어디서든 연결
적합한 상황 일반 소프트웨어 개발 단일 LLM 앱, 빠른 프로토타이핑 멀티 도구, 멀티 모뎁, 프로덕션

 

 

7.  그래서 Function Calling 안 쓰면 되나요?

 

아니다.

Function Calling이 모델의 “도구 선택 능력”이라면,
MCP는 그 도구들을 외부에서 표준화된 방식으로 제공하고 실행하기 위한 통신 프로토콜이다.

 

이거 좀 중요한 포인트라서 따로 정리해 보면 실제 동작 흐름을 보면 이렇다

 

사용자: "myorg/myrepo에 이슈 만들어줘"
         │
         ▼
    ┌─────────┐
    │  LLM    │  ← 여전히 Function Calling 메커니즘으로
    │         │    "어떤 도구를 쓸지" 결정
    └────┬────┘
         │ tool_use: create_github_issue
         ▼
    ┌─────────┐
    │  MCP    │  ← MCP가 해당 도구를 가진 서버를 찾아서
    │ Client  │    표준 프로토콜로 요청을 전달
    └────┬────┘
         │ JSON-RPC 2.0
         ▼
    ┌─────────┐
    │  MCP    │  ← 서버가 실제 GitHub API를 호출하고
    │ Server  │    결과를 반환
    └─────────┘

 

 

 

LLM의 Function Calling은 여전히 핵심 메커니즘이다.

MCP는 그 함수 호출이 어디에 정의되어 있고, 어떻게 실행되고, 어떻게 발견되는지를 표준화한 거다.

쉽게 말하면, Function Calling이 "LLM이 함수를 고르는 능력"이라면, MCP는 "그 함수들을 체계적으로 관리하고 연결하는 인프라"다.

 

 

8. 언제 뭘 쓰면 될까?

 

Function Calling으로 충분한 경우:

  • LLM 제공자가 하나이고, 바꿀 계획이 없다
  • 도구가 3~5개 이하로 단순하다
  • 빠르게 프로토타입 만들고 싶다
  • 개인 프로젝트이거나 단일 앱이다

MCP를 도입할 때:

  • 여러 AI 앱에서 같은 도구를 공유해야 한다
  • 도구가 많아져서 JSON 스키마 관리가 버거워졌다
  • LLM 제공자를 바꾸거나, 여러 모델을 동시에 사용한다
  • 팀 단위로 도구를 개발/배포하고 싶다
  • 보안과 권한 관리를 체계적으로 하고 싶다

 

참고로 이 두 가지를 섞어서 쓰는 것도 된다.

간단한 도구는 Function Calling으로 직접 정의하고, 복잡하거나 공유가 필요한 도구는 MCP 서버로 분리하는 식이다.

현실적으로 이 패턴이 제일 많이 쓰이는 것 같다.

 

9. 현재 MCP 생태계 (2025~2026년 기준)

 

MCP가 생각보다 빠르게 업계 표준으로 자리 잡고 있다. 간단히 정리해 보면

Anthropic이 2024년 11월 처음 발표한 이후, OpenAI가 2025년 3월 공식 채택했고

Google DeepMind도 Gemini에서 지원을 확인했다.

 

2025년 12월에는 Anthropic이 MCP를 Linux Foundation 산하의 Agentic AI Foundation(AAIF)에 기증하면서

특정 회사의 프로토콜이 아니라 진짜 오픈 표준이 됐다.

 

MCP는 빠르게 확산 중이며, 주요 AI 도구에서 클라이언트 지원을 시작했다.

Claude, ChatGPT, Cursor, Gemini, VS Code 등 주요 AI 도구에서 MCP 클라이언트를 기본 지원한다.

Python 개발자라면 FastMCP 라이브러리가 가장 쉬운 시작점이다.

데코레이터 기반으로 MCP 서버를 빠르게 만들 수 있어서, Flask로 REST API 만드는 것과 비슷한 느낌으로 개발할 수 있다.

 

 

10. 마무리

세 개념의 관계를 한 문장으로 정리하면 이렇다!

API는 소프트웨어 간 통신의 기본 방식이고,
Function Calling은 LLM이 어떤 API를 쓸지 스스로 결정하게 하는 메커니즘이고,
MCP는 그 도구들을 표준화된 방식으로 발견·연결·관리하는 프로토콜이다.

728x90
반응형
LIST