XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Add PA file handling and secure serving in MCPServer; update FileWriteTool to include file URL

b09317fe
hlohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

3 个文件 +88 -8
Modified g4f/mcp/pa_provider.py +9 -6
@@ -188,7 +188,12 @@ def _exec_in_thread(
188 188 prev = sys.getrecursionlimit()
189 189 sys.setrecursionlimit(max_depth)
190 190 try:
191 exec(compiled, safe_globals, local_vars) # noqa: S102
191 # Use safe_globals as both globals and locals so that top-level
192 # imports (e.g. ``from aiohttp import ClientSession``) are stored in
193 # the same namespace that class method ``__globals__`` points to.
194 # Otherwise imports land only in the locals dict and are invisible
195 # to class methods (NameError at call time).
196 exec(compiled, safe_globals, safe_globals) # noqa: S102
192 197 except Exception: # noqa: BLE001
193 198 exc_box.append(traceback.format_exc())
194 199 finally:
@@ -349,8 +354,6 @@ def execute_safe_code(
349 354 if extra_globals:
350 355 safe_globals.update(extra_globals)
351 356
352 local_vars: Dict[str, Any] = {}
353
354 357 # Compile outside the thread so SyntaxErrors surface immediately.
355 358 try:
356 359 compiled = compile(code, "<pa_provider>", "exec")
@@ -369,7 +372,7 @@ def execute_safe_code(
369 372 exc_box: List = []
370 373 thread = threading.Thread(
371 374 target=_exec_in_thread,
372 args=(compiled, safe_globals, local_vars, max_depth, exc_box),
375 args=(compiled, safe_globals, safe_globals, max_depth, exc_box),
373 376 daemon=True,
374 377 name="g4f-sandbox",
375 378 )
@@ -410,8 +413,8 @@ def execute_safe_code(
410 413 success=True,
411 414 stdout=stdout,
412 415 stderr=stderr,
413 result=local_vars.get("result"),
414 locals=local_vars,
416 result=safe_globals.get("result"),
417 locals=safe_globals,
415 418 )
416 419
417 420
Modified g4f/mcp/server.py +74 -1
@@ -392,6 +392,75 @@ class MCPServer:
392 392 sys.stderr.write(f"Synthesize error: {e}\n")
393 393 return web.Response(status=500, text=f"Synthesize error: {str(e)}")
394 394
395 _WORKSPACE_SAFE_TYPES: Dict[str, str] = {
396 "html": "text/html; charset=utf-8",
397 "htm": "text/html; charset=utf-8",
398 "css": "text/css; charset=utf-8",
399 "js": "application/javascript; charset=utf-8",
400 "mjs": "application/javascript; charset=utf-8",
401 "json": "application/json; charset=utf-8",
402 "txt": "text/plain; charset=utf-8",
403 "md": "text/markdown; charset=utf-8",
404 "svg": "image/svg+xml",
405 "png": "image/png",
406 "jpg": "image/jpeg",
407 "jpeg": "image/jpeg",
408 "gif": "image/gif",
409 "webp": "image/webp",
410 "ico": "image/x-icon",
411 "woff": "font/woff",
412 "woff2": "font/woff2",
413 "ttf": "font/ttf",
414 "otf": "font/otf",
415 }
416
417 async def handle_pa_providers(request: web.Request) -> web.Response:
418 """List all PA providers from workspace."""
419 from .pa_provider import get_pa_registry
420 providers = get_pa_registry().list_providers()
421 return web.json_response(providers, headers={"access-control-allow-origin": "*"})
422
423 async def handle_pa_file(request: web.Request) -> web.Response:
424 """Securely serve a workspace file for browser rendering.
425
426 Only files within ``~/.g4f/workspace`` are served. Path traversal
427 is blocked. Only the MIME types in ``_WORKSPACE_SAFE_TYPES`` are
428 allowed — ``.py``, ``.env``, and other sensitive types return 403.
429 HTML files are served with a ``Content-Security-Policy: sandbox``
430 header so they run in an isolated null origin.
431 """
432 from .pa_provider import get_workspace_dir
433 workspace = get_workspace_dir()
434
435 file_path = request.match_info.get("file_path", "")
436 try:
437 resolved = (workspace / file_path).resolve()
438 resolved.relative_to(workspace.resolve())
439 except (ValueError, Exception):
440 return web.Response(status=403, text="Path traversal is not allowed")
441
442 if not resolved.exists() or not resolved.is_file():
443 return web.Response(status=404, text=f"File not found: {file_path}")
444
445 ext = resolved.suffix.lstrip(".").lower()
446 mime = _WORKSPACE_SAFE_TYPES.get(ext)
447 if mime is None:
448 return web.Response(status=403, text=f"File type not allowed: .{ext}")
449
450 content = resolved.read_bytes()
451 headers: Dict[str, str] = {"access-control-allow-origin": "*"}
452 if ext in ("html", "htm"):
453 req_origin = f"{request.scheme}://{request.host}"
454 headers["content-security-policy"] = (
455 f"sandbox allow-scripts allow-forms allow-popups; "
456 f"default-src {req_origin}; "
457 f"img-src {req_origin} data: blob:; "
458 f"font-src {req_origin}; "
459 f"style-src {req_origin} 'unsafe-inline'; "
460 f"script-src {req_origin} 'unsafe-inline'"
461 )
462 return web.Response(body=content, content_type=mime.split(";")[0].strip(), headers=headers)
463
395 464 # Create aiohttp application
396 465 app = web.Application()
397 466 app.router.add_options('/mcp', lambda request: web.Response(headers={"access-control-allow-origin": "*", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-headers": "Content-Type"}))
@@ -399,13 +468,17 @@ class MCPServer:
399 468 app.router.add_get('/health', handle_health)
400 469 app.router.add_get('/media/{filename:.*}', handle_media)
401 470 app.router.add_get('/backend-api/v2/synthesize/{provider}', handle_synthesize)
402
471 app.router.add_get('/pa/providers', handle_pa_providers)
472 app.router.add_get('/pa/files/{file_path:.*}', handle_pa_file)
473
403 474 # Start server
404 475 sys.stderr.write(f"Starting {self.server_info['name']} v{self.server_info['version']} (HTTP mode)\n")
405 476 sys.stderr.write(f"Listening on http://{host}:{port}\n")
406 477 sys.stderr.write(f"MCP endpoint: http://{host}:{port}/mcp\n")
407 478 sys.stderr.write(f"Health check: http://{host}:{port}/health\n")
408 479 sys.stderr.write(f"Media files: http://{host}:{port}/media/{{filename}}\n")
480 sys.stderr.write(f"PA providers: http://{host}:{port}/pa/providers\n")
481 sys.stderr.write(f"PA files: http://{host}:{port}/pa/files/{{path}}\n")
409 482 sys.stderr.flush()
410 483
411 484 runner = web.AppRunner(app)
Modified g4f/mcp/tools.py +5 -1
@@ -682,11 +682,15 @@ class FileWriteTool(MCPTool):
682 682 f.write(content)
683 683 else:
684 684 target.write_text(content, encoding="utf-8")
685 return {
685 result: Dict[str, Any] = {
686 686 "path": rel_path,
687 687 "size": len(content),
688 688 "appended": append,
689 689 }
690 origin = arguments.get("origin")
691 if origin:
692 result["url"] = f"{origin}/pa/files/{rel_path}"
693 return result
690 694 except Exception as exc:
691 695 return {"error": f"Write failed: {exc}"}
692 696