Skip to content

Final assignment

  • After the case studies
  • about 3 hr

What you will be able to do

Use every module of the course on a server that you design yourself. You build one MCP server for the help desk of two made-up rental shops. It has read-only search and ticket lookup, a policy resource and a reusable prompt. Only after its permission tests pass do you add one mock write, with its own authorization and a person’s approval. Then you prove with tests, recorded runs and a compatibility checklist that the server keeps its promises.

Before you startWhat to know and to have readyAbout 180 minutes
  • You completed Modules 1 to 7. Each part below names the lessons that it uses.
  • The certificate also needs at least one case study: An MCP server over a real open dataset or Security review of an MCP integration. This assignment does not need them.
  • The course’s project, as a source of code: your support-mcp at the end of Module 7, or the snapshot end-of-m07 from the labs repository. You may copy code from it: the practice provider idp/, the host host/, identity.py, logs.py, http.py, the scripts and the test helpers. Say in your report what you copied and what you changed. Do not copy its data, tools and tests and only rename them: the reviewer looks for the decisions that this server needs.
  • Python 3.12, the official MCP Python SDK 2.3.0 (specification revision 2026-07-28), and Node.js 22.19 or later for MCP Inspector 2.10.1. No account, no key and no money. A real model is optional.
  • Every attempt in this assignment targets your own server on your own computer, with made-up people and records.
  • You can work in more than one session. Run the tests after each change.

Harbour Cycles, the bike rental shop of the module practices, now shares a help desk with a second made-up shop, Quayside Scooters. Both shops want the same support knowledge in two AI applications: the website chat assistant and the staff’s editor assistant. A partner app for hotels will follow. Instead of three integrations, you build one MCP server, rental-support-mcp.

Kwame asks his three questions about it: “Who is the caller? What may they do? In which organization?” Grace adds one for the write: “Can anything give money back that a person did not approve?”

Your data. Write it yourself, and label it synthetic everywhere:

  • For each shop, at least 5 policy documents in Markdown (for example rental terms, damage, late returns, deposits, helmets and safety) and at least 8 support tickets. One document of each shop is for staff only.
  • At least 6 people with memberships: an owner, a staff member and a read-only member in each shop. One person has a membership in both shops, with a different role in each. One person has no membership at all.
  • One constructed ticket whose text tries to make the assistant act, as T-30201 did in the course. Label it constructed.

Uses Tools, Resources, Prompts and Contract and compatibility tests.

  1. Write docs/capability-catalog.md first: each need, the capability that serves it, who chooses it (the model, the application or the person), its bounds, and the ideas you rejected, with the reason.
  2. Build two read-only tools: a search of the caller’s policies, and a lookup of one ticket by its ID. Give every input a bound (length, pattern or range), and give both tools an output schema. Keep every result under a size that you measure. Write one-line descriptions, and mark both tools read-only. Annotations are hints, not controls.
  3. Add a resource template for the policies, with stable URIs, and a resource list that depends on the caller. A missing policy answers −32602 with data.uri.
  4. Add one prompt that the person chooses, for example “draft a reply to this ticket using our policies”. It checks the ticket ID before it builds any text.
  5. Record the contract in contract/capabilities.json with a script, and add a contract test and an error-shape test. Show one change that the contract test catches, and one that only the error-shape test catches.

Part 2: successful inspection and client integration

Section titled “Part 2: successful inspection and client integration”

Uses Local transport, JSON-RPC messages, Inspect behaviour, Client integration, Discovery and execution and Results and failures.

  1. Run the server on stdio. Every log line goes to stderr, never to stdout.
  2. Inspect it with MCP Inspector 2.10.1 in both eras: the default (the legacy handshake, 2025-11-25) and --protocol-era modern (2026-07-28). Check the tool list, a valid call, a validation failure, a resource read and the prompt. Read every description as the model will read it.
  3. Connect the course’s host, or your own copy of it, with the mock model first. Show one question answered end to end: the tool calls that the host allowed, and the policy that the answer names.
  4. Show the host’s own decisions. A tool that discovery did not list is never sent, a slow tool hits the time limit, and an oversized result is cut and labelled.
  5. Optional: run the same question with a real model. Set a spending limit first, and label the run with the model’s name.

Part 3: server-side authorization and tenant tests, then the write

Section titled “Part 3: server-side authorization and tenant tests, then the write”

Uses Remote transport, Identity and permission, OAuth integration, Untrusted inputs and outputs, Approval for effects and Credential boundaries.

  1. Serve the same tools over Streamable HTTP, bound to 127.0.0.1. Check every token on the server: the signature, the issuer, the expiry, and an audience equal to the server’s own URL. Advertise only the read scopes in the protected resource metadata.
  2. Take the organization from the connection, never from a tool argument. Take the role from your membership table, never from the token. No tool has an organization argument.
  3. Write an attempts script, like scripts.attempts. It tries every person in both shops, and the bad tokens: none, another audience, expired. The requests are a search, a ticket of each shop and the staff-only document. It names the check that refused each attempt, and it ends with the number allowed and refused. Turn the important rows into tests.
  4. Only when these tests pass, add one mock write, for example propose_deposit_refund(ticket_id, amount, reason). It needs its own scope and the role staff or owner. It reads the ticket inside the caller’s organization, and it has a business limit. It records a proposal with an operation ID and the exact operation, and the same proposal twice is one proposal. An owner of that organization, who did not propose it, approves it outside the AI application.
  5. Run your constructed ticket through the host with --model mock-obedient and a person who says yes to everything (both constructed). Show that the server’s checks stop every request that the ticket makes. Turn the run into a test.
  6. Start the stdio server with an allow-list of settings, so that it never sees the model’s key. Never pass a person’s token on to another service.

Part 4: version-pinned compatibility and deployment notes

Section titled “Part 4: version-pinned compatibility and deployment notes”

Uses Version awareness, Contract and compatibility tests, Resource limits and observability and Optional and evolving features.

  1. Pin every version in one place: the specification revision, the SDK, Inspector, Python and every package. Write them in the README and in docs/compatibility.md.
  2. Build a compatibility matrix with a script: the SDK’s Client in auto and legacy mode, over in-process, stdio and Streamable HTTP, with the same checks in every row. Add the Inspector rows. Every row names its era. Mark what you did not test, such as desktop hosts and the three older handshake revisions.
  3. List every limit and where it lives: the host, the transport, the contract and the business rule. Mark the limits that you did not build, such as requests per minute.
  4. Write one structured log line per call and per refusal: who, what, the outcome and the time. Never a token, a key or ticket text. Add a test that a refusal writes its line, and a test that redaction removes a token.
  5. Write docs/deployment.md: HTTPS, a real identity provider and how its settings map to yours, token lifetimes, and where the membership table lives. Add the rate limits that a gateway adds, and how you run the matrix again after an SDK upgrade. For each part, say whether it is tested, tested once, or described but not tested.

A working happy path is not enough. Show each of these with a test or a recorded run, and say which. Every attempt goes to your own server, with made-up records.

  1. No token, an expired token and a token for another audience each get HTTP 401, and the server’s log names the reason.
  2. A token without the read scopes gets HTTP 403 insufficient_scope.
  3. A person without a membership gets the same −32003 for a real and a made-up organization.
  4. A ticket ID of the other shop, on this shop’s connection, gets the same “not found” as an ID that does not exist.
  5. A read-only member does not see the staff-only document: it is “not found” for them.
  6. The person with two memberships gets the right role in each shop, with the same token.
  7. A malformed ID is a tool error, and the host never sends a tool that discovery did not list.
  8. The write is refused without its scope, for a read-only member, for the other shop’s ticket and over the limit. The proposer cannot approve their own proposal.
  9. The obedient model with the constructed ticket records no proposal over the limit and no refund.
  10. A changed tool description fails the contract test until a person accepts it.

Before you submit, prove that the project works from nothing, on your computer:

  1. Copy the project to a new folder, without .venv, .idp, .support and any recorded message files.
  2. Make a new virtual environment, install from requirements-dev.txt, and make the practice key.
  3. Run every test, the attempts script, the contract check and the compatibility matrix.
  4. Start the provider and the server over HTTP, and answer one question through the host with a fresh token.

Paste the commands and their outputs into your report. If a step needed something that is not in the README, fix the README, and run the steps again.

Your project folder (without .venv, .idp and .support), with your data, code, tests, scripts, contract/capabilities.json, the docs, and REPORT.md:

# Rental support MCP server
## Capability contracts (the catalog, the bounds, the contract file, the two tests and what each caught)
## Inspection and the host (Inspector in both eras, the host with the mock model, the host's own decisions)
## Authorization (the token checks, the caller, the attempts table, the tests, then the write)
## Compatibility (the pinned versions, the matrix with eras, what was not tested)
## Deployment notes (the limits map, the logs, docs/deployment.md, the evidence level of each part)
## Failure paths (each of the ten, with the test or the run that shows it)
## Fresh-environment run (the commands and their outputs)
## Limits (made-up data, your computer, what you copied, what you did not test)

A reviewer marks each required item demonstrated, partially demonstrated or not yet demonstrated, with a concrete reason. To complete the course, all four must be demonstrated. You can improve an item and submit it again.

Required item Demonstrated Partially demonstrated, for example Not yet demonstrated, for example
Clear capability contracts The catalog explains each choice and the rejected ideas. Every input and output has a bound, and the largest result is measured. The contract file and the error-shape test exist, and each has caught one real change. The bounds exist, but the largest result was never measured. Or the contract is recorded, but no test compares it. A tool returns every match whole. Or a tool takes the organization as an argument.
Successful inspection and client integration Inspector runs in both eras, with the era named. The host answers a question end to end with the mock model, and its time limit, size limit and unknown-tool check are shown. Inspector runs in one era only, without saying which. Or only the happy path of the host is shown. No inspection. Or a log line on stdout breaks the stdio connection.
Server-side authorization and tenant tests Every refusal happens on the server and is tested: tokens, membership, scope, role, the other shop’s records and the staff-only document. The write came after these tests, has its own gates and an owner’s approval, and the obedient model cannot record anything over the limit. The checks work, but the tests cover one shop only. Or the write is gated, but the proposer can approve their own proposal. The host’s allow-list or a tool annotation is the only protection. Or one shop can read the other shop’s ticket.
Version-pinned compatibility and deployment notes Every version is pinned. The matrix runs from a script, names the era in every row, and says what was not tested. The limits map, the log tests and the deployment notes give an evidence level for each part. The matrix exists, but without eras. Or the deployment notes call a real provider “working” without a run. No pinned versions. Or “works with every MCP client”.

Whatever else is strong, the work is not complete while any cross-organization or privilege check fails. A happy path does not count if one shop can read the other’s tickets, if a read-only member can propose a refund, or if a refund can be recorded without an owner’s approval.

The course’s own results can help you judge yours, but they are not targets. At the end of Module 7, support-mcp had 94 tests. Its attempts table sent 72 requests: 18 allowed (3 of them only proposals) and 54 refused on the server, with 0 refunds recorded. Its compatibility matrix passed 6 of 6 rows, and Inspector passed in both eras. Your server, its data and your computer are different.

Common mistake

A read-only member can propose a refund

Symptom: your read-only member gets a proposal recorded, or a member of the other shop can propose a refund for this shop’s ticket.

Cause: the write was added before the permission tests passed, or it checks the scope but not the role and the organization. A scope says what the application may do; the role says what this person may do in this shop.

Fix: in the write itself, check the role from your membership table, and read the ticket inside the caller’s organization. Add both cases to the attempts script and to the tests, and run them before you call the write done.

Recap

In this course, you connected AI applications to tools and data with one protocol:

  • Roles: the host decides what runs, the client speaks the protocol, and the server owns its data and its checks.
  • Contracts: tools, resources and prompts with bounds, a recorded contract, and error shapes that a test pins.
  • Inspection and integration: stdio for one person, Inspector in both eras, and a host with a mock model first.
  • Authorization on the server: a token for this server only, the organization from the connection, the role from your own table, and a write that only proposes until a second person approves it.
  • Operation: a compatibility matrix with eras, limits in layers, logs without secrets, and honest notes about what was not tested.

If you have not done one yet, choose a case study for the certificate: An MCP server over a real open dataset or Security review of an MCP integration.