Skip to content

Commit e53ddcf

Browse files
authored
feat: add configurable CORS support with environment variables and improved exception handling (#18)
- Add CORS middleware with configurable origins via CORS_ORIGINS environment variable - Add CORS configuration logging with security warnings for wildcard origins - Improve exception handling in lifecycle.py with more specific exception types - Update README.md with comprehensive CORS configuration documentation - Add Table of Contents to README.md for better navigation - Default to allowing all origins (*) with production security warnings in logs
1 parent 1342579 commit e53ddcf

File tree

4 files changed

+80
-9
lines changed

4 files changed

+80
-9
lines changed

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@
1515
[![Python 3.10+](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://www.python.org/downloads/)
1616
![License](https://img.shields.io/badge/License-MIT-green.svg)
1717

18+
## Table of Contents
19+
20+
- [Features](#features)
21+
- [Requirements](#requirements)
22+
- [Installation](#installation)
23+
- [Quick Start](#quick-start)
24+
- [Or, install from PyPI with pip](#or-install-from-pypi-with-pip)
25+
- [Or, install from source](#or-install-from-source)
26+
- [How It Works](#how-it-works)
27+
- [Configuration](#configuration)
28+
- [MCP Servers Configuration](#mcp-servers-configuration)
29+
- [CORS Configuration](#cors-configuration)
30+
- [Usage](#usage)
31+
- [Start the Server](#start-the-server)
32+
- [CLI Options](#cli-options)
33+
- [API Usage](#api-usage)
34+
- [Example: Chat](#example-chat)
35+
- [Development](#development)
36+
- [Key Dependencies](#key-dependencies)
37+
- [Testing](#testing)
38+
- [Inspiration and Credits](#inspiration-and-credits)
1839

1940
## Features
2041

@@ -97,6 +118,8 @@ ollama-mcp-bridge
97118

98119
## Configuration
99120

121+
### MCP Servers Configuration
122+
100123
Create an MCP configuration file at `mcp-servers-config/mcp-config.json` with your servers:
101124

102125
```json
@@ -126,6 +149,34 @@ Create an MCP configuration file at `mcp-servers-config/mcp-config.json` with yo
126149
}
127150
```
128151

152+
### CORS Configuration
153+
154+
Configure Cross-Origin Resource Sharing (CORS) to allow requests from your frontend applications:
155+
156+
```bash
157+
# Allow all origins (default, not recommended for production)
158+
ollama-mcp-bridge
159+
160+
# Allow specific origins
161+
CORS_ORIGINS="http://localhost:3000,https://myapp.com" ollama-mcp-bridge
162+
163+
# Allow multiple origins with different ports
164+
CORS_ORIGINS="http://localhost:3000,http://localhost:8080,https://app.example.com" ollama-mcp-bridge
165+
```
166+
167+
**Environment Variables:**
168+
- `CORS_ORIGINS`: Comma-separated list of allowed origins (default: `*`)
169+
- `*` allows all origins (shows warning in logs)
170+
- Specific origins like `http://localhost:3000,https://myapp.com` for production
171+
172+
**CORS Logging:**
173+
- The bridge logs CORS configuration at startup
174+
- Shows warning when using `*` (all origins)
175+
- Shows allowed origins when properly configured
176+
177+
> [!WARNING]
178+
> Using `CORS_ORIGINS="*"` allows all origins and is not recommended for production. Always specify exact origins for security.
179+
129180
> [!NOTE]
130181
> An example MCP server script is provided at `mcp-servers-config/mock-weather-mcp-server.py`.
131182

src/ollama_mcp_bridge/api.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""FastAPI application"""
2+
import os
23
from typing import Dict, Any
4+
import httpx
35
from fastapi import FastAPI, HTTPException, Body, status, Request
46
from fastapi.responses import JSONResponse
7+
from fastapi.middleware.cors import CORSMiddleware
58
from loguru import logger
6-
import httpx
79

810
from .lifecycle import lifespan, get_proxy_service
911
from .schemas import CHAT_EXAMPLE
@@ -19,6 +21,24 @@
1921
lifespan=lifespan
2022
)
2123

24+
# Configure CORS
25+
cors_origins = os.getenv("CORS_ORIGINS", "*").split(",")
26+
cors_origins = [origin.strip() for origin in cors_origins]
27+
28+
# Log CORS configuration
29+
if cors_origins == ["*"]:
30+
logger.warning("CORS is configured to allow ALL origins (*). This is not recommended for production.")
31+
else:
32+
logger.info(f"CORS configured to allow origins: {cors_origins}")
33+
34+
app.add_middleware(
35+
CORSMiddleware,
36+
allow_origins=cors_origins,
37+
allow_credentials=True,
38+
allow_methods=["*"],
39+
allow_headers=["*"],
40+
)
41+
2242

2343
@app.get("/health", summary="Health check", description="Check the health status of the MCP Proxy and Ollama server.")
2444
async def health():

src/ollama_mcp_bridge/lifecycle.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ async def lifespan(fastapi_app: FastAPI):
5656
try:
5757
if proxy_service:
5858
await proxy_service.cleanup()
59-
except (IOError, httpx.HTTPError) as e:
59+
except (IOError, httpx.HTTPError, ConnectionError, TimeoutError) as e:
6060
logger.error(f"Error during proxy service cleanup: {str(e)}")
61-
except Exception as e:
61+
except (ValueError, AttributeError, RuntimeError) as e:
6262
logger.error(f"Unexpected error during cleanup: {str(e)}")
6363

6464
try:
6565
if mcp_manager:
6666
await mcp_manager.cleanup()
67-
except Exception as e:
67+
except (IOError, ConnectionError, TimeoutError) as e:
6868
logger.error(f"Error during MCP manager cleanup: {str(e)}")
6969

7070
# Reset globals

src/ollama_mcp_bridge/utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def check_ollama_health(ollama_url: str, timeout: int = 3) -> bool:
1818
return True
1919
logger.error(f"Ollama server not accessible at {ollama_url}")
2020
return False
21-
except Exception as e:
21+
except (httpx.ConnectError, httpx.ReadTimeout, httpx.HTTPError) as e:
2222
logger.error(f"Failed to connect to Ollama: {e}")
2323
return False
2424

@@ -31,7 +31,7 @@ async def check_ollama_health_async(ollama_url: str, timeout: int = 3) -> bool:
3131
return True
3232
logger.error(f"Ollama server not accessible at {ollama_url}")
3333
return False
34-
except Exception as e:
34+
except (httpx.ConnectError, httpx.ReadTimeout, httpx.HTTPError) as e:
3535
logger.error(f"Failed to connect to Ollama: {e}")
3636
return False
3737

@@ -45,13 +45,13 @@ async def iter_ndjson_chunks(chunk_iterator):
4545
if line.strip():
4646
try:
4747
yield json.loads(line)
48-
except Exception as e:
48+
except json.JSONDecodeError as e:
4949
logger.debug(f"Error parsing NDJSON line: {e}")
5050
# Handle any trailing data
5151
if buffer.strip():
5252
try:
5353
yield json.loads(buffer)
54-
except Exception as e:
54+
except json.JSONDecodeError as e:
5555
logger.debug(f"Error parsing trailing NDJSON: {e}")
5656

5757
def validate_cli_inputs(config: str, host: str, port: int, ollama_url: str):
@@ -61,7 +61,7 @@ def validate_cli_inputs(config: str, host: str, port: int, ollama_url: str):
6161
raise BadParameter(f"Config file not found: {config}")
6262

6363
# Validate port
64-
if not (1 <= port <= 65535):
64+
if not 1 <= port <= 65535:
6565
raise BadParameter(f"Port must be between 1 and 65535, got {port}")
6666

6767
# Validate host (basic check)

0 commit comments

Comments
 (0)