Plugin Composition

Use plugins when a set of tools should move as a unit. A plugin can package tool definitions, groups, adapters, and metadata, then be composed into one or more apps.

What you will build: two domain plugins composed into one MCP-ready app.

import { Ohtools, plugin } from "@bosun-sh/ohtools";

const issues = plugin("issues").tool("issues.list", {
  description: "List issues.",
  run: () => []
});

export const app = new Ohtools().use(issues);

Package A Domain Surface

Keep plugin IDs and tool IDs stable. They are part of the surface agents inspect and callers invoke.

import { plugin, jsonSchema } from "@bosun-sh/ohtools";

export const releases = plugin("releases")
  .metadata("owner", "platform")
  .tool("release.check", {
    description: "Check whether a release is ready.",
    input: jsonSchema<{ version: string }>({
      type: "object",
      properties: { version: { type: "string" } },
      required: ["version"],
      additionalProperties: false
    }),
    run: ({ version }) => ({ version, ready: true })
  });

Compose Plugins At The App Boundary

// docs-snippet: skip
import { Ohtools } from "@bosun-sh/ohtools";
import { mcpAdapter } from "@bosun-sh/ohtools/adapters/mcp";
import { issues } from "./plugins/issues";
import { releases } from "./plugins/releases";

export default new Ohtools({ name: "platform-tools" })
  .use(issues)
  .use(releases)
  .adapter(mcpAdapter({ stdio: true }));

Composition is deterministic. Conflicting IDs fail when the app builds, which is preferable to letting an agent discover ambiguity at runtime.

Validate The Package

Run plugin-level tests against definitions and app-level checks against the built registry. For public surfaces, also run CLI or MCP smoke commands.

Read Plugins Concept for the model and Production Patterns for release boundaries.