Skip to main content

MCP with Python

Summary​

🧭 What this page covers

MCP with .NET showed the C# implementation style. This page adds the Python path so teams can implement MCP servers using FastAPI, async services, and existing Python data tooling.

Python is a strong MCP option when your AI workflows, data pipelines, or orchestration logic already run in Python. The same MCP architecture applies: keep the MCP layer thin, validate inputs, and route business logic to existing services.

ConcernPython approachExample
Tool implementationAsync functions with typed models (Pydantic/dataclasses).search_tickets uses async def and returns a typed response object with deterministic fields.
ValidationPydantic schemas and explicit guards before executing side effects.Reject requests where query is empty or status is outside allowed enum values.
Transport hostingLocal process for stdio, web app for remote transport.Start locally for a VS Code host; move to FastAPI + streaming when shared across services.
ObservabilityStructured logs, traces, metrics (OpenTelemetry).Emit tool_name, duration_ms, status, and correlation ID for each invocation.

Core Concepts​

Minimal server shape​

from pydantic import BaseModel, Field

class TicketSearchRequest(BaseModel):
query: str = Field(min_length=1)
status: str = "open"

class TicketSearchResult(BaseModel):
ticket_id: str
title: str
status: str

class TicketService:
async def search(self, request: TicketSearchRequest) -> list[TicketSearchResult]:
raise NotImplementedError

Thin MCP adapter pattern​

import logging
from typing import Sequence

logger = logging.getLogger(__name__)

class TicketTools:
def __init__(self, ticket_service: TicketService) -> None:
self.ticket_service = ticket_service

async def search_tickets(self, request: TicketSearchRequest) -> Sequence[TicketSearchResult]:
if not request.query.strip():
raise ValueError("query is required")

logger.info("mcp_tool=search_tickets status=%s", request.status)
return await self.ticket_service.search(request)

Host-side usage with a Python AI app​

# Pseudocode: discover MCP tools, curate them, and expose only safe ones.
approved_tools = await mcp_client.list_tools()
safe_tools = [t for t in approved_tools if t.name in {"search_tickets", "get_ticket_details"}]

response = await llm_client.responses.create(
model="gpt-4.1",
input=messages,
tools=[convert_to_llm_tool(t) for t in safe_tools],
)

Realistic Example​

Scenario​

A support assistant uses Python for orchestration and reporting. Existing enterprise systems remain unchanged:

  • Device inventory API (existing service)
  • Ticketing API (existing service)
  • Security knowledge base (existing content store)

The Python MCP server exposes:

  • find_devices_by_user
  • search_tickets
  • get_password_policy (as a resource)

The host curates which capabilities are visible by user role, and the server enforces final authorization before execution.

Senior Tech vs Dev Conversation​

Senior Tech: Are we rewriting the .NET services in Python to use MCP?

Dev: No. MCP is the protocol boundary, not a rewrite requirement. Python can orchestrate and adapt existing services, including .NET APIs, as long as contracts remain stable and authorization is enforced.

Senior Tech: Is Python too dynamic for safe tool contracts?

Dev: It can be safe with typed schemas and strict validation. Pydantic models, explicit enums, and structured responses give us predictable contracts similar to typed C# DTOs.

Common Pitfalls​

PitfallWhat goes wrongPrevention
Mixing orchestration and domain logicMCP layer becomes a second applicationKeep business rules in existing services
Returning unstructured dictionariesHost/model integration becomes brittleUse typed request/response schemas
Exposing all tools to every sessionRisky capability surfaceCurate visible tools per scenario and role
Logging without correlation IDsMulti-hop debugging is slowInclude request or trace IDs in every invocation

References and Next Steps​