はじめに: Tech-MCP の 6 つの共有パッケージ
モノリポジトリで 22 台の MCP サーバー、コードの重複は壊滅的な問題になります。 各サーバーには独自のファクトリー、独自のロガー、独自のデータベース管理が必要です。 メンテナンスの複雑さは 22 倍になります。プロジェクトで採用されたアーキテクチャ ソリューション テックMCP そしてそれの 共有パッケージ: すべてのトランスバーサル ロジックを集約した 6 つの内部ライブラリ 単一ポイントにあり、各サーバーで再利用可能です。
これらのパッケージは単純なユーティリティではありません。重要なインフラ すべてのサーバーはこれに基づいています。ファクトリは 4 行のコードでサーバーを作成し、EventBus により次のことが可能になります。 29 種類のイベントを使用したサーバーのコラボレーションにより、データベースは SQLite の永続性を提供します。 自動移行、テストによりネットワークを使用せずにプロセス内テストが可能、CLI がサーバーを管理 端末から送信され、クライアント マネージャーがサーバー間の同期通信を調整します。
6 つの共有パッケージ
| パッケージ | 責任 | 主要な依存関係 |
|---|---|---|
| @mcpスイート/コア | サーバー ファクトリ、構成、ロガー、エラー、タイプ | @modelcontextprotocol/sdk、zod |
| @mcp-suite/event-bus | 29 の型付きイベント、LocalEventBus、パターン マッチング | マイクロマッチ |
| @mcpスイート/データベース | createDatabase、WAL モード、増分移行 | より良い-sqlite3 |
| @mcp-suite/テスト | TestHarness、MockEventBus、InMemoryTransport | @モデルコンテキストプロトコル/SDK |
| @mcp-suite/cli | サーバー管理用のリスト、開始、ステータスコマンド | 指揮官 |
| @mcp-suite/クライアントマネージャー | クライアント プール、3 つのトランスポート、サーバー間通信 | @モデルコンテキストプロトコル/SDK |
@mcp-suite/core: スイートの中心部
パッケージ @mcp-suite/core そしてすべてのサーバーが構築される基盤です。
これは 5 つの重要なモジュールを提供します。 サーバーを作成するためのファクトリー, il
構成システム Zod 検証では、 構造化ロガー
標準エラー出力に書き込む人、1 人 典型的なエラーの階層 そして私 ドメインの種類
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() そして各サーバーがそれ自体を作成するために呼び出すファクトリ。
わずか数行で、彼は 4 つの基本的な操作を調整します。
環境変数を使用し、構成されたレベルでロガーを作成し、 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: 標準エラー出力の構造化ロガー
ロガーは構造化ログを JSON 形式で書き込みます。 stderr。この選択は、
基本的: MCP プロトコルが使用する stdout コミュニケーションのために
JSON-RPC なので、ログは別のチャネルに送信される必要があります。ログが標準出力に出力された場合、
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');
}
}
なぜ標準エラーなのか?
MCP プロトコルでは、 stdout クライアントとサーバー間の JSON-RPC メッセージ専用。
ロガーが標準出力に書き込むと、ログ メッセージがプロトコルと混同されます。
コミュニケーションを破壊する。 stderr 診断用に指定されたチャネル、
データとログの分離を維持します。
errors.ts: 型指定されたエラーの階層
パッケージは、サーバーが処理できる型指定されたエラーの階層を定義します。 さまざまな種類の障害を均一に処理します。各エラーには機械可読コードがあり、 デバッグ用のオプションの詳細。
エラーの階層
| クラス | コード | 使用 |
|---|---|---|
McpSuiteError |
- | すべてのエラーの基本クラス |
ConfigError |
設定エラー | 構成が無効または欠落しています |
ConnectionError |
接続エラー | データベースまたはサービスへの接続の問題 |
ToolExecutionError |
TOOL_EXECUTION_ERROR | MCP ツールの実行中にエラーが発生しました |
NotFoundError |
見つかりません | 要求されたリソースが見つかりません |
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);
}
types.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;
ドメインごとのタイプの構成
| ドメイン | 主な種類 | 使用者 |
|---|---|---|
| コードと Git | FileReference, GitCommitInfo, CodeIssue |
コードレビュー、コードベースの知識 |
| プロジェクト管理 | TaskStatus, UserStory, SprintInfo |
スクラムボード、アジャイルメトリクス |
| 経済 | BudgetInfo, BudgetCategory, CostEntry |
プロジェクトの経済学 |
| CI/CD | PipelineStatus, PipelineRun, PipelineStage |
cicd-モニター |
| データベース | TableInfo, ColumnInfo, IndexInfo |
データベーススキーマエクスプローラー |
ファイル 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 あらゆる実装の契約を定義します
イベントバスの様子。ここでは 4 つの方法を紹介します。 publish 型指定されたイベントを発行するには、
subscribe 特定のイベントを聞くには、 subscribePattern のために
ワイルドカード パターンに一致するイベントをリッスンします。 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 |
|---|---|---|---|
| コードと Git | 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 = [];
}
}
ファイアアンドフォーゲットパターンとワイルドカードパターン
ツールはオペレーターを使用してイベントをパブリッシュします オプションのチェーン (?.)
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 データベースの。これは 2 つの主な機能を提供します。 工場 作成する
データベース接続 (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 ディレクトリの作成を自動的に管理します。
の構成 WALモード そして、 外部キー。
モードにも対応しています インメモリ テスト用。
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 パフォーマンスの向上 クラッシュリカバリー 自動。リーダーはファイルから読み取ります main は、ライターが 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 サーバーを何らかの方法でテストするためのユーティリティを提供します
孤立した、 実際のクライアントやネットワーク接続を必要とせずに。 2つを提供します
主なコンポーネント: テストハーネス に基づく 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();
},
};
}
TestHarness アーキテクチャ
+-------------------------------------------------------+
| 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 コマンドラインベースのインターフェースを提供します
コマンダー.js MCP Suite サーバーを管理します。次の 3 つのコマンドが提供されます。 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 (ファイアアンドフォーゲット、非同期) とは異なり、クライアント マネージャーは通信を提供します。
同期リクエスト/レスポンス.
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 つのトランスポート
Client Manager は 3 つのトランスポート タイプをサポートしており、それぞれが異なるシナリオに最適です。
クライアントマネージャートランスポート
| 輸送 | シナリオ | 詳細 |
|---|---|---|
| HTTP | 別のプロセスまたはマシン内のサーバー | MCP SDK からのストリーミング可能な HTTP を使用する |
| スタジオ | スレッドとしてのサーバー | プロセス 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);
遅延接続と正常な劣化
Client Manager はこの方法でクライアントを作成します 怠け者:接続が確立されました
最初の通話時のみ 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
そしてツールを登録します。
次の記事では、共有パッケージから コンクリートサーバー、 私を探索する 生産性向上サーバー: コード分析のためのコードレビュー、 依存関係を管理するための dependency-manager と、スキャフォールディングを行うための project-scaffolding 新しいプロジェクトを自動的に作成します。これらのサーバーがどのようにインフラストラクチャを利用するかを見ていきます。 共有パッケージを使用して、最小限のコードで高度な機能を提供します。
すべての共有パッケージの完全なコードはリポジトリで入手できます。 GitHub 上の Tech-MCP.







