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.
Declaring a tool
Section titled “Declaring a tool”{ "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"] } ]}idis the MCP tool name the model calls by. It can’t contain a dot — dots are reserved for namespace prefixing.titleis a short label;descriptionis what the model reads when deciding whether this tool is the right one for the turn.schemapoints at a JSON-Schema file describing the tool’s arguments, relative to the plugin root.handlerpoints at the JavaScript module that runs when the tool is called. Required forruntime: "node", forbidden forruntime: "process"— see Process runtime.timeoutbounds how long a call may run.fileExtensionsscopes 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.
Writing the description for the model
Section titled “Writing the description for the model”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.
Handler result shapes
Section titled “Handler result shapes”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.
One name, one tool
Section titled “One name, one tool”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.
Legacy shape
Section titled “Legacy shape”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.
- Actions — call this same tool from a right-click menu, no model turn
- Permissions — what a handler is allowed to touch
- Manifest reference — the full
tools[]field table
