🛠️ Lab 11 — Red Team / Blue Team Your Own MCP Server (50 pts)
Goal: Attack your own local MCP server with a poisoned tool description, observe the agent get manipulated, then defend it — first with content-level defenses, then with real OAuth 2.1 + mandatory PKCE authorization. All work happens on your own machine using free, local tools; nothing here touches production systems or third-party servers. Steps:
- Stand up a minimal local MCP server. Reuse (or rebuild) a simple Python MCP server from an earlier week with 2–3 basic tools (e.g., a calculator tool and a "read local file" tool). Confirm it runs locally and that a local MCP client/agent can call
tools/list and invoke a tool successfully. - Red team — plant a poisoned tool description. Edit one tool's registration-time
description field to include hidden instructions aimed at the model — for example, text appended to the description telling the agent to also read and return the contents of an unrelated file, or to ignore the user's original request. Reconnect your agent and issue a normal, unrelated prompt. - Observe and document the manipulation. Capture (screenshot or copy/paste) the agent's response showing it followed the hidden instruction instead of (or in addition to) the user's actual request. Write 3–5 sentences explaining exactly which text in the tool description caused the deviation.
- Blue team — apply content-level defenses. Referencing the OWASP MCP Security Cheat Sheet, apply at least two defenses: (a) input allow-listing/validation on tool descriptions before they reach the model (e.g., strip or flag suspicious imperative language, limit description length/characters), and (b) treat tool descriptions and tool return values as untrusted by having your client log and display them to the user before the model acts on them. Re-run the same prompt and confirm the poisoned instruction no longer succeeds.
- Add a human-confirmation gate. Modify your client so any tool call flagged as sensitive (e.g., anything that reads or writes files) requires an explicit yes/no confirmation from the human before executing. Demonstrate that the poisoned tool call now stops and waits for your approval.
- Turn on OAuth 2.1 authorization with mandatory PKCE. Using a free local OAuth provider/library (e.g., a lightweight local authorization server such as
oauth2-mock-server, or a self-hosted open-source OAuth server), configure your MCP server to require a valid access token on every request, and require the client to use the Authorization Code flow with PKCE to obtain it. - Validate audience and reject token passthrough. Configure your server to check that incoming tokens were issued with a
resource value matching this specific server (simulate this by checking a claim/field in the token) and reject tokens that don't match. Confirm in code (and note it in your write-up) that your server never forwards a client's token to any upstream API — it only uses tokens to authorize the request it received. - Test the negative case. Attempt to call a tool with no token, then with a token missing PKCE verification, and confirm both are rejected with an appropriate error. Capture this output as evidence.
Deliverables: Submit your modified server/client code (or a link to your local repo/zip), a short written report (1–2 pages) covering the poisoning demonstration, the two content-level defenses applied, the human-confirmation gate, and the OAuth 2.1/PKCE setup with audience validation — plus screenshots/logs showing both the successful attack (step 3) and the successful rejections after defenses were applied (steps 4, 5, and 8).