Skip to content

Tools

A tool is the thing the folder agent calls — an MCP tool the model can pick when a user’s request matches it. This page covers how to declare one and shape its result; the field-by-field contract is on the manifest reference.

{
"tools": [
{
"id": "count_words",
"title": "Count words",
"description": "Counts the words in a text file. Use when the user asks how long a document is.",
"schema": "./tools/count-words.schema.json",
"handler": "./tools/count-words.js",
"timeout": "30s",
"fileExtensions": [".md", ".txt"]
}
]
}
  • id is the MCP tool name the model calls by. It can’t contain a dot — dots are reserved for namespace prefixing.
  • title is a short label; description is what the model reads when deciding whether this tool is the right one for the turn.
  • schema points at a JSON-Schema file describing the tool’s arguments, relative to the plugin root.
  • handler points at the JavaScript module that runs when the tool is called. Required for runtime: "node", forbidden for runtime: "process" — see Process runtime.
  • timeout bounds how long a call may run.
  • fileExtensions scopes which files the app’s context menus offer this tool for.

This mirrors the word-count plugin from Your first plugin, which reads one file under permissions.fs.read.parameter: true rather than listing a folder — see Permissions for that shape.

description is not documentation for a person browsing a plugin list — it’s the only thing the model sees when choosing between tools. Say what the tool does and when to use it, in plain language, the way you’d brief a colleague picking a task off a list. A vague description (“Handles files”) gets skipped or misused; a description that names the situation (“Use when the user asks how long a document is”) gets picked correctly.

A node handler’s return value reaches the agent as one MCP text block, shaped by what you return:

  • A string reaches the agent verbatim.
  • Anything else (an object, array, number) is JSON.stringify’d into a single text block.

If a handler needs to return more than text — an image alongside a caption, for example — return an MCP content envelope instead, and it’s forwarded verbatim:

return {
content: [
{ type: 'text', text: 'Rendered page 1' },
{ type: 'image', data: pngBase64, mimeType: 'image/png' },
],
};

Each block in content must be well-formed. A malformed envelope falls back to the plain text wrapping — nothing is dropped silently.

Tool names share a single namespace across the agent’s whole toolset. The agent loop registers built-in tools first, then host tools, then plugin tools — a plugin tool whose id is already taken is skipped, with a warning that names the plugin it came from. A plugin can’t shadow a built-in or a host tool of the same name.

Pick ids that are unlikely to collide. Prefer something specific like read_text_document over a generic name like read_document that a built-in or another plugin is more likely to already use.

Older manifests declared a tool with name and inline parameters instead of id and schema. That shape is refused at load time with a message asking for the current fields — update the manifest rather than working around the rejection.