Salta al contenuto principale

11 - Server Avanzati: Incident, Quality Gate e Workflow

Server MCP avanzati: incident-manager, quality-gate, workflow-orchestrator, decision-log, access-policy, insight-engine, mcp-registry.

Cosa ne pensi di questo articolo?

11 - Server Avanzati: Incident, Quality Gate e Workflow
MCPFoto:Marta BrancoPexels

In Breve

Server MCP avanzati: incident-manager, quality-gate, workflow-orchestrator, decision-log, access-policy, insight-engine, mcp-registry.

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.

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti