DANIEL KIRCHNER
All articles
March 28, 20267 min read

MCP Servers: The Model Context Protocol in Practice

MCPAILangChainPythonFastAPILLM

A model writes good text. That is all it does on its own. It cannot reach your database, send mail or call an internal API. For a chatbot on a knowledge platform that means: without help the model knows nothing about your own content, it guesses.

MCP is that help, as an open standard. Instead of wiring a new integration for every LLM, the model gets one shared interface to tools and data sources. The model decides when to call which tool. The server decides what the tool returns.

Three parts, no magic

An MCP server has:

  • Tool definitions: which functions the model gets to see
  • Transport: stdio or HTTP/SSE, how the model talks to the server
  • Implementation: what actually happens when a tool runs
  • None of that is LLM-specific. That is the point.

    The server from the healthcare project

    For the healthcare platform with 500+ video and PDF items, I built an MCP server that gives the chatbot access to the knowledge base instead of stuffing every possible source into the prompt.

    from mcp.server import Server
    from mcp.types import Tool, TextContent
    
    server = Server("health-knowledge")
    
    @server.tool()
    async def search_knowledge(
        query: str,
        category: str | None = None,
    ) -> list[TextContent]:
        """Search the knowledge base for relevant content."""
        results = await vector_store.similarity_search(
            query=query,
            filter={"category": category} if category else None,
            k=5,
        )
        return [
            TextContent(
                type="text",
                text=f"**{r.metadata['title']}**\n{r.page_content}"
            )
            for r in results
        ]
    
    @server.tool()
    async def get_article_details(article_id: str) -> list[TextContent]:
        """Fetch the full details of a knowledge article."""
        article = await db.get_article(article_id)
        return [TextContent(type="text", text=article.to_markdown())]
    

    Two tools. Search and detail. The chatbot calls search_knowledge, gets the top 5 hits, calls get_article_details when it needs the full version. The server runs independent of the client. Whether Claude, a custom LangChain agent or another model asks, it does not care.

    MCP versus classic function calling

    AspectFunction callingMCP
    Standardper provider (OpenAI, Anthropic)open standard
    TransportHTTP/RESTstdio, HTTP/SSE
    Discoverywired manuallyautomatic tool discovery
    Reuserebuilt per integrationone server, many clients

    Function calling has been around longer, but it is wired differently per provider. MCP is open, has automatic tool discovery, and runs over stdio just as well as HTTP/SSE. The practical difference: a server I build once runs behind every MCP-capable client. With function calling you rebuild the integration per provider.

    Access is not an afterthought

    A tool that reaches the knowledge base needs the same access control as any other internal API. The MCP server often runs with wider privileges than the chat itself, because it touches a database or a filesystem directly. If you build the server on the argument that "the model decides what it queries anyway," with no authorization of its own, you hand access control to a text predictor. The tenant context belongs in the server call, not the prompt, for the same reason as the retrieval filter in a RAG system.

    What breaks in practice

    Tool descriptions are the interface, not documentation, and that gets underestimated regularly. The model decides purely from the text whether and when to call a tool. A weak description means the tool gets called too rarely or at the wrong moment, so I write examples into the description, not just one line of purpose.

    Errors also have to be interpretable. The database is down, the search returns nothing. An MCP server that throws an exception in that case does not help the model. It has to return something the model can read as "no results" or "error, ask differently."

    Without logging, a tool call is simply a blind spot: which tool, with which arguments, how long, what result. Every call needs metadata logged, timestamp, tool name, input, hit count, latency. Skip that and a bad answer cannot be reconstructed later, which becomes a problem the moment someone asks why the bot said something wrong.

    And rate limits belong in the server, not the model. An agent looping on the same tool call ten times a second because one intermediate result was not enough to continue is not an edge case, it is the normal case in more complex agent flows. A simple limit per tool and per session stops a model loop from turning into a cost spike or a database incident.

    When it is worth it

    When the chatbot needs internal sources that are not in the training corpus. When the same tool integration should serve more than one LLM. When an agent should act on your systems, not only talk. For a demo with a handful of questions the effort is not worth it. For a system that runs in production over months, it is.

    A server with the official Python SDK is built in a few days. The work after that, sharpening tool descriptions, covering error cases, setting up logging, takes longer than the first version.

    When it is not worth it

    A single internal endpoint that only ever gets called by one client does not need its own server with discovery and a transport layer. A direct function call is enough. MCP pays off once more than one client needs the same tools, or once the tool set grows enough that discovery makes the difference between a config file per client and one shared definition. Before that point, it is extra infrastructure for a problem that does not exist yet.

    Practice: the healthcare chatbot. To read the existing estate before rollout: architecture reviews. Technical lead for a stretch: interim CTO. The service behind this sits under AI engineering.

    If this needs to go into production: the LLM readiness check checks on day 3, among other things, whether tool calls can be traced.

    The standard itself is still young. What it solves is not new: systems have always needed a clean interface to their data. MCP turns that into a shared language instead of every team reinventing its own.

    Related articles

    GDPR and data residency once an LLM is in the loop

    Where does the model run, where do embeddings and logs sit? The technical list that makes a privacy review of an AI system possible.

    Read article

    The EU AI Act from the seat of the person building the system

    Traceability, human oversight, documentation: what an engineer can contribute to the AI Act, and where the legal classification begins.

    Read article

    A call

    30 minutes. If the use case does not belong in production, I'll say so.