Entegrasyon
Bir yerde bir daemon çalışıyor. Başka her şeyin ona ulaşmasının üç yolu ve aralarında nasıl seçim yapacağınız.
mcpd'nin tek bir kapısı, o kapıdan geçmenin de üç yolu var. Aşağıdakilerin hepsi aynı HTTP yüzeyiyle konuşur: SDK'lar onun üstüne konmuş bir kolaylıktır, MCP sunucusu ise onun bir istemcisidir.
Tek port, dört yüzey
Tek bir süreç bunların hepsini tek portta sunar, varsayılan olarak 4317. Sidecar yok, yanında çalıştırılacak bir collector yok, arayüz için ayrı bir statik dosya sunucusu yok.
| Yol | Ne olduğu | Anahtar |
|---|---|---|
/v1/traces, /v1/logs, /v1/metrics | OpenTelemetry alımı. Uygulamanızda mcpd'ye özgü tek satır kod olmadan, herhangi bir OTel SDK'sı. | Alım |
/v1/ingest | mcpd'nin kendi alımı. Bir yığın, altı sinyalin herhangi bir karışımını ve veri modelinin tamamını taşır; bir istek de bir yığın akışı taşır. | Alım |
/v1/api/… | Sorgu API'si. Arayüz de SDK'lar da yalnızca buradan okur. | Okuma, yazma, admin, execute veya worker; her endpoint kendisi bildirir |
/ | İkili dosyanın içine derlenmiş operatör arayüzü. | İşaretleme için gerekmez; verisi sorgu API'sinden geçer |
İki yol daha, /healthz ve /readyz, kimlik
doğrulaması istemez; böylece bir yoklama onlara ulaşabilir. Bir endpoint'e
erişilip erişilmediğini araştırırken buradan başlayın:
$ curl -s "$MCPD/healthz"{"status":"ok","version":"0.1.0","spec_version":"1.0.0",…}$ curl -s "$MCPD/readyz"storage is not reachable: …# readyz depoya da dokunur, yani buradaki bir 503 daemon ayakta# ama arkasındaki veritabanı değil demektir. SDK olmadan HTTP API
Dilinizde bir HTTP istemcisi varsa mcpd ile konuşabilir. Kaydolunacak bir şey, önceden açılacak bir proje ve el sıkışma yoktur. Her SDK aşağıdaki isteklerin üstüne konmuş bir kolaylıktır; bu yolu seçtiğinizde gruplama, yeniden deneme ve kimlik üretimi sizde kalır, onları da kendiniz yaparsınız.
Bir çalıştırmayı yazmak
POST /v1/ingest tek bir yığın alır. Bir yığın; span'leri,
log'ları, metrikleri, puanları, geri bildirimleri ve yön notlarını
birlikte taşıyabilir. mcpd'nin kendi kapısının OTLP'ninkinin yanında var
olma sebebi de budur: altı sinyal aynı trace ve span kimliklerini paylaşır,
dolayısıyla hepsini tek istekte göndermek, ikinci bir aramaya gerek kalmadan
ilişkilerini korur.
O endpoint'e üç biçim ulaşır ve hangisi olduğunu içerik türü söyler. Hiçbir şey baytlardan çıkarılmaz, yani bir dizginin içine yeni satır eklemek bir gövdenin anlamını asla değiştiremez.
| İçerik türü | Gövde |
|---|---|
application/json | Tek bir yığın nesnesi, her SDK'nın gönderdiği budur |
application/json | Yığın nesnelerinden bir dizi, elinde birkaç tane olan bir gönderen için |
application/x-ndjson | Satır başına bir yığın, tümü birden tutulmak yerine gövde geldikçe okunur. application/jsonl aynı anlama gelir |
Her öğe kendi başına bir yığındır: bir ingest_key taşıdığında
kendi işleminde saklanır, böylece yeniden gönderilen bir akış tek bir yığın
olarak değil satır satır tanınır; anahtarsız öğeler ise birlikte saklanır.
Bir satır okunamıyorsa ya da istek bir bütün olarak iki sınırdan birini
aşıyorsa isteğin tamamı reddedilir ve hiçbir şey saklanmaz. Yanıt, hepsini
kapsayan tek bir IngestResult'tır.
-
Spans
What ran, nested, with timings
-
Logs
Lines the run emitted
-
Metrics
Numbers, with exemplars back to a run
-
Scores
Machine evaluation
-
Feedback
What a person thought
-
Aspect notes
A sentence a model wrote about the run
tek bir yığın
# One batch carries any mix of the five signals. Send the whole run at once.
curl -s -X POST "$MCPD/v1/ingest" \
-H 'content-type: application/json' \
-H "authorization: Bearer ${MCPD_API_KEY:-}" \
-d @- <<JSON
{
"ingest_key": "run-$TRACE",
"spans": [
{
"trace_id": "$TRACE",
"span_id": "$ROOT",
"name": "support_agent",
"kind": "agent_run",
"status": "ok",
"start_time": "$NOW", "end_time": "$NOW",
"is_root": true, "is_event": false, "received_at": "$NOW",
"input": { "question": "Where is order 4417?" },
"output": { "answer": "It is in the Leeds depot." },
"correlation": { "project": "default", "environment": "production" }
},
{
"trace_id": "$TRACE",
"span_id": "$GEN",
"parent_span_id": "$ROOT",
"name": "chat",
"kind": "model_generation",
"status": "ok",
"start_time": "$NOW", "end_time": "$NOW",
"is_root": false, "is_event": false, "received_at": "$NOW",
"model": "gpt-4o",
"provider": "openai",
"usage": { "input_tokens": 1284, "output_tokens": 210 },
"correlation": { "project": "default" }
}
]
}
JSON
# → {"accepted":2,"rejected":0,"duplicate":0}
#
# mcpd prices the model span from its own price book on the way in, so cost is
# stored with the run rather than recomputed later from rates that may have
# changed since. İsteğin doğru yapması gereken dört şey:
- Kimlikler OpenTelemetry biçimindedir. Bir trace için 32, bir span için 16 küçük harfli onaltılık karakter, hiçbiri tamamen sıfır olamaz. Başka bir şey, çalıştırdığınız diğer bütün araçlarla ilişkilendirmeyi bozar.
-
ingest_keyyeniden denemeyi güvenli kılar. Bir yığın için sabit kalan bir idempotency anahtarıdır; böylece tekrar gönderim tanınır ve iki kez yazılmak yerine yinelenen olarak sayılır. - Anahtar bir başlıkta gider.
Authorization: Bearer …ya daX-Mcpd-Key. Alım bir alım anahtarı ister, o da yalnızca daemon'da tanımlı anahtar varsa. - Yığınların sınırları vardır. Varsayılan olarak 32 MiB
gövde ve 10.000 sinyal; ikisi de
--max-body-bytesve--max-batch-signalsile ayarlanır. Sinyal sınırı isteğin tamamını sayar, yani aynı sinyalleri satırlara bölmek bu sınırı aşmanızı sağlamaz. Büyük bir şey içincontent-encoding: gzipgönderin.
Yanıt her sinyal için ne olduğunu bildirir:
{"accepted":2,"rejected":0,"duplicate":0}, reddedilen her
şeyin sırasını ve sebebini veren bir errors dizisiyle birlikte.
OTLP endpoint'leri bunun yerine OTLP'nin kendi biçiminde, reddedilen sayısını
taşıyan bir partialSuccess nesnesiyle yanıt verir, çünkü bir
OpenTelemetry gönderenin okuduğu budur.
Geri okumak
okuma
# Read it back. Any filter in the API works here.
curl -s "$MCPD/v1/api/traces?project=default&limit=10"
# The whole run, with its logs, scores and feedback in one request.
curl -s "$MCPD/v1/api/traces/$TRACE?include=all"
# Find the run by something the model actually said.
curl -s --get "$MCPD/v1/api/traces" \
--data-urlencode 'project=default' \
--data-urlencode 'q=Leeds depot'
# Find the runs a scorer marked down. This is the query that finds bad answers.
curl -s --get "$MCPD/v1/api/traces" \
--data-urlencode 'project=default' \
--data-urlencode 'has_score=answer_relevance' \
--data-urlencode 'score_below=0.5'
# Where the money went, over the last week, by model.
curl -s --get "$MCPD/v1/api/analytics/cost" \
--data-urlencode 'project=default' \
--data-urlencode 'from=-7d' \
--data-urlencode 'group_by=model'
Filtreler birleşir, sayfalama imleçle yapılır ve project
yazılmadığında daemon'ın varsayılan projesi kullanılır. Saklanan payload'lar
üzerinde tam metin araması q parametresiyle yapılır; "modelin şunu
söylediği çalıştırmayı bul" isteğini bir grep değil de bir sorgu hâline getiren
odur.
mcpd'nin kendi ürettiği bir hata, makinenin okuyabileceği sabit bir
error kodu, sorunu çözecek kişi için yazılmış bir
message ve tek bir parametre hatalıysa bir field
taşıyan JSON olarak döner. Koda göre dallanın, mesajı gösterin.
Bir hata türü bu biçimde değildir ve öyle olduğunu varsayan bir istemci
yanıtı ayrıştıramaz. Endpoint'in beklediği türe çözülemeyen bir istek
gövdesi, mcpd kodu çalışmadan önce web çerçevesi tarafından reddedilir;
yanıt 422, content-type: text/plain ve
missing field `scorer_id` gibi bir mesajdır. Hiç JSON olmayan
gövde sözdizimi ise yine düz metin olarak 400 alır. İki durumda
da dallanılacak bir error kodu yoktur. Bu, türü belirtilmiş bir
gövde alan her endpoint için geçerlidir: POST /v1/api/scores,
/v1/api/feedback, /v1/api/score,
/v1/api/datasets, onun items ve
capture yolları ve /v1/api/experiments. Alım
kapıları gövdeyi kendileri okur, bu yüzden /v1/ingest ve OTLP
yolları JSON biçiminde yanıt verir. Bir 4xx üzerindeki JSON olmayan gövdeyi
kendi kodunuzdan gelen bozuk bir istek sayın ve metin olarak okuyun.
SDK'lar
Altı dil: TypeScript, Python, Rust, Go, Kotlin ve Elixir. Her biri olabildiği yerde kendi diline uygun, önemli olan yerde ise aynıdır. TypeScript hızlı başlangıcını okumuş biri Kotlin'inkini önceden kestirebilir.
Ham HTTP'ye göre kazandıklarınız:
- Gruplama ve arka planda bir worker, yani bir span kaydetmek kuyruğa ekleyip geri döner.
- 429 ve yeniden denemeye değer 5xx kodlarında beklemeli yeniden deneme, bozuk kalacak bir 4xx'te ise hiç denememe.
- Kimlik üretimi, bağlam taşıma ve gelirken ayrıştırılıp giderken yayılan W3C
traceparent, böylece bir trace servis sınırını aşar. - Daemon'ınkiyle aynı belirtimden üretilen türler, yani bir alan SDK'da başka, depoda başka bir anlama gelemez.
Her dilde geçerli üç söz vardır ve bir SDK'yı yoğun bir kod yoluna koymayı güvenli kılan da onlardır. Ölçümleme asla hata fırlatmaz: erişilemeyen bir daemon en fazla aralıkta bir kez loglanır ve yutulur. Asla engellemez: ağ işi worker'da olur, yani ajanınızın gecikmesi mcpd'nin ayakta olmasına bağlı değildir. Kuyruğu da sınırlıdır: dolduğunda en eski span'ler düşürülür ve bir sayaç artar, çünkü sınırsız bir tampon bir ağ kesintisini bellek yetmezliğiyle sonlanan bir sürece çevirir.
bağlanma
import { Mcpd } from '@mcpd/sdk'
// Every field here also reads an environment variable, so the same code runs
// unchanged in development and in production. With no arguments at all this
// talks to a daemon on http://localhost:4317.
const mcpd = new Mcpd({
project: 'support',
environment: 'development',
serviceName: 'support-agent',
serviceVersion: '1.4.0',
}) from mcpd import FeedbackKind, Mcpd, SpanKind
# Everything here has an MCPD_* environment variable behind it, so the same code
# runs in development and in production with no edit. `Mcpd()` with no arguments
# talks to a local daemon.
mcpd = Mcpd(
endpoint=os.environ.get("MCPD_ENDPOINT", "http://localhost:4317"),
project="support",
environment="development",
service_name="support-agent",
service_version="1.4.2",
) // Nothing here is required. `Mcpd::new()` reads MCPD_ENDPOINT, MCPD_API_KEY
// and the rest from the environment and talks to a local mcpd by default.
// Anything set in code wins over the environment.
let mcpd = Mcpd::with_config(
Config::from_env()
.with_project("support")
.with_environment("local")
.with_service("support-agent", Some("1.0.0")),
); m := mcpd.New(
mcpd.WithEndpoint("http://localhost:4317"),
mcpd.WithProject("support"),
mcpd.WithEnvironment("development"),
mcpd.WithService("support-agent", "1.4.2"),
)
// Shutdown flushes what is still queued and stops the background worker.
// A short-lived process that skips it loses whatever had not been sent.
defer m.Shutdown(ctx) // Every field also reads an environment variable (MCPD_ENDPOINT,
// MCPD_PROJECT, MCPD_API_KEY, ...), so `Mcpd()` on its own works too.
val mcpd = Mcpd(
McpdConfig(
endpoint = "http://localhost:4317",
project = "support-desk",
environment = "development",
serviceName = "support-agent",
serviceVersion = "1.4.0",
)
) # Every option also reads an environment variable (MCPD_ENDPOINT, MCPD_PROJECT,
# MCPD_API_KEY, ...), so `Mcpd.start_link()` on its own works too.
{:ok, mcpd} =
Mcpd.start_link(
endpoint: "http://localhost:4317",
project: "support-desk",
environment: "development",
service_name: "support-agent",
service_version: "1.4.0"
) # mcpd needs no registration and no project to be created up front. Check that
# the daemon is up and which spec version it implements.
curl -s "$MCPD/healthz"
# → {"status":"ok","version":"0.1.0","spec_version":"1.0.0", ...}
Argümansız kurulan bir istemci yerel bir daemon'a karşı çalışır. Her şey kod
içinden ya da ortam değişkeniyle ayarlanabilir, kod önceliklidir; değişken adları
her dilde aynıdır, böylece iki servis arasında kopyalanan bir değer, ikisi
farklı dillerde yazılmış olsa da çalışmayı sürdürür:
MCPD_ENDPOINT, MCPD_API_KEY,
MCPD_PROJECT, MCPD_ENVIRONMENT,
MCPD_SERVICE_NAME, MCPD_SERVICE_VERSION,
MCPD_SAMPLE ve MCPD_ENABLED.
MCPD_ENABLED=false her çağrıyı, hâlâ geçerli nesneler döndüren
bir boş işleme çevirir; böylece bir test takımı hiçbir koda dokunmadan
mcpd'yi kapatabilir.
Birbirlerinin yerine geçerler ve bu sınanır
Birim testleri her SDK'nın kendi yazarlarının kastettiğini yaptığını
kanıtlar. Altısının birbiriyle uyuştuğuna dair hiçbir şey kanıtlamaz. Bu
yüzden uygunluk takımı, tek bir canlı daemon'a karşı her dilden bir tane
olmak üzere aynı kanonik çalıştırmayı yazar, buna aynı çalıştırmayı yalnızca
curl ile gönderen yedinci bir kanal ekler ve geri gelenin yapıca
birebir aynı olduğunu doğrular:
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
Yeni bir servis için dil seçiyorsanız, "trace'ler elimizdekilerle aynı görünecek mi" sorusunun yanıtı budur.
Diliniz bu altısından biri değilse
Bir OpenTelemetry SDK'sını /v1/traces adresine yöneltin, yeni
kod yazmanız gerekmez. mcpd bir OTLP alıcısıdır ve hem OpenTelemetry'nin
gen_ai.* uzlaşımını hem de OpenInference'ın llm.*
uzlaşımını eşler, yani halihazırdaki ölçümlemeniz doğru yere düşer.
OpenTelemetry sayfasına
bakın.
Bir yapay zekâ ajanından, MCP üzerinden
Üçüncü yol bir servis için değil bir ajan içindir: trace'leri kodunuzun okuması yerine, bir kodlama ya da destek ajanı onları sizin adınıza okur ve onlara dair bir soruyu yanıtlar. "Son çalıştırma neden başarısız oldu", "bu hafta bir puanlayıcının düşük not verdiği çalıştırmaları göster", "bu oturum ne kadara mal oldu". mcpd bunu Model Context Protocol üzerinden sunar.
Üç araç
Otuz değil, üç. Bunlar tek bir incelemenin üç hamlesidir: hangi çalıştırma, içinde ne oldu ve bu tek bir çalıştırma mı yoksa hepsi mi.
| Araç | Yanıtladığı soru |
|---|---|
find_runs | Hangi çalıştırma. Her biri bir satır: kimlik, yaş, durum, süre, maliyet, adım. Duruma, modele, kullanıcıya, oturuma veya serbest metne göre filtreleyin; en yavaşları almak için süreye göre sıralayın. Varsayılan olarak son 24 saat. |
get_run | Ne olduğu. Şelale görünümü, başarısız olan adım ve sebebi, prompt'lar ve tamamlamalar, log'lar, puanlar. Payload'lar sınırlanır, böylece tek bir çalıştırma bir bağlam penceresini dolduramaz. |
run_stats | Tek bir çalıştırma ya da hepsi. Bir pencere boyunca maliyet, hata oranı, gecikme ve token; modele, ada, duruma, ortama veya kullanıcıya göre gruplanır. Yüzde 40 hata oranı, okuduğunuz çalıştırmanın sorun olmadığı anlamına gelir. |
Sonuçlar JSON yerine metin olarak döner; bu, kimin bağlamının harcandığına dair bilinçli bir seçimdir. Kırk span nesnesinin çoğu noktalama işaretidir; aynı çalıştırma şelale olarak çizildiğinde birkaç yüz token tutar ve ajan biçimini görebilir.
Nasıl kurulduğu
MCP sunucusu ikinci bir servis değil, aynı ikili dosyanın bir alt komutudur.
mcpd mcp olarak çalışır, standart giriş ve çıkış üzerinden
JSON-RPC konuşur ve kendine ait veri tutmaz. Açılacak bir port, ayakta
tutulacak bir süreç ve dağıtılacak bir şey yoktur: ajan host'u, bir konuşma
gerektirdiğinde onu bir alt süreç olarak başlatır, sonra da durdurur.
Tuttuğu tek şey bir daemon bağlantısıdır. Her araç çağrısı, yukarıda anlatılan aynı sorgu API'sine bir istek olur; aynı okuma anahtarı ve aynı proje kapsamıyla, yani bir ajan kendisine verilen anahtarın ulaşamadığı bir projeye ulaşamaz. Bir ajana bu sunucuyu vermenin etki alanı, ona o anahtarı vermenin etki alanıyla tam olarak aynıdır; bir sunucu yapılandırmadan önce denetleyeceğiniz özellik de budur.
Araç kümesi bilerek küçüktür. Geniş bir yüzey modeli kötü seçim yapmaya iter
ve bir ajanın davranışına dair soruların çoğu çalıştırmaları listelemeye,
birini açmaya ve bir pencere üzerinde sayısal bir soru sormaya indirgenir. Bir
istemci güncel kümeyi, burada yazılıp geride kalabilecek bir listeden değil,
protokolün kendi tools/list çağrısından öğrenir.
Bir istemciyi yapılandırmak
Bir MCP host'u sunucuyu bir komut, birkaç argüman ve bir ortamla başlatır. Tam dosya ve tam anahtar mcpd'nin değil host'un işidir, o yüzden onun belgelerine bakın; sizden istediği üç değer her yerde aynıdır.
| Host'un istediği | Değer |
|---|---|
| Komut | Host'un çalıştırabileceği bir yolda duran mcpd ikili dosyası. |
| Argümanlar | mcp alt komutu. Ortam yerine argüman geçmeyi yeğleyen bir
host için --endpoint, yani sorgulanacak daemon, bir de
--api-key, yani onun için bir okuma anahtarı alır.
|
| Ortam |
Aynı iki değer, MCPD_ENDPOINT ve
MCPD_API_KEY olarak. Endpoint varsayılan olarak
http://127.0.0.1:4317'dir ve daemon'da tanımlı anahtar
yoksa anahtar yazılmayabilir.
|
host'un çalıştırdığı
MCPD_ENDPOINT=https://mcpd.internal MCPD_API_KEY=$READ mcpd mcp O anahtarı verirken akılda tutulacak üç şey.
-
Ajana bir
ingestanahtarı değil, birreadanahtarı verin. Kapsamlar, span yazmaya yarayan bir kimlik bilgisinin aynı zamanda saklanmış her prompt'a erişim anlamına gelmemesi için vardır. - Anahtarı bir projeyle sınırlayın. Üst düzeyde yapılandırılmış bir anahtar daemon'daki her projeye ulaşır.
-
Siz bir
adminlistesi tanımlayana kadar bir okuma anahtarının trace silebildiğini bilin; admin kapsamı tanımsızken okuma listesine geri düşer. Kendisine hiç admin listesi verilmemiş bir daemon'da okuma anahtarı tutan bir ajan bir çalıştırmayı silebilir.
Üçü de Güvenlik sayfasında.
Hangisini kullanmalı
| Yol | Ne zaman |
|---|---|
| HTTP | Dilinizin SDK'sı yoksa, bir kabuktan ya da bir işten gönderiyorsanız ya da hiç bağımlılık istemiyorsanız. |
| Bir SDK | Bir uygulamayı ölçümlüyorsanız, yani gruplama, yeniden deneme ve hiç hata fırlatmamak bağımlılıktan daha değerliyse. |
| OpenTelemetry | Zaten OTel yayıyorsanız ve kütüphane eklemektense bir URL değiştirmeyi yeğliyorsanız. |
| MCP | Saklanmış çalıştırmalar hakkında soruları, zamanlı çalışan bir kod değil bir insan bir ajana soruyorsa. |
Bunlar karışır. TypeScript SDK'sıyla yayan bir filo, curl ile
puan gönderen gecelik bir iş ve hepsini MCP üzerinden geri okuyan bir ajan
üç dağıtım değil, tek bir dağıtımdır.
Sonraki
- Neyin saklandığı ve kimin okuyabildiği: Güvenlik.
- Var olan bir OTel kurulumunu mcpd'ye yöneltmek: OpenTelemetry.
- Belirtimden üretilen her endpoint: HTTP API.
- Bilmeye değer sorgular: Çalıştırma bulma.