cd /news/developer-tools/my-journey-building-a-quote-of-the-d… · home topics developer-tools article
[ARTICLE · art-122564] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

My Journey Building a Quote-of-the-Day MCP Server

Tala Saabneh, a developer at NextFlows AI Academy, built a Quote-of-the-Day MCP server with her team, implementing six tools for managing a quote dataset. She focused on the get_daily_quote and create_quote tools, integrating an external API with SSRF protection and a timeout fallback. The project demonstrates practical MCP development using Node.js and TypeScript.

read10 min views3 publishedSep 7, 2026

By Tala Saabneh

During my journey at NextFlows AI Academy, I had the opportunity to work on a practical MCP project with my team: a Quote-of-the-Day MCP Server.

The project was a great opportunity to move from learning the Model Context Protocol (MCP) concepts theoretically to actually building, testing, debugging, and securing a working MCP server.

Our server exposes six tools for working with a quote dataset. Three of them are read tools for finding and viewing quotes, while the other three are write tools for managing the dataset.

My main responsibility in the project was working on the get_daily_quote and create_quote tools.

This blog describes our journey from understanding the requirements to building and testing the final version, including the challenges we faced and the lessons I learned during the process.

The first step was understanding what the project needed to provide.

The main idea was to build an MCP server that could allow an AI client to interact with a quote dataset through well-defined tools.

After breaking down the requirements, our team decided to implement six tools:

get_daily_quote — Gets an inspirational quote from the local dataset or the external API, with a local fallback.search_quotes — Searches for quotes using a keyword, topic, or author.list_categories — Lists the available quote categories and tags.create_quote — Adds a new quote to the local dataset.update_quote — Updates an existing quote using its ID.delete_quote — Deletes a quote from the dataset. Dividing the work between the team members helped us work on different parts of the project while keeping the final server consistent. In the code, each tool is registered with a comment marking its owner:

My main responsibility was implementing get_daily_quote and create_quote.

We started by preparing the development environment and setting up the MCP server using Node.js and TypeScript.

We also worked with the MCP SDK to understand how tools are registered and how requests and responses are handled.

At this stage, I was still getting familiar with how all the pieces fit together. I had worked with programming and APIs before, but MCP introduced a different way of thinking about how functionality is exposed to AI applications.

Instead of building a standalone application where the user directly interacts with every function, we were building tools that an MCP client could discover and call.

That made the structure of the tools and their inputs especially important.

The first tool I worked on was get_daily_quote.

The purpose of this tool was to return an inspirational quote for the user.

The tool was designed to support more than one source for the quote. It could use the local quote dataset or an external API, while still having a local fallback if the external source was not available.

This made the tool more reliable because it was not completely dependent on the external API.

The general flow was:

Working on this tool helped me understand how an MCP tool can combine different sources of data while still providing a simple interface to the client.

One of the main challenges I faced while working on get_daily_quote was the external API integration.

Connecting to an API sounds simple at first, but there are several things that need to be considered.

We needed to make sure that the server was not making requests to arbitrary hosts and that the API configuration was handled safely. In the code, this is enforced by only allowing a single, hardcoded host (api.api-ninjas.com) and rejecting the request outright if the resolved URL's hostname doesn't match — a basic SSRF protection.

I worked on the API integration while keeping security in mind, including restricting the allowed host and handling the situation where the API was unavailable. External calls are also bounded by a 10-second timeout (via AbortController), so a slow or hanging API can never freeze the tool.

Another important part was the fallback behavior.

Instead of allowing the tool to fail completely when the external API could not be reached, the server could use the local dataset instead.

This taught me that a good implementation should not only work when everything goes perfectly. It should also have a reasonable behavior when something goes wrong.

The second tool I was responsible for was create_quote.

Its purpose was to allow a new quote to be added to the local dataset.

The tool accepts information such as:

One important requirement was that the tool should not accept a path or filename from the user.

Instead, it can only write to the server's own trusted data file.

This was an important security decision because allowing users to provide arbitrary file paths could create unnecessary risks. In the schema itself, there is no file or path field at all — the tool only accepts quote, author, and category, so there is simply nothing a malicious prompt could redirect.

The general flow of the tool was:

Implementing this tool gave me more experience with reading and modifying JSON data and also made me think more carefully about how user input should be handled.

While finishing this tool, our shared write layer (quotes-write.ts, used by all three write tools) ended up with a few extra safety nets worth mentioning: quotes.json half-written. Security became an important part of the project, especially when dealing with files.

While working on the tools, we discussed the risks of allowing user-controlled file paths. A user should not be able to provide an arbitrary path and make the server read or write files outside the intended data directory.

For read operations that involve file input, we used a safer approach where the path is resolved and checked against the trusted data directory. `get_daily_quote` optionally accepts a `file` argument, but before reading it, the server resolves the real path (following symlinks) and verifies it is still inside the trusted `data/` directory — the read is refused otherwise.

For `create_quote`, `update_quote`, and `delete_quote`, we took an even safer approach by not accepting a path or filename at all. These tools write only to the server's own quote data file, resolved once on the server. There is no user-controlled path to sanitize because there is no path input in the first place.

delete_quote — being the only destructive, irreversible action — also requires the caller to pass confirm: true as a literal boolean. This is a deliberate defense against prompt injection: even a manipulated instruction telling the model to "delete quote X" still has to satisfy an explicit, typed confirmation flag before anything is touched.

This helped me understand an important software engineering principle:

A feature should not only work correctly; it should also be designed so that it cannot be easily misused.

Since the project included six tools, we divided the responsibilities between team members.

The tools were organized into two main groups:

I worked mainly on get_daily_quote and create_quote, while the other team members worked on the remaining tools.

This required us to keep the interfaces and behavior of the tools consistent — for example, all three write tools share the same underlying quotes-write.ts module for , validating, and atomically saving the dataset, instead of each tool reimplementing its own file-handling logic.

Working this way also showed me that team projects are not only about completing your own part. Changes in one tool can affect the overall structure of the server, so communication and coordination are important.

After implementing the tools, we used MCP Inspector to test the server.

MCP Inspector was especially useful because it allowed us to test the tools individually and see the requests and responses directly.

For my tools, I tested:

For `get_daily_quote`, I checked that the server returned the expected quote and handled the available data sources correctly.

For `create_quote`, I tested adding new quotes and making sure that the data was updated correctly.

Testing this way helped us find issues earlier instead of waiting until the entire server was connected to an MCP client.

After testing the individual tools, we worked on connecting the MCP server to an MCP client.

This was an important step because it allowed us to see how the tools behaved in a real client environment rather than only through the Inspector.

We verified that the server could expose its tools correctly and that the client could call them.

This part also helped me understand that testing an application in one environment does not always guarantee that everything will behave exactly the same way in another environment.

It is important to test the complete flow.

The project was not completed without problems.

Some of the main challenges included:

Making the external API work correctly while also restricting access to trusted hosts required careful handling.

Working with local files made us pay attention to path traversal and the risks of allowing user-controlled paths.

Since multiple people were working on different tools, we needed to keep the tools consistent with each other.

Some issues only became clear when we moved from individual testing to testing the server through an MCP client.

These challenges were useful because they forced us to understand why something was happening instead of simply trying random fixes.

The project started with a simple goal: create an MCP server that could work with quotes.

As we progressed, the project became much more complete.

We moved from basic tool implementations to a server with:

The final result was much more than the initial basic implementation.

Each iteration helped improve both the functionality and the reliability of the server.

This project taught me many practical lessons.

I gained a much better understanding of how MCP servers expose tools and how AI clients can interact with them.

I learned that API integration is not only about sending a request and receiving a response. It also involves configuration, validation, security, and fallback behavior.

I learned why file paths and user input need to be handled carefully and how seemingly simple file operations can introduce security risks — and why the safest input is sometimes the one you don't accept at all.

Using MCP Inspector showed me how useful it is to test individual tools before testing the whole application. I learned that debugging requires understanding the entire flow of an application, including the environment in which it runs.

Working on different tools as part of one server taught me the importance of communication, consistent interfaces, and making sure individual contributions work together as one project.

My main contribution to the project was implementing and working on:

I worked on the quote retrieval logic, external API integration, fallback behavior, and related security considerations.

I worked on adding new quotes to the local dataset, validating the input, and making sure the tool only writes to the trusted server data file — with the extra safety of atomic writes and automatic backups underneath.

Through these two tools, I gained practical experience with MCP tool development, APIs, local data management, input validation, and security.

Looking back at the project, one of the most valuable parts of the experience was seeing how a simple idea could gradually become a complete working system.

I started by learning the requirements and understanding the basics of MCP. Then I moved into implementation, testing, debugging, API integration, and security.

The project also changed the way I think about software development.

Before this experience, it was easy to focus mainly on whether a feature worked. During this project, I learned to also ask:

These questions became an important part of my development process.

Overall, building this MCP server was a valuable learning experience that gave me practical exposure to MCP, TypeScript, APIs, file handling, security, testing, and teamwork.

It also gave me more confidence in working on real software projects where understanding the problem and handling unexpected situations are just as important as writing the code.

Tala Saabneh is a Computer Engineering student interested in software engineering, AI, and emerging technologies. Through the NextFlows AI Academy program, she has been developing practical experience with MCP, TypeScript, APIs, security, and AI-integrated applications.

This project was developed as part of the NextFlows AI Academy program.

── more in #developer-tools 4 stories · sorted by recency
── more on @tala saabneh 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/my-journey-building-…] indexed:0 read:10min 2026-09-07 ·