Homework — Lesson 09
Three tasks · per-user state, secret hygiene, destructive-action confirmation · ~60–90 min
The lesson body wired get_current_user(ctx) into a single tool (export).
These three tasks push the same discipline into the rest of PKB's tools, harden a hypothetical
third-party API call against credential leakage, and add a confirmation flag to a destructive
operation. Each task ends with a concrete self-check you can run from the shell.
Before you start
Make sure Lesson 09's lab is committed and the sample server runs (PKB_USER=local
python -m pkb.server). All three tasks edit files inside
mcp-servers/sample-server/python/pkb/; you should not need to touch the lesson HTML
pages.
Task 1 — Per-user write
Refactor the write tool (added in Lesson 08) so it stores notes under the
authenticated user instead of the global NOTES alias. Use
get_current_user(ctx) to identify the caller, and write into
USER_NOTES[user][title]. Update export's helper if needed so it
returns the same per-user bucket. The tool signature still takes a WriteArgs model
and a Context; only the body changes.
Pay attention to two subtleties:
-
The
overwrite=Falsecheck has to look at the per-user bucket, not the global one — otherwise Alice writing a note named"hello"blocks Bob from creating his own note with the same title. -
The
NOTESalias still exists for compatibility with the read tools from earlier lessons. You can either redirect reads throughget_current_usertoo, or leave them as-is and add a# TODOfor Lesson 10. Either is acceptable; document your choice in a comment.
✓ Self-check: run the server with PKB_USER=alice in one
terminal and PKB_USER=bob in another. Each user writes a different note titled
"hello". Calling export() as Alice returns only her notes; calling it
as Bob returns only his. There is no cross-talk in either direction.
Task 2 — Secret hygiene audit
Add a hypothetical pkb.translate(id, lang) tool that translates a stored note via a
fake third-party translation API. The API key lives in
os.environ["PKB_TRANSLATE_KEY"]. Your tool calls a helper
_call_translate_api(text, lang) (you can leave this as a one-line stub; the unit
test will mock it). When the helper raises, your error formatter must strip the API key
from the error message before returning it through McpError.
Then write a unit test test_no_key_leak (in
mcp-servers/sample-server/python/tests/) that:
- Sets
PKB_TRANSLATE_KEY=secret-token-xyzviamonkeypatch.setenv. - Monkey-patches
_call_translate_apito raise an exception whose message includes the URL and key, e.g.RuntimeError("GET https://api.example/?key=secret-token-xyz failed"). - Calls
translate(id="hello", lang="es")and expects anMcpErrorto be raised. - Asserts the literal string
"secret-token-xyz"is not in the resulting error message (or any field on theErrorData).
Watch the data field too
ErrorData has a top-level message and an optional data
dict. A naive redactor that only scrubs message will leak the key through
data if your handler ever stuffs the original exception there. Test both, or
simply pass str(exc.value) (which includes both fields) into the assertion.
✓ Self-check: pytest tests/test_no_key_leak.py -q passes,
and adding a deliberate bug (e.g. message=f"translate failed: {e}" with no
redaction) makes the test fail with a clear diff.
Task 3 — Lock down export
The export tool currently returns every note the authenticated user owns the moment
it's called. For a destructive or sensitive disclosure that's too eager — the
LLM should have to say it means it. Add a confirm_export: bool = Field(False)
argument to the tool. When confirm_export=False (the default), the tool returns the
plain string "set confirm_export=true to proceed" and does not
touch USER_NOTES. When confirm_export=True, it behaves exactly as
before.
Use a plain return string rather than raising McpError. Errors are for
protocol violations; “did you really mean it?” is a soft prompt to the model, not a
failure mode.
✓ Self-check: export() (no args) returns
"set confirm_export=true to proceed". export(confirm_export=True)
returns the dict of the current user's notes. The Inspector or your test client shows both
responses without any error frames in between.
What to submit
- A single diff (or branch) against
mcp-servers/sample-server/python/pkb/with all three tasks applied. tests/test_no_key_leak.py(and any other tests you wrote).- A short README note (4–6 sentences) describing what you changed in
writeand how you decided to handle theNOTESalias.