Introduzione: Come Comunicano i Server MCP
In un ecosistema con 22 server MCP attivi, la collaborazione tra server diventa essenziale. Un singolo server gestisce un dominio specifico (scrum, time-tracking, CI/CD), ma i flussi di lavoro reali attraversano più domini: quando uno sprint si chiude, le metriche devono aggiornarsi, i costi devono calcolarsi, la retrospettiva deve crearsi automaticamente.
Tech-MCP implementa due meccanismi complementari per la comunicazione inter-server: l'EventBus (pub/sub asincrono fire-and-forget) e il ClientManager (chiamate RPC sincrone). Insieme, permettono di costruire flussi end-to-end che attraversano l'intera suite senza accoppiamento diretto tra i server.
Cosa Imparerai in Questo Articolo
- L'architettura dell'EventBus e il pattern Publish/Subscribe in MCP Suite
- I 29 eventi tipizzati organizzati in 11 domini
- La matrice di collaborazione: chi pubblica, chi sottoscrive
- Il ClientManager per chiamate RPC sincrone tra server
- La classificazione dei server: bidirezionali, publisher-only, subscriber-only, pass-through
- Flussi end-to-end: ciclo di vita sprint, tempo e costi, DevOps, quality
- Pattern matching con micromatch per sottoscrizioni wildcard
EventBus: Il Cuore della Collaborazione Asincrona
L'EventBus e il meccanismo che permette ai server MCP Suite di collaborare in modo asincrono e disaccoppiato. Quando un server esegue un'azione significativa (creazione sprint, log tempo, completamento build), pubblica un evento tipizzato che altri server possono sottoscrivere per reagire automaticamente.
Server A EventBus Server B
| | |
|-- publish(evento) ------->| |
| |-- handler(payload) ------>|
| | |
| (fire-and-forget) | (delivery garantita |
| | in-process) |
Questo pattern e noto come Publish/Subscribe (Pub/Sub) e offre vantaggi fondamentali:
- Disaccoppiamento: il publisher non conosce i subscriber
- Estensibilita: aggiungere un nuovo subscriber non richiede modifiche al publisher
- Opzionalita: l'EventBus e sempre opzionale; ogni server funziona perfettamente anche senza
Interfaccia EventBus
L'interfaccia EventBus definisce il contratto per qualsiasi implementazione del bus eventi:
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;
}
Metodi dell'EventBus
| Metodo | Descrizione |
|---|---|
publish |
Pubblica un evento tipizzato con il suo payload |
subscribe |
Sottoscrive a un evento specifico. Ritorna una funzione di unsubscribe |
subscribePattern |
Sottoscrive a eventi che matchano un pattern glob (es. scrum:*) |
clear |
Rimuove tutte le sottoscrizioni |
LocalEventBus: Implementazione In-Process
La classe LocalEventBus e l'implementazione predefinita, basata su Node.js
EventEmitter con supporto pattern via micromatch:
import { EventEmitter } from "node:events";
import micromatch from "micromatch";
interface PatternSubscription {
pattern: string;
handler: PatternHandler;
}
export class LocalEventBus implements EventBus {
private emitter = new EventEmitter();
private patternSubs: PatternSubscription[] = [];
constructor() {
this.emitter.setMaxListeners(100);
}
async publish(event: string, payload: unknown): Promise<void> {
// Notifica subscriber esatti
this.emitter.emit(event, payload);
// Notifica subscriber pattern
for (const sub of this.patternSubs) {
if (micromatch.isMatch(event, sub.pattern)) {
try {
await sub.handler(event, payload);
} catch {
// Errori nei subscriber non bloccano la pubblicazione
}
}
}
}
subscribe(event: string, handler: EventHandler): () => void {
this.emitter.on(event, handler);
return () => this.emitter.off(event, handler);
}
subscribePattern(pattern: string, handler: PatternHandler): () => void {
const sub: PatternSubscription = { 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 = [];
}
}
Caratteristiche chiave: max 100 listener per evento, pattern matching via
micromatch per wildcard (scrum:*, *:completed), errori nei
pattern handler catturati silenziosamente per non interrompere la pubblicazione, e zero dipendenze
esterne oltre micromatch.
Tipizzazione Completa con EventMap
Ogni evento e tipizzato sia nel nome che nel payload grazie alla mappa EventMap.
Il compilatore TypeScript verifica a compile-time che il payload sia corretto:
// Il compilatore TypeScript verifica che il payload sia corretto
eventBus.publish('scrum:sprint-started', {
sprintId: '42',
name: 'Sprint 15',
startDate: '2025-01-01',
endDate: '2025-01-14',
});
// Errore di compilazione: manca il campo 'name'
eventBus.publish('scrum:sprint-started', {
sprintId: '42',
});
I 29 Eventi Tipizzati in 11 Domini
MCP Suite definisce 29 eventi tipizzati organizzati in 11 domini. Ogni
evento segue il formato dominio:azione-in-kebab-case e possiede un payload TypeScript
fortemente tipizzato.
Mappa dei Domini
| Dominio | N. Eventi | Area Funzionale |
|---|---|---|
code:* |
3 | Codice e Git |
cicd:* |
2 | CI/CD |
scrum:* |
4 | Project Management |
time:* |
2 | Time Tracking |
db:* |
2 | Database |
test:* |
2 | Testing |
docs:* |
2 | Documentazione |
perf:* |
2 | Performance |
retro:* |
2 | Retrospettive |
economics:* |
2 | Economia di Progetto |
standup:* |
1 | Standup |
| Totale | 29 |
Riferimento Completo degli Eventi
Tutti i 29 Eventi con Server e Trigger
| Dominio | Evento | Publisher | Trigger (Tool) |
|---|---|---|---|
| code | code:commit-analyzed |
code-review | analyze-diff |
code:review-completed |
code-review | suggest-improvements |
|
code:dependency-alert |
dependency-manager | check-vulnerabilities |
|
| cicd | cicd:pipeline-completed |
cicd-monitor | get-pipeline-status |
cicd:build-failed |
cicd-monitor | get-pipeline-status |
|
| scrum | scrum:sprint-started |
scrum-board | create-sprint |
scrum:sprint-completed |
scrum-board | close-sprint |
|
scrum:task-updated |
scrum-board | update-task-status |
|
scrum:story-completed |
scrum-board | close-sprint |
|
| time | time:entry-logged |
time-tracking | log-time, stop-timer |
time:timesheet-generated |
time-tracking | get-timesheet |
|
| db | db:schema-changed |
db-schema-explorer | explore-schema |
db:index-suggestion |
db-schema-explorer | suggest-indexes |
|
| test | test:generated |
test-generator | generate-unit-tests |
test:coverage-report |
test-generator | analyze-coverage |
|
| docs | docs:api-updated |
api-documentation | extract-endpoints |
docs:stale-detected |
api-documentation | find-undocumented |
|
| perf | perf:bottleneck-found |
performance-profiler | find-bottlenecks |
perf:profile-completed |
performance-profiler | benchmark-compare |
|
| retro | retro:action-item-created |
retrospective-manager | generate-action-items |
retro:completed |
retrospective-manager | (chiusura retrospettiva) | |
| economics | economics:budget-alert |
project-economics | get-budget-status |
economics:cost-updated |
project-economics | log-cost |
|
| standup | standup:report-generated |
standup-notes | log-standup |
Esempio di Payload Tipizzato
Ogni evento possiede un payload TypeScript specifico. Ecco alcuni esempi rappresentativi per ciascun dominio principale:
// scrum:sprint-completed
{
sprintId: string;
name: string;
velocity: number;
completedStories: number;
incompleteStories: number;
}
// time:entry-logged
{
taskId: string;
userId: string;
minutes: number;
date: string; // ISO 8601
}
// cicd:build-failed
{
pipelineId: string;
error: string;
stage: string;
branch: string;
}
// economics:budget-alert
{
project: string;
percentUsed: number;
threshold: number; // es. 80
remaining: number;
}
// code:dependency-alert
{
package: string;
severity: 'critical' | 'high' | 'medium' | 'low';
advisory: string;
}
Pattern di Integrazione nei Server
L'integrazione dell'EventBus nei server segue un pattern standardizzato a 4 step che garantisce separazione delle responsabilità e opzionalita completa.
1. Creazione dell'EventBus (index.ts)
// servers/<nome>/src/index.ts
import { LocalEventBus } from '@mcp-suite/event-bus';
const eventBus = new LocalEventBus();
const suite = createMyServer(eventBus);
2. Iniezione nel Server (server.ts)
export function createMyServer(eventBus?: EventBus): McpSuiteServer {
const suite = createMcpServer({
name: 'my-server',
version: '0.1.0',
eventBus,
});
const store = new MyStore();
// Tool che pubblica eventi
registerCreateItem(suite.server, store, suite.eventBus);
// Tool read-only: non serve l'eventBus
registerListItems(suite.server, store);
// Collaboration handlers
if (suite.eventBus) {
setupCollaborationHandlers(suite.eventBus, store);
}
return suite;
}
3. Pubblicazione negli Handler dei Tool (tools/*.ts)
export function registerCreateItem(
server: McpServer,
store: MyStore,
eventBus?: EventBus,
): void {
server.tool('create-item', 'Create a new item', schema, async (args) => {
const item = store.create(args);
// Fire-and-forget: eventBus può essere undefined
eventBus?.publish('domain:item-created', {
itemId: item.id,
// ...payload tipizzato
});
return { content: [{ type: 'text', text: JSON.stringify(item) }] };
});
}
4. Sottoscrizione (collaboration.ts)
export function setupCollaborationHandlers(
eventBus: EventBus,
store: MyStore,
): void {
eventBus.subscribe('other-domain:event', (payload) => {
// Reagisci all'evento
store.updateSomething(payload);
});
}
Principi di Design dell'EventBus
- Fire-and-Forget:
eventBus?.publish()con optional chaining gestisce il caso undefined; non si usaawait - Solo tool mutanti pubblicano: tool di creazione, aggiornamento e cancellazione pubblicano eventi; tool di lettura no
- Collaborazione isolata: la logica di reazione agli eventi e sempre in
collaboration.ts, mai nei tool handler - Errori contenuti: il fallimento di un subscriber non impatta mai il publisher
Pattern Matching con micromatch
Il metodo subscribePattern permette di sottoscrivere a gruppi di eventi usando
pattern glob, grazie alla libreria micromatch:
// Tutti gli eventi del dominio scrum
eventBus.subscribePattern('scrum:*', (event, payload) => {
console.log(`Scrum event: 






