When AI models need to interact with the real world — read files, query databases, call APIs, control software — there’s been no standard way to do it. Every tool had its own integration pattern. Anthropic released the Model Context Protocol (MCP) in late 2024, and it’s gaining rapid adoption as the emerging standard for AI-tool integration.
By February 2026, MCP support has been added to Claude Desktop, Cursor, Claude Code, Zed, and dozens of third-party integrations. If you’re building tools for AI or building AI that uses tools, MCP is worth understanding.
What MCP Is
MCP is a protocol — a specification for how AI models communicate with external systems. It defines:
- How a host application (like Claude Desktop or Cursor) connects to MCP servers
- What capabilities MCP servers can expose: tools (functions the AI can call), resources (data the AI can read), and prompts (pre-built prompt templates)
- How the AI model requests to use these capabilities
- How results are returned
The key insight: by standardizing this interface, any MCP server works with any MCP client. Build one MCP server for your database, and Claude, Cursor, and any other MCP client can use it without additional integration work.
MCP Host (Claude Desktop, Cursor, etc.)
↓
MCP Client (built into the host)
↓
MCP Protocol (JSON-RPC over stdio or SSE)
↓
MCP Server (your implementation)
↓
External System (database, API, filesystem, etc.)
Building an MCP Server
MCP servers can be built in any language. The official SDKs are for Python and TypeScript, with community SDKs for Go and other languages.
A minimal MCP server in Python:
#!/usr/bin/env python3
"""
MCP server that provides tools for querying a Kubernetes cluster
"""
import asyncio
import subprocess
import json
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
server = Server("kubernetes-tools")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="kubectl_get",
description="Get Kubernetes resources. Returns resource information as YAML.",
inputSchema={
"type": "object",
"properties": {
"resource": {
"type": "string",
"description": "Resource type (pods, services, deployments, etc.)"
},
"namespace": {
"type": "string",
"description": "Namespace (optional, defaults to all namespaces)"
},
"name": {
"type": "string",
"description": "Resource name (optional)"
},
"output": {
"type": "string",
"enum": ["yaml", "json", "wide"],
"description": "Output format"
}
},
"required": ["resource"]
}
),
Tool(
name="kubectl_describe",
description="Describe a Kubernetes resource in detail",
inputSchema={
"type": "object",
"properties": {
"resource": {"type": "string"},
"name": {"type": "string"},
"namespace": {"type": "string"}
},
"required": ["resource", "name"]
}
),
Tool(
name="kubectl_logs",
description="Get logs from a pod",
inputSchema={
"type": "object",
"properties": {
"pod": {"type": "string"},
"namespace": {"type": "string", "default": "default"},
"container": {"type": "string"},
"tail": {"type": "integer", "default": 50}
},
"required": ["pod"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "kubectl_get":
cmd = ["kubectl", "get", arguments["resource"]]
if name_arg := arguments.get("name"):
cmd.append(name_arg)
if ns := arguments.get("namespace"):
cmd.extend(["-n", ns])
else:
cmd.append("--all-namespaces")
output_format = arguments.get("output", "yaml")
cmd.extend(["-o", output_format])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
content = result.stdout if result.returncode == 0 else result.stderr
return [TextContent(type="text", text=content)]
elif name == "kubectl_describe":
cmd = ["kubectl", "describe", arguments["resource"], arguments["name"]]
if ns := arguments.get("namespace"):
cmd.extend(["-n", ns])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
content = result.stdout if result.returncode == 0 else result.stderr
return [TextContent(type="text", text=content)]
elif name == "kubectl_logs":
cmd = ["kubectl", "logs", arguments["pod"]]
cmd.extend(["-n", arguments.get("namespace", "default")])
if container := arguments.get("container"):
cmd.extend(["-c", container])
cmd.extend(["--tail", str(arguments.get("tail", 50))])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
content = result.stdout if result.returncode == 0 else result.stderr
return [TextContent(type="text", text=content)]
return [TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream, server.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
Adding Resources
Resources are data sources the AI can read — think of them as files or documents:
from mcp.types import Resource, ResourceContents
@server.list_resources()
async def list_resources() -> list[Resource]:
return [
Resource(
uri="kubernetes://cluster/overview",
name="Cluster Overview",
description="High-level overview of cluster health and resource usage",
mimeType="text/plain"
),
Resource(
uri="kubernetes://cluster/events",
name="Recent Cluster Events",
description="Last 50 cluster events",
mimeType="text/plain"
)
]
@server.read_resource()
async def read_resource(uri: str) -> ResourceContents:
if uri == "kubernetes://cluster/overview":
# Get cluster info
result = subprocess.run(
["kubectl", "cluster-info"],
capture_output=True, text=True
)
nodes_result = subprocess.run(
["kubectl", "get", "nodes", "-o", "wide"],
capture_output=True, text=True
)
content = f"Cluster Info:\n{result.stdout}\n\nNodes:\n{nodes_result.stdout}"
return ResourceContents(uri=uri, mimeType="text/plain", text=content)
elif uri == "kubernetes://cluster/events":
result = subprocess.run(
["kubectl", "get", "events", "--all-namespaces",
"--sort-by=.lastTimestamp", "--tail=50"],
capture_output=True, text=True
)
return ResourceContents(uri=uri, mimeType="text/plain", text=result.stdout)
Configuring Claude Desktop to Use MCP
Once you have an MCP server, add it to Claude Desktop’s configuration:
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// %APPDATA%\Claude\claude_desktop_config.json (Windows)
{
"mcpServers": {
"kubernetes": {
"command": "python3",
"args": ["/path/to/your/mcp-server.py"],
"env": {
"KUBECONFIG": "/home/user/.kube/config"
}
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "your-github-token"
}
}
}
}
After restarting Claude Desktop, you can ask Claude: “What pods are failing in my production namespace?” and it will call your kubectl_get tool to find out.
Available MCP Servers
An ecosystem of pre-built MCP servers is growing rapidly:
| Server | What it does |
|---|---|
@modelcontextprotocol/server-filesystem |
Read/write local files |
@modelcontextprotocol/server-github |
GitHub repos, issues, PRs |
@modelcontextprotocol/server-postgres |
PostgreSQL queries |
@modelcontextprotocol/server-slack |
Slack messages and channels |
@modelcontextprotocol/server-brave-search |
Web search via Brave |
mcp-server-kubernetes |
Kubernetes cluster management |
mcp-server-terraform |
Terraform state and plans |
Browse the full list at github.com/modelcontextprotocol/servers.
MCP in Automated Systems
MCP isn’t just for interactive use. You can use MCP programmatically when building AI agents:
# Using MCP with the Anthropic Python SDK
from anthropic import Anthropic
import anthropic.types as types
# The SDK supports MCP tool schemas directly
client = Anthropic()
# Manually pass MCP tool definitions to the API
tools = [
{
"name": "kubectl_get",
"description": "Get Kubernetes resources",
"input_schema": {
"type": "object",
"properties": {
"resource": {"type": "string"},
"namespace": {"type": "string"}
},
"required": ["resource"]
}
}
]
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
messages=[{
"role": "user",
"content": "List all failing pods across all namespaces"
}]
)
Security Considerations for MCP Servers
MCP servers run locally and have the same permissions as the user running them. Key security considerations:
Limit scope: Only expose the operations the AI actually needs. A read-only kubectl wrapper is safer than one with apply/delete capabilities.
Validate inputs: Don’t blindly pass AI-provided arguments to subprocess calls. Validate and sanitize:
import re
def validate_namespace(namespace: str) -> str:
"""Validate Kubernetes namespace name"""
if not re.match(r'^[a-z0-9][a-z0-9-]*[a-z0-9]$', namespace):
raise ValueError(f"Invalid namespace: {namespace}")
return namespace
Audit logging: Log all tool calls with their arguments:
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp-server")
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
logger.info(f"Tool called: {name} with args: {arguments}")
# ... rest of implementation
Principle of least privilege: Run MCP servers with a service account or kubeconfig that has only the permissions needed.
Conclusion
Model Context Protocol is solving the integration problem for AI tooling. Instead of every team building their own tool integration, MCP provides a standard that works across AI hosts and servers. The ecosystem is growing rapidly — database integrations, cloud provider APIs, development tools, communication platforms.
If you’re building internal tooling for AI use, building an MCP server is the path that makes your work reusable across whatever AI client your team uses. If you’re building AI integrations for your cluster or services, there’s likely already an MCP server that does what you need.
Start with the filesystem and GitHub MCP servers in Claude Desktop to get a feel for the model, then build your own for your specific needs.