Module 04 Exercises: Tools and MCP

These five exercises progress from design thinking to hands-on implementation to interview simulation. Complete them in order — each builds on the previous.


Exercise 1: Design Tool Schemas for a Todo App Agent

Type: Design / Schema writing
Time: 30–45 minutes
Prereq: Read Section 1 and 2 of ../README.md

Task

Design the complete tool layer for an AI assistant that can manage a todo list. The agent should be able to: create todos, list todos with filters, update a todo’s status and text, delete todos, and organize todos into projects.

Deliverables

Write the full JSON schema for each tool. For each tool, include:

  • A well-designed name (verb_noun)
  • A complete description (what, when, returns, edge cases)
  • A typed input_schema with proper use of enum, optional params, and descriptions
  • Which parameters are required

Starter

Here is a skeleton to fill in. Aim for 5–7 tools.

[
  {
    "name": "create_todo",
    "description": "...",
    "input_schema": {
      "type": "object",
      "properties": {
        "title": { "type": "string", "description": "..." },
        "priority": { ... },
        "due_date": { ... },
        "project_id": { ... }
      },
      "required": [...]
    }
  },
  {
    "name": "list_todos",
    ...
  },
  ...
]

Questions to Answer

After writing the schemas, answer these:

  1. Did you create separate tools for create_todo and update_todo, or a single “upsert” tool? Why?
  2. Which of your tools are idempotent? Which are not?
  3. If the agent calls delete_todo by mistake, how would you make that recoverable? (Hint: think about what the delete tool returns, or whether there’s a soft-delete pattern.)
  4. What would a list_todos tool return when the filter matches no items — an error or an empty list? Why does that choice matter?

Stretch Goal

Write a second version of your tool set where each tool returns a typed JSON object (not a plain string). Document the return schema in the tool description.


Exercise 2: Build an MCP Server Wrapping JSONPlaceholder

Type: Implementation
Time: 60–90 minutes
Prereq: Read Section 4 of ../README.md; have mcp installed

Task

Build a working MCP server that wraps the JSONPlaceholder API — a free, public fake REST API for testing.

Requirements

Your server must expose these tools:

ToolJSONPlaceholder endpoint
list_posts(user_id?)GET /posts?userId={id}
get_post(post_id)GET /posts/{id}
list_todos(user_id?, completed?)GET /todos?userId={id}&completed={bool}
get_user(user_id)GET /users/{id}

And these resources:

ResourceContent
jsonplaceholder://usersAll users as JSON
jsonplaceholder://posts/recentFirst 10 posts

Hints

  • Use urllib.request or httpx (if you add it as a dep) to make HTTP calls
  • JSONPlaceholder returns JSON — return it as a formatted string with json.dumps(..., indent=2)
  • Handle HTTP errors (404, 500) gracefully — return an error message, don’t let the exception propagate
  • Remember to make your tool descriptions answer: what it does, when to use it, what it returns

Verification

Register the server in ~/.claude/settings.json and ask Claude Code:

Can you find the most recent posts by user 1 and tell me their titles?

Claude should call list_posts with user_id=1 and return the titles from the response.

Stretch Goal

Add a create_post(user_id, title, body) tool. JSONPlaceholder accepts POST requests (though it doesn’t persist them — it returns a mock created ID). Practice handling the creation response and returning it in a structured way.


Exercise 3: Add a File Resource to the Minimal MCP Server

Type: Implementation (extend existing code)
Time: 20–30 minutes
Prereq: Complete the minimal server setup from ../examples/mcp_server_minimal/

Task

Extend ../examples/mcp_server_minimal/server.py to add a resource that reads the contents of an actual file from disk.

Requirements

Add a resource with URI file://notes that:

  1. Reads content from a file at a configurable path (use a constant NOTES_FILE_PATH at the top of the file)
  2. Returns the file contents as a string
  3. Handles the case where the file doesn’t exist — return a helpful message, not an exception
  4. Shows in list_resources() with an accurate description

Steps

  1. Add NOTES_FILE_PATH constant pointing to a local text file (create the file too)
  2. In list_resources(), add the new resource to the returned list
  3. In read_resource(), add a branch for "file://notes" that reads the file
  4. Test: ask Claude Code to read the file://notes resource

What You’ll Learn

  • How to make resources dynamic (contents change when the file changes)
  • How to handle file I/O errors gracefully in an MCP server
  • The difference between a static hardcoded resource and a live resource

Stretch Goal

Make the resource URI parameterized: file:///path/to/any/file. Update list_resources() to list files in a configured directory, and read_resource() to handle any file:// URI. (Be careful about path traversal security — constrain to a specific root directory.)


Exercise 4: Connect the Minimal Server to Claude Code and Verify

Type: Integration / DevOps
Time: 20–30 minutes
Prereq: Have Claude Code installed; minimal server code working

Task

Go through the full integration flow end-to-end:

  1. Register the minimal server in ~/.claude/settings.json
  2. Verify the connection with /doctor
  3. Confirm the tools appear with their MCP prefixed names
  4. Run 3 test queries that exercise each tool and the resource

Step-by-Step

Step 1: Add to ~/.claude/settings.json:

{
  "mcpServers": {
    "minimal": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"]
    }
  }
}

Step 2: Open a new Claude Code session. Run:

/doctor

Look for minimal server. Note whether it shows “connected” or an error.

Step 3: Ask Claude:

List all the MCP tools you have access to right now.

Verify you see mcp__minimal__add_numbers and mcp__minimal__get_current_time.

Step 4: Run these test queries:

Add the numbers 17 and 25 using the add_numbers tool.
What's the current time in London?
Read the memo://notes resource and summarize it.

Step 5: Document any issues you hit and how you resolved them.

Troubleshooting Checklist

  • Path to server.py is absolute
  • python in PATH (check with which python)
  • mcp package installed in Python environment Claude Code uses
  • No syntax errors in server.py (test: python server.py should hang, not crash)
  • settings.json is valid JSON (test with python -m json.tool ~/.claude/settings.json)

Exercise 5: Interview Simulation — Design the GitHub Workflow Assistant

Type: System design / Interview prep
Time: 45–60 minutes (recommended: time-box to 45 min)
Prereq: Read all of ../README.md

The Prompt

“Design the tool layer for an AI assistant that can manage a developer’s GitHub workflow. The assistant should be able to handle day-to-day GitHub tasks: reviewing PRs, managing issues, checking CI status, and helping with releases. Walk me through your tool design decisions.”

This is a real interview question at companies building AI developer tools.


What a Strong Answer Covers

1. Requirements clarification (2–3 minutes)

Before designing tools, clarify scope:

  • Is this read-only (review, report) or read-write (create, merge)?
  • Single repo or multi-repo?
  • Personal workflow or team workflow?
  • What level of GitHub access does the agent need?

2. Tool inventory (10–15 minutes)

Design tools for these capability areas:

AreaOperations
Pull Requestslist, get details, review, approve, merge, get diff
Issueslist, create, comment, close, label, assign
CI/CDget workflow runs, get job logs, trigger workflow
Repositorylist branches, get file contents, search code
Releaseslist releases, create release, get changelog

For each tool:

  • Name (verb_noun)
  • Key parameters and their types
  • What it returns
  • Whether it’s idempotent

3. Design principles you applied (5 minutes)

Explain your decisions:

  • Why you separated list_pull_requests from get_pull_request (minimal surface area)
  • Why you used enum for state parameter (open, closed, merged) vs free string
  • How you handle the PR diff tool — does it return raw diff or structured hunks?
  • What you did about dangerous operations (merge_pull_request, delete_branch)

4. MCP vs raw API tool use (3 minutes)

Would you build this as an MCP server or embed tools in the application?

  • If it’s a reusable GitHub integration for any Claude project → MCP server
  • If it’s specific to one internal tool → raw API tool use
  • What’s the right transport? (stdio for local dev tools, SSE for shared team server)

5. Security considerations (3 minutes)

  • GitHub token scope: request only the permissions needed (no admin scope if you only need repo)
  • Read operations vs write operations: separate tools, not a mode parameter
  • Audit logging: every write operation should log what was changed
  • Rate limiting: GitHub API has limits — how does your tool handle 429 responses?

Deliverable

Write up your answer covering all five areas. Include:

  • A complete list of tool names with brief descriptions (one sentence each)
  • 3 full JSON tool schemas (your choice of which ones — pick the most interesting)
  • A paragraph on whether you’d build this as MCP or raw tool use, and why
  • One thing you would do differently if this were a team tool vs a personal tool

Evaluation Rubric

DimensionWhat to Look For
Namingsnake_case, verb_noun, specific
Minimal surface areaSeparate create/update/delete; no god tools
Schema qualityEnum for constrained strings, clear descriptions, proper required
Return value designStructured data, error handling, not raw strings
Security thinkingMentions token scopes, dangerous op handling
MCP knowledgeCan explain when MCP adds value vs complexity
Trade-offsAcknowledges design choices and their consequences