Skip to content

feat: Profiles infrastructure - base support for named configuration profiles #54

@polaz

Description

@polaz

Summary

Add support for named configuration profiles and presets that enable multi-GitLab workflows and role-based access patterns.

Architecture: Profiles vs Presets

SECURITY CRITICAL: Built-in configurations must NEVER contain host or authentication to prevent accidental requests to wrong GitLab instances during testing.

Profile (User-defined only)

Full configuration including connection settings:

  • Location: ~/.config/gitlab-mcp/profiles.yaml
  • Contains: host, auth, TLS, features, restrictions
  • Purpose: Multi-instance access (work, personal, client)

Preset (Built-in, safe for testing)

Settings/restrictions only, NO connection:

  • Location: src/profiles/builtin/ (embedded in package)
  • Contains: features, read_only, denied_tools_regex, denied_actions
  • NO host, NO auth - uses environment variables
  • Purpose: Role-based tool restrictions (readonly, developer, admin)

Profile Format

Location: ~/.config/gitlab-mcp/profiles.yaml

profiles:
  work:
    host: gitlab.company.com
    auth:
      type: pat
      token_env: GITLAB_WORK_TOKEN  # Reference to env var
    read_only: false
    features:
      wiki: true
      pipelines: true
    timeout_ms: 30000

  client-acme:
    host: gitlab.acme-corp.com
    auth:
      type: oauth
      client_id_env: ACME_OAUTH_CLIENT_ID
    read_only: true
    denied_tools_regex: "^manage_"

default_profile: work

Preset Format

Location: src/profiles/builtin/*.yaml

# readonly.yaml - NO host/auth, safe for testing
description: "Read-only access - blocks all write operations"
read_only: true
denied_tools_regex: "^manage_|^create_"

features:
  wiki: true
  variables: false  # Variables often contain secrets
  webhooks: false   # Admin-level

Profile Selection

Priority order:

  1. CLI argument: --profile <name> (tries profile, then preset)
  2. Environment variable: GITLAB_PROFILE=<name>
  3. default_profile from user config
  4. Fallback to env vars (current behavior)

Profile Schema

// Full profile with connection (user-defined only)
interface Profile {
  // Connection
  host: string;
  api_url?: string;
  
  // Authentication
  auth: {
    type: "pat" | "oauth" | "cookie";
    token_env?: string;
    client_id_env?: string;
    cookie_path?: string;
  };
  
  // Settings (same as Preset)
  read_only?: boolean;
  denied_tools_regex?: string;
  allowed_tools?: string[];
  denied_actions?: string[];
  features?: FeatureFlags;
  timeout_ms?: number;
  
  // TLS
  skip_tls_verify?: boolean;
  ssl_cert_path?: string;
  ca_cert_path?: string;
}

// Preset without connection (built-in safe)
interface Preset {
  description?: string;
  read_only?: boolean;
  denied_tools_regex?: string;
  allowed_tools?: string[];
  denied_actions?: string[];
  features?: FeatureFlags;
  timeout_ms?: number;
  // NO host, NO auth
}

Implementation

1. Types and schemas

  • src/profiles/types.ts - Zod schemas for Profile, Preset, ProfilesConfig

2. Profile/Preset loader

// src/profiles/loader.ts
export class ProfileLoader {
  async loadProfile(name: string): Promise<Profile>;  // User profiles only
  async loadPreset(name: string): Promise<Preset>;    // Built-in presets only
  async loadAny(name: string): Promise<{ type: "profile" | "preset"; data: Profile | Preset }>;
  async listProfiles(): Promise<ProfileInfo[]>;
}

3. Profile/Preset application

// src/profiles/applicator.ts
export function applyProfile(profile: Profile): void;
export function applyPreset(preset: Preset): void;
export async function loadAndApplyProfile(name: string): Promise<ApplyProfileResult>;

Built-in Presets

src/profiles/builtin/
├── readonly.yaml    # Browse only, no modifications
├── developer.yaml   # Standard dev workflow
└── admin.yaml       # Full access

See #55 for extended role-based presets.

Tasks

  • Define Profile/Preset TypeScript interfaces
  • Create Zod schemas for validation
  • Implement ProfileLoader class
  • Implement applyProfile()/applyPreset() functions
  • Add --profile CLI argument to entry points
  • Support GITLAB_PROFILE environment variable
  • Create built-in presets directory (readonly, developer, admin)
  • Write unit tests for profile/preset loading
  • Write integration tests for application
  • Run full test suite

Acceptance Criteria

  • Profiles load from ~/.config/gitlab-mcp/profiles.yaml
  • Presets load from built-in package directory
  • Built-in presets contain NO host/auth (security)
  • --profile argument works
  • GITLAB_PROFILE env var works
  • Profile validation catches invalid configurations
  • Graceful fallback when no profile specified
  • Tokens read from env vars (never stored in yaml)

Priority

HIGH - Foundation for role-based presets and multi-instance workflows

Dependencies

None (foundation issue)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions