MCP 서버로 Claude를 “내 인프라에 붙이는” 방법: 2026년 5월 기준 구현 패턴과 함정 총정리
들어가며
LLM을 실무 시스템에 연결할 때 가장 큰 병목은 “모델 호출”이 아니라 컨텍스트/도구 통합의 N×M 지옥입니다. 앱(Claude Desktop, Claude Code, Cursor 등)마다 플러그인 방식이 달라 동일한 내부 API를 여러 번 래핑하게 되고, 권한/감사/에러처리도 제각각이 됩니다. Model Context Protocol(MCP)은 이 부분을 “표준화된 서버 계약(server contract)”으로 분리해, 한 번 MCP server를 만들면 여러 MCP client에서 재사용하게 해줍니다. (anthropic.com)
언제 쓰면 좋나:
- 에이전트 확장(server-side tools)이 핵심인 프로젝트(사내 DevOps bot, 데이터 조회/티켓 자동화, 레포 분석 등)
- “클라이언트별 플러그인” 대신 단일 도구 계층을 운영/감사/권한제어 하고 싶은 팀
- Claude Desktop(로컬 stdio) + Hosted(원격 HTTP) 하이브리드 배포가 필요한 경우 (ts.sdk.modelcontextprotocol.io)
언제 쓰면 안 되나:
- “모델에 파일 몇 개 넣고 끝” 같은 단발성 유틸(서버 운영비/보안 부담이 더 큼)
- 안전한 실행 경계(sandbox) 없이 모델이 곧바로 민감 시스템(배포/결제/DB쓰기)을 건드리게 할 계획인 경우
2026년 들어 MCP 생태계 전반에서 RCE/프롬프트 인젝션/공급망 위험이 실제로 문제화되고 있어(연구/보도 다수) “도구 서버=보안 경계”로 설계하지 않으면 사고가 나기 쉽습니다. (techradar.com)
🔧 핵심 개념
1) MCP에서 말하는 “서버 확장”의 실체
MCP server는 보통 아래 3가지를 노출합니다. (ts.sdk.modelcontextprotocol.io)
- Tools: 모델이 호출하는 행동(action). 네트워크 호출/계산/파일 변경처럼 side effect가 생길 수 있음
- Resources: 읽기 전용 데이터(예: 특정 문서, 스키마, 로그 스냅샷)
- Prompts: 재사용 가능한 프롬프트 템플릿(조직 표준 작업지시 등)
실무 관점에서 “에이전트 확장 서버 구축”은 결국:
- Tool schema(입력/출력 계약)
- 권한/인증/사용자 컨텍스트 바인딩
- 관측(로그/트레이싱), 타임아웃, 실패 모드 설계 이 4개를 제대로 만드는 일입니다(코드는 그 다음). 특히 2026년 현장 사례를 정리한 연구에서도 프로덕션 장애를 “계약/컨텍스트/타임아웃/에러/관측” 축으로 분류합니다. (arxiv.org)
2) 내부 작동 흐름(“왜 이렇게” 동작하나)
클라이언트(Claude Desktop/Code 등)가 MCP server를 붙일 때 대략 흐름은:
- 서버 시작/연결
- 로컬:
stdio(클라이언트가 자식 프로세스로 서버를 spawn) (support.claude.com) - 원격: Streamable HTTP(권장) 또는 HTTP+SSE(호환용) (ts.sdk.modelcontextprotocol.io)
- 로컬:
- 도구/리소스/프롬프트 목록 교환(discoverability)
- 모델이 대화 중 판단하여 tool call 생성 → 서버로 요청
- 서버는 외부 시스템 호출/처리 후 tool result 반환
- 클라이언트는 결과를 모델 컨텍스트에 반영
여기서 중요한 포인트:
- MCP는 “모델이 무엇을 할 수 있는지”를 도구 목록 자체로 학습시키므로, 도구 정의가 커지면 토큰/성능/정확도 비용이 바로 증가합니다(도구 난립은 안티패턴). (docs.claude.com)
- 2026년 기준 Claude API에는 Messages API에서 원격 MCP 서버를 직접 붙이는 MCP connector가 있고, 이 경우 “클라이언트 구현” 없이도 원격 도구 호출이 가능합니다. 단, 이 커넥터는 현재 tool calls만 지원하고 로컬 stdio 서버는 직접 연결할 수 없습니다. (platform.claude.com)
3) 다른 접근과의 차이점(비교 기준)
- “SDK로 tool calling 직접 구현” vs MCP
- 직접 구현: 앱마다 커스텀 통합이 늘고, 정책/감사/버전 호환이 분산
- MCP: 도구 계층을 서버로 모아 클라이언트 다변화에 강함
- “플러그인 마켓” vs MCP
- 마켓 기반은 배포는 편하지만 공급망 리스크가 커짐
- MCP는 표준이지만, 서버 운영자 책임(권한/격리)이 더 커집니다(최근 보안 이슈가 그 근거). (tomshardware.com)
💻 실전 코드
아래는 “현실적인 시나리오”로 사내 Kubernetes 운영 보조 에이전트용 MCP server를 만듭니다.
요구사항(현실성 포인트):
- 무조건
kubectl프리패스 금지 - allowlist된 namespace에서만 동작
- 변경 작업은 배제하고(읽기 위주), 그래도 위험한 출력은 길이 제한
- Claude Desktop(로컬)에서 바로 붙여서 검증 가능(stdio)
0) 의존성/실행 준비
1
2
3
4
5
6
7
8
9
# Node 18+ 권장(팀 표준에 맞추세요)
mkdir mcp-k8s-ops && cd mcp-k8s-ops
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D tsx typescript @types/node
# kubectl이 로컬에 설치되어 있고, 현재 kube-context가 읽기 권한을 가진다고 가정
kubectl version --client
TypeScript 실행은 tsx로 단순화합니다.
1) MCP 서버(stdio) 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
// 운영 정책: 이 서버가 “보안 경계”가 되게 만드는 핵심
const ALLOWED_NAMESPACES = new Set(
(process.env.MCP_K8S_NAMESPACES ?? "default,observability")
.split(",")
.map((s) => s.trim())
.filter(Boolean),
);
const MAX_STDOUT_CHARS = Number(process.env.MCP_MAX_STDOUT_CHARS ?? 30_000);
function assertAllowedNamespace(ns: string) {
if (!ALLOWED_NAMESPACES.has(ns)) {
throw new Error(
`namespace '${ns}' is not allowlisted. allowed=${[...ALLOWED_NAMESPACES].join(",")}`,
);
}
}
async function kubectl(args: string[]) {
// shell=true 금지: 인젝션 표면 줄이기
const { stdout, stderr } = await execFileAsync("kubectl", args, {
timeout: 15_000,
maxBuffer: 1024 * 1024 * 5,
});
const out = (stdout ?? "").slice(0, MAX_STDOUT_CHARS);
const err = (stderr ?? "").slice(0, MAX_STDOUT_CHARS);
// stderr가 있어도 일부 kubectl은 경고를 뿜으므로 함께 반환
return { out, err };
}
const server = new McpServer({
name: "k8s-ops-mcp",
version: "0.1.0",
});
// (A) 현실적인 도구 1: 장애 상황에서 제일 먼저 보는 “Pod 목록/상태”
server.tool(
"k8s_list_pods",
{
description:
"List pods in an allowlisted namespace. Read-only. Use this to triage incidents.",
inputSchema: z.object({
namespace: z.string().min(1),
labelSelector: z.string().optional().describe("kubectl -l selector"),
}),
},
async ({ namespace, labelSelector }) => {
assertAllowedNamespace(namespace);
const args = ["get", "pods", "-n", namespace, "-o", "wide"];
if (labelSelector) args.push("-l", labelSelector);
const { out, err } = await kubectl(args);
return {
content: [
{ type: "text", text: out || "(no stdout)" },
...(err ? [{ type: "text", text: `stderr:\n${err}` as const }] : []),
],
};
},
);
// (B) 현실적인 도구 2: 특정 Pod 이벤트/원인 파악용 describe
server.tool(
"k8s_describe_pod",
{
description:
"Describe a single pod (events 포함). Read-only. Use after identifying a suspect pod.",
inputSchema: z.object({
namespace: z.string().min(1),
podName: z.string().min(1),
}),
},
async ({ namespace, podName }) => {
assertAllowedNamespace(namespace);
const { out, err } = await kubectl(["describe", "pod", podName, "-n", namespace]);
return {
content: [
{ type: "text", text: out || "(no stdout)" },
...(err ? [{ type: "text", text: `stderr:\n${err}` as const }] : []),
],
};
},
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((e) => {
// stdio 서버는 “조용히 죽으면” 디버깅이 지옥입니다.
console.error("[fatal] MCP server failed:", e);
process.exit(1);
});
2) Claude Desktop / Claude Code에 붙여서 검증
- Claude Desktop은 로컬 MCP 서버를 child process + stdio로 띄우는 방식이 일반적입니다. (learn.netdata.cloud)
- Claude Code 역시 MCP 서버를 구성/관리할 수 있고, Desktop 설정을 가져오는 워크플로도 지원합니다. (docs.claude.com)
(개념 예시) claude_desktop_config.json에 서버 등록(경로/키는 환경에 따라 다를 수 있음):
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"mcpServers": {
"k8s-ops": {
"type": "stdio",
"command": "npx",
"args": ["tsx", "src/server.ts"],
"env": {
"MCP_K8S_NAMESPACES": "default,observability",
"MCP_MAX_STDOUT_CHARS": "30000"
}
}
}
}
예상 사용 흐름(대화):
- “
observabilitynamespace에서 CrashLoopBackOff인 pod 찾아줘” - Claude가
k8s_list_pods호출 → 결과에서 의심 pod 추출 - 이어서
k8s_describe_pod호출 → events/이미지풀/Probe 실패 등을 근거로 원인 제시
⚡ 실전 팁 & 함정
Best Practice (현장에서 바로 효과 나는 것)
1) 도구는 ‘원자적’이 아니라 ‘운영 단위’로 설계
get pods같은 원자 도구를 20개 늘리는 대신
“incident triage에 필요한 최소 묶음”으로 압축(예: list+filter+요약)- 이유: 도구 정의가 커질수록 모델의 선택 비용/오판 비용이 올라갑니다(토큰/주의 분산). (docs.claude.com)
2) 권한을 MCP 서버에서 강제(allowlist/denylist), 클라이언트에 기대지 말기
- “사용자 확인 UI”는 클라이언트마다 다르고(또는 없고), 자동화 환경에서는 우회됩니다.
- 서버가 정책을 enforce 해야 “확장 서버”가 보안 경계가 됩니다.
(최근 MCP 관련 보안 이슈들이 공통으로 지적하는 지점이기도 함) (techradar.com)
3) 관측 가능성(Observability) 먼저
- 최소: tool 호출마다 request-id, 호출자, 대상 리소스, duration, 성공/실패를 구조화 로깅
- 이유: 에이전트 장애는 “모델이 틀림”보다 “타임아웃/부분 실패/출력 과다”가 더 흔합니다. (arxiv.org)
흔한 함정/안티패턴
- stdio 서버에 만능 셸 도구 하나 달기:
run(command: string)같은 도구는 결국 “RCE를 제품 기능으로 만든 것”에 가깝습니다. 2026년 보안 논쟁의 핵심도 이런 실행 경계 문제로 번졌습니다. (tomshardware.com) - 도구 결과를 무제한으로 반환: 로그/스키마 덤프는 토큰 폭발 → 응답 품질 저하/비용 증가. Claude Code는 큰 출력에 경고/상한을 두는 방향으로 가이드합니다. (docs.claude.com)
- 버전 핀/클라이언트 매트릭스 없이 배포: 클라이언트가 프로토콜 버전을 올리면 서버가 깨질 수 있습니다(특히 여러 클라이언트를 지원하면 더). “클라이언트 매트릭스 테스트”를 운영 체크리스트에 넣으라는 실전 발표 자료도 있습니다. (jfokus.se)
비용/성능/안정성 트레이드오프
- 로컬 stdio: 설치/권한은 편하지만 로컬 실행 리스크가 크고, 배포/업데이트가 사용자 단으로 분산
- 원격 Streamable HTTP: 운영/관측/업데이트는 쉬우나, 인증(OAuth), 네트워크 지연, 멀티테넌시 격리가 과제 (ts.sdk.modelcontextprotocol.io)
- “Claude API MCP connector”는 클라이언트 구현 부담을 줄이지만, 2026년 5월 기준 제약(예: tool calls 중심, https 공개 필요, ZDR 불가 등)을 감안해야 합니다. (platform.claude.com)
🚀 마무리
정리하면, 2026년 5월의 MCP는 “장난감 플러그인 규격”이 아니라 에이전트 확장 서버를 제품 수준으로 운영하기 위한 표준 계약으로 굳어지는 흐름입니다(공식 SDK/다중 클라이언트/원격 커넥터까지). (modelcontextprotocol.io)
다만 동시에, MCP 서버는 곧 권한과 실행의 경계가 되므로(특히 stdio/local), 보안·격리·정책 강제가 설계의 1순위여야 합니다. (arxiv.org)
도입 판단 기준(현실적인 체크):
- 우리 조직은 “도구 통합을 여러 클라이언트에서 재사용”할 필요가 있는가?
- 도구 호출을 감사 가능하게 만들 요구가 있는가?
- 서버 측에서 권한/출력/타임아웃/관측을 강제할 운영 역량이 있는가?
- (Yes가 많다면) MCP server를 먼저 “읽기 전용/저위험 도구”부터 깔고, 점진적으로 쓰기 작업을 확장하세요.
다음 학습 추천:
- MCP 공식 SDK 문서에서 Streamable HTTP(원격) 서버 예제를 직접 돌려보고, 우리 인프라 인증(OAuth)과 로깅을 붙여 “프로덕션 형태”로 확장 (ts.sdk.modelcontextprotocol.io)
- Claude API의 MCP connector 제약/버전 헤더를 확인한 뒤, “서버 하나로 Desktop+API 양쪽”을 만족하는 배포 전략 수립 (platform.claude.com)