From Zero to MCP: Three Lessons I Learned Building Tools for LLMs

Community Article Published July 30, 2025

image/png

During the Gradio Agents & MCP Hackathon 2025, I decided to explore something I didn't know: the Model Context Protocol (MCP) from Anthropic. I started just by being curious and ended up learning a ton about how LLMs can use external tools.

What is MCP in simple terms?

LLMs are very good at reasoning and generating text, but they can't do things like calculate the real distance between two cities, get real-time data, or generate images. MCP is a standard way to give LLMs access to external tools.

In my case, I built tools for geographic calculations. When you ask an LLM "how long does it take to drive from Madrid to Barcelona?", the LLM can use my tools to:

  • Convert city names into coordinates
  • Calculate the best route
  • Get the estimated travel time

All of this happens without the LLM having to "know" about geography - it just uses the tools I give it.

Discovering the Simplicity

I built a geographic calculations MCP. I was prepared for something complex, but it blew my mind how easy it was. I had it working with very little code:

demo.launch(mcp_server=True)

Gradio is very simple for this. With just one line of code, it turns your Python functions into tools that any compatible LLM can use. Gradio simplifies the process: it turns functions into tools without complex setup.

Lesson #1: Your Docstring is Your API

This made me rethink how I write code. I used to think docstrings were just for other developers, but it turns out they are much more:

def get_coords_from_address(address: str) -> str:
    """
    Converts a street address into latitude and longitude coordinates.
    
    Args:
        address (str): The address to search for (e.g., "Eiffel Tower, Paris")
        
    Returns:
        str: Formatted coordinates "Lat: XX.XXXX, Lon: YY.YYYY"
    """

The LLM reads this docstring and understands what the function does, what parameters it needs, what kind of response to expect, and even the examples of how to use it.

What I learned: Writing good docstrings is not just a good practice anymore - it is literally how you design the interface for the AI to understand your tool. Basically, your docstrings are the user manual for the AI, like a OpenAPI spec. If your function is not understood from the docstring alone, the LLM will fail.

Lesson #2: Take Care of the LLM's Context

I made a mistake at first. My routes returned huge JSON files (~5KB) with thousands of coordinates for the complete route. The problem is that LLMs have limits on how much text they can process, and I was wasting a lot of space for no reason.

The solution was very simple: generate the map image on my server and return only the reference to the file:

# Before: Massive JSON with coordinates
{"coordinates": [[lat1,lon1], [lat2,lon2], ...]} // ~5KB

# After: Compact JSON with pre-generated image
{"map_image_path": "/tmp/route_12345.webp"} // ~200 bytes

What I learned: When you build tools for LLMs, optimizing the size of the responses is as important as optimizing the speed.

Lesson #3: LLMs Combine Tools Automatically

What I did not expect at all is how LLMs use several tools in sequence without you having to explain how:

User: "How long does it take to go from the Eiffel Tower to the Louvre?"

What the LLM does (without you telling it how):

  1. Uses get_coords_from_address("Eiffel Tower, Paris")
  2. Uses get_coords_from_address("Louvre, Paris")
  3. Uses get_route_data() with those coordinates
  4. Uses extract_route_time() for the final answer

image/gif

All of this happens on its own. The LLM understands that it needs to geocode first, then plan the route, and finally extract the time. LLMs figure out these dependencies between tools by using the descriptions in the docstrings (e.g., if a function says it "needs coordinates," the LLM knows it must call the geocoder first).

What I learned: You are not creating an isolated tool, you are creating something that can be combined with other tools in smart ways.

The great thing is that by making atomic functions (that do one very clear thing), the LLM can combine them in ways I never imagined. For example, I never thought someone would ask "how many coffee shops are within a 500m radius of each point on my route to the office?" but the LLM can perfectly chain my geocoding + routing + place search tools to solve it. This flexibility opens up use cases I would have never considered.

Why I Found This Interesting

MCP makes creating tools for LLMs accessible. Any developer can write a Python function with a good docstring, put it in a Gradio app with mcp_server=True, and they have a tool that any compatible LLM can use.

Imagine the potential: scientific calculators, chart generators, data analyzers, API connectors... MCP provides a standard way for LLMs to access all of this.

Final Thought

In the end, I felt like I wasn't just coding a map MCP. It was like teaching the LLM a new skill, almost like giving it eyes to see the real world.

What other tools can you think of? I would love to hear them in the comments.


TL;DR:

  • Clear docstrings = happy LLMs
  • Compact responses > giant JSONs
  • LLMs chain tools by themselves if the docstrings are good
  • To get started: a function + good docstring + gradio.launch(mcp_server=True)

If you want to experiment, that's all you need.

Community

Sign up or log in to comment