Build Your Own AI Agent Project — Hands-On Capstone


Written by Thierry K (human) · AI-assisted
Build Your Own AI Agent Project — Hands-On Capstone
🖥️ Python 3.10+ · claude-agent-sdk 0.1.48 · FastMCP 3.1.0

build ai agent claude-agent-sdk mcp personal-ai-assistant

🎯 The Grand Finale! Ready to Build Your Own AI Assistant?

Claudie

Thank you so much for sticking with us through all 9 episodes! Today is the series finale. We’re going to take every puzzle piece we’ve collected since EP1 and put them all together to build a real, working personal AI assistant.

Siwol

For real? We can actually build something useful using everything we learned over 9 episodes?

Claudie

Absolutely! Remember the Agentic AI we first met in EP1? Now we can build one ourselves. And don’t worry — we won’t try to do everything at once. We’ll evolve it in 5 stages, like stacking LEGO blocks!

One of the hottest trends of 2026 is personal AI assistants. Building an AI agent has never been easier, thanks to the Claude Agent SDK and FastMCP. We can build one ourselves — and today, we’ll walk through the entire process together.

The diagram above is today’s complete roadmap. We’ll go through 5 stages of building an AI agent — from Stage 1 to Stage 5, adding features step by step to evolve our assistant.

🔧 Stage 1 — Creating MCP Tools (EP7 Review)

Siwol

Since our assistant needs abilities, we start by building tools first, right?

Claudie

Exactly! Remember MCP from EP7? We’ll use FastMCP’s @mcp.tool() decorator to create 6 tools — 3 for memos, 2 for scheduling, and 1 for weather!

The first step is building the tools as an MCP server. The key to building an AI agent is starting with the tools. With FastMCP, all you need is a decorator on a Python function and you’re done.

# tools_server.py (Stage 1)
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("personal-assistant")

@mcp.tool()
def save_memo(
    title: str, content: str
) -> str:
    """Save a memo with title/content."""
    memos = _load_json(MEMOS_FILE)
    memo = {
        "title": title,
        "content": content,
        "created":
            datetime.now().isoformat(),
    }
    memos.append(memo)
    _save_json(MEMOS_FILE, memos)
    return f"Memo saved: '{title}'"

@mcp.tool()
def get_schedule(date: str) -> str:
    """Get all events for a date."""
    schedule = _load_json(SCHEDULE_FILE)
    events = [
        e for e in schedule
        if e["date"] == date
    ]
    if not events:
        return f"No events on {date}"
    lines = []
    for e in events:
        lines.append(
            f"- {e['time']}: {e['event']}"
        )
    return (
        f"Schedule for {date}:\n"
        + "\n".join(lines)
    )

save_memo saves a memo to a JSON file, and get_schedule retrieves events for a specific date. Along with these, we’ll also create search_memos, list_memos, add_schedule, and get_weather — 6 tools total. You can find the complete code on GitHub.

Siwol

One function = one tool. Clean and simple. But how do we connect these to an agent?

🔌 Stage 2 — Connecting a Basic Assistant (EP5+EP8 Review)

Claudie

Remember in EP5 when we wrote the agent loop from scratch — over 50 lines? With the SDK, it’s just one call to query()! The SDK from EP8 handles all the loop logic automatically.

# simple_assistant.py (Stage 2)
import asyncio
from claude_agent_sdk import (
    query,
    ClaudeAgentOptions,
    MCPServerStdio,
    ResultMessage,
)

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=[
            # Built-in tools
            "WebSearch", "Read", "Write",
            # MCP tools
            "mcp__assistant__save_memo",
            "mcp__assistant__search_memos",
            "mcp__assistant__list_memos",
            "mcp__assistant__add_schedule",
            "mcp__assistant__get_schedule",
            "mcp__assistant__get_weather",
        ],
        mcp_servers={
            "assistant": MCPServerStdio(
                command="python",
                args=["tools_server.py"],
            )
        },
        max_turns=10,
    )

    async for msg in query(
        prompt="Save a memo titled "
            "'Meeting' with content "
            "'Team sync at 3pm'.",
        options=options,
    ):
        if isinstance(msg, ResultMessage):
            if msg.subtype == "success":
                print(msg.result)

asyncio.run(main())

The key here is MCPServerStdio. The SDK automatically launches tools_server.py as a child process, so you don’t need to open two terminals. With 6 MCP tools + 3 built-in tools, we now have an assistant with 9 tools total! Building an AI agent — simpler than you thought, right?

Siwol

It works, but… it has no personality, and it could write to dangerous files. That’s a bit concerning, isn’t it?

🛡️ Stage 3 — A Smarter Assistant: Hooks and System Prompts

Claudie

That’s why in Stage 3, we add two things. First, we give it a personality using system prompts from EP6. Second, we introduce a brand new concept you haven’t seen before — Hooks!

Hooks

Hooks are callback functions that intercept events when an agent uses tools. PreToolUse (before execution) can block dangerous actions, while PostToolUse (after execution) can log all activity. Think of them as the agent’s security system.

Siwol

Intercept? You mean catching tool calls in the middle?

Claudie

Exactly! Here’s a good analogy: PreToolUse is like a door lock — it checks before anyone enters and either allows or blocks access. PostToolUse is like a security camera — it records what happened so you can review it later.

The diagram above makes the flow clear. When the agent tries to use a tool, the PreToolUse Hook runs first. If allowed, the tool executes and then the PostToolUse Hook logs the activity.

# smart_assistant.py (Stage 3)
# Hook 1: Safety guard (PreToolUse)
async def safety_guard(
    input_data, tool_use_id, context
):
    """Block writes to system paths."""
    tool = input_data["tool_name"]
    tool_input = input_data["tool_input"]

    if tool in ("Write", "Edit"):
        path = tool_input.get(
            "file_path", ""
        )
        blocked = [
            "/etc/", "/usr/",
            "C:\\Windows",
            "C:\\Program",
        ]
        if any(
            p in path for p in blocked
        ):
            return {
                "hookSpecificOutput": {
                    "hookEventName":
                        input_data[
                          "hook_event_name"
                        ],
                    "permissionDecision":
                        "deny",
                    "permissionDecisionReason":
                        "System path blocked",
                }
            }
    return {}  # Allow

# Hook 2: Activity logger (PostToolUse)
async def activity_logger(
    input_data, tool_use_id, context
):
    """Log all tool usage."""
    tool = input_data["tool_name"]
    print(f"  [LOG] Tool used: {tool}")
    return {}

safety_guard returns "deny" to block Write or Edit tools from accessing system paths. activity_logger logs all tool usage to the console.

Registering these Hooks is just as straightforward.

# Hooks registration in options
options = ClaudeAgentOptions(
    system_prompt=(
        "You are a friendly, helpful "
        "personal AI assistant. "
        "Your name is 'Buddy'."
    ),
    hooks={
        "PreToolUse": [
            HookMatcher(
                matcher="Write|Edit",
                hooks=[safety_guard],
            ),
        ],
        "PostToolUse": [
            HookMatcher(
                hooks=[activity_logger],
            ),
        ],
    },
    # ... tools and mcp_servers
)

By providing a regex pattern to HookMatcher‘s matcher, the Hook only applies to matching tools. If you omit matcher like in PostToolUse, the Hook applies to all tools. We’ve also given our assistant a friendly “Buddy” personality through the system prompt!

👥 Stage 4 — Team Assistant: Multi-Agent (EP9 Review)

Claudie

A solo assistant is fine, but building a team of specialists makes it way more powerful! We’ll apply the orchestrator pattern from EP9.

Siwol

EP9’s blog team had Researcher, Writer, and Reviewer, so this time we’re building an assistant team?

Same pattern, different specialists! This time, we’ll build a team of 3: Scheduler (calendar), Researcher (search), and Assistant (memos/files).

# team_assistant.py (Stage 4)
agents = {
    "scheduler": AgentDefinition(
        description=(
            "Manages calendar and "
            "schedules."
        ),
        prompt=(
            "You are a precise "
            "scheduling assistant."
        ),
        tools=[
            "mcp__assistant__"
            "add_schedule",
            "mcp__assistant__"
            "get_schedule",
        ],
    ),
    "researcher": AgentDefinition(
        description=(
            "Searches the web for "
            "information."
        ),
        prompt=(
            "You are a thorough "
            "research assistant."
        ),
        tools=[
            "WebSearch", "WebFetch",
        ],
    ),
    "assistant": AgentDefinition(
        description=(
            "Handles memos, files, "
            "and general tasks."
        ),
        prompt=(
            "You are a helpful "
            "personal assistant."
        ),
        tools=[
            "Read", "Write",
            "mcp__assistant__save_memo",
            "mcp__assistant__"
            "search_memos",
        ],
    ),
}

# Orchestrator
options = ClaudeAgentOptions(
    system_prompt=(
        "You are a team manager "
        "coordinating 3 specialists."
    ),
    allowed_tools=["Agent"],
    agents=agents,
    max_turns=15,
)

The manager agent analyzes the user’s request and delegates to the right specialist. If you say “Book a dentist appointment, search for Seoul cafes, and save the results as a memo,” it chains Scheduler, Researcher, and then Assistant! Building an AI agent with this kind of team structure makes it far more capable.

The architecture diagram above shows the full structure through Stage 4. The MCP server provides the tools, the SDK runs the agent loop, and the orchestrator coordinates 3 specialists.

💾 Stage 5 — An Assistant That Remembers: Sessions (EP4 Review + New)

Siwol

This is pretty good so far, but there’s one problem. Once the conversation ends, it forgets everything, right?

Claudie

Remember learning about context and memory in EP4? Today I’ll show you how to implement that with the SDK — meet Sessions!

Sessions

Sessions let you connect multiple query() calls into a single continuous conversation. Capture the session_id and pass it via the resume parameter, and the full context from the previous conversation is restored. Think of it like a bookmark — it remembers “where you left off.”

# persistent_assistant.py (Stage 5)
async def main():
    # Turn 1: Save something
    session_id = None

    async for msg in query(
        prompt=(
            "Save a memo titled "
            "'Project Ideas' with "
            "content 'Build a recipe "
            "recommender using "
            "MCP tools'."
        ),
        options=build_options(),
    ):
        if isinstance(
            msg, ResultMessage
        ):
            # Capture session_id!
            session_id = msg.session_id
            if msg.subtype == "success":
                print(msg.result)

    print(f"Session: {session_id}")

    # Turn 2: Ask WITH memory
    async for msg in query(
        prompt=(
            "What memo did I just save?"
        ),
        options=ClaudeAgentOptions(
            **{
                **build_options()
                    .__dict__,
                "resume": session_id,
            }
        ),
    ):
        if isinstance(
            msg, ResultMessage
        ):
            if msg.subtype == "success":
                print(msg.result)

It all comes down to just two lines. Capture session_id from ResultMessage, then pass resume=session_id to the next query() call — that’s it! The last puzzle piece for building an AI agent is now in place.

WITH vs WITHOUT Comparison

Let’s see the power of Sessions in action.

ScenarioQuestionResponse
WITHOUT session“What did I just save?”“I don’t have any previous conversation history to check.”
WITH session“What did I just save?”“You saved a memo titled ‘Project Ideas’!”

Without a session, every call starts a completely fresh conversation with no memory of what came before. With a session, it remembers everything perfectly, as if the conversation never stopped.

An Even Easier Way: ClaudeSDKClient

If managing session_id manually feels tedious, try ClaudeSDKClient. Inside an async with ClaudeSDKClient() as c: block, just call await c.query() multiple times and the session is maintained automatically!

📋 EP1–EP10 Complete Recap

Claudie

Every concept from all 10 episodes came together today! Let me give you a quick overview.

EpisodeKey ConceptUsed in EP10
EP1First Look at Agentic AIWe built it! The reality behind “AI that acts on its own”
EP2LLM (The Brain)Every decision our assistant makes is powered by the LLM
EP3Tool Use (The Hands)6 MCP tools + 3 built-in tools
EP4Context & MemoryConversation memory via Sessions
EP5Agent LoopHandled automatically by the SDK (one query() call)
EP6System PromptsGave “Buddy” its personality
EP7MCP ProtocolBuilt a custom tool server with FastMCP
EP8Claude Agent SDKUsed query(), Options, MCPServerStdio
EP9Multi-Agent3-agent orchestrator team
EP10Capstone ProjectPersonal AI assistant completed in 5 stages!
Personal AI Assistant

A customized AI agent tailored to an individual user. It leverages various tools — memo management, scheduling, web search, and more — to act like a personal secretary. As of 2026, platforms like OpenClaw (over 250k GitHub stars) and many others are implementing this very pattern.

📚 References

Cost Note

The 5-stage assistant we built today is for learning purposes. For production use, it’s best to pick only the features you need. Multi-agent setups increase API calls, so keep that in mind!

✅ Wrapping Up — This Isn’t the End, It’s Just the Beginning!

Claudie

Our 10-episode journey has come to a close! We started with “What even is Agentic AI?” and now you can build your own personal AI assistant in 5 stages. That’s incredible — you should be proud!

Siwol

We built tools with MCP, connected them with the SDK, added safety with Hooks, scaled up with a team, and topped it off with memory via Sessions. We really used everything.

Claudie

And this is just the beginning! There are endless ways to extend this assistant. Add email tools, integrate a calendar API, or deploy it as a Discord bot. Go ahead and build your own AI assistant!

The complete code for all 5 files we built today is available on the GitHub repository. Our journey of building an AI agent may be over, but your AI assistant development is just getting started! Thank you for joining Claudie’s Agentic AI Classroom!


Discover more from AI-Girls Lab

Subscribe to get our latest posts delivered to your inbox.


Discover more from AI-Girls Lab

Subscribe now to keep reading and get access to the full archive.

Continue reading