DeployStack Docs

Frontend Plugin System

DeployStack's frontend features a powerful plugin architecture that enables extending the application with additional functionality, UI components, routes, and state management. This modular approach allows for clean separation of concerns and extensible development.

Architecture Overview

The plugin system is designed with flexibility and maintainability in mind:

  • Modular Extension: Add new UI components at designated extension points
  • Route Registration: Register new routes in the Vue Router
  • State Management: Add new Pinia stores for plugin-specific state
  • Lifecycle Management: Initialize and cleanup plugins properly
  • Type Safety: Full TypeScript support for plugin development

Plugin Structure

A standard plugin follows this directory structure:

your-plugin/
├── index.ts              # Main plugin entry point (required)
├── components/           # Plugin-specific components
   ├── PluginComponent.vue
   └── PluginCard.vue
├── views/               # Plugin-specific views/pages
   ├── PluginPage.vue
   └── PluginSettings.vue
├── store.ts             # Plugin-specific Pinia store (optional)
├── composables/         # Plugin-specific composables (optional)
   └── usePluginFeature.ts
├── types.ts             # Plugin-specific types (optional)
└── README.md            # Plugin documentation

Plugin Interface

Every plugin must implement the Plugin interface:

interface Plugin {
  meta: PluginMeta
  initialize(app: App, router: Router, pinia: Pinia, pluginManager?: PluginManager): Promise<void>
  cleanup(): Promise<void>
}

interface PluginMeta {
  id: string           // Unique plugin identifier
  name: string         // Human-readable plugin name
  version: string      // Plugin version (semver)
  description: string  // Plugin description
  author: string       // Plugin author
  dependencies?: string[]  // Other plugin IDs this plugin depends on
}

Creating Your First Plugin

1. Basic Plugin Structure

Create a new directory for your plugin:

mkdir -p src/plugins/mcp-metrics-plugin
cd src/plugins/mcp-metrics-plugin

2. Create a Component

Start with a simple Vue component:

<!-- src/plugins/mcp-metrics-plugin/components/MetricsWidget.vue -->
<script setup lang=\"ts\">
import { ref, onMounted } from 'vue'
import { BarChart, TrendingUp, Server } from 'lucide-vue-next'

const metrics = ref({
  totalServers: 0,
  activeDeployments: 0,
  totalRequests: 0
})

const isLoading = ref(true)

async function fetchMetrics() {
  isLoading.value = true
  try {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000))
    metrics.value = {
      totalServers: 12,
      activeDeployments: 8,
      totalRequests: 1542
    }
  } catch (error) {
    console.error('Failed to fetch metrics:', error)
  } finally {
    isLoading.value = false
  }
}

onMounted(() => {
  fetchMetrics()
})
</script>

<template>
  <div class=\"metrics-widget p-6 bg-white rounded-lg shadow-md border\">
    <div class=\"flex items-center justify-between mb-4\">
      <h3 class=\"text-lg font-semibold text-gray-900 flex items-center\">
        <BarChart class=\"h-5 w-5 mr-2 text-indigo-600\" />
        MCP Server Metrics
      </h3>
      <button 
        @click=\"fetchMetrics\" 
        class=\"text-sm text-indigo-600 hover:text-indigo-800\"
        :disabled=\"isLoading\"
      >
        {{ isLoading ? 'Loading...' : 'Refresh' }}
      </button>
    </div>
    
    <div v-if=\"isLoading\" class=\"animate-pulse\">
      <div class=\"grid grid-cols-3 gap-4\">
        <div v-for=\"i in 3\" :key=\"i\" class=\"h-16 bg-gray-200 rounded\"></div>
      </div>
    </div>
    
    <div v-else class=\"grid grid-cols-3 gap-4\">
      <div class=\"text-center p-4 bg-blue-50 rounded-lg\">
        <Server class=\"h-6 w-6 mx-auto mb-2 text-blue-600\" />
        <div class=\"text-2xl font-bold text-blue-700\">{{ metrics.totalServers }}</div>
        <div class=\"text-sm text-blue-600\">Total Servers</div>
      </div>
      
      <div class=\"text-center p-4 bg-green-50 rounded-lg\">
        <TrendingUp class=\"h-6 w-6 mx-auto mb-2 text-green-600\" />
        <div class=\"text-2xl font-bold text-green-700\">{{ metrics.activeDeployments }}</div>
        <div class=\"text-sm text-green-600\">Active Deployments</div>
      </div>
      
      <div class=\"text-center p-4 bg-purple-50 rounded-lg\">
        <BarChart class=\"h-6 w-6 mx-auto mb-2 text-purple-600\" />
        <div class=\"text-2xl font-bold text-purple-700\">{{ metrics.totalRequests.toLocaleString() }}</div>
        <div class=\"text-sm text-purple-600\">Total Requests</div>
      </div>
    </div>
  </div>
</template>

3. Implement the Plugin

Create the main plugin file:

// src/plugins/mcp-metrics-plugin/index.ts
import type { Plugin } from '@/plugin-system/types'
import type { App } from 'vue'
import type { Router } from 'vue-router'
import type { Pinia } from 'pinia'
import { registerExtensionPoint } from '@/plugin-system/extension-points'
import MetricsWidget from './components/MetricsWidget.vue'
import MetricsPage from './views/MetricsPage.vue'

class McpMetricsPlugin implements Plugin {
  meta = {
    id: 'mcp-metrics-plugin',
    name: 'MCP Metrics Plugin',
    version: '1.0.0',
    description: 'Provides comprehensive metrics and analytics for MCP server deployments',
    author: 'DeployStack Team'
  }
  
  async initialize(app: App, router: Router, pinia: Pinia) {
    console.log('Initializing MCP Metrics Plugin...')
    
    // Register the metrics widget in the dashboard
    registerExtensionPoint('dashboard-widgets', MetricsWidget, this.meta.id, {
      order: 10, // Show early in the dashboard
      props: {
        refreshInterval: 30000 // Refresh every 30 seconds
      }
    })
    
    // Register a dedicated metrics page
    router.addRoute({
      path: '/metrics',
      name: 'Metrics',
      component: MetricsPage,
      meta: {
        title: 'MCP Metrics',
        requiresAuth: true
      }
    })
    
    // Add navigation item (if supported by your app)
    registerExtensionPoint('main-navigation', {
      template: `
        <router-link 
          to=\"/metrics\" 
          class=\"nav-link flex items-center space-x-2 px-3 py-2 rounded-md hover:bg-gray-100\"
        >
          <BarChart class=\"h-5 w-5\" />
          <span>Metrics</span>
        </router-link>
      `,
      components: { BarChart: () => import('lucide-vue-next').then(m => m.BarChart) }
    }, this.meta.id)
    
    console.log('MCP Metrics Plugin initialized successfully')
  }
  
  async cleanup() {
    console.log('Cleaning up MCP Metrics Plugin...')
    // Perform any necessary cleanup
    // Extension points are automatically cleaned up by the plugin manager
  }
}

export default McpMetricsPlugin

4. Create a Dedicated Page

Create a full page view for your plugin:

<!-- src/plugins/mcp-metrics-plugin/views/MetricsPage.vue -->
<script setup lang=\"ts\">
import { ref, onMounted } from 'vue'
import { BarChart, TrendingUp, Activity, Clock } from 'lucide-vue-next'
import MetricsWidget from '../components/MetricsWidget.vue'

const detailedMetrics = ref({
  responseTime: '125ms',
  uptime: '99.9%',
  errorRate: '0.1%',
  throughput: '1.2k req/min'
})

const chartData = ref([])
const isLoading = ref(true)

async function fetchDetailedMetrics() {
  isLoading.value = true
  try {
    // Simulate API call for detailed metrics
    await new Promise(resolve => setTimeout(resolve, 1500))
    
    // Mock chart data
    chartData.value = Array.from({ length: 24 }, (_, i) => ({
      hour: i,
      requests: Math.floor(Math.random() * 100) + 50,
      errors: Math.floor(Math.random() * 5)
    }))
  } catch (error) {
    console.error('Failed to fetch detailed metrics:', error)
  } finally {
    isLoading.value = false
  }
}

onMounted(() => {
  fetchDetailedMetrics()
})
</script>

<template>
  <div class=\"metrics-page max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8\">
    <div class=\"mb-8\">
      <h1 class=\"text-3xl font-bold text-gray-900 flex items-center\">
        <BarChart class=\"h-8 w-8 mr-3 text-indigo-600\" />
        MCP Server Metrics
      </h1>
      <p class=\"mt-2 text-gray-600\">
        Comprehensive analytics and performance metrics for your MCP server deployments
      </p>
    </div>
    
    <!-- Overview Widget -->
    <div class=\"mb-8\">
      <MetricsWidget />
    </div>
    
    <!-- Detailed Metrics -->
    <div class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8\">
      <div class=\"bg-white p-6 rounded-lg shadow-md border\">
        <div class=\"flex items-center\">
          <Clock class=\"h-8 w-8 text-blue-600\" />
          <div class=\"ml-4\">
            <div class=\"text-2xl font-bold text-gray-900\">{{ detailedMetrics.responseTime }}</div>
            <div class=\"text-sm text-gray-600\">Avg Response Time</div>
          </div>
        </div>
      </div>
      
      <div class=\"bg-white p-6 rounded-lg shadow-md border\">
        <div class=\"flex items-center\">
          <TrendingUp class=\"h-8 w-8 text-green-600\" />
          <div class=\"ml-4\">
            <div class=\"text-2xl font-bold text-gray-900\">{{ detailedMetrics.uptime }}</div>
            <div class=\"text-sm text-gray-600\">Uptime</div>
          </div>
        </div>
      </div>
      
      <div class=\"bg-white p-6 rounded-lg shadow-md border\">
        <div class=\"flex items-center\">
          <Activity class=\"h-8 w-8 text-red-600\" />
          <div class=\"ml-4\">
            <div class=\"text-2xl font-bold text-gray-900\">{{ detailedMetrics.errorRate }}</div>
            <div class=\"text-sm text-gray-600\">Error Rate</div>
          </div>
        </div>
      </div>
      
      <div class=\"bg-white p-6 rounded-lg shadow-md border\">
        <div class=\"flex items-center\">
          <BarChart class=\"h-8 w-8 text-purple-600\" />
          <div class=\"ml-4\">
            <div class=\"text-2xl font-bold text-gray-900\">{{ detailedMetrics.throughput }}</div>
            <div class=\"text-sm text-gray-600\">Throughput</div>
          </div>
        </div>
      </div>
    </div>
    
    <!-- Charts Section -->
    <div class=\"bg-white p-6 rounded-lg shadow-md border\">
      <h2 class=\"text-xl font-semibold mb-4\">24-Hour Request Pattern</h2>
      
      <div v-if=\"isLoading\" class=\"animate-pulse\">
        <div class=\"h-64 bg-gray-200 rounded\"></div>
      </div>
      
      <div v-else class=\"h-64\">
        <!-- Simple chart representation -->
        <div class=\"flex items-end justify-between h-full space-x-1\">
          <div 
            v-for=\"(data, index) in chartData\" 
            :key=\"index\"
            class=\"bg-indigo-500 min-w-[1rem] rounded-t transition-all hover:bg-indigo-600\"
            :style=\"{ height: `${(data.requests / 150) * 100}%` }\"
            :title=\"`Hour ${data.hour}: ${data.requests} requests`\"
          ></div>
        </div>
      </div>
    </div>
  </div>
</template>

5. Add Plugin State Management

Create a Pinia store for your plugin:

// src/plugins/mcp-metrics-plugin/store.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export interface MetricsData {
  totalServers: number
  activeDeployments: number
  totalRequests: number
  responseTime: string
  uptime: string
  errorRate: string
  throughput: string
}

export interface ChartDataPoint {
  hour: number
  requests: number
  errors: number
}

export const useMetricsStore = defineStore('mcp-metrics', () => {
  // State
  const metrics = ref<MetricsData>({
    totalServers: 0,
    activeDeployments: 0,
    totalRequests: 0,
    responseTime: '0ms',
    uptime: '0%',
    errorRate: '0%',
    throughput: '0 req/min'
  })
  
  const chartData = ref<ChartDataPoint[]>([])
  const isLoading = ref(false)
  const lastUpdated = ref<Date | null>(null)
  
  // Getters
  const healthScore = computed(() => {
    const uptimePercent = parseFloat(metrics.value.uptime.replace('%', ''))
    const errorPercent = parseFloat(metrics.value.errorRate.replace('%', ''))
    return Math.max(0, 100 - errorPercent * 10) * (uptimePercent / 100)
  })
  
  const isHealthy = computed(() => healthScore.value > 80)
  
  // Actions
  async function fetchMetrics() {
    isLoading.value = true
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1000))
      
      metrics.value = {
        totalServers: Math.floor(Math.random() * 20) + 10,
        activeDeployments: Math.floor(Math.random() * 15) + 5,
        totalRequests: Math.floor(Math.random() * 5000) + 1000,
        responseTime: `${Math.floor(Math.random() * 200) + 50}ms`,
        uptime: `${(99 + Math.random()).toFixed(1)}%`,
        errorRate: `${(Math.random() * 2).toFixed(1)}%`,
        throughput: `${(Math.random() * 2 + 0.5).toFixed(1)}k req/min`
      }
      
      lastUpdated.value = new Date()
    } catch (error) {
      console.error('Failed to fetch metrics:', error)
      throw error
    } finally {
      isLoading.value = false
    }
  }
  
  async function fetchChartData() {
    try {
      // Simulate API call for chart data
      await new Promise(resolve => setTimeout(resolve, 800))
      
      chartData.value = Array.from({ length: 24 }, (_, i) => ({
        hour: i,
        requests: Math.floor(Math.random() * 100) + 50,
        errors: Math.floor(Math.random() * 5)
      }))
    } catch (error) {
      console.error('Failed to fetch chart data:', error)
      throw error
    }
  }
  
  function refreshAllData() {
    return Promise.all([
      fetchMetrics(),
      fetchChartData()
    ])
  }
  
  return {
    // State
    metrics,
    chartData,
    isLoading,
    lastUpdated,
    
    // Getters
    healthScore,
    isHealthy,
    
    // Actions
    fetchMetrics,
    fetchChartData,
    refreshAllData
  }
})

Extension Points

Extension points are designated areas in your application where plugins can inject components.

Using Extension Points in Your App

Add extension points to your main application components:

<!-- In your Dashboard.vue or other main components -->
<template>
  <div class=\"dashboard\">
    <h1>Dashboard</h1>
    
    <!-- Extension point for dashboard widgets -->
    <div class=\"widgets-grid\">
      <ExtensionPoint pointId=\"dashboard-widgets\" />
    </div>
    
    <!-- Extension point for sidebar items -->
    <aside class=\"sidebar\">
      <ExtensionPoint pointId=\"sidebar-items\" />
    </aside>
    
    <!-- Extension point for action buttons -->
    <div class=\"actions\">
      <ExtensionPoint pointId=\"action-buttons\" />
    </div>
  </div>
</template>

Registering Components at Extension Points

In your plugin's initialize method:

// Register a single component
registerExtensionPoint(
  'dashboard-widgets',    // Extension point ID
  MetricsWidget,         // Vue component
  this.meta.id,          // Plugin ID
  {
    order: 10,           // Display order (optional)
    props: {             // Props to pass to component (optional)
      refreshInterval: 30000
    }
  }
)

// Register multiple components
registerExtensionPoint('action-buttons', RefreshButton, this.meta.id, { order: 1 })
registerExtensionPoint('action-buttons', ExportButton, this.meta.id, { order: 2 })

Conditional Rendering

Show specific plugin components based on conditions:

<template>
  <!-- Show only specific plugin -->
  <ExtensionPoint pointId=\"dashboard-widgets\" pluginName=\"mcp-metrics-plugin\" />
  
  <!-- Show all except specific plugin -->
  <ExtensionPoint pointId=\"dashboard-widgets\" :exclude=\"['debug-plugin']\" />
  
  <!-- Show based on user permissions -->
  <ExtensionPoint 
    pointId=\"admin-actions\" 
    v-if=\"userHasAdminRole\"
  />
</template>

Advanced Plugin Features

Plugin Dependencies

Specify dependencies in your plugin metadata:

class AdvancedPlugin implements Plugin {
  meta = {
    id: 'advanced-plugin',
    name: 'Advanced Plugin',
    version: '1.0.0',
    description: 'Advanced functionality that requires metrics plugin',
    author: 'You',
    dependencies: ['mcp-metrics-plugin']  // Require metrics plugin
  }
  
  async initialize(app: App, router: Router, pinia: Pinia, pluginManager?: PluginManager) {
    // Check if required plugins are available
    const metricsPlugin = pluginManager?.getPlugin('mcp-metrics-plugin')
    if (!metricsPlugin) {
      throw new Error('MCP Metrics Plugin is required but not available')
    }
    
    // Use functionality from the metrics plugin
    console.log('Metrics plugin available:', metricsPlugin.meta.name)
  }
}

Plugin Configuration

Support configuration through the plugin manager:

interface PluginConfig {
  refreshInterval?: number
  enableNotifications?: boolean
  theme?: 'light' | 'dark'
}

class ConfigurablePlugin implements Plugin {
  private config: PluginConfig = {}
  
  async initialize(app: App, router: Router, pinia: Pinia, pluginManager?: PluginManager) {
    // Get plugin configuration
    this.config = pluginManager?.getPluginConfig(this.meta.id) || {}
    
    // Use configuration
    const refreshInterval = this.config.refreshInterval || 30000
    const enableNotifications = this.config.enableNotifications !== false
    
    console.log('Plugin config:', this.config)
  }
}

Inter-Plugin Communication

Plugins can communicate through events or shared stores:

// Event-based communication
class PublisherPlugin implements Plugin {
  async initialize(app: App) {
    // Emit events
    app.config.globalProperties.$pluginEventBus.emit('metrics-updated', data)
  }
}

class SubscriberPlugin implements Plugin {
  async initialize(app: App) {
    // Listen to events
    app.config.globalProperties.$pluginEventBus.on('metrics-updated', (data) => {
      console.log('Received metrics update:', data)
    })
  }
}

Plugin Composables

Create reusable composition functions:

// src/plugins/mcp-metrics-plugin/composables/useMetrics.ts
import { ref, onMounted, onUnmounted } from 'vue'
import { useMetricsStore } from '../store'

export function useMetrics(refreshInterval = 30000) {
  const store = useMetricsStore()
  const isAutoRefreshEnabled = ref(true)
  let intervalId: number | null = null
  
  function startAutoRefresh() {
    if (intervalId) clearInterval(intervalId)
    
    intervalId = setInterval(() => {
      if (isAutoRefreshEnabled.value) {
        store.fetchMetrics()
      }
    }, refreshInterval)
  }
  
  function stopAutoRefresh() {
    if (intervalId) {
      clearInterval(intervalId)
      intervalId = null
    }
  }
  
  function toggleAutoRefresh() {
    isAutoRefreshEnabled.value = !isAutoRefreshEnabled.value
    if (isAutoRefreshEnabled.value) {
      startAutoRefresh()
    } else {
      stopAutoRefresh()
    }
  }
  
  onMounted(() => {
    store.fetchMetrics()
    startAutoRefresh()
  })
  
  onUnmounted(() => {
    stopAutoRefresh()
  })
  
  return {
    metrics: store.metrics,
    isLoading: store.isLoading,
    healthScore: store.healthScore,
    isHealthy: store.isHealthy,
    isAutoRefreshEnabled,
    refreshMetrics: store.fetchMetrics,
    toggleAutoRefresh
  }
}

Registering Plugins

Add your plugin to the plugin loader:

// src/plugins/index.ts
import type { Plugin } from '../plugin-system/types'
import HelloWorldPlugin from './hello-world'
import McpMetricsPlugin from './mcp-metrics-plugin'
import AdvancedPlugin from './advanced-plugin'

export async function loadPlugins(): Promise<Plugin[]> {
  return [
    new HelloWorldPlugin(),
    new McpMetricsPlugin(),
    new AdvancedPlugin(),
    // Add more plugins here
  ]
}

Testing Plugins

Unit Testing Plugin Components

// tests/plugins/mcp-metrics-plugin/MetricsWidget.test.ts
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import MetricsWidget from '@/plugins/mcp-metrics-plugin/components/MetricsWidget.vue'

describe('MetricsWidget', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })
  
  it('should render metrics correctly', async () => {
    const wrapper = mount(MetricsWidget)
    
    // Wait for component to load
    await wrapper.vm.$nextTick()
    
    expect(wrapper.find('.metrics-widget').exists()).toBe(true)
    expect(wrapper.text()).toContain('MCP Server Metrics')
  })
  
  it('should show loading state initially', () => {
    const wrapper = mount(MetricsWidget)
    expect(wrapper.find('.animate-pulse').exists()).toBe(true)
  })
  
  it('should handle refresh button click', async () => {
    const wrapper = mount(MetricsWidget)
    const refreshButton = wrapper.find('button')
    
    await refreshButton.trigger('click')
    expect(wrapper.vm.isLoading).toBe(true)
  })
})

Integration Testing

// tests/plugins/mcp-metrics-plugin/integration.test.ts
import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import McpMetricsPlugin from '@/plugins/mcp-metrics-plugin'
import { PluginManager } from '@/plugin-system/PluginManager'

describe('MCP Metrics Plugin Integration', () => {
  let pluginManager: PluginManager
  let router: any
  let pinia: any
  
  beforeEach(() => {
    router = createRouter({
      history: createWebHistory(),
      routes: []
    })
    pinia = createPinia()
    pluginManager = new PluginManager()
  })
  
  it('should initialize plugin successfully', async () => {
    const plugin = new McpMetricsPlugin()
    
    await expect(
      plugin.initialize(null as any, router, pinia, pluginManager)
    ).resolves.not.toThrow()
    
    // Check if routes were added
    const routes = router.getRoutes()
    expect(routes.some((route: any) => route.name === 'Metrics')).toBe(true)
  })
  
  it('should register extension points', async () => {
    const plugin = new McpMetricsPlugin()
    await plugin.initialize(null as any, router, pinia, pluginManager)
    
    // Verify extension points were registered
    const dashboardExtensions = pluginManager.getExtensionPoints('dashboard-widgets')
    expect(dashboardExtensions).toHaveLength(1)
    expect(dashboardExtensions[0].pluginId).toBe(plugin.meta.id)
  })
})

Plugin Store Testing

// tests/plugins/mcp-metrics-plugin/store.test.ts
import { createPinia, setActivePinia } from 'pinia'
import { useMetricsStore } from '@/plugins/mcp-metrics-plugin/store'

describe('Metrics Store', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })
  
  it('should initialize with default values', () => {
    const store = useMetricsStore()
    
    expect(store.metrics.totalServers).toBe(0)
    expect(store.metrics.activeDeployments).toBe(0)
    expect(store.isLoading).toBe(false)
    expect(store.lastUpdated).toBeNull()
  })
  
  it('should update metrics after fetching', async () => {
    const store = useMetricsStore()
    
    await store.fetchMetrics()
    
    expect(store.metrics.totalServers).toBeGreaterThan(0)
    expect(store.lastUpdated).toBeInstanceOf(Date)
  })
  
  it('should calculate health score correctly', async () => {
    const store = useMetricsStore()
    
    // Set known values for testing
    store.metrics.uptime = '99.5%'
    store.metrics.errorRate = '0.1%'
    
    expect(store.healthScore).toBeCloseTo(98.5)
    expect(store.isHealthy).toBe(true)
  })
})

Plugin Development Best Practices

1. Plugin Naming and Structure

// Good plugin naming
class McpServerMonitoringPlugin implements Plugin {
  meta = {
    id: 'mcp-server-monitoring',           // kebab-case
    name: 'MCP Server Monitoring',         // Human readable
    version: '1.2.3',                     // Semantic versioning
    description: 'Real-time monitoring for MCP servers', // Clear description
    author: 'DeployStack Team'
  }
}

// Avoid generic names
// ❌ class UtilsPlugin
// ❌ class MyPlugin
// ❌ class Plugin1

2. Component Naming Convention

<!-- Good: Prefix with plugin name -->
<!-- MetricsWidget.vue -->
<!-- MetricsDashboard.vue -->
<!-- MetricsChart.vue -->

<!-- Avoid generic names that might conflict -->
<!-- ❌ Widget.vue -->
<!-- ❌ Dashboard.vue -->
<!-- ❌ Chart.vue -->

3. Error Handling

class RobustPlugin implements Plugin {
  async initialize(app: App, router: Router, pinia: Pinia) {
    try {
      // Plugin initialization logic
      await this.setupComponents()
      await this.registerRoutes(router)
      await this.initializeStore(pinia)
      
      console.log(`${this.meta.name} initialized successfully`)
    } catch (error) {
      console.error(`Failed to initialize ${this.meta.name}:`, error)
      
      // Graceful degradation
      this.handleInitializationError(error)
      
      // Don't throw unless it's critical
      // throw error
    }
  }
  
  private handleInitializationError(error: Error) {
    // Log detailed error information
    console.error('Plugin initialization error details:', {
      pluginId: this.meta.id,
      version: this.meta.version,
      error: error.message,
      stack: error.stack
    })
    
    // Maybe show a user notification
    // Maybe disable certain features
    // Maybe use fallback functionality
  }
}

4. Resource Cleanup

class CleanPlugin implements Plugin {
  private intervals: number[] = []
  private eventListeners: Array<{ element: EventTarget, type: string, listener: EventListener }> = []
  
  async initialize(app: App, router: Router, pinia: Pinia) {
    // Set up intervals
    const intervalId = setInterval(() => {
      this.refreshData()
    }, 30000)
    this.intervals.push(intervalId)
    
    // Set up event listeners
    const listener = (event: Event`
}) => this.handleEvent(event)
    document.addEventListener('visibilitychange', listener)
    this.eventListeners.push({
      element: document,
      type: 'visibilitychange',
      listener
    })
  }
  
  async cleanup() {
    // Clean up intervals
    this.intervals.forEach(id => clearInterval(id))
    this.intervals = []
    
    // Clean up event listeners
    this.eventListeners.forEach(({ element, type, listener }) => {
      element.removeEventListener(type, listener)
    })
    this.eventListeners = []
    
    console.log(`${this.meta.name} cleaned up successfully`)
  }
}

5. Type Safety

// Define clear interfaces for your plugin data
interface MetricsData {
  totalServers: number
  activeDeployments: number
  totalRequests: number
  responseTime: string
  uptime: string
  errorRate: string
  throughput: string
}

interface PluginConfig {
  refreshInterval?: number
  enableNotifications?: boolean
  apiEndpoint?: string
}

// Use proper typing in components
interface Props {
  data: MetricsData
  config?: PluginConfig
  onRefresh?: () => void
}

const props = withDefaults(defineProps<Props>(), {
  config: () => ({}),
  onRefresh: () => {}
})

6. Performance Considerations

class PerformantPlugin implements Plugin {
  private cache = new Map<string, { data: any, timestamp: number }>()
  private readonly CACHE_DURATION = 30000 // 30 seconds
  
  async fetchData(key: string): Promise<any> {
    // Check cache first
    const cached = this.cache.get(key)
    if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {
      return cached.data
    }
    
    // Fetch fresh data
    const data = await this.performApiCall(key)
    
    // Update cache
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    })
    
    return data
  }
  
  private async performApiCall(key: string): Promise<any> {
    // Actual API call implementation
    const response = await fetch(`/api/plugin-data/${key}`)
    return response.json()
  }
}

Plugin Lifecycle Management

Initialization Order

Plugins are initialized in the order they're listed in the plugin loader, but you can control dependencies:

// In your plugin loader
export async function loadPlugins(): Promise<Plugin[]> {
  const plugins = [
    new CorePlugin(),        // Initialize first (no dependencies)
    new MetricsPlugin(),     // Depends on core
    new AdvancedPlugin(),    // Depends on metrics
  ]
  
  // Sort by dependencies if needed
  return sortPluginsByDependencies(plugins)
}

function sortPluginsByDependencies(plugins: Plugin[]): Plugin[] {
  // Implementation to sort plugins based on their dependencies
  // This ensures plugins are initialized in the correct order
  return plugins // simplified for example
}

Runtime Plugin Management

// Enable/disable plugins at runtime
class PluginManager {
  private activePlugins = new Map<string, Plugin>()
  
  async enablePlugin(pluginId: string): Promise<void> {
    const plugin = this.getAvailablePlugin(pluginId)
    if (!plugin) {
      throw new Error(`Plugin ${pluginId} not found`)
    }
    
    if (this.activePlugins.has(pluginId)) {
      console.warn(`Plugin ${pluginId} is already enabled`)
      return
    }
    
    await plugin.initialize(this.app, this.router, this.pinia, this)
    this.activePlugins.set(pluginId, plugin)
    
    console.log(`Plugin ${pluginId} enabled successfully`)
  }
  
  async disablePlugin(pluginId: string): Promise<void> {
    const plugin = this.activePlugins.get(pluginId)
    if (!plugin) {
      console.warn(`Plugin ${pluginId} is not currently enabled`)
      return
    }
    
    await plugin.cleanup()
    this.activePlugins.delete(pluginId)
    
    // Remove extension points
    this.removePluginExtensionPoints(pluginId)
    
    console.log(`Plugin ${pluginId} disabled successfully`)
  }
}

Plugin Distribution

Plugin Packaging

Create a proper plugin package structure:

my-plugin-package/
├── package.json          # NPM package configuration
├── README.md            # Plugin documentation
├── CHANGELOG.md         # Version history
├── LICENSE              # License file
├── src/
   ├── index.ts         # Main plugin export
   ├── components/      # Plugin components
   ├── stores/          # Plugin stores
   └── types/           # Plugin types
├── dist/                # Compiled plugin (generated)
├── docs/                # Additional documentation
└── examples/            # Usage examples

Package.json for Plugin

{
  "name": "@deploystack/mcp-metrics-plugin",
  "version": "1.0.0",
  "description": "MCP server metrics and analytics plugin for DeployStack",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "lint": "eslint src/**/*.ts",
    "test": "vitest"
  },
  "keywords": [
    "deploystack",
    "plugin",
    "mcp",
    "metrics",
    "analytics"
  ],
  "peerDependencies": {
    "vue": "^3.3.0",
    "vue-router": "^4.2.0",
    "pinia": "^2.1.0"
  },
  "devDependencies": {
    "@types/vue": "^3.3.0",
    "typescript": "^5.0.0",
    "vite": "^4.3.0"
  }
}

Plugin Registry

For team-wide plugin sharing:

// Plugin registry service
export class PluginRegistry {
  private static instance: PluginRegistry
  private plugins = new Map<string, Plugin>()
  
  static getInstance(): PluginRegistry {
    if (!this.instance) {
      this.instance = new PluginRegistry()
    }
    return this.instance
  }
  
  register(plugin: Plugin): void {
    if (this.plugins.has(plugin.meta.id)) {
      throw new Error(`Plugin ${plugin.meta.id} is already registered`)
    }
    
    this.plugins.set(plugin.meta.id, plugin)
    console.log(`Plugin ${plugin.meta.id} registered successfully`)
  }
  
  getPlugin(id: string): Plugin | undefined {
    return this.plugins.get(id)
  }
  
  getAllPlugins(): Plugin[] {
    return Array.from(this.plugins.values())
  }
  
  getPluginsByAuthor(author: string): Plugin[] {
    return this.getAllPlugins().filter(plugin => plugin.meta.author === author)
  }
}

Troubleshooting

Common Issues

Plugin Not Loading

// Debug plugin loading
export async function loadPlugins(): Promise<Plugin[]> {
  const plugins: Plugin[] = []
  
  try {
    const HelloWorldPlugin = (await import('./hello-world')).default
    plugins.push(new HelloWorldPlugin())
    console.log('✅ HelloWorldPlugin loaded')
  } catch (error) {
    console.error('❌ Failed to load HelloWorldPlugin:', error)
  }
  
  try {
    const MetricsPlugin = (await import('./mcp-metrics-plugin')).default
    plugins.push(new MetricsPlugin())
    console.log('✅ MetricsPlugin loaded')
  } catch (error) {
    console.error('❌ Failed to load MetricsPlugin:', error)
  }
  
  return plugins
}

Extension Points Not Rendering

<!-- Debug extension points -->
<template>
  <div>
    <h3>Debug Extension Points</h3>
    <div v-for="(point, id) in extensionPoints" :key="id">
      <strong>{{ id }}:</strong> {{ point.length }} components
      <ul>
        <li v-for="ext in point" :key="ext.pluginId">
          {{ ext.pluginId }} (order: {{ ext.options?.order || 0 }})
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup lang="ts">
import { inject } from 'vue'

const extensionPoints = inject('extensionPoints', {})
</script>

Plugin State Issues

// Debug plugin state
class DebuggablePlugin implements Plugin {
  async initialize(app: App, router: Router, pinia: Pinia) {
    // Add global debug method
    app.config.globalProperties.$debugPlugin = (pluginId: string) => {
      console.log('Plugin Debug Info:', {
        id: this.meta.id,
        routes: router.getRoutes().filter(r => r.meta?.pluginId === pluginId),
        stores: pinia._s,
        extensionPoints: this.getRegisteredExtensionPoints()
      })
    }
  }
  
  private getRegisteredExtensionPoints() {
    // Return extension points registered by this plugin
    return []
  }
}

Performance Debugging

// Performance monitoring for plugins
class PerformanceMonitoredPlugin implements Plugin {
  async initialize(app: App, router: Router, pinia: Pinia) {
    const startTime = performance.now()
    
    try {
      await this.doInitialization()
      
      const endTime = performance.now()
      console.log(`${this.meta.id} initialization took ${endTime - startTime}ms`)
    } catch (error) {
      const endTime = performance.now()
      console.error(`${this.meta.id} failed after ${endTime - startTime}ms:`, error)
      throw error
    }
  }
  
  private async doInitialization() {
    // Your plugin initialization logic
  }
}

This comprehensive plugin system documentation provides everything needed to create powerful, maintainable, and well-tested plugins for the DeployStack frontend. The modular architecture ensures that functionality can be extended cleanly while maintaining the core application's stability and performance.