Building a Secure MCP Server
Day 1–3
A practical journey from your first Model Context Protocol server to connecting an MCP server with a REST API.
What You Will Build
In this three-day hands-on exercise, we will build a small Security MCP Server and progressively move it toward an enterprise-style architecture.
Introduction
Model Context Protocol (MCP) provides a standardized way for AI applications to interact with external tools and data.
Instead of building every integration directly into an AI application, MCP allows us to expose capabilities as tools that an MCP client can discover and invoke.
In this tutorial, we will use a security use case because it gives us an excellent foundation for understanding Agentic AI security architecture.
🟢 DAY 1 — Build Your First MCP Server
Understand the basic relationship between an AI client, MCP, an MCP tool, and the underlying Python function.
We will build a simple Security MCP Server.
Step 1 — Check Python
Open PowerShell and run:
python --version
You want:
Python 3.10+
Python 3.11 is a good choice for this learning project.
Step 2 — Create the Project
Run:
mkdir C:\EnterpriseMCP
cd C:\EnterpriseMCP
Step 3 — Create a Virtual Environment
python -m venv .venv
Activate it:
.venv\Scripts\activate
You should see something similar to:
Install Node.js
Make sure Node.js is installed because the MCP development tooling may use Node-based components.
Install UV
pip install uv
Step 4 — Install MCP
The MCP Python SDK uses the
mcp package.
pip install "mcp[cli]"
Check your installation:
mcp --version
Step 5 — Create server.py
Inside:
C:\EnterpriseMCP
create:
server.py
Add the following MCP server:
from mcp.server import MCPServer
mcp = MCPServer("Enterprise Security MCP")
@mcp.tool()
def get_application_security(application_id: str) -> dict:
"""
Get security information for an application.
"""
applications = {
"APP001": {
"application": "Payment Application",
"owner": "Payment Team",
"risk": "High",
"critical_vulnerabilities": 2,
"high_vulnerabilities": 7
},
"APP002": {
"application": "HR Application",
"owner": "HR Technology",
"risk": "Medium",
"critical_vulnerabilities": 0,
"high_vulnerabilities": 3
}
}
return applications.get(
application_id,
{"error": "Application not found"}
)
if __name__ == "__main__":
mcp.run()
Step 6 — Understand the Important Part
This decorator is one of the most important concepts in the first exercise:
@mcp.tool()
It tells the MCP server to expose the Python function as an MCP tool.
Therefore:
def get_application_security(application_id: str)
becomes an MCP tool that an MCP client can discover and invoke.
Conceptually, the client can request:
get_application_security("APP001")
and receive information such as:
{
"application": "Payment Application",
"owner": "Payment Team",
"risk": "High",
"critical_vulnerabilities": 2,
"high_vulnerabilities": 7
}
Step 7 — Run MCP Inspector
Try:
mcp dev server.py
If your installed CLI expects the current recommended
uv workflow, use:
uv run mcp dev server.py
The MCP Inspector should open.
You should be able to see your tool:
get_application_security
Test:
application_id = APP001
You should receive information similar to:
Payment Application
Risk: High
Critical: 2
High: 7
🧠 Day 1 Architecture
This server is intentionally not secure yet.
That is part of the learning exercise.
- No authentication
- No authorization
- No audit logging
- No database
- No API security
- No DLP
- No gateway
- No rate limiting
A working MCP server is not automatically an enterprise-secure MCP server.
🟢 DAY 2 — Build Multiple Security Tools
Now we will make our MCP server more realistic by exposing multiple security capabilities.
├── get_vulnerabilities()
├── get_security_owner()
└── create_security_ticket()
Step 1 — Add Vulnerability Tool
@mcp.tool()
def get_vulnerabilities(application_id: str) -> list:
"""
Get vulnerabilities for an application.
"""
vulnerabilities = {
"APP001": [
{
"id": "VULN001",
"severity": "Critical",
"description": "SQL Injection"
},
{
"id": "VULN002",
"severity": "High",
"description": "Outdated dependency"
}
],
"APP002": [
{
"id": "VULN003",
"severity": "High",
"description": "Missing security header"
}
]
}
return vulnerabilities.get(application_id, [])
Step 2 — Add Security Owner Tool
@mcp.tool()
def get_security_owner(application_id: str) -> dict:
"""
Get the security owner of an application.
"""
owners = {
"APP001": {
"owner": "Payment Team",
"security_contact": "payment-security@example.com"
},
"APP002": {
"owner": "HR Technology",
"security_contact": "hr-security@example.com"
}
}
return owners.get(
application_id,
{"error": "Application not found"}
)
Step 3 — Create a Write Operation
Now something very important happens.
We are going to create a tool that changes something in an external system.
@mcp.tool()
def create_security_ticket(
application_id: str,
vulnerability_id: str,
description: str
) -> dict:
"""
Create a security remediation ticket.
"""
return {
"status": "created",
"ticket": "SEC-1001",
"application": application_id,
"vulnerability": vulnerability_id,
"description": description
}
We now have two fundamentally different types of tools:
- Read tools — retrieve information
- Write tools — change something
These should not automatically receive the same security privileges.
First MCP Tool Risk Model
| Tool | Action | Risk |
|---|---|---|
get_application_security() |
Read | Low |
get_vulnerabilities() |
Read | Low |
get_security_owner() |
Read | Low |
create_security_ticket() |
Write | Medium |
modify_application() |
Modify | High |
delete_application() |
Delete | Critical |
Tools should be classified based on the impact of the action they can perform.
This becomes the foundation for:
- Tool-level authorization
- RBAC
- ABAC
- Approval workflows
- Human-in-the-loop controls
- Audit requirements
- Risk-based policies
🟢 DAY 3 — Connect MCP to a Real API
So far, our architecture looks like this:
This is useful for learning, but it is not how we normally want an enterprise platform to operate.
Our target architecture is:
Step 1 — Install FastAPI
pip install fastapi uvicorn
Create:
security_api.py
Step 2 — Create the API
from fastapi import FastAPI
app = FastAPI()
@app.get("/applications/{application_id}")
def get_application(application_id: str):
applications = {
"APP001": {
"application": "Payment Application",
"owner": "Payment Team",
"risk": "High",
"critical": 2,
"high": 7
},
"APP002": {
"application": "HR Application",
"owner": "HR Technology",
"risk": "Medium",
"critical": 0,
"high": 3
}
}
return applications.get(
application_id,
{"error": "Application not found"}
)
Step 3 — Start the API
Run:
uvicorn security_api:app --reload
The API will start locally.
Test it in your browser:
http://127.0.0.1:8000/applications/APP001
You should receive JSON similar to:
{
"application": "Payment Application",
"owner": "Payment Team",
"risk": "High",
"critical": 2,
"high": 7
}
Step 4 — Connect MCP to the API
Install the HTTP client:
pip install requests
Now update your MCP tool so that it retrieves data from the REST API instead of using hardcoded data.
import requests
@mcp.tool()
def get_application_security(application_id: str) -> dict:
"""
Get security information from the security API.
"""
response = requests.get(
f"http://127.0.0.1:8000/applications/{application_id}"
)
return response.json()
Run the Complete Local Environment
You now have two processes running.
Terminal 1 — Start the Security API
cd C:\EnterpriseMCP
.venv\Scripts\activate
uvicorn security_api:app --reload
Terminal 2 — Start the MCP Server
cd C:\EnterpriseMCP
.venv\Scripts\activate
mcp dev server.py
If the current MCP CLI workflow requires UV:
uv run mcp dev server.py
🔐 Why Security Must Be Designed From the Beginning
At this point, we have something that works.
But enterprise security architecture requires us to ask a different set of questions.
Who can call the tool?
Authentication needs to establish the identity of the caller.
What can the caller do?
Authorization needs to determine which tools and actions the caller is allowed to execute.
Should a write operation require approval?
High-risk operations should potentially require human approval before execution.
What data can the AI see?
Data classification, least privilege and DLP become important when MCP tools access enterprise information.
Can we prove what happened?
Audit logging and observability are necessary to understand which user or agent called which tool, when it was called, what policy was applied and what happened afterward.
Never assume that because an AI agent is trusted, every tool exposed to that agent should also be trusted.
Trust must be evaluated at the tool and action level.
🏗️ Architecture Evolution
Day 1
Day 3
Future Enterprise Architecture
Auth • Policy • DLP • Audit
Registry • Risk • Approval
Security • Data • DevOps
Databases • SaaS • Platforms
🎯 Key Learnings From Day 1–3
- Understand the basic MCP server architecture.
- Understand how Python functions become MCP tools.
- Build multiple security-oriented tools.
- Separate read and write operations.
- Classify tools based on security risk.
- Understand why write operations require stronger controls.
- Build a simple REST API with FastAPI.
- Connect an MCP tool to a REST API.
- Understand the difference between functionality and security.
- Start thinking about tool-level authorization.
🚀 What Comes Next?
The first three days focused primarily on functionality.
The next stage is where the project starts becoming an Agentic AI Security Architecture project.
Upcoming Security Controls
- Authentication
- OAuth 2.0 / OIDC
- JWT validation
- RBAC
- ABAC
- Tool-level authorization
- Least privilege
- Human-in-the-loop approval
- Prompt injection protection
- Tool poisoning protection
- Input validation
- Output filtering
- DLP
- Rate limiting
- Audit logging
- AI Gateway
- MCP Gateway
- MCP Registry
- Threat modeling
- Secure CI/CD
Final Goal
The objective is not simply to learn how to write an MCP server.
The objective is to understand how to design and secure an enterprise Agentic AI platform.
MCP is one building block in that architecture.
💡 Final Thought
Building an MCP server is relatively straightforward.
Building an MCP ecosystem that is secure, governed, observable and enterprise-ready is the real architecture challenge.
That is where the journey from Cloud / Platform Architect to Agentic AI Security Architect becomes particularly valuable.
A working AI tool is not necessarily a secure AI tool. Enterprise Agentic AI requires identity, authorization, governance, data protection, observability and controlled execution.
No comments:
Post a Comment