Documentation
¶
Overview ¶
Package mcpperf serves this Go process's runtime performance data over a read-only Model Context Protocol (MCP) server, so an agent can introspect the harness's own latency, memory, goroutine, and profile state through MCP tools and resources instead of a human reading raw /metrics or /debug/pprof.
What it exposes ¶
Every tool and resource returns a REDUCED, numeric summary — never a raw blob. Histograms become count + p50/p90/p99 bucket UPPER BOUNDS; profiles become a ranked top-N of function names; the runtime snapshot is a small JSON DTO. Raw artifacts (a full pprof CPU profile, a flight-recorder trace) are offered ONLY as user-audience resource_links pointing at the existing loopback admin endpoints (/debug/pprof/*, /debug/flightrecorder) — this server never holds an artifact store and never streams a multi-MB blob into the model's context.
Transport ¶
Streamable HTTP only (a pure http.Handler), per the project's hard no-stdio constraint: no MCP server process is ever spawned and the stdio transport is never imported. Handler is built with the SDK's default options, which leaves the SDK's localhost / DNS-rebinding protection ON — a request that arrives on a loopback listener with a non-loopback Host header is rejected with 403. The surface is UNAUTHENTICATED by design: it is meant to be mounted on the same loopback admin listener as the rest of the perf surface, never exposed publicly. Its output can embed goroutine-derived function names and timing, so loopback-only is a security requirement (see docs/adr/0018-perf-observability.md).
Layering ¶
This is an edge adapter constructed by dependency injection (the Deps struct): it imports the MCP SDK, the telemetry adapter (only for the RuntimeSnapshot DTO type), google/pprof for profile parsing, and the prometheus client model — and NOTHING from the domain, port, or agent layers, none of which may import it. It is not wired into any composition root by this commit; that is a later step.
Index ¶
- func Handler(d Deps) http.Handler
- func NewServer(d Deps) *mcpsdk.Server
- type AllocStat
- type CaptureCPUProfileInput
- type CaptureFlightRecorderInput
- type CaptureFlightRecorderOutput
- type Deps
- type FlightRecorder
- type FuncStat
- type ListSlowTurnsInput
- type ListSlowTurnsOutput
- type MemstatsProjection
- type MetricSummaryEntry
- type Profiler
- type QuantileBound
- type QueryMetricInput
- type QueryMetricOutput
- type RoleBreakdown
- type SlowTurn
- type SlowTurnSource
- type TopAllocationsInput
- type TopAllocationsOutput
- type TopCPUFunctionsInput
- type TopCPUFunctionsOutput
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Handler ¶
Handler builds the Streamable HTTP handler for the perf MCP server. It uses the SDK default options (nil), which keeps the SDK's localhost / DNS-rebinding protection ON — NEVER set DisableLocalhostProtection. The returned handler is a pure http.Handler meant to be mounted on the loopback admin listener; it spawns no process and opens no outbound connection, satisfying the no-stdio constraint by construction.
The server is constructed once and shared across requests via the getServer closure: this server is stateless across calls (every tool reads live process state), so one instance is correct and avoids per-request allocation.
func NewServer ¶
NewServer builds the perf MCP server: it registers the read-only tools and the runtime/metrics/pprof resources against a fresh *mcpsdk.Server carrying the "mecatl-perf" identity and the declarative instructions. A cpuGate is created per server so the CPU-profiling tools share one cooldown + in-flight guard.
It panics if a required dependency (Snapshot, Gatherer, Profiler) is nil — a wiring error the caller must fix, not a runtime condition to be handled.
Types ¶
type AllocStat ¶
type AllocStat struct {
// Function is the allocating function's name.
Function string `json:"function"`
// File is the BASENAME of the allocating function's source file (path dropped).
File string `json:"file"`
// FlatBytes is the bytes attributed to this function alone.
FlatBytes int64 `json:"flat_bytes"`
// CumBytes is the bytes attributed to this function and its callees.
CumBytes int64 `json:"cum_bytes"`
}
AllocStat is one ranked allocation row from a heap/allocs profile. It mirrors FuncStat but names the value bytes, since the memTop reducer ranks by an allocation-space sample index.
type CaptureCPUProfileInput ¶
type CaptureCPUProfileInput struct {
DurationSeconds int `json:"duration_seconds,omitempty" jsonschema:"CPU profiling window in seconds (1-30, default 5)"`
Limit int `json:"limit,omitempty" jsonschema:"number of top functions to return (1-50, default 15)"`
IncludeRawLink bool `` /* 147-byte string literal not displayed */
}
CaptureCPUProfileInput mirrors top_cpu_functions plus a raw-link flag.
type CaptureFlightRecorderInput ¶
type CaptureFlightRecorderInput struct{}
CaptureFlightRecorderInput takes no parameters.
type CaptureFlightRecorderOutput ¶
type CaptureFlightRecorderOutput struct {
CapturedBytes int `json:"captured_bytes"`
WindowSummary string `json:"window_summary"`
}
CaptureFlightRecorderOutput summarizes a flight-recorder snapshot without returning the trace bytes.
type Deps ¶
type Deps struct {
// Snapshot returns the current runtime snapshot DTO. Required.
Snapshot func() telemetry.RuntimeSnapshot
// Gatherer gathers the prometheus metric families backing the latency
// histograms and counters. *prometheus.Registry satisfies it. Required.
Gatherer prometheus.Gatherer
// Recorder is the flight recorder seam. Nil-able (recorder not armed).
Recorder FlightRecorder
// Profiler is the pprof seam. Required (defaultProfiler in production).
Profiler Profiler
// SlowTurns is the slow-turn history seam. Nil-able (history not enabled).
SlowTurns SlowTurnSource
// Clock returns the current time, for cooldown accounting and timestamps.
// Nil falls back to time.Now.
Clock func() time.Time
// Logger is used for low-volume operational logging. It MUST NOT be used to
// log tool inputs or outputs at info level. Nil falls back to a discard logger.
Logger *slog.Logger
}
Deps is the dependency seam for the perf MCP server. Every external capability is an interface or a func so the adapter never reaches into a composition root and tests substitute fakes. *telemetry.FlightRecorder, *prometheus.Registry, and telemetry.Snapshot already satisfy the respective fields.
type FlightRecorder ¶
type FlightRecorder interface {
// SnapshotBytes returns the current trace window as parseable trace bytes.
SnapshotBytes() ([]byte, error)
// Enabled reports whether the recorder is currently running.
Enabled() bool
}
FlightRecorder is the read seam over the execution-trace flight recorder. *telemetry.FlightRecorder satisfies it. It is nil-able: when the recorder was never armed, capture_flight_recorder returns a tool error rather than failing.
type FuncStat ¶
type FuncStat struct {
// Function is the function's name (pprof Function.Name).
Function string `json:"function"`
// File is the BASENAME of the function's source file — the absolute path is
// dropped deliberately, because it leaks $HOME and workspace layout.
File string `json:"file"`
// FlatValue is the value attributed to this function alone (self time/space).
FlatValue int64 `json:"flat_value"`
// CumValue is the value attributed to this function and everything it called.
CumValue int64 `json:"cum_value"`
}
FuncStat is one ranked function row from a CPU/timing profile. It carries the REDACTED function identity (name + file basename only) plus its flat and cumulative values in the profile's own unit (e.g. nanoseconds for a CPU profile). No absolute path and no pprof label survives into this struct.
type ListSlowTurnsInput ¶
type ListSlowTurnsInput struct {
ThresholdMs int64 `json:"threshold_ms,omitempty" jsonschema:"only turns at least this slow (ms); omit for the source default"`
Limit int `json:"limit,omitempty" jsonschema:"max turns to return (1-20, default 10)"`
Cursor string `json:"cursor,omitempty" jsonschema:"opaque pagination cursor from a previous response's nextCursor"`
Role string `` /* 134-byte string literal not displayed */
}
ListSlowTurnsInput pages the slow-turn history.
type ListSlowTurnsOutput ¶
type ListSlowTurnsOutput struct {
Turns []SlowTurn `json:"turns"`
NextCursor string `json:"nextCursor,omitempty"`
TotalCount int `json:"totalCount"`
}
ListSlowTurnsOutput is the paginated slow-turn page. Each turn carries numerics and timestamps only — never prompt text or session IDs.
type MemstatsProjection ¶
type MemstatsProjection struct {
// HeapAllocsTotalBytes is the cumulative heap-allocation counter (bytes ever
// allocated since process start) — NOT live heap; live is HeapObjectBytes.
HeapAllocsTotalBytes uint64 `json:"heap_allocs_total_bytes"`
HeapObjects uint64 `json:"heap_objects"`
HeapObjectBytes uint64 `json:"heap_object_bytes"`
TotalMemoryBytes uint64 `json:"total_memory_bytes"`
RSSBytes uint64 `json:"rss_bytes"`
// Available echoes the snapshot's Available set so a consumer can tell which
// runtime/metrics-derived fields were actually present this read.
Available []string `json:"available"`
}
MemstatsProjection is the memory-focused superset view of the runtime snapshot served by perf://runtime/memstats. It is a projection of RuntimeSnapshot (NOT a runtime.ReadMemStats), so it inherits the snapshot's lock-free, no-STW guarantee. Byte counts are bytes.
type MetricSummaryEntry ¶
type MetricSummaryEntry struct {
Name string `json:"name"`
Description string `json:"description"`
Kind string `json:"kind"` // "histogram" | "scalar" | "absent"
Count uint64 `json:"count,omitempty"`
Quantiles []QuantileBound `json:"quantiles,omitempty"`
Value float64 `json:"value,omitempty"`
ByRole []RoleBreakdown `json:"by_role,omitempty"`
}
MetricSummaryEntry is one curated metric's reduced view in the metrics-summary resource: for a histogram, its count and quantile upper bounds; for a scalar (counter/gauge), its single value. Exactly one of Quantiles/Value is meaningful per Kind. ByRole, when present, is the bounded per-role breakdown (one row per role family observed on the family's series).
type Profiler ¶
type Profiler interface {
// Lookup returns the named profile's pprof bytes (debug=0, gzipped protobuf)
// for name in {heap, goroutine, allocs, mutex, block}, or an error if the
// name is unknown or the profile cannot be written.
Lookup(name string) ([]byte, error)
// CPUProfile runs a CPU profile for d and returns its pprof bytes. It blocks
// for the duration. Only one CPU profile may run process-wide at a time; the
// caller (the cpuGate) serializes access.
CPUProfile(d time.Duration) ([]byte, error)
}
Profiler is the seam over runtime/pprof, so tests inject a deterministic profile rather than profiling the test binary. The production implementation (defaultProfiler) wraps runtime/pprof: Lookup for named heap/goroutine/etc. profiles, and a start/stop pair for the CPU profile.
func NewProfiler ¶
func NewProfiler() Profiler
NewProfiler returns the production Profiler backed by runtime/pprof. Inject it into Deps.Profiler in the composition root; tests use a fake instead.
type QuantileBound ¶
type QuantileBound struct {
// Quantile is the requested rank in [0,1] (e.g. 0.99 for p99).
Quantile float64 `json:"quantile"`
// UpperBound is the upper bound of the bucket containing that rank, in the
// histogram's own unit (seconds for the latency instruments).
UpperBound float64 `json:"upper_bound"`
}
QuantileBound is one (quantile, upper-bound) pair derived from a histogram. The field is named UpperBound, not "value", to be HONEST about precision: it is the upper bound of the histogram bucket the quantile rank falls in, not an interpolated exact quantile.
type QueryMetricInput ¶
type QueryMetricInput struct {
MetricName string `json:"metric_name,omitempty" jsonschema:"the curated metric to query; omit to list available metric names"`
Quantile float64 `` /* 133-byte string literal not displayed */
Role string `` /* 150-byte string literal not displayed */
}
QueryMetricInput selects a curated metric to summarize. All fields optional: omitting metric_name lists the available names (discovery).
type QueryMetricOutput ¶
type QueryMetricOutput struct {
AvailableMetrics []string `json:"available_metrics,omitempty"`
Metric *MetricSummaryEntry `json:"metric,omitempty"`
}
QueryMetricOutput is the structured result of query_metric. Exactly one shape is populated per call: AvailableMetrics for discovery, or the metric summary.
type RoleBreakdown ¶
type RoleBreakdown struct {
Role string `json:"role"`
Count uint64 `json:"count,omitempty"`
Value float64 `json:"value,omitempty"`
}
RoleBreakdown is one role family's share of a curated metric: the observation count for a histogram, or the summed value for a counter. The role dimension is intrinsically bounded (the closed roleFamilies set), so the breakdown can never grow past a handful of rows.
type SlowTurn ¶
type SlowTurn struct {
// TurnIndex is the monotonic index of the turn within its run.
TurnIndex int `json:"turn_index"`
// DurationMs is the turn's model-call wall-clock duration in milliseconds.
DurationMs int64 `json:"duration_ms"`
// TTFTMs is the time-to-first-token in milliseconds (0 if not measured).
TTFTMs int64 `json:"ttft_ms"`
// InterTokenMaxMs is the worst inter-token gap in milliseconds (0 if not measured).
InterTokenMaxMs int64 `json:"inter_token_max_ms"`
// EndedAt is the wall-clock time the turn ended, RFC3339.
EndedAt time.Time `json:"ended_at"`
// Role is the BOUNDED engine role family that produced the turn
// (main|subagent|member|parallel|usermodel|child). It is a closed enum
// label — never a def/member name or session id — so redaction by shape holds.
Role string `json:"role,omitempty"`
}
SlowTurn is one entry of the per-turn slow-turn history. It carries ONLY numerics and timestamps — never prompt text, tool arguments, or session IDs — so list_slow_turns can never leak conversation content.
type SlowTurnSource ¶
type SlowTurnSource interface {
// Recent returns recent slow turns, newest first. thresholdMs, if > 0, filters
// to turns at least that slow; 0 means "use the source default threshold". It
// returns the whole (bounded) matching set so the tool can report an accurate
// totalCount and paginate over a stable snapshot — the buffer's fixed size is
// the cap, not a per-call limit argument.
Recent(thresholdMs int64) []SlowTurn
}
SlowTurnSource is the read seam over the slow-turn ring buffer. It is nil-able: the concrete buffer lands in a later commit, so when nil, list_slow_turns reports that per-turn history is not enabled. The implementation owns the window/eviction policy; the buffer is intrinsically bounded (a fixed-size ring), so returning its whole contents is cheap.
type TopAllocationsInput ¶
type TopAllocationsInput struct {
Limit int `json:"limit,omitempty" jsonschema:"number of top allocating functions to return (1-50, default 15)"`
}
TopAllocationsInput configures the heap top-N.
type TopAllocationsOutput ¶
type TopAllocationsOutput struct {
TotalHeapBytes int64 `json:"total_heap_bytes"`
Top []AllocStat `json:"top"`
}
TopAllocationsOutput is the heap allocation summary.
type TopCPUFunctionsInput ¶
type TopCPUFunctionsInput struct {
DurationSeconds int `json:"duration_seconds,omitempty" jsonschema:"CPU profiling window in seconds (1-30, default 5)"`
Limit int `json:"limit,omitempty" jsonschema:"number of top functions to return (1-50, default 15)"`
}
TopCPUFunctionsInput configures a short CPU profile + top-N reduction.
type TopCPUFunctionsOutput ¶
type TopCPUFunctionsOutput struct {
DurationSeconds int `json:"duration_seconds"`
SampleCount int `json:"sample_count"`
Top []FuncStat `json:"top"`
}
TopCPUFunctionsOutput is the FuncStat-based result for the CPU tools.