{"record":{"id":"970309238aaa8bd8","repo":"different-ai/openwork","slug":"plugin-empty","errorCode":"plugin_empty","errorMessage":"This plugin has no MCP servers, skills, commands, or agents OpenWork can install.","messagePattern":"This plugin has no MCP servers, skills, commands, or agents OpenWork can install\\.","errorType":"error_code","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"apps/server/src/claude-plugin-bundle.ts","lineNumber":450,"sourceCode":"      configObject: {\n        id: `${pluginId}/mcp`,\n        objectType: \"mcp\",\n        title: `${pluginName} MCP`,\n        description: null,\n        currentRelativePath: null,\n        status: \"active\",\n        updatedAt: null,\n        latestVersion: {\n          id: treeByPath.get(dotMcpPath)?.sha ?? `${pluginId}/mcp`,\n          rawSourceText: null,\n          normalizedPayloadJson: { mcpServers },\n        },\n      },\n    });\n  }\n\n  if (memberships.length === 0) {\n    throw new ApiError(400, \"plugin_empty\", \"This plugin has no MCP servers, skills, commands, or agents OpenWork can install.\");\n  }\n\n  const resolved: CloudPluginResolved = {\n    plugin: {\n      id: pluginId,\n      name: pluginName,\n      description,\n      updatedAt: null,\n    },\n    memberships,\n  };\n\n  const components: ClaudePluginComponent[] = [\n    ...Object.keys(mcpServers).map((name) => ({ type: \"mcp\" as const, name, description: null })),\n    ...fetched.map((component) => ({ type: component.type, name: component.title, description: component.description })),\n  ];\n\n  return {","sourceCodeStart":432,"sourceCodeEnd":468,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/claude-plugin-bundle.ts#L432-L468","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Verify the plugin actually ships MCP servers, skills, commands, or agents and that they are declared in the manifest sections OpenWork reads.","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.","Add at least one supported entry (e.g. a skill under skills/) to the plugin and re-publish.","Install the plugin's components individually if the plugin itself has no installable content."],"exampleFix":"// before (.claude-plugin/plugin.json)\n{ \"name\": \"empty-plugin\" }\n// after\n{ \"name\": \"empty-plugin\", \"skills\": [\"./skills/my-skill\"] }","handlingStrategy":"validation","validationCode":"const manifest = JSON.parse(readFileSync(\".claude-plugin/plugin.json\", \"utf8\"));\nconst hasContent = [manifest.mcpServers, manifest.skills, manifest.commands, manifest.agents]\n  .some((s) => Array.isArray(s) ? s.length > 0 : s != null);\nif (!hasContent) throw new Error(\"plugin has nothing OpenWork can install\");","typeGuard":null,"tryCatchPattern":"try {\n  await installPlugin(url);\n} catch (e) {\n  if (isApiError(e) && e.code === \"plugin_empty\") {\n    // warn user: plugin only ships unsupported features (e.g. hooks)\n  }\n}","preventionTips":["Ship at least one MCP server, skill, command, or agent in the plugin.","Remember hooks are skipped with a warning, not installed.","Check the warnings returned during resolution for skipped sections.","Verify content sections locally before publishing."],"tags":["plugin-install","empty-plugin","validation"],"backgroundTag":"empty-installable-content","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}