Panoramica: Otto Server Avanzati per l'Ecosistema MCP
Negli articoli precedenti abbiamo analizzato i server MCP dedicati al project management, alla produttività e al DevOps. Con questo undicesimo articolo della serie affrontiamo gli otto server avanzati che completano l'architettura del progetto Tech-MCP: gestione degli incidenti, quality gate, orchestrazione di workflow, controllo degli accessi, decision log, analytics cross-server, service registry e dashboard aggregata.
Questi server rappresentano il livello più sofisticato dell'ecosistema. Non operano in isolamento, ma sono progettati per collaborare tra loro attraverso l'EventBus e il ClientManager, creando un sistema integrato capace di reagire automaticamente agli eventi, applicare politiche di sicurezza, verificare la qualità del codice e orchestrare flussi di lavoro complessi.
Cosa Imparerai in Questo Articolo
- Come gestire il ciclo di vita completo di un incidente con 6 tool dedicati
- Come registrare e tracciare Architecture Decision Records (ADR) con stati e link
- Come implementare il controllo degli accessi RBAC con policy allow/deny
- Come definire e valutare quality gate basati su metriche con operatori di confronto
- Come orchestrare workflow event-driven con esecuzione cross-server via ClientManager
- Come aggregare analytics e metriche da tutti i server con l'insight engine
- Come implementare service discovery e health monitoring con il registry
- Come esporre dashboard aggregate con caching e dati da fonti multiple
Mappa dei Server Avanzati
Prima di analizzare ogni server nel dettaglio, ecco una panoramica sintetica dei ruoli e delle relazioni tra gli otto server avanzati:
Riepilogo degli 8 Server Avanzati
| Server | Ruolo | Tool | EventBus | ClientManager |
|---|---|---|---|---|
| incident-manager | Gestione ciclo di vita incidenti | 6 | Produce eventi | No |
| decision-log | Architecture Decision Records | 5 | Produce eventi | No |
| access-policy | Controllo accessi RBAC/ABAC | 5 | Produce eventi | No |
| quality-gate | Quality gate basati su metriche | 4 | Produce eventi | No |
| workflow-orchestrator | Orchestrazione workflow event-driven | 5 | Produce + Consuma | Si |
| insight-engine | Analytics e correlazione cross-server | 4 | No | Si |
| mcp-registry | Service discovery e health check | 4 | Produce eventi | No |
| dashboard-api | Dashboard aggregata multi-server | 4 | No | Si |
1. Incident Manager: Gestione del Ciclo di Vita degli Incidenti
Il server incident-manager gestisce l'intero ciclo di vita di un incidente: dall'apertura alla risoluzione, passando per l'investigazione, la mitigazione e la generazione automatica del post-mortem. Ogni transizione di stato viene tracciata in una timeline dettagliata, e gli eventi critici vengono pubblicati sull'EventBus per consentire reazioni automatiche da parte di altri server.
Il Ciclo di Vita di un Incidente
Un incidente attraversa cinque stati distinti, ciascuno con un significato preciso nel processo di gestione:
[open] ──────> [investigating] ──────> [mitigating] ──────> [resolved] ──────> [postmortem]
│ │ │ │
│ │ │ └── Genera report
│ │ └── Impatto ridotto post-mortem
│ └── Root cause in analisi
└── Incidente appena aperto
Eventi pubblicati:
incident:opened → Quando un incidente viene aperto
incident:escalated → Quando la severity cambia
incident:resolved → Quando l'incidente viene risolto
I 6 Tool dell'Incident Manager
Tool Disponibili
| Tool | Descrizione | Parametri Principali |
|---|---|---|
open-incident |
Apre un nuovo incidente | title, severity, description, affectedSystems |
update-incident |
Aggiorna stato e/o aggiunge nota | id, status, note |
add-timeline-entry |
Aggiunge entry alla timeline | incidentId, description, source |
resolve-incident |
Risolve con summary e root cause | id, resolution, rootCause |
generate-postmortem |
Genera report post-mortem formattato | id |
list-incidents |
Elenca incidenti con filtri | status, severity, limit |
Esempio: Apertura e Risoluzione di un Incidente
Ecco il flusso completo dalla creazione alla risoluzione di un incidente critico, con pubblicazione degli eventi sull'EventBus:
// 1. Apertura dell'incidente
server.tool('open-incident', 'Open a new incident...', {
title: z.string().describe('Short title of the incident'),
severity: z.enum(['critical', 'high', 'medium', 'low'])
.describe('Incident severity level'),
description: z.string().describe('Detailed description'),
affectedSystems: z.array(z.string()).optional()
.describe('List of affected systems or services'),
}, async ({ title, severity, description, affectedSystems }) => {
const incident = store.openIncident({
title, severity, description, affectedSystems
});
// Pubblicazione evento sull'EventBus
eventBus?.publish('incident:opened', {
incidentId: String(incident.id),
title: incident.title,
severity: incident.severity,
affectedSystems: incident.affectedSystems,
});
return {
content: [{ type: 'text', text: JSON.stringify(incident, null, 2) }]
};
});
Quando l'incidente viene risolto, il tool resolve-incident calcola automaticamente
la durata dell'incidente e genera i dati per il post-mortem:
// 2. Risoluzione con root cause analysis
server.tool('resolve-incident', '...', {
id: z.number().int().positive(),
resolution: z.string().describe('Summary of how it was resolved'),
rootCause: z.string().optional().describe('Root cause analysis'),
}, async ({ id, resolution, rootCause }) => {
const resolved = store.resolveIncident(id, resolution, rootCause);
const postmortem = store.generatePostmortemData(id);
const durationMinutes = postmortem?.durationMinutes ?? 0;
eventBus?.publish('incident:resolved', {
incidentId: String(id),
title: resolved.title,
resolution,
durationMinutes,
});
return {
content: [{ type: 'text', text: JSON.stringify(resolved, null, 2) }]
};
});
Modello Dati: Incident e Timeline
Lo store SQLite gestisce due tabelle: incidents per i dati principali e
incident_timeline per tracciare ogni evento durante il ciclo di vita:
interface Incident {
id: number;
title: string;
severity: string; // 'critical' | 'high' | 'medium' | 'low'
description: string;
status: string; // 'open' | 'investigating' | 'mitigating' | 'resolved' | 'postmortem'
affectedSystems: string[];
resolution: string | null;
rootCause: string | null;
createdAt: string;
resolvedAt: string | null;
}
interface TimelineEntry {
id: number;
incidentId: number;
description: string;
source: string | null; // es. 'monitoring', 'engineer', 'automated'
timestamp: string;
}
Collaborazione con Altri Server
L'incident-manager ascolta anche eventi da altri server attraverso i collaboration handler.
Ad esempio, un evento perf:bottleneck-found dal performance profiler o un evento
cicd:build-failed dal CI/CD monitor possono automaticamente aggiungere entry
alla timeline di un incidente correlato.
2. Decision Log: Architecture Decision Records
Il server decision-log implementa la gestione degli Architecture Decision Records (ADR), un pattern consolidato per documentare le decisioni architetturali di un progetto. Ogni decisione viene registrata con il suo contesto, le alternative considerate, le conseguenze previste e lo stato corrente.
Stati di una Decisione
Una decisione attraversa un ciclo di vita con quattro stati possibili:
[proposed] ──────> [accepted] ──────> [deprecated]
│
└──────> [superseded] ← (sostituita da una nuova decisione)
Eventi pubblicati:
decision:created → Quando una nuova decisione viene registrata
decision:superseded → Quando una decisione viene sostituita
I 5 Tool del Decision Log
Tool Disponibili
| Tool | Descrizione | Parametri Principali |
|---|---|---|
record-decision |
Registra un nuovo ADR | title, context, decision, alternatives, status |
list-decisions |
Elenca le decisioni registrate | Filtri opzionali |
get-decision |
Recupera una decisione per ID | id |
supersede-decision |
Sostituisce una decisione con una nuova | id, supersededBy |
link-decision |
Collega a ticket, commit o impatto | decisionId, linkType, targetId |
Esempio: Registrazione e Collegamento di un ADR
Una decisione architetturale viene registrata con contesto, alternative e conseguenze, poi collegata ai ticket e commit correlati:
// Registrazione di una decisione
server.tool('record-decision', '...', {
title: z.string().describe('Short title, e.g. "Use PostgreSQL for analytics"'),
context: z.string().describe('Context and problem statement'),
decision: z.string().describe('The decision that was made'),
alternatives: z.array(z.string()).optional()
.describe('Alternative options considered'),
consequences: z.string().optional()
.describe('Expected consequences'),
status: z.enum(['proposed', 'accepted', 'deprecated', 'superseded'])
.optional().default('proposed'),
relatedTickets: z.array(z.string()).optional()
.describe('Related ticket IDs, e.g. ["PROJ-123"]'),
}, async (args) => {
const record = store.recordDecision(args);
eventBus?.publish('decision:created', {
decisionId: String(record.id),
title: record.title,
status: record.status,
});
return { content: [{ type: 'text', text: JSON.stringify(record, null, 2) }] };
});
// Collegamento a ticket e commit
server.tool('link-decision', '...', {
decisionId: z.number().int().positive(),
linkType: z.enum(['ticket', 'commit', 'impact', 'related']),
targetId: z.string().describe('Ticket ID, commit hash, etc.'),
description: z.string().optional(),
}, async ({ decisionId, linkType, targetId, description }) => {
const link = store.linkDecision({ decisionId, linkType, targetId, description });
return { content: [{ type: 'text', text: JSON.stringify(link, null, 2) }] };
});
Modello Dati: Decision e Link
interface Decision {
id: number;
title: string;
context: string;
decision: string;
alternatives: string[];
consequences: string;
status: string; // 'proposed' | 'accepted' | 'deprecated' | 'superseded'
relatedTickets: string[];
supersededBy: number | null;
createdAt: string;
updatedAt: string;
}
interface DecisionLink {
id: number;
decisionId: number;
linkType: string; // 'ticket' | 'commit' | 'impact' | 'related'
targetId: string;
description: string | null;
createdAt: string;
}
3. Access Policy: Controllo degli Accessi RBAC
Il server access-policy implementa un sistema di controllo degli accessi
RBAC/ABAC (Role-Based / Attribute-Based Access Control) che opera trasversalmente
su tutti i server e i tool dell'ecosistema MCP. Permette di definire politiche di accesso
con effetto allow o deny, assegnare ruoli agli utenti e verificare
i permessi prima dell'esecuzione di un tool. Ogni verifica viene registrata in un audit log.
I 5 Tool dell'Access Policy
Tool Disponibili
| Tool | Descrizione | Parametri Principali |
|---|---|---|
create-policy |
Crea una nuova policy di accesso | name, effect, rules[] |
check-access |
Verifica se un utente ha accesso | userId, server, tool |
list-policies |
Elenca le policy attive | Nessuno |
assign-role |
Assegna un ruolo a un utente | userId, roleName |
audit-access |
Recupera il log di audit | userId, server, limit |
Esempio: Definizione di una Policy RBAC
Una policy definisce quali ruoli possono accedere a specifici server e tool.
L'effetto può essere allow (consentire) o deny (negare).
Le regole usano il wildcard * per indicare tutti i server o tool:
// Creazione di una policy: solo gli admin possono aprire incidenti
server.tool('create-policy', '...', {
name: z.string().describe('Unique name for the policy'),
effect: z.enum(['allow', 'deny']),
rules: z.array(z.object({
server: z.string().describe('Server name or "*" for all'),
tool: z.string().optional().describe('Tool name, omit for all'),
roles: z.array(z.string()).describe('Roles this rule applies to'),
})),
}, async ({ name, effect, rules }) => {
const policy = store.createPolicy({ name, effect, rules });
eventBus?.publish('access:policy-updated', {
policyId: String(policy.id),
name: policy.name,
effect: policy.effect,
});
return { content: [{ type: 'text', text: JSON.stringify(policy, null, 2) }] };
});
Esempio di Policy
Ecco come si struttura una policy che concede accesso agli sviluppatori senior per specifici server e tool:
{
"name": "senior-dev-policy",
"effect": "allow",
"rules": [
{
"server": "scrum-board",
"roles": ["senior-dev", "tech-lead"]
},
{
"server": "incident-manager",
"tool": "open-incident",
"roles": ["senior-dev", "tech-lead", "ops"]
},
{
"server": "quality-gate",
"tool": "evaluate-gate",
"roles": ["senior-dev"]
}
]
}
Verifica degli Accessi e Audit Log
Il tool check-access verifica i permessi e registra automaticamente ogni
tentativo nel log di audit. Se l'accesso viene negato, viene pubblicato un evento
access:denied sull'EventBus:
// Verifica accesso con audit automatico
server.tool('check-access', '...', {
userId: z.string(),
server: z.string(),
tool: z.string().optional(),
}, async ({ userId, server: targetServer, tool }) => {
const result = store.checkAccess(userId, targetServer, tool);
// Log automatico nell'audit log
store.logAccess(userId, targetServer, tool ?? '*',
result.allowed ? 'allowed' : 'denied', result.reason);
// Evento per accessi negati
if (!result.allowed) {
eventBus?.publish('access:denied', {
userId, server: targetServer,
tool: tool ?? '*', reason: result.reason,
});
}
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
});
Modello Dati: Policy, Ruoli e Audit
Lo store SQLite gestisce quattro tabelle: policies per le politiche di accesso,
roles per la definizione dei ruoli, role_assignments per l'associazione
utente-ruolo e audit_log per il tracciamento di ogni verifica di accesso.
Questa struttura consente di ricostruire l'intera storia degli accessi per compliance e debugging.
4. Quality Gate: Verifiche di qualità Basate su Metriche
Il server quality-gate permette di definire e valutare quality gate basati su metriche quantitative. Ogni gate contiene una lista di check con metrica, operatore di confronto e soglia. Quando un gate viene valutato, il risultato (passed/failed) viene pubblicato sull'EventBus per attivare workflow automatici.
I 4 Tool del Quality Gate
Tool Disponibili
| Tool | Descrizione | Parametri Principali |
|---|---|---|
define-gate |
Definisce un nuovo quality gate | name, projectName, checks[] |
evaluate-gate |
Valuta un gate con metriche reali | gateId, metrics |
list-gates |
Elenca i gate definiti | Nessuno |
get-gate-history |
Storico delle valutazioni | gateId |
Definizione di un Quality Gate con Regole
Un quality gate definisce una serie di check, ciascuno con una metrica, un operatore
di confronto e una soglia. Gli operatori supportati sono: >=, <=,
>, <, == e !=.
// Definizione di un quality gate
server.tool('define-gate', '...', {
name: z.string().describe('Name of the quality gate'),
projectName: z.string().optional(),
checks: z.array(z.object({
metric: z.string().describe('Metric name, e.g. "coverage"'),
operator: z.enum(['>=', '<=', '>', '<', '==', '!=']),
threshold: z.number().describe('Threshold value'),
})),
}, async ({ name, projectName, checks }) => {
const gate = store.defineGate({ name, projectName, checks });
return { content: [{ type: 'text', text: JSON.stringify(gate, null, 2) }] };
});
Esempio: Configurazione di un Quality Gate per il Deploy
{
"name": "deploy-readiness",
"projectName": "Tech-MCP",
"checks": [
{ "metric": "coverage", "operator": ">=", "threshold": 80 },
{ "metric": "complexity", "operator": "<=", "threshold": 15 },
{ "metric": "bugs", "operator": "==", "threshold": 0 },
{ "metric": "duplication", "operator": "<=", "threshold": 3 },
{ "metric": "buildTime", "operator": "<", "threshold": 300 }
]
}
Valutazione e Eventi
Quando il gate viene valutato con le metriche reali del progetto, il risultato determina
quale evento viene pubblicato. Se tutte le metriche soddisfano i check, viene emesso
quality:gate-passed; altrimenti quality:gate-failed con i dettagli dei fallimenti:
// Valutazione del gate
server.tool('evaluate-gate', '...', {
gateId: z.number().int().positive(),
metrics: z.record(z.string(), z.number())
.describe('Map of metric names to current values'),
}, async ({ gateId, metrics }) => {
const evaluation = store.evaluateGate(gateId, metrics);
const gate = store.getGate(gateId);
if (evaluation.passed) {
eventBus?.publish('quality:gate-passed', {
gateName: gate?.name ?? String(gateId),
project: gate?.projectName ?? '',
results: evaluation.results,
});
} else {
eventBus?.publish('quality:gate-failed', {
gateName: gate?.name ?? String(gateId),
project: gate?.projectName ?? '',
failures: evaluation.failures,
});
}
return { content: [{ type: 'text', text: JSON.stringify(evaluation, null, 2) }] };
});
Collaborazione con CI/CD
Il quality-gate ascolta eventi dal CI/CD monitor. Quando un evento cicd:pipeline-completed
viene pubblicato, il server può automaticamente valutare i quality gate associati al progetto.
Allo stesso modo, un evento test:coverage-report può attivare la verifica
dei gate legati alla copertura del codice.
5. Workflow Orchestrator: Automazione Event-Driven
Il workflow-orchestrator e il server più sofisticato dell'intero ecosistema.
Implementa un motore di orchestrazione event-driven che permette di definire
workflow composti da step sequenziali, ciascuno dei quali invoca un tool su un server MCP
diverso attraverso il ClientManager. I workflow vengono attivati automaticamente
da eventi pubblicati sull'EventBus, oppure manualmente tramite il tool trigger-workflow.
I 5 Tool del Workflow Orchestrator
Tool Disponibili
| Tool | Descrizione | Parametri Principali |
|---|---|---|
create-workflow |
Crea un workflow event-driven | name, triggerEvent, steps[] |
list-workflows |
Elenca i workflow definiti | Nessuno |
trigger-workflow |
Avvia manualmente un workflow | workflowId, payload |
get-workflow-run |
Dettagli di un'esecuzione | runId |
toggle-workflow |
Attiva/disattiva un workflow | workflowId, active |
Definizione di un Workflow con Step Cross-Server
Un workflow definisce un evento trigger, condizioni opzionali e una sequenza ordinata
di step. Ogni step specifica il server target, il tool da invocare e gli argomenti.
Gli argomenti supportano template {{payload.field}} e
{{steps[N].result.field}} per passare dati tra step:
// Creazione di un workflow
server.tool('create-workflow', '...', {
name: z.string(),
description: z.string().optional(),
triggerEvent: z.string()
.describe('Event that triggers this workflow, e.g. "incident:opened"'),
triggerConditions: z.record(z.unknown()).optional()
.describe('Conditions to match against the event payload'),
steps: z.array(z.object({
server: z.string().describe('Target MCP server name'),
tool: z.string().describe('Tool to invoke'),
arguments: z.record(z.unknown()).optional()
.describe('Arguments with { {payload.field} } template support'),
})),
}, async (args) => {
const workflow = store.createWorkflow(args);
return { content: [{ type: 'text', text: JSON.stringify(workflow, null, 2) }] };
});
Esempio: Workflow Automatico per Incidente Critico
Ecco un workflow completo che si attiva quando viene aperto un incidente con severity
critical. Il workflow esegue tre step su tre server diversi:
{
"name": "critical-incident-response",
"description": "Automated response for critical incidents",
"triggerEvent": "incident:opened",
"triggerConditions": {
"severity": "critical"
},
"steps": [
{
"server": "decision-log",
"tool": "record-decision",
"arguments": {
"title": "Emergency: {{payload.title}}",
"context": "Critical incident opened affecting {{payload.affectedSystems}}",
"decision": "Activating emergency response protocol",
"status": "accepted"
}
},
{
"server": "quality-gate",
"tool": "evaluate-gate",
"arguments": {
"gateId": 1,
"metrics": {
"activeIncidents": 1,
"severity": 4
}
}
},
{
"server": "incident-manager",
"tool": "add-timeline-entry",
"arguments": {
"incidentId": "{{payload.incidentId}}",
"description": "Automated response workflow triggered",
"source": "workflow-orchestrator"
}
}
]
}
Il WorkflowEngine: Esecuzione e Template Resolution
Il cuore del workflow-orchestrator e il WorkflowEngine, che gestisce la risoluzione dei template, la valutazione delle condizioni di trigger e l'esecuzione sequenziale degli step attraverso il ClientManager:
class WorkflowEngine {
constructor(
private store: WorkflowStore,
private clientManager?: McpClientManager,
private eventBus?: EventBus,
) {}
// Risolve template come {{payload.title}} e {{steps[0].result.id}}
resolveTemplates(
template: Record<string, unknown>,
context: {
payload: Record<string, unknown>;
steps: Array<{ result: Record<string, unknown> | null }>;
}
): Record<string, unknown>;
// Valuta condizioni trigger: ogni chiave deve matchare il payload
evaluateTrigger(
conditions: Record<string, unknown>,
payload: Record<string, unknown>
): boolean;
// Esegue il workflow step per step via ClientManager
async executeWorkflow(
workflow: Workflow,
triggerPayload: Record<string, unknown>
): Promise<WorkflowRunRecord>;
// Gestisce eventi: trova workflow attivi e li esegue
async handleEvent(event: string, payload: unknown): Promise<void>;
}
Pattern EventBus Wildcard
Il collaboration handler del workflow-orchestrator utilizza il pattern * per
sottoscriversi a tutti gli eventi dell'EventBus. Per ogni evento ricevuto,
il motore cerca i workflow attivi il cui triggerEvent corrisponde all'evento
e le cui triggerConditions sono soddisfatte dal payload. Questo permette
di creare automazioni reattive senza dover modificare il codice del server.
Modello Dati: Workflow, Run e Step
interface Workflow {
id: number;
name: string;
description: string | null;
triggerEvent: string;
triggerConditions: Record<string, unknown>;
steps: {
server: string;
tool: string;
arguments: Record<string, unknown>;
}[];
active: boolean;
createdAt: string;
updatedAt: string;
}
interface WorkflowRunRecord {
id: number;
workflowId: number;
status: string; // 'running' | 'completed' | 'failed'
triggerPayload: Record<string, unknown>;
error: string | null;
startedAt: string;
completedAt: string | null;
durationMs: number | null;
}
6. Insight Engine: Analytics Cross-Server
Il server insight-engine rappresenta il livello di intelligence dell'ecosistema. Utilizza il ClientManager per raccogliere dati da tutti i server disponibili, correlare metriche da fonti diverse ed esporre una visione aggregata dello stato del progetto. Il CorrelationEngine interno mappa le metriche ai server sorgente e gestisce la degradazione graceful quando un server non e disponibile.
I 4 Tool dell'Insight Engine
Tool Disponibili
| Tool | Descrizione | Parametri Principali |
|---|---|---|
query-insight |
Query in linguaggio naturale | question, forceRefresh |
correlate-metrics |
Correla metriche da server diversi | metrics[], period |
explain-trend |
Spiega un trend per una metrica | metric, direction |
health-dashboard |
Dashboard salute del progetto | forceRefresh |
Il CorrelationEngine: Mappatura Metriche-Server
Il cuore dell'insight-engine e il CorrelationEngine, che mantiene una mappa
interna delle metriche disponibili e dei server che le forniscono. Quando viene richiesta
una correlazione, il motore chiama i server appropriati tramite il ClientManager
e aggrega i risultati:
class CorrelationEngine {
// Mappatura metrica -> server/tool
private metricMap = {
'velocity': { server: 'agile-metrics', tool: 'calculate-velocity' },
'time-logged': { server: 'time-tracking', tool: 'get-timesheet' },
'budget-spent': { server: 'project-economics', tool: 'get-budget-status' },
};
// Chiamata sicura: ritorna null se il server non e disponibile
async safeCall(server: string, tool: string, args = {})
: Promise<Record<string, unknown> | null>;
// Correlazione di metriche multiple
async correlateMetrics(metrics: string[])
: Promise<{ metrics: Record; dataSources: Record; analyzedAt: string }>;
// Dashboard salute del progetto con health score
async getProjectHealth()
: Promise<{ healthScore: number; velocity; timeTracking; budget; dataSources }>;
// Query basata su keyword matching
async queryInsight(question: string)
: Promise<{ question; relevantMetrics; data; generatedAt }>;
}
Caching Intelligente
L'insight-engine utilizza un sistema di caching interno tramite l'InsightStore.
Le risposte vengono memorizzate con una chiave composta da tipo di analisi e query.
Il parametro forceRefresh permette di bypassare la cache quando necessario.
Il tool health-dashboard usa un TTL di 300 secondi per bilanciare
freschezza dei dati e performance.
7. MCP Registry: Service Discovery e Health Monitoring
Il server mcp-registry funziona come un service registry per l'ecosistema MCP. Ogni server può registrarsi con il proprio URL, protocollo di trasporto e lista di capabilities. Il registry supporta health check periodici e pubblica eventi quando un server diventa non disponibile.
I 4 Tool del MCP Registry
Tool Disponibili
| Tool | Descrizione | Parametri Principali |
|---|---|---|
register-server |
Registra un nuovo server | name, url, transport, capabilities |
discover-servers |
Scopre i server registrati | status, transport |
health-check |
Esegue un health check | serverId |
get-capabilities |
Ottiene le capabilities di un server | serverId |
Registrazione e Discovery dei Server
// Registrazione di un server nell'ecosistema
server.tool('register-server', '...', {
name: z.string().describe('Unique name of the MCP server'),
url: z.string().describe('URL or endpoint'),
transport: z.enum(['stdio', 'http', 'in-memory'])
.optional().default('stdio'),
capabilities: z.array(z.string()).optional()
.describe('List of capabilities'),
}, async ({ name, url, transport, capabilities }) => {
const record = store.registerServer({ name, url, transport, capabilities });
eventBus?.publish('registry:server-registered', {
serverName: record.name,
url: record.url,
capabilities: record.capabilities,
});
return { content: [{ type: 'text', text: JSON.stringify(record, null, 2) }] };
});
// Discovery con filtri per stato e trasporto
server.tool('discover-servers', '...', {
status: z.enum(['healthy', 'unhealthy', 'unknown']).optional(),
transport: z.enum(['stdio', 'http', 'in-memory']).optional(),
}, async ({ status, transport }) => {
const servers = store.listServers({ status, transport });
return { content: [{ type: 'text', text: JSON.stringify(servers, null, 2) }] };
});
Health Check e Monitoraggio
Il tool health-check esegue una verifica dello stato di un server registrato.
Se il server risulta non disponibile, viene pubblicato l'evento registry:server-unhealthy
sull'EventBus:
// Health check con pubblicazione eventi
server.tool('health-check', '...', {
serverId: z.number().int().positive(),
}, async ({ serverId }) => {
const srv = store.getServer(serverId);
const responseTimeMs = /* misurazione tempo risposta */;
const status = responseTimeMs > 450 ? 'unhealthy' : 'healthy';
const healthCheck = store.recordHealthCheck({
serverId, status, responseTimeMs,
error: status === 'unhealthy' ? 'Timeout' : undefined,
});
if (status === 'unhealthy') {
eventBus?.publish('registry:server-unhealthy', {
serverName: srv.name,
lastHealthy: srv.lastHealthCheck ?? srv.createdAt,
error: 'Health check failed',
});
}
return { content: [{ type: 'text', text: JSON.stringify(healthCheck, null, 2) }] };
});
8. Dashboard API: Aggregazione Multi-Server
Il server dashboard-api funge da punto di accesso unificato per ottenere una visione aggregata dell'intero ecosistema. Utilizza il ClientManager per interrogare simultaneamente più server (scrum-board, agile-metrics, time-tracking, project-economics, decision-log, incident-manager, quality-gate, retrospective-manager) e presentare i dati in un formato coerente con caching integrato.
I 4 Tool della Dashboard API
Tool Disponibili
| Tool | Descrizione | Server Interrogati |
|---|---|---|
get-overview |
Overview aggregata del progetto | scrum-board, agile-metrics, time-tracking, project-economics |
get-server-status |
Stato dei server registrati | mcp-registry |
get-recent-activity |
Attivita recente aggregata | decision-log, incident-manager, retrospective-manager |
get-project-summary |
Summary completo del progetto | Tutti i server disponibili (7+) |
Esempio: Overview Aggregata
Il tool get-overview raccoglie dati da quattro server contemporaneamente
usando Promise.all per massimizzare le performance. Per ogni server,
viene indicato se i dati sono disponibili o meno, garantendo
una degradazione graceful:
server.tool('get-overview', '...', {
forceRefresh: z.boolean().optional(),
}, async ({ forceRefresh }) => {
// Check cache (TTL 120 secondi)
if (!forceRefresh) {
const cached = store.getCached('overview', 'main');
if (cached) return { content: [{ type: 'text',
text: JSON.stringify({ ...cached, fromCache: true }, null, 2) }] };
}
// Aggregazione parallela da 4 server
const [scrumBoard, velocity, timesheet, budget] = await Promise.all([
safeCall(clientManager, 'scrum-board', 'list-sprints', {}),
safeCall(clientManager, 'agile-metrics', 'calculate-velocity', {...}),
safeCall(clientManager, 'time-tracking', 'get-timesheet', {}),
safeCall(clientManager, 'project-economics', 'get-budget-status', {...}),
]);
const overview = {
velocity: velocity ?? { status: 'unavailable' },
scrumBoard: scrumBoard ?? { status: 'unavailable' },
timeTracking: timesheet ?? { status: 'unavailable' },
budget: budget ?? { status: 'unavailable' },
dataSources: {
scrumBoard: scrumBoard ? 'available' : 'unavailable',
velocity: velocity ? 'available' : 'unavailable',
timeTracking: timesheet ? 'available' : 'unavailable',
budget: budget ? 'available' : 'unavailable',
},
generatedAt: new Date().toISOString(),
};
store.setCache('overview', 'main', overview, 120);
return { content: [{ type: 'text', text: JSON.stringify(overview, null, 2) }] };
});
Project Summary: Visione Completa
Il tool get-project-summary rappresenta l'aggregazione più completa,
interrogando simultaneamente tutti i server disponibili per generare una visione
a 360 gradi del progetto:
// Aggregazione parallela da 7 server
const [velocity, timesheet, budget, decisions,
incidents, qualityGates, retros] = await Promise.all([
safeCall(clientManager, 'agile-metrics', 'calculate-velocity', {...}),
safeCall(clientManager, 'time-tracking', 'get-timesheet', {}),
safeCall(clientManager, 'project-economics', 'get-budget-status', {...}),
safeCall(clientManager, 'decision-log', 'list-decisions', {}),
safeCall(clientManager, 'incident-manager', 'list-incidents', {}),
safeCall(clientManager, 'quality-gate', 'list-gates', {}),
safeCall(clientManager, 'retrospective-manager', 'list-retros', {}),
]);
Pattern Safe Call e Graceful Degradation
Sia il dashboard-api che l'insight-engine utilizzano il pattern safeCall
per gestire la comunicazione cross-server. Se un server non e disponibile, la chiamata
ritorna null invece di lanciare un'eccezione. Il campo dataSources
nella risposta indica lo stato di disponibilità di ogni server, permettendo al client
di sapere quali dati sono reali e quali sono mancanti.
Mappa degli Eventi dell'Ecosistema
Gli otto server avanzati producono e consumano eventi attraverso l'EventBus. Ecco la mappa completa degli eventi prodotti da questi server:
Eventi Pubblicati dai Server Avanzati
| Evento | Server Sorgente | Payload Principale |
|---|---|---|
incident:opened |
incident-manager | incidentId, title, severity, affectedSystems |
incident:escalated |
incident-manager | incidentId, previousSeverity, newSeverity |
incident:resolved |
incident-manager | incidentId, title, resolution, durationMinutes |
decision:created |
decision-log | decisionId, title, status |
decision:superseded |
decision-log | decisionId, supersededBy, title |
access:policy-updated |
access-policy | policyId, name, effect |
access:denied |
access-policy | userId, server, tool, reason |
quality:gate-passed |
quality-gate | gateName, project, results |
quality:gate-failed |
quality-gate | gateName, project, failures |
workflow:triggered |
workflow-orchestrator | workflowId, name, triggeredBy |
workflow:completed |
workflow-orchestrator | workflowId, runId, name, durationMs |
workflow:failed |
workflow-orchestrator | workflowId, runId, name, error |
registry:server-registered |
mcp-registry | serverName, url, capabilities |
registry:server-unhealthy |
mcp-registry | serverName, lastHealthy, error |
Architettura: Server Autonomi vs Server di Integrazione
Gli otto server avanzati si dividono in due categorie architetturali distinte in base a come interagiscono con il resto dell'ecosistema:
Confronto Architetturale
| Categoria | Server | Caratteristica |
|---|---|---|
| Autonomi | incident-manager, decision-log, access-policy, quality-gate | Gestiscono il proprio dominio, producono eventi, non necessitano di ClientManager |
| Integrazione | workflow-orchestrator, insight-engine, dashboard-api, mcp-registry | Aggregano dati da altri server, richiedono ClientManager per la comunicazione cross-server |
I server autonomi possono funzionare anche in isolamento: gestiscono il proprio database SQLite e pubblicano eventi sull'EventBus senza dipendere da altri server. I server di integrazione sono progettati per operare nel contesto dell'ecosistema completo, utilizzando il ClientManager per interrogare altri server e aggregare dati.
Conclusioni
Gli otto server avanzati completano l'architettura del progetto Tech-MCP, portando l'ecosistema a un livello di sofisticazione enterprise. Dalla gestione degli incidenti alla quality assurance, dal controllo degli accessi all'orchestrazione dei workflow, ogni server contribuisce con un dominio specifico alla piattaforma complessiva.
Il pattern che emerge e quello di un sistema distribuito event-driven: i server autonomi gestiscono i propri domini e pubblicano eventi, il workflow-orchestrator reagisce agli eventi eseguendo automazioni cross-server, e i server di aggregazione (insight-engine, dashboard-api) offrono visioni unificate dell'intero ecosistema.
Nel prossimo articolo approfondiremo la collaborazione inter-server: come i 31 server MCP dell'ecosistema comunicano tra loro attraverso l'EventBus, come il ClientManager abilita le chiamate cross-server e come i pattern di collaborazione permettono di costruire automazioni complesse a partire da componenti semplici.
Il codice completo di tutti i server e disponibile nel repository Tech-MCP su GitHub.







