Architecture·9 min read·30 July 2026

Build MCP Server Claude: A Production Engineering Guide

Learn how to build MCP server Claude integrations that are secure, observable, and exam-ready. Covers transport, tool design, error handling, and CCAR-F Domain 2 skills.

By Solomon Udoh · AI Architect & Certification Lead

Build MCP Server Claude: A Production Engineering Guide

When you build MCP server Claude integrations for production, three decisions dominate everything else: transport choice, tool schema design, and error signalling. Get those three right and the rest of the system follows naturally. Get them wrong and you spend weeks chasing misrouted tool calls and silent failures. This guide walks through each decision with concrete patterns, security considerations, and the exam-relevant reasoning that Domain 2 of the CCAR-F tests.

The Model Context Protocol (MCP) is Anthropic's open standard for connecting Claude to external systems. As of 12 March 2026, it sits at the heart of the Claude Partner Network's $100M programme and is tested directly in Tool Design & MCP Integration, which carries 18% of the CCAR-F Architect exam.

What is the MCP architecture and how does Claude use it?

MCP separates concerns cleanly: a host (Claude Desktop, a Claude agent, or your own application) connects to one or more servers that expose tools, resources, and prompts. Claude never calls your internal APIs directly. It calls tools declared by the MCP server, and the server executes against your systems. That indirection is both the security boundary and the observability hook.

text
Claude (host)
|
| MCP protocol (JSON-RPC 2.0)
v
MCP Server (your code)
|
+-- tools/ (actions Claude can invoke)
+-- resources/ (read-only content catalogs)
+-- prompts/ (reusable prompt templates)
|
v
Internal APIs, databases, file systems

The three primitive types map to distinct use cases:

PrimitiveDirectionTypical use
ToolModel-initiated callWrite actions, queries, computations
ResourceServer-exposes, model readsDocumentation, product catalogs, config
PromptServer-exposes, user selectsReusable workflow templates

For most production integrations, tools carry the bulk of the work. Resources are underused; they are the right answer when Claude needs to browse a content catalog without triggering a write action. See MCP Resources for Content Catalogs for the exam-relevant distinction.

Which transport should you choose: stdio, SSE, or Streamable HTTP?

Choose stdio for local tools (Claude Desktop plugins, developer workstations). Choose Streamable HTTP for any server that multiple clients or agents will reach over a network. SSE (Server-Sent Events) was the original remote transport; the MCP spec has since moved toward Streamable HTTP, which replaces the SSE session model with a cleaner request/response plus streaming envelope. If you are migrating an SSE-era server, the main breaking point is session initialisation: SSE used a persistent event stream from the first connection, whereas Streamable HTTP negotiates capabilities per-request and streams only when the response warrants it.

python
# Minimal Streamable HTTP MCP server (Python, using the official MCP SDK)
from mcp.server.fastmcp import FastMCP
app = FastMCP("internal-data-server")
@app.tool()
def get_customer(customer_id: str) -> dict:
"""Return a customer record by ID. Read-only. Never returns PII beyond name and tier."""
record = db.fetch_customer(customer_id)
if record is None:
return {"isError": True, "error": "NOT_FOUND", "customer_id": customer_id}
return {"name": record.name, "tier": record.tier}
if __name__ == "__main__":
app.run(transport="streamable-http", host="0.0.0.0", port=8080)

The transport="streamable-http" argument is the only change needed from an SSE setup in the official Python SDK. The capability handshake is handled by the framework.

How do you write tool descriptions Claude will actually use correctly?

Tool descriptions are the primary selection mechanism. Claude reads the description to decide which tool to call; it does not inspect your source code. A vague description produces misrouted calls. A precise description produces reliable routing. This is the single highest-leverage improvement in most MCP deployments, and it is tested directly in the CCAR-F exam under Tool Descriptions as Selection Mechanism.

The pattern that works:

  1. State what the tool does in one sentence.
  2. State what it does not do (the negative constraint eliminates the most common misrouting).
  3. State the preconditions (what must be true before calling).
  4. State the side effects (read-only vs. write, idempotent vs. not).
python
@app.tool()
def update_order_status(order_id: str, new_status: str) -> dict:
"""
Updates the fulfilment status of an existing order.
Use this tool ONLY when the user has explicitly confirmed they want to change
an order's status. Do NOT use this to look up order information; use
get_order_details for reads.
Preconditions: order_id must exist; new_status must be one of
['processing', 'shipped', 'delivered', 'cancelled'].
Side effects: WRITE. Not idempotent if transitioning from 'shipped' to
'cancelled' (triggers a refund workflow).
"""
...

Tools are selected by the model based on their descriptions. A well-written description is the difference between a tool that works and one that causes subtle, hard-to-debug misrouting.

Anthropic , MCP Tool Design Documentation

When two tools have overlapping descriptions, Claude will route inconsistently. The fix is Tool Splitting for Specificity: split the ambiguous tool into two narrower ones, each with a description that covers exactly one case. This is a low-effort, high-leverage change that the exam rewards.

How do you handle errors in MCP tool responses?

MCP defines a specific error signalling pattern that differs from HTTP status codes. A tool response can succeed at the protocol level (HTTP 200, no JSON-RPC error) while reporting a domain-level failure. The correct mechanism is the isError flag in the tool result content.

json
{
"content": [
{
"type": "text",
"text": "{\"isError\": true, \"error\": \"RATE_LIMITED\", \"retry_after_seconds\": 30}"
}
],
"isError": true
}

Setting isError: true at the result level tells Claude that the tool call failed and that it should reason about recovery rather than treating the response as a successful result. Omitting it and returning an error message as plain text is the Silent Suppression Anti-Pattern: Claude may interpret the error text as a valid answer and continue incorrectly.

The four error categories the exam tests are:

CategoryExampleRecommended response
Access failureAuth token expiredisError: true, structured metadata, do not retry without refresh
Valid empty resultQuery returns zero rowsisError: false, empty array, explicit "no results" message
Transient failureNetwork timeoutisError: true, retry_after hint
Permanent failureRecord not foundisError: true, no retry hint

The distinction between an access failure and a valid empty result is one of the most commonly tested scenarios. See Access Failure vs Valid Empty Result for the full decision tree.

How do you secure an MCP server against prompt injection and over-exposure?

Security is the most active concern in production MCP deployments. Three threat vectors dominate:

Prompt injection via tool results. If your tool returns content from an untrusted source (a web page, a user-submitted document, a third-party API), that content can contain instructions that attempt to hijack Claude's behaviour. The mitigation is to sanitise tool outputs before returning them, and to structure results so that data and instructions are clearly separated.

python
import re
def sanitise_tool_output(raw: str) -> str:
# Strip any text that looks like a system-level instruction
cleaned = re.sub(r"(?i)(ignore previous|you are now|system:)", "[REDACTED]", raw)
return cleaned

Over-exposed tool surface. Every tool you register is a potential action Claude can take. Limit the tool set to what the current session actually needs. The MCP Scoping Hierarchy defines three levels: user-level, project-level, and global. Prefer project-level scoping so that a tool registered for one workflow is not available to unrelated sessions.

Cross-tenant leakage. In multi-tenant deployments, a tool that accepts a customer_id parameter must validate that the authenticated principal is authorised for that customer. Never trust the customer_id value Claude passes; validate it server-side against the session's auth context.

python
@app.tool()
def get_account_balance(customer_id: str) -> dict:
"""Returns account balance for the authenticated customer."""
# Server-side authorisation check - never skip this
if not auth_context.can_access(customer_id):
return {"isError": True, "error": "FORBIDDEN"}
return {"balance": db.get_balance(customer_id)}

Environment variables for secrets should always be injected at runtime, never hardcoded. The Environment Variable Expansion in MCP Config pattern covers the correct claude_desktop_config.json approach for local servers.

How do you configure MCP servers in Claude Desktop and Claude Code?

Claude Desktop reads server configuration from claude_desktop_config.json. Claude Code uses a three-level hierarchy (user, project, system) described in Three-Level Configuration Hierarchy.

A minimal claude_desktop_config.json entry for a local stdio server:

json
{
"mcpServers": {
"internal-data": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"DB_URL": "${INTERNAL_DB_URL}",
"API_KEY": "${INTERNAL_API_KEY}"
}
}
}
}

For a remote Streamable HTTP server:

json
{
"mcpServers": {
"internal-data-remote": {
"url": "https://mcp.internal.example.com/mcp",
"headers": {
"Authorization": "Bearer ${MCP_TOKEN}"
}
}
}
}

The ${VAR} syntax expands from the shell environment at startup. Never commit real credentials to this file; the variable expansion exists precisely to keep secrets out of version control.

How do you observe and debug MCP tool calls in production?

Observability in MCP systems has three layers: protocol-level logging, tool-level structured output, and agent-level tracing.

At the protocol level, the MCP SDK emits JSON-RPC request and response objects. Pipe these to your log aggregator with a correlation ID that spans the full agent turn.

python
import logging
import uuid
logger = logging.getLogger("mcp.server")
@app.tool()
def search_products(query: str, limit: int = 10) -> dict:
trace_id = str(uuid.uuid4())
logger.info({"event": "tool_call", "tool": "search_products",
"query": query, "trace_id": trace_id})
results = catalog.search(query, limit=limit)
logger.info({"event": "tool_result", "count": len(results),
"trace_id": trace_id})
return {"results": results, "trace_id": trace_id}

Returning trace_id in the tool result means Claude can include it in any structured output it produces, linking the agent's reasoning to the server-side log entry. This is the pattern the exam rewards when it asks about diagnosing partial failures in multi-step agentic workflows.

For multi-server deployments, tool name collisions are a silent failure mode. If two servers register a tool called search, Claude's selection is non-deterministic. Use namespaced tool names (catalog_search, docs_search) and document the namespace convention in each server's description.

Should you build a new MCP server or use an existing one?

The Build vs Use Decision for MCP Servers framework reduces to three questions:

  1. Does a well-maintained community server already cover the integration (GitHub, Slack, Postgres, etc.)? If yes, use it and invest your time in tool description tuning and security configuration.
  2. Does your integration require proprietary business logic, internal auth, or data that cannot leave your perimeter? If yes, build.
  3. Is the integration a thin wrapper around a public REST API with no auth complexity? Consider whether a generic HTTP tool with a well-scoped system prompt is sufficient before writing a full server.

The right question is not "can I build an MCP server for this?" but "does building one produce better tool descriptions, better error handling, and better security than the alternatives?"

Anthropic , MCP Server Integration Best Practices

The exam consistently rewards proportionate solutions. A custom MCP server is the right answer when it produces meaningfully better tool descriptions, tighter auth, or cleaner error semantics than the alternative. It is not the right answer when an existing server or a direct API call would serve equally well.

How does MCP integration connect to the CCAR-F exam?

Domain 2 (Tool Design & MCP Integration) carries 18% of the CCAR-F exam weight. The task statements it tests map directly to the patterns in this guide: writing effective tool descriptions, handling the four error categories, applying the isError flag correctly, scoping MCP servers to the right level, and deciding when to build versus use.

The exam is scenario-based. A typical Domain 2 item presents a deployed MCP server with a symptom (misrouted calls, silent failures, cross-tenant data appearing in responses) and asks which single change fixes the root cause. The answer is almost always one of: improve the tool description, add the isError flag, tighten the scoping, or add server-side authorisation validation. The exam rewards root-cause tracing and proportionate fixes over broad rewrites.

The full concept map for Domain 2 is available in our Tool Design & MCP Integration concept library, which covers 174 atomic concepts mapped to the five exam domains and 30 task statements. Our adaptive engine uses Bayesian Knowledge Tracing with a 0.90 mastery threshold to surface the concepts where your gap is largest before exam day.

AI Skill Certs is an independent prep platform; we are not affiliated with or endorsed by Anthropic.

Frequently asked questions

How long does it take to build a working MCP server for Claude?
A minimal stdio MCP server with one or two tools can be running in under an hour using the official Python or TypeScript MCP SDK. A production-grade server with auth, structured error handling, observability, and security hardening typically takes two to four days of engineering work, depending on the complexity of the backend systems being wrapped.
What programming languages can I use to build an MCP server?
Anthropic publishes official SDKs for Python and TypeScript. Community SDKs exist for Go, Rust, Java, and C#. The protocol is JSON-RPC 2.0 over stdio or HTTP, so any language that can speak JSON-RPC can implement an MCP server, though the official SDKs handle capability negotiation and transport details automatically.
Can I run multiple MCP servers with Claude at the same time?
Yes. Claude Desktop and Claude Code both support multiple simultaneous MCP servers declared in the configuration file. Each server's tools are merged into a single tool namespace visible to Claude. To avoid selection ambiguity, use namespaced tool names (for example, 'catalog_search' rather than 'search') when two servers might register tools with similar purposes.
Does building an MCP server require a Claude API key?
The MCP server itself does not require a Claude API key; it is a standalone process that exposes tools over the MCP protocol. The Claude host (Claude Desktop, a Claude agent, or your application) holds the API key and initiates the connection to your server. Your server only needs credentials for the internal systems it wraps.
How is MCP tested on the CCAR-F Architect exam?
Domain 2 (Tool Design & MCP Integration) carries 18% of the CCAR-F exam weight. Items are scenario-based and test practical judgment: diagnosing misrouted tool calls, applying the isError flag correctly, scoping servers to the right level, and deciding when to build versus use an existing server. The exam rewards root-cause tracing and proportionate fixes.
What is the difference between MCP tools, resources, and prompts?
Tools are model-initiated calls that can trigger actions or queries; they are the most common primitive. Resources are server-exposed, read-only content that Claude can browse without triggering side effects, suited to documentation or product catalogs. Prompts are reusable workflow templates the user selects. Most production integrations rely primarily on tools, with resources used for read-heavy content scenarios.

People also ask

How do I connect Claude to my own database using MCP?
Build an MCP server that wraps your database with read and write tools, each with precise descriptions and server-side authorisation checks. Use environment variable expansion for credentials in the config file. Never pass raw SQL from Claude; define narrow, parameterised tool functions that validate inputs before executing against the database.
What is the MCP isError flag and why does it matter?
The isError flag in an MCP tool result tells Claude the call failed at the domain level, even if the HTTP and JSON-RPC layers succeeded. Without it, Claude may treat an error message as a valid answer and continue incorrectly. Setting isError: true prompts Claude to reason about recovery, retry logic, or escalation instead.
Is MCP secure enough for production use with sensitive data?
MCP is a protocol, not a security guarantee. Production security requires server-side authorisation on every tool call, sanitised tool outputs to prevent prompt injection, namespaced tool names to avoid collision, and scoped server registration so tools are only available to the sessions that need them. The protocol itself provides the boundary; you enforce the controls.
What is the difference between MCP stdio and HTTP transport?
Stdio transport runs the MCP server as a local subprocess and communicates over standard input/output; it is suited to Claude Desktop plugins and developer tools. HTTP transport (now Streamable HTTP in the current spec) runs the server as a network service accessible to multiple clients or agents, and is the correct choice for shared production deployments.
How do I debug MCP tool calls that are failing silently?
Add structured logging at the tool entry and exit points, returning a trace ID in every tool result. Ensure all failure paths set isError: true rather than returning error text as a plain string. For multi-server deployments, check for tool name collisions, which cause non-deterministic routing that looks like intermittent silent failure.

About the author

Solomon Udoh

AI Architect & Certification Lead

Solomon Udoh is an AI Architect who designs and ships production agent systems on the Claude API and Claude Code. He built AI Skill Certs' adaptive engine and authored its 174-concept knowledge graph, mapping every Claude Certified Architect - Foundations objective to hands-on, exam-aligned practice.

  • Designs production multi-agent systems on the Claude API and Agent SDK
  • Author of the AI Skill Certs knowledge graph (174 mapped exam concepts)
  • Builds with MCP, Claude Code, structured outputs, and agentic loops daily
  • Reviews every concept page against the official Anthropic exam guide

You might also like

Ready to put it into practice?

Study every exam concept with an adaptive tutor.

Start studying