Skip to content

Commit bd8068e

Browse files
committed
Add streamable-http transport support
1 parent 2db0f6d commit bd8068e

File tree

12 files changed

+865
-62
lines changed

12 files changed

+865
-62
lines changed

.env.example

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,19 @@ ODOO_API_KEY=your-api-key-here
4040

4141
# Maximum pagination limit (optional)
4242
# Maximum number of records that can be requested
43-
# ODOO_MCP_MAX_LIMIT=100
43+
# ODOO_MCP_MAX_LIMIT=100
44+
45+
# Transport Configuration
46+
# =======================
47+
48+
# Transport type (optional)
49+
# Valid values: stdio (default), streamable-http
50+
# ODOO_MCP_TRANSPORT=stdio
51+
52+
# Server host for HTTP transport (optional)
53+
# Only used when transport is streamable-http
54+
# ODOO_MCP_HOST=localhost
55+
56+
# Server port for HTTP transport (optional)
57+
# Only used when transport is streamable-http
58+
# ODOO_MCP_PORT=8000

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [0.1.2] - 2025-06-19
99

1010
### Added
1111
- **Resource Discovery**: Added `list_resource_templates` tool to provide resource URI template information
12+
- **HTTP Transport**: Added streamable-http transport support for web and remote access
1213

1314
## [0.1.1] - 2025-06-16
1415

README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,62 @@ The server requires the following environment variables:
191191
- If database listing is restricted on your server, you must specify `ODOO_DB`
192192
- API key authentication is recommended for better security
193193

194+
### Transport Options
195+
196+
The server supports multiple transport protocols for different use cases:
197+
198+
#### 1. **stdio** (Default)
199+
Standard input/output transport - used by desktop AI applications like Claude Desktop.
200+
201+
```bash
202+
# Default transport - no additional configuration needed
203+
uvx mcp-server-odoo
204+
```
205+
206+
#### 2. **streamable-http**
207+
Standard HTTP transport for REST API-style access and remote connectivity.
208+
209+
```bash
210+
# Run with HTTP transport
211+
uvx mcp-server-odoo --transport streamable-http --host 0.0.0.0 --port 8000
212+
213+
# Or use environment variables
214+
export ODOO_MCP_TRANSPORT=streamable-http
215+
export ODOO_MCP_HOST=0.0.0.0
216+
export ODOO_MCP_PORT=8000
217+
uvx mcp-server-odoo
218+
```
219+
220+
The HTTP endpoint will be available at: `http://localhost:8000/mcp/`
221+
222+
> **Note**: SSE (Server-Sent Events) transport has been deprecated in MCP protocol version 2025-03-26. Use streamable-http transport instead for HTTP-based communication. Requires MCP library v1.9.4 or higher for proper session management.
223+
224+
#### Transport Configuration
225+
226+
| Variable/Flag | Description | Default |
227+
|--------------|-------------|---------|
228+
| `ODOO_MCP_TRANSPORT` / `--transport` | Transport type: stdio, streamable-http | `stdio` |
229+
| `ODOO_MCP_HOST` / `--host` | Host to bind for HTTP transports | `localhost` |
230+
| `ODOO_MCP_PORT` / `--port` | Port to bind for HTTP transports | `8000` |
231+
232+
**Example: Running streamable-http transport for remote access**
233+
234+
```json
235+
{
236+
"mcpServers": {
237+
"odoo-remote": {
238+
"command": "uvx",
239+
"args": ["mcp-server-odoo", "--transport", "streamable-http", "--port", "8080"],
240+
"env": {
241+
"ODOO_URL": "https://your-odoo-instance.com",
242+
"ODOO_API_KEY": "your-api-key-here",
243+
"ODOO_DB": "your-database-name"
244+
}
245+
}
246+
}
247+
}
248+
```
249+
194250
### Setting up Odoo
195251

196252
1. **Install the MCP module**:
@@ -450,6 +506,34 @@ npx @modelcontextprotocol/inspector python -m mcp_server_odoo
450506
```
451507
</details>
452508

509+
## Testing
510+
511+
### Transport Tests
512+
513+
You can test both stdio and streamable-http transports to ensure they're working correctly:
514+
515+
```bash
516+
# Run comprehensive transport tests
517+
python tests/run_transport_tests.py
518+
```
519+
520+
This will test:
521+
- **stdio transport**: Basic initialization and communication
522+
- **streamable-http transport**: HTTP endpoint, session management, and tool calls
523+
524+
### Unit Tests
525+
526+
For complete testing including unit and integration tests:
527+
528+
```bash
529+
# Run all tests
530+
uv run pytest --cov
531+
532+
# Run specific test categories
533+
uv run pytest tests/test_tools.py -v
534+
uv run pytest tests/test_server_foundation.py -v
535+
```
536+
453537
## License
454538

455539
This project is licensed under the Mozilla Public License 2.0 (MPL-2.0) - see the [LICENSE](LICENSE) file for details.

mcp_server_odoo/__main__.py

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
Odoo MCP server via uvx or direct execution.
55
"""
66

7+
import argparse
78
import asyncio
89
import logging
10+
import os
911
import sys
1012
from typing import Optional
1113

@@ -19,7 +21,7 @@ def main(argv: Optional[list[str]] = None) -> int:
1921
"""Main entry point for the MCP server.
2022
2123
This function handles command-line arguments, loads configuration,
22-
and runs the MCP server with stdio transport.
24+
and runs the MCP server with the specified transport.
2325
2426
Args:
2527
argv: Command line arguments (defaults to sys.argv[1:])
@@ -30,48 +32,79 @@ def main(argv: Optional[list[str]] = None) -> int:
3032
# Load environment variables from .env file
3133
load_dotenv()
3234

33-
# Parse command line arguments
34-
if argv is None:
35-
argv = sys.argv[1:]
36-
37-
# Handle help flag
38-
if "--help" in argv or "-h" in argv:
39-
print("Odoo MCP Server - Model Context Protocol server for Odoo ERP", file=sys.stderr)
40-
print("\nUsage: mcp-server-odoo [options]", file=sys.stderr)
41-
print("\nOptions:", file=sys.stderr)
42-
print(" -h, --help Show this help message and exit", file=sys.stderr)
43-
print(" --version Show version information", file=sys.stderr)
44-
print("\nEnvironment variables:", file=sys.stderr)
45-
print(" ODOO_URL Odoo server URL (required)", file=sys.stderr)
46-
print(" ODOO_API_KEY Odoo API key (preferred authentication)", file=sys.stderr)
47-
print(" ODOO_USER Odoo username (fallback if no API key)", file=sys.stderr)
48-
print(" ODOO_PASSWORD Odoo password (required with username)", file=sys.stderr)
49-
print(" ODOO_DB Odoo database name (auto-detected if not set)", file=sys.stderr)
50-
print("\nOptional environment variables:", file=sys.stderr)
51-
print(" ODOO_MCP_LOG_LEVEL Log level (DEBUG, INFO, WARNING, ERROR)", file=sys.stderr)
52-
print(" ODOO_MCP_DEFAULT_LIMIT Default record limit (default: 10)", file=sys.stderr)
53-
print(" ODOO_MCP_MAX_LIMIT Maximum record limit (default: 100)", file=sys.stderr)
54-
print(
55-
"\nFor more information, visit: https://github.com/ivnvxd/mcp-server-odoo",
56-
file=sys.stderr,
57-
)
58-
return 0
59-
60-
# Handle version flag
61-
if "--version" in argv:
62-
print(f"odoo-mcp-server v{SERVER_VERSION}", file=sys.stderr)
63-
return 0
35+
# Create argument parser
36+
parser = argparse.ArgumentParser(
37+
description="Odoo MCP Server - Model Context Protocol server for Odoo ERP",
38+
formatter_class=argparse.RawDescriptionHelpFormatter,
39+
epilog="""Environment variables:
40+
ODOO_URL Odoo server URL (required)
41+
ODOO_API_KEY Odoo API key (preferred authentication)
42+
ODOO_USER Odoo username (fallback if no API key)
43+
ODOO_PASSWORD Odoo password (required with username)
44+
ODOO_DB Odoo database name (auto-detected if not set)
45+
46+
Optional environment variables:
47+
ODOO_MCP_LOG_LEVEL Log level (DEBUG, INFO, WARNING, ERROR)
48+
ODOO_MCP_DEFAULT_LIMIT Default record limit (default: 10)
49+
ODOO_MCP_MAX_LIMIT Maximum record limit (default: 100)
50+
ODOO_MCP_TRANSPORT Transport type: stdio or streamable-http (default: stdio)
51+
ODOO_MCP_HOST Server host for HTTP transports (default: localhost)
52+
ODOO_MCP_PORT Server port for HTTP transports (default: 8000)
53+
54+
For more information, visit: https://github.com/ivnvxd/mcp-server-odoo""",
55+
)
56+
57+
parser.add_argument(
58+
"--version",
59+
action="version",
60+
version=f"odoo-mcp-server v{SERVER_VERSION}",
61+
)
62+
63+
parser.add_argument(
64+
"--transport",
65+
choices=["stdio", "streamable-http"],
66+
default=os.getenv("ODOO_MCP_TRANSPORT", "stdio"),
67+
help="Transport type to use (default: stdio)",
68+
)
69+
70+
parser.add_argument(
71+
"--host",
72+
default=os.getenv("ODOO_MCP_HOST", "localhost"),
73+
help="Server host for HTTP transports (default: localhost)",
74+
)
75+
76+
parser.add_argument(
77+
"--port",
78+
type=int,
79+
default=int(os.getenv("ODOO_MCP_PORT", "8000")),
80+
help="Server port for HTTP transports (default: 8000)",
81+
)
82+
83+
# Parse arguments
84+
args = parser.parse_args(argv)
6485

6586
try:
87+
# Override environment variables with CLI arguments
88+
if args.transport:
89+
os.environ["ODOO_MCP_TRANSPORT"] = args.transport
90+
if args.host:
91+
os.environ["ODOO_MCP_HOST"] = args.host
92+
if args.port:
93+
os.environ["ODOO_MCP_PORT"] = str(args.port)
94+
6695
# Load configuration from environment
6796
config = load_config()
6897

6998
# Create server instance
7099
server = OdooMCPServer(config)
71100

72-
# Run the server with stdio transport
73-
# This is the standard way to run MCP servers with uvx
74-
asyncio.run(server.run_stdio())
101+
# Run the server with the specified transport
102+
if config.transport == "stdio":
103+
asyncio.run(server.run_stdio())
104+
elif config.transport == "streamable-http":
105+
asyncio.run(server.run_http(host=config.host, port=config.port))
106+
else:
107+
raise ValueError(f"Unsupported transport: {config.transport}")
75108

76109
return 0
77110

mcp_server_odoo/config.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import os
88
from dataclasses import dataclass
99
from pathlib import Path
10-
from typing import Optional
10+
from typing import Literal, Optional
1111

1212
from dotenv import load_dotenv
1313

@@ -30,6 +30,11 @@ class OdooConfig:
3030
default_limit: int = 10
3131
max_limit: int = 100
3232

33+
# MCP transport configuration
34+
transport: Literal["stdio", "streamable-http"] = "stdio"
35+
host: str = "localhost"
36+
port: int = 8000
37+
3338
def __post_init__(self):
3439
"""Validate configuration after initialization."""
3540
# Validate URL
@@ -68,6 +73,18 @@ def __post_init__(self):
6873
f"Must be one of: {', '.join(valid_log_levels)}"
6974
)
7075

76+
# Validate transport
77+
valid_transports = {"stdio", "streamable-http"}
78+
if self.transport not in valid_transports:
79+
raise ValueError(
80+
f"Invalid transport: {self.transport}. "
81+
f"Must be one of: {', '.join(valid_transports)}"
82+
)
83+
84+
# Validate port
85+
if self.port <= 0 or self.port > 65535:
86+
raise ValueError("Port must be between 1 and 65535")
87+
7188
@property
7289
def uses_api_key(self) -> bool:
7390
"""Check if configuration uses API key authentication."""
@@ -144,6 +161,9 @@ def get_int_env(key: str, default: int) -> int:
144161
log_level=os.getenv("ODOO_MCP_LOG_LEVEL", "INFO").strip(),
145162
default_limit=get_int_env("ODOO_MCP_DEFAULT_LIMIT", 10),
146163
max_limit=get_int_env("ODOO_MCP_MAX_LIMIT", 100),
164+
transport=os.getenv("ODOO_MCP_TRANSPORT", "stdio").strip(),
165+
host=os.getenv("ODOO_MCP_HOST", "localhost").strip(),
166+
port=get_int_env("ODOO_MCP_PORT", 8000),
147167
)
148168

149169
return config

mcp_server_odoo/server.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ def __init__(self, config: Optional[OdooConfig] = None):
5959
# Create FastMCP instance with server metadata
6060
self.app = FastMCP(
6161
name="odoo-mcp-server",
62-
version=SERVER_VERSION,
6362
instructions="MCP server for accessing and managing Odoo ERP data through the Model Context Protocol",
6463
)
6564

@@ -182,6 +181,46 @@ def run_stdio_sync(self):
182181

183182
asyncio.run(self.run_stdio())
184183

184+
# SSE transport has been deprecated in MCP protocol version 2025-03-26
185+
# Use streamable-http transport instead
186+
187+
async def run_http(self, host: str = "localhost", port: int = 8000):
188+
"""Run the server using streamable HTTP transport.
189+
190+
Args:
191+
host: Host to bind to
192+
port: Port to bind to
193+
"""
194+
try:
195+
# Establish connection before starting server
196+
with perf_logger.track_operation("server_startup"):
197+
self._ensure_connection()
198+
199+
# Register resources after connection is established
200+
self._register_resources()
201+
self._register_tools()
202+
203+
logger.info(f"Starting MCP server with HTTP transport on {host}:{port}...")
204+
205+
# Update FastMCP settings for host and port
206+
self.app.settings.host = host
207+
self.app.settings.port = port
208+
209+
# Use the specific streamable HTTP async method
210+
await self.app.run_streamable_http_async()
211+
212+
except KeyboardInterrupt:
213+
logger.info("Server interrupted by user")
214+
except (OdooConnectionError, ConfigurationError):
215+
# Let these specific errors propagate
216+
raise
217+
except Exception as e:
218+
context = ErrorContext(operation="server_run_http")
219+
error_handler.handle_error(e, context=context)
220+
finally:
221+
# Always cleanup connection
222+
self._cleanup_connection()
223+
185224
def get_capabilities(self) -> Dict[str, Dict[str, bool]]:
186225
"""Get server capabilities.
187226

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ classifiers = [
3030
"Topic :: Scientific/Engineering :: Artificial Intelligence",
3131
]
3232
dependencies = [
33-
"mcp>=1.9.2",
33+
"mcp>=1.9.4",
3434
"httpx>=0.27.0",
3535
"python-dotenv>=1.0.0",
3636
"pydantic>=2.0.0",

0 commit comments

Comments
 (0)