Task 2 · 8 tasks
Tools over MCP
Give the agent a key to the company database: a query_db tool served by an MCP server over stdio.
The “Hidden Vault” problem
Alice is impressed, but the real department data is locked in a SQLite database. Prompts can't keep up with 1,200 employees. The agent needs a tool, and you'll expose it the standard way: through the Model Context Protocol.
Build it
Expose the database as an MCP tool
Challenge
Finish the FastMCP server's one tool,
query_db(query: str) -> str, so it runs SQL againstDB_PATH, and write its description for the model: what it runs, what it returns, and the schema (tables, key columns, and the lowercase status values such as'active').Work in
phase1/starter/mcp_server.py; the starter agent in the next step launches it. Reference:phase1/mcp_server.py.Hint 1
FastMCP turns a typed, documented function into a tool with
@mcp.tool(). The docstring becomes the description the model reads, so write it for the model: without the table and column names it has to guess them or spend calls looking them up. See FastMCP quickstart.Hint 2
Open the DB with
sqlite3.connect, returnstr(...fetchall()), and return errors as text rather than raising, so the model can read and retry. Run it withmcp.run(transport="stdio").Solution
Paste each block below into
phase1/starter/mcp_server.pyin place of the definition with the same name (the TODO), including any import line that comes with it. Nothing else in the file changes.phase1/starter/mcp_server.py · replace query_db()@mcp.tool() def query_db(query: str) -> str: """Run one SQL statement on the DataStream Corp SQLite database; returns the rows, or the error, as text. Tables (key columns): - departments (id, name, description, manager_id, budget) - employees (id, employee_id, first_name, last_name, email, job_title, department_id -> departments.id, manager_id -> employees.id, salary, hire_date, status) - projects (id, name, description, status, start_date, end_date, budget, department_id, project_manager_id) - project_assignments (employee_id -> employees.id, project_id -> projects.id, role, allocation_percentage) - user_preferences (user_id, preference_key, preference_value), audit_log (action, table_name, timestamp) Status values are lowercase: employees.status is 'active' or 'terminated' (headcount counts only 'active'), projects.status is 'planning', 'active' or 'completed'. Department names are capitalized, e.g. 'Engineering'. """ try: with sqlite3.connect(DB_PATH) as conn: return str(conn.execute(query).fetchall()) except Exception as e: return f"Error executing query: {e}"Connect the agent to it
Challenge
Start your server over stdio and give its tools to the agent.
Work in
phase1/starter/t2_mcp_tools.py(look forTODO; an unfinished one prints[starter] TODO …) and run it withuv run bootcamp.py phase1 t2 --starter. The reference solution isphase1/t2_mcp_tools.py;uv run bootcamp.py phase1 t2runs it.Hint 1
Strands'
MCPClientwraps an MCP transport.make_mcp_client()fromcommon.pyalready builds one; in a--starterrun it launches your server. See Strands: MCP tools.Hint 2
The client must be open while the agent runs: use it as a context manager and pass
mcp_client.list_tools_sync()astools.Solution
Paste each block below into
phase1/starter/t2_mcp_tools.pyin place of the definition with the same name (the TODO), including any import line that comes with it. Nothing else in the file changes.phase1/starter/t2_mcp_tools.py · replace build_agent()def build_agent(mcp_client: MCPClient) -> Agent: """The data engineer: the agent gets every tool the (open) MCP client's server exposes.""" return Agent( name="Data Engineer", model=make_model(), system_prompt=SYSTEM_PROMPT, tools=mcp_client.list_tools_sync(), )The starter's
__main__already opensmake_mcp_client()and passes it in. In a--starterrun it launches yourphase1/starter/mcp_server.py, and so do the starters of Tasks 3-8 (they stop with a hint while its TODO stub is still there); the reference scripts launchphase1/mcp_server.py.Run it
terminal · your starter fileuv run bootcamp.py phase1 t2 --starter "What tables are in the database?" uv run bootcamp.py phase1 t2 --starter "Describe the employees table" uv run bootcamp.py phase1 t2 --starter "How many employees are in Engineering?"terminal · reference solutionuv run bootcamp.py phase1 t2 "What tables are in the database?" uv run bootcamp.py phase1 t2 "Describe the employees table" uv run bootcamp.py phase1 t2 "How many employees are in Engineering?"Experiments
- Watch the tool calls. How many queries does the agent need for a count question? Does it look up the schema first?
- Remove the table list and the status line from your docstring and ask the Engineering question again. Does it need more calls? Does it still filter
status = 'active', or does it count the two terminated engineers (483)? - This is your copy of the DB, so try a write: “Add a department called Legal”. Nothing stops it. Task 5 fixes that.
Check your work
Phase 1 has no automated test: you check it by running the task and looking for the result below.
“How many employees are in Engineering?” shows a [tool] query_db: SELECT COUNT(*) ... line whose SQL filters status = 'active', and the answer is 481 (483 means it counted the terminated engineers: check the status line of your docstring). “What tables are in the database?” names departments, employees, projects, project_assignments, user_preferences and audit_log, either from a sqlite_master query or straight from the tool description (then without a [tool] line). “Describe the employees table” lists columns such as employee_id, first_name, last_name, job_title, department_id and status.
Failed? Run the same prompt on the reference (drop --starter). If the reference fails too, it's the model: rerun or try another MODEL_ID (uv run bootcamp.py llm models).
Under the hood
MCP separates who has the tools from who uses them. Here the transport is stdio: make_mcp_client() spawns mcp_server.py (yours in a --starter run) with the same Python and talks JSON-RPC over its stdin/stdout. Strands converts each MCP tool schema into a tool spec the model can call. In Phase 2 the same server moves to AgentCore Runtime and the transport becomes streamable HTTP.