-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
172 lines (153 loc) · 4.93 KB
/
server.py
File metadata and controls
172 lines (153 loc) · 4.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/usr/bin/env uv run
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "mcp>=1.26.0",
# "qrcode[pil]>=8.0",
# "uvicorn>=0.34.0",
# "starlette>=0.46.0",
# ]
# ///
"""
QR Code MCP Server - Generates QR codes from text
"""
import os
import sys
import io
import base64
import qrcode
import uvicorn
from mcp.server.fastmcp import FastMCP
from mcp import types
from starlette.middleware.cors import CORSMiddleware
VIEW_URI = "ui://qr-server/view.html"
HOST = os.environ.get("HOST", "0.0.0.0") # 0.0.0.0 for Docker compatibility
PORT = int(os.environ.get("PORT", "3001"))
mcp = FastMCP("QR Code Server", stateless_http=True)
# Embedded View HTML for self-contained usage (uv run <url> or unbundled)
EMBEDDED_VIEW_HTML = """<!DOCTYPE html>
<html>
<head>
<meta name="color-scheme" content="light dark">
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
background: transparent;
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 340px;
width: 340px;
}
img {
width: 300px;
height: 300px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<div id="qr"></div>
<script type="module">
import { App } from "https://unpkg.com/@modelcontextprotocol/ext-apps@0.4.0/app-with-deps";
const app = new App({ name: "QR View", version: "1.0.0" });
app.ontoolresult = ({ content }) => {
const img = content?.find(c => c.type === 'image');
if (img) {
const qrDiv = document.getElementById('qr');
qrDiv.innerHTML = '';
const allowedTypes = ['image/png', 'image/jpeg', 'image/gif'];
const mimeType = allowedTypes.includes(img.mimeType) ? img.mimeType : 'image/png';
const image = document.createElement('img');
image.src = `data:${mimeType};base64,${img.data}`;
image.alt = "QR Code";
qrDiv.appendChild(image);
}
};
function handleHostContextChanged(ctx) {
if (ctx.safeAreaInsets) {
document.body.style.paddingTop = `${ctx.safeAreaInsets.top}px`;
document.body.style.paddingRight = `${ctx.safeAreaInsets.right}px`;
document.body.style.paddingBottom = `${ctx.safeAreaInsets.bottom}px`;
document.body.style.paddingLeft = `${ctx.safeAreaInsets.left}px`;
}
}
app.onhostcontextchanged = handleHostContextChanged;
await app.connect();
const ctx = app.getHostContext();
if (ctx) {
handleHostContextChanged(ctx);
}
</script>
</body>
</html>"""
@mcp.tool(meta={
"ui":{"resourceUri": VIEW_URI},
"ui/resourceUri": VIEW_URI, # legacy support
})
def generate_qr(
text: str = "https://modelcontextprotocol.io",
box_size: int = 10,
border: int = 4,
error_correction: str = "M",
fill_color: str = "black",
back_color: str = "white",
) -> list[types.ImageContent]:
"""Generate a QR code from text.
Args:
text: The text/URL to encode
box_size: Size of each box in pixels (default: 10)
border: Border size in boxes (default: 4)
error_correction: Error correction level - L(7%), M(15%), Q(25%), H(30%)
fill_color: Foreground color (hex like #FF0000 or name like red)
back_color: Background color (hex like #FFFFFF or name like white)
"""
error_levels = {
"L": qrcode.constants.ERROR_CORRECT_L,
"M": qrcode.constants.ERROR_CORRECT_M,
"Q": qrcode.constants.ERROR_CORRECT_Q,
"H": qrcode.constants.ERROR_CORRECT_H,
}
qr = qrcode.QRCode(
version=1,
error_correction=error_levels.get(error_correction.upper(), qrcode.constants.ERROR_CORRECT_M),
box_size=box_size,
border=border,
)
qr.add_data(text)
qr.make(fit=True)
img = qr.make_image(fill_color=fill_color, back_color=back_color)
buffer = io.BytesIO()
img.save(buffer, format="PNG")
b64 = base64.b64encode(buffer.getvalue()).decode()
return [types.ImageContent(type="image", data=b64, mimeType="image/png")]
# IMPORTANT: all the external domains used by app must be listed
# in the meta.ui.csp.resourceDomains - otherwise they will be blocked by CSP policy
@mcp.resource(
VIEW_URI,
mime_type="text/html;profile=mcp-app",
meta={"ui": {"csp": {"resourceDomains": ["https://unpkg.com"]}}},
)
def view() -> str:
"""View HTML resource with CSP metadata for external dependencies."""
return EMBEDDED_VIEW_HTML
if __name__ == "__main__":
if "--stdio" in sys.argv:
# Claude Desktop mode
mcp.run(transport="stdio")
else:
# HTTP mode for basic-host (default) - with CORS
app = mcp.streamable_http_app()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
print(f"QR Code Server listening on http://{HOST}:{PORT}/mcp")
uvicorn.run(app, host=HOST, port=PORT)