MCP, RAG, and Plain APIs
What MCP is actually for compared to retrieval-augmented generation, traditional API integrations, and agent frameworks, and when to combine them.
They solve different problems
It's easy to lump MCP in with retrieval-augmented generation (RAG) since both are ways of getting an AI model more than what's in its training data, but they answer different questions.
A support assistant that can quote your refund policy is doing RAG. One that can actually process the refund is doing something MCP-shaped. Most useful assistants eventually need both: information to reason with, and the ability to act once it has reasoned.
Where traditional APIs still win
MCP is not a replacement for a well-designed API; it's a standardized way for an AI model specifically to discover and call one. If you're wiring two backend services together with no AI model in the loop, a direct API call is simpler, more predictable, and has one less layer to reason about. Reach for MCP when the caller is an AI model that needs to discover what's available and decide which tool fits a task it wasn't hardcoded to expect. Reach for a plain API when the caller is your own code and you already know exactly which endpoint you're calling.
The same logic applies to agent frameworks like the ones used for chaining multi-step AI workflows: those frameworks bind tools to an agent at development time, in that framework's own way. MCP standardizes the tool-calling interface itself, at the protocol level, so a tool built once can be picked up by any framework or host that speaks MCP, not just the one it was written for.
A hybrid pattern in practice
Real systems usually end up combining more than one of these. Picture an onboarding assistant handling a new hire's question about expense policy:
async def handle_onboarding_question(question: str, employee_id: str) -> dict:
# Retrieve relevant policy text (RAG-style search over internal docs)
policy_context = await search_policies(question)
# Reason over the retrieved text to decide what, if anything, to do
plan = await interpret_request(question, policy_context)
# Act on that plan using an MCP tool, if an action is actually needed
if plan.requires_action:
result = await submit_expense_preapproval(
employee_id=employee_id,
category=plan.category,
estimated_amount=plan.amount,
)
return {"answer": plan.summary, "action_taken": result}
return {"answer": plan.summary, "action_taken": None}
The retrieval step answers "what's the policy," the MCP tool call handles "now actually do the thing." Neither one replaces the other.