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 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:
from mcp.server import Serverfrom mcp.types import Tool, TextContentimport 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:
@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:
{ "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:
@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:
| Mistake | Why It’s Bad | Fix |
|---|---|---|
| No error handling | Claude gets confusing errors | Wrap operations in try-catch, return clear messages |
| Hardcoded credentials | Security risk | Use environment variables |
| Missing inputSchema | Claude can’t call tool properly | Define all parameters with types |
| No rollback | Can’t recover from mistakes | Create 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