Happy Cinco de Mayo, by the way. Fun fact, the holiday started in California, not Mexico.
Enough theory, let’s see what’s really inside one of these things!
To do that, we’re going to put together a very simple MCP server in Python. It’s going to do one thing, tell Dad Jokes1:
That’ll do.
Quick Architecture
There’s a bit more complexity to MCP than what I’m going to discuss, but this is all you really need to get things up and running.
At the highest level, the MCP client (that’s Claude Desktop) starts the MCP Server running, and feeds requests into the server on stdin and gets response on stdout2.
For the MCP server we’re writing, we need to read the requests from Claude, process them, and send responses. Fortunately, there’s a library to make that easy in python, called, no surprise, mcp.
And here is the entirety of the dad joke server (with most jokes omitted for brevity):
"""
DadJokes - MCP Server
A simple MCP server that provides random dad jokes.
"""
import random
import sys
from mcp.server.fastmcp import FastMCP, Context
# Create the MCP server
mcp = FastMCP("DadJokes")
# List of dad jokes
DAD_JOKES = [
"I'm afraid for the calendar. Its days are numbered.",
"Why do fathers take an extra pair of socks when they go golfing? In case they get a hole in one!",
"I got a new pair of gloves today, but they're both 'lefts' which, on the one hand, is great, but on the other, it's just not right.",
]
@mcp.tool()
def TellMeAJoke(ctx: Context) -> str:
"""
Returns a random dad joke.
Returns:
str: A randomly selected dad joke.
"""
joke = random.choice(DAD_JOKES)
return joke
def main():
"""Main entry point for the MCP server."""
try:
# Run the MCP server
mcp.run()
except Exception as e:
sys.stderr.write(f"Error running DadJokes server: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()
I’ve bolded a couple of sections to highlight some key points:
The line mcp = FastMCP("DadJokes") creates the server that will listen to and respond to requests.
The line @mcp.tool(), right before a function definition, indicates that that function will be callable as an MCP service.
The string that’s in bold (starts with """) is the description of the function which is given to the client (in our case, Claude Desktop) that describes what the function does. That’s how it knows to call it. It’s also a good place to add in any addition instructions on how to use the service or what other steps might be needed before or after the service is used.
This one function is just about as dumb as they come, it doesn’t take any arguments and doesn’t do a lot of work. But this is the starting point. If you want it to take arguments, you just add them into the function definition like normal and add a description of what they are and how they are used.
Simple, yes?
A few more points:
Even though we didn’t write a function to list all the services offered, the MCP library looks at all our @mcp.tool() tagged functions and will return a list of them (with descriptions that you have written) to Claude when asked.
If you are serving up files etc., there’s a special kind of tool called a resource that has a standardized way of generating URI (Universal Resource Indicators) for the files (or database tables or whatever…).
There is a mechanism for putting prompts into the MCP Server, although I’ve not seen that being used so it may be not that common.
Now what?
Hopefully now you have a better sense of what MCP does and how it works. It’s not super complex. In fact, it’s kind of disappointingly simple: I “vibe coded” the dad joke server. The only work I did was to trim it down (by removing jokes) so it didn’t take up as much space in the article. More complex services will be harder to build, of course, but the MCP interface should always be almost effortless.
For those of us who remember the great IOT hype-curve peak, we could build MCP servers that control devices. I mean, who doesn’t want to be able to tell an LLM to open the garage or check the temperature in the living room? Wait, where are you all going???
One last concern I have, is that once you load up Claude with lots of MCP servers, is it going to be bogged down trying to figure out what to do? Certainly the start-up time seems more drawn out when I add in lots of MCP servers for it to bootstrap.
Updates
On May 1st, Anthropic announced that you can call remote MCP servers. (You could always sort of do this, but it was a bit of a hack and not directly supported).
How important this is really depends upon how many other client apps support it. Early days, still. Although once you go into remote APIs, there’s a whole world of other issues that arise that probably haven’t been thought all the way through by the writers of the MCP spec (even though they are no doubt aware of them).
Of course, LLMs are perfectly capable of telling Dad Jokes on their own. In fact, all the Dad Jokes on this server were generated by an LLM. That’s not the point! It’s just the simplest server I could think of that did something…
These terms come from command line programs (that is, NOT GUI programs): “stdin” represents what is normally the keyboard while “stdout” represents what the program outputs. If you’ve ever used the command line in a Linux or Mac system, you may be familiar with doing things like “ls -al | more”, which says to take the output of the “ls” command (which lists files) and send it directly into the input of “more” (which outputs a page’s worth of text and then pauses for you to indicate when you’re ready for the next page). In this case, the “stdout” of “ls” isn’t going directly to you, but is filtered through “more”. It’s a pretty simple concept — daisy chain programs together. In the case of MCP servers, it’s not a chain, it’s a loop…