What MCP Servers Are Built in Phase 13? Registry, Governance, and Tool Safety
Phase 13 of the AI Engineering from Scratch curriculum constructs three distinct production-ready Model Context Protocol (MCP) servers: a read-only server for safe operations, a destructive-tool server requiring human approval, and a centralized registry/gateway that governs capability discovery.
Phase 13 (officially titled Phase 13 – MCP Server with Registry and Governance) in the rohitg00/ai-engineering-from-scratch repository implements a complete enterprise tool-use stack. This capstone lesson demonstrates how to architect horizontally scalable, secure MCP infrastructure that enforces fine-grained authorization and safety-in-depth principles for AI agent deployments.
The Three MCP Servers Built in Phase 13
The Phase 13 implementation separates concerns across three specialized servers to minimize blast radius and enforce governance policies.
Read-Only MCP Server
The read-only MCP server exposes safe, non-mutating tools such as S3 bucket listing, PostgreSQL SELECT queries, and Jira GET operations. Implemented using FastMCP (Python) or the @modelcontextprotocol/sdk (TypeScript), this server utilizes the StreamableHTTP transport for stateless, horizontally scalable deployments. Each tool enforces OAuth 2.1 scopes (e.g., s3:list, postgres:query:readonly) to ensure callers possess only the minimum necessary permissions for read operations.
Destructive-Tool MCP Server
The destructive-tool MCP server hosts state-mutating operations like creating Jira tickets or writing to databases. Running on a separate FastMCP instance, this server implements a stricter authentication flow requiring the approved:by:human scope. This scope is granted only after a human approves the operation via a Slack-approval card, demonstrating safety-in-depth for high-risk actions. OPA-based policies (Open Policy Agent) provide an additional enforcement layer before any destructive tool executes.
Registry and Gateway Server
The registry/gateway server discovers, validates, and aggregates the capability manifests of both the read-only and destructive-tool servers. It serves a .well-known/mcp-capabilities document for automated client discovery and provides a management UI where platform teams can browse available tools, review required OAuth scopes, and enable or disable specific servers. The registry implements RBAC, rate limiting, audit logging, and centralized policy enforcement to govern the entire MCP ecosystem.
Security Architecture and Transport Layer
Phase 13 emphasizes production security patterns across all three MCP servers.
StreamableHTTP provides a stateless transport that supports streaming responses, allowing servers to scale horizontally behind standard load balancers. Unlike stateful transports, this approach requires no sticky sessions or connection affinity, making it ideal for Kubernetes deployments.
OAuth 2.1 scopes enforce fine-grained authorization per tool rather than per server. The read-only server validates scopes like s3:list for storage operations, while the destructive server requires compound scopes such as jira:create AND approved:by:human for ticket creation. This scope-based model enables precise access control that maps directly to organizational roles.
OPA policies protect destructive tools by evaluating contextual attributes (user identity, time of day, request payload) before execution. When combined with the human-approval workflow, this creates a dual-authorization pattern that prevents autonomous agents from executing dangerous operations without oversight.
FastMCP Implementation Example
The repository provides a minimal FastMCP server implementation that demonstrates how to register both safe and destructive tools with appropriate capability constraints.
# phases/13-tools-and-mcp/13-mcp-server-with-registry/code/fastmcp_server.py
"""
FastMCP example server exposing two sets of tools:
- safe (read‑only) tools
- destructive tools behind human‑approval.
"""
import fastmcp
from fastmcp import Tool, Capability, OAuthScope
# ----------------------------------------------------------------------
# 1️⃣ Define safe tools
# ----------------------------------------------------------------------
def list_s3_buckets(_: dict) -> dict:
"""List buckets – read‑only."""
return {"buckets": ["logs", "datasets", "backups"]}
def query_postgres(_: dict) -> dict:
"""Simple SELECT – read‑only."""
return {"rows": [{"id": 1, "value": "sample"}]}
safe_tools = [
Tool(
name="s3.list_buckets",
description="List S3 buckets (read‑only).",
input_schema={},
output_schema={"buckets": ["string"]},
capability=Capability(
scopes=[OAuthScope("s3:list")],
),
),
Tool(
name="postgres.query",
description="Run a SELECT query (read‑only).",
input_schema={"query": "string"},
output_schema={"rows": ["object"]},
capability=Capability(
scopes=[OAuthScope("postgres:query:readonly")],
),
),
]
# ----------------------------------------------------------------------
# 2️⃣ Define destructive tools (require human approval)
# ----------------------------------------------------------------------
def create_jira_ticket(payload: dict) -> dict:
"""Create a new Jira issue – mutating."""
# In a real server this would call the Jira API.
return {"ticket_id": "JIRA‑1234", "status": "created"}
destructive_tools = [
Tool(
name="jira.create_ticket",
description="Create a Jira ticket (requires human approval).",
input_schema={"title": "string", "description": "string"},
output_schema={"ticket_id": "string", "status": "string"},
capability=Capability(
scopes=[OAuthScope("jira:create"), OAuthScope("approved:by:human")],
),
)
]
# ----------------------------------------------------------------------
# 3️⃣ Assemble the server
# ----------------------------------------------------------------------
app = fastmcp.FastMCP(
tools=safe_tools + destructive_tools,
transport="streamable_http", # Stateless HTTP + streaming
auth_backend="oauth2.1", # Enforces scopes per‑tool
policy_engine="opa", # OPA policies gate destructive tools
)
if __name__ == "__main__":
# Run on port 8000 behind a load balancer for horizontal scaling
app.run(host="0.0.0.0", port=8000)
Execute the server with python fastmcp_server.py. When deployed, the registry service automatically discovers the server's capabilities at /.well-known/mcp-capabilities.
Key Files and Repository Structure
The Phase 13 lesson materials are organized in phases/13-tools-and-mcp/13-mcp-server-with-registry/ with the following critical resources:
phases/13-tools-and-mcp/13-mcp-server-with-registry/docs/en.md– Complete lesson narrative, prerequisites, and step-by-step implementation guide.phases/13-tools-and-mcp/13-mcp-server-with-registry/outputs/skill-mcp-server.md– Reusable skill blueprint that ships with the lesson, documenting the server architecture for production teams.phases/13-tools-and-mcp/13-mcp-server-with-registry/code/fastmcp_server.py– Reference implementation using FastMCP with Tool and Capability definitions.phases/13-tools-and-mcp/13-mcp-server-with-registry/code/tests.py– Unit tests verifying tool registration, OAuth scope enforcement, and OPA policy gating.site/assets/figures/001-d-mcp-servers.svg– Visual diagram illustrating the relationship between the three MCP servers and the registry.
Summary
- Phase 13 builds three MCP servers: a read-only server for safe queries, a destructive-tool server gated by human approval, and a registry/gateway for centralized governance.
- Security layers include OAuth 2.1 per-tool scopes, OPA policy enforcement, and StreamableHTTP for stateless scaling.
- Implementation uses FastMCP (Python) or the TypeScript SDK, with clear separation between mutating and non-mutating operations.
- Discovery mechanism relies on
.well-known/mcp-capabilitiesendpoints served by the registry for automated client configuration. - Source files are located in
phases/13-tools-and-mcp/13-mcp-server-with-registry/with comprehensive documentation and tests.
Frequently Asked Questions
What distinguishes the read-only MCP server from the destructive-tool server in Phase 13?
The read-only MCP server handles non-mutating operations like S3 listing and database queries, requiring only standard OAuth 2.1 scopes such as s3:list. The destructive-tool MCP server hosts state-changing operations like Jira ticket creation and requires the additional approved:by:human scope granted via Slack workflow, enforced by OPA policies to prevent unauthorized mutations.
How does the Registry server discover and validate MCP capabilities?
The Registry server queries the .well-known/mcp-capabilities endpoint on each registered MCP server to retrieve machine-readable manifests describing available tools, required OAuth scopes, and transport details. It validates these manifests against organizational policies and exposes them through a management UI where platform teams can audit and enable tools before they become available to AI agents.
What authentication protocol secures the Phase 13 MCP server architecture?
All three MCP servers implement OAuth 2.1 with fine-grained scopes that authorize specific tool invocations rather than blanket server access. The architecture supports scope hierarchies (e.g., postgres:query:readonly) and compound requirements for destructive tools, integrating with enterprise identity providers and the Registry's RBAC system for centralized access control.
Where can I access the complete Phase 13 MCP server source code and documentation?
The complete implementation resides in the rohitg00/ai-engineering-from-scratch repository under phases/13-tools-and-mcp/13-mcp-server-with-registry/. Key files include code/fastmcp_server.py for the server implementation, code/tests.py for validation suites, docs/en.md for the full lesson narrative, and outputs/skill-mcp-server.md for the production-ready skill blueprint.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →