중요: 이 스킬 사용 방법
이 파일은 탐색 가이드만을 제공합니다. MCP 서버 기능을 구현하기 전에 반드시:
- 이 개요를 읽고 어떤 레퍼런스 파일이 관련 있는지 파악하세요.
- 구현 중인 기능에 대해 특정 레퍼런스 파일을 항상 읽으세요.
- 해당 파일의 상세 패턴을 구현에 적용하세요.
이 파일의 빠른 참조 예시에만 의존하지 마세요 - 이는 최소한의 예시일 뿐입니다. 레퍼런스 파일에는 중요한 모범 사례, 보안 고려 사항, 고급 패턴이 포함되어 있습니다.
MCP 서버 모범 사례
mcp-use를 사용하여 도구, 리소스, 프롬프트, 위젯을 갖춘 생산 준비 MCP 서버를 구축하기 위한 포괄적인 가이드입니다.
⚠️ 먼저: 새 프로젝트인가요? 기존 프로젝트인가요?
다른 작업을 하기 전에, 현재 mcp-use 프로젝트 내에 있는지 확인하세요.
감지 방법: 워크스페이스에서 "mcp-use"를 종속성으로 나열하는 package.json이 있는지, 또는 "mcp-use/server"에서 임포트하는 .ts 파일이 있는지 확인하세요.
├─ mcp-use 프로젝트 발견됨 → 스캐폴딩하지 마세요. 이미 프로젝트 내에 있습니다.
│ └─ 아래 "빠른 탐색"으로 이동하여 기능을 추가하세요.
│
├─ mcp-use 프로젝트 없음 (빈 디렉토리, 관련 없는 프로젝트, 또는 신규)
│ └─ 먼저 npx create-mcp-use-app으로 스캐폴딩한 후 기능을 추가하세요.
│ 아래 "새 프로젝트 스캐폴딩"을 참조하세요.
│
└─ 관련 없는 프로젝트(예: Next.js 앱) 내부에 있으며 사용자가 MCP 서버를 원하는 경우
└─ 사용자에게 어디에 생성할지 물어본 후 해당 디렉토리에 스캐폴딩하세요.
기존 관련 없는 프로젝트 루트 내부에 스캐폴딩하지 마세요.
절대로 수동으로 MCPServer 보일러플레이트, package.json 또는 프로젝트 구조를 만들지 마세요. CLI는 TypeScript 설정, 개발 스크립트, 인스펙터 통합, 핫 리로드, 수동으로 복제하기 어려운 위젯 컴파일을 설정합니다.
새 프로젝트 스캐폴딩
npx create-mcp-use-app my-server
cd my-server
npm run dev
전체 스캐폴딩 세부 정보 및 CLI 플래그는 quickstart.md 를 참조하세요.
빠른 탐색
빌드 중인 내용에 따라 경로를 선택하세요:
🚀 기초
시기: MCP 작업을 새 대화에서 시작할 때 항상 먼저 읽으세요. 이후 아키텍처/개념 명확화를 위해 참조하세요.
- concepts.md - MCP 프리미티브(도구, 리소스, 프롬프트, 위젯) 및 각각을 사용해야 하는 경우
- architecture.md - 서버 구조(Hono 기반), 미들웨어 시스템, server.use() vs server.app
- quickstart.md - 스캐폴딩, 설정, 첫 번째 도구 예제
- deployment.md - Manufact Cloud 배포, 자체 호스팅, Docker, 배포 관리
도구/리소스/위젯 섹션으로 들어가기 전에 이 파일들을 로드하세요.
🔐 인증 추가?
시기: OAuth(WorkOS, Supabase 또는 커스텀)로 서버 보호
-
- 시기: 처음으로 인증 추가,
ctx.auth이해, 또는 공급자 선택 - 내용:
oauth설정, 사용자 컨텍스트 형태, 공급자 비교, 일반적인 실수
- 시기: 처음으로 인증 추가,
-
- 시기: WorkOS AuthKit 사용
- 내용: 설정, 환경 변수, DCR vs 사전 등록, 역할/권한, WorkOS API 호출
-
- 시기: Supabase 사용
- 내용: 설정, 환경 변수, HS256 vs ES256, RLS 인식 API 호출
-
- 시기: 다른 ID 공급자(GitHub, Okta, Azure AD, Google 등) 사용
- 내용: 커스텀 검증, 사용자 정보 추출, 공급자 예제
🔧 서버 백엔드 구축(UI 없음)?
시기: MCP 기능(작업, 데이터, 템플릿) 구현. 빌드 중인 프리미티브에 대한 특정 파일을 읽으세요.
-
- 시기: AI가 호출할 백엔드 작업 생성(이메일 보내기, 데이터 가져오기, 사용자 생성)
- 내용: 도구 정의, 스키마, 어노테이션, 컨텍스트, 오류 처리
-
- 시기: 클라이언트가 가져올 수 있는 읽기 전용 데이터 노출(설정, 사용자 프로필, 문서)
- 내용: 정적 리소스, 동적 리소스, 매개변수화된 리소스 템플릿, URI 완성
-
- 시기: AI 상호 작용을 위한 재사용 가능한 메시지 템플릿 생성(코드 리뷰, 요약)
- 내용: 프롬프트 정의, 매개변수화, 인수 완성, 프롬프트 모범 사례
-
- 시기: 도구/리소스의 응답 형식 지정(텍스트, JSON, 마크다운, 이미지, 오류)
- 내용:
text(),object(),markdown(),image(),error(),mix()
🎨 시각적 위젯 구축(대화형 UI)?
시기: 데이터 탐색, 비교 또는 선택을 위한 React 기반 시각적 인터페이스 생성
-
- 시기: 첫 번째 위젯 생성 또는 기존 도구에 UI 추가
- 내용: 위젯 설정,
useWidget()훅,isPending확인, props 처리
-
- 시기: 위젯 내 UI 상태 관리(선택, 필터, 탭)
- 내용:
useState,setState, 상태 지속성, 도구 vs 위젯 상태 사용 시기
-
- 시기: 버튼, 양식 추가 또는 위젯 내에서 도구 호출
- 내용:
useCallTool(), 양식 처리, 작업 버튼, 낙관적 업데이트
-
- 시기: 테마 지원, 반응형 레이아웃, 접근성을 위한 위젯 스타일링
- 내용:
useWidgetTheme(), 라이트/다크 모드,autoSize, 레이아웃 패턴, CSS 모범 사례
-
- 시기: 비동기 데이터, 오류 경계, 성능 최적화가 있는 복잡한 위젯 구축
- 내용: 로딩 상태, 오류 처리, 메모이제이션, 코드 분할
📚 전체 예제가 필요하신가요?
시기: 일반적인 사용 사례의 전체 구현을 보고 싶을 때
- common-patterns.md
- 종단 간 예제: 날씨 앱, 할일 목록, 레시피 브라우저
- 내용: 서버 코드 + 위젯 코드 + 컨텍스트 내 모범 사례
결정 트리
무엇이 필요하신가요?
├─ 처음부터 새 프로젝트
│ └─> quickstart.md (스캐폴딩 + 설정)
│
├─ OAuth / 사용자 인증
│ └─> authentication/overview.md → 공급자별 가이드
│
├─ 간단한 백엔드 작업 (UI 없음)
│ └─> 도구 사용: server/tools.md
│
├─ 클라이언트용 읽기 전용 데이터
│ └─> 리소스 사용: server/resources.md
│
├─ 재사용 가능한 프롬프트 템플릿
│ └─> 프롬프트 사용: server/prompts.md
│
├─ 시각적/대화형 UI
│ └─> 위젯 사용: widgets/basics.md
│
└─ 프로덕션 배포
└─> deployment.md (클라우드 배포, 자체 호스팅, Docker)
핵심 원칙
- 도구는 작업용 - 입출력이 있는 백엔드 작업
- 리소스는 데이터용 - 클라이언트가 가져올 수 있는 읽기 전용 데이터
- 프롬프트는 템플릿용 - 재사용 가능한 메시지 템플릿
- 위젯은 UI용 - 필요할 때 시각적 인터페이스
- 먼저 모의 데이터 - 빠르게 프로토타입 만들고, 나중에 API 연결
❌ 일반적인 실수
프로덕션 MCP 서버에서 발견된 안티패턴을 피하세요:
도구 정의
- ❌ 응답 헬퍼 대신 원시 객체 반환
- ✅
text(),object(),widget(),error()헬퍼 사용
- ✅
- ❌ 모든 필드에 Zod 스키마
.describe()생략- ✅ 더 나은 AI 이해를 위해 모든 스키마 필드에 설명 추가
- ❌ 입력 검증 또는 살균 누락
- ✅ Zod로 입력 검증, 사용자 제공 데이터 살균
- ❌
error()헬퍼 대신 오류 throw- ✅ 우아한 오류 응답을 위해
error("message")사용
- ✅ 우아한 오류 응답을 위해
위젯 개발
- ❌
isPending확인 없이props접근- ✅ 항상
if (isPending) return <Loading/>확인
- ✅ 항상
- ❌ 위젯이 서버 상태 처리(필터, 선택)
- ✅ 위젯은
useState로 자체 UI 상태 관리
- ✅ 위젯은
- ❌
McpUseProvider래퍼 또는autoSize누락- ✅ 루트 컴포넌트 래핑:
<McpUseProvider autoSize>
- ✅ 루트 컴포넌트 래핑:
- ❌ 테마 인식 없는 인라인 스타일
- ✅ 라이트/다크 모드 지원을 위해
useWidgetTheme()사용
- ✅ 라이트/다크 모드 지원을 위해
보안 및 프로덕션
- ❌ 코드에 하드코딩된 API 키 또는 시크릿
- ✅
process.env.API_KEY사용,.env.example에 문서화
- ✅
- ❌ 도구 핸들러에 오류 처리 없음
- ✅ try/catch로 감싸고, 실패 시
error()반환
- ✅ try/catch로 감싸고, 실패 시
- ❌ 캐싱 없는 비용이 많이 드는 작업
- ✅ TTL로 API 호출, 계산 캐싱
- ❌ CORS 구성 누락
- ✅ 프로덕션 배포를 위해 CORS 구성
🔒 황금 규칙
독단적인 아키텍처 지침:
1. 하나의 도구 = 하나의 기능
광범위한 작업을 집중된 도구로 분할하세요:
- ❌
manage-users(너무 모호함) - ✅
create-user,delete-user,list-users
2. 완전한 데이터를 미리 반환
도구 호출은 비용이 많이 듭니다. 지연 로딩을 피하세요:
- ❌
list-products+get-product-details(2번 호출) - ✅
list-products는 세부 정보를 포함한 전체 데이터 반환
3. 위젯은 자체 상태 소유
UI 상태는 별도의 도구가 아닌 위젯에 있습니다:
- ❌
select-item도구,set-filter도구 - ✅ 위젯이
useState또는setState로 관리
4. exposeAsTool 기본값은 false
위젯은 기본적으로 리소스로만 등록됩니다. 모델에 위젯을 노출하려면 커스텀 도구(권장)를 사용하거나 exposeAsTool: true를 설정하세요:
// ✅ 올바른 타입 추론을 위해 4단계 모두 필요:
// 1단계: 스키마를 별도로 정의
const propsSchema = z.object({
title: z.string(),
items: z.array(z.string()),
});
// 2단계: 메타데이터에서 스키마 변수 참조
export const widgetMetadata: WidgetMetadata = {
description: "...",
props: propsSchema, // ← 인라인 z.object() 아님
exposeAsTool: false,
};
// 3단계: 스키마 변수에서 Props 타입 추론
type Props = z.infer<typeof propsSchema>;
// 4단계: useWidget과 함께 타입화된 Props 사용
export default function MyWidget() {
const { props, isPending } = useWidget<Props>(); // ← <Props> 추가
// ...
}
⚠️ 일반적인 실수: 1-2단계만 수행하고 3-4단계 건너뛰기(타입 안전성 상실)
5. 경계에서만 검증
- 내부 코드 및 프레임워크 보장 신뢰
- 사용자 입력, 외부 API 응답 검증
- 발생할 수 없는 시나리오에 대한 오류 처리 추가 금지
6. 탐색/비교에는 위젯 선호
의심스러우면 위젯을 추가하세요. 시각적 UI는 다음을 개선합니다:
- 여러 항목 탐색
- 데이터 나란히 비교
- 대화형 선택 워크플로
빠른 참조
최소 서버
import { MCPServer, text } from "mcp-use/server";
import { z } from "zod";
const server = new MCPServer({
name: "my-server",
title: "My Server",
version: "1.0.0",
});
server.tool(
{
name: "greet",
description: "사용자에게 인사합니다",
schema: z.object({ name: z.string().describe("사용자의 이름") }),
},
async ({ name }) => text("안녕하세요 " + name + "님!"),
);
server.listen();
응답 헬퍼
| 헬퍼 | 사용 시기 | 예제 |
|---|---|---|
text() | 단순 문자열 응답 | text("성공!") |
object() | 구조화된 데이터 | object({ status: "ok" }) |
markdown() | 서식 있는 텍스트 | markdown("# 제목\n내용") |
widget() | 시각적 UI | widget({ props: {...}, output: text(...) }) |
mix() | 여러 콘텐츠 | mix(text("안녕"), image(url)) |
error() | 오류 응답 | error("데이터 가져오기 실패") |
resource() | 리소스 참조 포함 | resource("docs://guide", "text/markdown") |