One concise heuristic, refined through ecosystem usage so far. Walk it top to bottom on each new integration; the answer almost always lands cleanly.
- Is the consumer a single, bounded LLM-driven app you control? → use that provider's function calling. Don't introduce extra protocol surface for a single-app concern.
- Will the same capability serve multiple AI products you do not control? → write an MCP server. Every additional product becomes a configuration change instead of a code change.
- Will non-AI clients (browsers, mobile apps, scripts) also consume it? → expose REST/OpenAPI too; either as the underlying API the MCP server wraps, or as the canonical contract with a thin MCP layer derived from it.
- Is the producer pushing events the consumer must react to quickly? → add webhooks. Those events are still describable in the same OpenAPI doc.
- Is the operation slow and the consumer wants in-protocol asynchrony without a separate URL? → use MCP async tasks.
The deeper lesson is composition. The right answer is rarely "MCP or REST"; it is "REST as the underlying truth, MCP as the agent-friendly facade, function calling as the model's invocation envelope, webhooks for the event direction, async tasks for the slow paths." Each contract solves a piece. Stacking them is normal.