Tools
Tools are how your plugin talks to AI agents. Each tool is a named, typed function with a Zod input schema, a Zod output schema, and a handler that runs directly in the browser page — with full access to your authenticated session. The agent calls it, the platform validates the input, and your handler executes.
Import
import { defineTool } from '@opentabs-dev/plugin-sdk';
import type { ToolDefinition, ToolHandlerContext } from '@opentabs-dev/plugin-sdk';defineTool(config)
Type-safe factory function that returns a ToolDefinition. It's an identity function — no runtime behavior, just generic inference for TypeScript.
function defineTool<
TInput extends z.ZodObject<z.ZodRawShape>,
TOutput extends z.ZodType,
>(config: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;ToolDefinition
interface ToolDefinition<
TInput extends z.ZodObject<z.ZodRawShape>,
TOutput extends z.ZodType,
> {
name: string;
displayName?: string;
description: string;
summary?: string;
icon?: LucideIconName;
group?: string;
input: TInput;
output: TOutput;
handle(params: z.infer<TInput>, context?: ToolHandlerContext): Promise<z.infer<TOutput>>;
}Properties
| Property | Type | Description |
|---|---|---|
name | string | Tool name in snake_case (e.g., 'send_message'). Auto-prefixed with the plugin name for MCP registration (slack_send_message). |
displayName | string | Human-readable name shown in the side panel (e.g., 'Send Message'). Optional — auto-derived from name during build when omitted. |
description | string | Description shown to AI agents. Be specific — this is how the agent decides when to use the tool. |
summary | string | Short human-readable summary for the UI. Falls back to description if omitted. Useful when the full description is too verbose for compact UI layouts. |
icon | LucideIconName | Lucide icon in kebab-case (e.g., 'message-square'). Displayed in the side panel. Optional — defaults to 'wrench' during build when omitted. See lucide.dev/icons. |
group | string | Optional group name for visual grouping in the side panel. Tools with the same group render together under a section header. |
input | z.ZodObject | Zod object schema for tool input. Serialized to JSON Schema for MCP registration and used for server-side validation. |
output | z.ZodType | Zod schema describing the output shape. Used for manifest generation and MCP tool registration. |
handle | (params, context?) => Promise | Async handler that executes in the browser page context. Input is pre-validated against the input schema. The optional context parameter provides progress reporting. |
ToolHandlerContext
Optional second argument to handle(), injected by the adapter runtime. Provides progress reporting for long-running operations.
interface ToolHandlerContext {
reportProgress(opts: ProgressOptions): void;
}
interface ProgressOptions {
progress?: number; // Current value (e.g., 3 of 10). Omit for indeterminate progress.
total?: number; // Total expected value (e.g., 10). Omit for indeterminate progress.
message?: string; // Human-readable status (e.g., 'Processing page 3')
}The context parameter is optional — tools that don't need progress reporting can omit it. Always use optional chaining (context?.reportProgress(...)) for safety. See Streaming & Progress for the full guide.
Example
import { defineTool, postJSON, ToolError } from '@opentabs-dev/plugin-sdk';
import type { ToolHandlerContext } from '@opentabs-dev/plugin-sdk';
import { z } from 'zod';
export const sendMessage = defineTool({
name: 'send_message',
displayName: 'Send Message',
description: 'Send a message to a Slack channel by name or ID.',
icon: 'message-square',
input: z.object({
channel: z.string().describe('Channel name or ID'),
text: z.string().describe('Message text'),
}),
output: z.object({
ok: z.boolean(),
ts: z.string().describe('Message timestamp ID'),
}),
async handle(params, context?: ToolHandlerContext) {
const result = await postJSON<{ ok: boolean; ts: string }>(
'/api/chat.postMessage',
{ channel: params.channel, text: params.text },
);
if (!result || !result.ok) {
throw ToolError.internal('Failed to send message');
}
return { ok: result.ok, ts: result.ts };
},
});Zod Schema Rules
Tool schemas are serialized to JSON Schema via z.toJSONSchema() at build time. Keep schemas serialization-compatible:
- No
.transform()— transforms cannot be represented in JSON Schema. Normalize input inhandle()instead. - No
.pipe()or.preprocess()— these produce runtime-only behavior that breaks serialization. .refine()callbacks must not throw — Zod 4 runs refine callbacks even when the base validator fails. Wrap any throwing logic in try-catch and returnfalseon error.- Stick to declarative types — primitives, objects, arrays, unions, literals, enums, and standard validations (
.min(),.max(),.url(), etc.) serialize cleanly.
Name Prefixing
Tool names are automatically prefixed with the plugin name when registered with the MCP server. A tool named send_message in a plugin named slack becomes slack_send_message at the MCP level. You define just the unprefixed name.
Tool names must match ^[a-z][a-z0-9]*(_[a-z0-9]+)*$ — lowercase snake_case, starting with a letter.
Last Updated: 10 Mar, 2026