핵심 요약
MCP는 LLM이 외부 도구·데이터를 표준 방식으로 호출하는 프로토콜. Claude·Cursor·VS Code Copilot 등이 모두 지원. 이 글은 동일 기능 MCP 서버를 Python·TypeScript·Go 3개 언어로 구현하고 비교한다.
- SDK: 모든 주요 언어 공식 지원 (Python·TS·Go·Kotlin·Swift)
- Transport: stdio (로컬), HTTP (원격), SSE (스트리밍)
- 구성요소: Tools·Resources·Prompts
1. 공통 — MCP 서버가 노출하는 것
- Tools: LLM이 호출할 수 있는 함수 (예: search_files)
- Resources: LLM이 읽을 수 있는 데이터 (예: 파일 시스템)
- Prompts: 재사용 가능 프롬프트 템플릿
2. Python (가장 빠른 시작)
from mcp.server import Server
from mcp.types import Tool, TextContent
import asyncio
app = Server("my-server")
@app.list_tools()
async def list_tools():
return [
Tool(
name="greet",
description="인사 메시지 생성",
inputSchema={
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "greet":
return [TextContent(type="text", text=f"안녕, {arguments['name']}!")]
if __name__ == "__main__":
import mcp.server.stdio
asyncio.run(mcp.server.stdio.run_server(app))3. TypeScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"
const server = new Server({ name: "my-server", version: "1.0" }, { capabilities: { tools: {} } })
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "greet",
description: "인사 메시지",
inputSchema: {
type: "object",
properties: { name: { type: "string" } },
required: ["name"]
}
}]
}))
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name === "greet") {
const args = req.params.arguments as { name: string }
return { content: [{ type: "text", text: `안녕, ${args.name}!` }] }
}
throw new Error("Unknown tool")
})
const transport = new StdioServerTransport()
await server.connect(transport)4. Go
package main
import (
"context"
"fmt"
mcp "github.com/anthropics/mcp-go"
)
func main() {
s := mcp.NewServer("my-server", "1.0")
s.AddTool(mcp.Tool{
Name: "greet",
Description: "인사 메시지",
InputSchema: mcp.Schema{
Type: "object",
Properties: map[string]mcp.Schema{
"name": {Type: "string"},
},
Required: []string{"name"},
},
Handler: func(ctx context.Context, args map[string]any) (mcp.ToolResult, error) {
name := args["name"].(string)
return mcp.TextResult(fmt.Sprintf("안녕, %s!", name)), nil
},
})
s.RunStdio()
}5. 언어별 비교
| 항목 | Python | TypeScript | Go |
|---|---|---|---|
| 코드 양 | 적음 | 중간 | 중간 |
| 시작 속도 | 1.2초 | 0.4초 (Bun: 0.05초) | 0.05초 |
| 메모리 | 40MB | 30MB | 10MB |
| 배포 | Python 환경 필요 | node 또는 단일 바이너리 | 단일 바이너리 |
| 생태계 | 가장 풍부 | 풍부 | 중간 |
6. Transport 선택
stdio (로컬)
- Claude Code·Cursor 같은 로컬 도구
- 가장 빠름·보안 강함 (프로세스 격리)
- 인증 불필요
HTTP/SSE (원격)
- 웹 앱·여러 클라이언트가 공유
- OAuth·API key 인증 가능
- SSE로 streaming 지원
7. 실전 — File System MCP 서버 (Python)
@app.list_tools()
async def list_tools():
return [
Tool(name="read_file", ...),
Tool(name="write_file", ...),
Tool(name="list_dir", ...),
Tool(name="search", ...),
]
@app.call_tool()
async def call_tool(name: str, args: dict):
if name == "read_file":
path = args["path"]
if not is_safe_path(path):
raise ValueError("경로 권한 없음")
return [TextContent(type="text", text=open(path).read())]
elif name == "search":
pattern = args["pattern"]
results = grep(pattern, args.get("path", "."))
return [TextContent(type="text", text=results)]8. 보안 베스트 프랙티스
- 모든 파일 경로 sanitize (path traversal 방지)
- 실행 가능 명령 화이트리스트만 허용
- API key·secret은 환경변수로만, 인자로 받지 말 것
- 로깅: stdout 절대 사용 금지 (MCP 프로토콜 채널), stderr만
- JSON 출력에 indent=2 같은 옵션 금지 (newline이 메시지 경계 깨뜨림)
9. 흔한 함정
- console.log 사용 → MCP 통신 깨짐 (stderr 사용 필수)
- backpressure 미처리 → 버퍼 가득 차면 deadlock
- tool error에 throw 사용 시 클라이언트 끊김 → ToolResult.error 사용
- resource 리스트가 큰 경우 pagination 미구현
자주 묻는 질문
어느 언어로 시작?
Python — 코드 적고 생태계 풍부. 배포 시 단일 바이너리 필요하면 Go.
HTTP transport 보안?
OAuth 2.1 + scopes. MCP HTTP 인증 표준이 2026년 1월 확정.
Tool 개수 한도?
이론상 제한 없음. 실무는 30~50개가 한계 (LLM이 토큰으로 모든 tool spec을 받기 때문).

댓글 0