mcp server auth token
frdel committed
Jun 5, 2025 at 12:16 UTC
bb07e56f0004048349c63131f466181ca435e6fa
3 files changed
+70
-12
python/helpers/settings.py
+30
@@ -1,3 +1,5 @@
1
+import base64
2
+import hashlib
3
import json
4
import os
5
import re
@@ -67,6 +69,7 @@ class Settings(TypedDict):
69
mcp_client_init_timeout: int
70
mcp_client_tool_timeout: int
71
mcp_server_enabled: bool
72
+ mcp_server_token: str
73
74
75
class PartialSettings(Settings, total=False):
@@ -748,6 +751,17 @@ def convert_out(settings: Settings) -> SettingsOutput:
751
}
752
)
753
754
+ mcp_server_fields.append(
755
+ {
756
+ "id": "mcp_server_token",
757
+ "title": "MCP Server Token",
758
+ "description": "Token for MCP server authentication.",
759
+ "type": "text",
760
+ "hidden": True,
761
+ "value": settings["mcp_server_token"],
762
+ }
763
+ )
764
+
765
mcp_server_section: SettingsSection = {
766
"id": "mcp_server",
767
"title": "A0 MCP Server",
@@ -845,6 +859,9 @@ def normalize_settings(settings: Settings) -> Settings:
859
except (ValueError, TypeError):
860
copy[key] = value # make default instead
861
862
+ # mcp server token is set automatically
863
+ copy["mcp_server_token"] = create_token()
864
+
865
return copy
866
867
@@ -939,6 +956,7 @@ def get_default_settings() -> Settings:
956
mcp_client_init_timeout=5,
957
mcp_client_tool_timeout=120,
958
mcp_server_enabled=False,
959
+ mcp_server_token=create_token(),
960
)
961
962
@@ -1075,3 +1093,15 @@ def get_runtime_config(set: Settings):
1093
"code_exec_http_port": set["rfc_port_http"],
1094
"code_exec_ssh_user": "root",
1095
}
1096
+
1097
+
1098
+def create_token() -> str:
1099
+ username = dotenv.get_dotenv_value(dotenv.KEY_AUTH_LOGIN) or ""
1100
+ password = dotenv.get_dotenv_value(dotenv.KEY_AUTH_PASSWORD) or ""
1101
+ if not username or not password:
1102
+ return "0"
1103
+ # use base64 encoding for a more compact token with alphanumeric chars
1104
+ hash_bytes = hashlib.sha256(f"{username}:{password}".encode()).digest()
1105
+ # encode as base64 and remove any non-alphanumeric chars (like +, /, =)
1106
+ b64_token = base64.urlsafe_b64encode(hash_bytes).decode().replace('=', '')
1107
+ return b64_token[:16]
run_ui.py
+37
-10
@@ -240,19 +240,46 @@ def run():
240
241
242
# define a Starlette-compatible middleware handler
243
+ from starlette.requests import Request
244
+ import re
245
244
-
245
- async def mcp_middleware(request, call_next):
246
- set = settings.get_settings()
247
- if not set["mcp_server_enabled"]:
248
- # raise a proper Starlette HTTPException with a clear message
246
+ async def mcp_middleware(request: Request, call_next):
247
+
248
+ # check if MCP server is enabled
249
+ cfg = settings.get_settings()
250
+ if not cfg["mcp_server_enabled"]:
251
PrintStyle.error("[MCP] Access denied: MCP server is disabled in settings.")
250
- raise StarletteHTTPException(status_code=403, detail="MCP server is disabled in settings.")
252
+ raise StarletteHTTPException(status_code=403,
253
+ detail="MCP server is disabled in settings.")
254
+
255
+ # get auth token from path
256
+ full_path = request.url.path
257
+ if not full_path.startswith("/mcp/t-"):
258
+ raise StarletteHTTPException(status_code=401,
259
+ detail="Missing token.")
260
+ pattern = r'^/mcp/t-([^/]+)/(.+)$'
261
+ match = re.match(pattern, full_path)
262
+ if not match:
263
+ raise StarletteHTTPException(status_code=401,
264
+ detail="Missing token.")
265
+ token = match.group(1)
266
+ remainder = match.group(2)
267
+
268
+ # validate token
269
+ if token != cfg["mcp_server_token"]:
270
+ raise StarletteHTTPException(status_code=401,
271
+ detail="Invalid token.")
272
+
273
+ # rewrite path to standard MCP path and continue
274
+ new_path = f"/mcp/{remainder}"
275
+ request.scope["path"] = new_path
276
+ request.scope["raw_path"] = new_path.encode()
277
+ # request.state.token = token
278
+
279
return await call_next(request)
280
253
- mcp_middlewares = [
254
- Middleware(BaseHTTPMiddleware, dispatch=mcp_middleware)
255
- ]
281
+
282
+ mcp_middlewares = [Middleware(BaseHTTPMiddleware, dispatch=mcp_middleware)]
283
284
mcp_app = create_sse_app(
285
server=mcp_server_instance,
@@ -269,7 +296,7 @@ def run():
296
app = DispatcherMiddleware(webapp, {
297
"/mcp": ASGIMiddleware(app=mcp_app), # type: ignore
298
}) # type: ignore
272
- PrintStyle().debug("Registered middleware for MCP")
299
+ PrintStyle().debug("Registered middleware for MCP and MCP token")
300
301
try:
302
PrintStyle().debug(f"Starting server at {host}:{port}...")
webui/components/settings/mcp/server/example.html
+3
-2
@@ -9,19 +9,20 @@
9
<div x-data>
10
<p>Agent Zero MCP Server is an SSE MCP running on the same URL and port as the Web UI + /mcp/sse path.</p>
11
<p>The same applies if you run A0 on a public URL using a tunnel.</p>
12
-
12
+
13
<h3>Example MCP Server Configuration JSON</h3>
14
<div id="mcp-server-example"></div>
15
16
<script>
17
setTimeout(() => {
18
const url = window.location.origin;
19
+ const token = settingsModalProxy.settings.sections.filter(x => x.id == "mcp_server")[0].fields.filter(x => x.id == "mcp_server_token")[0].value;
20
const jsonExample = JSON.stringify({
21
"mcpServers":
22
{
23
"agent-zero": {
24
"type": "sse",
24
- "serverUrl": `${url}/mcp/sse`
25
+ "serverUrl": `${url}/mcp/t-${token}/sse`
26
}
27
}
28
}, null, 2);