Vai al contenuto
mcpd
nessun daemon
Codice

Ogni passo del tuo agente, tenuto fino alla fine e messo agli atti.

mcpd è un daemon, non un framework. Conserva un’esecuzione tra un passo e l’altro: i nuovi tentativi, i timer, l’attesa di una settimana per un’approvazione, mentre il tuo codice fa il lavoro dentro ogni passo, in qualsiasi linguaggio. Ogni passo finisce in una traccia, con quello che è costato.

Latenza del modello
A strip chart recording model latency over the last hour: a baseline near 420 milliseconds with five occasional excursions towards 800 milliseconds.
A strip chart recording model latency over the last hour: a baseline near 420 milliseconds with five occasional excursions towards 800 milliseconds.

Compila ed esegui

git clone https://github.com/mcpd/mcpd && cd mcpd && make build && ./target/release/mcpd demo

Poi apri localhost:4317. Nessun account, nessun database da mettere in piedi, niente da firmare. Non c’è ancora una release: l’immagine e i pacchetti arrivano con la prima. Leggi l’avvio rapido di cinque minuti.

Un’esecuzione che si legge.

Un agente che fallisce non fallisce nella riga di log che nomina l’errore. Fallisce tre passi prima, quando il modello ha scelto lo strumento sbagliato e poi lo ha scelto di nuovo.

mcpd ricostruisce l’intera esecuzione come un albero: ogni chiamata al modello, ogni chiamata a uno strumento, ogni recupero, con il prompt e la completion esatti allegati a ciascuna, la latenza di ciascuna e quanto è costata ciascuna.

Apri l’interfaccia

Trace b3d9e71a Fault
0ms 2.1s 4.2s 6.3s 8.4s
  1. agent support_agent fail
  2. model chat
  3. tool returns_policy
  4. model chat
  5. tool returns_policy
  6. model chat
  7. tool returns_policy

The model called one tool three times and never answered. That is visible in four seconds here, and invisible in a log file.

Tamburo dell’esecuzione

La stessa esecuzione, avvolta su un tamburo registratore. Il tempo gira intorno, una traccia per span. Giralo, e il pennino legge lo span sotto di sé.

Trascina per girare il tamburo, o usa le frecce 2.64 s · tool returns_policy · 90 ms

Un’esecuzione che sopravvive al processo che l’ha avviata.

Il daemon tiene l’esecuzione tra un passo e l’altro. Il tuo processo tiene tutto quello che accade dentro un passo, e non cede niente: né il modello, né il prompt, né una credenziale.

Quadro dei lease

Il daemon tiene sul quadro un’etichetta per passo. Un worker tiene un’etichetta solo finché arrivano i suoi heartbeat. Ferma il worker e, dopo il lease e una finestra di recupero, l’etichetta torna sul quadro per l’altro worker.

Fai clic, o premi Spazio, per fermare il worker draft · worker-a · started · lease 25 s

Il grafo è un dato, i corpi sono tuoi

Un workflow si scrive una volta sola, in codice. Il grafo arriva al daemon come una definizione: la convalida e la versiona. I corpi dei passi restano nel tuo processo, in uno dei sei linguaggi, e un worker li serve.

Nuovi tentativi, timer e attese sono righe

Un errore transitorio viene ritentato sul budget del passo stesso. Una pausa o un’attesa di approvazione trattiene l’esecuzione senza nessun processo vivo, attraverso un deploy, un crash o una settimana. Un evento risponde all’attesa per nome e per chiave, e un evento ripetuto è un duplicato, non una seconda esecuzione.

Ogni esecuzione è una traccia

Ogni passo è uno span con il suo input, il suo output e il consumo che ha dichiarato, valorizzato con lo stesso listino che valorizza tutti gli altri span. La chiamata al modello fatta dentro un passo si colloca sotto di esso, così la ricerca, il costo e gli scorer lavorano su un’esecuzione senza cambiarla.

Leggi la pagina sui workflow

Strumenta nel linguaggio che scrivi già.

Sei SDK, un solo comportamento. Registrare uno span non blocca mai, non solleva mai un’eccezione e non cresce mai senza limite: se mcpd è irraggiungibile il tuo agente non se ne accorge, che è l’unico modo accettabile in cui l’osservabilità può guastarsi. Ognuno porta con sé anche un worker, così i passi di un’esecuzione durevole sono serviti dallo stesso processo che li registra.

Nessuno di essi è obbligatorio. Punta OTEL_EXPORTER_OTLP_ENDPOINT su mcpd e un SDK OpenTelemetry che già esegui lo raggiunge, senza una riga del nostro codice dentro la tua applicazione. I sei sono una comodità posata su una superficie HTTP, non una condizione per essere registrati.

L’osservabilità che arriva attaccata a un framework sa registrare solo gli agenti costruiti con quel framework. mcpd prende tutto quello che raggiunge il suo endpoint OTLP: un framework per agenti, un ciclo che hai scritto tu, o i servizi che l’uno o l’altro chiama.

strumentare un’esecuzione

/**
 * One ticket, one trace.
 *
 * `withSpan` opens the span, keeps it active for everything the callback
 * starts, and ends it when the callback settles. Nothing inside has to be
 * passed a handle to be nested under it.
 */
async function handleTicket(ticket: Ticket): Promise<{ answer: string; traceId: string }> {
  let traceId = ''
  const answer = await mcpd.withSpan('support_agent', async (run) => {
    // Keep the id: a score or a piece of feedback that arrives later attaches
    // to this run by it.
    traceId = run.traceId
    // Tags label the whole run, so the trace list can filter on them.
    run.setTags(['support', 'tier-1'])
    run.setCorrelation({ sessionId: ticket.id, userId: ticket.customerId })
    run.setInput({ question: ticket.question })

    const order = await lookUpOrder(ticket)
    const answer = await draftAnswer(ticket, order)

    run.setOutput({ answer })
    return answer
  })
  return { answer, traceId }
}

Sei segnali, correlati dall’esecuzione che li ha prodotti.

Quasi tutti i prodotti ti danno le tracce qui e i log da un’altra parte, e ti lasciano far combaciare i timestamp a occhio. mcpd conserva tutti e sei i segnali sotto gli stessi id di traccia e di span, così un picco su un grafico apre l’esecuzione che lo ha causato e un’esecuzione mostra le righe di log che ha emesso.

Banco di smistamento

Sei tipi di segnale arrivano mescolati su un solo nastro. Il lettore legge il trace id di ciascuno, e ogni pezzo di un’esecuzione finisce nel cassetto di quell’esecuzione.

Indica un cassetto, o usa le frecce trace 4bf9 · span 3 · log 1 · metric 1 · score 1 · feedback 1

13 linguaggi che vanno d’accordo.

Cross-language conformance

tests/e2e/run.mjs

Channel root tree status error model tokens score feedback ids
TypeScript
Python
Go
Elixir
Kotlin
Rust
cURL
TypeScript
Python
Go
Elixir
Kotlin
Rust

13 languages, one canonical run, one daemon. Every trace comes back the same shape.

481 checks

A conformance panel with one row per language and one column per family of check. Every cell passes. The families are: the root span is the agent run; every child hangs off the root; a failed step makes the whole run an error, and only that step; the error carries a message, on the step that failed; the model call records its model; token usage and cost survive the wire; the scores land, with their scale; the feedback lands; ids are OpenTelemetry-shaped. The suite asserts 37 checks in each of the 13 languages.

Ogni SDK è testato per conto suo, il che dimostra che ognuno fa quello che i suoi autori volevano e non dice niente sul fatto che coincidano tra loro. Perciò ognuno scrive la stessa esecuzione su un solo daemon, e le tracce vengono rilette e confrontate una con l’altra.

Un comportamento che vale in un linguaggio e non in un altro si vede lì e in nessun altro posto. Gira in CI a ogni modifica. Come regge il confronto.

Chi ha letto che cosa, compreso chi è stato respinto.

La domanda che decide una revisione di sicurezza non è se i dati sono al sicuro. È se puoi mostrare, dopo, chi li ha raggiunti.

Audit log

GET /v1/api/audit

Time Principal Action Project Result
14:02:11 a3f9c2e1b7d4 get_trace checkout ok
14:02:11 a3f9c2e1b7d4 list_spans checkout ok
14:04:38 7c1e08b53a9f list_dataset_items billing refused
14:07:02 d52a4f8c0e63 capture_into_dataset checkout ok
14:11:55 d52a4f8c0e63 delete_trace checkout ok
14:19:20 7c1e08b53a9f list_feedback billing ok

A refusal is recorded like a read. The principal is a digest of the key, never the key. Retention never trims this table.

18 actions

An audit log with six entries, each showing the time, a truncated digest of the key that acted, the action, the project and the result. One entry, a read of another project's dataset items, is recorded as refused.

Sei ambiti di chiave

Emettere telemetria, leggere prompt, modificare dati archiviati, distruggerli, avviare esecuzioni e prendere in carico passi sono privilegi diversi. Un elenco write non impostato accetta le chiavi di ingest, e un elenco admin non impostato accetta le chiavi di lettura. Gli elenchi execute e worker non impostati accettano le chiavi di scrittura. Aggiornare non ti chiude fuori.

Un progetto è un confine

Le chiavi, la conservazione e il campionamento si impostano tutti per progetto. Una chiave elencata sotto un progetto raggiunge quel progetto, e il daemon stabilisce di chi è un dataset prima di restituirne anche un solo caso.

Niente chiama casa

In tutto il daemon ci sono quattro punti da cui parte una richiesta verso l’esterno, e li configuri tutti e quattro: l’inoltro, due webhook e il giudice. Non c’è controllo degli aggiornamenti, non c’è telemetria e non c’è un server di licenze.

Quale chiave, e quale persona

Il registro nomina la chiave. Dai a mcpd l’header che il tuo ingress imposta già, e gli indirizzi autorizzati a impostarlo, e nomina anche la persona. Inviato da qualunque altro posto, un header è un’affermazione che chiunque potrebbe fare, quindi la riga resta vuota invece di portare una supposizione.

Una richiesta di cancellazione è una sola chiamata

Un solo DELETE toglie tutte le esecuzioni registrate a nome di una persona, con gli span, i log, le metriche, i punteggi e i feedback che ne dipendono, e risponde con un conteggio per segnale. Le righe di un progetto che quella chiave non può aprire non vengono né toccate né contate.

Controlla prima di fidarti

mcpd doctor dà un voto a un deploy: se lo storage accetta una scrittura e la restituisce, se un ambito è rimasto aperto, se un progetto configurato ha mai ricevuto uno span. Tutto ciò su cui ti chiede di intervenire nomina l’opzione che lo corregge.

Quello che mcpd non fa.

Ogni prodotto ha una lista come questa. Quasi tutti la tengono interna, e tu la scopri la seconda settimana. Eccola, con il motivo invece che con una data.

Niente OTLP su gRPC.
HTTP porta le tracce in entrambe le codifiche, e i log e le metriche in JSON. gRPC non è supportato per niente.
Nessun exporter per singolo fornitore.
mcpd inoltra OTLP, che raggiunge quasi tutte le destinazioni con una sola implementazione e fa di mcpd un salto in più invece che il posto dove i tuoi dati devono restare. Una destinazione che parla solo il proprio protocollo resta fuori portata.
Niente SSO né SCIM.
Le chiavi sono limitate ai progetti, che è un confine fra tenant, non un identity provider. L’identità di una persona è quella che il tuo ingress stabilisce davanti a mcpd.
Niente ingest multiregione.
Su Postgres o ClickHouse gira come più repliche dietro un solo servizio. Non è un failover tra regioni.

Il resoconto completo, misurato rispetto a un audit diretto del codice sorgente dell’alternativa affermata, è in PARITY.md. Viene aggiornato quando qualcosa arriva, non quando qualcosa è previsto.

Cinque minuti alla tua prima traccia.

Esegui il daemon, puntaci il tuo agente, apri l’interfaccia. Se ci vuole più tempo, è un bug e ci interessa la segnalazione.

Avvia

docker run -p 4317:4317 ghcr.io/mcpd/mcpd