Bunli
PackagesPlugin Packages

@bunli/plugin-mcp

Create CLI commands from MCP (Model Context Protocol) tool schemas

Installation

bun add @bunli/plugin-mcp

Overview

@bunli/plugin-mcp converts MCP (Model Context Protocol) tool schemas into type-safe Bunli CLI commands. This lets you expose MCP tools as CLI commands that AI agents can discover and invoke.

Features

  • Schema to Zod conversion - Automatically converts JSON schemas to Zod validation
  • Dynamic registration - Commands registered at runtime from MCP tool definitions
  • Namespace support - Organize commands by MCP server namespace
  • Type generation - Optional TypeScript type generation for enhanced DX
  • Zero-config - Works out of the box with standard MCP tool definitions

Two Usage Patterns

import { createCLI } from "@bunli/core";
import { mcpPlugin } from "@bunli/plugin-mcp";

const cli = await createCLI({
  name: "my-cli",
  plugins: [
    mcpPlugin({
      toolsProvider: async (context) => {
        // Fetch tools from your MCP client
        const tools = await myMcpClient.listTools();
        return [{ namespace: "my-server", tools }];
      },
      createHandler:
        (namespace, toolName) =>
        async ({ flags }) => {
          return myMcpClient.callTool(toolName, flags);
        },
      sync: true, // Enable type generation
    }),
  ],
});

await cli.run();

2. Direct Command Conversion

Use the converter directly without the plugin pattern:

import { createCommandsFromMCPTools } from "@bunli/plugin-mcp";

const tools = await myMcpClient.listTools();

const commands = createCommandsFromMCPTools(tools, {
  namespace: "my-server",
  createHandler:
    (toolName) =>
    async ({ flags }) => {
      return myMcpClient.callTool(toolName, flags);
    },
});

commands.forEach((cmd) => cli.command(cmd));

MCP Tool Schema

The plugin converts standard MCP tool input schemas:

interface MCPTool {
  name: string;
  description?: string;
  inputSchema: {
    type: "object";
    properties?: Record<string, JSONSchema7>;
    required?: string[];
  };
}

Plugin Options

interface McpPluginOptions<TStore = Record<string, unknown>> {
  /** Function to fetch tools from MCP client(s) */
  toolsProvider: (context: IPluginContext) => Promise<MCPToolGroup[]>;

  /** Factory to create handlers for each tool */
  createHandler: (namespace: string, toolName: string) => CommandHandler<TStore>;

  /** Enable type generation (or set to { outputDir: string }) */
  sync?: boolean | { outputDir?: string };
}

MCPToolGroup

interface MCPToolGroup {
  namespace: string;
  tools: MCPTool[];
}

Type Generation

Enable automatic TypeScript type generation:

mcpPlugin({
  toolsProvider: async () => {
    /* ... */
  },
  createHandler:
    (ns, name) =>
    async ({ flags }) => {
      /* ... */
    },
  sync: true, // Generates .bunli/mcp-types.gen.ts
});

Or with custom output:

mcpPlugin({
  // ...
  sync: { outputDir: "./types" },
});

CLI Codegen (Builder API)

For CLI-based code generation workflows:

import { Commands } from "@bunli/plugin-mcp";

const builder = Commands.from(tools).namespace("exa").timeout(30000);

const code = template
  .replace("{{COMMANDS}}", builder.commands())
  .replace("{{REGISTRATIONS}}", builder.registrations());

Schema Utilities

Convert JSON schemas to Zod schemas:

import { jsonSchemaToZodSchema } from "@bunli/plugin-mcp";

const zodSchema = jsonSchemaToZodSchema({
  type: "object",
  properties: {
    name: { type: "string" },
    age: { type: "number" },
  },
  required: ["name"],
});

Naming Conventions

The plugin handles naming automatically:

MCP ToolCLI Command
create_projectcreate-project
listItemslist-items
getUserByIdget-user-by-id

Available utilities:

  • toKebabCase - Convert to kebab-case
  • toCamelCase - Convert to camelCase
  • toPascalCase - Convert to PascalCase
  • toCommandName - Convert MCP name to CLI command name
  • toFlagName - Convert to flag name format

Error Handling

import {
  SchemaConversionError,
  ConvertToolsError,
  GenerateMCPTypesError,
  McpToolsProviderError,
} from "@bunli/plugin-mcp";

try {
  // ...
} catch (error) {
  if (error instanceof McpToolsProviderError) {
    console.error("Failed to fetch tools:", error.cause);
  } else if (error instanceof SchemaConversionError) {
    console.error("Invalid schema:", error.message);
  }
}

Example: Linear MCP Integration

import { createCLI } from "@bunli/core";
import { mcpPlugin } from "@bunli/plugin-mcp";
import { LinearClient } from "@linear/sdk";

const linear = new LinearClient({ apiKey: process.env.LINEAR_API_KEY });

const cli = await createCLI({
  name: "linear",
  plugins: [
    mcpPlugin({
      toolsProvider: async () => {
        const tools = await fetchLinearTools(); // Your tool fetcher
        return [{ namespace: "linear", tools }];
      },
      createHandler:
        (namespace, toolName) =>
        async ({ flags }) => {
          return linear.call(toolName, flags);
        },
    }),
  ],
});

await cli.run();

Users can then run:

linear issues list --team=backend --limit=10
linear issues create --title="Bug fix" --team=backend

See Also

On this page