mcp

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package mcp exposes Spaniel's observability data to MCP (Model Context Protocol) clients such as Claude Code and Claude Desktop. It runs in-process on the main HTTP server (mounted at /mcp) over the streamable-HTTP transport and reads directly from storage — no second process, so it never contends with the server for DuckDB's single writer lock.

This file is the foundation: server construction, the telemetry middleware, and a single read-only get_server_info tool. Subsequent tools live in sibling files in this package and are registered from NewHandler.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewHandler

func NewHandler(store *storage.DB, opts Options) http.Handler

NewHandler builds the MCP server and returns an http.Handler serving the streamable-HTTP transport. Mount it at /mcp on the main server.

Types

type ActivateSessionInput

type ActivateSessionInput struct {
	SessionID string `json:"session_id" jsonschema:"id of the session to activate"`
}

type CreateSessionInput

type CreateSessionInput struct {
	Label    string `json:"label,omitempty" jsonschema:"human-readable label; defaults to a timestamp"`
	Baseline bool   `json:"baseline,omitempty" jsonschema:"mark the new session as a baseline"`
	Activate bool   `json:"activate,omitempty" jsonschema:"make this the active session so new telemetry records into it"`
}

type CreateSessionOutput

type CreateSessionOutput struct {
	ID         string `json:"id"`
	Label      string `json:"label"`
	IsBaseline bool   `json:"is_baseline"`
	IsActive   bool   `json:"is_active"`
}

type DiffSessionsInput

type DiffSessionsInput struct {
	Baseline string `json:"baseline" jsonschema:"baseline session id (the 'before')"`
	Compare  string `json:"compare" jsonschema:"compare session id (the 'after')"`
}

type DiffSessionsOutput

type DiffSessionsOutput struct {
	Baseline DiffSideOut    `json:"baseline"`
	Compare  DiffSideOut    `json:"compare"`
	Summary  DiffSummaryOut `json:"summary"`
	Spans    []DiffSpanOut  `json:"spans"`
}

type DiffSideOut

type DiffSideOut struct {
	SessionID string  `json:"session_id"`
	Label     string  `json:"label"`
	TotalMs   float64 `json:"total_ms"`
	SpanCount int     `json:"span_count"`
	DBCalls   int     `json:"db_calls"`
}

type DiffSpanOut

type DiffSpanOut struct {
	Service    string  `json:"service"`
	Name       string  `json:"name"`
	Status     string  `json:"status"`
	BaselineMs float64 `json:"baseline_ms"`
	CompareMs  float64 `json:"compare_ms"`
	DeltaPct   float64 `json:"delta_pct"`
}

type DiffSummaryOut

type DiffSummaryOut struct {
	DurationDeltaMs  float64 `json:"duration_delta_ms"`
	DurationDeltaPct float64 `json:"duration_delta_pct"`
	SpansAdded       int     `json:"spans_added"`
	SpansRemoved     int     `json:"spans_removed"`
	DBCallDelta      int     `json:"db_call_delta"`
}

type GetMetricSeriesInput

type GetMetricSeriesInput struct {
	Name      string `json:"name" jsonschema:"metric name (from get_metrics)"`
	Service   string `json:"service,omitempty" jsonschema:"narrow to one service"`
	SessionID string `json:"session_id,omitempty" jsonschema:"session to query; defaults to the active session"`
	FromNs    int64  `json:"from_ns,omitempty" jsonschema:"inclusive lower bound (unix ns); 0 = no bound"`
	ToNs      int64  `json:"to_ns,omitempty" jsonschema:"inclusive upper bound (unix ns); 0 = no bound"`
	Limit     int    `json:"limit,omitempty" jsonschema:"max points to return (default 500)"`
}

type GetMetricSeriesOutput

type GetMetricSeriesOutput struct {
	SessionID string           `json:"session_id"`
	Name      string           `json:"name"`
	Count     int              `json:"count"`
	Points    []MetricPointOut `json:"points"`
}

type GetMetricsInput

type GetMetricsInput struct {
	SessionID string `json:"session_id,omitempty" jsonschema:"session to query; defaults to the active session"`
}

type GetMetricsOutput

type GetMetricsOutput struct {
	SessionID string             `json:"session_id"`
	Count     int                `json:"count"`
	Metrics   []MetricCatalogOut `json:"metrics"`
}

type GetServiceMapInput

type GetServiceMapInput struct {
	SessionID string `json:"session_id,omitempty" jsonschema:"session to query; defaults to the active session"`
}

type GetSpanInput

type GetSpanInput struct {
	SpanID string `json:"span_id" jsonschema:"the span id to fetch"`
}

type GetSpanOutput

type GetSpanOutput struct {
	SpanID        string            `json:"span_id"`
	TraceID       string            `json:"trace_id"`
	ParentSpanID  string            `json:"parent_span_id"`
	Service       string            `json:"service"`
	Name          string            `json:"name"`
	Kind          string            `json:"kind"`
	Status        string            `json:"status"`
	StatusMessage string            `json:"status_message,omitempty"`
	DurationMs    float64           `json:"duration_ms"`
	StartUnixNs   int64             `json:"start_unix_ns"`
	Attributes    map[string]string `json:"attributes"`
	Resource      map[string]string `json:"resource"`
	Events        []SpanEventOut    `json:"events"`
	Links         []SpanLinkOut     `json:"links"`
}

type GetStatsInput

type GetStatsInput struct {
	SessionID string `json:"session_id,omitempty" jsonschema:"session to query; defaults to the active session"`
}

type GetTraceInput

type GetTraceInput struct {
	TraceID string `json:"trace_id" jsonschema:"the trace id to fetch (from list_traces or search)"`
	MaxLogs int    `json:"max_logs,omitempty" jsonschema:"max correlated log lines to include (default 100)"`
}

type GetTraceOutput

type GetTraceOutput struct {
	TraceID    string          `json:"trace_id"`
	SessionID  string          `json:"session_id"`
	SpanCount  int             `json:"span_count"`
	DurationMs float64         `json:"duration_ms"`
	Waterfall  string          `json:"waterfall"`
	Spans      []TraceSpanOut  `json:"spans"`
	Issues     []TraceIssueOut `json:"issues"`
	Logs       []TraceLogOut   `json:"logs"`
	Lint       []LintOut       `json:"lint"`
}

type IssueOut

type IssueOut struct {
	Kind          string  `json:"kind"`
	TraceID       string  `json:"trace_id"`
	Count         int     `json:"count"`
	WastedMs      float64 `json:"wasted_ms"`
	Fingerprint   string  `json:"fingerprint,omitempty"`
	ParentSpanID  string  `json:"parent_span_id,omitempty"`
	ExampleSpanID string  `json:"example_span_id,omitempty"`
}

type LintOut

type LintOut struct {
	SpanID   string `json:"span_id"`
	RuleID   string `json:"rule_id"`
	Severity string `json:"severity"`
	Message  string `json:"message"`
}

type LintWarningOut

type LintWarningOut struct {
	SpanID   string `json:"span_id"`
	TraceID  string `json:"trace_id"`
	RuleID   string `json:"rule_id"`
	Severity string `json:"severity"`
	Message  string `json:"message"`
}

type ListIssuesInput

type ListIssuesInput struct {
	SessionID string `json:"session_id,omitempty" jsonschema:"session to query when trace_id is not set; defaults to the active session"`
	TraceID   string `json:"trace_id,omitempty" jsonschema:"scope to a single trace; takes precedence over session_id"`
}

type ListIssuesOutput

type ListIssuesOutput struct {
	SessionID string     `json:"session_id,omitempty"`
	TraceID   string     `json:"trace_id,omitempty"`
	Count     int        `json:"count"`
	Issues    []IssueOut `json:"issues"`
}

type ListLintInput

type ListLintInput struct {
	SessionID string `json:"session_id,omitempty" jsonschema:"session to query; defaults to the active session"`
	TraceID   string `json:"trace_id,omitempty" jsonschema:"filter to a single trace"`
	Limit     int    `json:"limit,omitempty" jsonschema:"max warnings to return (default 200, max 500)"`
}

type ListLintOutput

type ListLintOutput struct {
	SessionID string           `json:"session_id"`
	Count     int              `json:"count"`
	Warnings  []LintWarningOut `json:"warnings"`
}

type ListServicesOutput

type ListServicesOutput struct {
	Count    int      `json:"count"`
	Services []string `json:"services"`
}

type ListSessionsOutput

type ListSessionsOutput struct {
	ActiveSessionID string       `json:"active_session_id"`
	Count           int          `json:"count"`
	Sessions        []SessionOut `json:"sessions"`
}

type ListSlowSpansInput

type ListSlowSpansInput struct {
	SessionID string `json:"session_id,omitempty" jsonschema:"session to query; defaults to the active session"`
	Limit     int    `json:"limit,omitempty" jsonschema:"max spans to return (default 25, max 200)"`
}

type ListSlowSpansOutput

type ListSlowSpansOutput struct {
	SessionID string        `json:"session_id"`
	Count     int           `json:"count"`
	Spans     []SlowSpanOut `json:"spans"`
}

type ListTracesInput

type ListTracesInput struct {
	SessionID string `json:"session_id,omitempty" jsonschema:"session to query; defaults to the active session"`
	Service   string `json:"service,omitempty" jsonschema:"filter to traces whose root span has this service.name"`
	Limit     int    `json:"limit,omitempty" jsonschema:"max traces to return (default 50, max 200)"`
	Page      int    `json:"page,omitempty" jsonschema:"1-based page number (default 1)"`
}

type ListTracesOutput

type ListTracesOutput struct {
	SessionID string         `json:"session_id"`
	Count     int            `json:"count"`
	Traces    []TraceSummary `json:"traces"`
}

type LogOut

type LogOut struct {
	TimestampNs int64  `json:"timestamp_ns"`
	Severity    string `json:"severity"`
	Body        string `json:"body"`
	Service     string `json:"service,omitempty"`
	TraceID     string `json:"trace_id,omitempty"`
	SpanID      string `json:"span_id,omitempty"`
}

type MetricCatalogOut

type MetricCatalogOut struct {
	Name        string `json:"name"`
	Type        string `json:"type"`
	Unit        string `json:"unit,omitempty"`
	Description string `json:"description,omitempty"`
	Service     string `json:"service,omitempty"`
	SampleCount int    `json:"sample_count"`
}

type MetricPointOut

type MetricPointOut struct {
	TimestampNs int64             `json:"timestamp_ns"`
	Value       float64           `json:"value"`
	Service     string            `json:"service,omitempty"`
	Attributes  map[string]string `json:"attributes,omitempty"`
	Exemplars   string            `json:"exemplars,omitempty"`
}

type OKOutput

type OKOutput struct {
	OK                 bool   `json:"ok"`
	ActiveSessionID    string `json:"active_session_id,omitempty"`
	ActiveSessionLabel string `json:"active_session_label,omitempty"`
}

type Options

type Options struct {
	// Version is the spaniel build version, reported in the MCP handshake and by
	// get_server_info.
	Version string
	// AllowWrites gates registration of the mutating tools (create/activate
	// session, set baseline, prune). When false, those tools are not registered
	// at all — they do not appear in tools/list and cannot be called.
	AllowWrites bool
}

Options configures the MCP server.

type PruneDataInput

type PruneDataInput struct {
	RetentionDays int `json:"retention_days,omitempty" jsonschema:"delete sessions older than N days (0 = no age limit)"`
	MaxSessions   int `json:"max_sessions,omitempty" jsonschema:"keep at most N sessions (0 = unlimited)"`
	MaxDBSizeMB   int `json:"max_db_size_mb,omitempty" jsonschema:"shrink to at most N MB on disk (0 = unlimited)"`
}

type PruneDataOutput

type PruneDataOutput struct {
	DeletedByAge     int   `json:"deleted_by_age"`
	DeletedByCount   int   `json:"deleted_by_count"`
	DeletedBySize    int   `json:"deleted_by_size"`
	FinalSessions    int   `json:"final_sessions"`
	FinalDBSizeBytes int64 `json:"final_db_size_bytes"`
}

type QueryLogsInput

type QueryLogsInput struct {
	SessionID   string `json:"session_id,omitempty" jsonschema:"session to query; defaults to the active session"`
	TraceID     string `json:"trace_id,omitempty" jsonschema:"only logs correlated with this trace"`
	SpanID      string `json:"span_id,omitempty" jsonschema:"only logs correlated with this span"`
	Service     string `json:"service,omitempty" jsonschema:"only logs from this service.name"`
	MinSeverity string `json:"min_severity,omitempty" jsonschema:"minimum severity: TRACE, DEBUG, INFO, WARN, ERROR, or FATAL"`
	Limit       int    `json:"limit,omitempty" jsonschema:"max log records to return (default 100, max 1000)"`
	Page        int    `json:"page,omitempty" jsonschema:"1-based page (ignored when service/min_severity is set)"`
}

type QueryLogsOutput

type QueryLogsOutput struct {
	SessionID string   `json:"session_id"`
	Count     int      `json:"count"`
	Logs      []LogOut `json:"logs"`
}

type QuerySQLInput

type QuerySQLInput struct {
	SQL     string `json:"sql" jsonschema:"the SQL to run (read-only; the engine rejects writes)"`
	MaxRows int    `json:"max_rows,omitempty" jsonschema:"max rows to return (default 1000, max 50000)"`
}

type QuerySQLOutput

type QuerySQLOutput struct {
	Columns   []string `json:"columns"`
	Rows      [][]any  `json:"rows"`
	RowCount  int      `json:"row_count"`
	Truncated bool     `json:"truncated"`
}

type SearchHit

type SearchHit struct {
	Kind      string `json:"kind"`
	Title     string `json:"title"`
	Subtitle  string `json:"subtitle,omitempty"`
	TraceID   string `json:"trace_id,omitempty"`
	SpanID    string `json:"span_id,omitempty"`
	SessionID string `json:"session_id,omitempty"`
}

type SearchInput

type SearchInput struct {
	Query     string `json:"query" jsonschema:"search text; also supports lint:<rule> and n+1 filters"`
	SessionID string `json:"session_id,omitempty" jsonschema:"scope to a session; leave empty to search all sessions"`
	Limit     int    `json:"limit,omitempty" jsonschema:"max results (default 20, max 50)"`
}

type SearchOutput

type SearchOutput struct {
	Query   string      `json:"query"`
	Count   int         `json:"count"`
	Results []SearchHit `json:"results"`
}

type ServerInfo

type ServerInfo struct {
	Version            string `json:"version" jsonschema:"spaniel build version"`
	ActiveSessionID    string `json:"active_session_id" jsonschema:"id of the session new telemetry is written to"`
	ActiveSessionLabel string `json:"active_session_label" jsonschema:"human-readable label of the active session"`
	WritesEnabled      bool   `json:"writes_enabled" jsonschema:"whether mutating tools are available (server started with --mcp-allow-writes)"`
	SpanCount          int    `json:"span_count"`
	TraceCount         int    `json:"trace_count"`
	LogCount           int    `json:"log_count"`
	SessionCount       int    `json:"session_count"`
	DBSizeBytes        int64  `json:"db_size_bytes"`
}

ServerInfo is the output of get_server_info.

type ServiceEdgeOut

type ServiceEdgeOut struct {
	From       string  `json:"from"`
	To         string  `json:"to"`
	CallCount  int     `json:"call_count"`
	AvgMs      float64 `json:"avg_ms"`
	ErrorCount int     `json:"error_count"`
}

type ServiceMapOutput

type ServiceMapOutput struct {
	SessionID string           `json:"session_id"`
	Nodes     []ServiceNodeOut `json:"nodes"`
	Edges     []ServiceEdgeOut `json:"edges"`
}

type ServiceNodeOut

type ServiceNodeOut struct {
	Service    string  `json:"service"`
	SpanCount  int     `json:"span_count"`
	ErrorCount int     `json:"error_count"`
	P95Ms      float64 `json:"p95_ms"`
}

type SessionOut

type SessionOut struct {
	ID         string  `json:"id"`
	Label      string  `json:"label"`
	IsActive   bool    `json:"is_active"`
	IsBaseline bool    `json:"is_baseline"`
	IsImported bool    `json:"is_imported"`
	SpanCount  int     `json:"span_count"`
	TraceCount int     `json:"trace_count"`
	ErrorCount int     `json:"error_count"`
	N1Count    int     `json:"n1_count"`
	P95Ms      float64 `json:"p95_ms"`
	CreatedAt  int64   `json:"created_at"`
	Note       string  `json:"note,omitempty"`
}

type SetBaselineInput

type SetBaselineInput struct {
	SessionID  string `json:"session_id" jsonschema:"id of the session"`
	IsBaseline bool   `json:"is_baseline" jsonschema:"true to mark as baseline, false to clear"`
}

type SlowSpanOut

type SlowSpanOut struct {
	SpanID      string  `json:"span_id"`
	TraceID     string  `json:"trace_id"`
	Service     string  `json:"service"`
	Name        string  `json:"name"`
	DurationMs  float64 `json:"duration_ms"`
	Status      string  `json:"status"`
	Tag         string  `json:"tag,omitempty"`
	StartUnixNs int64   `json:"start_unix_ns"`
}

type SpanEventOut

type SpanEventOut struct {
	TimeNs     int64             `json:"time_ns"`
	Name       string            `json:"name"`
	Attributes map[string]string `json:"attributes,omitempty"`
}

type SpanLinkOut

type SpanLinkOut struct {
	LinkedTraceID string            `json:"linked_trace_id"`
	LinkedSpanID  string            `json:"linked_span_id"`
	Attributes    map[string]string `json:"attributes,omitempty"`
}

type StatsOutput

type StatsOutput struct {
	SessionID           string `json:"session_id"`
	SpanCount           int    `json:"span_count"`
	TraceCount          int    `json:"trace_count"`
	LogCount            int    `json:"log_count"`
	SessionCount        int    `json:"session_count"`
	DBSizeBytes         int64  `json:"db_size_bytes"`
	DroppedSpans        int64  `json:"dropped_spans"`
	DroppedLogs         int64  `json:"dropped_logs"`
	DroppedMetricPoints int64  `json:"dropped_metric_points"`
}

type TraceIssueOut

type TraceIssueOut struct {
	Kind          string  `json:"kind"`
	Count         int     `json:"count"`
	WastedMs      float64 `json:"wasted_ms"`
	ParentSpanID  string  `json:"parent_span_id,omitempty"`
	ExampleSpanID string  `json:"example_span_id,omitempty"`
}

type TraceLogOut

type TraceLogOut struct {
	TimestampNs int64  `json:"timestamp_ns"`
	Severity    string `json:"severity"`
	Body        string `json:"body"`
	SpanID      string `json:"span_id,omitempty"`
	Service     string `json:"service,omitempty"`
}

type TraceSpanOut

type TraceSpanOut struct {
	SpanID        string            `json:"span_id"`
	ParentSpanID  string            `json:"parent_span_id"`
	Service       string            `json:"service"`
	Name          string            `json:"name"`
	Kind          string            `json:"kind"`
	Status        string            `json:"status"`
	StatusMessage string            `json:"status_message,omitempty"`
	DurationMs    float64           `json:"duration_ms"`
	StartUnixNs   int64             `json:"start_unix_ns"`
	Attributes    map[string]string `json:"attributes,omitempty"`
}

type TraceSummary

type TraceSummary struct {
	TraceID     string   `json:"trace_id"`
	Service     string   `json:"service"`
	RootName    string   `json:"root_name"`
	Status      string   `json:"status"`
	DurationMs  float64  `json:"duration_ms"`
	SpanCount   int      `json:"span_count"`
	HasN1       bool     `json:"has_n1"`
	IssueKinds  []string `json:"issue_kinds"`
	StartUnixNs int64    `json:"start_unix_ns"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL