Login Get started

Quick Reference

Plugin development quick reference card

Plugin Development Quick Reference#

Fast reference for common plugin development tasks

Quick Start Checklist#

  • [ ] Create plugin directory in plugins/core/
  • [ ] Create manifest.json following schema
  • [ ] Create package.json with dependencies
  • [ ] Create tsconfig.json for TypeScript
  • [ ] Implement plugin entry point in src/index.ts
  • [ ] Add i18n/en.json translation (required; additional locales auto-discovered)
  • [ ] Build plugin: npm run build
  • [ ] Test in dashboard

Minimal manifest.json#

{
  "id": "hay-plugin-myservice",
  "name": "My Service",
  "version": "1.0.0",
  "description": "Integration with My Service",
  "type": ["mcp-connector"],
  "entry": "./dist/index.js",
  "category": "integration",
  "capabilities": {
    "mcp": {
      "connection": { "type": "local" },
      "tools": [],
      "transport": "stdio",
      "auth": ["apiKey"],
      "installCommand": "npm install",
      "startCommand": "node mcp/index.js"
    }
  },
  "configSchema": {
    "apiKey": {
      "type": "string",
      "label": "API Key",
      "required": true,
      "encrypted": true,
      "env": "MYSERVICE_API_KEY"
    }
  },
  "permissions": {
    "env": ["MYSERVICE_API_KEY"],
    "scopes": ["org:<organizationId>:mcp:invoke"]
  }
}

Common Patterns#

Local MCP Server#

{
  "capabilities": {
    "mcp": {
      "connection": { "type": "local" },
      "serverPath": "./mcp/index.js",
      "transport": "sse|websocket|http",
      "startCommand": "node mcp/index.js"
    }
  }
}

Remote MCP Server#

{
  "capabilities": {
    "mcp": {
      "connection": {
        "type": "remote",
        "url": "https://mcp.service.com"
      },
      "transport": "http"
    }
  }
}

OAuth Authentication#

{
  "capabilities": {
    "mcp": {
      "auth": {
        "methods": ["oauth2", "apiKey"],
        "oauth": {
          "authorizationUrl": "https://service.com/oauth/authorize",
          "tokenUrl": "https://service.com/oauth/token",
          "scopes": ["read", "write"],
          "pkce": true,
          "clientIdEnvVar": "SERVICE_CLIENT_ID",
          "clientSecretEnvVar": "SERVICE_CLIENT_SECRET"
        }
      }
    }
  }
}

API Key Authentication#

{
  "configSchema": {
    "apiKey": {
      "type": "string",
      "label": "API Key",
      "placeholder": "sk_live_...",
      "required": true,
      "encrypted": true,
      "env": "SERVICE_API_KEY"
    }
  }
}

MCP Tool Definition#

{
  "name": "create_resource",
  "label": "Create Resource",
  "description": "Creates a new resource",
  "input_schema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "description": "Resource name"
      },
      "amount": {
        "type": "number",
        "description": "Amount in cents",
        "minimum": 0
      }
    },
    "required": ["name"]
  }
}

API Usage#

Frontend (Dashboard)#

import { Hay } from "@/utils/api";

// Get all plugins
const plugins = await Hay.plugins.getAll.query();

// Enable plugin
await Hay.plugins.enable.mutate({
  pluginId: "hay-plugin-myservice",
  configuration: {
    apiKey: "sk_test_123",
  },
});

// Get plugin details (includes configuration)
const plugin = await Hay.plugins.get.query({
  pluginId: "hay-plugin-myservice",
});

// Update configuration
await Hay.plugins.configure.mutate({
  pluginId: "hay-plugin-myservice",
  configuration: {
    apiKey: "new_key",
  },
});

// Disable plugin
await Hay.plugins.disable.mutate({
  pluginId: "hay-plugin-myservice",
});

Backend (Services)#

// Plugin Manager
import { pluginManagerService } from "@server/services/plugin-manager.service";

const plugin = pluginManagerService.getPlugin("hay-plugin-myservice");
await pluginManagerService.installPlugin("hay-plugin-myservice");
await pluginManagerService.buildPlugin("hay-plugin-myservice");

// Plugin Instance Manager
import { pluginInstanceManagerService } from "@server/services/plugin-instance-manager.service";

await pluginInstanceManagerService.ensureInstanceRunning(organizationId, "hay-plugin-myservice");
await pluginInstanceManagerService.updateActivityTimestamp(organizationId, "hay-plugin-myservice");

// Plugin Runner
import { getPluginRunnerService } from "@server/services/plugin-runner.service";

const pluginRunnerService = getPluginRunnerService();
await pluginRunnerService.startPluginWorker(organizationId, "hay-plugin-myservice");
await pluginRunnerService.stopPluginWorker(organizationId, "hay-plugin-myservice");
const isRunning = pluginRunnerService.isRunning(organizationId, "hay-plugin-myservice");

File Structure#

hay-plugin-myservice/
├── manifest.json              # Plugin configuration
├── package.json              # NPM dependencies
├── tsconfig.json             # TypeScript config
├── src/
│   └── index.ts             # Entry point
├── dist/
│   └── index.js             # Compiled output
├── i18n/                    # Translations (optional)
│   ├── en.json             # English (fallback)
│   └── pt-BR.json          # Brazilian Portuguese
├── mcp/                     # MCP server
│   ├── index.js
│   └── package.json
└── components/              # Vue components (optional)
    └── settings/
        └── CustomSettings.vue

Environment Variables#

Configuration fields automatically map to environment variables:

{
  "configSchema": {
    "apiKey": {
      "env": "SERVICE_API_KEY"
    }
  }
}

When MCP server starts:

SERVICE_API_KEY=decrypted_value node mcp/index.js

Channel Registration#

// Register a communication channel
await trpc.sources.register.mutate({
  id: "whatsapp",
  name: "WhatsApp Business",
  category: "messaging",
  pluginId: "my-plugin",
  metadata: { version: "1.0" },
});

// Create messages with source
import { MessageService } from "@server/services/core/message.service";

const message = await messageService.createAssistantMessageWithTestMode(
  conversation,
  "Hello!",
  "whatsapp", // sourceId
  agent,
  organization,
  metadata,
);

Internationalization (i18n)#

Add an i18n/ directory to your plugin with one JSON file per locale. No manifest changes needed — the system discovers translations automatically.

Translation File (i18n/en.json)#

{
  "name": "My Plugin",
  "description": "What this plugin does",
  "tools": {
    "tool_name": {
      "label": "Tool Display Name",
      "description": "What this tool does"
    }
  },
  "config": {
    "apiKey": {
      "label": "API Key",
      "description": "Your API key for authentication"
    }
  }
}

Key Rules#

  • en.json is required (fallback for all locales)
  • Tool keys must match MCP server tool names
  • Config keys must match configSchema / ctx.register.config() field names
  • Any locale filename is automatically discovered (e.g., es.json, fr.json, de.json)

See the API Reference for full details and examples.


UI Extensions#

Add Settings Section#

{
  "settingsExtensions": [
    {
      "slot": "after-settings",
      "component": "components/settings/Instructions.vue"
    }
  ]
}

Add Settings Tab#

{
  "settingsExtensions": [
    {
      "slot": "tab",
      "component": "components/settings/Advanced.vue",
      "tabName": "Advanced Settings",
      "tabOrder": 1
    }
  ]
}

TypeScript Types#

// Import plugin types
import type { HayPluginManifest } from "@server/types/plugin.types";

// Plugin manifest type
interface HayPluginManifest {
  id: string;
  name: string;
  version: string;
  description?: string;
  type: PluginType[];
  entry: string;
  capabilities?: PluginCapabilities;
  configSchema?: Record<string, ConfigField>;
  permissions?: PluginPermissions;
}

// Configuration field type
interface ConfigField {
  type: "string" | "number" | "boolean" | "array" | "object";
  description: string;
  label: string;
  placeholder?: string;
  required?: boolean;
  encrypted?: boolean;
  env?: string;
  regex?: string;
  default?: any;
}

Testing Plugins#

Local Testing#

# Install dependencies
cd plugins/core/my-plugin
npm install

# Build plugin
npm run build

# Test MCP server directly
cd mcp
SERVICE_API_KEY=test_key node index.js

Integration Testing#

# Run server
npm run dev

# Enable plugin in dashboard
# Test tool invocations
# Check logs for errors

Common Issues#

Issue Solution
Plugin not appearing Check manifest.json is valid, check console for errors
Install failed Verify installCommand, check network, review logs
MCP won't start Check startCommand, verify env vars, check ports
Tool invocation fails Verify tool name, check input schema, ensure running
Config not working Restart instance, verify env mapping

Useful Commands#

# Validate manifest JSON is well-formed
node -e "JSON.parse(require('fs').readFileSync('plugins/core/my-plugin/manifest.json', 'utf8'))"

# Build plugin
cd plugins/core/my-plugin && npm run build

# Check running processes
ps aux | grep "node mcp"

# Install all plugin dependencies
cd plugins/core/my-plugin && npm install && cd mcp && npm install

Plugin ID Naming#

  • Format: hay-plugin-{service-name}
  • Use lowercase and hyphens
  • Match pattern: /^[a-z0-9-]+$/
  • Examples: hay-plugin-shopify, hay-plugin-stripe

Version Management#

Follow semantic versioning:

  • MAJOR: Breaking changes (2.0.0)
  • MINOR: New features (1.1.0)
  • PATCH: Bug fixes (1.0.1)

Security Checklist#

  • [ ] Mark sensitive fields as encrypted: true
  • [ ] Validate all user input
  • [ ] Use HTTPS for remote servers
  • [ ] Request minimal OAuth scopes
  • [ ] Never log secrets
  • [ ] Sanitize error messages
  • [ ] Implement rate limiting
  • [ ] Use secure dependencies

Performance Tips#

  • Keep tool execution under 5 seconds
  • Cache expensive operations
  • Optimize MCP server startup time
  • Handle concurrent requests
  • Monitor memory usage
  • Clean up resources on shutdown

Resources#

  • Full Documentation: docs/technical/plugins/api-reference.md
  • Generation Guide: .claude/PLUGIN_GENERATION_WORKFLOW.md
  • Channel Guide: docs/technical/plugins/channel-registration.md
  • Example Plugins: plugins/core/ directory

Need Help? Check the full documentation at docs/technical/plugins/api-reference.md

Last updated June 22, 2026

View on GitHub