Skip to content

Extensions ​

An extension packages capabilities that would otherwise live under bot/, such as tools, skills, and connections. One mount file adds them under a namespace without copying their source into your project.

Extensions and Cursor plugins run as trusted host code. A namespace prevents name collisions but doesn't sandbox the package, so review the package before mounting it.

Mount an extension ​

Install the package and add one file under bot/extensions/. The filename becomes the namespace, so this mount contributes names such as ci__failed_jobs and ci__triage. The __ separator is reserved, so root contributions can't impersonate a mounted one.

ts
// bot/extensions/ci.ts
import ci from "@acme/ci-triage";

export default ci({
  org: "acme",
  token: () => process.env.BUILDKITE_TOKEN ?? "",
});

Pass token as a function so it reads BUILDKITE_TOKEN when a tool calls it.

The extension validates its settings at load, so a missing or mistyped setting fails agent-sdk validate.

Extension instructions follow the root agent's instructions. The root agent still needs its own bot/instructions.md.

Inspect a mount ​

Run discovery after mounting or upgrading an extension:

text
agent-sdk validate --dir .
agent-sdk info --dir . --json

info lists each mount and every namespaced name it adds. The same data appears under extensions in GET /v1/info.

info also shows the egress hosts and secret names each mount adds to a hosted deployment, and agent-sdk deploy names every extension that adds egress.

Adjust a mounted extension ​

To replace or remove one contribution, mount as a directory. Put the package mount in extension.ts and overlay the short name beneath it.

text
bot/extensions/ci/
  extension.ts
  tools/
    job_log.ts
    retry_job.ts
ts
// bot/extensions/ci/tools/job_log.ts
import { defineTool } from "@grok/agent-sdk/tools";
import {
  jobLogInput,
  readRedactedLog,
} from "../../../lib/ci.js";

export default defineTool({
  description: "Read a redacted CI log.",
  inputSchema: jobLogInput,
  execute: readRedactedLog,
});
ts
// bot/extensions/ci/tools/retry_job.ts
import { disableTool } from "@grok/agent-sdk/extensions";

export default disableTool();

The first file replaces ci__job_log, and the second removes ci__retry_job. Use the matching disable* helper for another slot, or generic disable() when the file location already identifies it. Validation fails when an overlay names a contribution the package doesn't provide.

Mount a Cursor plugin ​

cursorPlugin() mounts a vendored Cursor or Claude plugin the same way. Skills, commands, agents, and MCP servers become Agent SDK skills, subagents, and connections.

ts
// bot/extensions/notion.ts
import { cursorPlugin } from "@grok/agent-sdk/extensions";

export default cursorPlugin({
  path: "plugins/notion-workspace",
  variables: {
    NOTION_TOKEN: "${NOTION_TOKEN}",
  },
});

path is relative to the project root. variables fills the plugin's MCP placeholders with ${NAME} templates read when the connection opens. An unresolved placeholder fails validation instead of mounting a partial server.

Plugin rules and hooks aren't mounted. Overlay a generated mcp-connections/<server>.ts file to replace or disable one plugin server. OAuth-backed plugin servers use Host MCP OAuth.

Extension contents ​

Ships under the namespaceIgnored
Instructions, tools, skills, MCP and host connectionsagent.ts, storage, OpenTelemetry
Hooks, channels, schedules, and subagentsPlayground code, custom sandbox backends
Artifact kinds and workspace seed filesNested extensions

agent-sdk validate reports unsupported paths.

Build an extension ​

Lay out the package like the shareable part of bot/. The root extension.ts supplies a description and validates the consumer's settings.

text
@acme/ci-triage/
  package.json
  extension.ts
  instructions.md
  tools/
    failed_jobs.ts
    job_log.ts
  skills/
    triage.md
  lib/
    buildkite.ts
ts
// extension.ts
import { defineExtension } from "@grok/agent-sdk/extensions";
import { z } from "zod";

export default defineExtension({
  root: import.meta.dirname,
  description: "Investigate failing Buildkite jobs.",
  config: z.object({
    org: z.string(),
    token: z.function().args().returns(z.string()),
    logTailLines: z.number().int().positive().default(200),
  }),
  hosting: () => ({
    egressDomains: ["api.buildkite.com"],
    secretNames: ["BUILDKITE_TOKEN"],
  }),
});

Package modules import this definition and read the validated extension.config, with defaults applied. hosting adds its hosts and secret names to the mounting agent's deployment. Publish the built entry point with its slot directories and prose assets beside it.

Keep extension instructions and tool descriptions namespace-neutral because the consumer chooses the prefix. Project layout covers each slot's contract.