diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-06-27 01:09:00 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-06-27 01:09:00 +0900 |
| commit | fce9dc1b2940ee6699d2acf0c9e56456d9899581 (patch) | |
| tree | 6bb0cfd1b220b7a37f77d55348803dd46ea30e45 /src/ollama_web/routes | |
| parent | 3a9e8e18df4c596360da54000f65e1bebc6039c2 (diff) | |
MCP server機能をセキュア化
Diffstat (limited to 'src/ollama_web/routes')
| -rw-r--r-- | src/ollama_web/routes/chat.py | 7 | ||||
| -rw-r--r-- | src/ollama_web/routes/mcp.py | 38 |
2 files changed, 36 insertions, 9 deletions
diff --git a/src/ollama_web/routes/chat.py b/src/ollama_web/routes/chat.py index 75aabd7..2d855b1 100644 --- a/src/ollama_web/routes/chat.py +++ b/src/ollama_web/routes/chat.py @@ -90,7 +90,10 @@ async def _build_registry(session_id: str | None) -> ToolRegistry: name = fn.get("name") if not name: continue - reg.register_mcp_tool(name, tool_def, executor) + try: + reg.register_mcp_tool(name, tool_def, executor) + except ValueError as exc: + logger.warning("skipping duplicate MCP tool %s: %s", name, exc) return reg @@ -330,4 +333,4 @@ async def chat(request: Request) -> StreamingResponse: async def _single_event(data: str) -> AsyncIterator[bytes]: """Yield a single SSE event and finish.""" - yield f"data: {data}\n\n".encode()
\ No newline at end of file + yield f"data: {data}\n\n".encode() diff --git a/src/ollama_web/routes/mcp.py b/src/ollama_web/routes/mcp.py index 85d4bb3..ee8ba65 100644 --- a/src/ollama_web/routes/mcp.py +++ b/src/ollama_web/routes/mcp.py @@ -8,7 +8,13 @@ from typing import Any from starlette.requests import Request from starlette.responses import JSONResponse -from ..mcp import load_mcp_config, save_mcp_config +from ..mcp import ( + load_mcp_config, + redact_secrets, + save_mcp_config, + validate_mcp_server_config, + validate_mcp_server_name, +) logger = logging.getLogger("ollama_web.mcp_route") @@ -21,7 +27,7 @@ async def get_servers(request: Request) -> JSONResponse: """GET /api/mcp/servers: return the current MCP server configuration.""" data_dir = request.app.state.settings.data_dir config = load_mcp_config(data_dir) - return JSONResponse(config) + return JSONResponse(redact_secrets(config)) async def put_servers(request: Request) -> JSONResponse: @@ -51,7 +57,14 @@ async def put_servers(request: Request) -> JSONResponse: cleaned: dict[str, Any] = {"mcpServers": {}} errors: list[str] = [] - for name, server in raw_servers.items(): + data_dir = request.app.state.settings.data_dir + for raw_name, server in raw_servers.items(): + try: + name = validate_mcp_server_name(raw_name) + except ValueError as exc: + errors.append(f"Server '{raw_name}' invalid name: {exc}") + continue + if not isinstance(server, dict): errors.append(f"Server '{name}' must be an object") continue @@ -59,11 +72,23 @@ async def put_servers(request: Request) -> JSONResponse: if "command" in server: valid = _allowed_fields(server, _ALLOWED_STDIO_FIELDS, name, errors) if valid: - cleaned["mcpServers"][name] = _clean_stdio_server(server) + cleaned_server = _clean_stdio_server(server) + try: + validate_mcp_server_config(name, cleaned_server, data_dir) + except ValueError as exc: + errors.append(f"Server '{name}' invalid stdio config: {exc}") + continue + cleaned["mcpServers"][name] = cleaned_server elif "url" in server: valid = _allowed_fields(server, _ALLOWED_HTTP_FIELDS, name, errors) if valid: - cleaned["mcpServers"][name] = _clean_http_server(server) + cleaned_server = _clean_http_server(server) + try: + validate_mcp_server_config(name, cleaned_server, data_dir) + except ValueError as exc: + errors.append(f"Server '{name}' invalid http config: {exc}") + continue + cleaned["mcpServers"][name] = cleaned_server else: errors.append( f"Server '{name}' must define either 'command' (stdio) or 'url' (http)" @@ -72,7 +97,6 @@ async def put_servers(request: Request) -> JSONResponse: if errors: return JSONResponse({"error": errors}, status_code=400) - data_dir = request.app.state.settings.data_dir save_mcp_config(data_dir, cleaned) return JSONResponse(cleaned) @@ -116,4 +140,4 @@ def _clean_http_server(server: dict[str, Any]) -> dict[str, Any]: pass if "terminate_on_close" in server: out["terminate_on_close"] = bool(server["terminate_on_close"]) - return out
\ No newline at end of file + return out |
