Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The pre-release phase focuses on:
### Quality & Testing
- [x] Core test suite (80%+ coverage)
- [x] Type hints on all public APIs
- [ ] Integration tests with real ORFS flows
- [x] Integration tests with real ORFS flows
- [ ] Performance benchmarking suite
- [ ] Load testing (50+ concurrent sessions)
- [ ] Memory leak detection
Expand Down Expand Up @@ -192,7 +192,7 @@ We welcome community involvement at every stage:

---

**Last Updated:** 2025-12-08
**Last Updated:** 2026-02-23
**Current Phase:** Phase 1 (Pre-Release → v0.5)

*This roadmap is a living document and will evolve based on community feedback and priorities.*
8 changes: 5 additions & 3 deletions src/openroad_mcp/core/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ def safe_decode(data: bytes, encoding: str = "utf-8", errors: str = "replace") -
"""Safely decode bytes to string with error handling for unicode issues."""
return data.decode(encoding, errors=errors)

def __new__(cls) -> "OpenROADManager":
def __new__(cls, **_kwargs: object) -> "OpenROADManager":
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def __init__(self) -> None:
def __init__(self, max_sessions: int | None = None) -> None:
if not hasattr(self, "initialized"):
self.initialized = True
self.logger = get_logger("manager")

self._sessions: dict[str, InteractiveSession | None] = {}
self._max_sessions = settings.MAX_SESSIONS
self._max_sessions = max_sessions if max_sessions is not None else settings.MAX_SESSIONS
self._default_timeout_ms = int(settings.COMMAND_TIMEOUT * 1000)
self._default_buffer_size = settings.DEFAULT_BUFFER_SIZE
self._cleanup_lock = asyncio.Lock()
Expand Down Expand Up @@ -70,6 +70,8 @@ async def create_session(
actual_buffer_size = buffer_size or self._default_buffer_size
session = InteractiveSession(session_id, buffer_size=actual_buffer_size)
await session.start(command, env, cwd)
await asyncio.sleep(settings.COMMAND_COMPLETION_DELAY * 1.5)
await session.output_buffer.drain_all()
Comment thread
luarss marked this conversation as resolved.

self._sessions[session_id] = session
self.logger.info(f"Created session {session_id}, total sessions: {len(self._sessions)}")
Expand Down
40 changes: 24 additions & 16 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
"""Pytest configuration for integration tests without mocks."""

import asyncio
import os
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager

import pytest
import pytest_asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client


@pytest.fixture(scope="session")
def event_loop():
"""Create an event loop for the test session."""
loop = asyncio.new_event_loop()
yield loop
loop.close()


@pytest_asyncio.fixture(scope="function")
async def mcp_client() -> AsyncGenerator[ClientSession]:
"""Fixture providing a MCP client session."""
server_params = StdioServerParameters(command="python", args=["-m", "openroad_mcp.main"], env=None)

@asynccontextmanager
async def _mcp_session(server_params: StdioServerParameters):
"""Shared async context manager for MCP client sessions with cancel-scope guard."""
try:
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
await asyncio.sleep(1.0)
yield session
except RuntimeError as e:
if "cancel scope" in str(e):
# Skip teardown exception (openroad-mcp handles their own teardown)
# anyio emits a RuntimeError on cancel-scope teardown when the MCP
# server subprocess exits
if "cancel scope" in str(e).lower():
pass
else:
raise
Comment thread
coderabbitai[bot] marked this conversation as resolved.


@pytest_asyncio.fixture(scope="function")
async def mcp_client() -> AsyncGenerator[ClientSession]:
"""Fixture providing a MCP client session."""
server_params = StdioServerParameters(
command="python",
args=["-m", "openroad_mcp.main"],
env={
**os.environ,
"OPENROAD_ENABLE_COMMAND_VALIDATION": "false",
},
)

async with _mcp_session(server_params) as session:
yield session
Loading
Loading