Full lifecycle
Static declaration vs. dynamic registration
Capability registration in Finch is generally two-phase: the manifest declares a slot, and code fills in the behavior.
| Static manifest declaration | Dynamic registration in activate() | |
|---|---|---|
| Determines | Whether a button/container exists, its icon, its tooltip | The badge text, menu contents, click behavior |
| Read when | At install and startup | On-demand, during user interaction |
| Can it hide | The declared slot always remains | getBadge() can throw to hide the button |
This design exists because Finch needs to render the UI skeleton before a mini tool is even activated — static declarations keep the UI from flickering.
Note: getMenu() returning an empty array doesn't make the button disappear. Visibility is controlled by the manifest.
Three capability exit points
Capabilities: how mini tools cooperate
Mini tools never import each other directly — they cooperate through named interfaces. Both the provider and the consumer must declare this in the manifest:
{
"provides": { "capabilities": ["my.feature"] },
"requires": { "capabilities": ["mcp.client"] }
}
// Provider
ctx.capabilities.provide('my.feature', {
async listItems() { return []; },
}, { version: '1.2.0' });
// Consumer
if (ctx.capabilities.has('my.feature')) {
const feature = ctx.capabilities.get('my.feature');
const items = await feature.listItems(); // Always async on the consumer side
}
Three things to keep in mind:
- Every method is async on the consumer side, since it's routed across processes.
- Activation order is not a dependency contract. The target capability may activate after you do, so poll briefly while waiting (see MCP integration).
- Keep interfaces small and stable, using
versionto signal changes as they evolve.