Skip to main content

Lifecycle & capability registration

The full lifecycle, static declaration vs. dynamic registration, and capability cooperation

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 declarationDynamic registration in activate()
DeterminesWhether a button/container exists, its icon, its tooltipThe badge text, menu contents, click behavior
Read whenAt install and startupOn-demand, during user interaction
Can it hideThe declared slot always remainsgetBadge() 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 version to signal changes as they evolve.