Skip to content

Commit 872d6f3

Browse files
committed
[minimcp][examples] Add web framework examples with authentication
- Add issue tracker MCP with scope-based auth - Add FastAPI integration example - Add Django WSGI integration example
1 parent da9dde5 commit 872d6f3

File tree

3 files changed

+287
-0
lines changed

3 files changed

+287
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python3
2+
# pyright: basic
3+
# pyright: reportMissingImports=false
4+
5+
"""
6+
Django WSGI HTTP MCP Server with Basic Authentication
7+
This example demonstrates how to create a minimal MCP server with Django using HTTP transport. It shows
8+
how to interface MiniMCP with Django, and shows how to use scope to pass authenticated user details that
9+
can be accessed inside the handler functions.
10+
11+
MiniMCP provides a powerful scope object mechanism that can be used to pass any type of extra information
12+
to be used in the handler functions.
13+
14+
How to run:
15+
# Start the server (default: http://127.0.0.1:8000)
16+
uv run --with django --with djangorestframework \
17+
python examples/minimcp/web_frameworks/django_wsgi_server_with_auth.py runserver
18+
19+
Testing with basic auth (Not validated, any username/password will work):
20+
21+
# 1. Ping the MCP server
22+
curl -X POST http://127.0.0.1:8000/mcp \
23+
-u admin:admin \
24+
-H "Content-Type: application/json" \
25+
-H "Accept: application/json" \
26+
-d '{"jsonrpc": "2.0", "id": "1", "method": "ping"}'
27+
28+
# 2. List tools
29+
curl -X POST http://127.0.0.1:8000/mcp \
30+
-u admin:admin \
31+
-H "Content-Type: application/json" \
32+
-H "Accept: application/json" \
33+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'
34+
35+
# 3. Create an issue
36+
curl -X POST http://127.0.0.1:8000/mcp \
37+
-u admin:admin \
38+
-H "Content-Type: application/json" \
39+
-H "Accept: application/json" \
40+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call",
41+
"params":{"name":"create_issue","arguments":{"title":"First issue","description":"Issue description"}}}'
42+
43+
# 4. Read the issue
44+
curl -X POST http://127.0.0.1:8000/mcp \
45+
-u admin:admin \
46+
-H "Content-Type: application/json" \
47+
-H "Accept: application/json" \
48+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call",
49+
"params":{"name":"read_issue","arguments":{"issue_id":"MCP-1"}}}'
50+
51+
# 5. Delete the issue
52+
curl -X POST http://127.0.0.1:8000/mcp \
53+
-u admin:admin \
54+
-H "Content-Type: application/json" \
55+
-H "Accept: application/json" \
56+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call",
57+
"params":{"name":"delete_issue","arguments":{"issue_id":"MCP-1"}}}'
58+
59+
"""
60+
61+
from collections.abc import Mapping
62+
from http import HTTPStatus
63+
from typing import cast
64+
65+
import django
66+
from django.conf import settings
67+
from django.core.management import execute_from_command_line
68+
from django.http import HttpRequest, HttpResponse
69+
from django.urls import path
70+
from issue_tracker_mcp import Scope, mcp_transport
71+
from rest_framework.authentication import BasicAuthentication
72+
from rest_framework.exceptions import AuthenticationFailed
73+
74+
from mcp.server.minimcp.types import MESSAGE_ENCODING
75+
76+
# --- Minimal Django Setup ---
77+
settings.configure(
78+
SECRET_KEY="dev",
79+
ROOT_URLCONF=__name__,
80+
ALLOWED_HOSTS=["*"],
81+
MIDDLEWARE=["django.middleware.common.CommonMiddleware"],
82+
INSTALLED_APPS=["rest_framework"],
83+
)
84+
85+
django.setup()
86+
87+
88+
class DemoAuth(BasicAuthentication):
89+
"""Basic authentication that extracts username."""
90+
91+
def authenticate_credentials(self, userid, password, request=None):
92+
return (userid, None)
93+
94+
def get_username(self, request: HttpRequest) -> str | None:
95+
try:
96+
auth_result = self.authenticate(request)
97+
if auth_result:
98+
return auth_result[0]
99+
except AuthenticationFailed:
100+
return None
101+
102+
103+
# --- MCP View ---
104+
async def mcp_view(request: HttpRequest) -> HttpResponse:
105+
"""Handle MCP requests with scope containing authenticated user."""
106+
107+
username = DemoAuth().get_username(request)
108+
if not username:
109+
return HttpResponse(b"Authentication required", status=HTTPStatus.UNAUTHORIZED)
110+
111+
# Prepare request for MCP transport
112+
body_str = request.body.decode(MESSAGE_ENCODING) if request.body else ""
113+
headers = cast(Mapping[str, str], request.headers)
114+
method = request.method or "POST"
115+
116+
# Dispatch to MCP transport
117+
scope = Scope(user_name=username)
118+
response = await mcp_transport.dispatch(method, headers=headers, body=body_str, scope=scope)
119+
120+
# Prepare response for Django
121+
content = response.content.encode(MESSAGE_ENCODING) if response.content else b""
122+
return HttpResponse(content, status=response.status_code, headers=response.headers)
123+
124+
125+
# --- Start Server ---
126+
urlpatterns = [
127+
path("mcp", mcp_view),
128+
]
129+
130+
if __name__ == "__main__":
131+
execute_from_command_line()
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env python3
2+
# pyright: basic
3+
# pyright: reportMissingImports=false
4+
5+
"""
6+
FastAPI HTTP MCP Server with Auth
7+
This example demonstrates how to create a minimal MCP server with FastAPI using HTTP transport. It shows
8+
how to use scope to pass extra information that can be accessed inside the handler functions. It also shows
9+
how to use FastAPI's dependency injection along with MiniMCP. It uses FastAPI's HTTPBasic authentication
10+
middleware to extract the user information from the request.
11+
12+
MiniMCP provides a powerful scope object mechanism, and can be used to pass any type of extra information
13+
to the handler functions.
14+
15+
How to run:
16+
# Start the server (default: http://127.0.0.1:8000)
17+
uv run --with fastapi uvicorn examples.minimcp.web_frameworks.fastapi_http_server_with_auth:app
18+
19+
Testing with basic auth (Not validated, any username/password will work):
20+
21+
# 1. Ping the MCP server
22+
curl -X POST http://127.0.0.1:8000/mcp \
23+
-u admin:admin \
24+
-H "Content-Type: application/json" \
25+
-H "Accept: application/json" \
26+
-d '{"jsonrpc": "2.0", "id": "1", "method": "ping"}'
27+
28+
# 2. List tools
29+
curl -X POST http://127.0.0.1:8000/mcp \
30+
-u admin:admin \
31+
-H "Content-Type: application/json" \
32+
-H "Accept: application/json" \
33+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'
34+
35+
# 2. Create an issue
36+
curl -X POST http://127.0.0.1:8000/mcp \
37+
-u admin:admin \
38+
-H "Content-Type: application/json" \
39+
-H "Accept: application/json" \
40+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call",
41+
"params":{"name":"create_issue","arguments":{"title":"First issue","description":"Issue description"}}}'
42+
43+
# 3. Read the issue
44+
curl -X POST http://127.0.0.1:8000/mcp \
45+
-u admin:admin \
46+
-H "Content-Type: application/json" \
47+
-H "Accept: application/json" \
48+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call",
49+
"params":{"name":"read_issue","arguments":{"issue_id":"MCP-1"}}}'
50+
51+
# 4. Delete the issue
52+
curl -X POST http://127.0.0.1:8000/mcp \
53+
-u admin:admin \
54+
-H "Content-Type: application/json" \
55+
-H "Accept: application/json" \
56+
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call",
57+
"params":{"name":"delete_issue","arguments":{"issue_id":"MCP-1"}}}'
58+
59+
"""
60+
61+
from fastapi import Depends, FastAPI, Request
62+
from fastapi.security import HTTPBasic, HTTPBasicCredentials
63+
64+
from .issue_tracker_mcp import Scope, mcp_transport
65+
66+
# --- FastAPI Application ---
67+
app = FastAPI()
68+
security = HTTPBasic()
69+
70+
71+
@app.post("/mcp")
72+
async def mcp(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
73+
scope = Scope(user_name=credentials.username)
74+
return await mcp_transport.starlette_dispatch(request, scope)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
A dummy issue tracker MCP server.
3+
It provides tools to create, read, and delete issues.
4+
"""
5+
6+
import time
7+
8+
from pydantic import BaseModel
9+
10+
from mcp.server.minimcp import HTTPTransport, MiniMCP
11+
12+
# --- Schemas ---
13+
14+
15+
class Issue(BaseModel):
16+
id: str
17+
title: str
18+
description: str
19+
owner_user_name: str
20+
created_at: int
21+
22+
23+
# MiniMCP provides a powerful scope object mechanism. Its generic and can be typed by the user. It can be used
24+
# to pass any type of extra information to the handler functions. In this example, we use it to pass the current
25+
# user name to the handler functions.
26+
class Scope(BaseModel):
27+
user_name: str
28+
29+
30+
# --- MCP ---
31+
32+
mcp = MiniMCP[Scope](
33+
name="IssueTrackerMCP",
34+
version="0.1.0",
35+
instructions="An issue tracker MCP server that provides tools to create, read and delete issues.",
36+
)
37+
mcp_transport = HTTPTransport(mcp)
38+
39+
issues: dict[str, Issue] = {}
40+
41+
42+
@mcp.tool()
43+
def create_issue(title: str, description: str) -> Issue:
44+
# Get the current user id from the scope
45+
current_user_name = mcp.context.get_scope().user_name
46+
47+
# Create a new issue
48+
id = f"MCP-{len(issues) + 1}"
49+
new_issue = Issue(
50+
id=id,
51+
title=title,
52+
description=description,
53+
owner_user_name=current_user_name,
54+
created_at=int(time.time()),
55+
)
56+
issues[id] = new_issue
57+
58+
return new_issue
59+
60+
61+
@mcp.tool()
62+
def read_issue(issue_id: str) -> Issue:
63+
if issue_id not in issues:
64+
raise ValueError(f"Issue {issue_id} not found")
65+
66+
return issues[issue_id]
67+
68+
69+
@mcp.tool()
70+
def delete_issue(issue_id: str) -> str:
71+
if issue_id not in issues:
72+
raise ValueError(f"Issue {issue_id} not found")
73+
74+
# User check - Only the owner of the issue can delete it
75+
current_user_name = mcp.context.get_scope().user_name
76+
if issues[issue_id].owner_user_name != current_user_name:
77+
raise ValueError(f"You are not the owner of issue {issue_id}")
78+
79+
# Delete the issue
80+
del issues[issue_id]
81+
82+
return "Issue deleted successfully"

0 commit comments

Comments
 (0)