Aller au contenu
mcpd
aucun daemon
Source

Chaque étape de votre agent, tenue jusqu’au bout et consignée.

mcpd est un daemon, pas un framework. Il conserve une exécution entre les étapes : les nouvelles tentatives, les timers, l’attente d’une approbation qui dure une semaine, pendant que votre propre code fait le travail à l’intérieur de chaque étape, dans n’importe quel langage. Chaque étape arrive dans une trace, avec ce qu’elle a coûté.

Latence du modèle
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.

Compiler et lancer

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

Ouvrez ensuite localhost:4317. Pas de compte, pas de base de données à monter, rien à signer. Aucune version publiée pour l’instant : l’image et les paquets arriveront avec la première. Lisez la prise en main en cinq minutes.

Une exécution qui se lit.

Un agent qui échoue n’échoue pas dans la ligne de log qui mentionne l’erreur. Il échoue trois étapes plus tôt, quand le modèle a choisi le mauvais outil, puis l’a choisi de nouveau.

mcpd reconstitue l’exécution entière sous forme d’arbre : chaque appel de modèle, chaque appel d’outil, chaque récupération, avec le prompt et la complétion exacts attachés à chacun, la latence de chacun, et ce que chacun a coûté.

Ouvrir l’interface

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.

Tambour d’exécution

La même exécution, enroulée sur un tambour enregistreur. Le temps en fait le tour, une piste par span. Tournez-le, et la plume lit le span sous elle.

Faites glisser pour tourner le tambour, ou utilisez les flèches 2.64 s · tool returns_policy · 90 ms

Une exécution qui survit au processus qui l’a lancée.

Le daemon détient l’exécution entre les étapes. Votre processus détient tout ce qui se passe à l’intérieur d’une étape, et ne cède rien : ni le modèle, ni le prompt, ni un secret.

Tableau des baux

Le daemon garde une étiquette par étape sur son tableau. Un worker ne garde une étiquette que tant que ses battements arrivent. Arrêtez le worker : après le bail et une fenêtre de reprise, l’étiquette retourne au tableau pour l’autre worker.

Cliquez, ou appuyez sur Espace, pour arrêter le worker draft · worker-a · started · lease 25 s

Le graphe est une donnée, les corps sont à vous

Un workflow s’écrit une fois, en code. Le graphe part vers le daemon comme une définition qu’il valide et versionne. Les corps des étapes restent dans votre processus, dans l’un des six langages, servis par un worker.

Les nouvelles tentatives, les timers et les attentes sont des lignes

Une défaillance passagère donne lieu à une nouvelle tentative, sur le budget propre à l’étape. Une mise en veille ou une attente d’approbation retient l’exécution sans qu’aucun processus ne tourne : elle survit à un déploiement, à un plantage ou à une semaine entière. Un événement répond à l’attente par nom et par clé, et un événement répété est un doublon, pas une seconde exécution.

Chaque exécution est une trace

Chaque étape est un span avec son entrée, sa sortie et la consommation qu’elle a déclarée, tarifée avec la grille qui tarife tous les autres spans. L’appel de modèle effectué à l’intérieur d’une étape se place sous elle, si bien que la recherche, le coût et les scorers travaillent sur une exécution sans rien y changer.

Lire la page sur les workflows

Instrumentez dans le langage que vous écrivez déjà.

Six SDK, un seul comportement. Enregistrer un span ne bloque jamais, ne lève jamais d’exception et ne grossit jamais sans limite : si mcpd est injoignable, votre agent ne s’en aperçoit pas, c’est la seule défaillance acceptable pour de l’observabilité. Chacun porte aussi un worker, si bien que les étapes d’une exécution durable sont servies par le processus même qui les enregistre.

Aucun n’est obligatoire. Pointez OTEL_EXPORTER_OTLP_ENDPOINT vers mcpd et un SDK OpenTelemetry que vous exécutez déjà l’atteint, sans une ligne de notre code dans votre application. Les six sont une facilité posée sur une surface HTTP, pas une condition pour être enregistré.

Une observabilité livrée avec un framework ne sait enregistrer que les agents bâtis avec ce framework. mcpd prend tout ce qui atteint son endpoint OTLP : un framework d’agents, une boucle que vous avez écrite vous-même, ou les services que l’un ou l’autre appelle.

instrumenter une exécution

/**
 * 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 }
}

Six signaux, corrélés par l’exécution qui les a produits.

La plupart des outils vous donnent les traces ici et les logs ailleurs, puis vous laissent apparier les horodatages à l’œil. mcpd range les six signaux sous les mêmes identifiants de trace et de span : un pic sur un graphique ouvre l’exécution qui l’a causé, et une exécution montre les lignes de log qu’elle a émises.

Banc de tri

Six types de signaux arrivent mêlés sur un seul tapis. Le lecteur lit l’identifiant de trace de chacun, et chaque pièce d’une exécution tombe dans le tiroir de cette exécution.

Pointez un tiroir, ou utilisez les flèches trace 4bf9 · span 3 · log 1 · metric 1 · score 1 · feedback 1

Les 13 langages s’accordent.

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.

Chaque SDK est testé isolément, ce qui prouve que chacun fait ce que ses auteurs voulaient et ne dit rien de leur accord entre eux. Chacun écrit donc la même exécution vers un seul daemon, et les traces sont relues puis comparées entre elles.

Un comportement qui tient dans un langage et pas dans un autre se voit là et nulle part ailleurs. Cela tourne en CI à chaque changement. Comment mcpd se compare.

Qui a lu quoi, y compris qui s’est vu refuser l’accès.

La question qui décide d’une revue de sécurité n’est pas de savoir si les données sont à l’abri. C’est de savoir si vous pouvez montrer, après coup, qui les a atteintes.

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.

Six portées de clé

Émettre de la télémétrie, lire des prompts, modifier des données stockées, les détruire, démarrer des exécutions et réclamer des étapes sont des privilèges distincts. Une liste write non définie accepte les clés d’ingestion, et une liste admin non définie accepte les clés de lecture. Des listes execute et worker non définies acceptent les clés d’écriture. Une mise à jour ne vous ferme pas l’accès.

Un projet est une frontière

Les clés, la conservation et l’échantillonnage se règlent par projet. Une clé inscrite sous un projet atteint ce projet, et le daemon établit à qui appartient un dataset avant d’en renvoyer le moindre cas.

Rien n’appelle l’extérieur

Le daemon entier compte quatre endroits d’où part une requête sortante, et vous les configurez tous les quatre : le transfert, deux webhooks et le juge. Il n’y a pas de vérification de mise à jour, pas de télémétrie et pas de serveur de licences.

Quelle clé, et quelle personne

Le journal nomme la clé. Donnez à mcpd l’en-tête que votre ingress définit déjà, et les adresses autorisées à le définir, et il nomme aussi la personne. Envoyé depuis ailleurs, un en-tête est une affirmation que n’importe qui pourrait faire : la ligne reste vide plutôt que de porter une supposition.

Une demande de suppression tient en un appel

Un seul DELETE retire toutes les exécutions enregistrées pour une personne, avec les spans, logs, métriques, scores et feedback qui en dépendent, et répond par un décompte signal par signal. Les lignes d’un projet que cette clé ne peut pas ouvrir ne sont ni touchées ni comptées.

Vérifiez avant de faire confiance

mcpd doctor note un déploiement : si le stockage accepte une écriture et la restitue, si une portée est restée ouverte, si un projet configuré a déjà reçu un span. Tout ce sur quoi il vous demande d’agir nomme l’indicateur qui le corrige.

Ce que mcpd ne fait pas.

Tout outil a une liste comme celle-ci. La plupart la gardent en interne, et vous la découvrez la deuxième semaine. La voici, avec la raison plutôt qu’une date.

Pas d’OTLP sur gRPC.
HTTP porte les traces dans les deux encodages, et les logs et les métriques en JSON. gRPC n’est pas pris en charge du tout.
Pas d’exporteur nommé par fournisseur.
mcpd transfère de l’OTLP, ce qui atteint la plupart des destinations avec une seule implémentation et fait de mcpd un saut de plus plutôt que l’endroit où vos données doivent rester. Une destination qui ne parle que son propre protocole reste hors de portée.
Pas de SSO ni de SCIM.
Les clés sont limitées à des projets, ce qui est une frontière de cloisonnement, pas un fournisseur d’identité. L’identité d’une personne est celle que votre ingress établit devant mcpd.
Pas d’ingestion multirégion.
Sur Postgres ou ClickHouse, il tourne en plusieurs répliques derrière un seul service. Ce n’est pas un basculement entre régions.

Le relevé complet, mesuré par rapport à un audit direct du code de l’outil en place, est dans PARITY.md. Il est mis à jour quand quelque chose est livré, pas quand quelque chose est prévu.

Cinq minutes jusqu’à votre première trace.

Lancez le daemon, pointez votre agent vers lui, ouvrez l’interface. Si cela prend plus longtemps, c’est un bug et le rapport nous intéresse.

Démarrer

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