X

Connecting Claude & AI Agents to Custom Local MCP Tools in Python

The release of the open-source Model Context Protocol (MCP) by Anthropic has fundamentally solved the fragmented landscape of AI tool calling. Before MCP, connecting an LLM to your local database, developer terminal, or internal API required writing bespoke integration wrappers for every distinct framework (LangChain, LlamaIndex, Semantic Kernel, and AutoGen).

With MCP, you write your tool once as a standardized server, and any compliant host—such as the Claude Desktop app, Cursor, or custom Python autonomous agents—can immediately discover, inspect schemas, and execute your tools securely.

If you have already read our introductory guide on how to Build a Model Context Protocol (MCP) Server in Python and explored Tool Calling in AI Agents: 2026, this tutorial takes your skills to the production level. In this hands-on engineering walkthrough, we cover how to build custom local tools and connect python mcp server ai agent systems step-by-step.

1. High-Level Architecture: Client-Server Protocol Model

MCP follows a client-server architecture where communication occurs through standardized JSON-RPC 2.0 messages transmitted via standard input/output (stdio) or Server-Sent Events (SSE):

Model Context Protocol (MCP) Runtime Architecture
Bidirectional JSON-RPC 2.0

1. Host / Client Layer (Claude Desktop or Custom Python Agent)

Runs the LLM client application and initiates tool discovery using mcp.client.stdio. When you prompt Claude, it checks registered schemas to determine if a local action is required.

↓ Transport Layer: Standard Input / Output (stdin & stdout) Pipes ↓

2. Local Subprocess Boundary (FastMCP Server)

An isolated Python worker process managed by FastMCP. The server exposes type-safe functions without binding public HTTP ports, executing commands entirely within your secure local environment.

Tool 1: SQLite Inspector
Tool 2: Git Telemetry
Tool 3: S3 Healthcheck

Because the server runs as an isolated subprocess, it runs safely inside your local development environment without exposing public HTTP ports to the internet.

2. Prerequisites & Environment Setup

🚀 High-Performance GPU Cloud for LLMs

Need on-demand GPUs to run Ollama, vLLM, or fine-tune models in production? RunPod offers serverless GPUs and secure pods from $0.20/hr.

Deploy on RunPod →

Set up a dedicated Python 3.10+ virtual environment and install the official Anthropic and MCP SDKs:

pip install --upgrade mcp anthropic pydantic

Verify your installation:

import mcp
import anthropic
print(f"MCP SDK Version: {mcp.__version__}")
print(f"Anthropic SDK Version: {anthropic.__version__}")

3. Step 1: Building a Custom Local MCP Server in Python

Create a file named server.py. We will expose two essential developer tools: a system resource inspector and an SQLite query executor:

import os
import psutil
import sqlite3
from typing import Dict, Any, List
from mcp.server.fastmcp import FastMCP

# Initialize FastMCP Server
mcp = FastMCP("LocalDevOpsTools")

@mcp.tool()
def get_system_telemetry() -> Dict[str, Any]:
    """Fetches live CPU utilization, memory pressure, and available disk space."""
    return {
        "cpu_percent": psutil.cpu_percent(interval=0.5),
        "memory_used_gb": round(psutil.virtual_memory().used / (1024**3), 2),
        "memory_total_gb": round(psutil.virtual_memory().total / (1024**3), 2),
        "disk_free_gb": round(psutil.disk_usage("/").free / (1024**3), 2)
    }

@mcp.tool()
def query_developer_db(sql_query: str) -> List[Dict[str, Any]]:
    """Executes a read-only SELECT query against the local SQLite developer database."""
    # Enforce read-only safety guardrail
    cleaned = sql_query.strip().upper()
    if not cleaned.startswith("SELECT") or any(kw in cleaned for kw in ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"]):
        raise ValueError("Security Violation: Only read-only SELECT queries are permitted.")

    db_path = os.path.expanduser("~/dev_database.db")
    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    cursor.execute(sql_query)
    rows = cursor.fetchall()
    results = [dict(row) for row in rows]
    conn.close()
    return results

if __name__ == "__main__":
    # Launch via stdio transport
    mcp.run(transport="stdio")

4. Step 2: Connecting Claude Desktop to Your Local MCP Tools

Claude Desktop natively reads configured MCP servers from a JSON manifest.

Configuration File Locations:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

Open or create claude_desktop_config.json and add your Python server configuration:

{
  "mcpServers": {
    "local-devops": {
      "command": "/Users/username/.venv/bin/python",
      "args": [
        "/Users/username/projects/mcp-tools/server.py"
      ],
      "env": {
        "PYTHONUNBUFFERED": "1"
      }
    }
  }
}

Important: Always use the absolute path to your virtual environment Python binary and your script to prevent PATH resolution errors.

Restart Claude Desktop. In the bottom-right corner of the chat input box, you will now see a Hammer (Tools) icon indicating that your custom tools get_system_telemetry and query_developer_db are registered and available to Claude!

5. Step 3: Building a Custom Autonomous AI Agent in Python

If you are developing custom agentic software, you can connect directly to the MCP server from Python using the mcp.client.stdio transport and Anthropic’s Messages API:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from anthropic import Anthropic

async def run_mcp_autonomous_agent(user_query: str):
    # 1. Define server parameters pointing to local Python MCP server
    server_params = StdioServerParameters(
        command="python",
        args=["server.py"],
        env=None
    )

    anthropic_client = Anthropic()

    # 2. Establish stdio client connection to MCP server
    async with stdio_client(server_params) as (read_stream, write_stream):
        async with ClientSession(read_stream, write_stream) as session:
            # Initialize connection and handshake
            await session.initialize()

            # 3. Discover available tools from MCP server dynamically
            tools_result = await session.list_tools()
            available_tools = []
            for tool in tools_result.tools:
                available_tools.append({
                    "name": tool.name,
                    "description": tool.description,
                    "input_schema": tool.inputSchema
                })

            print(f"✓ Discovered {len(available_tools)} MCP tools: {[t['name'] for t in available_tools]}")

            # 4. Invoke Claude with user instruction and MCP tool schemas
            messages = [{"role": "user", "content": user_query}]
            response = anthropic_client.messages.create(
                model="claude-3-5-sonnet-20241022",
                max_tokens=1024,
                tools=available_tools,
                messages=messages
            )

            # 5. Check if Claude requested tool execution
            if response.stop_reason == "tool_use":
                for block in response.content:
                    if block.type == "tool_use":
                        tool_name = block.name
                        tool_args = block.input
                        print(f"⚡ Claude requested execution: {tool_name}({tool_args})")

                        # Execute tool via MCP session
                        execution_result = await session.call_tool(tool_name, tool_args)
                        tool_output = execution_result.content[0].text

                        # Feed tool result back to Claude for final synthesis
                        messages.append({"role": "assistant", "content": response.content})
                        messages.append({
                            "role": "user",
                            "content": [
                                {
                                    "type": "tool_result",
                                    "tool_use_id": block.id,
                                    "content": str(tool_output)
                                }
                            ]
                        })

                        final_response = anthropic_client.messages.create(
                            model="claude-3-5-sonnet-20241022",
                            max_tokens=1024,
                            messages=messages
                        )
                        print(f"\nAgent Final Answer:\n{final_response.content[0].text}")
            else:
                print(f"Claude Response: {response.content[0].text}")

if __name__ == "__main__":
    asyncio.run(run_mcp_autonomous_agent("Check my local system telemetry and report if memory usage is within safe bounds."))

6. Security & Sandboxing Best Practices

  1. Read-Only Enforcements: When connecting tools to databases or filesystem paths, enforce strict whitelist checks (e.g., verifying SELECT statements only).
  2. Path Sanitization: When accepting filenames or directory paths, use os.path.abspath and verify that the target path does not escape your sandbox via directory traversal (../).
  3. Execution Timeouts: Wrap tool execution in asyncio.wait_for(timeout=15.0) to prevent hung processes from blocking your AI agent loop.

7. Common Errors & Troubleshooting

Error 1: MCP server exited with code 1 before handshake

Root Cause: A syntax error or missing module import in server.py.
Fix: Run python server.py directly in your terminal. If the script throws a traceback or exits immediately, fix the Python exception before launching through Claude Desktop.

Error 2: FileNotFoundError: [Errno 2] No such file or directory: 'python'

Root Cause: Claude Desktop runs without inheriting your terminal’s custom shell $PATH.
Fix: Specify the complete absolute path to your Python interpreter (e.g. /usr/local/bin/python3 or /home/user/venv/bin/python).

8. Frequently Asked Questions (FAQ)

Can I run MCP servers across different programming languages?

Yes! One of the greatest strengths of MCP is language interoperability. Your client can be written in Python while connecting to MCP servers written in TypeScript, Go, Rust, or C#.

Does MCP support streaming output?

Yes. MCP supports progress notifications and streaming chunk responses across long-running data analysis and retrieval tasks.

9. Summary & Actionable Checklist

  1. Implement custom tools: Use mcp.server.fastmcp.FastMCP with @mcp.tool() decorators.
  2. Configure Claude Desktop: Add your server definition to claude_desktop_config.json with absolute paths.
  3. Build programmatic Python agents: Connect via stdio_client and ClientSession to dynamically forward tools to Claude or custom LLM runtimes.
  4. Implement safety guardrails: Enforce strict query parsing and path validation before executing system operations.

📦 Download the Production Code & Architecture Checklist

Get the complete, runnable GitHub repository for this tutorial, including Docker Compose configs, environment templates, and CI/CD pipelines.

Join 1,200+ Engineers on TechJunkGigs Digest (Free) →

admin:
Related Post