Skip to content

[API] Decouple pkg/api from pkg/kubernetes to eliminate cyclic dependencies #577

@manusa

Description

@manusa

Description

Background

The current package architecture has evolved organically as the project grew, leading to problematic coupling between core packages. Originally, toolsets directly depended on pkg/kubernetes, but as the project matured, the dependency structure became inverted in a way that creates architectural friction.

Problem Statement

The pkg/api package, which should be a foundational package containing type definitions and interfaces, has dependencies on pkg/kubernetes:

Current problematic imports in pkg/api:

  • pkg/api/toolsets.go imports pkg/kubernetes (as internalk8s)
  • pkg/api/prompts.go imports pkg/kubernetes (as internalk8s)

Specific coupling points:

  1. ToolHandlerParams struct (pkg/api/toolsets.go:67-72):

    type ToolHandlerParams struct {
        context.Context
        *internalk8s.Kubernetes  // ← Direct dependency on concrete type
        ToolCallRequest
        ListOutput output.Output
    }
  2. PromptHandlerParams struct (pkg/api/prompts.go:89-93):

    type PromptHandlerParams struct {
        context.Context
        *internalk8s.Kubernetes  // ← Direct dependency on concrete type
        PromptCallRequest
    }
  3. Toolset.GetTools() method (pkg/api/toolsets.go:46):

    GetTools(o internalk8s.Openshift) []ServerTool  // ← Uses interface from pkg/kubernetes

This creates a quasi-circular dependency pattern:

pkg/api/config (leaf - only interfaces, no deps)
    ↑
pkg/kubernetes (imports pkg/api/config)
    ↑
pkg/api (imports pkg/kubernetes) ← PROBLEM: api should be a leaf package
    ↑
pkg/toolsets/* (imports pkg/api AND pkg/kubernetes)

Impact

  1. PR feat(config): add configurable prompts support #559 (configurable prompts): Had to define TOML primitives in pkg/config instead of directly deserializing api.Prompt structures because of the coupling.

  2. Reduced reusability: The pkg/api package cannot be used independently without pulling in the entire Kubernetes client stack.

  3. Testing complexity: Testing API types requires setting up Kubernetes dependencies even when not needed.

  4. Architectural violation: The API/types package should be a stable, dependency-free foundation that other packages build upon.

Previous Work

  • PR refactor(config)!: extract config interfaces to pkg/api/config #566 started decoupling by extracting config interfaces to pkg/api/config. This introduced AuthProvider, ClusterProvider, DeniedResourcesProvider, and ExtendedProvider interfaces, allowing pkg/kubernetes to depend on abstractions rather than StaticConfig directly.
  • However, the reverse dependency (pkg/apipkg/kubernetes) remains unaddressed.

Proposed Solution

Phase 1: Extract Kubernetes Interfaces to pkg/api

Move the Openshift interface and introduce a new KubernetesClient interface in pkg/api:

// pkg/api/kubernetes.go (NEW FILE)
package api

import "context"

// Openshift provides OpenShift-specific detection capabilities
type Openshift interface {
    IsOpenShift(context.Context) bool
}

// KubernetesClient defines the interface for Kubernetes operations
// that tool and prompt handlers need
type KubernetesClient interface {
    // Core Kubernetes operations needed by toolsets
    // (methods to be determined by analyzing actual usage in toolsets)
}

Phase 2: Update Handler Params to Use Interfaces

// pkg/api/toolsets.go
type ToolHandlerParams struct {
    context.Context
    KubernetesClient    // ← Interface instead of concrete type
    ToolCallRequest
    ListOutput output.Output
}

// pkg/api/prompts.go
type PromptHandlerParams struct {
    context.Context
    KubernetesClient    // ← Interface instead of concrete type
    PromptCallRequest
}

Phase 3: Toolset Interface Cleanup

type Toolset interface {
    GetName() string
    GetDescription() string
    GetTools(o Openshift) []ServerTool  // ← Uses api.Openshift interface
}

Phase 4: pkg/kubernetes Implements Interfaces

The pkg/kubernetes.Kubernetes struct would implement the api.KubernetesClient interface, and pkg/kubernetes.Manager would implement api.Openshift.

Target Architecture

pkg/api (leaf package - NO internal dependencies except pkg/api/config)
├── pkg/api/config (configuration interfaces)
├── Tool, ServerTool, Toolset definitions
├── Prompt, ServerPrompt definitions  
├── KubernetesClient interface (NEW)
├── Openshift interface (MOVED from pkg/kubernetes)
    ↑
pkg/config (imports pkg/api/config)
    ↑
pkg/kubernetes (imports pkg/api, implements interfaces)
    ↑
pkg/toolsets/* (imports pkg/api, receives kubernetes via interfaces)
    ↑
pkg/mcp (orchestrates everything)

Acceptance Criteria

  • pkg/api has no imports from pkg/kubernetes
  • pkg/api only depends on pkg/api/config, pkg/output, and standard library + external schema libraries
  • All toolsets continue to function correctly
  • All prompts continue to function correctly
  • No breaking changes to public API (toolset registration, MCP server initialization)
  • All existing tests pass
  • Configuration parsing (TOML) works directly with api.Prompt types

Tests

  • Verify that go list -f '{{.Imports}}' ./pkg/api/... does not include pkg/kubernetes
  • Existing test suite passes without modification
  • Add interface compliance tests: var _ api.KubernetesClient = (*kubernetes.Kubernetes)(nil)

Related Issues/PRs

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions