different-ai/openwork · error · ApiError

plugin_empty

plugin_empty

Error message

This plugin has no MCP servers, skills, commands, or agents OpenWork can install.

What it means

After parsing the manifest and expanding all sections, resolveClaudePluginBundle checks the collected memberships (MCP servers, skills, commands, agents). If none were produced it throws this 400 ApiError — the plugin parsed fine but offers nothing OpenWork can install.

Source

Thrown at apps/server/src/claude-plugin-bundle.ts:450

      configObject: {
        id: `${pluginId}/mcp`,
        objectType: "mcp",
        title: `${pluginName} MCP`,
        description: null,
        currentRelativePath: null,
        status: "active",
        updatedAt: null,
        latestVersion: {
          id: treeByPath.get(dotMcpPath)?.sha ?? `${pluginId}/mcp`,
          rawSourceText: null,
          normalizedPayloadJson: { mcpServers },
        },
      },
    });
  }

  if (memberships.length === 0) {
    throw new ApiError(400, "plugin_empty", "This plugin has no MCP servers, skills, commands, or agents OpenWork can install.");
  }

  const resolved: CloudPluginResolved = {
    plugin: {
      id: pluginId,
      name: pluginName,
      description,
      updatedAt: null,
    },
    memberships,
  };

  const components: ClaudePluginComponent[] = [
    ...Object.keys(mcpServers).map((name) => ({ type: "mcp" as const, name, description: null })),
    ...fetched.map((component) => ({ type: component.type, name: component.title, description: component.description })),
  ];

  return {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Verify the plugin actually ships MCP servers, skills, commands, or agents and that they are declared in the manifest sections OpenWork reads.
  2. Check the warning about hooks — if hooks are the only content, OpenWork cannot install this plugin yet; remove it or ask the vendor for supported content.
  3. Add at least one supported entry (e.g. a skill under skills/) to the plugin and re-publish.
  4. Install the plugin's components individually if the plugin itself has no installable content.

Example fix

// before (.claude-plugin/plugin.json)
{ "name": "empty-plugin" }
// after
{ "name": "empty-plugin", "skills": ["./skills/my-skill"] }
Defensive patterns

Strategy: validation

Validate before calling

const manifest = JSON.parse(readFileSync(".claude-plugin/plugin.json", "utf8"));
const hasContent = [manifest.mcpServers, manifest.skills, manifest.commands, manifest.agents]
  .some((s) => Array.isArray(s) ? s.length > 0 : s != null);
if (!hasContent) throw new Error("plugin has nothing OpenWork can install");

Try / catch

try {
  await installPlugin(url);
} catch (e) {
  if (isApiError(e) && e.code === "plugin_empty") {
    // warn user: plugin only ships unsupported features (e.g. hooks)
  }
}

Prevention

When it happens

Trigger: A plugin whose manifest declares no mcpServers, skills, commands, or agents (or whose declared entries all fail to yield memberships, e.g. only hooks, which are skipped with a warning).

Common situations: Plugin consisting solely of unsupported features like hooks (which are skipped); plugin manifest with empty arrays; plugin author expected commands to be discovered but they live outside the manifest sections OpenWork reads.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/970309238aaa8bd8. Report an issue: GitHub.