I have been spending a lot of time lately watching how teams approach the MCP build. Almost every one of them starts the same way. They pick an existing API — usually something already stable, already documented — and ask “how do we expose this through MCP?” Then they work backward from the endpoints they already have to whatever tool surface they can reasonably bolt on top.
That is backwards.
This is post eight of the nine-part walk through the Naftiko Framework use cases, and this one is the inversion of that default. Capability-first context engineering. Design the capability first for the MCP client. Then map it down to whatever APIs happen to be underneath.
The API-first MCP trap
Here is the shape of the problem as I keep seeing it.
A team has a REST API. The REST API was designed for an application — a web app, a mobile app, a partner integration. It has the endpoints and payload shapes that those consumers needed. When MCP shows up and the team decides to expose the system to a copilot, the path of least resistance is to wrap every operation as a tool and ship it. One tool per endpoint. Same parameter names. Same output shapes. MCP-as-a-proxy.
The agent gets a tool surface that was never designed for it. The parameter names are the ones a frontend engineer asked for in 2022. The outputs include nested envelopes built for pagination in a list view. The model burns tokens parsing structures that were shaped for a completely different consumer.
That is what happens when the API comes first and MCP comes second.
Flip it — capability-first
The capability-first approach says the MCP surface is the product. The upstream API is plumbing.
You start by asking: what does the agent need to do? Then you describe that — in the exposes block of a Naftiko capability — as a set of MCP tools with typed inputs, clean output shapes, tool descriptions that actually help discovery, and prompts and resources the model can reach for. You do that before you write a single call to an upstream operation.
Here is what that looks like in the Naftiko spec:
naftiko: "1.0.0-alpha1"
info:
title: Sales Opportunity Context
description: "MCP server exposing sales opportunity context — designed for copilots, wired to the CRM underneath"
capability:
exposes:
- type: mcp
namespace: sales-context
transport: http
description: "Retrieve and summarize sales opportunity context for a given account"
tools:
- name: get-open-opportunities
description: "Return open opportunities for an account, shaped for summarization by an AI assistant"
hints:
readOnly: true
idempotent: true
inputParameters:
- name: accountId
description: "The CRM account identifier"
type: string
required: true
outputParameters:
- type: array
items:
type: object
properties:
- name: opportunityId
type: string
- name: name
type: string
- name: amount
type: number
- name: stage
type: string
- name: closeDate
type: string
- name: summarize-account-health
description: "Return a short account health snapshot combining open opportunities and last activity"
hints:
readOnly: true
inputParameters:
- name: accountId
description: "The CRM account identifier"
type: string
required: true
outputParameters:
- type: object
properties:
- name: accountId
type: string
- name: openOpportunityCount
type: integer
- name: pipelineValue
type: number
- name: lastActivityAt
type: string
resources:
- name: sales-playbook
description: "Internal sales playbook used by the agent for context"
uri: "file://./resources/sales-playbook.md"
mimeType: "text/markdown"
prompts:
- name: account-review
description: "Prompt template for a weekly account review"
arguments:
- name: accountId
description: "The account under review"
required: true
template: |
You are reviewing account {{ accountId }}.
Use get-open-opportunities and summarize-account-health
to build a weekly review focused on next steps.
Notice what is missing. There is no consumes block yet. There is no call on any of the tools. There is no upstream URL, no auth token, no HTTP method. The spec at this stage is pure agent-side design. Tools with clean types, prompts that teach the agent how to use them, a resource the agent can read.
That alone is a useful artifact. You can put it in front of an AI engineer, a product owner, and a security reviewer and have a real conversation about the surface the agent will actually see, before anyone argues about which microservice owns the opportunity data.
Mock mode — ship the agent before the API exists
This is the part that tends to surprise people. The Naftiko Engine will happily run a capability whose tools have no upstream wiring yet. Mock mode. Each tool returns a const value declared in its output parameters. The MCP server is live. Agent-side work can start immediately.
tools:
- name: get-open-opportunities
description: "Return open opportunities for an account"
inputParameters:
- name: accountId
type: string
required: true
outputParameters:
- type: array
const:
- opportunityId: "OPP-1001"
name: "Platform Expansion"
amount: 145000
stage: "Proposal"
closeDate: "2026-07-15"
- opportunityId: "OPP-1002"
name: "Phase 2 Rollout"
amount: 78000
stage: "Negotiation"
closeDate: "2026-08-30"
Same tool signature. Same output shape. No upstream call. The copilot connects to the MCP server and gets real-looking data. The prompt engineer tunes prompts against a stable surface. The security reviewer audits the spec. The product team demos the experience.
Meanwhile, the platform team finishes the upstream API work. When it is ready, you replace the const with a call to an operation in a consumes block and redeploy. No agent-side changes. No tool signature changes. The mock becomes real.
This is a pattern I have watched save weeks of schedule slip. Agent work and upstream API work stop being sequential.
Wiring it up — call and steps
Once the upstream lands, you declare it in consumes and wire the tools. A single operation is a call. A composed response is a steps sequence — the same multi-step orchestration primitive I covered in use case 5.
consumes:
- namespace: crm
type: http
baseUri: "https://api.crm.internal"
authentication:
type: bearer
token: "{{CRM_API_TOKEN}}"
resources:
- name: opportunities
path: "/v2/opportunities"
operations:
- name: list-by-account
method: GET
inputParameters:
- name: accountId
in: query
type: string
required: true
- name: activities
path: "/v2/activities"
operations:
- name: last-activity
method: GET
inputParameters:
- name: accountId
in: query
type: string
required: true
exposes:
- type: mcp
namespace: sales-context
transport: http
tools:
- name: get-open-opportunities
call: crm.list-by-account
# outputParameters shape unchanged
- name: summarize-account-health
steps:
- name: opps
call: crm.list-by-account
with:
accountId: "{{ inputs.accountId }}"
- name: last
call: crm.last-activity
with:
accountId: "{{ inputs.accountId }}"
outputParameters:
- type: object
properties:
- name: accountId
const: "{{ inputs.accountId }}"
- name: openOpportunityCount
mapping: "steps.opps.$.data | length"
- name: pipelineValue
mapping: "steps.opps.$.data[*].amount | sum"
- name: lastActivityAt
mapping: "steps.last.$.data.timestamp"
The agent contract has not changed. The tool names, descriptions, and output shapes are the same. What changed is what happens behind the curtain.
Two transports, same capability
One more thing that matters for capability-first design. The same MCP capability can expose itself over HTTP for remote MCP clients, or over stdio for local IDE and agent runtimes. Swap transport: http for transport: stdio and the Naftiko Engine runs the same spec as a local subprocess. The capability travels. The agent code does not care which transport it is on.
Three-dimensional read
Technology. The MCP surface is designed against agent needs — typed inputs, clean outputs, helpful descriptions, prompts and resources in the same spec. The underlying APIs remain whatever they already are.
Business. The capability-first artifact is reviewable before any integration work is done. That changes the shape of every cross-team conversation. You are arguing about the tool contract, not the wrapper code.
Politics. This is the one that tends to matter most. When MCP is designed first, the platform team owns the agent-facing contract. When MCP is bolted on top of whatever API was already there, the API team inherits an agent surface by accident. The first is governed. The second is an artifact.
Next
Next post in the series is use case 9 — Capability-first API reusability. Same inversion, different audience. Start with reusable capabilities, let them power new AI and app experiences, instead of treating every API as a one-off.
- Wiki: Guide — Use Cases
- GitHub: github.com/naftiko/framework
- Fleet Community Edition: github.com/naftiko/fleet
Still walking the list.