Documentation
¶
Overview ¶
Package mcp implements the Model Context Protocol server side for Coremetry. MCP is JSON-RPC 2.0 over an HTTP+SSE transport, designed so external LLM clients (Claude Desktop, Anthropic API tool-calling integrations, internal copilots) can discover and invoke a server's tools, list its resources, and read its prompt templates.
What Coremetry exposes (built up across v0.6.4–v0.6.7):
v0.6.4 — core protocol scaffolding: JSON-RPC framing,
HTTP+SSE transport, session lifecycle, initialize +
ping methods. tools/resources/prompts capabilities
advertised but their registries are empty.
v0.6.5 — tools/list + tools/call wired to a Coremetry tools
registry (list_services, search_logs, get_trace,
query_metric, list_problems, …). Salt-okunur —
mutation tool'u bugüne dek hiç eklenmedi (v0.9.14
audit'i: yazma gelirse audit_log source alanı şart).
v0.6.6 — resources/list + resources/read exposing
traces/logs/metrics as MCP resources.
v0.6.7 — prompts/list + prompts/get exposing Coremetry's
curated system prompts (Explain trace, Suggest
runbook, Compare deploys, …) as MCP prompts so an
external LLM can take the same systematic approach
an operator sees in /problems.
Auth: the HTTP handlers in this package are wrapped by the same auth middleware as /api/*. Browser sessions work via the JWT cookie; programmatic clients (Claude Desktop, internal LLMs) carry either a JWT (POST /api/auth/login) or a cmk_ service token in the Authorization: Bearer header. So every MCP call arrives authenticated with the SAME Claims a REST call would carry.
Roles, precisely (v0.9.1136 — AI Faz 3.1): the middleware proves WHO, but this package has no route table to hang auth.RequireRole off — one HTTP endpoint fronts every method. So authorization is per-REGISTRY-ENTRY metadata plus one gate:
Tool/Resource/ResourceTemplate/Prompt.MinRole — "" means "any authenticated identity" (the viewer floor); "editor" / "admin" raise it. CallGate (SetCallGate, implemented in api/mcp_gate.go) is consulted before EVERY tools/call, resources/read and prompts/get, and receives the resolved MinRole. It answers the role question first, then the rate question.
Before v0.9.1136 this comment claimed "roles apply to MCP exactly as they do to REST". That was FALSE: the gate read only the identity (for rate limiting) and never the role, and resources/read + prompts/get bypassed the gate entirely — so a viewer token reached data a REST viewer was refused (GET /api/anomalies/active). The mechanism above is what makes the sentence true; keep them in sync.
Transport choice: this implementation uses the original HTTP+SSE transport (MCP spec 2024-11-05). Newer Streamable-HTTP (2025-03-26) is similar but uses a single endpoint + session header; can layer it on later without breaking existing clients.
References:
Index ¶
- Constants
- func Denied(format string, a ...any) error
- func ExtractURITemplateValue(template, concrete string) string
- func ToolErrorJSON(err error) string
- type CallGate
- type CapPromptsBag
- type CapResourcesBag
- type CapToolsBag
- type ClientInfo
- type Error
- type GateCall
- type Prompt
- type PromptArgument
- type PromptContent
- type PromptMessage
- type PromptRenderer
- type Request
- type Resource
- type ResourceReader
- type ResourceTemplate
- type Response
- type Server
- func (s *Server) HandleMessage(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleSSE(w http.ResponseWriter, r *http.Request)
- func (s *Server) HandleStreamable(w http.ResponseWriter, r *http.Request)
- func (s *Server) PromptCount() int
- func (s *Server) RegisterPrompt(p Prompt)
- func (s *Server) RegisterResource(r Resource)
- func (s *Server) RegisterResourceTemplate(rt ResourceTemplate)
- func (s *Server) RegisterTool(t Tool)
- func (s *Server) ResourceCount() (int, int)
- func (s *Server) SetCallGate(g CallGate)
- func (s *Server) ToolCount() int
- type ServerCapabilities
- type ServerInfo
- type Tool
- type ToolError
- type ToolHandler
Constants ¶
const ( ErrParse = -32700 ErrInvalidRequest = -32600 ErrMethodNotFound = -32601 ErrInvalidParams = -32602 ErrInternal = -32603 // ErrRateLimited (v0.9.14) — tools/call kapısının reddi; server- // error bandının tepesi. HTTP 429 yerine JSON-RPC hatası: LLM // tool-use döngüleri bunu sonuç olarak görür, bekleyip yeniden // dener. ErrRateLimited = -32000 // ErrForbidden (v0.9.1136) — kapının ROL reddi. Rate reddinden // AYRI kod: rate "bekle, sonra tekrar dene", rol "bu kimlikle // asla" demek. İkisini tek kodda birleştirmek modeli sonsuz // yeniden-denemeye iter (ve istemci UI'ı 429 gibi gösterir). ErrForbidden = -32001 )
Error codes — covers JSON-RPC base + MCP-specific.
const ( // ToolErrTimeout — okuma bütçeyi aştı (ctx deadline, CH 159, // max_execution_time). Eylem: pencereyi daralt, tekrar dene. ToolErrTimeout = "timeout" // reddi, bellek sınırı, yapılandırılmamış log backend'i. Eylem: // kısa bekle + bir kez daha, yoksa başka kanıt yolu. ToolErrBackendUnavailable = "backend_unavailable" // ToolErrBadArgs — çağıran hatası: eksik zorunlu alan, bozulmuş // JSON, yanlış biçim. Eylem: argümanı düzelt; AYNI argümanla // tekrar denemenin faydası yok. ToolErrBadArgs = "bad_args" // ToolErrNotFound — ad/kimlik bu pencerede yok. Eylem: keşif // tool'u ile doğrula ya da pencereyi büyüt. ToolErrNotFound = "not_found" // ToolErrInternal — sınıflandırılamayan. Eylem: tekrarlama, // eksikliği cevapta söyle. ToolErrInternal = "internal" )
Tool hata sınıfları. BEŞ tane, bilerek: her biri modelin yapabileceği FARKLI bir eyleme karşılık gelir. Altıncı bir sınıf ancak altıncı bir eylem varsa eklenmeli.
const ProtocolVersion = "2024-11-05"
ProtocolVersion is what we advertise during initialize. Clients echoing a different version are negotiated against this — if the client's version isn't supported we still reply with ours per the MCP spec: "the server SHOULD respond with its own version" and let the client decide whether to proceed.
const ProtocolVersionStreamable = "2025-03-26"
ProtocolVersionStreamable — Streamable-HTTP transport'unun (v0.9.14) initialize yanıtında bildirdiği sürüm. SSE yolu 2024-11-05'te kalır; iki transport aynı dispatch gövdesini paylaşır, yalnız el-sıkışma sürümü ayrışır.
Variables ¶
This section is empty.
Functions ¶
func Denied ¶ added in v0.9.1136
Denied — kapı implementasyonunun "rol yetersiz" reddi. Metin LLM'e gider, o yüzden gereken rolü ADIYLA söylemesi beklenir.
func ExtractURITemplateValue ¶ added in v0.6.6
ExtractURITemplateValue pulls the placeholder value out of a concrete URI matched against its template. Exported so concrete Reader implementations don't have to re-implement the slicing. Returns "" if the template has no placeholder or the URI doesn't match.
func ToolErrorJSON ¶ added in v0.9.1234
ToolErrorJSON — sözleşmenin tel/model üzerindeki hâli: kompakt JSON.
Neden JSON: model BAŞARILI tool sonuçlarını zaten JSON okuyor (handleToolsCall out'u json.Marshal'lar, sohbet döngüsü aynısını besler). Hata yolunun düz metin olması, modelin iki ayrı biçim öğrenmesini gerektiriyordu — küçük modelde bu bedava değil.
Types ¶
type CallGate ¶ added in v0.9.1136
CallGate — tools/call, resources/read ve prompts/get öncesi çağrılır; hata dönerse çağrı JSON-RPC hatasıyla reddedilir (HTTP 429/403 DEĞİL: istemci kütüphaneleri JSON-RPC hatasını LLM'e sonuç olarak gösterir, model okuyup davranışını değiştirebilir). Rol reddi Denied ile sarılırsa -32001, sarılmazsa -32000 döner.
initialize/ping/*-list kapı dışıdır (ucuz keşif, veri okumaz). MinRole ÇÖZÜMÜ bu pakette yapılır — kayıt defterinin sahibi burasıdır; api katmanı registry'ye uzanmak zorunda kalmaz.
type CapPromptsBag ¶
type CapPromptsBag struct {
ListChanged bool `json:"listChanged,omitempty"`
}
type CapResourcesBag ¶
type CapToolsBag ¶
type CapToolsBag struct {
// ListChanged: server emits notifications/tools/list_changed
// when its tool catalogue mutates. v0.6.4 returns false
// (tools registry is empty + immutable); v0.6.5 flips this
// if we ever support hot-loading tools.
ListChanged bool `json:"listChanged,omitempty"`
}
type ClientInfo ¶
type Error ¶
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data,omitempty"`
}
Error follows JSON-RPC 2.0 — standard codes are -32700..-32603, implementation-defined server errors are -32099..-32000. MCP defines its own codes in the same -32xxx space which we hand out below.
type GateCall ¶ added in v0.9.1136
GateCall — kapıya inen çağrının tanımı. Kind kapının hata metnini doğru isimlendirmesi için ("tool" | "resource" | "prompt"), Name tool adı / resource URI / prompt adı, MinRole ise kayıt defterinden ÇÖZÜLMÜŞ eşiktir ("" = viewer tabanı).
Neden struct: v0.9.14'te imza (ctx, tool string) idi ve rol bilgisini taşımıyordu. Üç alanı struct'a almak, bir sonraki genişlemenin (ör. arg boyutu) yine imza kırmasını engeller.
type Prompt ¶ added in v0.6.7
type Prompt struct {
Name string
Description string
Arguments []PromptArgument
Renderer PromptRenderer
// MinRole — Tool.MinRole ile aynı sözleşme. Prompt renderer'ları
// chstore'dan VERİ çeker (in-app ✨ Explain ile aynı gövde), yani
// prompts/get bir okuma yoludur, salt şablon dağıtımı değil —
// v0.9.1136'ya dek kapısızdı.
MinRole string
}
Prompt is a server-curated LLM prompt template. prompts/list surfaces them so the client can show "/coremetry-explain- trace [trace_id]" in its slash-command menu; prompts/get with args runs the Renderer to produce the final messages the client feeds to its model.
Coremetry's prompts close over chstore/logstore so calling "explain_trace" actually fetches the trace data and embeds it into the user message — the LLM doesn't have to make a follow-up tool call before reasoning. This is the same pattern as the in-app "✨ Explain" button, just exposed via MCP for external clients.
type PromptArgument ¶ added in v0.6.7
PromptArgument describes one input slot for prompts/get. Type is always implicitly string in the MCP spec; the description is what gets shown to the user when their client renders the argument form.
type PromptContent ¶ added in v0.6.7
PromptContent is the body of a message. Type is currently always "text" for Coremetry's prompts — image / resource content can layer on later.
type PromptMessage ¶ added in v0.6.7
type PromptMessage struct {
Role string `json:"role"`
Content PromptContent `json:"content"`
}
PromptMessage is one role+content pair the Renderer emits. MCP prompts can return system + user + assistant messages; the client typically replays them as the conversation seed.
type PromptRenderer ¶ added in v0.6.7
PromptRenderer takes the prompts/get arguments and returns the resolved message set. Errors bubble up as JSON-RPC errors. Receives the raw arg map so the renderer picks its own decode.
type Request ¶
type Request struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
Request is the incoming JSON-RPC message. ID is left as RawMessage so we can echo it back verbatim — clients send numbers, strings, or null per spec and we shouldn't coerce.
func (*Request) IsNotification ¶
IsNotification — JSON-RPC spec: a request without an id is a notification, no response expected. notifications/initialized is the canonical example.
type Resource ¶ added in v0.6.6
type Resource struct {
URI string
Name string
Description string
MimeType string
// Reader returns the content for this URI. ctx is the live
// request context; the URI is passed through unchanged so
// the same Reader can serve a template family.
Reader ResourceReader
// MinRole — Tool.MinRole ile aynı sözleşme. Resource'lar
// v0.9.1136'ya dek kapının TAMAMEN dışındaydı: aynı veriyi
// döndüren bir tool gate'liyken URI yan kapısı serbestti.
MinRole string
}
Resource is a named read-only data reference. Where tools are "invoke an action with args", resources are "fetch the current state of <URI>". Browsers in Claude Desktop list resources as pinned references the user can click to attach; LLMs read them via resources/read.
URI scheme is opaque to the protocol — we use coremetry:// for everything Coremetry exposes (services, problems, etc.). MimeType hints how the client renders the read response; "application/json" is standard for our payloads.
type ResourceReader ¶ added in v0.6.6
ResourceReader is the function signature both concrete and templated resources share. uri is the full URI being read. Returns either a text payload or an error; binary blobs can layer on later if needed (image/png, audio, etc.) — the spec supports them via a `blob` field on the content envelope.
type ResourceTemplate ¶ added in v0.6.6
type ResourceTemplate struct {
URITemplate string
Name string
Description string
MimeType string
Reader ResourceReader
// MinRole — Tool.MinRole ile aynı sözleşme; kapı eşleşen
// ŞABLONUN değerini kullanır (concrete URI'nin değil).
MinRole string
}
ResourceTemplate is a URI pattern with {placeholder} segments. LLMs match against the pattern to discover that "coremetry://service/{name}" can be parameterised. The Reader receives the concrete URI; its handler parses out the placeholder values.
type Response ¶
type Response struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
}
Response is the outgoing reply. Either Result or Error is set, never both. omitempty on both fields keeps the wire form clean.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the MCP server. One instance per Coremetry process; holds the live session map + the registries that v0.6.5+ will populate.
The handler funcs HandleSSE + HandleMessage are wired onto the existing api.Server mux as /api/mcp/sse and /api/mcp/messages behind the standard auth middleware. Session state is in-memory only; a session is local to the pod the client first connected to. In distributed deploy + multi-replica api, a sticky-session LB rule (or session affinity at the ingress) is required — documented in v0.6.4's CHANGELOG.
func New ¶
New constructs an MCP server with the given serverInfo. Name + version surface in the initialize response — clients (and Claude Desktop's debug UI) display them.
func (*Server) HandleMessage ¶
func (s *Server) HandleMessage(w http.ResponseWriter, r *http.Request)
HandleMessage is the POST side of the transport. Client sends one JSON-RPC request; we respond synchronously via the SSE stream (not in the HTTP response body — per MCP spec, the POST returns 202 Accepted and the response rides the SSE channel).
This split lets the server send notifications + responses to unsolicited events back on the same SSE stream, which is what makes "live tool call" streaming work in v0.6.5+.
func (*Server) HandleSSE ¶
func (s *Server) HandleSSE(w http.ResponseWriter, r *http.Request)
HandleSSE implements the GET side of the HTTP+SSE transport. The first event emitted is an `endpoint` event whose data is the absolute path the client should POST messages to — including the session id query param. All subsequent events are `message` events carrying JSON-RPC responses for requests the client POSTed.
func (*Server) HandleStreamable ¶ added in v0.9.14
func (s *Server) HandleStreamable(w http.ResponseWriter, r *http.Request)
HandleStreamable — Streamable-HTTP transport'u (v0.9.14, MCP spec 2025-03-26), STATELESS kipte: tek POST = tek JSON-RPC isteği, yanıt DOĞRUDAN gövdede (SSE kanalı yok). Session hiç üretilmez (Mcp-Session-Id başlığı dönülmez) — istemci sessionless çalışır. Bu, çok-pod'lu kurulumdaki pod-lokal session kırılganlığını (audit EK BULGU) kökten çözer: her POST bağımsızdır, LB'de hangi pod'a düşerse düşsün. Claude Code'un birincil `--transport http` yolu budur; SSE yolu eski istemciler için aynen kalır.
func (*Server) PromptCount ¶ added in v0.6.7
PromptCount reports how many prompts are registered. Useful in boot logs so the operator can confirm the registry wired up.
func (*Server) RegisterPrompt ¶ added in v0.6.7
RegisterPrompt adds a prompt to the registry. Name uniqueness enforced — duplicate registration logs and overwrites.
func (*Server) RegisterResource ¶ added in v0.6.6
RegisterResource adds a concrete (fixed-URI) resource. URI uniqueness enforced — duplicate registration logs a warning and overwrites.
func (*Server) RegisterResourceTemplate ¶ added in v0.6.6
func (s *Server) RegisterResourceTemplate(rt ResourceTemplate)
RegisterResourceTemplate adds a URI-pattern resource. The pattern uses {placeholder} segments per RFC 6570 level 1 — we match prefix + suffix around each placeholder without pulling in a full URI Template engine. Good enough for the patterns Coremetry exposes (single placeholder near the end).
func (*Server) RegisterTool ¶ added in v0.6.5
RegisterTool adds a tool to the registry. Caller MUST do this before serving requests — there's no list_changed notification yet (tools/list returns a static snapshot). Last writer wins on a duplicate name; we log because that's almost certainly a bug.
func (*Server) ResourceCount ¶ added in v0.6.6
ResourceCount reports the static + template totals so boot logs can confirm registration. Returns (static, templates).
func (*Server) SetCallGate ¶ added in v0.9.1136
SetCallGate wires the optional gate. Boot'ta bir kez çağrılır (api.SetMCP), sonrasında değişmez.
type ServerCapabilities ¶
type ServerCapabilities struct {
Tools *CapToolsBag `json:"tools,omitempty"`
Resources *CapResourcesBag `json:"resources,omitempty"`
Prompts *CapPromptsBag `json:"prompts,omitempty"`
Logging *struct{} `json:"logging,omitempty"`
}
ServerCapabilities is what we tell the client we support. Each optional bag is "exists or doesn't" semantics — an empty struct signals "feature supported, no sub-features yet". As tools / resources / prompts come online in later releases the corresponding pointer gets non-nil.
v0.6.4 advertises tools/resources/prompts as supported (so clients run their listing dance against us) even though the registries are empty — keeps client codepaths warm and means the v0.6.5+ rollouts don't need a protocol bump.
type ServerInfo ¶
type Tool ¶ added in v0.6.5
type Tool struct {
Name string
Description string
InputSchema map[string]any
Handler ToolHandler
// MinRole (v0.9.1136) — en düşük rol; "" = viewer tabanı, yani
// kimliği doğrulanmış HERKES. Alan yalnız METADATA'dır: zorlama
// CallGate'te (api/mcp_gate.go). Tek kayıt defteri iki tüketiciyi
// birden besler (MCP dispatch + in-app sohbet spec listesi), yani
// burada yazılan kural iki yolda da geçerlidir — sohbet tarafı
// aşan tool'u LİSTELEMEZ (gizlemek > reddetmek: reddedilen tool
// bir tur harcar), MCP tarafı ÇAĞIRTMAZ.
//
// REST eşi editor/admin ise buraya da onu yaz — sapma bug'dır
// (v0.9.1136 A7: /api/anomalies/active editor'dı, list_anomalies
// kapısızdı; REST viewer'a indi, sapma sıfırlandı).
MinRole string
// ShortDescription (v0.9.1230) — kataloğun KOMPAKT görünümü,
// SADECE in-app sohbetin LLM isteğini kurarken kullanılır.
//
// Neden: tek kayıt defterinin İKİ tüketicisi var ve bağlam
// bütçeleri taban tabana zıt. Dış MCP istemcisi (Claude Desktop,
// bir frontier model) kataloğu bir kez okur ve uzun İngilizce
// sözleşmeden — maliyet sınırları, dürüstlük şerhleri, "şunu
// uydurma" negatifleri — kazanç sağlar. Yerel gemma4 ise AYNI
// kataloğu her tur yeniden yutar: ölçüm (v0.9.1230) 33 tool için
// 24.268 B açıklama + 17.672 B şema = 41.940 B, ve serbest döngü
// 5 tura kadar çıkıyor. Küçük modelde bu, "schema soup"un ta
// kendisi (copilot_guided.go başlığındaki başarısızlık modu).
//
// Sözleşme: MCP tools/list DAİMA Description'ı servis eder —
// dış istemcinin gördüğü şey değişmedi (mcptools testi pinliyor).
// Sohbet tarafı ChatDescription()'ı çağırır. Alan bu yüzden
// AYNI literal'de, Name'in hemen altında yaşıyor: ikinci bir
// elle-bakımlı liste açılırsa sürüklenme kaçınılmazdı, burada
// yeni tool'un yazarı iki görünümü de yan yana görür (ve
// mcptools'taki yapısal test boş bırakılmasına izin vermez).
ShortDescription string
}
Tool is an MCP tool definition. Tools are the LLM's callable surface — discoverable via tools/list, invoked via tools/call. The input schema is a JSON Schema object the client uses to validate args before invocation (and to render a form UI in Claude Desktop / similar inspectors).
The handler receives the raw params JSON as a RawMessage — concrete tool implementations decode into their own typed args struct. Return value is anything json.Marshal-able; the server wraps it in the tools/call response envelope.
Schema example (mirrors JSON Schema draft-2020-12 enough for every MCP client):
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"service": map[string]any{"type": "string"},
"range_ns": map[string]any{"type": "integer", "minimum": 0},
},
"required": []string{"service"},
}
func (Tool) ChatDescription ¶ added in v0.9.1230
ChatDescription — kataloğun sohbet (küçük model) görünümü.
Boşsa tam Description'a düşer: bu, geri-uyum değil GÜVENLİ YÖN — kompakt metni unutulmuş bir tool yine de doğru çağrılabilir, yalnız pahalı olur. Sessiz YANLIŞ çağrı ihtimali sıfır. Boş kalmasını mcptools'taki yapısal test engeller (kapı ORADA, çünkü kayıt defteri orada).
SAF — tablo testli.
type ToolError ¶ added in v0.9.1234
type ToolError struct {
Error string `json:"error"`
Retryable bool `json:"retryable"`
Hint string `json:"hint"`
Detail string `json:"detail,omitempty"`
}
ToolError — başarısız bir tool çağrısının modele giden hâli.
Alan sırası bilinçli: model önce NE olduğunu (error), sonra tekrar denemenin işe yarayıp yaramayacağını (retryable), sonra NE YAPACAĞINI (hint) okur; ham metin (detail) en sonda, çünkü en az eyleme dönük olan o.
func ClassifyToolError ¶ added in v0.9.1234
ClassifyToolError — bir handler hatasını sözleşmeye çevirir. SAF: yalnız error değerine bakar, hiçbir şey yazmaz. Tablo testli.
nil hata çağıran hatasıdır (başarı yolunda çağrılmaz); yine de internal döner, panik değil — hata yolunda panik atmak, hata yolunun kendisini bozar.
type ToolHandler ¶ added in v0.6.5
ToolHandler runs one tools/call invocation. Returns a value the server marshals into the response, OR an error which is converted into a JSON-RPC error with code ErrInternal. The raw args are passed through so the handler picks its own decode (tagged struct, manual map dispatch, etc.).