1인 SaaS 아키텍처에서 AI 에이전트 핵심 노드가 MCP 서버를 거쳐 데이터베이스 및 API와 연동되는 3D 네트워크 일러스트

1인 개발자를 위한 MCP 도입 실전 가이드: 2026년 무상태 명세, Python SDK v2 예제, 90일 로드맵

혼자서 제품을 만들고 운영하는 1인 개발자에게 AI 에이전트는 이제 단순한 보조 도구를 넘어 운영 업무 일부를 실제로 맡는 존재가 되었습니다. 문제는 프로덕션 서비스에 에이전트를 연결하는 순간부터 시작됩니다. 데이터베이스, 외부 API, 문서 저장소를 붙일 때마다 LLM 제공사(OpenAI, Anthropic 등)나 프레임워크별로 연동 코드를 따로 써야 하기 때문입니다. 흩어진 도구 호출(Tool Calling) 코드와 그 유지보수 부담은 혼자 일하는 개발자의 시간을 조금씩 갉아먹습니다. 이 연동 문제를 구조적으로 정리하려고 나온 오픈 표준이 MCP(Model Context Protocol)입니다.

이 글은 7월에 처음 썼고, 2026년 10월에 다시 고쳤습니다. 그사이 MCP 명세에 큰 변화가 있었기 때문입니다. 2026년 7월 28일 개정판에서 MCP는 연결마다 세션을 맺는 방식을 버리고 요청 하나하나가 스스로 완결되는 무상태(stateless) 구조로 바뀌었고, 공식 Python SDK도 v2로 올라가면서 FastMCP라는 이름이 MCPServer로 바뀌었습니다. 처음 글에 있던 “유지보수 공수 90% 이상 절감”, “요청당 20~50ms 지연” 같은 숫자는 근거를 댈 수 없어 모두 뺐고, 대신 공식 명세와 SDK 문서에서 확인한 내용과 직접 돌려 본 예제 코드로 채웠습니다.

연동 코드가 계속 늘어나는 이유

1인 개발자가 AI 에이전트를 도입할 때 가장 먼저 부딪히는 문제는 연동 코드가 도구 수와 클라이언트 수의 곱만큼 늘어난다는 점입니다.

처음에는 OpenAI API의 tools 파라미터에 JSON 스키마를 직접 정의해 DB를 조회하는 함수를 붙입니다. 그러다 Cursor나 Claude Code 같은 개발 도구, 혹은 내 서비스의 백엔드 에이전트에서도 같은 조회 기능을 쓰고 싶어지면, 같은 로직을 각 플랫폼 형식에 맞춰 여러 번 다시 써야 합니다. 그러면 DB 테이블 구조나 API 인증 방식이 바뀔 때마다 모든 연동 코드를 손봐야 하는 글루 코드(Glue Code) 문제가 생깁니다.

[기존 방식: 클라이언트마다 따로 연동]
Claude Code    ───▶ 커스텀 JSON 스키마 A ───▶ PostgreSQL
Cursor IDE     ───▶ 커스텀 JSON 스키마 B ───▶ Slack API
Custom SaaS App───▶ 커스텀 API 핸들러 C   ───▶ Notion API

[MCP 방식: 서버 하나를 여러 클라이언트가 공유]
Claude Code    ──┐
Cursor IDE     ──┼──▶ [MCP Client] ──(JSON-RPC)──▶ [MCP Server] ──▶ 데이터/도구
Custom SaaS App──┘

MCP를 쓰면 데이터 소스와 도구 실행부를 독립된 MCP 서버로 감쌀 수 있습니다. 한 번 만든 서버는 MCP를 지원하는 개발 도구, 데스크톱 AI 앱, 내 서비스의 에이전트에서 그대로 다시 쓸 수 있습니다.

도입을 판단하는 기준

  • 도입할 만한 경우: 연결할 외부 데이터 소스(DB, API, 파일)가 두 개 이상이거나, 개발 도구와 서비스 백엔드 양쪽에서 같은 도구를 불러야 할 때입니다. 이미 공개된 MCP 서버(GitHub, Notion, Stripe 등 각 회사가 직접 운영하는 서버)를 가져다 쓰기만 해도 되는 경우라면 더 빨리 이득을 봅니다.
  • 미뤄도 되는 경우: 모델 하나에 API 하나만 붙이는 단순한 챗봇이라면 MCP 서버를 따로 두는 것이 오히려 관리할 부품만 늘립니다. 응답 지연이 매우 중요한 경로라면 프로토콜 계층을 하나 더 거치는 비용을 직접 측정해 보고 판단해야 합니다.

MCP의 구조: Host, Client, Server 그리고 세 가지 Primitive

MCP는 앤트로픽이 2024년 11월 오픈소스로 공개한 오픈 프로토콜입니다. 메시지는 JSON-RPC 형식으로 주고받고, 역할은 셋으로 나뉩니다.

  • MCP Host: 사용자가 직접 쓰는 AI 애플리케이션입니다. (예: Claude Code, Cursor, 직접 만든 SaaS 앱)
  • MCP Client: Host 안에서 MCP Server와 통신을 담당하는 연결 부품입니다.
  • MCP Server: 데이터나 도구 실행 기능을 제공하는 경량 서비스입니다.

MCP 서버는 자기 기능을 세 가지 primitive로 나눠 노출합니다. 최신 명세의 설명을 그대로 옮기면, Resources는 사용자나 모델이 쓰는 맥락과 데이터, Prompts는 사용자를 위한 템플릿 메시지와 워크플로, Tools는 모델이 실행하는 함수입니다.

Primitive 성격 주요 역할 활용 예시
Resources 읽기용 맥락 URI로 식별되는 데이터 제공 saas://metrics/daily_summary, file:///docs/api.md
Tools 실행 가능 조회하거나 상태를 바꾸는 함수 search_user_by_email(), request_plan_change()
Prompts 템플릿 반복 워크플로를 재사용 가능한 지시문으로 관리 weekly_churn_report, summarize_feedback

반대 방향으로 클라이언트가 서버에 제공하는 기능도 있는데, 최신 명세 기준으로는 서버가 사용자에게 추가 정보를 묻는 Elicitation이 핵심입니다. 예전에 함께 소개되던 Sampling(서버가 클라이언트의 모델을 빌려 쓰기)과 Roots는 아래에서 설명할 7월 개정에서 지원 중단(deprecated) 방향으로 정리됐습니다.

2026년 7월 개정에서 바뀐 것: 무상태 MCP

MCP를 처음 공부하던 때의 자료를 보면 “클라이언트와 서버가 initialize 핸드셰이크로 1:1 상태 연결을 맺는다”는 설명이 자주 나옵니다. 이 설명은 이제 맞지 않습니다. 2026-07-28 명세 변경 기록에서 1인 개발자에게 영향이 큰 변화만 추리면 이렇습니다.

  • 핸드셰이크와 세션 제거: initialize 절차가 없어지고, 모든 요청이 _meta 안에 프로토콜 버전과 클라이언트 기능을 담아 보냅니다. Streamable HTTP의 Mcp-Session-Id 헤더도 사라졌습니다. 호출 사이에 상태가 필요하면 서버가 만든 핸들을 일반 도구 인자로 주고받으라는 것이 명세의 안내입니다.
  • server/discover 추가: 서버는 지원하는 프로토콜 버전과 기능을 알려 주는 이 메서드를 반드시 구현해야 합니다.
  • 서버가 먼저 요청하지 않음: 서버가 클라이언트에게 직접 요청을 보내던 방식(sampling, elicitation, roots) 대신, 서버가 “입력이 더 필요하다”는 중간 결과(input_required)를 돌려주고 클라이언트가 답을 붙여 원래 요청을 다시 보내는 다중 왕복 요청(MRTR) 패턴이 들어왔습니다. 모든 결과에는 resultType 필드가 붙습니다.
  • 변경 알림은 구독으로: 도구 목록 변경 같은 알림은 subscriptions/listen이라는 하나의 긴 스트림으로 받습니다. ping과 logging/setLevel은 제거됐습니다.
  • 캐시 힌트: tools/list, resources/read 같은 결과에 ttlMs와 cacheScope가 붙어, 클라이언트가 목록을 불필요하게 다시 받지 않게 됐습니다.
  • Tasks는 확장으로: 오래 걸리는 작업을 다루던 실험 기능 Tasks는 핵심 명세에서 빠져 공식 확장(extension)이 됐습니다.

운영 관점에서 보면 반가운 변화입니다. 요청마다 필요한 정보가 다 들어 있으니 서버를 여러 대로 늘리거나 서버리스로 올리기가 쉬워졌고, 세션을 붙잡고 있을 필요가 없어졌습니다. 다만 Streamable HTTP에서 응답 스트림이 끊기면 진행 중이던 요청은 잃어버리고, 클라이언트가 새 요청으로 다시 보내야 합니다. 그래서 상태를 바꾸는 도구는 같은 요청이 두 번 와도 문제가 없게(멱등하게) 만들어 두는 편이 안전합니다. 예전 클라이언트와의 호환은 명세의 하위 호환 절차를 따르고, SDK가 대부분 처리해 줍니다.

전송 방식: stdio와 Streamable HTTP

최신 명세의 표준 전송 방식은 두 가지입니다. 처음 글에서 원격 방식으로 소개했던 SSE 전송은 지금 기준으로 구형입니다.

  • stdio: Host가 MCP 서버를 자식 프로세스로 띄우고 표준 입출력으로 줄 단위 JSON-RPC 메시지를 주고받습니다. 네트워크 포트를 열지 않으니 로컬 개발 도구를 연결할 때 가장 간단하고 안전합니다. 대신 stdout이 곧 통신선이라, 서버 코드에서 print()로 디버그 출력을 찍으면 메시지가 깨질 수 있습니다. 로그는 stderr로 보내야 합니다.
  • Streamable HTTP: 각 메시지를 하나의 MCP 엔드포인트로 HTTP POST하고, 응답은 JSON 하나나 그 요청에 딸린 SSE 스트림으로 받습니다. 클라우드에 배포한 서버와 연결할 때 쓰는 방식입니다. Python SDK 문서는 SSE 전송을 “예전 HTTP 전송”으로 분류하고, 새로 만들 때는 쓰지 말라고 적어 둡니다.

인증도 전송 방식에 따라 다릅니다. 명세의 인증(Authorization) 문서는 HTTP 기반 서버에 OAuth 2.1 기반 인증 흐름을 정의하고, stdio 서버는 이 흐름을 따르지 말고 환경 변수에서 자격 증명을 읽으라고 안내합니다. 그래서 로컬용 서버는 SAAS_DB_PATH나 API 키를 환경 변수로 받고, 외부에 공개하는 서버는 OAuth를 붙이는 것이 기본 구성입니다.

1인 SaaS에 적용해 보는 세 장면

1인 창업가가 적은 공수로 운영 효율을 올릴 수 있는 장면 세 가지를 설계 관점에서 정리해 보겠습니다. 실제 수치는 서비스마다 다르니, 아래 제한값은 출발점으로만 보시면 됩니다.

고객 문의가 들어오면: 오류 원인 1차 분석

고객이 오류 문의를 남기면 에이전트가 먼저 최근 예외 로그를 담은 Resource(예: system://logs/err_latest)를 읽고, GitHub MCP 서버의 이슈 검색 도구로 이미 해결된 이슈와 비교한 뒤 원인 추정과 수정 방향 초안을 씁니다. 여기까지는 읽기만 하므로 위험이 낮습니다. 다만 고객에게 실제로 답장을 보내거나 결제를 취소하는 조치는 자동으로 하지 않고, 답변 초안을 만드는 단계에서 멈추도록 경계를 둡니다.

데이터를 뽑아야 할 때: 읽기 전용 분석

“지난주보다 구독 취소가 많이 늘어난 고객군과 주된 사유를 정리해줘” 같은 요청이 오면, 에이전트는 스키마를 담은 Resource로 테이블 구조를 확인하고 읽기 전용 조회 도구로 지표를 모읍니다. 이때 핵심은 모델이 SQL을 마음대로 쓰게 두지 않는 것입니다. 미리 정의한 조회 함수만 도구로 노출하고, 함수 안에서 읽기 전용 연결, 실행 시간 제한, 행 수 제한을 강제합니다. 아래 예제 코드가 바로 이 구조입니다.

되돌리기 어려운 작업: 사람 승인 기반 환불·요금제 변경

“고객 A 환불하고 계정 정지해줘”처럼 상태를 바꾸는 요청은 절차를 다르게 가져갑니다. 에이전트는 대상과 금액을 조회해 정리하지만, 결제 API를 직접 호출하는 도구는 아예 노출하지 않습니다. 대신 “승인 대기” 요청만 등록하는 도구를 두고, 실제 실행은 제가 관리 화면이나 별도 스크립트에서 확인한 뒤 처리합니다. 명세의 보안 원칙도 같은 방향입니다. 도구는 임의 코드 실행으로 취급해야 하고, Host는 도구를 실행하기 전에 사용자의 명시적 동의를 받아야 한다고 적고 있습니다. 모델이 잘못 이해하거나 프롬프트 인젝션에 걸려도 실제 돈이 움직이지 않게 하는 가장 단순한 방법이 “쓰기 도구는 요청만 남긴다”입니다.

실전 코드: Python SDK v2로 만드는 1인 SaaS 서버

아래는 공식 MCP Python SDK v2 문법으로 쓴 단일 파일 서버입니다. 읽기용 Resource 하나, 읽기 전용 도구 하나, 승인 대기만 등록하는 쓰기 도구 하나로 구성했습니다. 제 작업 환경(Python 3.13, mcp 2.3.0)에서 테스트용 SQLite DB를 만들어 도구 목록 조회, 도구 호출, 리소스 읽기까지 돌려 보고 올렸습니다. SDK v2는 Python 3.11 이상이 필요합니다.

"""
1인 SaaS용 경량 MCP 서버 예제 (MCP Python SDK v2 기준)
- 읽기: 리소스와 읽기 전용 도구
- 쓰기: 바로 실행하지 않고 '승인 대기' 요청만 남긴다
"""
import json
import logging
import os
import sqlite3
import time

from mcp.server import MCPServer
from mcp.types import ToolAnnotations

# stdio에서는 stdout이 통신선이라 print 대신 logging(stderr)을 쓴다
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("solo-saas")

mcp = MCPServer("solo-saas-core")
DB_PATH = os.getenv("SAAS_DB_PATH", "./saas_data.db")
QUERY_TIMEOUT_SEC = 3   # 예시 값: 서비스 규모에 맞게 조정
MAX_ROWS = 50           # 예시 값: 한 번에 모델에 넘길 최대 행 수


def connect(read_only: bool = True) -> sqlite3.Connection:
    """읽기 전용 연결을 기본으로 하고, 오래 걸리는 쿼리는 중단한다."""
    uri = f"file:{DB_PATH}?mode={'ro' if read_only else 'rw'}"
    conn = sqlite3.connect(uri, uri=True)
    conn.row_factory = sqlite3.Row
    deadline = time.monotonic() + QUERY_TIMEOUT_SEC
    # 0이 아닌 값을 돌려주면 SQLite가 실행 중인 쿼리를 중단한다
    conn.set_progress_handler(lambda: int(time.monotonic() > deadline), 10_000)
    return conn


@mcp.resource("saas://metrics/daily_summary")
def daily_summary() -> str:
    """오늘의 활성 사용자 수와 매출 합계 (읽기 전용)"""
    with connect() as conn:
        row = conn.execute(
            "SELECT COUNT(*) AS active_users, COALESCE(SUM(amount), 0) AS revenue "
            "FROM daily_stats WHERE date = DATE('now')"
        ).fetchone()
    return json.dumps(dict(row), ensure_ascii=False)


@mcp.tool(
    title="이메일로 고객 찾기",
    annotations=ToolAnnotations(read_only_hint=True, open_world_hint=False),
)
def search_user_by_email(email: str) -> str:
    """이메일 주소로 고객의 기본 정보를 찾는다."""
    if "@" not in email:
        return "ERROR: 이메일 형식이 아닙니다."
    with connect() as conn:
        user = conn.execute(
            "SELECT id, name, email, plan, created_at FROM users "
            "WHERE email = ? LIMIT 1",
            (email,),
        ).fetchone()
    if user is None:
        return "INFO: 해당 이메일의 고객이 없습니다."
    return json.dumps(dict(user), ensure_ascii=False)


@mcp.tool(
    title="요금제 변경 요청 등록",
    annotations=ToolAnnotations(destructive_hint=False, idempotent_hint=False),
)
def request_plan_change(user_id: int, new_plan: str, reason: str) -> str:
    """요금제 변경을 '승인 대기'로 등록한다. 실제 변경은 사람이 승인한 뒤 별도로 처리한다."""
    if new_plan not in ("free", "pro", "team"):
        return "ERROR: 허용되지 않은 요금제입니다. (free, pro, team)"
    with connect(read_only=False) as conn:
        conn.execute(
            "INSERT INTO pending_actions (kind, user_id, payload, status) "
            "VALUES ('plan_change', ?, ?, 'pending')",
            (user_id, json.dumps({"new_plan": new_plan, "reason": reason}, ensure_ascii=False)),
        )
    log.info("plan change requested: user=%s plan=%s", user_id, new_plan)
    return "PENDING: 요청을 등록했습니다. 운영자가 승인하면 반영됩니다."


if __name__ == "__main__":
    mcp.run()  # 인자가 없으면 stdio. 원격 배포는 mcp.run(transport="streamable-http")

코드에서 짚어 둘 부분은 다섯 가지입니다.

  • 타입 힌트가 곧 스키마: @mcp.tool()을 붙인 함수의 인자 타입과 docstring에서 SDK가 입력 스키마와 설명을 만듭니다. JSON 스키마를 손으로 쓸 필요가 없습니다.
  • annotations는 힌트일 뿐: read_only_hint, destructive_hint 같은 값은 클라이언트가 “실행 전에 물어볼지”를 판단하는 데 참고하는 힌트입니다. SDK 문서도 “보안 장치가 아니니 클라이언트가 지켜 줄 거라고 기대하지 말라”고 적어 둡니다. 실제 방어는 함수 안의 읽기 전용 연결과 승인 대기 구조가 맡습니다.
  • 로그는 stderr로: stdio에서는 stdout이 통신선이라 logging을 씁니다. 예전 예제에 흔하던 ctx.info() 같은 MCP 로그 메시지는 7월 개정에서 지원 중단 대상이 됐고, 요청이 로그를 원한다고 밝힌 경우에만 전달됩니다.
  • 실행 시간과 행 수 제한: SQLite의 progress handler로 오래 걸리는 쿼리를 끊고, 조회에는 LIMIT을 겁니다. 3초, 50행은 예시 값이니 서비스에 맞게 조정하세요. PostgreSQL이라면 statement_timeout과 읽기 전용 DB 계정을 쓰는 것이 같은 역할입니다.
  • v1에서 옮겨 올 때: v1 예제의 from mcp.server.fastmcp import FastMCP는 v2에서 from mcp.server import MCPServer로 바뀌었습니다. SDK 마이그레이션 가이드에 따르면 v1.x는 유지보수 모드로 보안 패치를 계속 받으니, 당장 옮기기 어렵다면 mcp>=1.28,<2처럼 상한을 걸어 두면 됩니다.

클라이언트에 연결하고 확인하기

서버를 만들었으면 먼저 MCP Inspector로 단독 확인을 하고, 그다음 실제로 쓰는 도구에 붙이는 순서가 편합니다.

# 설치 (cli extra에 mcp dev / mcp run 명령이 들어 있음)
pip install "mcp[cli]"

# Inspector로 도구·리소스 확인 (stdio로 서버를 띄워 연결)
mcp dev server.py

# Claude Code에 로컬 stdio 서버로 등록 (-- 뒤는 서버 실행 명령)
claude mcp add --env SAAS_DB_PATH=/path/to/saas_data.db solo-saas -- python server.py

# 원격(Streamable HTTP)으로 띄운 서버를 등록할 때
claude mcp add --transport http solo-saas https://example.com/mcp

Claude Code 기준으로 claude mcp add는 기본적으로 나만 쓰는 local 범위에 저장되고, 팀과 공유하려면 --scope project를 붙이거나 프로젝트 루트의 .mcp.json에 적어 커밋하면 됩니다. 연결 상태는 claude mcp get solo-saas로 확인합니다. 다른 클라이언트도 설정 위치만 다를 뿐, “실행 명령과 환경 변수” 또는 “URL”을 등록한다는 구조는 같습니다.

알아 두어야 할 트레이드오프

표준화에는 대가가 있습니다. 1인 개발자가 현실적으로 신경 써야 할 부분을 정리합니다.

계층이 하나 늘어난다

MCP는 클라이언트와 서버 사이에 프로토콜 계층을 하나 더 둡니다. stdio라면 프로세스 간 통신이, Streamable HTTP라면 네트워크 왕복이 추가됩니다. 처음 글에는 이 지연을 구체적인 숫자로 적었는데, 환경마다 크게 달라서 근거 있는 숫자를 댈 수 없어 뺐습니다. 지연이 중요한 경로라면 직접 측정해 보고 판단하는 것이 맞습니다. 대부분의 운영 업무 자동화에서는 모델 응답 시간이 훨씬 커서 이 계층이 병목이 되는 경우는 드뭅니다.

토큰 비용이 새는 곳

Resource로 로그나 긴 문서를 통째로 넘기면 한 번의 호출에 많은 토큰이 들어갑니다. 연결한 MCP 서버가 많으면 도구 설명 자체도 매 요청의 맥락을 차지합니다. 그래서 Resource 함수 안에서 최근 N건만 자르거나 요약해서 돌려주고, 쓰지 않는 서버는 연결을 끊어 두는 습관이 필요합니다. 7월 개정에서 tools/list 결과를 일정한 순서로 돌려주라는 권고(SHOULD)가 들어간 것도 프롬프트 캐시 적중률을 높이려는 취지입니다.

보안 경계와 프롬프트 주입

도구가 DB 쓰기 권한을 가지면, 모델이 읽은 고객 문의나 웹 페이지 속 문장이 그 권한을 움직일 수 있습니다. 다음 원칙은 꼭 지키는 편이 좋습니다.

  • 모델이 만든 SQL 문자열을 그대로 실행하는 도구를 만들지 않습니다. 미리 정의한 함수와 파라미터 바인딩만 씁니다.
  • 읽기 도구는 읽기 전용 DB 계정이나 연결로, 쓰기 도구는 승인 대기 등록으로 분리합니다.
  • HTTP로 공개하는 서버는 명세의 인증 절차를 따르고, 클라이언트가 보낸 토큰을 다른 API로 그대로 넘기는(token passthrough) 구조를 피합니다. 명세의 보안 모범 사례(Security Best Practices) 문서가 이 문제와 혼동된 대리인(confused deputy), SSRF 같은 공격을 따로 다룹니다.
  • 남이 만든 MCP 서버를 붙일 때는 그 서버가 어떤 도구를 노출하는지 목록을 먼저 보고, 쓰기 도구가 있다면 클라이언트 쪽 권한 설정으로 막거나 승인을 요구하게 해 둡니다.

90일 실행 로드맵

아이디어 검증부터 프로덕션 연동까지, 혼자서 단계적으로 밟아 갈 순서입니다. 처음 글에 있던 비용 금액과 비율 기준은 근거가 없어 뺐고, 대신 각 단계에서 무엇을 확인해야 다음으로 넘어갈지를 적었습니다. 기준값은 본인 서비스의 매출 구조에 맞게 직접 정하는 것을 전제로 합니다.

[1인 AI SaaS 90일 단계]
1~2주차 : 반복 업무 1개를 골라 로컬 MCP 서버 시제품
   │
3~4주차 : 쓰기 기능은 승인 대기 구조로 추가
   │
2개월차 : 소수 사용자에게 공개, 비용·응답 시간 측정
   │
3개월차 : 운영 시간 비교 후 확장 여부 결정
시기 목표 다음 단계로 넘어가는 조건
1~2주차 가장 자주 반복하는 백엔드 작업 하나를 골라 stdio MCP 서버로 시제품 제작 Inspector와 실제 개발 도구 양쪽에서 도구 목록이 보이고, 읽기 작업이 의도대로 동작함
3~4주차 쓰기가 필요한 기능을 “승인 대기 등록” 도구로 추가하고 승인 처리 화면이나 스크립트 마련 어떤 프롬프트를 넣어도 사람 승인 없이 실제 데이터가 바뀌지 않음을 직접 시험해 확인
2개월차 소수 사용자에게 에이전트 기능을 열고 Streamable HTTP로 배포, 인증 적용 사용자당 토큰 비용과 응답 시간을 기록하고, 미리 정한 비용 상한 안에 들어옴
3개월차 도입 전후 주간 운영 시간을 비교하고 정식 기능으로 확장할지 결정 운영 시간이 실제로 줄었고, 승인 대기 요청 처리가 새로운 부담이 되지 않음

자주 묻는 질문

MCP 서버를 꼭 직접 만들어야 하나요?

아닙니다. GitHub, Notion, Stripe처럼 많이 쓰는 서비스는 회사가 직접 MCP 서버를 운영하는 경우가 많아서, 클라이언트에 URL만 등록하면 됩니다. 직접 만들 가치가 있는 것은 내 서비스의 DB나 내부 API처럼 남이 대신 만들어 줄 수 없는 부분입니다.

예전에 FastMCP로 만든 서버는 버려야 하나요?

버릴 필요는 없습니다. Python SDK v1.x는 유지보수 모드로 보안 패치를 계속 받습니다. 다만 새 명세(2026-07-28)의 기능을 쓰려면 v2로 옮겨야 하고, 이름 변경(FastMCP → MCPServer)과 import 경로 변경이 대부분이라 작은 서버는 옮기는 데 오래 걸리지 않습니다.

로컬용과 배포용 서버를 따로 만들어야 하나요?

같은 코드로 됩니다. mcp.run()은 인자가 없으면 stdio로, mcp.run(transport="streamable-http")를 주면 HTTP 서버로 뜹니다. 다만 배포용은 인증과 접근 제어가 반드시 필요하니, 공개 전에 명세의 인증 문서를 따라 설정을 추가해야 합니다.

에이전트가 MCP 도구를 마음대로 실행하면 어떡하죠?

두 겹으로 막습니다. 서버 쪽에서는 위험한 기능을 아예 도구로 노출하지 않거나 승인 대기만 남기게 만들고, 클라이언트 쪽에서는 도구별 권한을 설정합니다. 예를 들어 Claude Code는 mcp__서버이름__도구이름 형태로 도구별 허용·거부 규칙을 걸 수 있습니다. 클라이언트 권한 설정과 훅 사용법은 Claude Code 하네스 정리에, 에이전트를 확인 없이 돌리는 YOLO 모드의 위험은 AI 코딩 에이전트 9종 총정리에 정리해 두었습니다.

지금 바로 시작하는 방법

새 기술을 도입할 때 가장 위험한 태도는 모든 구조를 한 번에 바꾸려는 욕심입니다. MCP도 큰 플랫폼을 한꺼번에 만들려 하지 말고, 오늘 가장 자주 쓰는 조회 작업 하나를 MCP 서버로 옮겨 보는 것부터 시작하는 편이 낫습니다.

  1. 파이썬 환경 준비: Python 3.11 이상에서 pip install "mcp[cli]"로 공식 SDK를 설치합니다.
  2. 예제 저장: 위 예제를 server.py로 저장하고, 테이블 이름을 내 서비스에 맞게 바꿉니다.
  3. 단독 확인: mcp dev server.py로 Inspector를 열어 도구와 리소스가 보이는지, 쓰기 도구가 승인 대기만 남기는지 확인합니다.
  4. 클라이언트 연결: 쓰고 있는 개발 도구에 서버를 등록하고, 실제 질문으로 시험합니다.

1인 개발자에게 가장 귀한 자산은 시간과 집중력입니다. 클라이언트마다 연동 코드를 따로 쓰는 데 시간을 쓰는 대신, 표준화된 MCP 서버 하나에 연결을 모으고 제품 자체의 가치 검증에 집중할 때 작지만 오래가는 1인 SaaS를 운영할 수 있습니다. 바이브 코딩으로 만든 서비스라면 데이터베이스 권한부터 점검하는 것이 먼저인데, 그 부분은 Supabase RLS 설정 체크리스트를 참고하세요.

참고 자료 및 관련 글

공식 출처

블로그 내 관련 글