MCP Fusion/Core concepts/Tool exposition

Tool exposition

Ask AI about Vinkius

Author actions once and choose how they reach the wire: one grouped tool with a discriminator or one atomic tool per action, plus tag-based visibility and per-action schemas.

How you author a connector and how the agent sees it are two different decisions in MCP Fusion. The exposition layer sits between them: it takes your builders and compiles the tools/list surface the client receives. Switch strategy without touching a single handler.

Namespaces: one tool, many actions

Authoring uses dotted names, and the first dot splits the namespace from the action:

You writeMCP toolAction
f.query('billing.get_invoice')billingget_invoice
f.mutation('billing.refund')billingrefund
f.action('support.tickets.search')support.ticketssearch

Tool names have at most one dot; nested prefixes use f.router('support.tickets'). Builders that share a namespace are merged by the registry, so three files exporting compliance.scan, compliance.report and compliance.status produce one compliance tool with three actions. The agent sees a coherent surface; you keep file-per-action organization.

Two strategies

attachToServer(server, { toolExposition }) picks the wire shape:

Grouped (the default for multi-action namespaces): one MCP tool per namespace with a discriminator field, action by default and renameable with .discriminator('operation'). The agent picks the action as an argument, exactly like a resource-oriented API.

Flat: one atomic MCP tool per action, named billing_get_invoice (the separator is configurable). Each tool gets its own purified schema (common fields merged with that action only, discriminator stripped) and its own annotations. Use flat when the client's tool-picking is weak or when actions share almost nothing.

Flat tools are also where HATEOAS affordances become direct: a .suggest('billing.remind') from a flat surface is a tool the client already lists.

Annotation truth

Semantic verbs set MCP annotations, and exposition preserves them per action:

BuilderAnnotation
f.query()readOnlyHint: true
f.mutation()destructiveHint: true
f.action()neutral
.idempotent()idempotentHint: true

In flat mode each atomic tool carries its own hints; in grouped mode the tool carries the safest aggregate, and per-action hints travel in the per-action schema descriptions. A destructive mutation that reports itself as read-only is a lie clients act on, so the framework never infers safety you did not declare.

Parameter annotations do the teaching

Grouped schemas merge common and per-action parameters, and each field description gains a requiredness annotation generated from the actions themselves:

AnnotationMeaning
(always required)required by every action of the tool
Required for: refund, voidrequired by all the listed actions
Required for: refund. For: getrequired by some, optional in others
For: get, listnever required, but used by those actions

The agent does not have to learn the matrix from errors; the schema states it. Combined with the discriminator enum, a grouped tool is a self-describing API.

Visibility by tag

.tags('billing', 'finance') marks a tool, and attachToServer(server, { filter }) decides what it exposes:

typescript
attachToServer(server, {
  filter: {
    tags: ['finance'],          // AND: must carry every tag
    anyTag: ['public'],         // OR: at least one
    exclude: ['internal'],      // NOT
  },
});

Tags are how one codebase serves several products: the free tier exposes public tools, the enterprise deployment exposes the internal ones, and the handler codebase stays identical. See Multi-tenant connectors for the per-request version of the same idea.

Grouping as a token strategy

Every tool name, description and schema costs context on every turn. A hundred flat tools is a hundred descriptions the model reads before it plans. Grouping ten actions into one tool with a discriminator enum trades schema size for prompt size, usually a large win above roughly ten actions. .toonDescription() compresses the description further, and compactDescription() supplies the short form for FSM progressive disclosure. The economics are covered in Token economics.

Next steps