Implement complete MCP server for parsing Primavera P6 XER files and exposing schedule data through MCP tools. All 4 user stories complete. Tools implemented: - load_xer: Parse XER files into SQLite database - list_activities: Query activities with pagination and filtering - get_activity: Get activity details by ID - list_relationships: Query activity dependencies - get_predecessors/get_successors: Query activity relationships - get_project_summary: Project overview with counts - list_milestones: Query milestone activities - get_critical_path: Query driving path activities Features: - Tab-delimited XER format parsing with pluggable table handlers - In-memory SQLite database for fast queries - Pagination with 100-item default limit - Multi-project file support with project selection - ISO8601 date formatting - NO_FILE_LOADED error handling for all query tools Test coverage: 81 tests (contract, integration, unit)
67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
"""Database connection management for XER MCP Server."""
|
|
|
|
import sqlite3
|
|
from collections.abc import Generator
|
|
from contextlib import contextmanager
|
|
|
|
from xer_mcp.db.schema import get_schema
|
|
|
|
|
|
class DatabaseManager:
|
|
"""Manages SQLite database connections and schema initialization."""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize database manager with in-memory database."""
|
|
self._connection: sqlite3.Connection | None = None
|
|
|
|
def initialize(self) -> None:
|
|
"""Initialize the in-memory database with schema."""
|
|
self._connection = sqlite3.connect(":memory:", check_same_thread=False)
|
|
self._connection.row_factory = sqlite3.Row
|
|
self._connection.executescript(get_schema())
|
|
|
|
def clear(self) -> None:
|
|
"""Clear all data from the database."""
|
|
if self._connection is None:
|
|
return
|
|
|
|
tables = ["relationships", "activities", "wbs", "calendars", "projects"]
|
|
for table in tables:
|
|
self._connection.execute(f"DELETE FROM {table}") # noqa: S608
|
|
self._connection.commit()
|
|
|
|
@property
|
|
def connection(self) -> sqlite3.Connection:
|
|
"""Get the database connection."""
|
|
if self._connection is None:
|
|
raise RuntimeError("Database not initialized. Call initialize() first.")
|
|
return self._connection
|
|
|
|
@contextmanager
|
|
def cursor(self) -> Generator[sqlite3.Cursor]:
|
|
"""Get a cursor for executing queries."""
|
|
cur = self.connection.cursor()
|
|
try:
|
|
yield cur
|
|
finally:
|
|
cur.close()
|
|
|
|
def commit(self) -> None:
|
|
"""Commit the current transaction."""
|
|
self.connection.commit()
|
|
|
|
def close(self) -> None:
|
|
"""Close the database connection."""
|
|
if self._connection is not None:
|
|
self._connection.close()
|
|
self._connection = None
|
|
|
|
@property
|
|
def is_initialized(self) -> bool:
|
|
"""Check if the database is initialized."""
|
|
return self._connection is not None
|
|
|
|
|
|
# Global database manager instance
|
|
db = DatabaseManager()
|