Skip to content

How to Build MCP Tools for Claude Code: Extending Claude's Capabilities

Problem

When I tried to let Claude Code interact with my WordPress site or custom APIs, I hit a wall. Claude Code couldn’t directly access:

  • My application’s database
  • WordPress content and settings
  • Custom internal APIs
  • Any system outside the working directory

I had to manually copy-paste context, which was tedious and error-prone. Then I discovered MCP (Model Context Protocol).

Environment

  • Claude Code CLI
  • Python 3.10+ (for MCP server implementation)
  • macOS/Linux
  • Node.js (optional, for TypeScript-based MCP servers)

What MCP Is

MCP stands for Model Context Protocol. It’s a JSON-RPC 2.0 based protocol that lets Claude Code connect to external systems through “servers”.

Think of MCP as a bridge between Claude Code and your real-world systems. The Reddit discussion highlights this perfectly: one user built 151 MCP tools to let Claude safely edit WordPress sites using page builders like Elementor and Divi.

The protocol provides three core primitives:

MCP Primitives
┌─────────────────────────────────────────────────────────────┐
│ MCP Architecture │
├─────────────────────────────────────────────────────────────┤
│ │
│ TOOLS → Actions Claude can call │
│ (e.g., create_post, edit_page) │
│ │
│ RESOURCES → Data Claude can read │
│ (e.g., database schema, settings) │
│ │
│ PROMPTS → Pre-defined templates │
│ (e.g., "create a blog post workflow") │
│ │
└─────────────────────────────────────────────────────────────┘

How to Build an MCP Tool

I started with a simple goal: let Claude Code read and update WordPress pages.

Step 1: Define Your Tools

First, I created a Python MCP server that defines what tools are available:

wordpress_mcp_server.py
from mcp.server import Server
from mcp.types import Tool, TextContent
import json
server = Server("wordpress-mcp")
@server.list_tools()
async def list_tools():
return [
Tool(
name="get_page_content",
description="Get WordPress page content by ID",
inputSchema={
"type": "object",
"properties": {
"page_id": {"type": "integer", "description": "The WordPress page ID"}
},
"required": ["page_id"]
}
),
Tool(
name="update_page_title",
description="Update a WordPress page title",
inputSchema={
"type": "object",
"properties": {
"page_id": {"type": "integer"},
"new_title": {"type": "string"}
},
"required": ["page_id", "new_title"]
}
)
]

The inputSchema is crucial—it tells Claude what parameters each tool needs. Claude reads this schema and knows exactly how to call your tool.

Step 2: Implement Tool Handlers

Next, I implemented what happens when Claude calls each tool:

wordpress_mcp_server.py (continued)
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_page_content":
page_id = arguments["page_id"]
# Call WordPress REST API
content = await fetch_wp_page(page_id)
return [TextContent(type="text", text=json.dumps(content))]
elif name == "update_page_title":
page_id = arguments["page_id"]
new_title = arguments["new_title"]
result = await update_wp_page(page_id, {"title": new_title})
return [TextContent(type="text", text=f"Updated page {page_id} title to: {new_title}")]
else:
return [TextContent(type="text", text=f"Unknown tool: {name}")]

I used TextContent to return results to Claude. Claude receives this as text and can incorporate it into its reasoning.

Step 3: Connect Claude Code to Your MCP Server

I added the MCP server configuration to Claude Code’s settings:

~/.claude/settings.json
{
"mcpServers": {
"wordpress": {
"command": "python",
"args": ["wordpress_mcp_server.py"],
"cwd": "/path/to/mcp-server"
}
}
}

Now when I start Claude Code, it automatically connects to my WordPress MCP server.

Step 4: Add Safety Measures (Important!)

The Reddit discussion emphasized safety: MCP provides a “safe door” for AI edits with rollback capabilities.

I added snapshot functionality before any destructive operation:

wordpress_mcp_server.py (safety pattern)
@server.call_tool()
async def call_tool(name: str, arguments: dict):
# Safety pattern: snapshot before destructive operations
if name.startswith("update_") or name.startswith("delete_"):
snapshot_id = await create_snapshot(arguments)
result = await perform_operation(arguments)
return [
TextContent(type="text", text=f"Snapshot {snapshot_id} created for rollback"),
TextContent(type="text", text=json.dumps(result))
]

This way, if something goes wrong, I can restore from the snapshot.

The Reason MCP Matters

I think MCP is important for three reasons:

1. Safety First

MCP servers sit between Claude and your systems. You control what Claude can do. The Reddit user’s WordPress MCP creates “full fidelity snapshots for undo in case something misfires.”

2. Domain-Specific Behavior

Different systems have different quirks. The Respira WordPress MCP “teaches the AIs how to write proper code for 11 most popular page builders.” Each page builder has its own peculiarities—MCP abstracts this away.

3. Scalability

One MCP server can expose dozens or hundreds of tools. The Reddit example has 151 tools for WordPress alone. You can build MCP servers for:

  • Database operations (query, insert, update)
  • CMS management (WordPress, Ghost, Strapi)
  • Cloud services (AWS, GCP, Azure)
  • Internal APIs and microservices

Common Mistakes to Avoid

I made these mistakes when starting:

MistakeWhy It’s BadFix
No error handlingClaude gets confusing errorsWrap operations in try-catch, return clear messages
Hardcoded credentialsSecurity riskUse environment variables
Missing inputSchemaClaude can’t call tool properlyDefine all parameters with types
No rollbackCan’t recover from mistakesCreate snapshots before changes

Summary

In this post, I showed how to build MCP tools that extend Claude Code’s capabilities. The key point is that MCP acts as a safe bridge between Claude and your external systems—you define the tools, implement handlers, and configure Claude Code to connect.

Start simple: define one tool, test it, then expand. Don’t forget safety measures like snapshots for destructive operations. The Reddit discussion proves that even non-developers can build production MCP servers with Claude’s help.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments