MCP is easy to overhype because it sits at an exciting intersection: models, tools, enterprise systems, and interoperability. The useful way to think about it is much simpler. An MCP server is a standardized capability provider. An agent runtime is a consumer of those capabilities.
That means MCP is not an orchestration strategy. It does not decide how your agent plans, whether a workflow is sequential, when to ask a human, or whether a financial action is permitted. It standardizes how a client discovers and invokes capabilities such as tools, resources, and prompts.
1Agent runtime / MCP client2 ↓3connect + discover4 ↓5MCP server6 ├── tools7 ├── resources8 └── prompts9 ↓10real backend servicesWhen MCP is the right abstraction
If one application has three internal functions, direct function tools are often simpler. MCP becomes compelling when the same capability surface needs to be consumed by multiple agents, development environments, or model stacks—or when you want a stable agent-facing facade over a complicated enterprise API.
Salesforce is a good example. Instead of every agent implementing Salesforce integration separately, a shared MCP server can expose semantic tools such as get_opportunity(), add_note(), and update_stage(). Different agents can reuse the same capability layer while authenticating with different identities and receiving different effective permissions.
What an MCP server and client actually look like
On the server, an MCP tool is the same three-part contract as a direct tool — a name the model can select, a validated input schema, and an execute function — just declared on the capability provider instead of inside the agent. Here is a trimmed version of the add_note() tool from the facade above:
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";2import { z } from "zod";3 4const server = new McpServer({ name: "crm", version: "1.0.0" });5 6server.registerTool(7 "add_opportunity_note",8 {9 description: "Append a note to a CRM opportunity.",10 inputSchema: {11 opportunityId: z.string(),12 note: z.string().max(2000),13 },14 },15 async ({ opportunityId, note }) => {16 // Runs against the connecting client's identity and permissions,17 // never with the MCP server's own privileges.18 const actor = await authorizeRequest(requestContext);19 await crm.addNote(opportunityId, note, { actor });20 return { content: [{ type: "text", text: "Note added." }] };21 },22);On the client, the agent runtime connects to the server, discovers its capability list, and bridges those tools into its own tool map. With the AI SDK, the bridge is a few lines:
1import { generateText, stepCountIs } from "ai";2import { experimental_createMCPClient as createMCPClient } from "ai/mcp-stdio";3 4// Connect and discover what the server offers.5const crm = await createMCPClient({6 transport: { command: "node", args: ["dist/crm-server.js"] },7});8 9const result = await generateText({10 model,11 system: SUPPORT_AGENT_INSTRUCTIONS,12 messages,13 // Discovered MCP tools merge into the agent's action space14 // alongside any direct tools.15 tools: { ...directTools, ...(await crm.tools()) },16 stopWhen: stepCountIs(50),17});Notice what stayed the same and what moved. The tool map is still the agent's entire action space — discovery only changes where its entries come from. And the authorization model inverts: with a direct tool, the runtime enforces permissions; with MCP, the server enforces them per connected identity. That inversion is exactly what lets a shared capability layer stay safe when multiple agents, clients, and frameworks reuse it.
Shared integration does not mean shared authority
A shared MCP server should never imply that every connected agent can use every capability. Authorization remains a server-side concern. One agent might only have read access and add_note(); another privileged automation agent might additionally receive update_stage(). The server or underlying service should enforce those permissions even if a tool is accidentally exposed to the wrong model.
MCP can be a semantic facade
Many enterprise APIs are optimized for application developers, not models. An MCP layer can trim, combine, normalize, and rename those APIs into a smaller semantic surface. That is not obfuscation; it is interface design. It gives the model fewer ways to be wrong while preserving the backend system of record.
The protocol keeps evolving
The MCP specification continues to evolve quickly. The July 2026 specification introduced a stateless protocol core, cacheable capability-list results, authorization hardening, and a formal extensions framework. That reinforces the architectural direction: MCP is maturing into infrastructure that can sit behind gateways, rate limiters, and enterprise authorization systems rather than remaining a local-tool novelty.
PFPLabs takeaways
- MCP is an interoperability protocol, not an orchestration strategy.
- Use direct tools when they are simpler. Reach for MCP when reuse, standardization, or cross-client interoperability matters.
- Keep authorization server-side. Shared integration logic must not become shared privilege.
- Design the MCP surface semantically. Expose business capabilities, not arbitrary backend plumbing.
