Agentic Coding

MCP for Developer Tooling: A Practical Introduction

Connect internal tools, docs, and databases to coding agents with MCP servers. A practical guide to agentic coding that acts on your stack, not just generic knowledge.

By Laxaar Engineering Team Sep 1, 2026 11 min read
MCP for Developer Tooling: A Practical Introduction

Coding agents are only as useful as the context they can reach. A model that knows Python syntax but can't read your schema, query your internal API, or look up your team's runbooks will produce generic output: plausible-looking code that still needs heavy editing because it was written against an imaginary version of your stack.

The Model Context Protocol (MCP) is the mechanism that closes that gap. It lets you expose your own tools, databases, and documentation as structured resources that any MCP-compatible agent can call. The agent stops guessing about your infrastructure and starts operating on it directly.

At Laxaar, we've found this to be the most impactful change teams can make when doing agentic coding. The model you already have gets dramatically better output when it has accurate information about the thing you're actually building.

What you'll learn

What MCP is and how it fits into agent architectures

MCP (Model Context Protocol) is an open protocol, introduced by Anthropic, that standardises how AI models communicate with external tools and data sources. Instead of each agent framework inventing its own tool-calling convention, MCP defines a shared interface: an MCP server exposes resources, tools, and prompts; an MCP client (your agent host) calls them.

The practical outcome is that an MCP server you write once works with Claude Desktop, Claude Code, Cursor, Continue, and any other MCP-compatible host without modification. You build the integration once and it's available everywhere your team runs agents.

In a typical agentic workflow, the agent receives a task, decides it needs information or an action outside its context window, calls one or more MCP tools, incorporates the results, and continues. Without MCP, those tool calls are hardcoded into the agent's system prompt or framework config. With MCP, the server advertises its capabilities at connection time. The agent discovers what's available and uses what it needs.

The architecture has three moving parts: the host (the IDE, CLI, or agent runner), the MCP client embedded in the host that speaks the protocol, and the MCP server you write that wraps your tools. Communication happens over stdio for local servers and HTTP with Server-Sent Events for remote ones.

The anatomy of an MCP server

An MCP server is a process that responds to a small set of protocol messages. The core surface area is straightforward:

  • Tools. Functions the agent can call with structured arguments. Think of these as the agent's hands: they perform actions or fetch data.
  • Resources. Read-only data sources the agent can request by URI. Useful for documentation, schemas, file trees.
  • Prompts. Reusable prompt templates the agent can invoke by name. Good for team-standard formats and context injections.

The server declares these at initialization. When the agent host connects, it asks for the server's capabilities via tools/list, resources/list, and prompts/list. The agent then has a live menu of what it can do with your system.

One important design insight: the tool descriptions you write become the agent's vocabulary for deciding when to call each tool. A vague description leads to missed calls and hallucinated alternatives. A specific one (naming the data source, the expected input shape, and when it's relevant) lets the agent route correctly.

Writing your first MCP server in Python

The mcp Python SDK handles the protocol mechanics. You focus on writing the tool logic.

from mcp.server.fastmcp import FastMCP
import httpx
import os

mcp = FastMCP("internal-tools")

@mcp.tool()
async def get_service_status(service_name: str) -> dict:
    """
    Check the current health and deployment status of an internal service.
    Use this when you need to know if a service is running, its current
    version, or its last deployment time.

    Args:
        service_name: The short identifier of the service (e.g. 'auth-api', 'billing-worker')
    """
    base_url = os.environ["INTERNAL_DASHBOARD_URL"]
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"{base_url}/api/services/{service_name}",
            headers={"Authorization": f"Bearer {os.environ['DASHBOARD_TOKEN']}"},
            timeout=10.0,
        )
        resp.raise_for_status()
        return resp.json()

@mcp.tool()
async def search_runbooks(query: str, limit: int = 5) -> list[dict]:
    """
    Search the internal runbook library for operational procedures.
    Use when the task involves deployment, rollback, incident response,
    or any procedure that may have a documented step-by-step guide.

    Args:
        query: A natural-language description of the procedure you're looking for
        limit: Maximum number of results to return (default 5)
    """
    base_url = os.environ["DOCS_API_URL"]
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"{base_url}/search",
            params={"q": query, "type": "runbook", "limit": limit},
            headers={"Authorization": f"Bearer {os.environ['DOCS_TOKEN']}"},
            timeout=10.0,
        )
        resp.raise_for_status()
        return resp.json()["results"]

@mcp.resource("schema://database/{table_name}")
async def get_table_schema(table_name: str) -> str:
    """Returns the column definitions and indexes for a database table."""
    import asyncpg
    conn = await asyncpg.connect(os.environ["DATABASE_URL"])
    try:
        rows = await conn.fetch(
            """
            SELECT column_name, data_type, is_nullable
            FROM information_schema.columns
            WHERE table_name = $1
            ORDER BY ordinal_position
            """,
            table_name,
        )
        if not rows:
            return f"Table '{table_name}' not found."
        lines = [f"{r['column_name']} {r['data_type']} {'NULL' if r['is_nullable'] == 'YES' else 'NOT NULL'}"
                 for r in rows]
        return f"Table: {table_name}\n" + "\n".join(lines)
    finally:
        await conn.close()

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

To wire this into Claude Code or any MCP host, add an entry to your MCP config:

{
  "mcpServers": {
    "internal-tools": {
      "command": "python",
      "args": ["/path/to/your/server.py"],
      "env": {
        "INTERNAL_DASHBOARD_URL": "https://dashboard.internal",
        "DASHBOARD_TOKEN": "...",
        "DOCS_API_URL": "https://docs.internal",
        "DOCS_TOKEN": "...",
        "DATABASE_URL": "postgresql://..."
      }
    }
  }
}

The server starts on demand and the host manages the process lifecycle. No HTTP server to run, no port to expose on your dev machine.

What tools are worth exposing first

Not every internal system needs an MCP server. The highest-return tools share a pattern: the agent needs the information repeatedly, the information lives in a system the agent can't reach through public docs, and fetching it manually takes more than 30 seconds.

Concrete examples worth prioritising:

Database schema access. Coding agents write queries and ORM models constantly. If the agent can fetch column definitions and foreign keys directly, it stops guessing column names and types. This single tool eliminates a category of hallucinated schema.

Internal API specs. If your team has private REST or gRPC APIs, expose their OpenAPI specs as MCP resources. The agent can read the spec before generating a client or integration test.

Error search. A tool that queries your error tracking system (Sentry, Datadog, etc.) by error message or trace ID lets the agent pull real production context when debugging rather than working from a stack trace you've pasted into the chat.

Deployment and service status. When an agent is doing infrastructure-related tasks, knowing the current deployment state of a service prevents it from writing code against a version that's not live.

Ticket and PR lookup. Agents that can fetch the description of the Jira ticket or the linked PR have better task framing and produce output that matches the actual acceptance criteria.

Start with two or three of these. Each one compounds: as the agent's awareness of your stack grows, the quality of its output improves across all tasks, not just the ones that directly use that tool.

Security boundaries and access control

MCP servers run with whatever permissions you grant them. On a developer's local machine, a server that can read the database schema and query a read-only analytics endpoint is fine. A server that can execute arbitrary SQL, push to production, or call billing APIs needs more thought.

A few principles the Laxaar team applies on every MCP build:

Read-only by default. Start with tools that fetch data, not tools that mutate it. The agent can still be highly effective with read access (schema, status, docs), and you avoid the class of incident where the agent runs a destructive operation it was told "not to."

Scope credentials to the server. The token your MCP server uses should have the minimum permissions required by its tools. A docs-search server shouldn't carry a token that can write to the database.

Log tool calls. MCP servers should emit structured logs for every tool invocation: which tool, which arguments, which user or session triggered it. Without this, you can't audit what the agent did during an autonomous run.

Human-in-the-loop for writes. If a tool does write something (creating a ticket, opening a PR draft, inserting a row), build a confirmation step or make the operation return a preview before committing. Agents are good at drafting; humans should approve state changes.

MCP vs function calling: when each fits

MCP and direct function calling (as supported by OpenAI, Anthropic, and others) solve related but different problems.

DimensionMCPDirect function calling
Tool discoverabilityDynamic at connection timeStatic in system prompt
PortabilityWorks across any MCP-compatible hostTied to the specific API/SDK
Tooling complexityServer process requiredJust a function definition in your app
Best forInternal infra tools, shared team serversApp-specific actions in a single product
Versioning and updatesServer-side, no client change neededMust update prompt/schema everywhere used
Local vs remoteBoth (stdio or HTTP/SSE)Depends on framework

The honest answer: if you're building a single application with its own agent loop and you control the full stack, direct function calling is simpler. MCP pays off when you want the same tools available across multiple agent hosts (your IDE, your CLI tool, your automated pipeline) or when the tools are owned by a different team than the application code.

For teams building on AI-powered software development workflows across multiple products and environments, MCP wins on maintainability. You update the server once and every host picks up the change.

Common mistakes when wiring MCP to your stack

Vague tool descriptions. The single most common issue. If a tool description says "get data from the API," the agent will call it inconsistently and sometimes ignore it in favour of guessing. Write the description as if you're explaining to a new engineer when and why to use this endpoint, not just what it does.

Returning raw, noisy responses. When a tool returns a 2000-line JSON blob, the agent spends tokens parsing it instead of using it. Shape the output: return only the fields the agent is likely to need, summarise counts, and strip internal IDs the agent can't act on.

No error handling or graceful degradation. If a tool throws an unhandled exception, the agent often retries or halts rather than routing around the failure. Return structured error responses ({"error": "service_unavailable", "message": "..."}) so the agent can decide how to proceed.

Exposing too many tools at once. A server that advertises 40 tools creates a routing problem. The agent struggles to choose between overlapping tools and may call the wrong one. Group related tools in separate servers, or keep a single server focused on a domain (database, CI/CD, docs) with no more than 8-10 tools each.

Skipping local testing. MCP servers can be tested with the mcp CLI's inspector before you wire them to a real agent host. Run mcp dev server.py to get an interactive interface that lets you call each tool directly. Testing at the protocol level is faster than round-tripping through an IDE every time.

Our custom software development work at Laxaar frequently starts with a tooling audit: mapping which internal systems a development team touches most often and building MCP servers for the top three. The time savings compound quickly once agents can reach the stack accurately.

Frequently Asked Questions

Do I need to write a separate MCP server for every tool?

No. A single MCP server can expose multiple tools, resources, and prompts. The practical split is by domain or ownership: one server for database access, another for your CI/CD system, another for internal docs. That way each server can be maintained independently and scoped to the credentials it actually needs.

Can MCP servers call other MCP servers?

Not directly through the protocol. MCP is a client-server model, not a peer-to-peer one. But an MCP server tool can call another HTTP service, and if that service happens to be an MCP server's HTTP endpoint, you can compose them at the application layer. For most teams, the cleaner pattern is having the agent call multiple servers in sequence rather than chaining servers internally.

How do I handle authentication for MCP servers accessing internal APIs?

Environment variables passed in the MCP host config are the standard approach for local servers. For remote servers deployed inside your infrastructure, the HTTP transport supports Authorization headers. Avoid hardcoding credentials in server source code. Use environment injection so the same server binary can run with different permission scopes in dev and production.

Is MCP stable enough to use in production today?

The spec reached 1.0 in late 2024 and is backed by Anthropic with active adoption from Cursor, Block, Sourcegraph, and others. The Python and TypeScript SDKs are stable. The area that's still evolving is the remote transport story (HTTP/SSE at scale) and the authorization spec. For local developer tooling and internal CI pipelines, MCP is production-ready. For public-facing remote servers with complex auth, keep a close eye on the authorization spec work.

What's the performance overhead of going through MCP?

For local stdio servers the round-trip latency is under 5ms. The bottleneck is almost always the underlying tool (a database query, an API call) rather than the protocol itself. For remote HTTP servers, you're adding one network hop. Neither is meaningful compared to the LLM inference time in a typical agent turn.


Ready to wire your team's tools into your coding agents? The Laxaar team builds custom MCP servers and agentic development infrastructure for engineering teams. Reach out to talk through which internal systems would give your agents the most impact.

Working on something like this?

Get a fixed scope, timeline, and price within one business day — no obligation.

Agentic CodingMCP ServersAI-Powered Development
Grow your business with us

Take your business to the next level.

Tell us what you're building. We'll come back inside one business day with a fixed scope, timeline, and team — or an honest “this isn't a fit”.

ENGINEERING PHILOSOPHY

Code is useless if it's not comprehensible to those who maintain it. We write code the next person can actually understand.