MCP Server

MCP

Definition

A process that implements the MCP protocol and exposes tools, resources, and prompts to MCP clients. Built with official SDKs (Python, TypeScript). Deployed locally via stdio or remotely via StreamableHTTP. Claude Code auto-discovers servers configured in .mcp.json.

Example Usage

Build an MCP server that exposes your internal APIs as tools so Claude can query them during agentic tasks without custom integration code.

Why It Matters for the CCA-F Exam

Exam questions distinguish MCP servers from MCP clients and ask about transport selection. Know that stdio is for local subprocesses; Streamable HTTP is for remote/shared servers. Know that Claude Code auto-starts servers listed in .mcp.json before the session begins. Also expect security questions about least-privilege scoping for server processes.

In Depth

An MCP server is a process that implements the server side of the Model Context Protocol. Its job is to expose capabilities — tools, resources, or prompts — in the standard MCP vocabulary, so any compliant MCP client can discover and call them without bespoke integration code.

The Initialize Handshake

Every MCP session begins with a handshake. The client sends an initialize request carrying its protocol version and supported capabilities. The server responds with its own protocol version, a serverInfo block (name, version), and a capabilities object that advertises which primitive types it supports (tools, resources, prompts, roots). The client then sends an initialized notification to signal it is ready. Only after this exchange does the client call tools/list (or resources/list, prompts/list) to enumerate what is available.

Declaring a Tool

Each tool has three required fields: name (machine-readable identifier), description (natural language the model reads to decide when to call it), and inputSchema (JSON Schema defining expected arguments). The description is the most consequential field — vague descriptions lead to wrong invocations. Write it as if instructing a capable colleague: state what the tool does, when to use it, and what it does *not* do.

Transport Branching

A server process runs over one of two transports:

  • stdio: the client spawns the server as a subprocess and pipes JSON-RPC messages over stdin/stdout. Zero network setup, perfect for developer tooling and single-user contexts.
  • Streamable HTTP: the server runs as an independent HTTP service. Suitable when multiple clients need to share the same server, or when the server lives in a container or remote environment.

Transport choice is a deployment decision, not a capability decision — the tools, resources, and prompts a server exposes are identical regardless of transport.

Security Responsibilities

Each MCP server is a trust boundary with real-world side effects. Apply least-privilege at every layer: narrow the process's OS permissions, scope API key access to only what the server's tools require, and declare only the roots the server genuinely needs. A broad-permission server that gets compromised or misbehaves can affect anything within its granted scope.

Example

A single server binary that supports both stdio (default) and StreamableHTTP (--http flag). The initialize handshake is handled automatically by the SDK; you implement list_tools and call_tool.

python
# Minimal Python MCP server: initialize handshake + one tool + transport branching
import sys
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.server.streamable_http import streamable_http_server
from mcp.types import Tool, TextContent

app = Server("internal-api-server")

# --- Tool declaration ---
@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="get_ticket",
            description=(
                "Fetch a support ticket by ID. Use when the user asks about a specific ticket "
                "or when you need ticket details to answer a follow-up question. "
                "Do NOT use for ticket creation or updates."
            ),
            inputSchema={
                "type": "object",
                "properties": {
                    "ticket_id": {"type": "string", "description": "The ticket identifier, e.g. TICK-1234"}
                },
                "required": ["ticket_id"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "get_ticket":
        data = await fetch_ticket_from_api(arguments["ticket_id"])  # your impl
        return [TextContent(type="text", text=str(data))]
    raise ValueError(f"Unknown tool: {name}")

# --- Transport branching ---
async def main():
    if "--http" in sys.argv:
        # Remote: run as HTTP service on port 8080
        async with streamable_http_server(app, host="0.0.0.0", port=8080):
            await asyncio.Event().wait()  # run forever
    else:
        # Local: communicate via stdin/stdout
        async with stdio_server(app):
            await asyncio.Event().wait()

if __name__ == "__main__":
    asyncio.run(main())

How It's Tested & Common Confusions

  • Transport selection scenarios: given a deployment requirement ("team shares one server," "developer-local only"), choose the correct transport.
  • Capability exposure: identify which MCP primitive a described capability belongs to.
  • Security questions: applying least-privilege when granting server process permissions or API key scopes.
  • Configuration questions: where server definitions live (.mcp.json vs. user-scoped config) and what env var expansion looks like.

Frequently Asked Questions

Can one MCP server expose all three primitive types — Tools, Resources, and Prompts?

Yes. A single server can implement any combination of the three primitives. A well-scoped server might expose only tools, but nothing prevents combining them. The client discovers which primitive types are available during the `initialize` handshake via the server's `capabilities` object.

What happens when an MCP server process crashes during a session?

The client loses access to that server's capabilities. Claude Code will surface an error rather than silently failing. In production agentic pipelines, handle server unavailability gracefully — surface a clear error rather than letting the agent silently skip tool calls that would have used the now-dead server.

Do I need to implement the initialize handshake myself?

No — the official Python and TypeScript MCP SDKs handle the handshake automatically. You implement list_tools/call_tool (or equivalent resource/prompt handlers) and the SDK manages protocol framing, version negotiation, and the initialized notification.