PTENES
MODULE 4.1

📋 Task Manager

Build a complete MCP Server that manages tasks with JSON persistence—create, list, update, and remove.

6
Topics
40
Minutes
Intermediate
Level
Practical
Type
1

📋 Planning the Project

Before writing any code, it's essential to plan the structure of our Task Manager. Let's define which tools the MCP server will expose, what the data structure for each task will be, and how we will organize the project. Good planning now avoids rework later and ensures that the server is consistent and easy to maintain.

💎 Core Concept

Our MCP Server will expose 4 tools for the model: criar_tarefa (adds a new task), listar_tarefas (shows existing tasks filtered by status), atualizar_tarefa (modifies the title, description, or status) and remove_task (deletes a task by ID). Each task will have the fields: id (unique UUID), title, description, status ("pending" or "done") and creation_date. All tasks will be persisted in a local JSON file.

💡 Practical Tip

Always start by defining the tools and their parameters before coding. Think from the user’s perspective: "What would I ask Claude to do with my tasks?" Each natural-language request becomes a tool. "Create a task" → criar_tarefa. "Show me the pending ones" → listar_tarefas(status="pendente"). This top-down approach ensures the server is intuitive.

📋 Project Structure
File 1: server.py

The main MCP server — imports FastMCP, defines the 4 tools, and starts the server. All the logic lives here.

File 2: tarefas.json

Persistence file — created automatically by the server. Contains the task list in JSON format.

Configuration: Claude Desktop

Register the server in claude_desktop_config.json so Claude can use the tools automatically.

2

📄 JSON Data Structure

To persist tasks between sessions, we'll use a file tarefas.json. It’s simple, readable, requires no external dependencies, and works perfectly for local projects. We’ll create helper functions to read and save data in this file using the module pathlib to manage paths safely and across platforms.

💎 Core Concept

The JSON structure will be a list of dictionaries, where each dictionary represents a task. Example: [{"id": "abc-123", "titulo": "Estudar MCP", "descricao": "Completar trilha 4", "status": "pendente", "data_criacao": "2025-01-15T10:30:00"}]. Two helper functions centralize access to the file: load_tasks() reads the JSON and returns the list (or an empty list if the file doesn't exist) and save_tasks(tasks) writes the updated list to the file. Use pathlib.Path ensures paths work on any operating system.

❌ Avoid
  • •Use a complex database for this simple case
  • •Hardcode paths with OS-specific slashes
  • •Read/save JSON in each tool individually
  • •Ignore the case where the file doesn't exist yet
✅ Do
  • •JSON for lightweight local persistence — perfect here
  • •Use pathlib for cross-platform paths
  • •Centralize reading/writing in helper functions
  • •Return an empty list if the file doesn’t exist
3

➕ Tool: Create Task

The first tool we’ll implement is the criar_tarefa. It takes a required title and an optional description, and generates a unique ID using the uuid, sets the initial status to "pending", records the creation date, and saves everything to the JSON file. It’s the most fundamental operation in our task manager.

💎 Core Concept

The tool is decorated with @mcp.tool() and defined as def criar_tarefa(titulo: str, descricao: str = ""). Internally, it: 1) loads the existing tasks from the JSON, 2) creates a new dictionary with uuid.uuid4() for the ID, "pending" status, and datetime.now().isoformat() for the date, 3) adds it to the list and saves, 4) returns a confirmation message with the generated ID. Claude receives this confirmation and can naturally inform the user.

💡 Practical Tip

Use type hints in the tool parameters — MCP uses these annotations to automatically generate the documentation the model receives. titulo: str tells Claude that it needs to provide text. descricao: str = "" indicates that it's optional. The clearer the types, the better the model understands how to use the tool.

🚨 Alert

Always use str(uuid.uuid4()) to convert the UUID to a string before saving it to JSON. Python's UUID type is not serializable by default by the module json — if you try to save a UUID object directly, you'll get a TypeError. Converting it to a string solves the problem and preserves the identifier's uniqueness.

4

📋 Tool: List Tasks

The tool listar_tarefas allows Claude to view existing tasks, optionally filtering by status. It returns a formatted list that the model can present in a user-friendly way, including the total number of results. It’s the most commonly used tool—nearly every interaction with the task manager probably starts or ends with a listing.

💎 Core Concept

Set to def listar_tarefas(status: str = "todas"), the tool loads all the tasks from the JSON and, if the parameter status other than "all", filters for only those that match ("pending" or "done"). The return value should be a formatted string, with each task showing: title, status, description (if any), and creation date. Always include the total count at the beginning: "Found X tasks". If there are no tasks, return a clear message like "No tasks found."

❌ Avoid
  • •Return raw JSON to the model
  • •Don't handle the empty list case
  • •Ignore invalid filters (e.g., status="xyz")
  • •Return data without a result count
✅ Do
  • •Format output in a readable and organized way
  • •Include a count: "Found X tasks"
  • •Clear message when there are no results
  • •Include the ID so the user can reference it
📊 Data and Research

Tools for query tools like listar_tarefas are typically the most frequently called in any MCP Server — they account for about 60-70% of interactions. So, invest time in formatting the return value well. The model uses this text to compose its response to the user. The clearer and more structured the tool's return value, the better the final response Claude generates for the user.

5

✏️ Tools: Update and Remove

The tools for atualizar_tarefa e remove_task complete the CRUD operations for our task manager. Both take the task ID as a required parameter and must validate that the ID actually exists before performing any operation. Updating lets you change the title, description, and/or status. Deleting permanently removes the task.

💎 Core Concept

The tool atualizar_tarefa(id: str, titulo: str = None, descricao: str = None, status: str = None) looks up the task by ID, updates only the fields provided (those that are not None), and saves. The tool remover_tarefa(id: str) finds the task by ID and removes it from the list. Both should validate that the ID exists — if it doesn't exist, return a clear error message such as "Task with ID 'xyz' not found." This prevents the model from getting confused while trying to operate on nonexistent tasks.

💡 Practical Tip

Use parameters optional with None as the default in the update tool. This way, Claude can update only the necessary field without having to resend all the data. If the user says "mark it as done," the model calls atualizar_tarefa(id="abc", status="feita") — the title and description remain unchanged because they are None.

🚨 Alert

Always validate the ID! If the model tries to update or remove a task with an ID that doesn’t exist, never return nothing or an unhandled exception. Return a clear, descriptive error message. Without this validation, Claude may think the operation succeeded and give the user incorrect information, resulting in a confusing experience and inconsistent data.

6

🧪 Testing the Project

With all 4 tools implemented, it's time to test the Task Manager end to end. We'll configure the server in Claude Desktop and go through a complete scenario: create tasks, list them, update their status, and remove them. This test validates not just the code, but also the natural-language user experience.

💎 Core Concept

Configure the server in claude_desktop_config.json adding the entry with the command to run the server.py. Restart Claude Desktop and check that the 4 tools appear in the tools icon. From then on, all interactions are in natural language — Claude decides on its own which tool to call based on what the user asks.

📋 Complete Test Scenario
Test 1: Create

"Create a task to study MCP with the description: Complete course track 4". Claude should call criar_tarefa and confirm with the generated ID.

Test 2: List

"List all my tasks" and then "Show only the pending ones." Claude should call listar_tarefas with and without a status filter.

Test 3: Update

"Mark the task to study MCP as done." Claude should use the task ID and call atualizar_tarefa with status="feita".

Test 4: Remove

"Remove the task I just completed." Claude should call remover_tarefa with the correct ID and confirm the deletion.

💡 Practical Tip

If something doesn’t work, check the terminal where the server is running — Python errors will appear there. Common issues: incorrect path in the config, missing import, or JSON serialization error. Use print() for temporary debugging and verify that the file tarefas.json is being created and updated correctly in the project directory.

📝 Module Summary

  • ✓ We planned the project by defining 4 tools (create, list, update, remove) and the data structure for each task.
  • ✓ We implemented persistence with a JSON file and centralized helper functions for reading and writing.
  • ✓ We created the criar_tarefa tool with UUID generation, a default "pendente" status, and an automatically generated creation date.
  • ✓ We implemented listar_tarefas with a status filter and readable formatting of the results.
  • ✓ We completed the CRUD operations with atualizar_tarefa and remover_tarefa, including required ID validation.
  • ✓ We tested the complete project via Claude Desktop with natural language scenarios.

Next Module: 4.2 — Database Consultant