frdel commited on
Commit
21051cf
·
1 Parent(s): 15728b4

mcp server auth token

Browse files
python/helpers/settings.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import json
2
  import os
3
  import re
@@ -67,6 +69,7 @@ class Settings(TypedDict):
67
  mcp_client_init_timeout: int
68
  mcp_client_tool_timeout: int
69
  mcp_server_enabled: bool
 
70
 
71
 
72
  class PartialSettings(Settings, total=False):
@@ -748,6 +751,17 @@ def convert_out(settings: Settings) -> SettingsOutput:
748
  }
749
  )
750
 
 
 
 
 
 
 
 
 
 
 
 
751
  mcp_server_section: SettingsSection = {
752
  "id": "mcp_server",
753
  "title": "A0 MCP Server",
@@ -845,6 +859,9 @@ def normalize_settings(settings: Settings) -> Settings:
845
  except (ValueError, TypeError):
846
  copy[key] = value # make default instead
847
 
 
 
 
848
  return copy
849
 
850
 
@@ -939,6 +956,7 @@ def get_default_settings() -> Settings:
939
  mcp_client_init_timeout=5,
940
  mcp_client_tool_timeout=120,
941
  mcp_server_enabled=False,
 
942
  )
943
 
944
 
@@ -1075,3 +1093,15 @@ def get_runtime_config(set: Settings):
1075
  "code_exec_http_port": set["rfc_port_http"],
1076
  "code_exec_ssh_user": "root",
1077
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import hashlib
3
  import json
4
  import os
5
  import re
 
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):
 
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",
 
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
 
 
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
 
 
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 CHANGED
@@ -240,19 +240,46 @@ def run():
240
 
241
 
242
  # define a Starlette-compatible middleware handler
 
 
243
 
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
249
  PrintStyle.error("[MCP] Access denied: MCP server is disabled in settings.")
250
- raise StarletteHTTPException(status_code=403, detail="MCP server is disabled in settings.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  return await call_next(request)
252
 
253
- mcp_middlewares = [
254
- Middleware(BaseHTTPMiddleware, dispatch=mcp_middleware)
255
- ]
256
 
257
  mcp_app = create_sse_app(
258
  server=mcp_server_instance,
@@ -269,7 +296,7 @@ def run():
269
  app = DispatcherMiddleware(webapp, {
270
  "/mcp": ASGIMiddleware(app=mcp_app), # type: ignore
271
  }) # type: ignore
272
- PrintStyle().debug("Registered middleware for MCP")
273
 
274
  try:
275
  PrintStyle().debug(f"Starting server at {host}:{port}...")
 
240
 
241
 
242
  # define a Starlette-compatible middleware handler
243
+ from starlette.requests import Request
244
+ import re
245
 
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.")
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
 
281
+
282
+ mcp_middlewares = [Middleware(BaseHTTPMiddleware, dispatch=mcp_middleware)]
 
283
 
284
  mcp_app = create_sse_app(
285
  server=mcp_server_instance,
 
296
  app = DispatcherMiddleware(webapp, {
297
  "/mcp": ASGIMiddleware(app=mcp_app), # type: ignore
298
  }) # type: ignore
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 CHANGED
@@ -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
-
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 jsonExample = JSON.stringify({
20
  "mcpServers":
21
  {
22
  "agent-zero": {
23
  "type": "sse",
24
- "serverUrl": `${url}/mcp/sse`
25
  }
26
  }
27
  }, null, 2);
 
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
+
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",
25
+ "serverUrl": `${url}/mcp/t-${token}/sse`
26
  }
27
  }
28
  }, null, 2);