@mcp-suite/core
Introduzione
Il pacchetto @mcp-suite/core e il cuore della suite. Fornisce le utility fondamentali utilizzate da tutti i 22 server: la factory per la creazione di server, il sistema di configurazione, il logger strutturato, la gerarchia di errori e i tipi di dominio condivisi.
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
Dipendenze:
@modelcontextprotocol/sdk- SDK ufficiale del protocollo MCP@mcp-suite/event-bus- EventBus per collaborazione inter-serverzod- Validazione e parsing dello schema di configurazione
server-factory.ts
Questo modulo contiene la factory principale per la creazione di server MCP e le funzioni di avvio.
CreateServerOptions
L'interfaccia per le opzioni di creazione del server:
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
}
McpSuiteServer
L'interfaccia che rappresenta un server istanziato e pronto per l'uso:
export interface McpSuiteServer {
name: string; // Nome univoco del server (es. 'scrum-board')
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 attivo)
}
Questa interfaccia e il contratto centrale dell'architettura: ogni server la implementa, ogni funzione di registrazione tool la riceve (o riceve i suoi campi).
createMcpServer()
La factory che crea l'istanza 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 };
}
Flusso:
CreateServerOptions
│
├──► loadConfig(name, overrides) ──► ServerConfig
│
├──► new Logger(name, logLevel) ──► Logger
│
├──► new McpServer({name, version}) ──► McpServer
│
└──► { server, config, logger, eventBus } ──► McpSuiteServer
startServer(), startStdioServer() e startHttpServer()
Funzioni per avviare il server con il trasporto appropriato:
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(), // Modalita stateful
});
await suite.server.connect(transport);
// Route MCP standard (Streamable HTTP spec)
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.delete('/mcp', async (req, res) => { await transport.handleRequest(req, res); });
// Health check
app.get('/health', (_req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ 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);
}
}
La funzione startServer() seleziona automaticamente il trasporto in base alla configurazione (MCP_SUITE_TRANSPORT=http o MCP_SUITE_TRANSPORT=stdio).
Il trasporto HTTP usa il protocollo Streamable HTTP dell'SDK MCP con modalita stateful (ogni sessione ha un UUID). L'app Express e creata tramite createMcpExpressApp() dell'SDK, che gestisce il parsing del body e le route standard. L'endpoint /health permette il monitoraggio.
config.ts
Il modulo di configurazione gestisce il caricamento dei parametri da variabili d'ambiente con validazione 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()
Il caricamento segue una cascata di priorita:
Variabile Specifica ──► Variabile Globale ──► Override Programmatico ──► Default Zod
(MCP_SUITE_XXX_YYY) (MCP_SUITE_YYY) (parametro overrides) (schema .default())
export function loadConfig(
serverName: string,
overrides?: Partial<ServerConfig>
): ServerConfig {
const raw: Record<string, unknown> = {};
// Cerca variabile specifica, poi globale
const transport = process.env[envKey(serverName, 'TRANSPORT')]
|| process.env.MCP_SUITE_TRANSPORT;
if (transport) raw.transport = transport;
// ... altri campi ...
const merged = { ...raw, ...overrides };
return ServerConfigSchema.parse(merged); // Validazione + default
}
La funzione helper envKey converte il nome del server nel formato variabile d'ambiente:
function envKey(serverName: string, field: string): string {
const prefix = serverName.replace(/-/g, '_').toUpperCase();
return `MCP_SUITE_${prefix}_${field.toUpperCase()}`;
}
// envKey('scrum-board', 'LOG_LEVEL') => 'MCP_SUITE_SCRUM_BOARD_LOG_LEVEL'
logger.ts
Il Logger scrive log strutturati in formato JSON su stderr. L'uso di stderr e fondamentale: il protocollo MCP usa stdout per la comunicazione JSON-RPC, quindi i log devono andare su un canale separato.
Classe Logger
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
const LOG_LEVELS: Record<LogLevel, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
};
export class Logger {
private level: number;
constructor(
private readonly name: string, // Nome del server
level: LogLevel = 'info', // Livello minimo di log
) {
this.level = LOG_LEVELS[level];
}
debug(message: string, data?: Record<string, unknown>): void {
this.log('debug', message, data);
}
info(message: string, data?: Record<string, unknown>): void {
this.log('info', message, data);
}
warn(message: string, data?: Record<string, unknown>): void {
this.log('warn', message, data);
}
error(message: string, data?: Record<string, unknown>): void {
this.log('error', message, data);
}
private log(level: LogLevel, message: string, data?: Record<string, unknown>): void {
if (LOG_LEVELS[level] < this.level) return; // Filtraggio per livello
const entry = {
timestamp: new Date().toISOString(),
level,
server: this.name,
message,
...data, // Dati aggiuntivi strutturati
};
process.stderr.write(JSON.stringify(entry) + '\n');
}
}
Esempio di output:
{"timestamp":"2025-01-15T10:30:00.000Z","level":"info","server":"scrum-board","message":"All scrum-board tools registered"}
Perche stderr?
┌───────────────────────────────────┐
│ Processo Node.js │
│ │
│ stdout ──► JSON-RPC (MCP) │ ← comunicazione con il client
│ stderr ──► Log strutturati │ ← messaggi diagnostici
└───────────────────────────────────┘
Se i log andassero su stdout, corromperebbero il flusso JSON-RPC e il client MCP non potrebbe comunicare con il server.
errors.ts
Una gerarchia di errori tipizzati per gestire i diversi tipi di fallimento in modo uniforme.
McpSuiteError (base)
├── ConfigError (errori di configurazione)
├── ConnectionError (errori di connessione)
├── ToolExecutionError (errori durante l'esecuzione di un tool)
├── NotFoundError (risorsa non trovata)
└── ValidationError (errori di validazione input)
McpSuiteError (classe base)
export class McpSuiteError extends Error {
constructor(
message: string,
public readonly code: string, // Codice errore machine-readable
public readonly details?: unknown, // Dettagli aggiuntivi
) {
super(message);
this.name = 'McpSuiteError';
}
}
Classi derivate
Esempio di utilizzo
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"
// Codice: "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
Questo modulo definisce tutti i tipi di dominio condivisi tra i server. I tipi sono organizzati per area funzionale.
Tipi di Risultato dei Tool
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;
Organizzazione dei Tipi per Dominio
Export del Pacchetto
Il file index.ts ri-esporta tutto in modo organizzato:
// Factory e server
export { createMcpServer, startStdioServer, startHttpServer, startServer,
type CreateServerOptions, type McpSuiteServer } from './server-factory.js';
// EventBus (ri-esportato per comodita)
export type { EventBus } from '@mcp-suite/event-bus';
// Configurazione
export { loadConfig, ServerConfigSchema, type ServerConfig } from './config.js';
// Logger
export { Logger, type LogLevel } from './logger.js';
// Errori
export { McpSuiteError, ConfigError, ConnectionError,
ToolExecutionError, NotFoundError, ValidationError } from './errors.js';
// Tipi di dominio (30+ tipi esportati)
export type { ToolSuccess, ToolError, ToolResult, FileReference,
GitCommitInfo, CodeIssue, TaskStatus, /* ... */ } from './types.js';
Questo permette ai server di importare tutto da un unico punto:
import {
createMcpServer,
type McpSuiteServer,
type EventBus,
Logger,
NotFoundError
} from '@mcp-suite/core';