Part II — Day‑1 Labs (with Solutions)¶
Canonical gateway port: 4444. Use the built‑in token utility for demo JWTs.
Lab 0 — Environment Checks (20m)¶
docker --version && docker compose version
python3 --version
Clean slate:
docker compose down -v 2>/dev/null || true
docker system prune -f --volumes
Lab 1 — Quickstart: Run the Gateway (≈45m)¶
python3 -m venv .venv && source .venv/bin/activate
pip install -U mcp-contextforge-gateway
mcpgateway --host 0.0.0.0 --port 4444
Health:
curl -s http://localhost:4444/health | jq .
JWT:
export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token --username [email protected] --exp 10080 --secret my-test-key)
✅ Solution¶
You should see a small JSON object from /health, typically {"status":"ok"...} (exact fields may vary). Keep this terminal open so you can watch gateway logs during subsequent labs.
Lab 2 — Register Your First MCP Server (≈45m)¶
Create calculator_server.py:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
import uvicorn
app = FastAPI(title="Calculator MCP Server")
class AddPayload(BaseModel):
a: float = Field(..., description="First addend")
b: float = Field(..., description="Second addend")
@app.get("/tools")
def tools():
return {
"tools": [
{
"name": "calc.add",
"description": "Add two numbers",
"schema": {
"type": "object",
"properties": {
"a": {"type":"number"},
"b": {"type":"number"}
},
"required": ["a","b"]
}
}
]
}
@app.post("/call/calc.add")
def call_add(payload: AddPayload):
try:
return {"result": payload.a + payload.b}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=9100)
Run:
pip install fastapi uvicorn pydantic
python calculator_server.py
Test locally:
curl -s http://localhost:9100/tools | jq .
curl -s -X POST http://localhost:9100/call/calc.add -H 'Content-Type: application/json' -d '{"a":2,"b":3}' | jq .
Register with the gateway:
export BASE_URL="http://localhost:4444"; export TOKEN="$MCPGATEWAY_BEARER_TOKEN"
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"name":"calc-server","url":"http://localhost:9100","description":"Calculator MCP server","enabled":true,"request_type":"STREAMABLEHTTP"
}' $BASE_URL/gateways | jq '.'
curl -s -H "Authorization: Bearer $TOKEN" $BASE_URL/tools | jq '.[] | {name, gateway: .gatewaySlug}'
✅ Solution¶
/toolsreturns a list including calc.add.- POST returns
{"result": 5}fora=2,b=3. - Gateway catalog now lists
calc.addunder your gateway slug.
Lab 3 — Clients & CLI (35m)¶
mcp --server http://localhost:4444 tools list
mcp --server http://localhost:4444 tool call calc.add '{"a":2,"b":3}'
✅ Solution¶
The CLI returns 5. If listing works but calling fails, re-check the JSON payload matches the tool schema exactly and that your server is still running.
Lab 4 — Simple Passthrough / Wrapper (35m)¶
Create httpbin_wrapper.py:
from fastapi import FastAPI, HTTPException
import requests, uvicorn
app = FastAPI(title="HTTPBin Wrapper Server")
@app.get("/tools")
def tools():
return {
"tools": [
{
"name":"httpbin.get",
"description":"GET https://httpbin.org/get",
"schema": {"type":"object","properties":{},"additionalProperties":False}
}
]
}
@app.post("/call/httpbin.get")
def call_httpbin():
try:
r = requests.get("https://httpbin.org/get", timeout=20)
r.raise_for_status()
return r.json()
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
if __name__=="__main__":
uvicorn.run(app, host="0.0.0.0", port=9200)
Run & register similar to Lab 2.
✅ Solution¶
Calling the tool via the gateway returns an httpbin JSON (headers, origin, URL). You’ve wrapped a public HTTP API as a first‑class tool.
Lab 5 — Guardrails Intro (30m)¶
Enable a basic rate limiter in your gateway config:
plugins:
- name: rate_limiter
kind: plugins.rate_limiter.rate_limiter.RateLimiterPlugin
hooks: ["prompt_pre_fetch", "tool_pre_invoke"]
mode: enforce
priority: 50
config:
by_user: "60/m"
by_tenant: "600/m"
by_tool: "30/m"
burst: 5
Provoke:
for i in {1..5}; do
mcp --server http://localhost:4444 tool call calc.add '{"a":1,"b":1}' || true
done
✅ Solution¶
Early calls return 200; later ones return 429 Too Many Requests. Gateway logs show which bucket tripped (by user, tool, or tenant).