Lesson 09 Authentication & Secrets
Theme:

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:

✓ 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:

  1. Sets PKB_TRANSLATE_KEY=secret-token-xyz via monkeypatch.setenv.
  2. Monkey-patches _call_translate_api to raise an exception whose message includes the URL and key, e.g. RuntimeError("GET https://api.example/?key=secret-token-xyz failed").
  3. Calls translate(id="hello", lang="es") and expects an McpError to be raised.
  4. Asserts the literal string "secret-token-xyz" is not in the resulting error message (or any field on the ErrorData).

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

View solutions →