vmcp

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Nov 10, 2025 License: Apache-2.0 Imports: 2 Imported by: 0

Documentation

Overview

Package vmcp provides the Virtual MCP Server implementation.

Virtual MCP Server aggregates multiple MCP servers from a ToolHive group into a single unified interface. This package contains the core domain models and interfaces that are platform-agnostic (work for both CLI and Kubernetes deployments).

Architecture

The vmcp package follows Domain-Driven Design (DDD) principles with clear separation of concerns into bounded contexts:

pkg/vmcp/
├── types.go              // Shared domain types (BackendTarget, Tool, etc.)
├── errors.go             // Domain errors
├── router/               // Request routing
│   └── router.go         // Router interface + routing strategies
├── aggregator/           // Capability aggregation
│   └── aggregator.go     // Aggregator interface + conflict resolution
├── auth/                 // Authentication (incoming & outgoing)
│   └── auth.go           // Auth interfaces + strategies
├── composer/             // Composite tool workflows
│   └── composer.go       // Composer interface + workflow engine
├── config/               // Configuration model
│   └── config.go         // Config types + loaders
└── cache/                // Token caching
    └── cache.go          // Cache interface + implementations

Core Concepts

**Routing**: Forward MCP protocol requests (tools, resources, prompts) to appropriate backend workloads. Supports session affinity and load balancing.

**Aggregation**: Discover backend capabilities, resolve naming conflicts, and merge into a unified view. Three-stage process: discovery, conflict resolution, merging.

**Authentication**: Two-boundary model:

  • Incoming: Clients authenticate to virtual MCP (OIDC, local, anonymous)
  • Outgoing: Virtual MCP authenticates to backends (extensible strategies)

**Composition**: Execute multi-step workflows across multiple backends. Supports sequential and parallel execution, elicitation, error handling.

**Configuration**: Platform-agnostic config model with adapters for CLI (YAML) and Kubernetes (CRDs).

**Caching**: Token caching to reduce auth overhead. Pluggable backends (memory, Redis).

Key Interfaces

Router (pkg/vmcp/router):

type Router interface {
	RouteTool(ctx context.Context, toolName string) (*vmcp.BackendTarget, error)
	RouteResource(ctx context.Context, uri string) (*vmcp.BackendTarget, error)
	RoutePrompt(ctx context.Context, name string) (*vmcp.BackendTarget, error)
	UpdateRoutingTable(ctx context.Context, table *vmcp.RoutingTable) error
}

Aggregator (pkg/vmcp/aggregator):

type Aggregator interface {
	DiscoverBackends(ctx context.Context) ([]vmcp.Backend, error)
	QueryCapabilities(ctx context.Context, backend vmcp.Backend) (*BackendCapabilities, error)
	ResolveConflicts(ctx context.Context, capabilities map[string]*BackendCapabilities) (*ResolvedCapabilities, error)
	MergeCapabilities(ctx context.Context, resolved *ResolvedCapabilities) (*AggregatedCapabilities, error)
}

Composer (pkg/vmcp/composer):

type Composer interface {
	ExecuteWorkflow(ctx context.Context, def *WorkflowDefinition, params map[string]any) (*WorkflowResult, error)
	ValidateWorkflow(ctx context.Context, def *WorkflowDefinition) error
	GetWorkflowStatus(ctx context.Context, workflowID string) (*WorkflowStatus, error)
	CancelWorkflow(ctx context.Context, workflowID string) error
}

IncomingAuthenticator (pkg/vmcp/auth):

type IncomingAuthenticator interface {
	Authenticate(ctx context.Context, r *http.Request) (*Identity, error)
	Middleware() func(http.Handler) http.Handler
}

OutgoingAuthRegistry (pkg/vmcp/auth):

type OutgoingAuthRegistry interface {
	GetStrategy(name string) (Strategy, error)
	RegisterStrategy(name string, strategy Strategy) error
}

Design Principles

  1. Platform Independence: Core domain logic works for both CLI and Kubernetes
  2. Interface Segregation: Small, focused interfaces for better testability
  3. Dependency Inversion: Depend on abstractions, not concrete implementations
  4. Modularity: Each bounded context can be developed and tested independently
  5. Extensibility: Plugin architecture for auth strategies, routing strategies, etc.
  6. Type Safety: Shared types at package root avoid circular dependencies

Usage Example

import (
	"github.com/stacklok/toolhive/pkg/vmcp"
	"github.com/stacklok/toolhive/pkg/vmcp/router"
	"github.com/stacklok/toolhive/pkg/vmcp/aggregator"
	"github.com/stacklok/toolhive/pkg/vmcp/auth"
)

// Load configuration
cfg, err := loadConfig("vmcp-config.yaml")
if err != nil {
	return err
}

// Create components
agg := createAggregator(cfg)
rtr := createRouter(cfg)
inAuth := createIncomingAuth(cfg)
outAuth := createOutgoingAuth(cfg)

// Discover and aggregate backends
backends, err := agg.DiscoverBackends(ctx)
capabilities, err := agg.AggregateCapabilities(ctx, backends)

// Update router with aggregated capabilities
err = rtr.UpdateRoutingTable(ctx, capabilities.RoutingTable)

// Handle incoming requests
http.Handle("/tools/call", inAuth.Middleware()(
	http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Authenticate request
		identity, err := inAuth.Authenticate(ctx, r)

		// Route to backend
		target, err := rtr.RouteTool(ctx, toolName)

		// Authenticate to backend (resolve strategy and call it)
		backendReq := createBackendRequest(...)
		strategy, err := outAuth.GetStrategy(target.AuthStrategy)
		err = strategy.Authenticate(ctx, backendReq, target.AuthMetadata)

		// Forward request and return response
		// ...
	}),
))

- Proposal: docs/proposals/THV-2106-virtual-mcp-server.md - GitHub Issues: #146-159 in stacklok/stacklok-epics - MCP Specification: https://modelcontextprotocol.io/specification

See individual subpackage documentation for detailed usage and examples.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound indicates a requested resource (tool, resource, prompt, workflow) was not found.
	// Wrapping errors should provide specific details about what was not found.
	ErrNotFound = errors.New("not found")

	// ErrInvalidConfig indicates invalid configuration was provided.
	// Wrapping errors should provide specific details about what is invalid.
	ErrInvalidConfig = errors.New("invalid configuration")

	// ErrAuthenticationFailed indicates authentication failure.
	// Wrapping errors should include the underlying authentication error.
	ErrAuthenticationFailed = errors.New("authentication failed")

	// ErrAuthorizationFailed indicates authorization failure.
	// Wrapping errors should include the policy or permission that was denied.
	ErrAuthorizationFailed = errors.New("authorization failed")

	// ErrWorkflowFailed indicates workflow execution failed.
	// Wrapping errors should include the step ID and failure reason.
	ErrWorkflowFailed = errors.New("workflow execution failed")

	// ErrTimeout indicates an operation timed out.
	// Wrapping errors should include the operation type and timeout duration.
	ErrTimeout = errors.New("operation timed out")

	// ErrCancelled indicates an operation was cancelled.
	// Context cancellation should wrap this error with context.Cause().
	ErrCancelled = errors.New("operation cancelled")

	// ErrInvalidInput indicates invalid input parameters.
	// Wrapping errors should specify which parameter is invalid and why.
	ErrInvalidInput = errors.New("invalid input")

	// ErrUnsupportedTransport indicates an unsupported MCP transport type.
	// Wrapping errors should specify which transport type is not supported.
	ErrUnsupportedTransport = errors.New("unsupported transport type")

	// ErrToolExecutionFailed indicates an MCP tool execution failed (domain error).
	// This represents the tool running but returning an error result (IsError=true in MCP).
	// These errors should be forwarded to the client transparently as the LLM needs to see them.
	// Wrapping errors should include the tool name and error message from MCP.
	ErrToolExecutionFailed = errors.New("tool execution failed")

	// ErrBackendUnavailable indicates a backend MCP server is unreachable (operational error).
	// This represents infrastructure issues (network down, server not responding, etc.).
	// These errors may be retried, circuit-broken, or handled differently from domain errors.
	// Wrapping errors should include the backend ID and underlying cause.
	ErrBackendUnavailable = errors.New("backend unavailable")
)

Functions

This section is empty.

Types

type Backend

type Backend struct {
	// ID is the unique identifier for this backend.
	ID string

	// Name is the human-readable name.
	Name string

	// BaseURL is the backend's MCP server URL.
	BaseURL string

	// TransportType is the MCP transport protocol.
	TransportType string

	// HealthStatus is the current health state.
	HealthStatus BackendHealthStatus

	// AuthStrategy identifies how to authenticate to this backend.
	AuthStrategy string

	// AuthMetadata contains strategy-specific auth configuration.
	AuthMetadata map[string]any

	// Metadata stores additional backend information.
	Metadata map[string]string
}

Backend represents a discovered backend MCP server workload.

type BackendClient

type BackendClient interface {
	// CallTool invokes a tool on the backend MCP server.
	// Returns the tool output or an error.
	CallTool(ctx context.Context, target *BackendTarget, toolName string, arguments map[string]any) (map[string]any, error)

	// ReadResource retrieves a resource from the backend MCP server.
	// Returns the resource content or an error.
	ReadResource(ctx context.Context, target *BackendTarget, uri string) ([]byte, error)

	// GetPrompt retrieves a prompt from the backend MCP server.
	// Returns the rendered prompt text or an error.
	GetPrompt(ctx context.Context, target *BackendTarget, name string, arguments map[string]any) (string, error)

	// ListCapabilities queries a backend for its capabilities.
	// Returns tools, resources, and prompts exposed by the backend.
	ListCapabilities(ctx context.Context, target *BackendTarget) (*CapabilityList, error)
}

BackendClient abstracts MCP protocol communication with backend servers. This interface handles the protocol-level details of calling backend MCP servers, supporting multiple transport types (HTTP, SSE, stdio, streamable-http).

type BackendHealthStatus

type BackendHealthStatus string

BackendHealthStatus represents the health state of a backend.

const (
	// BackendHealthy indicates the backend is healthy and accepting requests.
	BackendHealthy BackendHealthStatus = "healthy"

	// BackendDegraded indicates the backend is operational but experiencing issues.
	BackendDegraded BackendHealthStatus = "degraded"

	// BackendUnhealthy indicates the backend is not responding to health checks.
	BackendUnhealthy BackendHealthStatus = "unhealthy"

	// BackendUnknown indicates the backend health status is unknown.
	BackendUnknown BackendHealthStatus = "unknown"

	// BackendUnauthenticated indicates the backend is not authenticated.
	BackendUnauthenticated BackendHealthStatus = "unauthenticated"
)

type BackendRegistry

type BackendRegistry interface {
	// Get retrieves a backend by ID.
	// Returns nil if the backend is not found.
	// This method is safe for concurrent reads.
	//
	// Example:
	//   backend := registry.Get(ctx, "github-mcp")
	//   if backend == nil {
	//       return fmt.Errorf("backend not found")
	//   }
	Get(ctx context.Context, backendID string) *Backend

	// List returns all registered backends.
	// The returned slice is a snapshot and safe to iterate without additional locking.
	// Order is not guaranteed unless specified by the implementation.
	//
	// Example:
	//   backends := registry.List(ctx)
	//   for _, backend := range backends {
	//       fmt.Printf("Backend: %s\n", backend.Name)
	//   }
	List(ctx context.Context) []Backend

	// Count returns the number of registered backends.
	// This is more efficient than len(List()) for large registries.
	Count() int
}

BackendRegistry provides thread-safe access to discovered backends. This is a shared kernel interface used across vmcp bounded contexts (aggregator, router, health monitoring).

The registry serves as the single source of truth for backend information during the lifecycle of a virtual MCP server instance. It supports both immutable (Phase 1) and mutable (future phases) implementations.

Design Philosophy:

  • Phase 1: Immutable registry (backends discovered once, never change)
  • Future: Mutable registry with health monitoring and dynamic updates
  • Thread-safe for concurrent reads across all implementations
  • Implementations may support concurrent writes with appropriate locking

func NewImmutableRegistry

func NewImmutableRegistry(backends []Backend) BackendRegistry

NewImmutableRegistry creates a registry from a static list of backends.

This implementation is used in Phase 1 where backends are discovered once at startup and don't change during the virtual MCP server's lifetime. The registry is thread-safe for concurrent reads.

Parameters:

  • backends: List of discovered backends to register

Returns:

  • BackendRegistry: An immutable registry instance

Example:

backends := discoverer.Discover(ctx, "engineering-team")
registry := vmcp.NewImmutableRegistry(backends)
backend := registry.Get(ctx, "github-mcp")

type BackendTarget

type BackendTarget struct {
	// WorkloadID is the unique identifier for the backend workload.
	WorkloadID string

	// WorkloadName is the human-readable name of the workload.
	WorkloadName string

	// BaseURL is the base URL for the backend's MCP server.
	// For local deployments: http://localhost:PORT
	// For Kubernetes: http://service-name.namespace.svc.cluster.local:PORT
	BaseURL string

	// TransportType specifies the MCP transport protocol.
	// Supported: "stdio", "http", "sse", "streamable-http"
	TransportType string

	// OriginalCapabilityName is the original name of the capability (tool/resource/prompt)
	// as known by the backend. This is used when forwarding requests to the backend.
	//
	// When conflict resolution renames capabilities, this field preserves the original name:
	// - Prefix strategy: "fetch" → "fetch_fetch" (OriginalCapabilityName="fetch")
	// - Priority strategy: usually unchanged (OriginalCapabilityName="tool_name")
	// - Manual strategy: "fetch" → "custom_name" (OriginalCapabilityName="fetch")
	//
	// If empty, the resolved name is used when forwarding to the backend.
	OriginalCapabilityName string

	// AuthStrategy identifies the authentication strategy for this backend.
	// The actual authentication is handled by OutgoingAuthRegistry interface.
	// Examples: "pass_through", "token_exchange", "client_credentials", "oauth_proxy"
	AuthStrategy string

	// AuthMetadata contains strategy-specific authentication metadata.
	// This is opaque to the router and interpreted by the authenticator.
	AuthMetadata map[string]any

	// SessionAffinity indicates if requests from the same session
	// must be routed to this specific backend instance.
	SessionAffinity bool

	// HealthStatus indicates the current health of the backend.
	HealthStatus BackendHealthStatus

	// Metadata stores additional backend-specific information.
	Metadata map[string]string
}

BackendTarget identifies a specific backend workload and provides the information needed to forward requests to it.

func BackendToTarget

func BackendToTarget(backend *Backend) *BackendTarget

BackendToTarget converts a Backend to a BackendTarget for routing. This helper is used when populating routing tables during capability aggregation.

The BackendTarget contains all information needed to forward requests to a specific backend workload, including authentication strategy and metadata.

func (*BackendTarget) GetBackendCapabilityName added in v0.6.0

func (t *BackendTarget) GetBackendCapabilityName(resolvedName string) string

GetBackendCapabilityName returns the name to use when forwarding a request to the backend. If conflict resolution renamed the capability, this returns the original name that the backend expects. Otherwise, it returns the resolved name as-is.

This method encapsulates the name translation logic for all capability types (tools, resources, prompts).

type CapabilityList

type CapabilityList struct {
	// Tools available on this backend.
	Tools []Tool

	// Resources available on this backend.
	Resources []Resource

	// Prompts available on this backend.
	Prompts []Prompt

	// SupportsLogging indicates if the backend supports MCP logging.
	SupportsLogging bool

	// SupportsSampling indicates if the backend supports MCP sampling.
	SupportsSampling bool
}

CapabilityList contains the capabilities from a backend's MCP server. This is returned by BackendClient.ListCapabilities().

type ConflictResolutionStrategy

type ConflictResolutionStrategy string

ConflictResolutionStrategy defines how to handle capability name conflicts. Placed in vmcp root package to be shared by config and aggregator packages.

const (
	// ConflictStrategyPrefix prefixes all tools with workload identifier.
	ConflictStrategyPrefix ConflictResolutionStrategy = "prefix"

	// ConflictStrategyPriority uses explicit priority ordering (first wins).
	ConflictStrategyPriority ConflictResolutionStrategy = "priority"

	// ConflictStrategyManual requires explicit overrides for all conflicts.
	ConflictStrategyManual ConflictResolutionStrategy = "manual"
)

type HealthChecker

type HealthChecker interface {
	// CheckHealth checks if a backend is healthy and responding.
	// Returns the current health status and any error encountered.
	CheckHealth(ctx context.Context, target *BackendTarget) (BackendHealthStatus, error)
}

HealthChecker performs health checks on backend MCP servers.

type Prompt

type Prompt struct {
	// Name is the prompt name (may conflict with other backends).
	Name string

	// Description describes the prompt.
	Description string

	// Arguments are the prompt parameters.
	Arguments []PromptArgument

	// BackendID identifies the backend that provides this prompt.
	BackendID string
}

Prompt represents an MCP prompt capability.

type PromptArgument

type PromptArgument struct {
	// Name is the argument name.
	Name string

	// Description describes the argument.
	Description string

	// Required indicates if the argument is mandatory.
	Required bool
}

PromptArgument represents a prompt parameter.

type Resource

type Resource struct {
	// URI is the resource URI (should be globally unique).
	URI string

	// Name is a human-readable name.
	Name string

	// Description describes the resource.
	Description string

	// MimeType is the resource's MIME type (optional).
	MimeType string

	// BackendID identifies the backend that provides this resource.
	BackendID string
}

Resource represents an MCP resource capability.

type RoutingTable

type RoutingTable struct {
	// Tools maps tool names to their backend targets.
	// After conflict resolution, tool names are unique.
	Tools map[string]*BackendTarget

	// Resources maps resource URIs to their backend targets.
	Resources map[string]*BackendTarget

	// Prompts maps prompt names to their backend targets.
	Prompts map[string]*BackendTarget
}

RoutingTable contains the mappings from capability names to backend targets. This is the output of the aggregation phase and input to the router. Placed in vmcp root package to avoid circular dependencies between aggregator and router packages.

Note: Composite tools are NOT included here. They are executed by the composer package and do not route to a single backend.

type Tool

type Tool struct {
	// Name is the tool name (may conflict with other backends).
	Name string

	// Description describes what the tool does.
	Description string

	// InputSchema is the JSON Schema for tool parameters.
	InputSchema map[string]any

	// BackendID identifies the backend that provides this tool.
	BackendID string
}

Tool represents an MCP tool capability.

Directories

Path Synopsis
Package aggregator provides capability aggregation for Virtual MCP Server.
Package aggregator provides capability aggregation for Virtual MCP Server.
mocks
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.
Package auth provides authentication for Virtual MCP Server.
Package auth provides authentication for Virtual MCP Server.
factory
Package factory provides factory functions for creating vMCP authentication components.
Package factory provides factory functions for creating vMCP authentication components.
mocks
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.
strategies
Package strategies provides authentication strategy implementations for Virtual MCP Server.
Package strategies provides authentication strategy implementations for Virtual MCP Server.
Package cache provides token caching interfaces for Virtual MCP Server.
Package cache provides token caching interfaces for Virtual MCP Server.
Package client provides MCP protocol client implementation for communicating with backend servers.
Package client provides MCP protocol client implementation for communicating with backend servers.
mocks
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.
Package composer provides composite tool workflow execution for Virtual MCP Server.
Package composer provides composite tool workflow execution for Virtual MCP Server.
Package config provides the configuration model for Virtual MCP Server.
Package config provides the configuration model for Virtual MCP Server.
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.
Package router provides request routing for Virtual MCP Server.
Package router provides request routing for Virtual MCP Server.
mocks
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.
Package server implements the Virtual MCP Server that aggregates multiple backend MCP servers into a unified interface.
Package server implements the Virtual MCP Server that aggregates multiple backend MCP servers into a unified interface.

Jump to

Keyboard shortcuts

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