Skip to main content
Extensions

Plugins

options.plugins is used to load local plugin directories into the current session. The SDK converts each local plugin into a --plugin-dir <path> startup argument; commands, agents, skills, and MCP servers contained in the plugin all take part in capability discovery for the session.

Loading Local Plugins

import { query } from '@qoder-ai/qoder-agent-sdk';

const q = query({
  prompt: 'List the commands and agents contributed by the current plugins',
  options: {
    plugins: [
      { type: 'local', path: '/path/to/my-plugin' },
    ],
  },
});

const init = await q.initializationResult();
console.log(init.commands);
console.log(init.agents);
console.log(init.skills);

const plugins = await q.listPlugins();
console.log(plugins);
You can pass multiple local plugins at once; multiple --plugin-dir arguments will be written in order:
const q = query({
  options: {
    plugins: [
      { type: 'local', path: '/path/to/plugin-a' },
      { type: 'local', path: '/path/to/plugin-b' },
    ],
  },
});
💡 In Python, query() is a one-shot message stream and can't query the init response after the handshake. To read plugin-contributed commands / agents / skills, use QoderSDKClient and call get_server_info() after connect(), or capture SystemMessage(subtype='init') in the stream and read message.data yourself. For the full plugin inventory, call client.list_plugins().

Plugin Directory Layout

A local plugin typically contains:
my-plugin/
  .qoder-plugin/plugin.json
  commands/
  agents/
  skills/
  .mcp.json
.qoder-plugin/plugin.json declares the plugin name, version, and description. The other directories are automatically scanned by the CLI based on file type. The SDK does not validate whether the path exists or is well-formed:
  • A non-existent --plugin-dir path is silently ignored in SDK mode; the session still initializes normally.
  • Broken frontmatter or .mcp.json does not block init; broken commands simply do not appear in the init response.
  • To diagnose plugin load failures explicitly, the only fallback today is the post-reload error_count.

Plugin-contributed Slash Commands

commands/*.md in a plugin appears in the initialization result, named in the <plugin>:<cmd> qualified form:
const commands = await q.supportedCommands();
console.log(commands.map((cmd) => cmd.name));

Plugin-contributed Agents

agents/*.md in a plugin appears in the initialization result; the SDK offers a convenience method to fetch the list:
const agents = await q.supportedAgents();
console.log(agents.map((agent) => agent.name));

Plugin-contributed Skills

skills/<name>/SKILL.md in a plugin is registered under the plugin-qualified name (plugin:skill). Control its main-session context visibility and invocation policy via options.skills: pass qualified names to enable specific skills, 'all' to enable all discovered ones, or omit for CLI default policy. See the Skills docs.

Plugin-contributed MCP Servers

.mcp.json in a plugin is launched by the CLI and included in MCP status:
const servers = await q.mcpServerStatus();
console.log(servers);

Temporarily Overriding an Installed Plugin with the Same Name

Plugins loaded via options.plugins are session-scoped. During the current session, if a local plugin shares a name with an installed plugin, the local plugin takes priority in capability discovery. This is useful for plugin development, debugging, and canary testing.
const q = query({
  options: {
    // The local version only takes effect for this query session and does not
    // touch the user's global install state.
    plugins: [{ type: 'local', path: './my-plugin-dev' }],
  },
});

Reloading Plugins at Runtime

When the plugin directory changes, call reloadPlugins() / reload_plugins() within the same session to have the CLI rescan plugin resources.
const refreshed = await q.reloadPlugins();

console.log(refreshed.commands);
console.log(refreshed.agents);
console.log(refreshed.plugins);
console.log(refreshed.mcpServers);
console.log(refreshed.error_count);
Typical use cases:
  • Refreshing after adding or deleting commands/*.md during plugin development.
  • After installing or updating a local plugin without restarting the host application.
  • When the host UI needs to display commands, agents, plugins, and MCP status after a reload.
Note: in Python, reload_plugins() is only meaningful in QoderSDKClient (streaming) mode; the one-shot query() stream has no runtime control channel.

Options Reference

Field (TypeScript / Python)Description
plugins / pluginsLoads local plugin directories; { type: 'local', path } is the common form
settings / settingsSettings passed through to the CLI; may include enabledPlugins, pluginConfigs, etc.
settingSources / setting_sourcesControls which settings sources the CLI reads
Use settings.enabledPlugins to control plugin enablement and settings.pluginConfigs to supply plugin config such as MCP server replacements.

Return Value Reference

Initialization result

initializationResult() (TypeScript) / client.get_server_info() (Python) returns the commands, agents, skills, and other initialization resources discovered in the session. It has no stable plugin-inventory field; full return type in SDK References.
{
    "commands": [
        {"name": "plugin-a:greet", "description": "...", "argumentHint": "..."},
        ...
    ],
    "agents": [
        {"name": "plugin-a:helper", "description": "...", "model": "sonnet"},
        ...
    ],
    "skills": [
        {"name": "plugin-a:echo", "description": "...", "source": "plugin"},
        ...
    ],
    # Also includes models / account / output_style and other fields
}

Listing plugins

listPlugins(): Promise<PluginDetails[]>;
Reads the CLI's plugin inventory with a resource summary per plugin. Host UIs showing the plugin inventory should use this method rather than the plugins field of the initialization result. Each item is a PluginDetails object with id, name, source, path, version, scope, enabled, canDisable, and a resources summary grouped into skills, agents, mcpServers, commands, and hooks.

Reloading plugins

reloadPlugins() / reload_plugins() returns:
type SDKControlReloadPluginsResponse = {
  commands: Array<{ name: string; description?: string; argumentHint?: string }>;
  agents: Array<{ name: string; description?: string }>;
  plugins: Array<{ name: string; path: string; source?: string }>;
  mcpServers: Array<Record<string, unknown>>;
  error_count: number;  // Number of plugins that failed to load in this reload
};

Best Practices

  • Separate initialization resources from the plugin inventory: show discovered commands/agents/skills from the initialization result; show the plugin inventory via listPlugins() / list_plugins(). The skills there are a discovery list, not the main session's invocable set.
  • Use options.plugins during plugin development: It only affects the current session without modifying the user's global install state.
  • Prepare user messaging before reloads: a reload rescans the disk and may briefly change the available resource lists—keep the UI in sync.
  • Check error_count for diagnostics: If error_count > 0 after reload, some plugin resources failed to load; display the source to the user.

Current Limitations

  • On some qodercli versions, a local plugin's commands, agents, and MCP appear in the initialization result while its skills may be missing from the discovery list—a CLI-side discovery issue.
  • In the current qodercli implementation, nonexistent --plugin-dir paths are silently ignored in SDK mode; the only explicit diagnostic today is the post-reload error_count.
  • Reload is a runtime control API exposed by the SDK; if your CLI version returns internal this._plugins errors, upgrade to a fixed qodercli.