🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Giving DevAssist Real, Mutable Task State

Implement create_task, list_tasks, and complete_task against real shared in-memory state, and understand why in-place mutation matters here.

Total XP: 0|💻 mcpmasterclass XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

Stateful Tools

Tools that actually change something.

Quick Quiz //

Why does setting `t["done"] = True` inside the loop correctly update the task stored in TASKS?


🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Read-only tools are half a server. A real tool server also lets an assistant change something and have that change persist.

1Tools Can — and Often Should — Have Side Effects

Not every useful tool is read-only. create_task and complete_task change TASKS, and that change is visible to every subsequent call, exactly like a real database would behave. This is what makes an MCP server useful for more than lookups — an assistant can actually get things done through it, not just retrieve information.

2Why In-Place Mutation Is Correct Here

TASKS holds the actual dict objects, not copies. When complete_task_handler finds the matching task and sets t["done"] = True, it's modifying the exact object already sitting inside TASKS — there's nothing else to update. Building a new dict and returning it, without also replacing the one inside TASKS, would silently leave the stored task unchanged.

3Step-by-Step Breakdown

A Tool That Actually Changes State. read_file and list_files only ever read. create_task and complete_task are different — they mutate real, shared state that later calls can observe. This is where a tool server starts to feel like a real backend, not just a read-only lookup.

Complete a Real Task. create_task_handler and list_tasks_handler are done. complete_task_handler finds the right task by id but never actually marks it done — finish it so that task becomes done in place.

Why does complete_task_handler mutate the task dict in place (t["done"] = True) rather than building and returning a brand-new dict?

  • Because that same dict object is the one already stored inside TASKS — mutating it in place means every future call to list_tasks_handler sees the update, since there's only one shared list of tasks.
  • Because building a new dict is syntactically illegal inside a for loop in Python.

Module 2 Complete: A Real Server Core. DevAssist can now really read files, list them, create tasks, and complete them — all through a working dispatch table. It's still only reachable from Python code calling it directly, though. Next module: connecting a real LLM that decides on its own which of these tools to call.

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Confirm State-Changing Tool Calls in Their Result

A create_task or complete_task result should echo back the task's new state (like its id and done status) so any client rendering it can immediately confirm to the user what actually changed.

return {"id": 1, "done": True}

SEO Implications

  • 1

    Target 'MCP tool with side effects' and 'stateful MCP server example' separately

    Developers moving past read-only tools search for the state-mutation pattern specifically, distinct from basic tool setup.

Best Practices

Return the Updated Object From Any State-Changing Tool

Returning the task after completing it lets the calling model (and any UI) confirm the change succeeded without a separate follow-up list_tasks call.

Frequent Bugs

THE BUG

Reassigning a local variable instead of mutating the stored dict, e.g. `t = {**t, "done": True}`.

THE FIX

That creates a brand-new dict bound only to the local variable `t` — the original object still sitting inside TASKS is untouched, so the change is silently lost.

Real-World Examples

Real Task-Tracker Integrations

A production MCP server backing a real task tracker performs the equivalent of this in-place update as a database UPDATE statement — the in-memory version here is the same mutation pattern at smaller scale.

UPDATE tasks SET done = true WHERE id = 1

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Full-Stack Software and AI Engineer

Full-Stack Software and AI Engineer with 6 years of experience building enterprise-grade web applications across React, Angular, Node.js, and Python. Recently completed a Master's in AI Development specializing in LLMs, RAG, and AI agent architectures, and currently builds enterprise systems that integrate AI and Digital Twins to optimize industrial and logistics processes.

LinkedIn ↗
Common Pitfalls & Errors

The Error //

Hardcoding sensitive credentials

// Wrong const API_KEY = 'sk-123456789'; // Correct const API_KEY = process.env.API_KEY;

The Solution //

Never hardcode API keys, passwords, or secrets in your source code. Use environment variables (.env files) to keep them secure and out of version control.

Lesson Glossary

[01]In-Place Mutation

Modifying an existing object's contents directly, so every reference to that object sees the change.

Code Preview
t["done"] = True

[02]Stateful Tool

A tool whose call has a side effect that persists and affects the result of later calls.

Code Preview
create_task, complete_task

Continue Learning