소개: Tech-MCP의 6가지 공유 패키지
모노레포에서 MCP 서버 22대, 코드 중복은 심각한 문제가 될 것입니다. 각 서버에는 자체 공장, 자체 로거, 자체 데이터베이스 관리가 필요합니다. 유지 관리 복잡성을 22배로 늘립니다. 프로젝트에서 채택한 아키텍처 솔루션 Tech-MCP 그리고 그것은 공유 패키지: 모든 횡단 논리를 집중시키는 6개의 내부 라이브러리 단일 지점에서 각 서버에서 재사용 가능합니다.
이 패키지는 단순한 유틸리티가 아닙니다.중요 인프라 모든 단일 서버의 기반이 됩니다. 공장에서는 4줄의 코드로 서버를 생성하고 EventBus를 통해 29가지 유형의 이벤트를 사용한 서버 협업을 통해 데이터베이스는 SQLite 지속성을 제공합니다. 자동 마이그레이션, 테스트를 통해 네트워크 없이 프로세스 내 테스트가 가능하며 CLI가 서버를 관리합니다. 터미널에서 클라이언트 관리자가 동기식 서버 간 통신을 조정합니다.
6가지 공유 패키지
| 패키지 | 책임 | 주요 종속성 |
|---|---|---|
| @mcp-suite/코어 | 서버 팩토리, 구성, 로거, 오류, 유형 | @modelcontextprotocol/sdk, 조드 |
| @mcp-suite/이벤트-버스 | 29개의 입력된 이벤트, LocalEventBus, 패턴 일치 | 마이크로매치 |
| @mcp-suite/데이터베이스 | createDatabase, WAL 모드, 증분 마이그레이션 | 더 나은-sqlite3 |
| @mcp-suite/테스트 | TestHarness, MockEventBus, InMemoryTransport | @modelcontextprotocol/sdk |
| @mcp-suite/cli | 서버 관리를 위한 목록, 시작, 상태 명령 | 사령관 |
| @mcp-suite/클라이언트-관리자 | 클라이언트 풀, 3개 전송, 서버 간 통신 | @modelcontextprotocol/sdk |
@mcp-suite/core: 제품군의 핵심
패키지 @mcp-suite/core 모든 서버가 구축되는 기반입니다.
다섯 가지 중요한 모듈을 제공합니다. 서버를 만드는 공장, il
구성 시스템 Zod 검증을 통해 구조화된 로거
stderr에 글을 쓰는 사람, 하나 유형화된 오류의 계층 구조 그리고 나 도메인 유형
22개 서버 전체에서 공유됩니다.
packages/core/
+-- package.json
+-- tsconfig.json
+-- src/
+-- index.ts # Re-export di tutti i moduli
+-- server-factory.ts # createMcpServer, startServer, McpSuiteServer
+-- config.ts # loadConfig, ServerConfigSchema
+-- logger.ts # Logger strutturato su stderr
+-- errors.ts # Gerarchia errori tipizzati
+-- types.ts # Tipi di dominio condivisi
CreateServerOptions 및 McpSuiteServer
마음의 @mcp-suite/core 서버로 정의하는 인터페이스 쌍
생성되고 인스턴스화되면 무엇을 나타내는지 알 수 있습니다. CreateServerOptions 설명하다
입력 옵션은 McpSuiteServer 을 나타냅니다 중앙 계약
아키텍처: 모든 서버가 이를 구현하고 모든 녹음 도구 기능이 이를 수신합니다.
export interface CreateServerOptions {
name: string; // Nome univoco del server (es. 'scrum-board')
version: string; // Versione semantica (es. '0.1.0')
description?: string; // Descrizione leggibile
config?: Partial<ServerConfig>; // Override della configurazione
eventBus?: EventBus; // EventBus opzionale per collaborazione
}
export interface McpSuiteServer {
name: string; // Nome univoco del server
server: McpServer; // Istanza del server MCP dall'SDK ufficiale
config: ServerConfig; // Configurazione caricata e validata con Zod
logger: Logger; // Logger strutturato (scrive su stderr)
eventBus?: EventBus; // EventBus opzionale (undefined se non fornito)
httpServer?: Server; // Riferimento al server HTTP (se trasporto HTTP)
}
createMcpServer(): 메인 팩토리
기능 createMcpServer() 그리고 각 서버가 자신을 생성하기 위해 호출하는 팩토리입니다.
단 몇 줄만으로 그는 네 가지 기본 작업을 조율합니다.
환경 변수, 구성된 수준으로 로거 생성, 인스턴스화 McpServer
공식 SDK에서 전체 번들을 반환합니다. McpSuiteServer.
export function createMcpServer(options: CreateServerOptions): McpSuiteServer {
// 1. Carica configurazione da env + override
const config = loadConfig(options.name, options.config);
// 2. Crea logger con il livello configurato
const logger = new Logger(options.name, config.logLevel);
logger.info(`Initializing ${options.name} v${options.version}`);
// 3. Istanzia il McpServer dall'SDK ufficiale
const server = new McpServer({
name: options.name,
version: options.version,
});
// 4. Restituisce il bundle completo
return { server, config, logger, eventBus: options.eventBus };
}
공장 흐름
CreateServerOptions
|
+--> loadConfig(name, overrides) --> ServerConfig
|
+--> new Logger(name, logLevel) --> Logger
|
+--> new McpServer({name, version}) --> McpServer
|
+--> { server, config, logger, eventBus } --> McpSuiteServer
startServer(): AutoTransport로 시작
일단 McpSuiteServer, 기능 startServer() 시작한다
구성에 따라 자동으로 전송을 선택합니다. STDIO 전송 사용
stdin/stdout JSON-RPC 통신용, HTTP 전송용
프로토콜을 사용하다 스트리밍 가능한 HTTP 양식을 갖춘 MCP SDK 상태 저장
(각 세션에는 UUID가 있습니다).
export async function startStdioServer(suite: McpSuiteServer): Promise<void> {
const transport = new StdioServerTransport();
suite.logger.info('Starting server with STDIO transport');
await suite.server.connect(transport);
}
export async function startHttpServer(suite: McpSuiteServer): Promise<void> {
const port = suite.config.port ?? 3000;
const app = createMcpExpressApp();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
await suite.server.connect(transport);
app.post('/mcp', async (req, res) => {
await transport.handleRequest(req, res, req.body);
});
app.get('/mcp', async (req, res) => {
await transport.handleRequest(req, res);
});
app.get('/health', (_req, res) => {
res.json({ status: 'ok', server: suite.name });
});
suite.httpServer = app.listen(port);
}
export async function startServer(suite: McpSuiteServer): Promise<void> {
if (suite.config.transport === 'http') {
await startHttpServer(suite);
} else {
await startStdioServer(suite);
}
}
config.ts: Zod 검증을 사용한 구성
구성 시스템은 다음을 따릅니다. 우선순위 캐스케이드: 특정 변수
서버당, 전역 변수, 프로그래밍 방식 재정의, 마지막으로 Zod 스키마에 정의된 기본값입니다.
계획 ServerConfigSchema 유효한 매개변수와 해당 기본값을 정의합니다.
export const ServerConfigSchema = z.object({
transport: z.enum(['stdio', 'http']).default('stdio'),
port: z.number().optional(),
logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
dataDir: z.string().optional(),
eventBus: z.object({
type: z.enum(['local', 'redis']).default('local'),
redisUrl: z.string().optional(),
}).default({ type: 'local' }),
});
기능 loadConfig() 서버 이름을 환경 변수 형식으로 변환합니다.
각 서버에 대한 세부적인 구성이 가능합니다.
# Globale (tutti i server)
MCP_SUITE_TRANSPORT=http
# Specifico per scrum-board
MCP_SUITE_SCRUM_BOARD_LOG_LEVEL=debug
MCP_SUITE_SCRUM_BOARD_TRANSPORT=stdio
logger.ts: stderr의 구조화된 로거
Logger는 JSON 형식으로 구조화된 로그를 작성합니다. stderr. 이 선택은
근본적인: MCP 프로토콜은 stdout 의사소통을 위해
JSON-RPC이므로 로그는 별도의 채널에 있어야 합니다. 로그가 stdout으로 이동한 경우
JSON-RPC 스트림이 손상되고 MCP 클라이언트가 서버와 통신할 수 없게 됩니다.
export class Logger {
private level: number;
constructor(
private readonly name: string,
level: LogLevel = 'info',
) {
this.level = LOG_LEVELS[level];
}
info(message: string, data?: Record<string, unknown>): void {
this.log('info', message, data);
}
private log(level: LogLevel, message: string, data?: Record<string, unknown>): void {
if (LOG_LEVELS[level] < this.level) return;
const entry = {
timestamp: new Date().toISOString(),
level,
server: this.name,
message,
...data,
};
process.stderr.write(JSON.stringify(entry) + '\n');
}
}
왜 stderr야?
MCP 프로토콜은 다음을 사용합니다. stdout 클라이언트와 서버 간의 JSON-RPC 메시지 전용입니다.
로거가 stdout에 기록하면 로그 메시지가 프로토콜과 혼합됩니다.
의사소통을 손상시킵니다. stderr 진단을 위해 지정된 채널,
데이터와 로그 사이의 분리를 유지합니다.
error.ts: 입력된 오류의 계층 구조
패키지는 서버가 처리할 수 있는 입력된 오류의 계층 구조를 정의합니다. 다양한 유형의 실패가 균일하게 발생합니다. 각 오류에는 기계가 읽을 수 있는 코드가 있으며 디버깅을 위한 선택적 세부 정보입니다.
오류 계층
| 수업 | 암호 | 사용 |
|---|---|---|
McpSuiteError |
- | 모든 오류의 기본 클래스 |
ConfigError |
CONFIG_ERROR | 구성이 잘못되었거나 누락되었습니다. |
ConnectionError |
CONNECTION_ERROR | 데이터베이스 또는 서비스 연결 문제 |
ToolExecutionError |
TOOL_EXECUTION_ERROR | MCP 도구 실행 중 오류가 발생했습니다. |
NotFoundError |
NOT_FOUND | 요청한 리소스를 찾을 수 없습니다. |
ValidationError |
VALIDATION_ERROR | 잘못된 사용자 입력 |
import { NotFoundError, ToolExecutionError } from '@mcp-suite/core';
// In uno store
const sprint = db.prepare('SELECT * FROM sprints WHERE id = ?').get(id);
if (!sprint) {
throw new NotFoundError('Sprint', String(id));
// Messaggio: "Sprint with id '42' not found"
}
// In un tool handler
try {
const result = store.createSprint(input);
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
} catch (error) {
if (error instanceof NotFoundError) {
return { content: [{ type: 'text', text: error.message }], isError: true };
}
throw new ToolExecutionError('Failed to create sprint', error);
}
type.ts: 공유 도메인 유형
형태 types.ts 서버 간에 공유되는 30개 이상의 도메인 유형을 정의하고,
기능 영역별로 구성됩니다. 이를 통해 모든 서버가 동일한 기능을 사용하도록 보장합니다.
작업, 스프린트, 측정항목, 도구 결과 등 일반적인 개념에 대한 데이터입니다.
export interface ToolSuccess<T = unknown> {
success: true;
data: T;
metadata?: Record<string, unknown>;
}
export interface ToolError {
success: false;
error: string;
code: string;
details?: unknown;
}
export type ToolResult<T = unknown> = ToolSuccess<T> | ToolError;
도메인별 유형 구성
| 도메인 | 주요 유형 | 사용처 |
|---|---|---|
| 코드 및 힘내 | FileReference, GitCommitInfo, CodeIssue |
코드 검토, 코드베이스 지식 |
| 프로젝트 관리 | TaskStatus, UserStory, SprintInfo |
스크럼 보드, 애자일 메트릭 |
| 경제학 | BudgetInfo, BudgetCategory, CostEntry |
프로젝트 경제학 |
| CI/CD | PipelineStatus, PipelineRun, PipelineStage |
cicd-모니터 |
| 데이터베이스 | TableInfo, ColumnInfo, IndexInfo |
db-스키마-탐색기 |
파일 index.ts 서버를 허용하여 모든 것을 체계적으로 다시 내보냅니다.
단일 진입점에서 구성요소를 가져오려면 다음을 수행하세요.
import {
createMcpServer,
type McpSuiteServer,
type EventBus,
Logger,
NotFoundError
} from '@mcp-suite/core';
@mcp-suite/event-bus: 유형화된 이벤트를 통한 서버 간 협업
패키지 @mcp-suite/event-bus 유형화된 이벤트 시스템을 구현합니다.
서버 간 비동기 협업. 정의하다 도메인별로 구성된 29개 이벤트,
인터페이스 EventBus 일반 및 로컬 구현 기반
EventEmitter 다음을 지원하는 Node.js 패턴 매칭
도서관을 통해 micromatch.
packages/event-bus/
+-- package.json
+-- tsconfig.json
+-- src/
+-- index.ts # Re-export dei moduli pubblici
+-- bus.ts # Interfaccia EventBus, tipi handler
+-- events.ts # EventMap con tutti i 29 eventi tipizzati
+-- local-bus.ts # LocalEventBus (implementazione in-process)
이벤트버스 인터페이스
인터페이스 EventBus 모든 구현에 대한 계약을 정의합니다.
이벤트 버스의 모습입니다. 다음과 같은 네 가지 방법을 제시합니다. publish 입력된 이벤트를 내보내려면
subscribe 특정 이벤트를 듣고, subscribePattern 에 대한
와일드카드 패턴과 일치하는 이벤트를 수신합니다. e clear 제거하다
모든 구독.
export interface EventBus {
publish<E extends EventName>(
event: E, payload: EventPayload<E>
): Promise<void>;
subscribe<E extends EventName>(
event: E, handler: EventHandler<E>
): () => void;
subscribePattern(
pattern: string, handler: PatternHandler
): () => void;
clear(): void;
}
// Handler per evento specifico (payload tipizzato)
export type EventHandler<E extends EventName> = (
payload: EventPayload<E>
) => void | Promise<void>;
// Handler per pattern (nome evento + payload generico)
export type PatternHandler = (
event: string, payload: unknown
) => void | Promise<void>;
EventMap: 도메인별로 구성된 29개 이벤트
L'EventMap 각 이벤트 이름을 페이로드에 매핑하는 TypeScript 인터페이스
전형적인. 이벤트는 규칙을 따릅니다. dominio:azione 11개 도메인을 포괄합니다.
코드 검토부터 회고까지, CI/CD부터 스탠드업까지 기능적입니다.
이벤트 도메인 맵
| 도메인 | 패턴 | 번호 이벤트 | Esempi |
|---|---|---|---|
| 코드 및 힘내 | code:* |
3 | 커밋 분석, 검토 완료, 종속성 경고 |
| CI/CD | cicd:* |
2 | 파이프라인 완료, 빌드 실패 |
| 스크럼 | scrum:* |
4 | 스프린트 시작, 스프린트 완료, 작업 업데이트, 스토리 완료 |
| 시간 추적 | time:* |
2 | 항목 기록, 작업표 생성 |
| 데이터베이스 | db:* |
2 | 스키마 변경, 인덱스 제안 |
| 테스트 | test:* |
2 | 생성, 적용 범위 보고서 |
| 문서 | docs:* |
2 | API 업데이트, 오래된 감지 |
| 성능 | perf:* |
2 | 병목 현상 발견, 프로필 완료 |
| 회고전 | retro:* |
2 | 작업 항목 생성, 완료 |
| 경제학 | economics:* |
2 | 예산 알림, 비용 업데이트 |
| 스탠드업 | standup:* |
1 | 보고서 생성 |
EventMap에서 파생된 유형은 컴파일 타임에 강력한 유형 지정을 허용합니다.
// Nome di qualsiasi evento valido (union di 29 stringhe letterali)
export type EventName = keyof EventMap;
// Payload tipizzato per un evento specifico
export type EventPayload<E extends EventName> = EventMap[E];
// EventPayload<'scrum:sprint-started'>
// => { sprintId: string; name: string; startDate: string; endDate: string }
LocalEventBus: 프로세스 내 구현
La LocalEventBus 기본 구현은 다음을 기반으로 합니다. EventEmitter
Node.js의 이벤트가 게시되면 버스는 먼저 직접 구독자에게 게시하고,
그런 다음 다음을 사용하여 모든 구독자 패턴을 확인하십시오. micromatch 와일드카드 일치를 위해.
export class LocalEventBus implements EventBus {
private emitter = new EventEmitter();
private patternSubs: PatternSubscription[] = [];
constructor() {
this.emitter.setMaxListeners(100);
}
async publish<E extends EventName>(
event: E, payload: EventPayload<E>
): Promise<void> {
// 1. Emetti per subscriber diretti
this.emitter.emit(event, payload);
// 2. Verifica pattern subscriber
for (const sub of this.patternSubs) {
if (micromatch.isMatch(event, sub.pattern)) {
try {
await sub.handler(event, payload);
} catch {
// Errori catturati silenziosamente
}
}
}
}
subscribe<E extends EventName>(
event: E, handler: EventHandler<E>
): () => void {
this.emitter.on(event, handler);
return () => { this.emitter.off(event, handler); };
}
subscribePattern(pattern: string, handler: PatternHandler): () => void {
const sub = { pattern, handler };
this.patternSubs.push(sub);
return () => {
const index = this.patternSubs.indexOf(sub);
if (index >= 0) this.patternSubs.splice(index, 1);
};
}
clear(): void {
this.emitter.removeAllListeners();
this.patternSubs = [];
}
}
Fire-and-Forget 및 와일드카드 패턴
도구는 연산자를 사용하여 이벤트를 게시합니다. 선택적 연결 (?.)
EventBus가 없는 경우를 관리합니다. 이 패턴 실행하고 잊어버리세요
도구가 핸들러가 완료될 때까지 기다리지 않고 오류가 발생하지 않도록 합니다.
핸들러는 호출 도구로 전파되지 않습니다.
// Nel tool handler: fire-and-forget
eventBus?.publish('scrum:sprint-started', {
sprintId: String(sprint.id),
name: sprint.name,
startDate: sprint.startDate,
endDate: sprint.endDate,
});
도서관 micromatch 구독에 대한 고급 와일드카드 패턴을 지원합니다.
와일드카드 패턴 지원
| 패턴 | 에 해당 | Esempio |
|---|---|---|
scrum:* |
모든 스크럼 이벤트 | 스크럼:스프린트 시작, 스크럼:작업 업데이트 |
*:*-completed |
모든 완료 이벤트 | 스크럼:스프린트 완료, 레트로:완료 |
*:*-alert |
모든 알림 | 코드:종속성 경고, 경제:예산 경고 |
{scrum,time}:* |
스크럼 또는 시간 이벤트 | 스크럼:작업 업데이트, 시간:항목 기록 |
// Sottoscrizione a tutti gli eventi del dominio scrum
eventBus.subscribePattern('scrum:*', (eventName, payload) => {
console.log(`Evento scrum: ${eventName}`, payload);
});
// Sottoscrizione a tutti gli eventi di completamento
eventBus.subscribePattern('*:*-completed', (eventName, payload) => {
console.log(`Completamento: ${eventName}`, payload);
});
// Cancellare una sottoscrizione
const unsubscribe = eventBus.subscribe('scrum:task-updated', handler);
unsubscribe(); // rimuove il listener
@mcp-suite/database: WAL 및 마이그레이션을 통한 SQLite 지속성
패키지 @mcp-suite/database 생성 및 관리에 대한 추상화를 제공합니다.
SQLite 데이터베이스. 두 가지 주요 기능을 제공합니다. 공장 창조하다
데이터베이스 연결(createDatabase) 그리고 증분 마이그레이션 시스템
(runMigrations). 미국 better-sqlite3 기본 동기 AD 드라이버로
고성능.
packages/database/
+-- package.json
+-- tsconfig.json
+-- src/
+-- index.ts # Re-export dei moduli pubblici
+-- connection.ts # createDatabase(), DatabaseOptions
+-- migrations.ts # runMigrations(), Migration
createDatabase(): 데이터베이스 팩토리
기능 createDatabase 디렉토리 생성을 자동으로 관리합니다.
구성 월 모드 그리고 활성화 외래 키.
모드도 지원합니다 메모리 내 테스트용.
export interface DatabaseOptions {
serverName: string; // Nome del server (diventa nome file .db)
dataDir?: string; // Directory custom (default: ~/.mcp-suite/data/)
inMemory?: boolean; // Se true, database in memoria (per test)
}
export function createDatabase(options: DatabaseOptions): Database.Database {
if (options.inMemory) {
return new Database(':memory:');
}
const dataDir = options.dataDir || DEFAULT_DATA_DIR;
if (!existsSync(dataDir)) {
mkdirSync(dataDir, { recursive: true });
}
const dbPath = join(dataDir, `${options.serverName}.db`);
const db = new Database(dbPath);
// Configurazione SQLite ottimale
db.pragma('journal_mode = WAL'); // Write-Ahead Logging
db.pragma('foreign_keys = ON'); // Integrita referenziale
return db;
}
왜 WAL 모드인가?
Il WAL(미리 쓰기 로깅) 이점을 제공하는 SQLite 저널 모드 중요한: 경쟁 판독값 블록 없이, 비차단 쓰기, 향상된 I/O 성능 충돌 복구 오토매틱. 독자가 파일에서 읽습니다. 작성자가 상호 간섭 없이 WAL 파일에 쓰는 동안 메인입니다. 주기적으로 체크포인트는 WAL을 기본 파일과 동기화합니다.
기본 데이터베이스 디렉토리 구조 e ~/.mcp-suite/data/, 파일 포함
.db 지속성이 필요한 각 서버에 대해:
~/.mcp-suite/data/
+-- scrum-board.db # Sprint, storie, task
+-- scrum-board.db-wal # WAL file (Write-Ahead Log)
+-- scrum-board.db-shm # Shared memory per WAL
+-- standup-notes.db # Note di standup
+-- time-tracking.db # Registrazioni tempo
+-- snippet-manager.db # Snippet di codice
+-- project-economics.db # Budget, costi
+-- retrospective-manager.db # Retrospettive
+-- environment-manager.db # Variabili d'ambiente
증분 마이그레이션 시스템
마이그레이션 시스템을 사용하면 서버가 데이터베이스 스키마를 다음과 같은 방식으로 발전시킬 수 있습니다.
점진적이고 안전합니다. 각 마이그레이션에는 점진적 버전 번호, 설명이 있습니다.
읽을 수 있고 실행할 SQL입니다. 테이블 _migrations 추적하다
마이그레이션이 이미 적용되었습니다.
export interface Migration {
version: number; // Numero progressivo
description: string; // Descrizione leggibile
up: string; // SQL da eseguire
}
export function runMigrations(db: Database.Database, migrations: Migration[]): void {
// 1. Crea la tabella _migrations se non esiste
db.exec(`
CREATE TABLE IF NOT EXISTS _migrations (
version INTEGER PRIMARY KEY,
description TEXT NOT NULL,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`);
// 2. Legge le migrazioni già applicate
const applied = db
.prepare('SELECT version FROM _migrations ORDER BY version')
.all() as Array<{ version: number }>;
const appliedVersions = new Set(applied.map((m) => m.version));
// 3. Ordina e applica solo le migrazioni nuove
const sorted = [...migrations].sort((a, b) => a.version - b.version);
const insertMigration = db.prepare(
'INSERT INTO _migrations (version, description) VALUES (?, ?)',
);
for (const migration of sorted) {
if (appliedVersions.has(migration.version)) continue;
db.exec(migration.up);
insertMigration.run(migration.version, migration.description);
}
}
서버가 데이터베이스를 사용하는 방법
지속성이 필요한 각 서버는 자체 마이그레이션을 정의하고 적용합니다. 스토어 빌더에서:
// In servers/scrum-board/src/services/scrum-store.ts
const migrations: Migration[] = [
{
version: 1,
description: 'Create sprints, stories, and tasks tables',
up: `
CREATE TABLE IF NOT EXISTS sprints (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
startDate TEXT NOT NULL,
endDate TEXT NOT NULL,
goals TEXT NOT NULL DEFAULT '[]',
status TEXT NOT NULL DEFAULT 'planning',
createdAt TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS stories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
storyPoints INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'todo',
sprintId INTEGER REFERENCES sprints(id),
createdAt TEXT NOT NULL DEFAULT (datetime('now'))
);
`,
},
];
export class ScrumStore {
private db: Database.Database;
constructor(options?: { inMemory?: boolean }) {
this.db = createDatabase({
serverName: 'scrum-board',
inMemory: options?.inMemory,
});
runMigrations(this.db, migrations);
}
}
@mcp-suite/testing: TestHarness 및 MockEventBus
패키지 @mcp-suite/testing MCP 서버를 테스트하는 유틸리티를 제공합니다.
절연, 실제 클라이언트나 네트워크 연결이 필요하지 않습니다.. 두 가지를 제공합니다
주요 구성 요소: 테스트하네스 기반으로 InMemoryTransport
MCP SDK의 모의이벤트버스 테스트에서 이벤트의 출력을 확인합니다.
TestHarness: 프로세스 내 테스트
Il TestHarness 다음을 사용하여 메모리에 연결된 클라이언트-서버 쌍을 만듭니다.
InMemoryTransport.createLinkedPair(). 테스트 클라이언트는 도구를 호출할 수 있습니다.
실제 클라이언트와 마찬가지로 리소스를 나열하고 결과를 확인합니다.
외부 프로세스, 네트워크 포트 또는 STDIO 파일.
export interface TestHarness {
client: Client; // Client MCP collegato al server
close: () => Promise<void>; // Funzione per chiudere la connessione
}
export async function createTestHarness(server: McpServer): Promise<TestHarness> {
// 1. Crea coppia di trasporti collegati in memoria
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
// 2. Crea un client di test
const client = new Client({
name: 'test-client',
version: '1.0.0',
});
// 3. Collega server e client ai rispettivi trasporti
await server.connect(serverTransport);
await client.connect(clientTransport);
// 4. Restituisce client e funzione di cleanup
return {
client,
close: async () => {
await client.close();
await server.close();
},
};
}
테스트하네스 아키텍처
+-------------------------------------------------------+
| Processo di Test |
| |
| +------------+ InMemoryTransport +------------+ |
| | Client |<--------------------->| Server | |
| | (test) | (collegamento | (MCP) | |
| | | bidirezionale | | |
| | callTool | in memoria) | tools | |
| | listTools | | resources | |
| +------------+ +------------+ |
| |
| Nessun processo esterno |
| Nessuna porta di rete |
| Nessun file STDIO |
+-------------------------------------------------------+
MockEventBus: 테스트에서 이벤트 확인
Il MockEventBus 인터페이스를 구현합니다 EventBus 모두 녹음하기
테스트에서 어설션을 활성화하기 위해 게시된 이벤트입니다. 다음과 같은 유틸리티 방법을 제공합니다.
wasPublished() e getPublishedEvents().
export class MockEventBus implements EventBus {
public published: PublishedEvent[] = [];
async publish<E extends EventName>(
event: E, payload: EventPayload<E>
): Promise<void> {
this.published.push({ event, payload, timestamp: new Date() });
// Esegue anche gli handler registrati (per testare i subscriber)
}
// Metodi di utilita per le asserzioni
wasPublished(eventName: string): boolean {
return this.published.some((e) => e.event === eventName);
}
getPublishedEvents(eventName?: string): PublishedEvent[] {
if (eventName) {
return this.published.filter((e) => e.event === eventName);
}
return this.published;
}
clear(): void {
this.published = [];
}
}
Vitest를 사용한 완전한 테스트 예
다음은 TestHarness와 MockEventBus를 모두 사용하여 확인하는 전체 테스트입니다. 스프린트를 생성하고 해당 이벤트를 발행합니다.
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createTestHarness, type TestHarness } from '@mcp-suite/testing';
import { MockEventBus } from '@mcp-suite/testing';
describe('Scrum Board Server', () => {
let harness: TestHarness;
let eventBus: MockEventBus;
beforeAll(async () => {
eventBus = new MockEventBus();
const suite = createScrumBoardServer(eventBus);
harness = await createTestHarness(suite.server);
});
afterAll(async () => {
await harness.close();
});
it('should create a sprint', async () => {
const result = await harness.client.callTool({
name: 'create-sprint',
arguments: {
name: 'Sprint 1',
startDate: '2025-01-13',
endDate: '2025-01-24',
goals: ['Completare autenticazione'],
},
});
expect(result.content).toBeDefined();
expect(result.isError).toBeUndefined();
});
it('should emit sprint-started event', async () => {
expect(eventBus.wasPublished('scrum:sprint-started')).toBe(true);
const events = eventBus.getPublishedEvents('scrum:sprint-started');
expect(events).toHaveLength(1);
expect(events[0].payload).toMatchObject({
name: 'Sprint 1',
startDate: '2025-01-13',
});
});
it('should list tools', async () => {
const tools = await harness.client.listTools();
const toolNames = tools.tools.map(t => t.name);
expect(toolNames).toContain('create-sprint');
expect(toolNames).toContain('sprint-board');
});
});
@mcp-suite/cli: 터미널에서 서버 관리
패키지 @mcp-suite/cli 명령줄 기반 인터페이스를 제공합니다.
Commander.js MCP Suite 서버를 관리합니다. 세 가지 명령을 제공합니다. list
사용 가능한 모든 서버를 나열하려면 start 특정 서버를 시작하려면 e
status 어떤 서버가 컴파일되었는지 확인합니다.
# Elencare tutti i server disponibili
npx @mcp-suite/cli list
# Avviare un server con trasporto STDIO (default)
npx @mcp-suite/cli start scrum-board
# Avviare un server con trasporto HTTP
npx @mcp-suite/cli start scrum-board --transport http
# Verificare lo stato di compilazione
npx @mcp-suite/cli status
CLI가 디렉터리를 검색합니다. servers/ 모든 서버를 자동으로 검색하려면
사용 가능하며 파일이 있는지 확인합니다. dist/index.js 시작하기 전에 작성
서버. 명령 start Node.js 프로세스를 하위 프로세스로 시작하고,
stdin/stdout/stderr을 전달하고 적절한 환경 변수를 설정합니다.
npx @mcp-suite/cli status
MCP Suite Status:
Total servers: 22
Built: 20
Not built: 2
Built servers:
+ agile-metrics
+ api-documentation
+ scrum-board
...
Not built:
x docker-compose
x performance-profiler
@mcp-suite/client-manager: 서버 간 통신을 위한 클라이언트 풀
패키지 @mcp-suite/client-manager 관리하다 MCP 클라이언트 풀
동기식 서버 간 통신을 위한 것입니다. 서버가 노출된 도구를 호출할 수 있도록 허용합니다.
다른 서버는 마치 로컬 기능인 것처럼 전송 세부 정보를 완전히 추상화합니다.
EventBus(fire-and-forget, 비동기식)와 달리 Client Manager는 통신 기능을 제공합니다.
동기식 요청/응답.
ServerRegistryEntry 및 McpClientManager
클라이언트 매니저는 패턴으로 작업합니다. 레지스트리 + 지연 연결 + 풀: 대상 서버를 등록하면 처음 사용할 때만 연결이 생성되고 후속 호출에서 재사용됩니다.
export interface ServerRegistryEntry {
name: string; // Nome univoco del server target
transport: 'stdio' | 'http' | 'in-memory'; // Tipo di trasporto
command?: string; // Comando per STDIO (es. 'node')
args?: string[]; // Argomenti per STDIO
url?: string; // URL per HTTP (es. 'http://localhost:3018/mcp')
env?: Record<string, string>; // Variabili d'ambiente per STDIO
}
class McpClientManager {
// Registrazione
register(entry: ServerRegistryEntry): void;
registerMany(entries: ServerRegistryEntry[]): void;
// Connessione (lazy)
getClient(serverName: string): Promise<Client>;
// Chiamate RPC
callTool(serverName: string, toolName: string,
args?: Record<string, unknown>): Promise<unknown>;
readResource(serverName: string, uri: string): Promise<unknown>;
// Gestione ciclo vita
disconnect(serverName: string): Promise<void>;
disconnectAll(): Promise<void>;
// Query
getRegisteredServers(): string[];
isConnected(serverName: string): boolean;
}
지원되는 3가지 전송 수단
클라이언트 관리자는 세 가지 전송 유형을 지원하며 각 유형은 서로 다른 시나리오에 적합합니다.
클라이언트 관리자 전송
| 수송 | 대본 | 세부 |
|---|---|---|
| HTTP | 별도의 프로세스 또는 시스템에 있는 서버 | MCP SDK에서 스트리밍 가능한 HTTP 사용 |
| STDIO | 스레드로서의 서버 | 프로세스 stdin/stdout을 통해 통신합니다. |
| 인메모리 | 진행 중인 테스트 및 시나리오 | InMemoryTransport.createLinkedPair() 사용 |
// Trasporto HTTP
clientManager.register({
name: 'scrum-board',
transport: 'http',
url: 'http://localhost:3018/mcp',
});
// Trasporto STDIO
clientManager.register({
name: 'scrum-board',
transport: 'stdio',
command: 'node',
args: ['servers/scrum-board/dist/index.js'],
env: { MCP_SUITE_TRANSPORT: 'stdio' },
});
// Trasporto InMemory (per test)
const [clientTransport, serverTransport] = McpClientManager.createInMemoryPair();
await targetServer.connect(serverTransport);
await clientManager.connectInMemoryWithTransport('target-server', clientTransport);
게으른 연결과 우아한 저하
클라이언트 매니저는 이런 방식으로 클라이언트를 생성합니다. 게으른: 연결이 설정되었습니다.
첫 번째 통화에만 callTool(). 후속 호출에서는 클라이언트를 재사용합니다.
캐시에서. 대상 서버에 연결할 수 없는 경우 호출 도구는 다음 패턴을 사용합니다.
우아한 저하: 서버 간 강화 없이 계속 작업합니다.
// Esempio completo: uso in un server
const clientManager = new McpClientManager();
clientManager.registerMany([
{
name: 'scrum-board',
transport: 'http',
url: process.env.MCP_SUITE_SCRUM_BOARD_URL || 'http://localhost:3018/mcp',
},
{
name: 'time-tracking',
transport: 'http',
url: process.env.MCP_SUITE_TIME_TRACKING_URL || 'http://localhost:3022/mcp',
},
]);
// Chiamata cross-server con graceful degradation
if (enrichFromExternal && clientManager) {
try {
const result = await clientManager.callTool('scrum-board', 'get-sprint',
{ sprintId: 1 });
// usa il risultato per arricchire i dati
} catch (error) {
logger.warn('Cross-server call failed, continuing without enrichment');
}
}
// Pulizia alla fine
await clientManager.disconnectAll();
요약: 패키지가 함께 작동하는 방식
6개의 공유 패키지는 각 서버를 생성할 수 있는 계층형 아키텍처를 형성합니다. 몇 줄의 코드로 구성, 테스트 및 조정되었습니다.
MCP 서버의 수명주기
- 창조:
createMcpServer()da@mcp-suite/core구성, 로거 및 EventBus를 사용하여 서버 생성 - 고집:
createDatabase()da@mcp-suite/databaseWAL 모드로 SQLite 데이터베이스 생성 - 마이그레이션:
runMigrations()증분 방식을 적용 - 이벤트:
LocalEventBusda@mcp-suite/event-bus실행 후 잊어버리는 협업 가능 - 서버 간:
McpClientManagerda@mcp-suite/client-manager동기 RPC 호출 활성화 - 시작:
startServer()적절한 교통수단부터 시작하세요 - 테스트:
createTestHarness()da@mcp-suite/testing진행 중인 테스트를 허용합니다. - 관리:
@mcp-suite/cli터미널에서 서버를 관리합니다
결론
공유 패키지는 Tech-MCP의 아키텍처 핵심을 나타냅니다. 그들 없이는 모든
서버는 공장, 로거, 데이터베이스 관리, 이벤트 시스템을 다시 구현해야 합니다.
서버 간 통신 - 복잡성을 22배로 늘립니다.
이 아키텍처 덕분에 새 서버를 만드는 것은 몇 줄이면 됩니다.
에서 @mcp-suite/core, 다음을 사용하여 마이그레이션을 정의합니다. @mcp-suite/database
도구를 등록합니다.
다음 기사에서는 공유 패키지에서 다음으로 이동하겠습니다. 콘크리트 서버, 나 탐색 생산성 서버: 코드 분석을 위한 코드 리뷰, 종속성을 관리하기 위한 종속성 관리자 및 스캐폴딩을 위한 프로젝트 스캐폴딩 새로운 프로젝트의 자동. 우리는 이러한 서버가 인프라를 어떻게 활용하는지 살펴보겠습니다. 최소한의 코드로 고급 기능을 제공하는 공유 패키지입니다.
모든 공유 패키지의 전체 코드는 저장소에서 사용할 수 있습니다. GitHub의 Tech-MCP.







