MCP & Claude: From Weather Bots to Email Validation
Hello š
As everyone working with AI knows, MCP has been a popular topic lately. I wanted to learn about this topic and create a few applications. I was looking into it in my spare time after work, but hadnāt made much progress. The innovation week we have at the company after each cycle was an opportunity for me. During this innovation week, I set out to learn about MCP and develop at least one application.
When I first encountered Claudeās Model Context Protocol (MCP) support, it looked simple enough. A quick demo tool that checks the weather? Nice. It made me think: āThis could be useful. Maybe Iāll try building something small of my own.ā
That āsomething smallā turned into a week-long adventure full of surprises, frustrations, and unexpected wins. I started with Claudeās weather tool, built a football match API on top of it, and then dove head-first into a CSV email validator. What I thought would be a couple of lines of code turned into a masterclass in JSON-RPC, tool registration, prompt design, and Claudeās tendency to⦠make things up.
Step 1: The Weather Tool (from MCP Docs)
My MCP journey began with the example tool provided in the official Model Context Protocol documentation. It was a weather bot ā a small service that, when asked, could return the weather in a given city using a public API.
I didnāt build it myself, but running it was my first taste of how Claude interacts with tools using JSON-RPC, how zod
is used to validate input schemas, and how responses are structured in a way Claude can understand and present back to the user.
It was clean, simple, and gave me just enough confidence to try building my own tool. That first step was important: it showed me how Claude calls a tool, what metadata it expects, and what it means when a tool āappearsā in Claudeās UI.
Of course, I didnāt yet know that getting Claude to call my tool ā and use the real data I gave it ā would become a battle of prompt phrasing, schema formatting, and taming Claudeās sometimes overly helpful hallucinations.
Step 2: Building My Own ā The Football Tool
After playing around with the weather example, I wanted to try something of my own. Iām a football fan, so I decided to build a tool that could fetch the next match for any Premier League team using the Football Data API.
This was my first custom MCP tool, and it taught me a lot:
- I used
fetch
to query real-time data from the API - I created a simple schema where Claude could pass in a team name like
"Liverpool"
or"Arsenal"
- I returned a friendly summary: next match date, teams, and venue
Most importantly, I learned how to register a tool using the method I preferred:
1
2
3
4
5
6
7
8
const server = new McpServer({
name: "football",
version: "1.0.0",
capabilities: {
tools: {},
resources: {},
},
});
Then, I added the tool with server.tool(...)
instead of defining tools inline. This gave me more control and matched the structure I wanted to follow going forward.
Claude responded beautifully. I could upload a file or just type āWhatās the next match for Tottenham?ā and the tool did its job. It was a win ā both technically and emotionally.
I felt like I understood the flow. And then I tried to build a tool that reads a CSV fileā¦
//image
Step 3: The Email Checker Tool (aka The Boss Fight)
After the football tool, I was feeling confident. So I challenged myself to build something more complex: a tool that would read a CSV file, find rows with invalid or missing email addresses, and return a report.
Sounds simple, right?
It wasnāt.
What I wanted:
- Let Claude users upload a CSV
- Check the
email
column for missing or invalid entries - Return structured output with error rows
What actually happened:
I spent days untangling edge cases and weird behaviors. Here are just a few:
1. Tool didnāt show up in Claude
I initially defined my tool like this:
1
2
3
4
5
const server = new McpServer({
name: "email-checker",
version: "1.0.0",
tools: [ ... ],
});
But Claude wouldnāt list it. After some digging, I realized Claude only recognizes tools when theyāre registered through the capabilities.tools structure or added dynamically via server.tool(ā¦). Once I switched to the football-style setup, it finally worked.
2. Console logs crashed the JSON parser
My tool used console.log()
to show debug info like āāļø Tool calledā or āš„ Received file from Claude.ā
Claude interpreted this as invalid JSON (yep ā even emoji caused JSON.parse errors).
Switching to console.error()
helped, but sometimes even that made Claude silently ignore the response.
In the end, I just removed logs entirely unless I was running locally.
3. Claude ignored my file and made one up
This was the most frustrating part. I uploaded a real emails.csv, but Claude decided to āhelpā by generating its own content instead. Every time I asked it to run the tool, it sent a fake CSV string like:
id,email
1,test@example.com
2,fake@demo.com
Meanwhile, my actual file had missing values and real formatting ā things I needed the tool to analyze.
The fix? Prompt engineering.
I had to explicitly instruct Claude to use the uploaded file as-is. Hereās the one prompt that consistently worked:
āPlease call the check-emails tool and pass the file using the file argument, with the full CSV content. Do not simulate or summarize it ā use the actual uploaded fileās content.ā
Only then did Claude finally send the correct file.content
, and the tool behaved as expected.
What I Learned
Every tool I built taught me something new ā not just about MCP or Claude, but about debugging, designing for unpredictability, and the importance of developer empathy when tools behave oddly.
Here are the key lessons I walked away with:
MCP is simple in theory, sensitive in practice
MCPās structure is straightforward: define a name, a version, a tool with an input schema and an execute method. But if you misplace a tool inside the wrong part of the server setup ā say, using tools: [ā¦] instead of capabilities.tools or server.tool() ā Claude wonāt see it. And it wonāt tell you why.
stdout ā stderr (and Claude cares a lot)
Claude listens to stdout for JSON-RPC responses ā so if your tool prints logs with console.log(ā¦), it might break JSON parsing. Emoji? Multiline logs? Youāre in trouble. Use console.error(ā¦) or remove logs entirely when testing in Claude.
Prompt engineering matters ā a lot
Claude is smart, but itās also imaginative. If youāre not specific, it might invent file content, misinterpret schema expectations, or summarize rather than relay. You need to tell it exactly what to do. I learned to write prompts like a lawyer:
āUse the uploaded file. Pass it via the file argument. Do not simulate, summarize, or replace the content.
Logging is life
When I wasnāt sure what Claude was actually sending my tool, I started logging incoming arguments. Thatās how I spotted empty strings, fake data, or missing keys. Donāt trust the prompt output alone ā verify it on your end.
The smallest detail can break everything
A misplaced emoji, a missing await, a capital letter in a tool name⦠all of these broke things silently at one point. Debugging tools that talk to AI requires the patience of a saint ā or at least a strong coffee habit.
Closing Thoughts
What started as a simple experiment with Claudeās weather tool ended up being one of the most intense debugging experiences Iāve had in a while.
I didnāt expect to wrestle with output streams, prompt phrasing, or a language modelās tendency to make things up. But I did. And while it was frustrating at times (okay, a lot of the time), it was also deeply rewarding.
I learned how Claude sees the world ā not just how to call a tool, but how it interprets intent, how it guesses context, and how it sometimes needs to be guided very, very precisely. I also learned that building tools for AI is less about āwriting codeā and more about ācreating contracts between code and language.ā
If youāre planning to build your own MCP tools:
š start small,
š test everything locally first,
š and assume nothing ā especially when Claude tells you āDone!ā š
Would I go through this again?
Yes ā because now I can build tools that really work, and I know how to make Claude behave when it doesnāt.
ā¤ļø