- Published on
Todos-MCP: Lessons from Creating a Todo App as a Minimal MCP Server
Table of Contents
I wanted to wrap my head around what writing and publishing an MCP server looks like today.
What better way than the age old tradition of making a "todo list" app?
If you're also interested in building MCP servers, read on.
If you just want to get started using MCP servers, we have the fastest guide on the internet.
Demo video
If you want to try it out you can: Todos MCP server
Development overview
Frameworks
I used the fastmcp
repo as a foundation. Next time instead I would just install fastmcp
or another equivalent as a dependency (rather than cloning and modifying the fastmcp
repo as they suggest).
There are a handful - let me know if there's more I should add to the list:
Tools
Inspector
The Model Context Protocol team have an Inspector you can use:

It allows you to interact with your MCP server, and monitor the requests and responses. It handles Tools, Resources, Sampling and Roots.
mcp-cli
mcp-cli
is another tool for interacting with your MCP server during development.
It doesn't give you the same depth of debugging info as the MCP Inspector, but it can be quicker to use from the terminal in your IDE.

Deploying
There's a lot of different workflows for deploying to a package repo (in this case NPM).
I leveraged the GitHub actions from the fastmcp
repo to autodeploy to NPM and JSR for every new release.
There are a few caveats to be aware of:
- You have to have the package created on NPM before you can deploy to it. I did this initially with
$ npm publish
from the CLI. You can read more here. - The
fastmcp
repo uses the@semantic-release
package as part of the deploy pipeline. This will automatically parse your commit logs (since the previous commit) to work out if a new release should be created. This is great once you have a project up and running, but can be a blocker when you're trying to initially publish a package and forgot to include a key (likeNPM_TOKEN
) in your GitHub actions environment.
Issues I ran in to
Configuration
MCP servers need to be runnable via a CLI command, and easy to distribute. For todos-mcp I decided to just support npx
usage.
Because it has to run as an executable without the user having having access to the runtime, using environment variables for configratuion doesn't really make sense. How would they set the variables? So instead we've gotta use command-line arguments. I'm using yargs
for todos-mcp.
Some benefits:
- Immediate usage without environment setup (
npx todos-mcp --baseDir ~/.claude/todos
) - Built-in help documentation (
--help
) - Easily set defaults
- Type validation and coercion out of the box
- Nested command support for future extensibility
Spec support
The MCP spec defines both Tools and Resources, but I found there's a gap in how they're used in practice. While implementing todos-mcp, I discovered that Claude only effectively engages with Tools, not Resources.
For example, when implementing a todo list endpoint as a Resource:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resources": [
{
"uri": "file:///todos/all.yaml",
"name": "all.yaml",
"description": "List of all todos",
"mimeType": "text/plain"
}
]
}
}
Claude would fail to recognise and engage with the resource.
What are Resources for then? From the spec: "Tools enable models to interact with external systems, such as querying databases, calling APIs, or performing computations", and "Resources allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information". It seems like Claude's MCP Client still needs some work to be using the protocol satisfactorily.
Debugging & Logging
Debugging MCP servers presents unique challenges:
- Standard console logging (
console.log()
) isn't useful since STDOUT is captured by the LLM client, not visible to the developer - Server state can be hard to inspect during LLM interactions
We use pino
for logging to a file, with a convenience script to monitor logs in real-time. This provides:
- Structured JSON logging
- Log level filtering
- Timestamp and request correlation
- Pretty printing for development
Our solution in todos-mcp:
{
"scripts": {
"logs": "tail -f ~/.claude/filesystem/todos/logs/app.log|npx pino-pretty"
}
}
Storage
Persistent storage for MCP servers is also another curiousity. The server needs a reliable place to store data, but it's being run as an executable via npx
, so how should we define where the storage goes?
For todos-mcp, we store data in ~/.claude/filesystem/todos/
by default (configurable via --baseDir
), using YAML for todo lists.
Documentation
I'm also interested in how you document an MCP server. In some way they should be self-documenting. They advertise their capabilities as a response to requests from the Client. Documenting all that for a human reader is very verbose:
{
"method": "tools/list",
"params": {}
}
{
"tools": [
{
"name": "Get-Todo",
"description": "Get a specific todo by ID",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"exclusiveMinimum": 0
}
},
"required": [
"id"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
},
...
]
}