Back to articles

Model Context Protocol (MCP): a practical intro

MCP (Model Context Protocol) is a way for an LLM app (the client) to connect to external capabilities exposed by one or more MCP servers—things like reading a repo, querying a database, calling an internal API, or running safe automations.

The value is standardization: instead of building custom integrations per tool, your client speaks MCP to many servers.

The moving parts

  • MCP Client: your app/IDE/agent that decides when to call tools.
  • MCP Server: exposes capabilities (often “tools”) in a consistent format.
  • Tools / Resources: the actual operations and data sources behind the server.

Typical request flow

Rendering diagram…

When MCP is a good fit

  • You want one client to talk to many internal tools safely.
  • You want tool schemas and consistent calling patterns.
  • You want to keep credentials + network access on the server side, not inside the LLM client.

Practical tips

  • Keep tools small and composable (one job per tool).
  • Return structured output (JSON-ish) when possible.
  • Treat servers as "capability boundaries" (auth, audit logs, rate limits).

Example: App MCP Server Implementation

Let's walk through a concrete example—building an MCP server that exposes a tool to query the App API info endpoint. The server uses stdio transport, runs in Docker, and integrates with Cursor's agent window.

Architecture

Rendering diagram…

Files to Create

1. MCP Server (mcp/server.py)

Main MCP server implementation using the mcp Python SDK:

from mcp.server import Server
from mcp.server.stdio import stdio_server
import requests

app = Server("app-mcp-server")

@app.tool()
async def get_app_info() -> dict:
    """Query the App API info endpoint."""
    response = requests.get(
        "https://api-dv2.app-usw2.dev.aligntech.com/app/info"
    )
    response.raise_for_status()
    return response.json()

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await app.run(read_stream, write_stream)

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

2. Dependencies (mcp/requirements.txt)

mcp>=1.0.0
requests>=2.28.0

3. Dockerfile (mcp/Dockerfile)

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY server.py .

CMD ["python", "server.py"]

4. Docker Compose (optional) (mcp/docker-compose.yml)

For easier container management during development:

version: "3.8"
services:
  app-mcp:
    build: .
    image: app-mcp-server
    stdin_open: true
    tty: true

5. README (mcp/README.md)

Documentation with Cursor configuration instructions.

Cursor Configuration

After building the Docker image, add to .cursor/mcp.json:

{
  "mcpServers": {
    "app": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "app-mcp-server"]
    }
  }
}

Build and Run Steps

  1. Build Docker image: docker build -t app-mcp-server ./mcp
  2. Restart Cursor to pick up the MCP configuration
  3. Use the agent window to ask about App API status

Tool Specification

The MCP server exposes one tool:

ToolDescriptionParameters
get_app_infoQuery the App API info endpointNone