All posts
mcpllm

What Is MCP: A Practical Guide for Full-Stack Developers

A practical guide to What Is MCP — setup, core concepts, common mistakes, and production tips for full-stack developers.

SR

Suhail Roushan

August 6, 2026

·
6 min read
·
0 views

MCP, or the Model Context Protocol, is an open standard that lets AI models pull data from and act on external tools and services through a unified interface. If you are a full-stack developer, understanding what is MCP matters because it is the layer that turns a chatbot into an actual application that can query your database, call your APIs, and mutate your production data. This guide breaks down the protocol, shows you a working setup, and tells you exactly when to use it—and when to skip it.

Why What Is MCP Matters (and When to Skip It)

Here is my opinionated take: MCP is not another AI framework you need to learn to stay relevant. It is a plumbing standard, like HTTP or WebSockets, that solves a specific pain point—connecting LLMs to your existing backend without writing brittle, one-off integration code.

When MCP matters: you have multiple tools (databases, CRMs, internal APIs) and you want an AI agent to orchestrate them safely. It gives you a typed, permissioned, and auditable way to expose those tools.

When to skip it: if you are just building a simple RAG chatbot over a few documents, or if your AI feature only calls one internal API. Adding MCP there is over-engineering. You will spend more time defining schemas than shipping features.

Getting Started with What Is MCP

The core idea is a client-server architecture. Your AI application is the MCP client, and it connects to MCP servers that expose tools, resources, and prompts. Here is a minimal TypeScript setup using the official SDK.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "user-api-server",
  version: "1.0.0",
});

server.tool(
  "get_user",
  { userId: z.string() },
  async ({ userId }) => {
    // In production, call your actual DB or REST endpoint here
    const user = await fetchUserFromDb(userId);
    return {
      content: [{ type: "text", text: JSON.stringify(user) }],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Run this with npx tsx server.ts. Your AI client (like Claude Desktop or a custom agent) can now discover and call get_user through the standard MCP handshake.

Core What Is MCP Concepts Every Developer Should Know

1. Tools (Actions)

Tools are functions the model can invoke. They are the most common primitive. The key is the Zod schema—it gives the LLM a typed contract, so it knows exactly what arguments to pass.

server.tool(
  "create_order",
  { userId: z.string(), items: z.array(z.object({ sku: z.string(), qty: z.number().int().positive() })) },
  async ({ userId, items }) => {
    const order = await createOrder(userId, items);
    return { content: [{ type: "text", text: `Order created: ${order.id}` }] };
  }
);

2. Resources (Data)

Resources are read-only data the model can pull in. Think of them as endpoints that return context. Unlike tools, they do not cause side effects.

server.resource(
  "user_profile",
  new ResourceTemplate("users://{userId}/profile", { list: undefined }),
  async (uri, { userId }) => ({
    contents: [{ uri: uri.href, text: await getUserProfile(userId) }],
  })
);

3. Prompts (Templates)

Prompts are reusable instruction templates. They are useful for standardizing how the model should approach a task, like "summarize this bug report" or "generate a SQL query for this schema."

server.prompt(
  "sql_generator",
  { schema: z.string() },
  ({ schema }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Given this schema: ${schema}, write a SQL query that returns the last 7 days of orders.`,
      },
    }],
  })
);

4. Sampling (Model Calls)

This is the reverse direction—your server asks the client to make a model call. This is powerful for delegation, but it is rarely needed in the first version. Skip it until you have a concrete use case.

Common What Is MCP Mistakes and How to Fix Them

Mistake 1: Exposing too many tools at once. If you register 50 tools, the model gets confused and hallucinates arguments. Fix: group related tools into separate MCP servers. Have one server for users, another for orders. You can connect multiple servers to one client.

Mistake 2: Not handling errors in tool responses. If your tool throws, the model gets a cryptic error. Fix: catch errors and return them as structured text.

server.tool("get_user", { userId: z.string() }, async ({ userId }) => {
  try {
    const user = await fetchUserFromDb(userId);
    return { content: [{ type: "text", text: JSON.stringify(user) }] };
  } catch (error) {
    return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
  }
});

Mistake 3: Ignoring permissions. MCP does not enforce auth—it just transports calls. If your tool mutates data, you need to check the user's session inside the tool implementation. Never trust the model to be careful.

When Should You Use What Is MCP?

You should use MCP when you have multiple, independent systems that an AI agent needs to interact with, and you want those integrations to be reusable across different AI clients. The classic example is an internal developer assistant that can query Postgres, create GitHub issues, and deploy to staging—all through one protocol.

You should also use MCP when you want to decouple your AI logic from your tool implementations. If you write direct API calls inside your agent code, you will rewrite them every time you switch models or frameworks. MCP gives you a stable interface.

You should not use MCP for a single, simple integration. If your use case is "summarize this text with OpenAI," just call the API directly.

What Is MCP in Production

Running MCP in production requires more than just wiring up the SDK.

Tip 1: Use SSE (Server-Sent Events) instead of stdio. Stdio is fine for local development, but for remote servers, use the StreamableHTTPServerTransport. It supports both SSE and POST requests, which works behind load balancers.

import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: undefined,
  onsessioninitialized: (sessionId) => console.log(`Session: ${sessionId}`),
});

Tip 2: Add observability. Log every tool call, its arguments, and its duration. You need this to debug why the model made a bad decision. Tools like Langfuse or even a simple structured log file work.

Tip 3: Version your tools. The model will cache tool definitions. If you change a schema, the client might send stale arguments. Use semantic versioning in your server name and keep breaking changes explicit.

The takeaway: start with one MCP server exposing three tools, wire it to your local agent, and verify the round-trip works. Then expand. Do not design a microservice architecture for MCP on day one—get the protocol working end-to-end first, then refactor.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch