MCP Fusion/Core concepts/Tool exposition
Tool exposition
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 write | MCP tool | Action |
|---|---|---|
f.query('billing.get_invoice') | billing | get_invoice |
f.mutation('billing.refund') | billing | refund |
f.action('support.tickets.search') | support.tickets | search |
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:
| Builder | Annotation |
|---|---|
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:
| Annotation | Meaning |
|---|---|
(always required) | required by every action of the tool |
Required for: refund, void | required by all the listed actions |
Required for: refund. For: get | required by some, optional in others |
For: get, list | never 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:
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
- Token economics: paying less context for the same surface
- FSM state gating: removing tools the state forbids
- Routing: where the builders live on disk
