소개: 코드 작성부터 제작까지
이 시리즈의 이전 12개 기사에서 우리는 도구가 포함된 서버, 리소스, SQLite 지속성, EventBus 및 ClientManager를 통한 서버 간 통신. 하지만 MCP 서버 전문가는 하나 없이는 완전하지 않습니다 엄격한 테스트 전략 그리고 하나 생산을 위한 적절한 준비.
MCP 서버 테스트에는 특정 과제가 있습니다. 도구는 JSON-RPC 프로토콜을 통해 통신합니다.
이벤트는 비동기식 버스를 통해 이동하며 서버 간 통신에는 메모리 내 전송이 포함됩니다.
패키지 @mcp-suite/testing 프로젝트의
Tech-MCP 이러한 문제를 해결합니다
다음과 같은 전용 유틸리티를 제공하여 복잡성을 완화합니다. TestHarness, InMemoryTransport e
MockEventBus.
이 기사에서 배울 내용
- MCP 테스트 피라미드: 단위, 통합 및 배선 테스트
- 인메모리 데이터베이스와 격리된 저장소를 테스트하는 방법
- 패키지
@mcp-suite/testing: TestHarness, InMemoryTransport, MockEventBus - 다음을 사용한 도구의 엔드투엔드 통합 테스트
createTestHarness() - 게시된 이벤트 확인하기
MockEventBus - 서버 간 통신을 위한 배선 테스트
- 보안 모범 사례: 입력 유효성 검사, 준비된 문, 속도 제한
- 성능: SQLite WAL 모드, 인덱스, 일괄 트랜잭션
- 프로덕션 환경에 배포: 구조화된 로깅, 단계적 종료, Docker
MCP 테스트 피라미드
전문 MCP 서버에는 균형 잡힌 피라미드로 구성된 세 가지 수준의 테스트가 필요합니다. 실행 속도 및 기능 적용 범위. 기본에서 가장 빠르고 가장 많은 테스트를 찾을 수 있습니다. 더 복잡하지만 덜 빈번한 것:
/\
/ \
/ W \ Wiring Test
/ I R \ (cross-server con InMemoryTransport)
/ I I \
/ N N G \
/────────────\
/ INTEGRATION \ Tool Test
/ (test harness) \ (tool → store → risultato)
/──────────────────\
/ \
/ UNIT TEST \ Store Test
/ (store in-memory) \ (logica pura, nessun MCP)
/──────────────────────────\
MCP 테스트의 세 가지 수준
| 수준 | 무슨 테스타 | 속도 | 설정 필요 |
|---|---|---|---|
| 단위 | 저장 방법(지속성 논리) | 매우 빠르다 | 메모리 내 저장소만 해당 |
| 완성 | MCP 프로토콜을 통한 엔드투엔드 도구 | 빠른 | 테스트하네스 + InMemoryTransport |
| 배선 | 실제 서버 간 통신 | 중간 | 다중 서버 + ClientManager |
매장 단위 테스트
피라미드의 첫 번째 레벨에서는 다음을 테스트합니다. 지속성 논리가 분리되어 있음, 관여하지 않고
MCP 프로토콜. 저장소는 메모리 내 데이터베이스(inMemory: true), 보장
각 테스트는 깨끗한 상태에서 시작되며 실행 속도가 매우 빠릅니다.
저장 단위 테스트는 모든 CRUD(생성, 읽기, 업데이트, 삭제) 작업, 제약 조건을 확인합니다. JSON 데이터의 무결성 제약 조건(UNIQUE 제약 조건), 필터, 검색 및 직렬화/역직렬화.
import { describe, it, expect, beforeEach } from "vitest";
import { NotesStore } from "../../src/services/notes-store.js";
describe("NotesStore", () => {
let store: NotesStore;
beforeEach(() => {
// Ogni test ha un database fresco in-memory
store = new NotesStore({ inMemory: true });
});
it("should add and retrieve a note", () => {
const note = store.addNote({
title: "Test",
content: "Contenuto di test",
tags: ["tag1", "tag2"],
});
expect(note.id).toBe(1);
expect(note.title).toBe("Test");
expect(note.tags).toEqual(["tag1", "tag2"]);
const retrieved = store.getNote(1);
expect(retrieved).toEqual(note);
});
it("should return undefined for non-existent note", () => {
const note = store.getNote(999);
expect(note).toBeUndefined();
});
it("should list notes ordered by updatedAt desc", () => {
store.addNote({ title: "Prima", content: "A" });
store.addNote({ title: "Seconda", content: "B" });
store.addNote({ title: "Terza", content: "C" });
const notes = store.listNotes();
expect(notes).toHaveLength(3);
expect(notes[0].title).toBe("Terza");
});
it("should handle UNIQUE constraint on title", () => {
store.addNote({ title: "Unico", content: "A" });
expect(() => store.addNote({ title: "Unico", content: "B" }))
.toThrow();
});
it("should search notes by content", () => {
store.addNote({ title: "JS", content: "Arrow functions e closures" });
store.addNote({ title: "TS", content: "Tipi generici e interfacce" });
const results = store.searchNotes("functions");
expect(results).toHaveLength(1);
expect(results[0].title).toBe("JS");
});
it("should delete a note and return true", () => {
store.addNote({ title: "Da cancellare", content: "X" });
expect(store.deleteNote(1)).toBe(true);
expect(store.getNote(1)).toBeUndefined();
});
});
매장 단위 테스트 모범 사례
- 미국
beforeEach~와 함께inMemory: true테스트 간의 완전한 격리를 보장하기 위해 - 전체 CRUD 루프 테스트: 생성, 읽기, 업데이트, 삭제
- 극단적인 사례 확인: 존재하지 않는 레코드, 위반된 제약 조건, 빈 필터
- JSON 직렬화/역직렬화 테스트(태그 배열, 중첩 객체)
- SQL 쿼리를 직접 테스트하지 마세요. 관찰 가능한 저장소 동작을 테스트하세요.
TestHarness를 사용한 도구 통합 테스트
피라미드 머리의 두 번째 수준 i MCP 프로토콜을 통한 엔드투엔드 도구.
패키지 @mcp-suite/testing 기능을 제공합니다 createTestHarness()
이는 메모리 내 연결된 클라이언트-서버 쌍을 생성하여 도구를 정확하게 호출할 수 있도록 합니다.
실제 MCP 클라이언트처럼 말이죠.
createTestHarness() 작동 방식
이 함수는 InMemoryTransport 페어링하고 서버와 클라이언트를 연결하고 반환합니다.
물건 TestHarness 클라이언트가 도구를 호출할 준비가 된 상태에서:
export async function createTestHarness(server: McpServer): Promise<TestHarness> {
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
const client = new Client({ name: "test-client", version: "1.0.0" });
await server.connect(serverTransport); // Server PRIMA
await client.connect(clientTransport); // Client DOPO
return {
client,
close: async () => {
await client.close();
await server.close();
},
};
}
연결 순서는 기본입니다. 서버는 클라이언트보다 먼저 연결되어야 합니다. 이는 서버가 클라이언트가 제공하는 기능 협상을 처리할 준비가 되어 있어야 하기 때문입니다. 연결 후 즉시 시작됩니다.
도구 테스트 완료
다음은 도구를 테스트하는 완전한 예입니다. add-note MCP 응답 형식을 확인하고,
결과 내용 및 오류 처리:
import { describe, it, expect, afterEach } from "vitest";
import { createTestHarness, MockEventBus, type TestHarness } from "@mcp-suite/testing";
import { createNotesServer } from "../../src/server.js";
describe("add-note tool", () => {
let harness: TestHarness;
afterEach(async () => {
if (harness) await harness.close();
});
it("should add a note and return it as JSON", async () => {
const { server } = createNotesServer({ storeOptions: { inMemory: true } });
harness = await createTestHarness(server);
const result = await harness.client.callTool({
name: "add-note",
arguments: {
title: "Test Note",
content: "Hello World",
tags: ["test"],
},
});
// Verifica formato risultato MCP
const content = result.content as Array<{ type: string; text: string }>;
expect(content[0].type).toBe("text");
// Verifica contenuto
const note = JSON.parse(content[0].text);
expect(note.title).toBe("Test Note");
expect(note.content).toBe("Hello World");
expect(note.tags).toEqual(["test"]);
expect(note.id).toBeDefined();
});
it("should return error for duplicate title", async () => {
const { server } = createNotesServer({ storeOptions: { inMemory: true } });
harness = await createTestHarness(server);
await harness.client.callTool({
name: "add-note",
arguments: { title: "Duplicato", content: "Primo" },
});
const result = await harness.client.callTool({
name: "add-note",
arguments: { title: "Duplicato", content: "Secondo" },
});
expect(result.isError).toBe(true);
});
});
MockEventBus: 이벤트 게시 확인
Il MockEventBus 인터페이스 테스트 구현 EventBus 그
게시된 모든 이벤트를 전파하지 않고 기록합니다. 어설션을 위한 두 가지 주요 방법을 제공합니다.
wasPublished(eventName): 특정 이벤트가 게시되었는지 확인합니다.getPublishedEvents(eventName): 페이로드를 포함하여 해당 이름으로 게시된 모든 이벤트의 배열을 반환합니다.
it("should publish event when note is added", async () => {
const eventBus = new MockEventBus();
const { server } = createNotesServer({
eventBus,
storeOptions: { inMemory: true },
});
harness = await createTestHarness(server);
await harness.client.callTool({
name: "add-note",
arguments: { title: "Evento", content: "Test" },
});
// Verifica che l'evento sia stato pubblicato
expect(eventBus.wasPublished("notes:created")).toBe(true);
// Verifica il payload dell'evento
const events = eventBus.getPublishedEvents("notes:created");
expect(events[0].payload).toMatchObject({ title: "Evento" });
});
MockEventBus를 사용한 테스트 패턴
MockEventBus를 사용하면 도구가 필요 없이 올바른 이벤트를 게시하는지 확인할 수 있습니다. 실제 버스를 구성하거나 핸들러를 구독합니다. 이 접근 방식은 도구 테스트를 격리합니다. 협업의 논리에서, 테스트에서도 단일 책임의 원칙을 따릅니다.
서버 간 배선 테스트
피라미드의 세 번째 수준에서는 다음을 테스트합니다. MCP 서버 간의 실제 통신.
배선 테스트는 서버가 다음을 통해 다른 서버의 도구를 호출할 수 있는지 확인합니다.
는 McpClientManager, 전체 생산 시나리오를 시뮬레이션합니다.
import { describe, it, expect, afterEach } from "vitest";
import { createTestHarness, type TestHarness } from "@mcp-suite/testing";
import { McpClientManager } from "@mcp-suite/client-manager";
import { createInsightEngineServer } from "../../src/server.js";
import { createAgileMetricsServer } from "../../../agile-metrics/src/server.js";
describe("insight-engine -> agile-metrics wiring", () => {
let callerHarness: TestHarness;
let clientManager: McpClientManager;
afterEach(async () => {
if (callerHarness) await callerHarness.close();
if (clientManager) await clientManager.disconnectAll();
});
it("should fetch velocity from agile-metrics", async () => {
// 1. Crea il server target (quello che viene chiamato)
const targetSuite = createAgileMetricsServer({
storeOptions: { inMemory: true },
});
// 2. Configura la connessione in-memory tramite ClientManager
clientManager = new McpClientManager();
const [ct, st] = McpClientManager.createInMemoryPair();
await targetSuite.server.connect(st);
await clientManager.connectInMemoryWithTransport("agile-metrics", ct);
// 3. Crea il server caller (quello che chiama)
const callerSuite = createInsightEngineServer({
clientManager,
storeOptions: { inMemory: true },
});
callerHarness = await createTestHarness(callerSuite.server);
// 4. Invoca il tool che effettua chiamata cross-server
const result = await callerHarness.client.callTool({
name: "health-dashboard",
arguments: {},
});
// 5. Verifica il risultato
const content = result.content as Array<{ type: string; text: string }>;
const dashboard = JSON.parse(content[0].text);
expect(dashboard.dataSources["agile-metrics"]).toBe("available");
});
});
배선 테스트의 구조
각 배선 테스트는 5단계의 정확한 흐름을 따릅니다.
- 대상 서버 만들기 인메모리: 호출자가 호출할 서버
- 전송 구성: 생성
InMemoryTransport대상을 ClientManager에 페어링하고 연결합니다. - 호출자 서버 만들기 ClientManager 사용: 서버 간 호출을 수행하는 서버
- TestHarness 생성 호출자용: 마치 MCP 클라이언트인 것처럼 호출자 도구를 호출할 수 있습니다.
- 결과 확인: 대상 서버 데이터가 성공적으로 복구되었는지 확인합니다.
테스트 파일 구성
프로젝트에서 Tech-MCP, 테스트 파일은 각 서버에 대해 표준화된 구조를 따릅니다.
servers/my-server/
tests/
services/
my-store.test.ts # Unit test dello store
tools/
add-item.test.ts # Integration test del tool
get-stats.test.ts # Integration test del tool
get-stats-wiring.test.ts # Wiring test cross-server
server.test.ts # Test della factory (opzionale)
Vitest 구성은 최소한이며 작업 공간을 통해 공유됩니다.
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
include: ["tests/**/*.test.ts"],
},
});
보안 모범 사례
MCP 서버를 프로덕션 환경으로 가져오려면 여러 수준의 보안에 주의가 필요합니다. 모든 도구는 악의적인 입력의 잠재적 진입점이 되며 모든 HTTP 엔드포인트가 노출됩니다. 적절한 보호가 필요합니다.
Zod를 사용한 입력 검증
MCP SDK는 선언된 Zod 스키마를 통해 인수 유효성 검사를 자동으로 처리합니다. 도구 정의에서. 정확한 제약 조건과 첫 번째 방어선을 사용하세요.
import { z } from "zod";
server.tool(
"create-project",
"Crea un nuovo progetto",
{
name: z.string().min(1).max(100),
budget: z.number().positive().optional(),
priority: z.enum(["low", "medium", "high"]).default("medium"),
tags: z.array(z.string()).max(10).optional(),
startDate: z.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, "Formato: YYYY-MM-DD")
.optional(),
},
async (args) => {
// args e già validato e tipizzato da Zod
// ...
},
);
준비된 문장과 SQL 인젝션
저장소는 연결된 문자열로 구성된 SQL을 실행해서는 안 됩니다. 도서관 better-sqlite3
기본적으로 준비된 문을 사용하여 SQL 주입 위험을 제거합니다.
// CORRETTO: prepared statement con parametri posizionali
const stmt = this.db.prepare("SELECT * FROM items WHERE title = ?");
const item = stmt.get(title);
// SBAGLIATO: SQL injection vulnerabile
const item = this.db.exec(`SELECT * FROM items WHERE title = '${title}'`);
HTTP 인증
HTTP 전송을 통해 네트워크에 노출된 MCP 서버의 경우 Bearer 토큰 인증으로 엔드포인트를 보호하세요.
import express from "express";
const app = express();
const API_KEY = process.env.MCP_API_KEY;
app.use("/mcp", (req, res, next) => {
if (!API_KEY) return next(); // Dev mode: nessuna autenticazione
const auth = req.headers.authorization;
if (auth !== `Bearer ${API_KEY}`) {
return res.status(401).json({ error: "Non autorizzato" });
}
next();
});
최소 권한 원칙
- 반드시 필요한 작업만 노출하세요.
- 읽기 도구는 글을 쓸 수 없어야 합니다.
- 위험한 도구를 분리하십시오(
delete,purge) 안전한 도구(list,get) - 프로덕션에서는 파괴적인 작업을 위한 별도의 도구를 고려하세요.
- 오류 시 스택 추적을 노출하지 마세요. AI가 읽을 수 있는 메시지만 표시됩니다.
도구 오류 관리
각 도구는 예외를 포착하고 MCP 형식으로 구조화된 오류를 반환해야 합니다.
깃발 isError: true AI에게 작업이 실패했다는 신호를 보내서
재시도 여부를 결정하거나 사용자에게 알릴 언어 모델:
server.tool(
"my-tool",
"Descrizione del tool",
{ param: z.string() },
async ({ param }) => {
try {
const result = store.doSomething(param);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
} catch (error) {
return {
content: [{
type: "text",
text: `Errore: ${error instanceof Error ? error.message : String(error)}`,
}],
isError: true,
};
}
},
);
오류 처리 규칙
- 처리되지 않은 예외를 발생시키지 마세요.: 도구는 항상 결과를 반환해야 합니다.
isError: true: 작업이 실패했음을 AI 모델에 보고- 읽을 수 있는 메시지: AI가 오류 메시지를 읽고 다음 조치를 결정합니다.
- 프로덕션에는 스택 추적이 없습니다.: 내부 서버 세부정보를 노출하지 마세요.
SQLite 성능
SQLite 데이터베이스 성능은 응답성 MCP 서버에 매우 중요합니다. 프로젝트 Tech-MCP 성능을 최적화하기 위해 세 가지 주요 전략을 채택합니다.
WAL 모드(미리 쓰기 로깅)
WAL 모드는 동시 읽기 성능을 크게 향상시켜 쓰기를 차단하지 않고 동시에 데이터베이스에 액세스할 수 있는 여러 리더:
constructor(options?: { inMemory?: boolean }) {
this.db = new Database(
options?.inMemory ? ":memory:" : "data/my-server.db"
);
this.db.pragma("journal_mode = WAL");
this.db.pragma("foreign_keys = ON");
this.migrate();
}
배치 작업을 위한 트랜잭션
트랜잭션은 여러 삽입에 대해 성능을 몇 배나 향상시킵니다. 원자성을 보장합니다(전부 아니면 전무):
addBulkItems(items: NewItem[]): Item[] {
const insert = this.db.prepare(
"INSERT INTO items (title, content) VALUES (?, ?)"
);
const addMany = this.db.transaction((items: NewItem[]) => {
return items.map(item => {
const result = insert.run(item.title, item.content);
return { id: Number(result.lastInsertRowid), ...item };
});
});
return addMany(items);
}
전략적 지수
필터 및 정렬에서 자주 사용되는 열에 인덱스를 추가합니다.
CREATE INDEX IF NOT EXISTS idx_items_created ON items(createdAt);
CREATE INDEX IF NOT EXISTS idx_items_status ON items(status);
프로덕션을 위한 구조화된 로깅
STDIO의 MCP는 stdout JSON-RPC 메시지의 경우. 로깅에는 다음을 사용해야 합니다.
stderr 통신 프로토콜 손상을 방지하려면:
// CORRETTO: log su stderr (non interferisce con MCP)
console.error("[INFO] Server avviato sulla porta 3000");
console.error("[ERROR] Connessione database fallita:", error.message);
// SBAGLIATO: stdout corrompe il protocollo JSON-RPC
console.log("Server avviato"); // ROMPE MCP!
JSON 구조화된 로거
프로덕션 환경의 경우 JSON 출력을 stderr로 생성하는 구조화된 로거 ELK Stack 또는 Datadog과 같은 모니터링 시스템과의 통합을 용이하게 합니다.
type LogLevel = "debug" | "info" | "warn" | "error";
function createLogger(name: string, level: LogLevel = "info") {
const levels: Record<LogLevel, number> = {
debug: 0, info: 1, warn: 2, error: 3,
};
const minLevel = levels[level];
return {
debug: (msg: string, data?: unknown) => {
if (minLevel <= 0) console.error(JSON.stringify({
level: "debug", server: name, msg, data,
ts: new Date().toISOString()
}));
},
info: (msg: string, data?: unknown) => {
if (minLevel <= 1) console.error(JSON.stringify({
level: "info", server: name, msg, data,
ts: new Date().toISOString()
}));
},
warn: (msg: string, data?: unknown) => {
if (minLevel <= 2) console.error(JSON.stringify({
level: "warn", server: name, msg, data,
ts: new Date().toISOString()
}));
},
error: (msg: string, data?: unknown) => {
if (minLevel <= 3) console.error(JSON.stringify({
level: "error", server: name, msg, data,
ts: new Date().toISOString()
}));
},
};
}
const logger = createLogger("my-server");
logger.info("Tool invocato", { tool: "add-item", args: { title: "Test" } });
정상적인 종료
프로덕션 환경에서는 서버를 종료하여 종료 신호를 깔끔하게 처리해야 합니다. 종료하기 전에 연결 및 리소스 해제:
// index.ts - Entry point del server
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createMyServer } from "./server.js";
const { server } = createMyServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("[INFO] Server avviato");
// Gestione segnale SIGINT (Ctrl+C)
process.on("SIGINT", async () => {
console.error("[INFO] Chiusura in corso...");
await server.close();
process.exit(0);
});
// Gestione segnale SIGTERM (container orchestration)
process.on("SIGTERM", async () => {
console.error("[INFO] Terminazione in corso...");
await server.close();
process.exit(0);
});
Docker를 사용하여 배포
컨테이너화된 배포의 경우 MCP 서버에 최적화된 Dockerfile이 있습니다. 다단계 빌드 및 최소한의 최종 이미지 사용:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
# Installa pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copia solo i file necessari per le dipendenze
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/ ./packages/
COPY servers/my-server/package.json ./servers/my-server/
# Installa le dipendenze
RUN pnpm install --frozen-lockfile
# Copia il codice sorgente
COPY servers/my-server/ ./servers/my-server/
COPY tsconfig.json ./
# Build
RUN pnpm --filter @mcp-suite/server-my-server build
# Stage 2: Produzione
FROM node:20-alpine AS production
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copia solo i file compilati e le dipendenze di produzione
COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
COPY --from=builder /app/packages/ ./packages/
COPY --from=builder /app/servers/my-server/dist/ ./servers/my-server/dist/
COPY --from=builder /app/servers/my-server/package.json ./servers/my-server/
RUN pnpm install --frozen-lockfile --prod
# Directory per i database SQLite
RUN mkdir -p /app/data
VOLUME ["/app/data"]
# Variabili d'ambiente
ENV NODE_ENV=production
# Entry point
CMD ["node", "servers/my-server/dist/index.js"]
프로덕션을 위한 Claude 데스크탑 구성
Claude Desktop과 함께 MCP 서버를 사용하려면 파일에 구성을 추가하십시오.
claude_desktop_config.json:
로컬 STDIO 서버
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/percorso/al/server/dist/index.js"],
"env": {
"NODE_ENV": "production"
}
}
}
}
원격 HTTP 서버
{
"mcpServers": {
"my-server": {
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer il-tuo-token"
}
}
}
}
제작 전 체크리스트
MCP 서버를 배포하기 전에 이 체크리스트의 각 사항을 체계적으로 확인하세요.
배포 체크리스트 완료
| 영역 | 요구 사항 | 확인하다 |
|---|---|---|
| 기능성 | 모든 도구에 대해 Zod 검증 완료 | 다음과 같은 구성표 min(), max(), enum() |
| 기능성 | try/catch e를 사용한 오류 처리 isError: true |
처리되지 않은 예외 없음 |
| 기능성 | 행복한 경로 적용 범위 테스트 및 오류 사례 | 단위 + 통합 테스트 통과 |
| 안전 | 아니요 console.log 표준 출력에서 |
홀로 console.error 로깅을 위해 |
| 안전 | 모든 SQL 쿼리에 대해 준비된 문 | SQL 문자열 연결 없음 |
| 안전 | 노출된 HTTP 엔드포인트에 대한 인증 | 베어러 토큰 또는 API 키 |
| 성능 | SQLite에서 WAL 모드 활성화 | pragma("journal_mode = WAL") |
| 성능 | 필터링된 열의 인덱스 | WHERE/ORDER BY 열에 대한 INDEX 생성 |
| 배포 | SIGINT 및 SIGTERM을 사용한 정상 종료 | 서버가 연결을 올바르게 닫습니다. |
| 건축학 | 선택적 매개변수가 있는 서버 팩토리 | eventBus, clientManager, storeOptions |
작업공간이 포함된 모노레포
여러 MCP 서버가 있는 프로젝트의 경우 pnpm 작업 공간과 Turborepo가 있는 모노레포가 단순화됩니다. 종속성 관리, 컴파일 및 테스트:
# pnpm-workspace.yaml
packages:
- "packages/*"
- "servers/*"
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"]
}
}
}
지시어 "dependsOn": ["^build"] Turborepo에게 먼저 종속성을 구축하라고 지시합니다.
그런 다음 현재 패키지. 빌드 시스템은 자동으로 순서를 확인하고 병렬화합니다.
독립적인 빌드로 빌드 시간을 크게 단축합니다.
진화의 길: 제로에서 생산까지
이 시리즈에서 우리는 복잡성이 증가하는 네 가지 수준을 통해 완전한 여정을 여행했습니다.
Livello 1 (Base)
─────────────────────────────────
Tool in-memory + STDIO
Client singolo + Validazione Zod
|
v
Livello 2 (Intermedio)
─────────────────────────────────
Resources + Prompts
HTTP transport + SQLite store
Migrazioni versionabili
|
v
Livello 3 (Avanzato)
─────────────────────────────────
EventBus (fire-and-forget)
ClientManager (cross-server)
Collaboration handlers
|
v
Livello 4 (Produzione)
─────────────────────────────────
Piramide dei test completa
Logging strutturato
Sicurezza + Performance
Monorepo + CI/CD + Docker
결론
테스트 및 생산 준비는 MCP 서버 개발의 중요한 단계입니다. 전문. 테스트 피라미드(단위, 통합 및 배선)는 각각에 대한 적용 범위를 보장합니다. 지속성 논리에서 서버 간 통신에 이르기까지 아키텍처 수준.
패키지 @mcp-suite/testing ~와 함께 TestHarness, InMemoryTransport
e MockEventBus 테스트 설정을 크게 단순화하여 다음을 확인할 수 있습니다.
외부 종속성 없이 실제 MCP 프로토콜을 통해 도구 동작을 수행합니다.
보안 모범 사례(Zod 검증, 준비된 명령문, 인증), 성능 (WAL 모드, 인덱스, 트랜잭션) 및 배포(구조적 로깅, 정상적인 종료, Docker) 프로덕션 준비가 완료된 MCP 서버로의 경로를 완성하세요.
시리즈의 다음 기사와 마지막 기사에서 우리는 한 가지를 소개할 것입니다. Tech-MCP 프로젝트의 전체 개요: 전체 31개 서버의 카탈로그, 프로젝트 통계, 배운 교훈 및 기여 방법.
모든 예제가 포함된 전체 코드는 저장소에서 사용할 수 있습니다. GitHub의 Tech-MCP.







