Assignment
Use an MCP SDK version you pin in the project. Expose a synthetic ticket lookup and a policy resource; no production credentials are needed.
Build sequence
- Connect a host/client harness and list the available capabilities.
- Invoke lookup with valid, invalid, and unauthorized ticket IDs.
- Change a schema and confirm the integration detects the mismatch.
- Return instruction-like text from a resource and verify it cannot expand the tool allowlist.
Acceptance checks
- The repository records protocol and SDK versions and reproducible setup.
- Discovery never grants additional application permissions automatically.
- Credentials and synthetic tenant data are isolated by the integration boundary.
Evidence to keep
Submit the working artifact or decision document described above, the inputs used, and the observed results for every acceptance check. Include one failed attempt and the change you made after investigating it. Label fixture-based outcomes separately from live-system measurements. These artifacts become part of your final portfolio review.
Guided workshop: run a local MCP server and client
MCP separates a host application, its client connections, and servers exposing capabilities. Tools perform operations; resources expose context; prompts expose reusable templates. The host decides what to offer the model and what the user is permitted to do. A server's presence is not authorization for every call.
Use Python 3.10 or newer and create a fresh virtual environment. This exercise targets the v1 SDK API: install it with python -m pip install "mcp==1.30.0", then record the exact resolved version with python -m pip freeze > requirements.lock. An unbounded install may select the incompatible v2 API. Save this as server.py:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Academy sandbox")
DOCS = {"reset": "Use Settings > Reset password."}
@mcp.tool()
def search_docs(query: str) -> list[str]:
"""Search public synthetic help documents."""
if not 1 <= len(query) <= 100:
raise ValueError("query length must be 1..100")
return [text for key, text in DOCS.items() if key in query.casefold()]
@mcp.resource("academy://guide/reset")
def reset_guide() -> str:
return DOCS["reset"]
if __name__ == "__main__":
mcp.run(transport="stdio")
Save the client alongside it as client.py and run python client.py from that directory:
import asyncio
import sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
parameters = StdioServerParameters(command=sys.executable, args=["server.py"])
async with stdio_client(parameters) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
assert any(tool.name == "search_docs" for tool in tools.tools)
result = await session.call_tool("search_docs", {"query": "reset"})
assert not result.isError
print(result.content)
asyncio.run(main())
The expected result contains the synthetic reset instruction. These examples follow the official v1 Python SDK; SDK-dependent execution requires the dependency to be installed and is separate from the dependency-free course checks.
Failure cases: empty query, unexpected argument, unknown tool, server exit, oversized result, and a server description containing misleading instructions. Keep protocol messages on standard output and diagnostics on standard error for stdio transport. Never replace the fixed server command with a command suggested by model output.
Extension: remote HTTP transport introduces a different trust boundary, authentication, origin validation, and deployment concerns. Implement those using the selected protocol revision and SDK guidance before exposing this server remotely. The local stdio sample is intentionally public-fixture-only.
Assessment: capture discovery, one successful call, one error, and clean process shutdown. Explain what a resource offers that a tool does not, and where the host applies authorization. Consult the versioned architecture when recording your protocol assumptions.