책쓰자. Part1 - 6 6.3까지
6장. MCP 서버 만들기
MCPServer는 LLM이 외부 세계와 상호작용할 수 있도록 도구(Tools), 리소스(Resources), 프롬프트(Prompts)를 표준화된 방식으로 제공하는 핵심 컴포넌트다. 여기에 더해, 서버의 동작을 손쉽게 검증하고 테스트할 수 있도록 돕는 도구가 바로 MCP Inspector다. MCP Inspector는 MCPServer에 등록된 도구와 리소스를 시각적으로 탐색하고 호출할 수 있는 개발자용 유틸리티로, Python과 TypeScript 등 서버 구현 언어와 관계없이 활용 가능하다. 이를 통해 LLM 없이도 서버 기능을 독립적으로 테스트하고 디버깅할 수 있으며, 개발 효율성을 크게 높일 수 있다.
6.1 MCPServer 구조 이해
MCPServer는 LLM이 외부 세계와 안전하게 상호작용할 수 있도록 설계된 표준화된 인터페이스다. 단순히 외부 기능을 실행하는 역할을 넘어서, 도구 호출의 일관성, 확장성, 그리고 안정성을 보장하는 핵심 컴포넌트라고 할 수 있다. 이를 이해하기 위해서는 MCPServer의 내부 구성요소와 작동 방식을 체계적으로 살펴볼 필요가 있다.
MCPServer는 크게 다섯 가지 주요 구성 요소로 이루어져 있다.
-
Tools: LLM이 호출할 수 있는 함수형 API로, MCPServer의 핵심 기능을 담당한다. 예를 들어 번역, 요약, 이미지 분석 같은 작업이 여기에 해당된다. 각 Tool은 JSON Schema로 입력값이 명확히 정의되며, LLM은 이 스키마를 기반으로 적절한 입력을 구성해 호출한다.
-
Resources: 읽기 전용 데이터 소스로, 외부 API나 내부 고정 데이터를 LLM에게 제공한다. 환율 정보, 국가 코드, 사전 데이터처럼 조회만 필요한 경우에 사용된다. 부수효과(side-effect)가 없다는 점에서 Tools와 구분된다.
-
Prompts: LLM이 특정 작업을 더 효과적으로 수행할 수 있도록 돕는 템플릿 모음이다. 예를 들어, 이메일 작성 템플릿이나 요약 포맷 등이 여기에 포함된다. 프롬프트를 표준화함으로써 일관된 출력 결과를 기대할 수 있다.
-
Protocol Layer: MCPServer와 MCPClient 간의 통신 규약을 담당하는 계층이다. 주로 JSON-RPC 2.0을 기반으로 하며, 요청과 응답의 형식을 정의하고 메시지 일관성을 유지한다. 이 계층 덕분에 클라이언트와 서버는 언어나 플랫폼에 상관없이 안정적으로 통신할 수 있다.
-
Transport Layer: 실제 데이터가 전달되는 물리적 통신 수단이다. Stdio, HTTP, WebSocket, SSE(Server-Sent Events) 등이 여기에 해당한다. 개발 환경이나 서비스 목적에 따라 적절한 전송 방식을 선택할 수 있다.
다음 표는 MCPServer의 핵심 구성요소를 요약한 것이다.
구성 요소 | 설명 |
---|---|
Tools | LLM이 호출할 수 있는 함수형 API |
Resources | 읽기 전용 데이터 소스 (예: 환율 정보, 고정 데이터 조회) |
Prompts | LLM 활용을 돕는 템플릿 또는 프롬프트 예시 |
Protocol Layer | JSON-RPC 2.0 기반 메시지 프레이밍 및 요청/응답 처리 |
Transport Layer | STDIO, HTTP, WebSocket, SSE 등 데이터 전달 계층 |
MCPServer는 이러한 구조를 통해 단순 실행기가 아닌, LLM과 외부 기능 간의 안전하고 표준화된 브릿지 역할을 수행한다. 특히 다양한 도구와 데이터를 일관된 방식으로 노출함으로써, 에이전트 시스템의 확장성과 유지보수성을 크게 향상시킨다.
1. 실행 환경에 따른 Transport 계층 선택 기준
MCPServer를 설계할 때 중요한 요소 중 하나는 Transport Layer의 선택이다. 실행 환경과 목적에 따라 최적의 전송 방식을 선택해야 시스템의 효율성과 안정성을 모두 확보할 수 있다.
실행 환경 | 권장 Transport | 특징 및 용도 |
---|---|---|
로컬 개발 환경 | Stdio | 빠른 테스트, 설정이 간단하고 별도 네트워크 설정 불필요 |
웹 애플리케이션 | HTTP / SSE | RESTful API 통신, 실시간 스트리밍 데이터 처리에 적합 |
클라우드 서버 | HTTP / WebSocket | 확장성 높은 연결 유지, 다수의 클라이언트 요청을 효율적으로 처리 가능 |
서버리스 환경 | HTTP | 이벤트 기반 호출 최적화, 서버리스 아키텍처에 적합 |
예를 들어, 개발 초기 단계에서는 Stdio를 활용해 빠르게 기능을 테스트할 수 있고, 서비스 배포 시에는 HTTP나 WebSocket을 선택해 확장성과 네트워크 기반 통신을 지원하는 구조로 전환할 수 있다. 실시간 데이터 피드백이 중요한 경우에는 SSE가 적합하다.
2. 프로토콜과 트랜스포트의 관계
많은 초보 개발자들이 혼동하는 부분이 바로 Protocol Layer와 Transport Layer의 차이다. 간단히 정리하면 다음과 같다.
- Protocol Layer: "어떻게 대화할 것인가"를 정의 (예: JSON-RPC 형식)
- Transport Layer: "어디로, 어떤 통로를 통해 보낼 것인가"를 정의 (예: HTTP, Stdio)
즉, JSON-RPC라는 약속된 형식으로 메시지를 주고받되, 이 메시지를 실어나르는 방법이 Transport 계층이다. 두 계층을 분리함으로써, 동일한 프로토콜을 유지하면서도 다양한 실행 환경에 맞게 유연하게 대응할 수 있다.
이처럼 MCPServer는 단순한 서버 구현이 아니라, LLM 기반 에이전트가 외부 세계와 안전하고 효율적으로 상호작용할 수 있는 표준화된 실행 환경을 제공하는 데 그 핵심 가치가 있다. 따라서 MCPServer 구조를 이해하는 것은 곧 에이전트 시스템의 확장성과 유지보수 전략을 설계하는 기초가 된다.
6.2 Python 기반 구현
Python으로 MCPServer를 구축하는 방식은 크게 두 가지가 있다.
-
FastMCP 사용
FastMCP는 Python 환경에서 MCPServer를 빠르게 구축할 수 있도록 설계된 고수준 추상화 프레임워크다. 반복적인 설정을 최소화하고, 데코레이터 기반으로 도구 등록을 간편하게 처리할 수 있어 프로토타이핑, 테스트용 서버, 경량화된 에이전트 서버에 적합하다. -
기본 MCPServer 클래스 직접 사용
FastMCP를 사용하지 않고McpServer
와StdioServerTransport
를 직접 활용해 서버를 구성하는 방식이다. 이 방법은 초기 설정이 다소 복잡할 수 있지만, 세밀한 제어와 커스터마이징이 가능해 복잡한 서비스나 대규모 시스템에 적합하다.
따라서 개발 목적에 따라 FastMCP의 간편함을 선택할지, 직접 구현을 통해 확장성을 확보할지 결정할 수 있다. FastMCP는 Python 환경에서 MCPServer를 빠르게 구축할 수 있도록 제공되는 고수준 추상화 프레임워크다. 반복적인 설정을 최소화하고, 데코레이터 기반으로 도구 등록을 간편하게 처리할 수 있도록 설계되었다. 주로 프로토타이핑, 테스트, 경량화된 서버 구축에 적합하다.
이제 FastMCP를 활용하여 MCPServer를 어떻게 쉽게 구축할 수 있는지 살펴보자.
# FastMCP 라이브러리 임포트
from mcp.server.fastmcp import FastMCP
# MCPServer 인스턴스 생성
mcp = FastMCP("hello-server")
# say_hello 도구 등록
@mcp.tool()
def say_hello(name: str) -> dict:
return {"message": f"Hello, {name}!"}
# 서버 실행
mcp.run()
위 코드는 FastMCP를 활용해 MCPServer를 구축하는 가장 기본적인 예제이다.
from mcp.server.fastmcp import FastMCP
는 FastMCP 클래스를 불러오는 부분으로, MCPServer의 핵심 기능을 제공한다.mcp = FastMCP("hello-server")
는 서버 인스턴스를 생성하며, 서버의 이름을 지정한다. 이 이름은 클라이언트와 LLM이 서버를 식별할 때 사용된다.@mcp.tool()
데코레이터는 LLM이 호출할 수 있는 도구를 등록하는 부분이다. 여기서는say_hello
라는 간단한 인사 기능을 제공한다.mcp.run()
을 호출하면 서버가 실행되며, 클라이언트 요청을 받을 준비를 한다.
이 구조 덕분에 복잡한 설정 없이도 빠르게 MCPServer를 구축할 수 있으며, 필요한 도구만 추가하면 쉽게 확장할 수 있다.
FastMCP 서버 실행 및 MCP Inspector 연결
MCP 서버가 제대로 동작하는지 확인하기 위해 MCP Inspector를 사용할 수 있다. Inspector는 MCP 서버의 도구, 리소스, 프롬프트를 시각적으로 테스트하고 검증할 수 있는 유용한 도구이다.
1. MCP Inspector 설치
먼저 터미널에서 다음 명령어를 실행하여 MCP Inspector를 설치한다. Node.js와 npm이 설치되어 있어야 한다.
npm install -g @modelcontextprotocol/inspector
2. 서버 코드 수정 및 실행
FastMCP 서버 코드를 다음과 같이 수정하여 Inspector와 연동한다.
from mcp.server.fastmcp import FastMCP
import subprocess
# MCPServer 인스턴스 생성
mcp = FastMCP("hello-server")
# say_hello 도구 등록
@mcp.tool()
def say_hello(name: str) -> dict:
return {"message": f"Hello, {name}!"}
# MCP Inspector 실행
inspector_process = subprocess.Popen(["npx", "@modelcontextprotocol/inspector"])
# FastMCP 서버 실행
server_process = subprocess.Popen(["python", "-c", "from mcp.server.fastmcp import FastMCP; mcp = FastMCP('hello-server'); @mcp.tool(); def say_hello(name: str) -> dict: return {'message': f'Hello, {name}!'}; mcp.run()"])
3. Inspector 실행 확인 및 사용
- 서버 코드를 실행한다.
- 웹 브라우저에서
http://localhost:3000
접속. - Inspector UI에서 도구를 테스트한다.
FastMCP를 사용하지 않는 서버 구축 및 인스펙터 연결
FastMCP를 사용하지 않고 MCPServer를 구현하려면 저수준 API를 직접 활용해야 한다.
from mcp.server.mcp import McpServer
from mcp.server.stdio import StdioServerTransport
import subprocess
# MCPServer 정의
definition = {
"name": "hello-server",
"tools": [
{
"name": "say_hello",
"description": "사용자에게 인사말을 반환합니다.",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
}
}
]
}
server = McpServer(definition)
@server.handler("say_hello")
def handle_say_hello(params):
name = params["name"]
return {"message": f"Hello, {name}!"}
transport = StdioServerTransport()
# MCP Inspector 실행
inspector_process = subprocess.Popen(["npx", "@modelcontextprotocol/inspector"])
# 서버 실행
server_process = subprocess.Popen(["python", "-c", "from mcp.server.mcp import McpServer; from mcp.server.stdio import StdioServerTransport; definition = {'name': 'hello-server', 'tools': [{'name': 'say_hello', 'description': '사용자에게 인사말을 반환합니다.', 'input_schema': {'type': 'object', 'properties': {'name': {'type': 'string'}}, 'required': ['name']}}]}; server = McpServer(definition); @server.handler('say_hello'); def handle_say_hello(params): name = params['name']; return {'message': f'Hello, {name}!'}; transport = StdioServerTransport(); server.run(transport)"])
이러한 방식으로 FastMCP 또는 기본 MCPServer를 사용하여 MCP 서버를 구축하고, MCP Inspector를 통해 서버의 동작을 손쉽게 테스트할 수 있다.
6.3 TypeScript 기반 구현
TypeScript 기반 MCPServer 구현은 대규모 서비스 환경이나 타입 안전성이 중요한 프로젝트에서 매우 유용하다. 특히 TypeScript의 정적 타입 검사를 활용하면, 도구(inputSchema) 정의와 실행 과정에서 발생할 수 있는 오류를 사전에 방지할 수 있다. 또한 zod
라이브러리를 통해 입력 스키마를 명확하게 정의하고 검증할 수 있어, 안정적인 서버 개발이 가능하다.
TypeScript로 MCPServer를 구축할 때 기본적으로 사용하는 구성 요소는 다음과 같다.
McpServer
: MCP 서버의 핵심 클래스. 서버 이름, 버전, 도구 등을 정의.StdioServerTransport
: 기본 전송 계층으로, 로컬 개발 환경에서 빠르게 테스트할 수 있는 방식.zod
: JSON Schema를 타입스크립트에서 효율적으로 정의하고 검증하는 라이브러리.
이제 실제 코드 예제를 통해 자세히 살펴보자.
// 1. MCPServer 및 관련 라이브러리 임포트
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 2. MCPServer 인스턴스 생성
const server = new McpServer({ name: "hello-server", version: "1.0.0" });
// 3. say_hello 도구 등록
server.tool(
"say_hello",
z.object({ name: z.string() }),
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }]
})
);
// 4. Transport 설정 및 서버 실행
const transport = new StdioServerTransport();
await server.connect(transport);
코드 설명
- 라이브러리 임포트: MCPServer와 통신을 위한 Stdio Transport, 그리고 입력 스키마를 정의할 zod를 불러온다.
- 서버 인스턴스 생성: 서버의 이름과 버전을 지정한다. 이는 클라이언트와 LLM이 서버를 식별할 때 사용된다.
- 도구 등록:
say_hello
라는 이름의 도구를 등록한다. 입력값으로 문자열 타입의name
을 요구하며, 호출 시 "Hello, [name]!" 형식의 응답을 반환한다. - 서버 실행: Stdio 전송 방식을 설정한 후 서버를 실행해 클라이언트 요청을 받을 준비를 한다.
이처럼 TypeScript에서는 타입 기반으로 입력과 출력을 명확하게 관리할 수 있기 때문에, 대규모 시스템에서 오류를 최소화하고 유지보수성을 높일 수 있다.
MCP Inspector 연동 방법
TypeScript로 구축한 MCPServer 역시 Python과 동일하게 MCP Inspector를 통해 손쉽게 테스트할 수 있다.
1. MCP Inspector 설치
npm install -g @modelcontextprotocol/inspector
2. 서버 코드 실행
일반적으로 ts-node
또는 빌드 후 실행한다.
ts-node server.ts
3. MCP Inspector 실행
npx @modelcontextprotocol/inspector
Inspector가 실행되면 자동으로 Stdio 연결을 감지하고, 등록된 도구 목록을 UI로 표시한다. 여기서 say_hello
도구를 선택하고 입력값(name
)을 넣어 호출 테스트를 진행할 수 있다.
실행 예시
- 입력:
{ "name": "Alice" }
- 출력:
{ "content": [ { "type": "text", "text": "Hello, Alice!" } ] }
확장 팁
- Transport 변경: Stdio 외에도 HTTP, WebSocket으로 쉽게 전환 가능하다.
- 복수 도구 등록: 서버 인스턴스에 여러 도구를 등록하여 확장성을 확보.
- 버전 관리: 서버 버전을 명시함으로써 클라이언트와의 호환성을 체계적으로 관리할 수 있다.
TypeScript 기반 MCPServer는 엔터프라이즈 환경이나 복잡한 비즈니스 로직을 처리해야 하는 경우에 적합하며, 정적 타입 시스템 덕분에 신뢰성 높은 서버 구현이 가능하다.
이제 TypeScript 환경에서도 MCPServer를 효율적으로 구축하고, MCP Inspector를 활용해 개발 및 테스트 과정을 체계적으로 관리할 수 있다.