들어가며
AI 에이전트가 외부 도구와 데이터에 접근하는 방식은 오랫동안 표준이 없었습니다. AI 엔지니어링 패러다임의 진화 살펴보기 각 AI 서비스마다 플러그인, 함수 호출, 도구 사용 방식이 제각각이었고, 개발자는 같은 기능을 여러 형식으로 반복 구현해야 했습니다. MCP(Model Context Protocol)는 Anthropic이 2024년 11월에 공개한 오픈 프로토콜로, AI 모델과 외부 시스템 간의 연결을 표준화합니다. USB가 다양한 기기 연결을 통일한 것처럼, MCP는 AI 에이전트와 도구/데이터 소스 간의 통합을 하나의 프로토콜로 통일합니다. 이번 글에서는 MCP의 핵심 개념부터 직접 서버를 구현하는 방법까지 실무 관점에서 다루겠습니다.
1. MCP란 무엇인가
MCP는 JSON-RPC 2.0 기반의 클라이언트-서버 프로토콜로, AI 애플리케이션(호스트)이 다양한 데이터 소스와 도구에 안전하고 표준화된 방식으로 접근할 수 있게 해줍니다.
기존 방식의 문제점
- 각 AI 서비스마다 다른 통합 방식 (OpenAI Functions, Claude Tools, 등) Google Gemma 4 네이티브 도구 호출 기능
- N개의 AI 앱 x M개의 도구 = N*M 개의 통합 필요
- 도구 추가 시마다 각 AI 앱에 맞는 어댑터를 별도 개발
- 보안, 인증, 권한 관리의 일관성 부재
MCP의 해결 방식
- 하나의 표준 프로토콜로 모든 AI 앱과 도구를 연결
- MCP 서버 1개 만들면 모든 MCP 호환 클라이언트에서 사용 가능
- N개의 AI 앱 + M개의 도구 = N + M 개의 구현만 필요
- 보안과 권한을 프로토콜 수준에서 관리
2. MCP 아키텍처
MCP는 세 가지 핵심 컴포넌트로 구성됩니다.
| 컴포넌트 | 역할 | 예시 |
|---|---|---|
| Host | 사용자와 상호작용하는 AI 애플리케이션 | Claude Desktop, Cursor, Claude Code |
| Client | Host 내부에서 MCP 서버와 1:1 통신하는 커넥터 | Host가 자동 관리 |
| Server | 특정 기능/데이터를 제공하는 서비스 | DB 서버, GitHub 서버, Slack 서버 |
┌─────────────────────────────────────────┐
│ Host (Claude Desktop) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ MCP │ │ MCP │ │ MCP │ │
│ │ Client A │ │ Client B │ │ Client │ │
│ └────┬─────┘ └────┬─────┘ └───┬────┘ │
│ │ │ │ │
└───────┼──────────────┼────────────┼──────┘
│ │ │
┌────▼─────┐ ┌─────▼────┐ ┌───▼──────┐
│ MCP │ │ MCP │ │ MCP │
│ Server │ │ Server │ │ Server │
│ (GitHub) │ │ (DB) │ │ (Slack) │
└──────────┘ └──────────┘ └──────────┘
통신 방식
MCP는 두 가지 전송(Transport) 방식을 지원합니다.
- stdio (Standard I/O): 로컬 프로세스 간 통신. 서버를 자식 프로세스로 실행하고 stdin/stdout으로 JSON-RPC 메시지를 주고받음. 로컬 도구에 적합.
- Streamable HTTP: HTTP 기반의 원격 통신. SSE(Server-Sent Events)를 활용한 서버 푸시 지원. 원격 서버, 클라우드 배포에 적합.
3. MCP의 세 가지 핵심 개념
Resources - 데이터 제공
Resources는 서버가 클라이언트에 노출하는 읽기 전용 데이터입니다. 파일 내용, DB 레코드, API 응답 등을 URI 형태로 제공합니다.
// Resource 예시
{
"uri": "postgres://mydb/users/123",
"name": "사용자 #123 정보",
"description": "사용자의 프로필 데이터",
"mimeType": "application/json"
}
AI 모델은 Resource를 컨텍스트에 포함시켜 더 정확한 응답을 생성할 수 있습니다. 예를 들어 DB 스키마를 Resource로 제공하면, AI가 해당 스키마에 맞는 SQL을 생성합니다.
Tools - 기능 실행
Tools는 AI 모델이 호출할 수 있는 함수입니다. LLM의 Function Calling과 유사하지만 MCP 프로토콜로 표준화되어 있습니다. Spring AI로 Function Calling 구현하는 방법
// Tool 정의 예시
{
"name": "query_database",
"description": "SQL 쿼리를 실행하고 결과를 반환합니다",
"inputSchema": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "실행할 SELECT SQL 쿼리"
},
"database": {
"type": "string",
"description": "대상 데이터베이스 이름"
}
},
"required": ["sql"]
}
}
Prompts - 대화 템플릿
Prompts는 서버가 제공하는 미리 정의된 대화 템플릿입니다. 사용자가 특정 작업을 시작할 때 최적화된 프롬프트를 제공합니다.
// Prompt 예시
{
"name": "analyze_table",
"description": "데이터베이스 테이블을 분석합니다",
"arguments": [
{
"name": "table_name",
"description": "분석할 테이블 이름",
"required": true
}
]
}
4. MCP 서버 구현 (TypeScript SDK)
Anthropic에서 공식 제공하는 TypeScript SDK를 사용하여 MCP 서버를 구현해봅니다.
프로젝트 초기화
# 프로젝트 생성
mkdir my-mcp-server && cd my-mcp-server
npm init -y
# MCP SDK 설치
npm install @modelcontextprotocol/sdk
# TypeScript 설정
npm install -D typescript @types/node
npx tsc --init
기본 MCP 서버 구현
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-backend-tools",
version: "1.0.0",
});
// Tool 등록: 데이터베이스 쿼리
server.tool(
"query_database",
"SQL SELECT 쿼리를 실행합니다",
{
sql: z.string().describe("실행할 SELECT SQL 쿼리"),
database: z.string().optional().describe("대상 데이터베이스"),
},
async ({ sql, database }) => {
// 안전성 검증: SELECT만 허용
if (!sql.trim().toUpperCase().startsWith("SELECT")) {
return {
content: [
{
type: "text",
text: "오류: SELECT 쿼리만 허용됩니다.",
},
],
};
}
try {
const result = await executeQuery(sql, database);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `쿼리 실행 오류: ${error.message}`,
},
],
isError: true,
};
}
}
);
// Tool 등록: API 호출
server.tool(
"call_api",
"외부 REST API를 호출합니다",
{
method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe("HTTP 메서드"),
url: z.string().url().describe("API URL"),
body: z.string().optional().describe("요청 본문 (JSON)"),
headers: z.record(z.string()).optional().describe("추가 헤더"),
},
async ({ method, url, body, headers }) => {
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
...headers,
},
body: body ? body : undefined,
});
const responseBody = await response.text();
return {
content: [
{
type: "text",
text: `Status: ${response.status}\n\n${responseBody}`,
},
],
};
}
);
// Resource 등록: DB 스키마 정보
server.resource(
"db-schema",
"postgres://mydb/schema",
async (uri) => {
const schema = await getDbSchema();
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(schema, null, 2),
},
],
};
}
);
// 서버 시작
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP 서버가 시작되었습니다.");
}
main().catch(console.error);
Streamable HTTP 전송 방식
// src/http-server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
const app = express();
const server = new McpServer({
name: "my-remote-tools",
version: "1.0.0",
});
// ... Tool, Resource 등록 (동일)
app.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport("/mcp");
await server.connect(transport);
await transport.handleRequest(req, res);
});
app.listen(3001, () => {
console.log("MCP HTTP 서버가 3001번 포트에서 시작되었습니다.");
});
5. 실무 예제: DB 연동 MCP 서버
// src/db-mcp-server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { Pool } from "pg";
import { z } from "zod";
const pool = new Pool({
host: process.env.DB_HOST || "localhost",
port: parseInt(process.env.DB_PORT || "5432"),
database: process.env.DB_NAME || "myapp",
user: process.env.DB_USER || "postgres",
password: process.env.DB_PASSWORD,
});
const server = new McpServer({
name: "postgres-mcp",
version: "1.0.0",
});
// 테이블 목록 조회 도구
server.tool(
"list_tables",
"데이터베이스의 테이블 목록을 조회합니다",
{},
async () => {
const result = await pool.query(`
SELECT table_name, table_type
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name
`);
return {
content: [
{ type: "text", text: JSON.stringify(result.rows, null, 2) },
],
};
}
);
// 테이블 스키마 조회 도구
server.tool(
"describe_table",
"테이블의 컬럼 정보를 조회합니다",
{
table_name: z.string().describe("테이블 이름"),
},
async ({ table_name }) => {
// SQL Injection 방지
const sanitized = table_name.replace(/[^a-zA-Z0-9_]/g, "");
const result = await pool.query(`
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = $1
ORDER BY ordinal_position
`, [sanitized]);
return {
content: [
{ type: "text", text: JSON.stringify(result.rows, null, 2) },
],
};
}
);
// 안전한 SELECT 쿼리 실행 도구
server.tool(
"run_select",
"SELECT 쿼리를 실행합니다 (읽기 전용)",
{
query: z.string().describe("SELECT SQL 쿼리"),
limit: z.number().optional().default(100).describe("최대 결과 수"),
},
async ({ query, limit }) => {
const upperQuery = query.trim().toUpperCase();
if (!upperQuery.startsWith("SELECT")) {
return {
content: [{ type: "text", text: "오류: SELECT만 허용됩니다." }],
isError: true,
};
}
// 위험 키워드 차단
const forbidden = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "TRUNCATE"];
if (forbidden.some(kw => upperQuery.includes(kw))) {
return {
content: [{ type: "text", text: "오류: 변경 쿼리는 허용되지 않습니다." }],
isError: true,
};
}
const limitedQuery = `${query} LIMIT ${limit}`;
const result = await pool.query(limitedQuery);
return {
content: [
{
type: "text",
text: `결과: ${result.rowCount}건\n\n${JSON.stringify(result.rows, null, 2)}`,
},
],
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
6. Claude Desktop / Claude Code에서 MCP 서버 사용
Claude Desktop 설정
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
{
"mcpServers": {
"postgres": {
"command": "node",
"args": ["/path/to/postgres-mcp/dist/index.js"],
"env": {
"DB_HOST": "localhost",
"DB_NAME": "myapp",
"DB_USER": "postgres",
"DB_PASSWORD": "secret"
}
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxx"
}
}
}
}
Claude Code에서의 설정
// .mcp.json (프로젝트 루트)
{
"mcpServers": {
"my-db": {
"command": "node",
"args": ["./mcp-servers/db-server/dist/index.js"],
"env": {
"DB_HOST": "localhost",
"DB_NAME": "devdb"
}
}
}
}
Cursor에서의 설정
// .cursor/mcp.json
{
"mcpServers": {
"postgres": {
"command": "node",
"args": ["/path/to/mcp-server/dist/index.js"],
"env": {
"DB_HOST": "localhost"
}
}
}
}
7. 공식 MCP 서버 생태계
Anthropic과 커뮤니티에서 이미 다양한 MCP 서버를 제공하고 있습니다.
| MCP 서버 | 기능 | 활용 사례 |
|---|---|---|
| @modelcontextprotocol/server-github | GitHub 리포지토리, 이슈, PR 관리 | 코드 리뷰, 이슈 분석 |
| @modelcontextprotocol/server-postgres | PostgreSQL 쿼리 실행 | 데이터 분석, 스키마 탐색 |
| @modelcontextprotocol/server-filesystem | 파일 시스템 읽기/쓰기 | 프로젝트 파일 관리 |
| @modelcontextprotocol/server-slack | Slack 메시지/채널 관리 | 팀 커뮤니케이션 자동화 |
| @modelcontextprotocol/server-puppeteer | 브라우저 자동화 | 웹 스크래핑, UI 테스트 |
| @modelcontextprotocol/server-memory | 지식 그래프 기반 메모리 | 장기 컨텍스트 유지 |
8. MCP 서버 개발 시 Best Practice
- 입력 검증을 철저히: Zod 스키마로 입력을 검증하고, SQL Injection 등 보안 위협을 방어하세요. 특히 DB 도구는 SELECT만 허용하는 등 제한을 두세요.
- 에러를 명확하게: isError 플래그와 함께 사용자가 이해할 수 있는 에러 메시지를 반환하세요. AI 모델이 에러를 해석하고 재시도할 수 있습니다.
- 설명을 상세하게: Tool과 파라미터의 description은 AI 모델이 도구를 이해하는 핵심 정보입니다. 용도, 제약 조건, 예시를 포함하세요.
- 권한 최소화: 서버가 접근하는 리소스의 권한을 최소한으로 설정하세요. DB는 읽기 전용 사용자, API는 필요한 스코프만 부여하세요.
- 타임아웃 설정: 외부 API나 DB 호출에 적절한 타임아웃을 설정하세요. AI 에이전트가 무한 대기하지 않도록 합니다.
- 로깅: stderr로 디버그 로그를 출력하세요 (stdio 전송에서 stdout은 MCP 메시지용). 문제 발생 시 디버깅에 필수적입니다.
마치며
MCP는 AI 에이전트와 외부 시스템 간의 통합을 표준화하는 중요한 진전입니다. 백엔드 개발자 관점에서 MCP의 핵심 가치를 정리합니다.
- 한 번 구축, 어디서나 사용: MCP 서버 하나를 만들면 Claude, Cursor, 기타 MCP 호환 클라이언트 어디서든 동작합니다. 도구 통합 비용이 획기적으로 줄어듭니다.
- 백엔드 개발자의 기회: 사내 시스템(DB, API, 인프라)을 MCP 서버로 래핑하면, AI 에이전트가 자연어로 시스템과 상호작용할 수 있습니다. DevOps 자동화, 데이터 분석, 장애 대응 등 다양한 활용이 가능합니다.
- 프로토콜 수준의 안전성: JSON-RPC 기반의 구조화된 통신, 입력 스키마 검증, 권한 관리를 프로토콜 수준에서 지원하므로, ad-hoc 통합보다 안전합니다.
- 성장하는 생태계: 공식 서버 외에도 커뮤니티 서버가 빠르게 늘고 있습니다. Meta Llama 4와 같은 오픈소스 모델과 MCP 연동 필요한 서버가 이미 있을 수 있으니, 공식 저장소를 먼저 확인하세요.
AI 에이전트 시대에 MCP는 백엔드 개발자가 반드시 알아야 할 프로토콜입니다. 2026 백엔드 기술 스택에서의 MCP 위상 간단한 도구 하나부터 MCP 서버로 만들어보며 감을 익혀보시길 권합니다.
'최신 트렌드' 카테고리의 다른 글
| 2026년 4월 AI 코딩·모델 총정리 - Cursor 3, Windsurf, Claude Code 그리고 프론티어 모델 3파전 (2) | 2026.04.16 |
|---|---|
| Claude Code 데스크톱 앱 대규모 리디자인 총정리 - 병렬 세션부터 Routines 자동화까지 (1) | 2026.04.16 |
| 2026 백엔드 기술 스택 총정리 - 현업 개발자의 선택과 트렌드 (1) | 2026.04.10 |
| AI 시대의 백엔드 개발자 생존 전략 - 무엇을 배우고 어떻게 적응할 것인가 (1) | 2026.04.10 |
| Spring AI로 LLM 애플리케이션 개발 - RAG부터 Function Calling까지 (1) | 2026.04.10 |