mcpbeat Sign in

Create Plugin Skill for Claude

Create an OpenCode plugin for iPolloWork. Scaffolds the plugin file with the correct API shape, tool definitions, and hook registration. Use when the user asks to 'create a plugin', 'write a plugin', or 'make a plugin that does X'.

1k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
3593
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/Devin-AXIS/iPolloWork --skill create-plugin

The instruction itself

11 sections, as written by the author

Skill: Create an OpenCode Plugin

Scaffold a working OpenCode plugin for use in iPolloWork.

When to use

  • User asks "create a plugin that does X"
  • User asks "write a plugin" or "make a plugin"
  • User wants to extend iPolloWork/OpenCode with custom tools

Plugin API

An OpenCode plugin is an async factory function that returns a hooks object.

File location

Plugins can live in:

  • Project: .opencode/plugins/my-plugin.ts (auto-discovered)
  • Global: ~/.config/opencode/plugins/my-plugin.ts
  • npm: published as a package, referenced in opencode.json plugin array
  • URL: file: or https: path in the plugin array

Minimal shape

import { z } from "zod";

export default async () => ({
  tool: {
    my_tool_name: {
      description: "What this tool does.",
      args: z.object({
        input: z.string().describe("The input parameter."),
      }).shape,   // NOTE: .shape, not the ZodObject itself
      async execute(args: { input: string }) {
        // Your logic here. Can use fetch(), fs, child_process, etc.
        return `Result: ${args.input}`;
      },
    },
  },
});

Key rules

  • Export default an async function that returns the hooks object.
  • Tool args use zodSchema.shape (a ZodRawShape), not the ZodObject.
  • execute returns a string or { output: string; metadata?: Record<string, unknown> }.
  • Tools are declared, not registered imperatively — return them in the hooks object.
  • fetch() works — plugins run in-process inside the OpenCode runtime.
  • process.env is accessible — use env vars for secrets/config.

Available hooks

{
  // Modify the system prompt
  "experimental.chat.system.transform": async (input, output: { system: string[] }) => {
    output.system.push("Extra instruction for the agent.");
  },

  // Define tools the agent can call
  tool: {
    tool_name: { description, args, execute },
  },

  // Run code before/after a tool executes
  "tool.execute.before": async ({ tool, args }) => { /* ... */ },
  "tool.execute.after": async ({ tool, args, result }) => { /* ... */ },

  // React to lifecycle events
  event: async ({ event }) => { /* ... */ },
}

Registering the plugin

Add to opencode.json:

{
  "plugin": [
    ".opencode/plugins/my-plugin.ts"
  ]
}

Or install from npm:

{
  "plugin": [
    "my-published-plugin"
  ]
}

Anthropic / Claude plugin compatibility

OpenCode plugins are NOT the same as Anthropic's plugin format. Key differences:

| Aspect | OpenCode Plugin | Anthropic Plugin |

|--------|----------------|-----------------|

| Entry point | Async factory function | Manifest JSON |

| Tool args | Zod schema .shape | JSON Schema |

| Runtime | In-process (Bun/Node) | Sandboxed container |

| Auth | process.env | OAuth/API key in manifest |

| Distribution | npm / file path / URL | Anthropic marketplace |

To adapt an Anthropic plugin for OpenCode:

  • Rewrite the tool definitions using Zod instead of JSON Schema.
  • Move any auth from the manifest to env vars or opencode.json provider config.
  • Export the async factory function instead of the manifest.
  • HTTP-based Anthropic plugins can often be wrapped as MCP servers instead.

Example: plugin with multiple tools

import { z } from "zod";

export default async () => ({
  "experimental.chat.system.transform": async (_input: unknown, output: { system: string[] }) => {
    output.system.push("You have access to a note-taking system. Use save_note and list_notes.");
  },
  tool: {
    save_note: {
      description: "Save a note with a title and body.",
      args: z.object({
        title: z.string().describe("Note title"),
        body: z.string().describe("Note content"),
      }).shape,
      async execute(args: { title: string; body: string }) {
        const fs = await import("node:fs/promises");
        const path = `.opencode/notes/${args.title.replace(/[^a-zA-Z0-9-_]/g, "_")}.md`;
        await fs.mkdir(".opencode/notes", { recursive: true });
        await fs.writeFile(path, `# ${args.title}\n\n${args.body}\n`);
        return `Saved note: ${path}`;
      },
    },
    list_notes: {
      description: "List all saved notes.",
      args: {},
      async execute() {
        const fs = await import("node:fs/promises");
        try {
          const files = await fs.readdir(".opencode/notes");
          return files.filter(f => f.endsWith(".md")).join("\n") || "No notes yet.";
        } catch {
          return "No notes yet.";
        }
      },
    },
  },
});

Scaffold workflow

When the user describes what they want the plugin to do:

  • Create .opencode/plugins/<name>.ts with the plugin code.
  • Add it to opencode.json plugin array if not already present.
  • Tell the user to restart their session for the plugin to load.

How to use it

Copy the folder

Take devin-axis/create-plugin from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.