Files
IdeaSDK/docs/manifest.md

12 KiB

Manifest

Every plugin package has an idea-plugin.json file at the archive root.

For development installs, the same file must exist at the root of the plugin source directory selected in IdeA with Paramètres > Plugins > Installer depuis un dossier….

Minimal Plugin Directory

A plugin directory must contain the manifest at its root and a built ESM entrypoint matching the manifest main field.

my-plugin/
├── idea-plugin.json
├── package.json
├── tsconfig.json
├── src/
│   └── index.ts
└── dist/
    └── index.js

Create the directory:

mkdir -p my-plugin/src
cd my-plugin
npm init -y
npm install --save-dev typescript @idea/plugin-sdk

Use a package script that emits JavaScript into dist/:

{
  "type": "module",
  "scripts": {
    "build": "tsc -p tsconfig.json"
  },
  "devDependencies": {
    "@idea/plugin-sdk": "^0.3.0",
    "typescript": "^5.0.0"
  }
}

When developing against a local SDK checkout instead of a published package, replace the SDK dependency with a file: reference, for example:

{
  "devDependencies": {
    "@idea/plugin-sdk": "file:../IdeaSDK",
    "typescript": "^5.0.0"
  }
}

Minimal tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true
  },
  "include": ["src"]
}

Minimal src/index.ts:

import type { IdeAPluginModule } from "@idea/plugin-sdk";

const plugin: IdeAPluginModule = {
  activate(ctx) {
    ctx.logger.info("plugin activated", { pluginId: ctx.pluginId });
  }
};

export default plugin;

Build before installing or reloading:

npm run build

Then install the my-plugin/ directory in IdeA. For hot reload, keep installing from the directory, rebuild after source changes, then run idea_plugin_reload for the installed plugin id. Do not install from a ZIP archive for a hot-reload development loop.

{
  "ideaPluginManifestVersion": 1,
  "id": "com.example.hello-plugin",
  "displayName": "Hello Plugin",
  "publisher": "Example",
  "version": "0.1.0",
  "description": "Example IdeA plugin.",
  "main": "dist/index.js",
  "engines": {
    "idea": ">=0.1.0"
  },
  "trustLevel": "full",
  "capabilities": ["ui", "tooling"],
  "activationScope": "app",
  "contributes": {
    "skills": [],
    "menus": [],
    "menuItems": [],
    "layouts": [],
    "mcpServers": []
  }
}

Required Fields

  • ideaPluginManifestVersion: currently 1.
  • id: stable lowercase id using letters, digits, dots and dashes. It must start and end with an alphanumeric character.
  • displayName: human-readable plugin name.
  • version: semver version.
  • main: package-relative ESM entrypoint loaded by IdeA.
  • trustLevel: currently "full".

Optional Fields

  • description, publisher.
  • engines.idea: host compatibility hint.
  • activationScope: "app" by default, or "project" when activation needs a focused project immediately.
  • capabilities: "ui", "tooling" and/or "mcp".

Contributions

contributes.skills declares read-only agent skills shipped inside the IdeA plugin package. These skills are IdeA-native: IdeA can expose them in project plugin settings, assign them to agents, inject them into the agent skill catalogue, and serve their Markdown body through idea_skill_read for every supported harness that receives IdeA capabilities, including Claude Code, Codex, OpenCode local and OpenCode cloud.

{
  "contributes": {
    "skills": [
      {
        "id": "unity.build-debug",
        "name": "unity-build-debug",
        "description": "Diagnose Unity build failures.",
        "kind": "workflow",
        "path": "skills/unity-build-debug/SKILL.md"
      }
    ]
  }
}

Skill contribution rules:

  • id is stable within the plugin and is used by project/agent assignments.
  • name is the agent-facing name shown in the injected skill catalogue and accepted by idea_skill_read.
  • description is the short affordance shown before the agent loads the skill.
  • kind defaults to "workflow"; "reference" is also supported.
  • path must be a package-relative Markdown file path ending in .md.
  • Installing a plugin globally is not enough: a project must enable the plugin, then assign the skill to the target agent.

See Project Plugin Assignments for the .ideai/project-plugins.json schema, validation rules, assignment order, and agent relaunch requirements.

contributes.layouts is the only surface for plugin layouts. The same layout contribution can be mounted inside an IdeA layout cell or opened in a detached OS window. Do not add a separate manifest contribution type for windows.

For a command that opens a visible plugin window, declare both the menu command and the layout type:

{
  "ideaPluginManifestVersion": 1,
  "id": "com.example.unity-plugin",
  "displayName": "Unity Developer Tools",
  "publisher": "Example",
  "version": "0.1.0",
  "description": "Unity integration for IdeA.",
  "main": "dist/index.js",
  "engines": {
    "idea": ">=0.1.0"
  },
  "trustLevel": "full",
  "capabilities": ["ui", "tooling"],
  "activationScope": "app",
  "contributes": {
    "menus": [
      {
        "id": "unity-plugin.menu",
        "label": "Unity Developer Tools",
        "topLevel": true,
        "order": 100
      }
    ],
    "menuItems": [
      {
        "id": "unity-plugin.health.item",
        "targetMenuId": "unity-plugin.menu",
        "label": "Health",
        "command": "unity-plugin.health",
        "order": 10,
        "when": "projectOpen"
      }
    ],
    "layouts": [
      {
        "type": "unity-plugin.health",
        "label": "Unity Health",
        "component": "UnityHealth",
        "order": 10
      }
    ]
  }
}

capabilities: ["ui"] is required when a plugin relies on layouts or windows as human-facing UI. Add "tooling" when the same plugin uses workspace, task, tooling or terminal services. The contributes.layouts[].type value is the public id used by both ctx.layouts.register({ type, component }) and ctx.services.windows.open({ layoutType }).

Contribution ids are part of the runtime contract:

  • commands can only register ids declared by contributes.menuItems[*].command;
  • layouts can only register type values declared by contributes.layouts;
  • services.windows.open({ layoutType }) only accepts a layout type declared by the calling plugin.

MCP Servers And Agent Tools

contributes.mcpServers is the IdeA-native contract for plugin-provided agent tools. A plugin does not register individual MCP tools from activate(ctx). Instead, the plugin ships or references a stdio MCP server process, declares it in the manifest, and that server advertises its own tools through the MCP tools/list protocol.

{
  "capabilities": ["mcp"],
  "contributes": {
    "mcpServers": [
      {
        "id": "unity-editor",
        "displayName": "Unity Editor Tools",
        "command": "scripts/unity-mcp-server.mjs",
        "args": [],
        "cwd": "${pluginRoot}",
        "transport": "stdio",
        "autoStart": true,
        "tools": [
          {
            "name": "unity_get_scene",
            "description": "Read the active Unity scene."
          },
          {
            "name": "unity_run_tests"
          }
        ]
      }
    ]
  }
}

MCP server contribution rules:

  • id is stable within the plugin and is used by project/agent assignments.
  • displayName is the human-readable server name shown in plugin settings.
  • transport must currently be "stdio".
  • autoStart: true is required for IdeA to start the server during plugin MCP reconciliation. Non-auto-start servers are manifest metadata only today.
  • A relative command is resolved under the installed plugin package root.
  • A literal absolute command is rejected unless allowAbsoluteCommand: true is set. Use this only for an intentional dependency on a host binary.
  • A command that becomes absolute after ${pluginRoot} or ${appDataDir} substitution is allowed and is passed through as substituted.
  • cwd defaults to ${pluginRoot} when omitted.
  • tools is optional static display metadata for IdeA's capability-assignment UI. Each entry has an exact non-empty name and an optional non-empty description; names must be unique within the server.
  • This metadata does not replace or cache MCP tools/list, and declaring a tool here does not register it or prove that the running server exposes it.

The tools an agent can call are assigned with the triplet pluginId/serverId/toolName in project plugin settings. toolName must match a tool name advertised by the MCP server. IdeA does not currently provide a runtime API such as ctx.mcp.registerTool() or ctx.mcp.registerServer().

For multi-harness workflows, keep contributes.skills as instructions and expose executable behavior through assigned MCP tools. Do not make skill Markdown depend on filesystem paths relative to the Markdown returned by idea_skill_read; that body is served as read-only content, not mounted as a working directory. If a skill needs packaged scripts, put the script behind a manifest-declared MCP server and tell the agent to call the assigned tool.

MCP Server Paths

contributes.mcpServers entries are resolved by the host before starting a declared MCP server. In command, args, env and cwd, the host expands:

  • ${pluginRoot} to the installed plugin package root.
  • ${appDataDir} to the host-owned application data directory.

There is currently no ${node}, ${runtimeNode}, ${hostNode} substitution and no runtime field. IdeA does not provide a plugin-scoped Node.js runtime for MCP servers.

This string substitution is limited to manifest-declared MCP server startup. Runtime handlers receive the same installed package location separately as ctx.pluginRoot; ctx.services.tasks.runCommand() does not perform placeholder substitution, and workspace APIs remain project-confined.

command is not shell syntax. IdeA starts it directly and passes args separately. Do not rely on shell startup files, shell aliases, or a user's login shell PATH.

Prefer a packaged executable for plugin-owned MCP servers:

{
  "command": "scripts/unity-mcp-server",
  "transport": "stdio",
  "autoStart": true
}

Because the command is package-relative, IdeA resolves it to ${pluginRoot}/scripts/unity-mcp-server. If this file is a script, its shebang and executable bit must be valid in the installed package. A JavaScript shebang such as #!/usr/bin/env node still depends on a node binary visible to the IdeA app process, not necessarily the user's interactive shell.

For JavaScript MCP servers that must not depend on the user's shell PATH, use one of these approaches:

  • Ship an executable server that includes its runtime, or install a managed runtime under plugin/app-owned storage and point command at that executable with ${pluginRoot} or ${appDataDir}.
  • Declare an explicit host dependency with an absolute command such as /usr/bin/node, set allowAbsoluteCommand: true, and keep plugin-owned script paths in args.

Avoid command: "node" for a packaged MCP server. With allowAbsoluteCommand: false, it is treated as package-relative and resolves to ${pluginRoot}/node. With allowAbsoluteCommand: true, it is passed as a host command name and depends on the environment inherited by IdeA, which may differ from an interactive shell.

The same rule applies to shell wrappers. command: "bash" resolves to ${pluginRoot}/bash unless absolute-command handling is explicitly enabled. Use a package-relative wrapper executable, or use an absolute shell such as /bin/bash with allowAbsoluteCommand: true when that host dependency is intentional.

Validation

Use the SDK validator in tests or build tooling:

import { assertPluginManifest } from "@idea/plugin-sdk";

assertPluginManifest(JSON.parse(manifestText));