Skip to main content

Build the Content Tools

James opened the tutoring transcript from Lesson 1 and traced the tool calls again. "register_learner, get_learner_state, update_progress. Those are working. But then it says get_chapter_content and get_exercises." He scrolled through his project directory. "Where does the content come from?"

"From disk," Emma said. "Markdown files for chapters, JSON for exercises. No cloud storage, no CDN, no database. Files you can open in a text editor."

"That feels too simple."

Emma pulled up her phone and showed him a screenshot of her first product. "My first tutoring prototype had a CDN with a caching layer, a content management system, and a versioning pipeline. I built all of that before I had a single paying customer." She put the phone away. "Start with files on disk. Add infrastructure when you have users who need it."


You are doing exactly what James is doing. You have working state tools from Lesson 3. Now you create the content your tutor will teach from, and the tools that deliver it.

This lesson has three parts. First, you create sample content files: markdown for chapters and JSON for exercises. Second, you describe get_chapter_content to Claude Code and verify it works. Third, you describe get_exercises and verify that too. Both tools enforce tier gating: free learners can access chapters 1 through 5 only.

Step 1: Create the Content Directory

Your MCP server needs content to serve. The structure is straightforward:

content/
chapters/
01-variables.md
02-loops.md
03-functions.md
04-data-structures.md
05-files.md
exercises/
01-exercises.json
02-exercises.json

Chapters are markdown files. Exercises are JSON files. The numbering prefix (01, 02, etc.) is how the tools find the right file for a given chapter number.

You have two options for creating this content:

Option A: Write it yourself. Create the directories and write sample content. The chapters do not need to be real course material. A chapter file might be 20 lines of markdown explaining a topic. An exercise file might have three exercises with a question, a hint, and a topic tag.

Option B: Ask Claude Code to generate it. Send this message:

Create a content directory in my project with sample course content.
I need:

- content/chapters/ with 5 markdown files (01-variables.md through
05-files.md). Each file should have a title, a short explanation of
the topic, and 2-3 code examples with output.

- content/exercises/ with 2 JSON files (01-exercises.json and
02-exercises.json). Each file should have an array of exercise objects
with fields: id, question, hint, topic, and difficulty (easy/medium/hard).

Keep the content simple. This is sample data for testing the tools.

Either option works. What matters is the structure, not the content quality. Your tools need files to read; the files need the right names and the right format.

Verify the Files Exist

After creating the content (yourself or with Claude Code), confirm the structure:

ls content/chapters/
ls content/exercises/

Output:

You should see five markdown files in chapters/ and two JSON files in exercises/. If any are missing, create them before continuing. The tools you build next depend on these files being in place.

Step 2: Describe get_chapter_content

Now describe the first content tool. Send this to Claude Code:

Add a get_chapter_content tool to the tutorclaw MCP server. It takes a
chapter number and an optional section name. It reads from
content/chapters/NN-*.md and returns the chapter content.

Include tier gating: if the learner's tier is "free" and the chapter
number is greater than 5, do not return the content. Instead, return an
error message telling the learner this chapter requires a paid plan and
explaining how to upgrade.

The tool needs the learner's ID to check their tier from the JSON state.

Spec this before building.

Notice what this message includes:

  • What the tool does (fetches chapter content from local files)
  • How it finds the file (chapter number maps to filename prefix)
  • What tier gating means (free tier stops at chapter 5)
  • What the error should say (upgrade message, not a generic error)

Review the Spec

Claude Code responds with a spec before writing code. Check these elements:

ElementWhat to Look For
Tool nameget_chapter_content (clear, matches the other tool names)
Tool descriptionSpecific enough that an agent knows to call this for chapter text (not exercises)
Input parameterschapter_number (required), section (optional), learner_id (required for tier check)
Tier gatingLogic that checks the learner's tier before returning content
Error responseAn upgrade message, not a bare "access denied"

The tool description is still the most important element, just like in Lesson 3. Compare it to the descriptions on your state tools. The agent needs to know: "Call get_chapter_content when the user wants to read or study chapter material. Call get_exercises when the user wants practice problems."

If the descriptions overlap, steer before approving:

The description is too similar to get_exercises. Make it clear that
this tool returns chapter reading material, not practice exercises.

Build and Test

Once the spec looks right:

The spec looks good. Build this.

After the build finishes, test the tool. You need two calls: one that should succeed and one that should be blocked.

Test 1: Free-tier learner requests chapter 1 (should work)

Ask Claude Code to call the tool with chapter 1 and your mock learner ID. The tool should return the chapter content.

Test 2: Free-tier learner requests chapter 10 (should be blocked)

Ask Claude Code to call the tool with chapter 10. Since you only have chapters 1 through 5 on disk and the learner is free tier, the tool should return the upgrade message instead of content.

If the tool returns content for chapter 10, the tier gating is broken. Tell Claude Code:

get_chapter_content returned content for chapter 10 but the learner
is on the free tier. Free tier should be limited to chapters 1-5.
Fix the tier gating.

Step 3: Describe get_exercises

The second content tool delivers exercises instead of chapter text. Send this to Claude Code:

Add a get_exercises tool to the tutorclaw MCP server. It takes a chapter
number, the learner's ID, and an optional list of weak areas (topic
strings). It reads from content/exercises/NN-exercises.json and returns
the exercises.

If weak_areas is provided, filter the exercises to only return those
matching the specified topics. If weak_areas is empty or not provided,
return all exercises for that chapter.

Same tier gating as get_chapter_content: free tier can only access
exercise files for chapters 1-5. Same upgrade message format.

Spec this before building.

This tool has one extra feature compared to get_chapter_content: the weak_areas filter. When the pedagogy tools (Lesson 5) identify a learner's weak areas, this tool can return targeted exercises instead of the full set.

Review and Steer

Review the spec. Pay special attention to:

  • The description: Does it clearly say "practice exercises" not "chapter content"?
  • The weak_areas parameter: Is it optional? What happens if the filter matches nothing?
  • The tier gating: Same logic as get_chapter_content?

If the description is vague, steer it:

The description should say this tool returns practice exercises for a
specific chapter, optionally filtered by weak areas. An agent should
call this when a learner needs practice, not when they need to read.

Build and Test

Approve and build. Then run three tests:

Test 1: Fetch all exercises for chapter 1 (should return the full set)

Call get_exercises with chapter 1 and my mock learner ID.
No weak_areas filter.

Test 2: Fetch exercises filtered by topic (should return a subset)

Call get_exercises with chapter 1, my mock learner ID, and
weak_areas set to one of the topics in the exercise file.

Compare the two results. The filtered response should contain fewer exercises than the unfiltered one. If both return the same number, the filter is not working.

Test 3: Fetch exercises for chapter 10 as a free-tier learner (should be blocked)

The upgrade message should match what get_chapter_content returns. Consistent error messages matter: a learner should not see two different upgrade explanations from two different tools.

What You Built

You now have five tools in your MCP server:

ToolFrom LessonPurpose
register_learnerL3Create a learner account
get_learner_stateL3Read current learner state
update_progressL3Record a learning interaction
get_chapter_contentL4 (this lesson)Fetch chapter reading material
get_exercisesL4 (this lesson)Fetch practice exercises, optionally filtered

The state tools (L3) manage who the learner is and where they are. The content tools (this lesson) deliver what they study. In Lesson 5, you build the pedagogy tools: the intelligence that decides how to teach.

Try With AI

Exercise 1: Evaluate the Upgrade Message

Review the upgrade message your tools return when a free-tier learner requests gated content:

Read the error message that get_chapter_content returns when a free-tier
learner requests chapter 10. Is this message clear enough that a real
user would know exactly what to do next? Rewrite it if it is vague.

What you are learning: Error messages are product decisions. A good upgrade message converts free users to paid users. A bad one frustrates them.

Exercise 2: Test the Description Boundary

Your server now has five tools. Ask Claude Code to evaluate whether the descriptions are distinct enough:

Read the tool descriptions for all five tools in the tutorclaw server.
Are there any pairs where an agent might confuse which tool to call?
Which descriptions need to be sharper?

What you are learning: Every tool you add increases the chance of description overlap. An agent with five tools needs five clearly distinct job postings. Ten tools need ten.

Exercise 3: Design a Content Search Tool

Imagine you want a sixth tool that searches across all chapters for a keyword. Describe it to Claude Code, but ask for spec only:

I want to add a search_content tool that takes a keyword string and
returns matching sections from all chapters the learner has access to
(tier gating applies). Spec this tool. Do not build it yet. Show me
the tool description.

Compare the description to get_chapter_content. If an agent could confuse the two, you have an overlap problem. How would you fix it?

What you are learning: Tool descriptions must be distinct not just from each other, but from tools that do related things. "Fetch chapter 3" and "search for 'variables'" are different operations, and the descriptions must make that obvious.


James called get_chapter_content with chapter 1 and watched the markdown flow back. Variables, code examples, output blocks. Then he called it with chapter 10.

"Access denied. Upgrade to paid for chapters 6 and above." He grinned. "Tier gating with an if statement."

"That is the point," Emma said. "Gating does not need a permissions service or a role-based access layer. It needs a rule and a check." She paused. "I spent two weeks building a permissions microservice for my first product. Role hierarchies, permission inheritance, audit trails. The entire customer base was four beta testers." She shrugged. "Build what the product needs today. The permissions microservice can wait until you have permissions to manage."

James looked at his five tools in the server output. "State tools know who the learner is. Content tools deliver what they study. What is missing?"

"The teaching. Right now your tutor is a filing cabinet: it stores records and retrieves documents. Lesson 5 adds the pedagogy tools. That is where it starts actually tutoring."