mastra-ai/mastra · error · Error
Plugin tool "${name}" must be an object with a tool property
Error message
Plugin tool "${name}" must be an object with a tool property What it means
normalizePluginToolEntries iterates a plugin's exported tool map and requires every entry to be an object containing a `tool` property (checked by isToolEntryObject). A bare McpServer/Tool function or any non-object value throws 'Plugin tool "<name>" must be an object with a tool property'. This enforces the { tool, render? } wrapper format so render configs can be extracted uniformly.
Source
Thrown at mastracode/sdk/src/plugins/loader.ts:307
if (plugin.instructions === undefined) return undefined;
const instructions =
typeof plugin.instructions === 'function' ? await plugin.instructions(context) : plugin.instructions;
if (typeof instructions !== 'string') {
throw new Error('Plugin instructions must be a string');
}
const trimmed = instructions.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
function normalizePluginToolEntries(entries: MastraCodePluginToolEntries): {
tools: MastraCodePluginTools;
renderConfigs: Record<string, MastraCodeToolRenderConfig>;
} {
const tools: MastraCodePluginTools = {};
const renderConfigs: Record<string, MastraCodeToolRenderConfig> = {};
for (const [name, entry] of Object.entries(entries)) {
if (!isToolEntryObject(entry)) {
throw new Error(`Plugin tool "${name}" must be an object with a tool property`);
}
tools[name] = entry.tool;
if (entry.render) renderConfigs[name] = entry.render;
}
return { tools, renderConfigs };
}
function isToolEntryObject(entry: MastraCodePluginToolEntries[string]): entry is MastraCodePluginToolEntries[string] {
if (!entry || typeof entry !== 'object' || !('tool' in entry)) return false;
const tool = (entry as { tool?: unknown }).tool;
return !!tool && typeof tool === 'object' && !Array.isArray(tool);
}
function validatePluginConfigSchema(schema: unknown): MastraCodePluginConfigSchema | undefined {
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return undefined;
const validated: MastraCodePluginConfigSchema = {};
for (const [key, option] of Object.entries(schema)) {
if (!option || typeof option !== 'object' || Array.isArray(option)) continue;View on GitHub (pinned to 75dd419e61)
Solutions
- Wrap each tool entry: `tools: { myTool: { tool: myToolImplementation } }`.
- Add the `render` property alongside `tool` only if you supply a MastraCodeToolRenderConfig; it is optional.
- Check for entries that are undefined/null due to conditional construction and remove or populate them.
- Compare against another working plugin's tool export shape in the same repo.
Example fix
// before
export const tools = { weatherLookup: createTool({ ... }) }
// after
export const tools = { weatherLookup: { tool: createTool({ ... }) } } Defensive patterns
Strategy: validation
Validate before calling
for (const [name, entry] of Object.entries(plugin.tools ?? {})) {
if (!entry || typeof entry !== 'object' || !('tool' in entry)) {
throw new TypeError(`Plugin tool "${name}" must be wrapped as { tool, render? }`);
}
} Type guard
function isToolEntryObject(v: unknown): v is { tool: unknown; render?: MastraCodeToolRenderConfig } {
return typeof v === 'object' && v !== null && 'tool' in v;
} Try / catch
try {
await manager.registerPlugin(plugin);
} catch (err) {
if (err instanceof Error && /must be an object with a tool property/.test(err.message)) {
console.error(`Malformed tool export: ${err.message} — wrap entries as { tool: ... }`);
} else throw err;
} Prevention
- Always use the { tool, render? } wrapper shape in plugin tool exports, even for single tools.
- Define plugin tools with a typed helper (e.g. definePluginTools()) that enforces the entry shape at compile time.
- Add a smoke test that loads each plugin and asserts normalizePluginToolEntries succeeds.
When it happens
Trigger: Exporting tools from a plugin as raw tool instances instead of wrappers, e.g. `tools: { searchTool }` or `tools: { searchTool: createTool({...}) }` instead of `tools: { searchTool: { tool: createTool({...}) } }`; or a value in the map is null/undefined/a function.
Common situations: Upgrading from an older plugin API where raw tool objects were accepted; copy-pasting tool definitions from core examples that do not use the plugin wrapper; a tool entry left undefined after a failed conditional assignment; a typo like `{ tool: myTool }` written as `myTool` at the entry level.
Related errors
- Plugin instructions must be a string
- Plugin tool "${toolName}" is no longer available
- mastra-wrapper plugin did not return code, there is likely a
- @mastra/livekit: the agent requested tool approval or suspen
- MastraFactory: integration tool '${name}' from '${ownerId}'
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/dfc71f53b02a6f1d.
Report an issue: GitHub.