소개: 프로토콜에서 코드까지
이전 기사에서 우리는 기본 사항을 탐구했습니다. 모델 컨텍스트 프로토콜, 3시 기본 요소(도구, 리소스, 프롬프트) 및 모노레포 아키텍처. 이제 코드를 작성할 시간입니다. 이 튜토리얼에서 우리는 TypeScript로 MCP 서버 완성, 프로젝트에서 시작 Claude Desktop으로 테스트할 수 있는 작업 서버에 도달할 때까지 비어 있습니다.
우리가 만들 서버는 다음과 같은 시스템을 관리합니다. 기억 속 메모, 추가할 도구 노출, 메모를 읽고, 검색하고, 삭제할 수 있습니다. 우리는 고급 검증을 구현할 것입니다. 조드, 구조화된 오류 처리 및 URI를 통해 메모에 액세스하기 위한 리소스. 모든 코드 사용 가능 저장소에 GitHub의 Tech-MCP.
이 기사에서 배울 내용
- MCP 서버용 TypeScript 프로젝트를 설정하는 방법(package.json, tsconfig.json)
- 클래스를 사용하여 서버를 만드는 방법
McpServer공식 SDK의 - 도구를 등록하는 방법
server.tool()Zod 구성표로 입력 유효성을 검사합니다. - 리소스를 노출하는 방법
server.resource()및 URI 템플릿 - 패턴 오류를 처리하는 방법
isError: true - 전송을 구성하는 방법
StdioServerTransport - MCP Inspector 및 Claude Desktop을 사용하여 서버를 테스트하는 방법
- 디버깅 규칙: 절대 사용하지 않는 이유
console.log()
전제 조건
시작하기 전에 시스템에 다음 도구가 설치되어 있는지 확인하십시오.
- Node.js 18+: JavaScript/TypeScript 런타임(
node --version확인하기 위해) - npm o pnpm: 종속성을 관리하는 패키지 관리자
- 타입스크립트 5.x: 서버 전체에서 사용할 입력 언어
- 클로드 데스크탑 (선택 사항): 실제 MCP 클라이언트로 서버를 테스트하려면
1단계: 프로젝트 초기화
우리는 처음부터 프로젝트 구조를 만듭니다. 우리 서버가 호출됩니다 mcp-notes-server
TypeScript를 사용하는 Node.js 프로젝트가 될 것입니다.
디렉토리 생성 및 초기화
# Crea la directory del progetto
mkdir mcp-notes-server && cd mcp-notes-server
# Inizializza il progetto Node.js
npm init -y
# Installa le dipendenze di produzione
npm install @modelcontextprotocol/sdk zod
# Installa le dipendenze di sviluppo
npm install -D typescript @types/node
# Crea la directory per il codice sorgente
mkdir -p src
설치된 종속성에는 특정 역할이 있습니다.
- @modelcontextprotocol/sdk: 공식 MCP SDK는 다음을 제공합니다.
McpServer, 전송 및 프로토콜 유틸리티 - 조드: MCP가 도구 매개변수를 정의하고 유효성을 검사하는 데 사용하는 스키마 유효성 검사 라이브러리
- 타이프스크립트: 정적 타이핑을 위한 TypeScript 컴파일러
- @유형/노드: Node.js API에 대한 유형 정의
Package.json 구성
파일 업데이트 package.json MCP 서버에 필요한 구성으로 생성되었습니다.
분야 "type": "module" MCP SDK는 ESM(ECMAScript 모듈)을 사용하기 때문에 기본입니다.
{
"name": "mcp-notes-server",
"version": "1.0.0",
"description": "Server MCP per la gestione di note",
"type": "module",
"main": "dist/index.js",
"bin": {
"mcp-notes": "dist/index.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"start": "node dist/index.js",
"inspect": "npx @modelcontextprotocol/inspector dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.22.0"
},
"devDependencies": {
"typescript": "^5.5.0",
"@types/node": "^20.0.0"
}
}
필드 참고 "유형": "모듈"
분야 "type": "module" 그리고 필수. 이것이 없으면 Node.js가 파일을 처리합니다. .js
CommonJS와 마찬가지로 MCP SDK 가져오기는 구문 오류로 인해 실패합니다. 이 필드를 잊어버린 경우
다음과 같은 오류가 표시됩니다. SyntaxError: Cannot use import statement in a module.
TypeScript 구성(tsconfig.json)
파일 만들기 tsconfig.json 프로젝트 루트에서. 구성이 호환되어야 합니다.
ESM 및 Node.js 16+ 사용:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true
},
"include": ["src"],
"exclude": ["node_modules"]
}
가장 중요한 구성 필드는 다음과 같습니다.
- 대상: "ES2022": 최상위 수준 대기를 지원하는 최신 버전의 JavaScript로 컴파일합니다.
- 모듈: "Node16": Node.js 16+ ESM 시스템과 호환되는 모듈을 생성합니다.
- moduleResolution: "Node16": Node.js 16 규칙에 따라 모듈을 해결합니다(가져올 때 .js 확장이 필요함)
- 엄격한: 사실: 보안 강화를 위해 모든 엄격한 유형 검사를 활성화합니다.
- 선언: 사실: 파일 생성
.d.ts외부 유형 검사용
프로젝트의 최종 구조
설정이 끝나면 프로젝트 구조는 다음과 같습니다.
mcp-notes-server/
src/
index.ts # Entry point e logica del server
package.json # Configurazione npm con type: module
tsconfig.json # Configurazione TypeScript
node_modules/ # Dipendenze installate
2단계: 최소 MCP 서버 생성
단일 도구를 등록하는 최소 서버부터 시작합니다. 파일 만들기 src/index.ts:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// ============================================
// Stato in memoria
// ============================================
const notes: Map<string, string> = new Map();
// ============================================
// Creazione del server MCP
// ============================================
const server = new McpServer({
name: "notes-server",
version: "1.0.0",
});
// ============================================
// Tool: aggiungere una nota
// ============================================
server.tool(
"add-note",
"Aggiunge una nuova nota con titolo e contenuto",
{
title: z.string().describe("Titolo della nota"),
content: z.string().describe("Contenuto della nota"),
},
async ({ title, content }) => {
notes.set(title, content);
return {
content: [
{
type: "text",
text: `Nota "${title}" salvata con successo.`,
},
],
};
},
);
// ============================================
// Avvio del server con transport STDIO
// ============================================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Notes MCP Server avviato su stdio");
}
main().catch((error) => {
console.error("Errore fatale:", error);
process.exit(1);
});
코드 분석
각 구성 요소의 역할을 이해하기 위해 코드의 각 섹션을 분석해 보겠습니다.
1. 셰방과 수입품
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
Lo 오두막 (#!/usr/bin/env node)를 사용하면 파일을 다음과 같이 직접 실행할 수 있습니다.
명령줄에서 스크립트를 실행합니다. 가져오기는 세 가지 필수 구성요소를 로드합니다.
- Mcp서버: 프로토콜 수명주기, 도구 등록 및 기능 협상을 관리하는 메인 클래스
- StdioServerTransport: 프로세스의 stdin/stdout을 통해 통신하는 전송
- z (Zod): 매개변수 체계 정의를 위한 검증 라이브러리
2. 서버 인스턴스 생성
const server = new McpServer({
name: "notes-server", // Identificativo univoco del server
version: "1.0.0", // Versione seguendo il formato semver
});
인스턴스 McpServer 다음을 사용하여 구성 객체를 받아들입니다. name (식별자
클라이언트가 서버를 인식하는 데 사용하는 고유 코드) e version (semver 형식 버전).
이 데이터는 프로토콜 초기화 단계에서 클라이언트로 전송됩니다.
3. server.tool()의 서명
방법 server.tool() 공구등록의 핵심입니다. 다음 네 가지 인수를 허용합니다.
server.tool()에 대한 인수
| 주제 | 유형 | 설명 |
|---|---|---|
| 이름 | string |
도구의 고유 식별자(예: "메모 추가") |
| 설명 | string |
도구 호출 시기를 결정하기 위해 AI 모델이 읽은 설명 |
| 입력스키마 | object |
허용되는 매개변수를 정의하는 Zod 키 객체 |
| 매니저 | async function |
형식화된 매개변수를 수신하고 결과를 반환하는 비동기 함수 |
4. 결과 형식
각 도구 핸들러는 필드가 있는 객체를 반환해야 합니다. content, 이는 요소의 배열입니다.
전형적인. MCP는 결과에서 세 가지 유형의 콘텐츠를 지원합니다.
// Tipo 1: Testo semplice
return {
content: [
{ type: "text", text: "Il risultato dell'operazione" }
],
};
// Tipo 2: Immagine (base64)
return {
content: [
{ type: "image", data: "iVBORw0KGgo...", mimeType: "image/png" }
],
};
// Tipo 3: Risorsa embedded
return {
content: [
{ type: "resource", resource: { uri: "note://lista", text: "..." } }
],
};
5. 전송 STDIO
const transport = new StdioServerTransport();
await server.connect(transport);
StdioServerTransport 다음을 통해 통신하도록 서버를 구성합니다. 표준입력 (클라이언트로부터 메시지를 받음)
전자 표준 출력 (클라이언트에게 응답을 보냅니다). 방법 connect() 루프를 시작하다
읽기/쓰기 및 그 순간부터 서버는 JSON-RPC 메시지를 수신합니다.
기본 규칙: stdout에 쓰지 마세요.
STDIO 전송을 사용하면 채널 stdout JSON-RPC 메시지 전용으로 예약되어 있습니다.
프로토콜의. stdout에 대한 다른 출력(예: console.log()) 부패할 것이다
프로토콜에 영향을 미치고 클라이언트에서 구문 분석 오류가 발생할 수 있습니다. 로깅 및 디버깅의 경우 다음을 사용하십시오.
언제나 console.error(), 누가 글을 쓰고 있나요? stderr.
3단계: 전체 도구 추가(CRUD)
Notes에 대한 완전한 CRUD 작업 세트로 서버를 확장해 보겠습니다. 다음 도구를 추가하세요
도구 후에 add-note 파일에 src/index.ts:
도구: 메모 읽기
server.tool(
"get-note",
"Recupera il contenuto di una nota dal titolo",
{
title: z.string().describe("Titolo della nota da leggere"),
},
async ({ title }) => {
const content = notes.get(title);
if (!content) {
return {
content: [{ type: "text", text: `Nota "${title}" non trovata.` }],
isError: true,
};
}
return {
content: [{ type: "text", text: content }],
};
},
);
도구: 모든 노트 나열
server.tool(
"list-notes",
"Elenca tutte le note salvate con i rispettivi titoli",
{}, // Nessun parametro richiesto: schema vuoto
async () => {
if (notes.size === 0) {
return {
content: [{ type: "text", text: "Nessuna nota salvata." }],
};
}
const list = Array.from(notes.entries())
.map(([title, content], i) =>
`${i + 1}. ${title} (${content.length} caratteri)`
)
.join("\n");
return {
content: [{ type: "text", text: `Note salvate:\n${list}` }],
};
},
);
도구: 메모 삭제
server.tool(
"delete-note",
"Cancella una nota esistente dal titolo",
{
title: z.string().describe("Titolo della nota da cancellare"),
},
async ({ title }) => {
const deleted = notes.delete(title);
if (!deleted) {
return {
content: [{ type: "text", text: `Nota "${title}" non trovata.` }],
isError: true,
};
}
return {
content: [{ type: "text", text: `Nota "${title}" cancellata con successo.` }],
};
},
);
4단계: Zod를 사용한 고급 검증
Zod는 단순한 검증 라이브러리가 아닙니다. 도구의 매개변수를 설명하는 언어입니다.
AI 모델에. 각 Zod 스키마는 JSON 스키마로 변환되어 단계 중에 클라이언트로 전송됩니다.
발견의 (tools/list). 방법 .describe() 그리고 특히 중요한
텍스트가 스키마에 포함되어 AI가 인수로 제공할 내용을 이해하는 데 도움이 되기 때문입니다.
Zod의 기능을 보여주기 위해 고급 검증 기능이 있는 검색 도구를 추가해 보겠습니다.
server.tool(
"search-notes",
"Cerca note per parola chiave con opzioni di filtro avanzate",
{
query: z.string()
.min(2)
.describe("Testo da cercare (minimo 2 caratteri)"),
caseSensitive: z.boolean()
.optional()
.default(false)
.describe("Se true, la ricerca e case-sensitive"),
limit: z.number()
.int()
.min(1)
.max(100)
.optional()
.default(10)
.describe("Numero massimo di risultati (1-100)"),
},
async ({ query, caseSensitive, limit }) => {
const results: string[] = [];
for (const [title, content] of notes) {
const haystack = caseSensitive ? content : content.toLowerCase();
const needle = caseSensitive ? query : query.toLowerCase();
if (haystack.includes(needle)) {
results.push(title);
}
if (results.length >= limit) break;
}
return {
content: [{
type: "text",
text: results.length > 0
? `Trovate ${results.length} note:\n${results.join("\n")}`
: `Nessun risultato per "${query}".`,
}],
};
},
);
MCP의 일반적인 Zod 패턴
다음은 MCP 도구 회로도 정의에 가장 많이 사용되는 Zod 패턴에 대한 빠른 참조입니다.
MCP에 대한 Zod 빠른 참조
| 패턴 | 암호 | 사용 |
|---|---|---|
| 필수 문자열 | z.string() |
필수 텍스트 매개변수 |
| 비어 있지 않은 문자열 | z.string().min(1) |
비워둘 수 없는 매개변수 |
| 유효한 이메일 | z.string().email() |
이메일 형식 검증 |
| 유효한 URL | z.string().url() |
URL 형식 유효성 검사 |
| 양의 정수 | z.number().int().positive() |
ID, 카운터 |
| 숫자 범위 | z.number().min(0).max(100) |
백분율, 점수 |
| 부울 | z.boolean() |
플래그 켜기/끄기 |
| 문자열 열거형 | z.enum(["low", "medium", "high"]) |
기본값 |
| 기본값은 선택 사항 | z.string().optional().default("valore") |
기본값이 있는 매개변수 |
| 문자열 배열 | z.array(z.string()) |
목록, 태그 |
| AI에 대한 설명 | z.string().describe("spiegazione") |
컴파일 시 AI 모델을 안내합니다. |
방법 .describe() AI 경험에 매우 중요합니다. 텍스트가
검색 중에 모델에 전송된 JSON 스키마를 통해 제공할 내용을 정확히 파악할 수 있습니다.
각 매개변수의 값으로 명확하고 간결한 설명을 작성하세요.
5단계: isError를 사용한 오류 처리
MCP는 도구 수준에서 오류 처리를 위한 명확한 패턴을 정의합니다. 두 가지 카테고리가 있습니다. 처리할 오류 수:
실행 오류(도구로 관리됨)
도구에서 예측 가능한 오류(리소스를 찾을 수 없음, 유효성 검사 실패, 서비스 없음)가 발생한 경우
사용 가능), 다음과 같은 결과를 반환해야 합니다. isError: true. 이는 AI 모델에 신호를 보냅니다.
작업이 실패하여 적절하게 대응할 수 있게 되었습니다.
server.tool(
"get-note",
"Recupera una nota dal titolo",
{
title: z.string().describe("Titolo della nota"),
},
async ({ title }) => {
try {
const content = notes.get(title);
if (!content) {
// Errore prevedibile: nota non trovata
return {
content: [{
type: "text",
text: `Errore: la nota "${title}" non esiste.`,
}],
isError: true,
};
}
return {
content: [{ type: "text", text: content }],
};
} catch (error) {
// Errore imprevisto: catturato e gestito
return {
content: [{
type: "text",
text: `Errore: ${error instanceof Error ? error.message : String(error)}`,
}],
isError: true,
};
}
},
);
프로토콜 오류(처리되지 않은 예외)
도구 핸들러가 처리되지 않은 예외를 발생시키는 경우 SDK는 자동으로 이를 오류로 전환합니다.
코드가 포함된 JSON-RPC -32603 (내부 오류). 항상 오류를 처리하는 것이 좋습니다.
명시적으로 try/catch를 사용하여 모델에 더 많은 정보를 제공하는 오류 메시지를 제공합니다.
모범 사례: 항상 오류 처리
권장되는 패턴은 모든 도구 로직을 하나의 블록으로 래핑하는 것입니다. try/catch,
돌아오는 isError: true 오류가 발생한 경우 설명 메시지와 함께. 이
AI 모델이 오류를 컨텍스트로 수신하고 진행 방법을 결정할 수 있습니다(다시 시도,
사용자에게 물어보거나 다른 접근 방식을 시도해 보세요.)
6단계: server.resource()를 사용하여 리소스 노출
도구 외에도 MCP 서버는 다음을 노출할 수 있습니다. 자원: 다음에서 액세스할 수 있는 상황별 데이터 URI를 통한 클라이언트. 리소스는 메시지를 표시하지 않고 읽기 전용 정보를 제공하는 데 유용합니다. 모델의 명시적인 동작.
전체 메모 목록을 표시하는 리소스를 추가해 보겠습니다.
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
// Risorsa: elenco di tutte le note
server.resource(
"notes-list",
"note://list",
"Elenco completo di tutte le note salvate",
async (uri) => {
const allNotes = Array.from(notes.entries())
.map(([title, content]) => `## ${title}\n${content}`)
.join("\n\n---\n\n");
return {
contents: [
{
uri: uri.href,
mimeType: "text/markdown",
text: allNotes || "Nessuna nota disponibile.",
},
],
};
},
);
// Risorsa con template URI: singola nota
server.resource(
"note-by-title",
new ResourceTemplate("note://{title}", { list: undefined }),
"Accede a una singola nota tramite il titolo",
async (uri, { title }) => {
const content = notes.get(title as string);
return {
contents: [
{
uri: uri.href,
mimeType: "text/plain",
text: content ?? `Nota "${title}" non trovata.`,
},
],
};
},
);
도구와 리소스의 차이점은 MCP 프로토콜의 기본입니다.
- 도구: AI 모델이 호출하는 작업에는 부작용이 있을 수 있습니다(데이터 생성, 수정, 삭제).
- 자원: 클라이언트 애플리케이션에서 액세스할 수 있는 읽기 전용 데이터이며 부작용이 없습니다.
7단계: 완전한 서버
다음은 모든 도구, 리소스 및 오류 처리 기능이 포함된 전체 서버 코드입니다.
이것은 파일입니다 src/index.ts 결정적인:
#!/usr/bin/env node
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// ============================================
// Stato in memoria
// ============================================
const notes: Map<string, string> = new Map();
// ============================================
// Creazione del server MCP
// ============================================
const server = new McpServer({
name: "notes-server",
version: "1.0.0",
});
// ============================================
// TOOL: Aggiungere una nota
// ============================================
server.tool(
"add-note",
"Aggiunge una nuova nota con titolo e contenuto",
{
title: z.string().min(1).describe("Titolo della nota"),
content: z.string().min(1).describe("Contenuto della nota"),
},
async ({ title, content }) => {
if (notes.has(title)) {
return {
content: [{
type: "text",
text: `Nota "${title}" già esistente. Usa un titolo diverso.`,
}],
isError: true,
};
}
notes.set(title, content);
return {
content: [{
type: "text",
text: `Nota "${title}" salvata con successo (${content.length} caratteri).`,
}],
};
},
);
// ============================================
// TOOL: Leggere una nota
// ============================================
server.tool(
"get-note",
"Recupera il contenuto di una nota dal titolo",
{
title: z.string().describe("Titolo della nota da leggere"),
},
async ({ title }) => {
const content = notes.get(title);
if (!content) {
return {
content: [{ type: "text", text: `Nota "${title}" non trovata.` }],
isError: true,
};
}
return {
content: [{ type: "text", text: content }],
};
},
);
// ============================================
// TOOL: Elencare tutte le note
// ============================================
server.tool(
"list-notes",
"Elenca tutte le note salvate con i rispettivi titoli",
{},
async () => {
if (notes.size === 0) {
return {
content: [{ type: "text", text: "Nessuna nota salvata." }],
};
}
const list = Array.from(notes.entries())
.map(([title, content], i) =>
`${i + 1}. ${title} (${content.length} caratteri)`
)
.join("\n");
return {
content: [{ type: "text", text: `Note salvate:\n${list}` }],
};
},
);
// ============================================
// TOOL: Cancellare una nota
// ============================================
server.tool(
"delete-note",
"Cancella una nota esistente dal titolo",
{
title: z.string().describe("Titolo della nota da cancellare"),
},
async ({ title }) => {
const deleted = notes.delete(title);
if (!deleted) {
return {
content: [{ type: "text", text: `Nota "${title}" non trovata.` }],
isError: true,
};
}
return {
content: [{ type: "text", text: `Nota "${title}" cancellata.` }],
};
},
);
// ============================================
// TOOL: Cercare note con filtri avanzati
// ============================================
server.tool(
"search-notes",
"Cerca note per parola chiave con opzioni di filtro",
{
query: z.string().min(2).describe("Testo da cercare (minimo 2 caratteri)"),
caseSensitive: z.boolean().optional().default(false)
.describe("Se true, la ricerca e case-sensitive"),
limit: z.number().int().min(1).max(100).optional().default(10)
.describe("Numero massimo di risultati (1-100)"),
},
async ({ query, caseSensitive, limit }) => {
const results: string[] = [];
for (const [title, content] of notes) {
const haystack = caseSensitive ? content : content.toLowerCase();
const needle = caseSensitive ? query : query.toLowerCase();
if (haystack.includes(needle)) {
results.push(title);
}
if (results.length >= limit) break;
}
return {
content: [{
type: "text",
text: results.length > 0
? `Trovate ${results.length} note:\n${results.join("\n")}`
: `Nessun risultato per "${query}".`,
}],
};
},
);
// ============================================
// RISORSA: Elenco completo note
// ============================================
server.resource(
"notes-list",
"note://list",
"Elenco completo di tutte le note salvate",
async (uri) => {
const allNotes = Array.from(notes.entries())
.map(([title, content]) => `## ${title}\n${content}`)
.join("\n\n---\n\n");
return {
contents: [{
uri: uri.href,
mimeType: "text/markdown",
text: allNotes || "Nessuna nota disponibile.",
}],
};
},
);
// ============================================
// Avvio del server con transport STDIO
// ============================================
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Notes MCP Server avviato su stdio");
}
main().catch((error) => {
console.error("Errore fatale:", error);
process.exit(1);
});
8단계: MCP Inspector를 사용하여 빌드 및 테스트
MCP 서버용 공식 디버깅 도구인 MCP Inspector를 사용하여 프로젝트를 컴파일하고 서버를 테스트합니다.
# Compila il progetto TypeScript
npm run build
# Testa con MCP Inspector (apre un'interfaccia web)
npx @modelcontextprotocol/inspector dist/index.js
MCP Inspector는 브라우저에서 다음을 수행할 수 있는 웹 인터페이스를 엽니다.
- 해당 구성표로 등록된 모든 도구 보기
- 테스트 매개변수를 제공하여 도구를 수동으로 호출합니다.
- 답변 형식을 확인하세요.
- URI를 통해 리소스 테스트
- 교환된 JSON-RPC 메시지 모니터링
9단계: Claude Desktop 구성
Claude Desktop(가장 널리 사용되는 MCP 클라이언트)과 함께 서버를 사용하려면 구성을 추가하세요. 운영 체제에 맞는 파일에:
구성 파일 경로
| 운영 체제 | Percorso |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| 윈도우 | %APPDATA%\Claude\claude_desktop_config.json |
| 리눅스 | ~/.config/claude/claude_desktop_config.json |
JSON 파일에 서버 구성을 추가합니다.
{
"mcpServers": {
"notes": {
"command": "node",
"args": ["/percorso/assoluto/mcp-notes-server/dist/index.js"]
}
}
}
파일을 저장하고 Claude Desktop을 다시 시작하면 모델에 Notes 서버 도구가 표시됩니다. 대화 중에 자동으로 호출할 수 있습니다. 서버가 있는지 확인할 수 있습니다. Claude Desktop의 하단 표시줄에 있는 도구 아이콘을 찾아 올바르게 연결되었습니다.
환경 변수 추가
서버에 환경 변수(예: API 키 또는 구성)가 필요한 경우
필드를 사용하여 구성에 추가할 수 있습니다. env:
{
"mcpServers": {
"notes": {
"command": "node",
"args": ["/percorso/assoluto/mcp-notes-server/dist/index.js"],
"env": {
"STORAGE_PATH": "/home/utente/notes",
"MAX_NOTES": "1000"
}
}
}
}
디버깅: 규칙 및 팁
MCP 서버 디버깅은 기존 애플리케이션에 비해 고유한 과제를 제시합니다. 가장 일반적인 문제를 해결하기 위한 기본 규칙과 제안은 다음과 같습니다.
규칙 1: console.log()를 절대 사용하지 마세요
STDIO 전송을 사용하면 console.log() 에 쓴다 stdout, 채널입니다
JSON-RPC 메시지용으로 예약되어 있습니다. 싱글 console.log() 그것은 전체를 부패시킬 수 있다
프로토콜. 미국 언제나 console.error() 디버깅을 위해:
// SBAGLIATO - corrompe il protocollo STDIO
console.log("Debug: nota aggiunta");
// CORRETTO - scrive su stderr, non interferisce
console.error("Debug: nota aggiunta");
console.error("[DEBUG]", JSON.stringify({ title, content }));
규칙 2: 빌드 확인
일반적인 실수는 변경 후 다시 컴파일하는 것을 잊어버리는 것입니다. 미국 npm run dev
감시 모드에서 자동 컴파일을 위해:
# Compilazione automatica ad ogni modifica
npm run dev
# In un altro terminale, testa con l'inspector
npx @modelcontextprotocol/inspector dist/index.js
규칙 3: 서버 로그 확인
에 적힌 메시지 stderr Claude Desktop 터미널에 표시됩니다.
(개발자> 콘솔 열기) 및 MCP Inspector에서. 구조화된 로그를 사용하여 디버깅을 용이하게 합니다.
async ({ title, content }) => {
console.error(`[add-note] Ricevuta richiesta: title=${title}`);
notes.set(title, content);
console.error(`[add-note] Nota salvata. Totale note: ${notes.size}`);
return {
content: [{ type: "text", text: `Nota "${title}" salvata.` }],
};
}
디버깅 체크리스트
| 문제 | 가능한 원인 | 해결책 |
|---|---|---|
| 서버가 시작되지 않습니다 | 부족함 "type": "module" package.json에서 |
package.json에 필드 추가 |
| 가져오기를 찾을 수 없습니다. | 부족함 .js 가져오기 경로에서 |
확장을 추가합니다: "...mcp.js" |
| 클로드는 도구를 보지 못합니다 | JSON 구성의 잘못된 경로 | 필드에서 절대 경로를 사용하십시오. args |
| 프로토콜 오류 | console.log() 코드에서 |
다음으로 교체 console.error() |
| 검증되지 않은 매개변수 | 불완전한 Zod 다이어그램 | 추가하다 .describe() 모든 분야에 |
요약
이 기사에서는 처음부터 완전한 MCP 서버를 구축했습니다. 우리가 다룬 내용은 다음과 같습니다.
- 프로젝트 설정: npm 초기화, ESM을 통한 TypeScript 구성, 폴더 구조
- Mcp서버: 이름과 버전으로 서버 인스턴스 생성
- 서버.도구(): 이름, 설명, Zod 스키마 및 비동기 처리기를 포함한 도구 등록
- Zod 검증: 표현 패턴을 정의하기 위한 고급 패턴(min, max, enum, option, default, explain)
- 오류 처리: 패턴
isError: true오류 처리 및 예상치 못한 예외에 대한 try/catch - 서버.리소스(): URI 및 URI 템플릿을 통해 상황별 데이터 노출
- StdioServerTransport: stdin/stdout을 통한 통신을 위한 전송 구성
- 테스트: MCP Inspector로 확인 후 Claude Desktop 설정
- 디버깅: 표준 출력에 절대 쓰지 않는 기본 규칙, 로그에 console.error() 사용
다음 기사
시리즈의 다음 기사에서는 프로토콜의 다른 측면을 다룰 것입니다. MCP 클라이언트 생성 타입스크립트에서. 서버에 연결하고, 기능을 협상하고, 나열하고 호출하는 방법을 살펴보겠습니다. 도구를 프로그래밍 방식으로 사용하고 원격 연결을 위한 HTTP 전송을 관리합니다. 우리는 구축하는 법을 배울 것입니다 표준 프로토콜과 호환되는 모든 MCP 서버와 통신할 수 있는 클라이언트입니다.
모든 예제가 포함된 전체 코드는 저장소에서 사용할 수 있습니다. GitHub의 Tech-MCP.







