MCP 서버를 “에이전트 확장 레이어”로 쓰는 법: 2026년 6월 기준 Claude 연동까지 끝내는 구현 가이드
들어가며
LLM/Agent를 실제 업무 시스템에 붙일 때 가장 큰 비용은 “모델 ↔ 툴/데이터 소스” 연결부가 매번 커스텀으로 증식하는 문제입니다. MCP(Model Context Protocol)는 이 M×N 통합 폭발을 “표준화된 서버/클라이언트” 구조로 줄이려는 프로토콜이고, Claude(Desktop/Claude Code/claude.ai 등)에서 MCP 서버를 붙여 도구(tools)·리소스(resources)·프롬프트(prompts)를 에이전트에게 노출하는 방식이 사실상 업계 표준으로 굳었습니다. (anthropic.com)
언제 쓰면 좋은가
- 내부 API/DB/티켓/런북/배포 시스템 등 “에이전트가 자주 호출하는 기능”을 일관된 계약(schema)으로 제공하고 싶을 때
- IDE/Claude Code/데스크톱 클라이언트 등 여러 클라이언트에서 한 번 구현해 재사용하고 싶을 때 (docs.anthropic.com)
- 에이전트에게 “검색 → 조치”를 시키되, 툴 경계를 명확히 해서 감사/권한통제를 하고 싶을 때
언제 쓰면 안 되는가
- 툴 호출이 거의 없고 단순 Q&A 위주라서 통합 레이어 유지비가 더 큰 경우
- 보안 경계가 불명확한 상태에서 “로컬 stdio 서버를 마구 실행”해야 하는 조직(2026년 들어 MCP 생태계에서 RCE/공급망 이슈가 반복적으로 지적됨). 특히 stdio는 “내 머신 권한으로 프로세스를 실행”하는 형태라 운영/감사 체계를 먼저 잡아야 합니다. (techradar.com)
🔧 핵심 개념
1) MCP가 표준화하는 것: “컨텍스트 제공 인터페이스”
MCP 서버는 단순히 함수 엔드포인트를 제공하는 게 아니라, 에이전트가 활용할 컨텍스트를 세 가지 축으로 모델링합니다.
- Tools: 실행 가능한 액션(예:
deploy_service,create_jira_ticket) - Resources: 조회 가능한 데이터(예:
runbook://payments/rollback,db://analytics/query) - Prompts: 특정 작업을 잘 수행하도록 서버가 제공하는 재사용 프롬프트 템플릿
이 구조 덕분에 “API 문서 읽고 맞춰 호출”이 아니라, 서버가 스키마/의미를 명시적으로 노출하고 클라이언트(Claude 등)가 이를 발견(discovery)해서 에이전트에게 제공합니다. (docs.anthropic.com)
2) 내부 작동 흐름(서버 관점)
TypeScript SDK 기준으로 보면, 핵심은 McpServer에 툴/리소스/프롬프트를 등록하고, transport를 붙여 JSON-RPC로 주고받는 모델입니다. (ts.sdk.modelcontextprotocol.io)
- Transport 선택
- Streamable HTTP: 원격(프로덕션) 서버에 권장(현행 “추천”) (ts.sdk.modelcontextprotocol.io)
- HTTP + SSE: deprecated(레거시 호환) (ts.sdk.modelcontextprotocol.io)
- stdio: 로컬에서 클라이언트가 서버 프로세스를 spawn(가장 개발이 쉽지만 보안/운영 리스크가 큼) (ts.sdk.modelcontextprotocol.io)
에이전트 호출 흐름(실무적으로 중요한 포인트)
1) 클라이언트가 서버에 연결 → 서버 capability/툴 목록을 가져옴
2) 모델이 계획 수립(“어떤 툴을 어떤 입력으로 호출할지”)
3) 툴 호출(JSON-RPC) → 서버는 실제 외부 시스템 호출/검증/로그 기록
4) 결과를 모델에게 반환 → 모델이 후속 툴 호출 또는 최종 응답 작성
여기서 “서버”가 해야 할 일은 단순 실행이 아니라,
- 입력 검증/권한 체크
- 외부 호출의 idempotency/timeout/retry
- 결과 축약(모델 토큰/민감정보 제약)
- 감사 로그(누가 어떤 툴을 어떤 파라미터로 호출했는지) 까지 포함됩니다. 즉 MCP 서버는 에이전트 확장 서버(Agent Extension Server)로 보는 게 맞습니다.
3) 다른 접근과의 차이점
- 기존: 각 클라이언트/프레임워크별 Tool Calling 스펙에 맞춰 개별 구현 → 통합 부채 폭발
- MCP: 서버가 표준 JSON-RPC 계약으로 툴/리소스/프롬프트를 제공 → Claude/IDE 등 다양한 클라이언트가 동일 방식으로 연결 (docs.anthropic.com)
- “함수 호출”만 제공하는 것보다 한 단계 위: 서버가 컨텍스트 패키징의 책임을 갖기 때문에 에이전트 품질이 재현 가능해집니다.
💻 실전 코드
아래는 “실제 팀에서 흔한” 시나리오로 구성했습니다.
- 목적: 운영자가 매번 수동으로 하던 Kubernetes 배포/롤백 + 변경 기록(JIRA/티켓 대신 간단히 파일 기록)을 MCP tool로 제공
- 특징:
- toy가 아니라 “외부 커맨드(kubectl/helm) 실행”이 들어감
- 입력 검증, dry-run, timeout, 출력 요약을 포함
- 로컬에서는 stdio로 Claude Desktop/Claude Code에 붙여 빠르게 검증 후, 원격 Streamable HTTP로 이관 가능한 구조
1) 초기 셋업
mkdir mcp-ops-server && cd mcp-ops-server
npm init -y
npm i @modelcontextprotocol/sdk zod execa
npm i -D typescript tsx @types/node
npx tsc --init
2) MCP 서버(stdio) 구현: src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { execa } from "execa";
import fs from "node:fs/promises";
const server = new McpServer({ name: "ops-mcp", version: "1.0.0" });
/**
* 현실 포인트:
* - 모델이 이상한 값을 넣을 수 있으니 zod로 강검증
* - kubectl/helm 호출은 timeout 필수
* - 출력은 길어질 수 있으니 요약해서 반환
*/
const DeployInput = z.object({
namespace: z.string().min(1),
release: z.string().min(1),
chart: z.string().min(1), // 예: "./charts/payments"
imageTag: z.string().min(1), // 예: "2026.06.30-1234"
dryRun: z.boolean().default(false),
});
server.tool(
"deploy_service",
{
description: "Deploy a Helm release to Kubernetes with a specific image tag. Supports dry-run.",
inputSchema: DeployInput,
},
async (input) => {
const { namespace, release, chart, imageTag, dryRun } = DeployInput.parse(input);
const args = [
"upgrade",
"--install",
release,
chart,
"-n",
namespace,
"--set",
`image.tag=${imageTag}`,
"--history-max",
"20",
"--wait",
"--timeout",
"5m",
];
if (dryRun) args.push("--dry-run");
// kubectl/helm은 stderr에도 정상 로그를 뿌리는 경우가 있어 둘 다 수집
const startedAt = Date.now();
const cmd = await execa("helm", args, { timeout: 6 * 60 * 1000, reject: false });
const durationMs = Date.now() - startedAt;
// 변경 기록(예: 내부 티켓 시스템 대신 파일로 남김. 실무에선 DB/로그로 교체)
await fs.appendFile(
"./deploy-log.ndjson",
JSON.stringify({
ts: new Date().toISOString(),
namespace,
release,
chart,
imageTag,
dryRun,
exitCode: cmd.exitCode,
durationMs,
}) + "\n",
"utf-8"
);
const output = [cmd.stdout, cmd.stderr].filter(Boolean).join("\n");
const summarized =
output.length > 4000 ? output.slice(0, 4000) + "\n...(truncated)" : output;
return {
content: [
{
type: "text",
text:
`helm ${args.join(" ")}\n` +
`exitCode=${cmd.exitCode}, durationMs=${durationMs}\n\n` +
summarized,
},
],
// 모델이 후속 조치를 판단할 수 있게 "성공/실패"를 명확히
isError: cmd.exitCode !== 0,
};
}
);
server.tool(
"rollback_service",
{
description: "Rollback a Helm release to a previous revision.",
inputSchema: z.object({
namespace: z.string().min(1),
release: z.string().min(1),
revision: z.number().int().positive(),
}),
},
async (input) => {
const { namespace, release, revision } = z
.object({
namespace: z.string().min(1),
release: z.string().min(1),
revision: z.number().int().positive(),
})
.parse(input);
const args = ["rollback", release, String(revision), "-n", namespace, "--wait", "--timeout", "5m"];
const cmd = await execa("helm", args, { timeout: 6 * 60 * 1000, reject: false });
return {
content: [{ type: "text", text: `helm ${args.join(" ")}\nexitCode=${cmd.exitCode}\n${cmd.stdout}\n${cmd.stderr}` }],
isError: cmd.exitCode !== 0,
};
}
);
async function main() {
// stdio transport: 로컬에서 Claude Desktop/Claude Code가 서버 프로세스를 직접 실행
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
// 주의: stdout에 찍으면 JSON-RPC가 깨질 수 있음.
// stderr만 사용.
console.error(err);
process.exit(1);
});
3) 실행 & 예상 출력(서버 단독 테스트)
npx tsx src/server.ts
이 상태로는 “클라이언트가 붙어야” 요청이 들어옵니다. (서버가 stdout으로 JSON-RPC를 주고받기 때문)
4) Claude Code에 붙이기(로컬 stdio 서버)
Claude Code는 claude mcp add-json로 stdio 서버를 추가할 수 있습니다. (code.claude.com)
claude mcp add-json ops-mcp \
'{"type":"stdio","command":"node","args":["--enable-source-maps","./node_modules/.bin/tsx","src/server.ts"],"env":{"NODE_ENV":"development"}}'
이후 Claude Code에서 /mcp를 통해 서버가 인식되면 deploy_service, rollback_service가 도구 목록으로 노출됩니다.
⚡ 실전 팁 & 함정
Best Practice
1) “stdio는 개발용, 원격은 Streamable HTTP”로 분리
TypeScript SDK 문서에서도 원격 서버는 Streamable HTTP를 권장하고, SSE는 deprecated로 분류합니다. (ts.sdk.modelcontextprotocol.io)
팀 확장(여러 명이 같은 툴 사용)까지 고려하면 결국 원격 배포가 필요하니, 초기에 “transport 추상화”를 염두에 두세요.
2) Tool 입력 스키마를 강하게 + 결과는 축약 모델은 종종 “있을 법한 값”을 만들어냅니다. zod 같은 검증을 통과하지 못하면 즉시 실패시키고, stdout 전체를 그대로 반환하지 말고(토큰/민감정보/노이즈) 요약+트렁케이트 전략을 기본값으로 두는 게 운영에 유리합니다.
3) OAuth/headersHelper 같은 인증 전략을 표준 흐름에 얹기
Claude Code는 HTTP 기반 MCP 서버에 대해 OAuth 설정/스코프 제한/메타데이터 discovery 우회 같은 옵션을 제공합니다. 특히 oauth.scopes로 권한 범위를 “보안팀 승인된 최소치”로 고정할 수 있습니다. (code.claude.com)
사내 API는 OAuth가 아니더라도 headersHelper로 런타임 토큰을 주입하는 패턴이 꽤 실용적입니다(다만 임의 쉘 실행이라는 위험을 인지해야 함). (code.claude.com)
흔한 함정/안티패턴
- stdout에 로그 찍기: stdio transport는 stdout이 프로토콜 채널이라, 서버 시작 시
console.log()한 줄로도 연결이 깨질 수 있습니다(로그는 stderr로). 실제 사용자 경험담에서도 이 문제가 반복됩니다. (reddit.com) - 무권한/무감사로 “배포/삭제” 툴 제공: MCP 서버는 사실상 “에이전트가 사용하는 운영자 권한 API”입니다. 호출자 식별, 승인 흐름(최소한의 human-in-the-loop), 감사 로그 없이는 사고가 납니다.
- MCP 마켓/예제 서버를 검증 없이 복붙: 2026년에도 공급망/툴 오염(tool poisoning)/RCE 계열 우려가 지속적으로 제기됩니다. “내 PC에서 실행되는 커맨드 목록”으로 취급하고 코드/릴리즈 노트를 리뷰하세요. (techradar.com)
비용/성능/안정성 트레이드오프
- stdio: 제일 빠른 개발/디버그 vs 로컬 권한 그대로 실행(리스크 큼)
- 원격 HTTP: 운영/권한/감사에 유리 vs OAuth/네트워크/배포 비용 증가
- 툴 출력량: 많이 주면 모델이 잘 판단 vs 토큰 비용/응답 지연/민감정보 유출 위험 증가
→ 실무에서는 “짧은 결과 + 필요 시 follow-up resource/tool로 추가 조회” 형태가 안정적입니다.
🚀 마무리
핵심은 MCP를 “Claude에 툴 몇 개 붙이는 플러그인”이 아니라, 에이전트 확장 서버(Agent Extension Server)로 설계하는 겁니다.
- 로컬에서 stdio로 빠르게 검증하고(단, stdout 오염 금지) (ts.sdk.modelcontextprotocol.io)
- 원격으로 갈 때는 Streamable HTTP + OAuth/스코프 최소화 + 감사 로그를 기본 구성으로 잡는 게 2026년 현실적인 정답입니다. (ts.sdk.modelcontextprotocol.io)
- 보안 이슈(RCE/공급망/툴 오염)는 “남 얘기”가 아니라 MCP의 구조적 리스크로 계속 거론되고 있으니, 프로덕션 도입 시 위협 모델링을 선행하세요. (tomshardware.com)
도입 판단 기준(요약)
- 툴 호출이 잦고, 여러 클라이언트/에이전트에서 재사용해야 한다 → MCP 서버 가치 큼
- 운영 권한을 다루거나 데이터가 민감하다 → 원격/인증/감사 체계 없으면 도입 보류
- “일단 붙여보자” 단계 → stdio로 작게 시작하되, 배포/삭제 같은 파괴적 툴은 처음부터 분리(승인 필요)
다음 학습으로는:
- MCP 공식 문서의 Architecture/Servers 섹션(transport/보안 가이드) (docs.anthropic.com)
- Claude Code의 MCP 설정(OAuth discovery, scopes, headersHelper) (code.claude.com) 을 순서대로 보면 “내 조직에 맞는 운영 모델”까지 빠르게 설계할 수 있습니다.