Documentation
¶
Index ¶
- func EnableWithMCP(config *Config) (*devtools.DevTools, error)
- func SanitizeInput(input string) string
- func ValidateResourceURI(uri string) error
- func ValidateToolParams(toolName string, params map[string]interface{}) error
- type AuthHandler
- type ClearEventLogParams
- type ClearResult
- type ClearStateHistoryParams
- type ComponentMatch
- type ComponentsResource
- type ComputedInfo
- type Config
- type EventsResource
- type ExportParams
- type ExportResult
- type FilterEventsParams
- type FilterEventsResult
- type FlushHandler
- type NotificationSender
- type PerformanceResource
- type PerformanceSummary
- type RateLimiter
- type RefInfo
- type SearchComponentsParams
- type SearchComponentsResult
- type Server
- func (s *Server) GetConfig() *Config
- func (s *Server) GetDevTools() *devtools.DevTools
- func (s *Server) GetHTTPAddr() string
- func (s *Server) GetHTTPPort() int
- func (s *Server) GetSDKServer() *mcp.Server
- func (s *Server) GetStore() *devtools.Store
- func (s *Server) RegisterClearEventLogTool() error
- func (s *Server) RegisterClearStateHistoryTool() error
- func (s *Server) RegisterComponentResource() error
- func (s *Server) RegisterComponentsResource() error
- func (s *Server) RegisterEventsResource() error
- func (s *Server) RegisterExportTool() error
- func (s *Server) RegisterFilterEventsTool() error
- func (s *Server) RegisterPerformanceResource() error
- func (s *Server) RegisterSearchComponentsTool() error
- func (s *Server) RegisterSetRefValueTool() error
- func (s *Server) RegisterStateResource() error
- func (s *Server) StartHTTPServer(ctx context.Context) error
- func (s *Server) StartStdioServer(ctx context.Context) error
- type SetRefResult
- type SetRefValueParams
- type StateChangeDetector
- func (d *StateChangeDetector) HandleComponentMount(componentID, componentName string)
- func (d *StateChangeDetector) HandleComponentUnmount(componentID, componentName string)
- func (d *StateChangeDetector) HandleEventEmit(eventName, componentID string, data interface{})
- func (d *StateChangeDetector) HandleRefChange(refID string, oldValue, newValue interface{})
- func (d *StateChangeDetector) Initialize(dt *devtools.DevTools) error
- func (d *StateChangeDetector) SetNotifier(notifier notificationSender)
- type StateResource
- type Subscription
- type SubscriptionManager
- func (sm *SubscriptionManager) GetSubscriptionCount(clientID string) int
- func (sm *SubscriptionManager) GetSubscriptions(clientID string) []*Subscription
- func (sm *SubscriptionManager) Subscribe(clientID, uri string, filters map[string]interface{}) error
- func (sm *SubscriptionManager) Unsubscribe(clientID, subscriptionID string) error
- func (sm *SubscriptionManager) UnsubscribeAll(clientID string) error
- type Throttler
- type TransportType
- type UpdateBatcher
- type UpdateNotification
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EnableWithMCP ¶
EnableWithMCP creates and enables the dev tools singleton with MCP server integration.
This function combines devtools.Enable() with MCP server initialization, providing a convenient way to start dev tools with AI-assisted debugging capabilities enabled.
The MCP server exposes devtools data via the Model Context Protocol, allowing AI agents to inspect components, state, events, and performance metrics in real-time.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
// Enable with stdio transport (default)
cfg := mcp.DefaultMCPConfig()
dt, err := mcp.EnableWithMCP(cfg)
if err != nil {
log.Fatal(err)
}
// Enable with HTTP transport
cfg := &mcp.Config{
Transport: mcp.MCPTransportHTTP,
HTTPPort: 8765,
EnableAuth: true,
AuthToken: "secret-token",
}
dt, err := mcp.EnableWithMCP(cfg)
if err != nil {
log.Fatal(err)
}
Parameters:
- config: MCP server configuration
Returns:
- *devtools.DevTools: The singleton dev tools instance with MCP enabled
- error: Configuration validation error, or MCP server creation error
func SanitizeInput ¶
SanitizeInput removes dangerous characters from user input.
This function removes or replaces:
- SQL injection characters: ; ' "
- Command injection: ` $ ( ) | & < >
- Path traversal: ../
- Null bytes: \x00
- Control characters (except space and tab)
Note: This is defense-in-depth. Primary defense is proper parameterization and validation. Use this for logging and display purposes.
Example:
input := "'; DROP TABLE users; --" safe := SanitizeInput(input) // "' DROP TABLE users --"
Parameters:
- input: The string to sanitize
Returns:
- string: Sanitized string with dangerous characters removed
func ValidateResourceURI ¶
func ValidateToolParams ¶
ValidateToolParams validates parameters for MCP tool calls.
This function performs tool-specific validation to prevent injection attacks:
- export_session: format, destination path, include sections
- search_components: query, fields, max_results
- filter_events: event_names, source_ids, limit
- set_ref_value: ref_id, new_value
- clear_state_history: no params
- clear_event_log: no params
Security Features:
- SQL injection prevention
- Command injection prevention
- Path traversal prevention
- Parameter type validation
- Range validation
Example:
params := map[string]interface{}{
"format": "json",
"destination": "/tmp/export.json",
}
err := ValidateToolParams("export_session", params)
Parameters:
- toolName: The name of the tool being called
- params: The parameters passed to the tool
Returns:
- error: nil if valid, descriptive error otherwise
Types ¶
type AuthHandler ¶
type AuthHandler struct {
// contains filtered or unexported fields
}
AuthHandler provides bearer token authentication for HTTP transport.
The handler implements HTTP middleware that validates bearer tokens in the Authorization header. It uses constant-time comparison to prevent timing attacks and never logs or exposes tokens in error messages.
Security Features:
- Constant-time token comparison (timing attack resistant)
- Token sanitization in error messages
- Configurable enable/disable
- Thread-safe operation
Example:
auth, err := NewAuthHandler("secret-token-123", true)
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.Handle("/api", auth.Middleware(apiHandler))
http.ListenAndServe(":8080", mux)
func NewAuthHandler ¶
func NewAuthHandler(token string, enabled bool) (*AuthHandler, error)
NewAuthHandler creates a new authentication handler.
The handler validates bearer tokens in the Authorization header using constant-time comparison to prevent timing attacks. When enabled is false, all requests are allowed through without authentication.
Parameters:
- token: The bearer token to validate against (required if enabled is true)
- enabled: Whether authentication is enabled
Returns:
- *AuthHandler: The configured authentication handler
- error: Validation error if token is empty when enabled is true
Example:
// Enable authentication
auth, err := NewAuthHandler("my-secret-token", true)
if err != nil {
log.Fatal(err)
}
// Disable authentication (for development)
auth, err := NewAuthHandler("", false)
func (*AuthHandler) Middleware ¶
func (a *AuthHandler) Middleware(next http.Handler) http.Handler
Middleware wraps an HTTP handler with bearer token authentication.
The middleware validates the Authorization header and only calls the next handler if authentication succeeds. When authentication is disabled, all requests pass through without validation.
Authentication Flow:
- If auth is disabled, call next handler immediately
- Extract Authorization header
- Validate "Bearer <token>" format
- Compare token using constant-time comparison
- Return 401 Unauthorized on failure
- Call next handler on success
Security:
- Uses subtle.ConstantTimeCompare to prevent timing attacks
- Never includes tokens in error messages
- Returns generic error messages to prevent information leakage
Parameters:
- next: The HTTP handler to call if authentication succeeds
Returns:
- http.Handler: The wrapped handler with authentication
Example:
auth, _ := NewAuthHandler("secret", true)
protectedHandler := auth.Middleware(myHandler)
http.Handle("/api", protectedHandler)
type ClearEventLogParams ¶
type ClearEventLogParams struct {
// Confirm is required to be true to prevent accidental deletion
Confirm bool `json:"confirm"`
}
ClearEventLogParams defines the parameters for the clear_event_log tool.
This structure is used by AI agents to specify options when clearing the event log.
Example:
{
"confirm": true
}
type ClearResult ¶
type ClearResult struct {
// Cleared is the number of items that were cleared
Cleared int `json:"cleared"`
// Timestamp is when the clear operation completed
Timestamp time.Time `json:"timestamp"`
}
ClearResult contains the result of a clear operation.
This structure is returned to AI agents after a successful clear operation.
Example:
{
"cleared": 150,
"timestamp": "2025-01-13T14:30:22Z"
}
type ClearStateHistoryParams ¶
type ClearStateHistoryParams struct {
// Confirm is required to be true to prevent accidental deletion
Confirm bool `json:"confirm"`
}
ClearStateHistoryParams defines the parameters for the clear_state_history tool.
This structure is used by AI agents to specify options when clearing state history.
Example:
{
"confirm": true
}
type ComponentMatch ¶
type ComponentMatch struct {
// ID is the component's unique identifier
ID string `json:"id"`
// Name is the component's name
Name string `json:"name"`
// Type is the component's type
Type string `json:"type"`
// Status is the component's lifecycle status
Status string `json:"status"`
// MatchScore is a relevance score (0.0 to 1.0)
MatchScore float64 `json:"match_score"`
// MatchedField is which field matched the query
MatchedField string `json:"matched_field"`
}
ComponentMatch represents a single component that matched the search.
type ComponentsResource ¶
type ComponentsResource struct {
// Roots contains all root-level components (components without parents)
Roots []*devtools.ComponentSnapshot `json:"roots"`
// TotalCount is the total number of components in the tree
TotalCount int `json:"total_count"`
// Timestamp indicates when this snapshot was captured
Timestamp time.Time `json:"timestamp"`
}
ComponentsResource represents the full component tree resource.
This structure is returned by the bubblyui://components resource and provides a snapshot of all root components in the application.
JSON Schema:
{
"roots": [ComponentSnapshot],
"total_count": int,
"timestamp": string (ISO 8601)
}
type ComputedInfo ¶
type ComputedInfo struct {
// ID is the unique identifier of the computed value
ID string `json:"id"`
// Name is the variable name
Name string `json:"name"`
// Type is the Go type
Type string `json:"type"`
// Value is the current computed value
Value interface{} `json:"value"`
// Dependencies lists the ref IDs this computed value depends on
Dependencies []string `json:"dependencies,omitempty"`
}
ComputedInfo provides information about computed values (future enhancement).
This structure will be populated in future versions when computed value tracking is fully implemented in the devtools system.
type Config ¶
type Config struct {
// Transport specifies which transport(s) to enable
// Can be MCPTransportStdio, MCPTransportHTTP, or both (bitwise OR)
Transport TransportType
// HTTPPort is the port for HTTP transport (1-65535)
// Only used when MCPTransportHTTP is enabled
HTTPPort int
// HTTPHost is the host to bind HTTP server to
// Use "localhost" for local-only, "0.0.0.0" for all interfaces
// Only used when MCPTransportHTTP is enabled
HTTPHost string
// WriteEnabled allows state modification tools (set_ref_value, etc.)
// DANGER: Enable only for testing, not production debugging
// Default: false (read-only access)
WriteEnabled bool
// MaxClients limits concurrent client connections
// Prevents resource exhaustion from too many clients
// Default: 5
MaxClients int
// SubscriptionThrottle is minimum time between subscription updates
// Prevents overwhelming clients with high-frequency changes
// Default: 100ms
SubscriptionThrottle time.Duration
// RateLimit is maximum requests per second per client
// Prevents DoS attacks and resource exhaustion
// Default: 60 req/sec
RateLimit int
// EnableAuth requires bearer token authentication for HTTP transport
// Recommended for any non-localhost HTTP access
// Default: false
EnableAuth bool
// AuthToken is the bearer token for HTTP authentication
// Required when EnableAuth is true
// Should be a strong random string (e.g., UUID)
AuthToken string
// SanitizeExports automatically removes PII from exported data
// Recommended to keep enabled for security
// Default: true
SanitizeExports bool
}
Config holds configuration options for the MCP server.
Configuration controls transport selection, security settings, performance tuning, and write operation permissions. All fields have sensible defaults via DefaultMCPConfig().
Thread Safety:
Config instances are not thread-safe. Create separate instances for concurrent use or protect access with a mutex.
Example:
// Use defaults (stdio transport, read-only)
cfg := mcp.DefaultMCPConfig()
// Enable HTTP transport with auth
cfg := &mcp.Config{
Transport: mcp.MCPTransportHTTP,
HTTPPort: 8765,
HTTPHost: "localhost",
EnableAuth: true,
AuthToken: "secret-token",
}
// Validate before use
if err := cfg.Validate(); err != nil {
log.Fatal(err)
}
func DefaultMCPConfig ¶
func DefaultMCPConfig() *Config
DefaultMCPConfig returns an Config with sensible default values.
The defaults are optimized for local development with stdio transport:
- Stdio transport only (no network exposure)
- HTTP port 8765 (if HTTP enabled later)
- Localhost binding (no remote access)
- Read-only access (no state modification)
- 5 max concurrent clients
- 100ms subscription throttle
- 60 requests/second rate limit
- No authentication (stdio is local-only)
- Sanitization enabled
These defaults can be modified as needed for specific use cases.
Example:
cfg := mcp.DefaultMCPConfig()
cfg.Transport = mcp.MCPTransportHTTP // Enable HTTP
cfg.EnableAuth = true // Require auth
cfg.AuthToken = "secret-token" // Set token
if err := cfg.Validate(); err != nil {
log.Fatal(err)
}
Returns:
- *Config: A new config with default values
func (*Config) Validate ¶
Validate checks that the configuration values are valid.
This method verifies that:
- HTTP port is in valid range (1-65535) when HTTP transport enabled
- HTTP host is not empty when HTTP transport enabled
- Max clients is positive
- Rate limit is positive
- Subscription throttle is non-negative
- Auth token is provided when auth is enabled
Call this after creating or modifying config to ensure validity.
Example:
cfg := mcp.DefaultMCPConfig()
cfg.HTTPPort = 0 // Invalid
if err := cfg.Validate(); err != nil {
log.Printf("Invalid config: %v", err)
}
Returns:
- error: Validation error, or nil if config is valid
type EventsResource ¶
type EventsResource struct {
// Events contains all event records
Events []devtools.EventRecord `json:"events"`
// TotalCount is the total number of events
TotalCount int `json:"total_count"`
// Timestamp indicates when this snapshot was captured
Timestamp time.Time `json:"timestamp"`
}
EventsResource represents the events log resource.
This structure is returned by the bubblyui://events/log resource and provides a snapshot of all events that occurred in the application.
JSON Schema:
{
"events": [EventRecord],
"total_count": int,
"timestamp": string (ISO 8601)
}
type ExportParams ¶
type ExportParams struct {
// Format specifies the export format: "json", "yaml", or "msgpack"
Format string `json:"format"`
// Compress enables gzip compression of the export file
Compress bool `json:"compress"`
// Sanitize enables redaction of sensitive data
Sanitize bool `json:"sanitize"`
// Include specifies which data sections to include
// Valid values: "components", "state", "events", "performance"
Include []string `json:"include"`
// Destination is the file path where the export will be saved
// Use "stdout" to write to standard output (not recommended for large exports)
Destination string `json:"destination"`
}
ExportParams defines the parameters for the export_session tool.
This structure is used by AI agents to specify export options when calling the export_session tool via MCP.
Example:
{
"format": "json",
"compress": true,
"sanitize": true,
"include": ["components", "state", "events"],
"destination": "/tmp/debug-session.json.gz"
}
type ExportResult ¶
type ExportResult struct {
// Path is the absolute path to the exported file
Path string `json:"path"`
// Size is the file size in bytes
Size int64 `json:"size"`
// Format is the export format used
Format string `json:"format"`
// Compressed indicates if the file is gzip compressed
Compressed bool `json:"compressed"`
// Timestamp is when the export was created
Timestamp time.Time `json:"timestamp"`
}
ExportResult contains the result of an export operation.
This structure is returned to AI agents after a successful export.
Example:
{
"path": "/tmp/debug-session.json.gz",
"size": 245678,
"format": "json",
"compressed": true,
"timestamp": "2025-01-13T14:30:22Z"
}
type FilterEventsParams ¶
type FilterEventsParams struct {
// EventNames filters by event name (empty = all events)
EventNames []string `json:"event_names"`
// SourceIDs filters by source component ID (empty = all sources)
SourceIDs []string `json:"source_ids"`
// StartTime filters events after this time (optional)
StartTime *time.Time `json:"start_time"`
// EndTime filters events before this time (optional)
EndTime *time.Time `json:"end_time"`
// Limit limits the number of results returned (default: 100)
Limit int `json:"limit"`
}
FilterEventsParams defines the parameters for the filter_events tool.
This structure is used by AI agents to specify filtering criteria when querying the event log.
Example:
{
"event_names": ["click", "submit"],
"source_ids": ["comp-1"],
"start_time": "2025-01-13T14:00:00Z",
"end_time": "2025-01-13T15:00:00Z",
"limit": 100
}
type FilterEventsResult ¶
type FilterEventsResult struct {
// Events contains the filtered events
Events []devtools.EventRecord `json:"events"`
// TotalMatches is the number of events that matched the filter
TotalMatches int `json:"total_matches"`
// FilteredFrom is the total number of events before filtering
FilteredFrom int `json:"filtered_from"`
// Timestamp is when the filter was performed
Timestamp time.Time `json:"timestamp"`
}
FilterEventsResult contains the result of an event filtering operation.
This structure is returned to AI agents after a successful filter.
Example:
{
"events": [
{
"id": "event-1",
"name": "click",
"source_id": "comp-1",
"timestamp": "2025-01-13T14:30:22Z"
}
],
"total_matches": 1,
"filtered_from": 150,
"timestamp": "2025-01-13T14:30:22Z"
}
type FlushHandler ¶
type FlushHandler func(clientID string, updates []UpdateNotification)
FlushHandler is called when a batch of updates is ready to be sent.
The handler receives the client ID and a slice of updates to send. It should send the updates to the client via the MCP protocol.
Thread Safety:
Handlers may be called concurrently for different clients.
Example:
handler := func(clientID string, updates []UpdateNotification) {
for _, update := range updates {
// Send update to client via MCP
server.SendNotification(clientID, update)
}
}
type NotificationSender ¶
type NotificationSender struct {
// contains filtered or unexported fields
}
NotificationSender sends resource update notifications to MCP clients.
It integrates with the UpdateBatcher to queue notifications for batching and throttling, preventing client overload from high-frequency updates.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Lifecycle:
- NewNotificationSender() - Creates the sender with a batcher
- QueueNotification() - Queue notifications for batching
- ... Batcher handles flushing and sending ...
Example:
batcher, err := NewUpdateBatcher(100*time.Millisecond, 10)
if err != nil {
log.Fatal(err)
}
notifier, err := NewNotificationSender(batcher)
if err != nil {
log.Fatal(err)
}
// Queue a notification
notifier.QueueNotification("client-1", "bubblyui://state/refs", map[string]interface{}{
"ref_id": "count-ref",
"value": 42,
})
func NewNotificationSender ¶
func NewNotificationSender(batcher *UpdateBatcher) (*NotificationSender, error)
NewNotificationSender creates a new notification sender.
The sender uses the provided batcher to queue notifications for batching and throttling. The batcher must have a flush handler configured to actually send the notifications.
Thread Safety:
Safe to call concurrently (creates new instance each time).
Example:
batcher, err := NewUpdateBatcher(100*time.Millisecond, 10)
if err != nil {
log.Fatal(err)
}
notifier, err := NewNotificationSender(batcher)
if err != nil {
log.Fatal(err)
}
Parameters:
- batcher: The update batcher to use for queuing notifications
Returns:
- *NotificationSender: A new notification sender instance
- error: Validation error, or nil on success
func (*NotificationSender) QueueNotification ¶
func (n *NotificationSender) QueueNotification(clientID, uri string, data map[string]interface{})
QueueNotification queues a notification for batching and sending.
The notification will be added to the batcher, which will flush it either after the flush interval or when the batch size is reached.
This method does not block - it queues the notification and returns immediately. The actual sending is handled asynchronously by the batcher.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
notifier.QueueNotification("client-1", "bubblyui://state/refs", map[string]interface{}{
"ref_id": "count-ref",
"value": 42,
})
Parameters:
- clientID: The client to send the notification to
- uri: The resource URI that changed
- data: The notification payload (can be nil)
type PerformanceResource ¶
type PerformanceResource struct {
// Components maps component ID to performance metrics
Components map[string]*devtools.ComponentPerformance `json:"components"`
// Summary provides aggregated performance statistics
Summary *PerformanceSummary `json:"summary"`
// Timestamp indicates when this snapshot was captured
Timestamp time.Time `json:"timestamp"`
}
PerformanceResource represents the performance metrics resource.
This structure is returned by the bubblyui://performance/metrics resource and provides a comprehensive view of component performance metrics.
JSON Schema:
{
"components": {
"component-id": ComponentPerformance
},
"summary": PerformanceSummary,
"timestamp": string (ISO 8601)
}
type PerformanceSummary ¶
type PerformanceSummary struct {
// TotalComponents is the total number of components tracked
TotalComponents int `json:"total_components"`
// TotalRenders is the total number of renders across all components
TotalRenders int64 `json:"total_renders"`
// SlowestComponent is the ID of the component with the slowest max render time
SlowestComponent string `json:"slowest_component,omitempty"`
// SlowestRenderTime is the slowest max render time
SlowestRenderTime time.Duration `json:"slowest_render_time,omitempty"`
// FastestComponent is the ID of the component with the fastest min render time
FastestComponent string `json:"fastest_component,omitempty"`
// FastestRenderTime is the fastest min render time
FastestRenderTime time.Duration `json:"fastest_render_time,omitempty"`
// MostRenderedComponent is the ID of the component with the most renders
MostRenderedComponent string `json:"most_rendered_component,omitempty"`
// MostRenderedCount is the number of renders for the most rendered component
MostRenderedCount int64 `json:"most_rendered_count,omitempty"`
}
PerformanceSummary provides aggregated performance statistics.
This structure summarizes performance metrics across all components, identifying the slowest, fastest, and most rendered components.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter implements per-client rate limiting for HTTP requests.
It uses the token bucket algorithm via golang.org/x/time/rate to enforce request rate limits on a per-client basis. Each client (identified by IP) gets their own rate limiter instance, ensuring fair resource allocation and preventing DoS attacks.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Example:
rl, err := NewRateLimiter(10) // 10 requests per second per client
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.Handle("/api", rl.Middleware(apiHandler))
func NewRateLimiter ¶
func NewRateLimiter(requestsPerSecond int) (*RateLimiter, error)
NewRateLimiter creates a new rate limiter with the specified requests per second.
The rate limiter uses a token bucket algorithm where each client can make up to `requestsPerSecond` requests per second, with a burst capacity of 2x the rate limit to allow for bursty traffic patterns.
Parameters:
- requestsPerSecond: Maximum requests per second per client (must be > 0)
Returns:
- *RateLimiter: Configured rate limiter instance
- error: Validation error if requestsPerSecond is invalid
Example:
rl, err := NewRateLimiter(100) // 100 req/s per client
if err != nil {
log.Fatal(err)
}
func (*RateLimiter) Middleware ¶
func (rl *RateLimiter) Middleware(next http.Handler) http.Handler
Middleware wraps an HTTP handler with rate limiting.
Requests exceeding the rate limit receive a 429 Too Many Requests response. The client IP is extracted from the request (supporting X-Forwarded-For and X-Real-IP headers for proxy scenarios).
Parameters:
- next: The HTTP handler to wrap
Returns:
- http.Handler: Wrapped handler with rate limiting
Example:
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
limited := rl.Middleware(handler)
http.ListenAndServe(":8080", limited)
type RefInfo ¶
type RefInfo struct {
// ID is the unique identifier of the ref
ID string `json:"id"`
// Name is the variable name of the ref
Name string `json:"name"`
// Type is the Go type of the ref's value
Type string `json:"type"`
// Value is the current value of the ref
Value interface{} `json:"value"`
// OwnerID is the ID of the component that owns this ref
OwnerID string `json:"owner_id"`
// OwnerName is the name of the component that owns this ref
OwnerName string `json:"owner_name,omitempty"`
// Watchers is the number of active watchers on this ref
Watchers int `json:"watchers"`
}
RefInfo provides detailed information about a reactive reference.
This structure includes the ref's identity, value, type, ownership, and watcher information for debugging and analysis.
type SearchComponentsParams ¶
type SearchComponentsParams struct {
// Query is the search term to match against component fields
Query string `json:"query"`
// Fields specifies which fields to search in: "name", "type", "id"
// If empty, searches all fields
Fields []string `json:"fields"`
// MaxResults limits the number of results returned (default: 50)
MaxResults int `json:"max_results"`
}
SearchComponentsParams defines the parameters for the search_components tool.
This structure is used by AI agents to specify search criteria when searching for components in the component tree.
Example:
{
"query": "counter",
"fields": ["name", "type"],
"max_results": 10
}
type SearchComponentsResult ¶
type SearchComponentsResult struct {
// Matches contains the components that matched the search
Matches []ComponentMatch `json:"matches"`
// TotalMatches is the total number of matches found
TotalMatches int `json:"total_matches"`
// Query is the search query that was used
Query string `json:"query"`
// Timestamp is when the search was performed
Timestamp time.Time `json:"timestamp"`
}
SearchComponentsResult contains the result of a component search operation.
This structure is returned to AI agents after a successful search.
Example:
{
"matches": [
{
"id": "comp-1",
"name": "Counter",
"type": "Counter",
"match_score": 1.0,
"matched_field": "name"
}
],
"total_matches": 1,
"query": "counter",
"timestamp": "2025-01-13T14:30:22Z"
}
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the main MCP server instance that exposes BubblyUI devtools data and capabilities to AI agents via the Model Context Protocol.
The server provides:
- Resources: Read-only access to component tree, state, events, performance
- Tools: Actions like export, search, clear history, state modification
- Subscriptions: Real-time updates on component/state/event changes
Thread Safety:
All methods are thread-safe and can be called concurrently.
Lifecycle:
- NewMCPServer() - Creates and initializes the server
- StartStdioServer() or StartHTTPServer() - Starts transport (Task 1.2/1.3)
- ... AI agents connect and interact ...
- Shutdown() - Graceful cleanup (Task 7.1)
Example:
dt := devtools.Enable()
cfg := mcp.DefaultMCPConfig()
server, err := mcp.NewMCPServer(cfg, dt)
if err != nil {
log.Fatal(err)
}
// Start transport (Task 1.2)
if err := server.StartStdioServer(ctx); err != nil {
log.Fatal(err)
}
func NewMCPServer ¶
NewMCPServer creates and initializes a new MCP server.
This function:
- Validates the configuration
- Validates the devtools instance
- Creates the MCP SDK server with proper implementation details
- Stores references to devtools and store for resource handlers
The server is created but not started. Call StartStdioServer() or StartHTTPServer() to begin accepting connections (Task 1.2/1.3).
Thread Safety:
Safe to call concurrently (creates new instance each time).
Example:
dt := devtools.Enable()
cfg := mcp.DefaultMCPConfig()
server, err := mcp.NewMCPServer(cfg, dt)
if err != nil {
log.Fatalf("Failed to create MCP server: %v", err)
}
Parameters:
- config: MCP server configuration (transport, security, performance)
- dt: DevTools instance to expose via MCP
Returns:
- *Server: Initialized server ready to start transport
- error: Validation error, or nil on success
func (*Server) GetConfig ¶
GetConfig returns the server's configuration.
The returned config is the same instance passed to NewMCPServer(). Do not modify the returned config - it's shared.
Thread Safety:
Safe to call concurrently.
Example:
cfg := server.GetConfig()
fmt.Printf("Transport: %s\n", cfg.Transport)
Returns:
- *Config: The server's configuration
func (*Server) GetDevTools ¶
GetDevTools returns the DevTools instance.
This provides access to the full DevTools API for advanced use cases.
Thread Safety:
Safe to call concurrently.
Example:
dt := server.GetDevTools()
if dt.IsEnabled() {
fmt.Println("DevTools active")
}
Returns:
- *devtools.DevTools: The DevTools instance
func (*Server) GetHTTPAddr ¶
GetHTTPAddr returns the actual address the HTTP server is listening on. This is useful when using port 0 (random port assignment).
Returns empty string if HTTP server is not running.
Note: This is a helper method for testing. In production, the port should be configured explicitly.
func (*Server) GetHTTPPort ¶
GetHTTPPort returns the actual port the HTTP server is listening on. Returns 0 if HTTP server is not running or port is not yet assigned.
This is useful for testing with random port assignment (port 0).
func (*Server) GetSDKServer ¶
GetSDKServer returns the underlying MCP SDK server instance.
This is primarily for testing purposes to allow integration tests to connect the server with custom transports (e.g., in-memory).
Thread Safety:
Safe to call concurrently.
Example (for testing):
mcpServer := mcp.NewMCPServer(config, dt) sdkServer := mcpServer.GetSDKServer() session, _ := sdkServer.Connect(ctx, inMemoryTransport, nil)
Returns:
- *mcp.Server: The MCP SDK server instance
func (*Server) GetStore ¶
GetStore returns the Store instance.
This provides direct access to collected debug data for resource handlers. Used internally by resource/tool implementations (Task 2.x, 3.x).
Thread Safety:
Safe to call concurrently.
Example:
store := server.GetStore()
components := store.GetAllComponents()
fmt.Printf("Tracking %d components\n", len(components))
Returns:
- *devtools.Store: The Store instance
func (*Server) RegisterClearEventLogTool ¶
RegisterClearEventLogTool registers the clear_event_log tool with the MCP server.
This tool allows AI agents to clear the event log. It requires explicit confirmation to prevent accidental data loss.
The tool is registered with JSON Schema validation for parameters.
Thread Safety:
Safe to call concurrently. Uses MCP SDK's thread-safe registration.
Example:
server, _ := NewMCPServer(cfg, dt)
err := server.RegisterClearEventLogTool()
if err != nil {
log.Fatalf("Failed to register clear event log tool: %v", err)
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterClearStateHistoryTool ¶
RegisterClearStateHistoryTool registers the clear_state_history tool with the MCP server.
This tool allows AI agents to clear the state change history. It requires explicit confirmation to prevent accidental data loss.
The tool is registered with JSON Schema validation for parameters.
Thread Safety:
Safe to call concurrently. Uses MCP SDK's thread-safe registration.
Example:
server, _ := NewMCPServer(cfg, dt)
err := server.RegisterClearStateHistoryTool()
if err != nil {
log.Fatalf("Failed to register clear state history tool: %v", err)
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterComponentResource ¶
RegisterComponentResource registers the individual component resource template.
This resource template provides access to individual components by ID via the URI pattern: bubblyui://components/{id}
The resource returns a ComponentSnapshot JSON structure for the requested component.
Thread Safety:
Safe to call concurrently. Resource reads use Store's thread-safe methods.
Example Request:
URI: bubblyui://components/comp-123
Example Response:
{
"id": "comp-123",
"name": "Counter",
"type": "Counter",
"state": {"count": 42},
"refs": [...],
"props": {...},
"children": [...]
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterComponentsResource ¶
RegisterComponentsResource registers the full component tree resource.
This resource provides access to all root components and their children via the URI: bubblyui://components
The resource returns a ComponentsResource JSON structure containing:
- roots: Array of root component snapshots
- total_count: Total number of components tracked
- timestamp: When the snapshot was captured
Thread Safety:
Safe to call concurrently. Resource reads use Store's thread-safe methods.
Example Response:
{
"roots": [
{
"id": "comp-1",
"name": "App",
"type": "App",
"state": {},
"refs": [],
"props": {},
"children": [...]
}
],
"total_count": 5,
"timestamp": "2025-01-13T14:30:00Z"
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterEventsResource ¶
RegisterEventsResource registers the events resources.
This method registers two resources:
- bubblyui://events/log - All event records
- bubblyui://events/{id} - Individual event by ID
Thread Safety:
Safe to call concurrently. Resource reads use Store's thread-safe methods.
Example Response (bubblyui://events/log):
{
"events": [
{
"seq_id": 1,
"id": "event-1",
"name": "click",
"source_id": "comp-1",
"target_id": "comp-2",
"payload": {"button": "submit"},
"timestamp": "2025-01-13T14:30:00Z",
"duration": 5000000
}
],
"total_count": 1,
"timestamp": "2025-01-13T14:30:00Z"
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterExportTool ¶
RegisterExportTool registers the export_session tool with the MCP server.
This tool allows AI agents to export debug data with compression and sanitization. It supports multiple formats (JSON, YAML, MessagePack) and selective inclusion of data sections.
The tool is registered with JSON Schema validation for parameters.
Thread Safety:
Safe to call concurrently. Uses MCP SDK's thread-safe registration.
Example:
server, _ := NewMCPServer(cfg, dt)
err := server.RegisterExportTool()
if err != nil {
log.Fatalf("Failed to register export tool: %v", err)
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterFilterEventsTool ¶
RegisterFilterEventsTool registers the filter_events tool with the MCP server.
This tool allows AI agents to filter events by name, source, and time range. Results can be limited to prevent overwhelming responses.
The tool is registered with JSON Schema validation for parameters.
Thread Safety:
Safe to call concurrently. Uses MCP SDK's thread-safe registration.
Example:
server, _ := NewMCPServer(cfg, dt)
err := server.RegisterFilterEventsTool()
if err != nil {
log.Fatalf("Failed to register filter events tool: %v", err)
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterPerformanceResource ¶
RegisterPerformanceResource registers the performance resources.
This method registers the following resource:
- bubblyui://performance/metrics - All performance metrics with summary
Thread Safety:
Safe to call concurrently. Resource reads use Store's thread-safe methods.
Example Response (bubblyui://performance/metrics):
{
"components": {
"comp-1": {
"component_id": "comp-1",
"component_name": "Counter",
"render_count": 42,
"total_render_time": "210ms",
"avg_render_time": "5ms",
"max_render_time": "15ms",
"min_render_time": "2ms",
"memory_usage": 1024,
"last_update": "2025-01-13T14:30:00Z"
}
},
"summary": {
"total_components": 3,
"total_renders": 150,
"slowest_component": "comp-2",
"slowest_render_time": "50ms",
"fastest_component": "comp-1",
"fastest_render_time": "2ms",
"most_rendered_component": "comp-1",
"most_rendered_count": 42
},
"timestamp": "2025-01-13T14:30:00Z"
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterSearchComponentsTool ¶
RegisterSearchComponentsTool registers the search_components tool with the MCP server.
This tool allows AI agents to search for components by name, type, or ID with fuzzy matching support. Results are ranked by relevance.
The tool is registered with JSON Schema validation for parameters.
Thread Safety:
Safe to call concurrently. Uses MCP SDK's thread-safe registration.
Example:
server, _ := NewMCPServer(cfg, dt)
err := server.RegisterSearchComponentsTool()
if err != nil {
log.Fatalf("Failed to register search components tool: %v", err)
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) RegisterSetRefValueTool ¶
RegisterSetRefValueTool registers the set_ref_value tool with the MCP server.
This tool allows AI agents to modify ref values for testing purposes. It is a WRITE operation and only registers if WriteEnabled=true in Config.
The tool performs type checking to prevent invalid value assignments and supports dry-run mode for validation without side effects.
Thread Safety:
Safe to call concurrently. Uses MCP SDK's thread-safe registration.
Example:
server, _ := NewMCPServer(cfg, dt)
err := server.RegisterSetRefValueTool()
if err != nil {
log.Fatalf("Failed to register set ref value tool: %v", err)
}
Returns:
- error: nil on success, error if WriteEnabled=false or registration fails
func (*Server) RegisterStateResource ¶
RegisterStateResource registers the state resources.
This method registers two resources:
- bubblyui://state/refs - All reactive references
- bubblyui://state/history - State change history
Thread Safety:
Safe to call concurrently. Resource reads use Store's thread-safe methods.
Example Response (bubblyui://state/refs):
{
"refs": [
{
"id": "ref-1",
"name": "count",
"type": "int",
"value": 42,
"owner_id": "comp-1",
"owner_name": "Counter",
"watchers": 2
}
],
"computed": [],
"timestamp": "2025-01-13T14:30:00Z"
}
Returns:
- error: nil on success, error describing the failure otherwise
func (*Server) StartHTTPServer ¶
StartHTTPServer starts the MCP server using HTTP/SSE transport.
This method enables IDE integration and remote debugging by serving MCP over HTTP with Server-Sent Events for real-time updates. The server will:
- Create a StreamableHTTPHandler for MCP sessions
- Set up HTTP endpoints (/mcp for MCP protocol, /health for health checks)
- Listen on the configured host and port
- Support multiple concurrent client connections
- Handle graceful shutdown on context cancellation
The method starts the HTTP server in a goroutine and blocks until:
- Context is canceled (graceful shutdown)
- Server encounters a fatal error
Thread Safety:
Safe to call concurrently (uses internal mutex for state access).
Error Handling:
All errors are wrapped with context. Panics are recovered and reported to the observability system. MCP failures never crash the host application.
Example:
dt := devtools.Enable()
cfg := mcp.DefaultMCPConfig()
cfg.Transport = mcp.MCPTransportHTTP
cfg.HTTPPort = 8765
cfg.HTTPHost = "localhost"
server, err := mcp.NewMCPServer(cfg, dt)
if err != nil {
log.Fatal(err)
}
// Start HTTP server (blocks until context canceled)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := server.StartHTTPServer(ctx); err != nil {
log.Printf("HTTP server error: %v", err)
}
Parameters:
- ctx: Context for cancellation and timeout control
Returns:
- error: Configuration error, bind error, or nil on clean shutdown
func (*Server) StartStdioServer ¶
StartStdioServer starts the MCP server using stdio transport.
This method enables local CLI integration by communicating over stdin/stdout using newline-delimited JSON-RPC messages. The server will:
- Create a stdio transport (uses os.Stdin/os.Stdout)
- Connect to the MCP SDK server
- Complete the initialization handshake with the client
- Negotiate protocol version (2025-06-18)
- Declare server capabilities (resources, tools, subscriptions)
- Block until the client disconnects or context is canceled
The method blocks until one of the following occurs:
- Client disconnects gracefully
- Context is canceled
- Transport error occurs
Thread Safety:
Safe to call concurrently (uses internal mutex for state access).
Error Handling:
All errors are wrapped with context. Panics are recovered and reported to the observability system. MCP failures never crash the host application.
Example:
dt := devtools.Enable()
cfg := mcp.DefaultMCPConfig()
server, err := mcp.NewMCPServer(cfg, dt)
if err != nil {
log.Fatal(err)
}
// Start stdio server (blocks until client disconnects)
ctx := context.Background()
if err := server.StartStdioServer(ctx); err != nil {
log.Printf("Stdio server error: %v", err)
}
Parameters:
- ctx: Context for cancellation and timeout control
Returns:
- error: Connection error, session error, or nil on clean shutdown
type SetRefResult ¶
type SetRefResult struct {
// RefID is the ref that was modified
RefID string `json:"ref_id"`
// OldValue is the value before the change
OldValue interface{} `json:"old_value"`
// NewValue is the value after the change
NewValue interface{} `json:"new_value"`
// OwnerID is the component that owns this ref
OwnerID string `json:"owner_id"`
// Timestamp is when the operation was performed
Timestamp time.Time `json:"timestamp"`
// DryRun indicates if this was a validation-only operation
DryRun bool `json:"dry_run"`
// TypeMatch indicates if the new value type matches the old value type
TypeMatch bool `json:"type_match"`
}
SetRefResult contains the result of a set_ref_value operation.
This structure is returned to AI agents after a successful (or dry-run) operation.
Example:
{
"ref_id": "ref-counter-123",
"old_value": 41,
"new_value": 42,
"owner_id": "comp-main-456",
"timestamp": "2025-01-13T14:30:22Z",
"dry_run": false,
"type_match": true
}
type SetRefValueParams ¶
type SetRefValueParams struct {
// RefID is the unique identifier of the ref to modify
RefID string `json:"ref_id"`
// NewValue is the new value to set (type must match ref's current type)
NewValue interface{} `json:"new_value"`
// DryRun validates the operation without applying changes
// Useful for checking if a value change would succeed
DryRun bool `json:"dry_run"`
}
SetRefValueParams defines the parameters for the set_ref_value tool.
This structure is used by AI agents to modify ref values for testing purposes. This is a WRITE operation and requires WriteEnabled=true in Config.
Example:
{
"ref_id": "ref-counter-123",
"new_value": 42,
"dry_run": false
}
type StateChangeDetector ¶
type StateChangeDetector struct {
// contains filtered or unexported fields
}
StateChangeDetector hooks into DevTools to detect changes for subscriptions.
It monitors ref changes, component lifecycle events, and event emissions, then notifies subscribed clients when changes match their subscription filters.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Lifecycle:
- NewStateChangeDetector() - Creates the detector
- Initialize() - Hooks into DevTools
- ... DevTools fires hooks as changes occur ...
- HandleRefChange/HandleComponentMount/HandleEventEmit - Process changes
- ... Notifications sent to subscribed clients ...
Example:
sm := NewSubscriptionManager(50)
detector := NewStateChangeDetector(sm)
err := detector.Initialize(devtools.Enable())
if err != nil {
log.Printf("Failed to initialize detector: %v", err)
}
func NewStateChangeDetector ¶
func NewStateChangeDetector(subscriptionMgr *SubscriptionManager) *StateChangeDetector
NewStateChangeDetector creates a new state change detector.
The detector is created but not yet hooked into DevTools. Call Initialize() to register the hooks.
Thread Safety:
Safe to call concurrently (creates new instance each time).
Example:
sm := NewSubscriptionManager(50) detector := NewStateChangeDetector(sm)
Parameters:
- subscriptionMgr: The subscription manager to use for tracking subscriptions
Returns:
- *StateChangeDetector: A new change detector instance
func (*StateChangeDetector) HandleComponentMount ¶
func (d *StateChangeDetector) HandleComponentMount(componentID, componentName string)
HandleComponentMount processes a component mount event and notifies subscribed clients.
This method is called when a component is mounted in the application. It checks all active subscriptions for the components resource and notifies clients whose filters match the mounted component.
The notification includes:
- component_id: Unique identifier of the mounted component
- component_name: Name/type of the mounted component
- event_type: "mount"
Thread Safety:
This method acquires a read lock and is safe for concurrent use.
Parameters:
- componentID: Unique identifier of the mounted component
- componentName: Name/type of the mounted component
Example:
detector.HandleComponentMount("btn-123", "Button")
func (*StateChangeDetector) HandleComponentUnmount ¶
func (d *StateChangeDetector) HandleComponentUnmount(componentID, componentName string)
HandleComponentUnmount processes a component unmount event and notifies subscribed clients.
This method:
- Finds all subscriptions interested in component changes
- Checks if the unmount event matches each subscription's filters
- Queues notifications for matching subscriptions
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
detector.HandleComponentUnmount("comp-1", "Counter")
Parameters:
- componentID: The unique identifier of the unmounted component
- componentName: The name of the unmounted component
func (*StateChangeDetector) HandleEventEmit ¶
func (d *StateChangeDetector) HandleEventEmit(eventName, componentID string, data interface{})
HandleEventEmit processes an event emission and notifies subscribed clients.
This method:
- Finds all subscriptions interested in event emissions
- Checks if the event matches each subscription's filters
- Queues notifications for matching subscriptions
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
detector.HandleEventEmit("increment", "comp-1", nil)
Parameters:
- eventName: The name of the emitted event
- componentID: The ID of the component that emitted the event
- data: Optional event data
func (*StateChangeDetector) HandleRefChange ¶
func (d *StateChangeDetector) HandleRefChange(refID string, oldValue, newValue interface{})
HandleRefChange processes a ref value change and notifies subscribed clients.
This method:
- Finds all subscriptions interested in ref changes
- Checks if the change matches each subscription's filters
- Queues notifications for matching subscriptions
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
detector.HandleRefChange("ref-1", 41, 42)
Parameters:
- refID: The unique identifier of the ref that changed
- oldValue: The value before the change
- newValue: The value after the change
func (*StateChangeDetector) Initialize ¶
func (d *StateChangeDetector) Initialize(dt *devtools.DevTools) error
Initialize hooks the detector into the DevTools system.
This method registers a custom hook with DevTools that will be called whenever state changes, components mount/unmount, or events are emitted.
Thread Safety:
Safe to call concurrently, but should only be called once per detector.
Example:
dt := devtools.Enable()
err := detector.Initialize(dt)
if err != nil {
log.Printf("Failed to initialize: %v", err)
}
Parameters:
- dt: The DevTools instance to hook into
Returns:
- error: Initialization error, or nil on success
func (*StateChangeDetector) SetNotifier ¶
func (d *StateChangeDetector) SetNotifier(notifier notificationSender)
SetNotifier sets the notification sender for this detector.
This method configures the detector to use the provided notifier for sending notifications to clients when changes are detected.
Thread Safety:
Safe to call concurrently, but should be called before any changes occur.
Example:
notifier, err := NewNotificationSender(batcher)
if err != nil {
log.Fatal(err)
}
detector.SetNotifier(notifier)
Parameters:
- notifier: The notification sender to use
type StateResource ¶
type StateResource struct {
// Refs contains all reactive references across all components
Refs []*RefInfo `json:"refs"`
// Computed contains all computed values (future enhancement)
Computed []*ComputedInfo `json:"computed"`
// Timestamp indicates when this snapshot was captured
Timestamp time.Time `json:"timestamp"`
}
StateResource represents the reactive state resource.
This structure is returned by the bubblyui://state/refs resource and provides a snapshot of all reactive state in the application.
JSON Schema:
{
"refs": [RefInfo],
"computed": [ComputedInfo],
"timestamp": string (ISO 8601)
}
type Subscription ¶
type Subscription struct {
// ID is the unique identifier for this subscription
ID string
// ClientID identifies the client that owns this subscription
ClientID string
// ResourceURI is the MCP resource URI being subscribed to
// Examples: "bubblyui://components", "bubblyui://state/refs"
ResourceURI string
// Filters are optional criteria for filtering updates
// Keys and values depend on the resource type
// Example: {"ref_id": "count-ref"} to only receive updates for a specific ref
Filters map[string]interface{}
// CreatedAt is when the subscription was created
CreatedAt time.Time
}
Subscription represents a client's subscription to a resource URI.
Subscriptions enable real-time updates when the subscribed resource changes. Each subscription is identified by a unique ID and associated with a client.
Thread Safety:
Subscription instances are immutable after creation.
Example:
sub := &Subscription{
ID: "sub-123",
ClientID: "client-456",
ResourceURI: "bubblyui://state/refs",
Filters: map[string]interface{}{"ref_id": "count-ref"},
CreatedAt: time.Now(),
}
type SubscriptionManager ¶
type SubscriptionManager struct {
// contains filtered or unexported fields
}
SubscriptionManager manages client subscriptions to MCP resources.
It maintains a registry of active subscriptions, enforces limits, prevents duplicates, and provides cleanup on client disconnect.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Lifecycle:
- NewSubscriptionManager() - Creates the manager
- Subscribe() - Clients add subscriptions
- ... updates are sent via change detectors (Task 4.2) ...
- Unsubscribe() or UnsubscribeAll() - Cleanup
Example:
sm := NewSubscriptionManager(50) // Max 50 subscriptions per client
err := sm.Subscribe("client-1", "bubblyui://state/refs", nil)
if err != nil {
log.Printf("Subscribe failed: %v", err)
}
func NewSubscriptionManager ¶
func NewSubscriptionManager(maxPerClient int) *SubscriptionManager
NewSubscriptionManager creates a new subscription manager.
The maxPerClient parameter sets the maximum number of subscriptions a single client can have. This prevents resource exhaustion from subscription spam.
Thread Safety:
Safe to call concurrently (creates new instance each time).
Example:
sm := NewSubscriptionManager(50)
Parameters:
- maxPerClient: Maximum subscriptions per client (typically 50)
Returns:
- *SubscriptionManager: A new subscription manager instance
func (*SubscriptionManager) GetSubscriptionCount ¶
func (sm *SubscriptionManager) GetSubscriptionCount(clientID string) int
GetSubscriptionCount returns the total number of subscriptions for a client.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
count := sm.GetSubscriptionCount("client-1")
fmt.Printf("Client has %d subscriptions\n", count)
Parameters:
- clientID: Unique identifier for the client
Returns:
- int: Number of active subscriptions (0 if client not found)
func (*SubscriptionManager) GetSubscriptions ¶
func (sm *SubscriptionManager) GetSubscriptions(clientID string) []*Subscription
GetSubscriptions returns all subscriptions for a client.
This method returns a copy of the subscriptions slice to prevent external modification of the internal state.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
subs := sm.GetSubscriptions("client-1")
fmt.Printf("Client has %d subscriptions\n", len(subs))
Parameters:
- clientID: Unique identifier for the client
Returns:
- []*Subscription: Copy of client's subscriptions (empty slice if none)
func (*SubscriptionManager) Subscribe ¶
func (sm *SubscriptionManager) Subscribe(clientID, uri string, filters map[string]interface{}) error
Subscribe adds a new subscription for a client.
This method:
- Validates the subscription doesn't already exist (duplicate prevention)
- Enforces the per-client subscription limit
- Generates a unique subscription ID
- Adds the subscription to the registry
Duplicate Detection:
A subscription is considered duplicate if the same client is already subscribed to the same resource URI with the same filters.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
err := sm.Subscribe("client-1", "bubblyui://state/refs", map[string]interface{}{
"ref_id": "count-ref",
})
if err != nil {
log.Printf("Subscribe failed: %v", err)
}
Parameters:
- clientID: Unique identifier for the client
- uri: MCP resource URI to subscribe to
- filters: Optional filters for updates (can be nil)
Returns:
- error: Validation error, limit exceeded, or nil on success
func (*SubscriptionManager) Unsubscribe ¶
func (sm *SubscriptionManager) Unsubscribe(clientID, subscriptionID string) error
Unsubscribe removes a specific subscription.
This method:
- Finds the subscription by ID
- Removes it from the client's subscription list
- Cleans up empty client entries
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
err := sm.Unsubscribe("client-1", "sub-123")
if err != nil {
log.Printf("Unsubscribe failed: %v", err)
}
Parameters:
- clientID: Unique identifier for the client
- subscriptionID: ID of the subscription to remove
Returns:
- error: Subscription not found, or nil on success
func (*SubscriptionManager) UnsubscribeAll ¶
func (sm *SubscriptionManager) UnsubscribeAll(clientID string) error
UnsubscribeAll removes all subscriptions for a client.
This method is typically called when a client disconnects. It performs bulk cleanup of all the client's subscriptions.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
err := sm.UnsubscribeAll("client-1")
if err != nil {
log.Printf("UnsubscribeAll failed: %v", err)
}
Parameters:
- clientID: Unique identifier for the client
Returns:
- error: Client not found, or nil on success
type Throttler ¶
type Throttler struct {
// contains filtered or unexported fields
}
Throttler prevents sending updates too frequently to clients.
It enforces a minimum interval between updates for each client+resource combination. This prevents overwhelming clients with high-frequency changes.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Lifecycle:
- NewThrottler() - Creates the throttler
- ShouldSend() - Check if update should be sent
- ... automatic throttling occurs ...
- Reset() - Optional: reset throttle state for a client
Example:
throttler, err := NewThrottler(100*time.Millisecond)
if err != nil {
log.Fatal(err)
}
if throttler.ShouldSend("client-1", "bubblyui://state/refs") {
// Send update
}
func NewThrottler ¶
NewThrottler creates a new throttler.
The throttler will enforce a minimum interval between updates for each client+resource combination.
Thread Safety:
Safe to call concurrently (creates new instance each time).
Example:
throttler, err := NewThrottler(100*time.Millisecond)
if err != nil {
log.Fatal(err)
}
Parameters:
- minInterval: Minimum time between updates (must be > 0)
Returns:
- *Throttler: A new throttler instance
- error: Validation error, or nil on success
func (*Throttler) Reset ¶
Reset clears the throttle state for a client.
This allows the next update to be sent immediately, regardless of when the last update was sent.
Typically called when a client disconnects or reconnects.
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
throttler.Reset("client-1")
Parameters:
- clientID: The client to reset throttle state for
func (*Throttler) ShouldSend ¶
ShouldSend checks if an update should be sent to a client.
Returns true if:
- This is the first update for this client+resource
- minInterval has elapsed since the last update
Returns false if:
- minInterval has not yet elapsed (throttled)
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
if throttler.ShouldSend("client-1", "bubblyui://state/refs") {
// Send update
server.SendNotification(clientID, update)
}
Parameters:
- clientID: The client to check
- resourceURI: The resource URI to check
Returns:
- bool: True if update should be sent, false if throttled
type TransportType ¶
type TransportType int
TransportType defines the transport mechanism for MCP server.
Multiple transports can be enabled simultaneously using bitwise OR. For example: MCPTransportStdio | MCPTransportHTTP enables both.
const ( // MCPTransportStdio enables stdio transport for local CLI integration MCPTransportStdio TransportType = 1 << iota // MCPTransportHTTP enables HTTP/SSE transport for IDE integration MCPTransportHTTP )
func (TransportType) String ¶
func (t TransportType) String() string
String returns the string representation of the transport type.
Returns:
- string: Human-readable transport type (e.g., "stdio", "http", "stdio|http")
type UpdateBatcher ¶
type UpdateBatcher struct {
// contains filtered or unexported fields
}
UpdateBatcher batches updates and flushes them periodically or when batch size is reached.
It collects updates for each client and flushes them either:
- After flushInterval has elapsed since the last flush
- When the batch reaches maxBatchSize updates
This prevents overwhelming clients with high-frequency updates.
Thread Safety:
All methods are thread-safe and can be called concurrently.
Lifecycle:
- NewUpdateBatcher() - Creates the batcher
- SetFlushHandler() - Configures the flush handler
- AddUpdate() - Add updates to batch
- ... automatic flushing occurs ...
- Stop() - Graceful shutdown (flushes pending updates)
Example:
batcher, err := NewUpdateBatcher(100*time.Millisecond, 10)
if err != nil {
log.Fatal(err)
}
batcher.SetFlushHandler(func(clientID string, updates []UpdateNotification) {
// Send updates to client
})
batcher.AddUpdate(notification)
defer batcher.Stop()
func NewUpdateBatcher ¶
func NewUpdateBatcher(flushInterval time.Duration, maxBatchSize int) (*UpdateBatcher, error)
NewUpdateBatcher creates a new update batcher.
The batcher will flush updates either:
- After flushInterval has elapsed
- When a client's batch reaches maxBatchSize
Thread Safety:
Safe to call concurrently (creates new instance each time).
Example:
batcher, err := NewUpdateBatcher(100*time.Millisecond, 10)
if err != nil {
log.Fatal(err)
}
Parameters:
- flushInterval: Maximum time between flushes (must be > 0)
- maxBatchSize: Maximum updates per batch (must be > 0)
Returns:
- *UpdateBatcher: A new batcher instance
- error: Validation error, or nil on success
func (*UpdateBatcher) AddUpdate ¶
func (b *UpdateBatcher) AddUpdate(update UpdateNotification)
AddUpdate adds an update to the batch.
The update will be flushed either:
- After flushInterval has elapsed
- When the client's batch reaches maxBatchSize (immediate flush)
Thread Safety:
Safe to call concurrently from multiple goroutines.
Example:
update := UpdateNotification{
ClientID: "client-1",
URI: "bubblyui://state/refs",
Data: map[string]interface{}{"ref_id": "ref-1", "value": 42},
}
batcher.AddUpdate(update)
Parameters:
- update: The notification to add to the batch
func (*UpdateBatcher) SetFlushHandler ¶
func (b *UpdateBatcher) SetFlushHandler(handler FlushHandler)
SetFlushHandler sets the handler to call when flushing updates.
This must be called before adding any updates.
Thread Safety:
Not safe to call concurrently with AddUpdate(). Should be called once during initialization.
Example:
batcher.SetFlushHandler(func(clientID string, updates []UpdateNotification) {
for _, update := range updates {
server.SendNotification(clientID, update)
}
})
Parameters:
- handler: The flush handler function
func (*UpdateBatcher) Stop ¶
func (b *UpdateBatcher) Stop()
Stop gracefully shuts down the batcher.
This method:
- Stops the flush timer
- Flushes all pending updates
- Waits for the flush goroutine to complete
Thread Safety:
Safe to call concurrently, but should only be called once.
Example:
defer batcher.Stop()
After calling Stop(), no more updates should be added.
type UpdateNotification ¶
type UpdateNotification struct {
// ClientID identifies the client to send the notification to
ClientID string
// URI is the resource URI that changed
URI string
// Data contains the notification payload
Data map[string]interface{}
}
UpdateNotification represents a notification to be sent to a client.
Notifications are batched and throttled to prevent client overload.
Thread Safety:
UpdateNotification instances are immutable after creation.
Example:
notification := UpdateNotification{
ClientID: "client-1",
URI: "bubblyui://state/refs",
Data: map[string]interface{}{"ref_id": "ref-1", "value": 42},
}