MODULE 08 · 5 HOUR BUILD
MCP boundary inspector
Build an inspector that explains why recorded tool attempts were accepted, rejected, or left uncertain, then connect it to one verified MCP server revision.
Build evidence Record your actual checks, results, and limitations.
Build it in stages
- Run the offline seed and inspect the scope, header-mismatch, and uncertain-outcome classifications.
- Add request/result correlation and catalog identities for two configured servers.
- Add fixtures for unsupported revisions, partial results, stale catalogs, unknown tools, and interrupted mutations.
- Integrate one maintained client against a server with explicitly supported revision and transport.
- Document the authorization flow and produce a redacted trace with one allowed and one denied call.
Your acceptance criteria
Use these as your project review. Record commands, outputs, and failure cases in your repository.
- At least 10 offline trace fixtures receive deterministic classifications.
- No trace includes bearer tokens or complete private arguments.
- The integration report pins SDK, server protocol revision, and transport.
- A lost mutation response is classified unknown until authoritative reconciliation.
A working starting point
The seed runs as supplied. Extend it to satisfy the full brief. It is a teaching starting point, not a finished portfolio submission.
main.py
python
import json
VERSION = "2026-07-28"
TOOLS = {"lookup": {"read"}, "reserve": {"write"}}
def inspect(trace):
body = trace["body"]
params = body["params"]
name = params["name"]
headers = {key.lower(): value for key, value in trace["headers"].items()}
if params["_meta"]["io.modelcontextprotocol/protocolVersion"] != VERSION:
return "unsupported revision"
expected = {"mcp-protocol-version": VERSION, "mcp-method": "tools/call", "mcp-name": name}
if any(headers.get(key) != value for key, value in expected.items()):
return "header mismatch"
if name not in TOOLS:
return "unknown tool"
if not TOOLS[name] <= set(trace["scopes"]):
return "scope denied"
if trace.get("response_lost"):
return "outcome unknown"
return "eligible local trace"
def fixture(name, scopes, header_name=None, lost=False):
return {
"body": {"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {
"name": name, "arguments": {}, "_meta": {
"io.modelcontextprotocol/protocolVersion": VERSION,
"io.modelcontextprotocol/clientCapabilities": {}}}},
"headers": {"MCP-Protocol-Version": VERSION, "Mcp-Method": "tools/call", "Mcp-Name": header_name or name},
"scopes": scopes, "response_lost": lost,
}
traces = [fixture("lookup", ["read"]), fixture("reserve", ["read"]),
fixture("lookup", ["read"], "reserve"), fixture("reserve", ["write"], lost=True)]
for index, trace in enumerate(traces, 1):
print(json.dumps({"trace": index, "classification": inspect(trace)}, sort_keys=True))
Push it further
Add an explicit legacy compatibility adapter and test an older initialize-based revision separately from the modern per-request revision.