Chuyển đến nội dung chính

Bài 16: Plugin System & MCP Protocol

Plugin architecture: load/unload, sandboxed execution, marketplace. MCP (Model Context Protocol): server implementation, tool exposure, resource management. Third-party integrations.

🧠 AI & ML — Bài 15 Bài 16: Plugin System & MCP Protocol

Xây dựng AI Agent Platform từ Zero — Thực chiến với xClaw

Phần 5: Skills, Domains & Plugins

xdev.asia

Giới thiệu

Plugin System cho phép third-party mở rộng platform. MCP (Model Context Protocol) là standard mới cho AI tool interoperability — xClaw implement cả MCP server (expose tools) và MCP client (consume external tools).


1. Plugin Architecture

// packages/core/src/plugins/types.ts
export interface Plugin {
  id: string;
  name: string;
  version: string;
  description: string;

  // Lifecycle hooks
  onLoad(context: PluginContext): Promise<void>;
  onUnload(): Promise<void>;

  // What plugin provides
  tools?: AdditionalTool[];
  skills?: SkillDefinition[];
  middleware?: MiddlewareFunction[];
  routes?: RouteDefinition[];
}

export interface PluginContext {
  config: Record<string, unknown>;
  logger: Logger;
  eventBus: EventBus;
  registerTool(def: ToolDefinition, handler: ToolHandler): void;
  registerSkill(skill: SkillDefinition): void;
}

Plugin Manager

// packages/core/src/plugins/plugin-manager.ts
export class PluginManager {
  private plugins = new Map<string, Plugin>();
  private context: PluginContext;

  async loadPlugin(plugin: Plugin): Promise<void> {
    if (this.plugins.has(plugin.id)) {
      throw new Error(`Plugin ${plugin.id} already loaded`);
    }

    // Initialize plugin
    await plugin.onLoad(this.context);

    // Register tools
    if (plugin.tools) {
      for (const tool of plugin.tools) {
        this.context.registerTool(tool.definition, tool.handler);
      }
    }

    // Register skills
    if (plugin.skills) {
      for (const skill of plugin.skills) {
        this.context.registerSkill(skill);
      }
    }

    this.plugins.set(plugin.id, plugin);
    console.log(`Plugin loaded: ${plugin.name} v${plugin.version}`);
  }

  async unloadPlugin(pluginId: string): Promise<void> {
    const plugin = this.plugins.get(pluginId);
    if (!plugin) return;

    await plugin.onUnload();
    this.plugins.delete(pluginId);
    console.log(`Plugin unloaded: ${plugin.name}`);
  }

  listPlugins(): { id: string; name: string; version: string }[] {
    return Array.from(this.plugins.values()).map(p => ({
      id: p.id,
      name: p.name,
      version: p.version,
    }));
  }
}

2. Ví dụ Plugin: GitHub Integration

// packages/integrations/src/github-plugin.ts
export const githubPlugin: Plugin = {
  id: 'github',
  name: 'GitHub Integration',
  version: '1.0.0',
  description: 'GitHub issues, PRs, and repository management',

  async onLoad(ctx: PluginContext) {
    const token = ctx.config.githubToken as string;
    if (!token) throw new Error('GitHub token required');
  },

  async onUnload() {},

  tools: [
    {
      definition: {
        name: 'github_list_issues',
        description: 'List issues in a GitHub repository',
        parameters: {
          type: 'object',
          properties: {
            repo: { type: 'string', description: 'owner/repo format' },
            state: { type: 'string', enum: ['open', 'closed', 'all'] },
          },
          required: ['repo'],
        },
      },
      handler: async (args) => {
        const { repo, state = 'open' } = args as { repo: string; state?: string };
        const response = await fetch(
          `https://api.github.com/repos/${repo}/issues?state=${state}`,
          { headers: { Authorization: `token ${process.env.GITHUB_TOKEN}` } },
        );
        return response.json();
      },
    },
    {
      definition: {
        name: 'github_create_issue',
        description: 'Create a new issue',
        parameters: {
          type: 'object',
          properties: {
            repo: { type: 'string', description: 'owner/repo' },
            title: { type: 'string', description: 'Issue title' },
            body: { type: 'string', description: 'Issue body' },
          },
          required: ['repo', 'title'],
        },
      },
      handler: async (args) => {
        const { repo, title, body } = args as { repo: string; title: string; body?: string };
        const response = await fetch(
          `https://api.github.com/repos/${repo}/issues`,
          {
            method: 'POST',
            headers: {
              Authorization: `token ${process.env.GITHUB_TOKEN}`,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({ title, body }),
          },
        );
        return response.json();
      },
    },
  ],
};

3. MCP Server Implementation

// packages/core/src/mcp/mcp-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

export class MCPServer {
  private server: Server;
  private toolRegistry: ToolRegistry;

  constructor(toolRegistry: ToolRegistry) {
    this.toolRegistry = toolRegistry;

    this.server = new Server(
      { name: 'xclaw-mcp', version: '1.0.0' },
      { capabilities: { tools: {} } },
    );

    this.setupHandlers();
  }

  private setupHandlers() {
    // List available tools
    this.server.setRequestHandler('tools/list', async () => {
      const tools = this.toolRegistry.getDefinitions();

      return {
        tools: tools.map(t => ({
          name: t.name,
          description: t.description,
          inputSchema: t.parameters,
        })),
      };
    });

    // Execute a tool
    this.server.setRequestHandler('tools/call', async (request) => {
      const { name, arguments: args } = request.params;

      const result = await this.toolRegistry.execute(
        name,
        args as Record<string, unknown>,
        { tenantId: 'mcp', userId: 'mcp', sessionId: 'mcp' },
      );

      return {
        content: [{
          type: 'text',
          text: JSON.stringify(result.result),
        }],
        isError: !result.success,
      };
    });
  }

  async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.log('MCP Server started on stdio');
  }
}

4. MCP Client — Consume External Tools

// packages/core/src/mcp/mcp-client.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

export class MCPClient {
  private client: Client;

  async connect(command: string, args: string[]) {
    const transport = new StdioClientTransport({ command, args });
    this.client = new Client({ name: 'xclaw', version: '1.0.0' });
    await this.client.connect(transport);
  }

  // Import external MCP tools into xClaw's ToolRegistry
  async importTools(registry: ToolRegistry) {
    const { tools } = await this.client.listTools();

    for (const tool of tools) {
      registry.register(
        {
          name: `mcp_${tool.name}`,
          description: tool.description || '',
          parameters: tool.inputSchema as ToolDefinition['parameters'],
        },
        async (args) => {
          const result = await this.client.callTool({
            name: tool.name,
            arguments: args,
          });
          return result.content;
        },
      );
    }

    console.log(`Imported ${tools.length} MCP tools`);
  }
}

5. Tổng kết

  • Plugin System — lifecycle hooks (load/unload), provides tools + skills + routes
  • MCP Server — expose xClaw tools to Claude Desktop, Cursor, etc.
  • MCP Client — import external MCP tools into xClaw
  • Bidirectional — xClaw vừa là MCP server vừa là MCP client

Bài tiếp theo: Multi-tenant RBAC — Isolated tenants với granular permissions.