feat: add direct database access for scripts (v0.2.0)

Implement persistent SQLite database feature that allows scripts to
query schedule data directly via SQL after loading XER files through MCP.

Key changes:
- Extend load_xer with db_path parameter for persistent database
- Add get_database_info tool to retrieve database connection details
- Add schema introspection with tables, columns, primary/foreign keys
- Support WAL mode for concurrent read access
- Use atomic write pattern to prevent corruption

New features:
- db_path=None: in-memory database (default, backward compatible)
- db_path="": auto-generate path from XER filename (.sqlite extension)
- db_path="/path/to/db": explicit persistent database path

Response includes complete DatabaseInfo:
- db_path: absolute path (or :memory:)
- is_persistent: boolean
- source_file: loaded XER path
- loaded_at: ISO timestamp
- schema: tables with columns, primary keys, foreign keys, row counts

Closes: User Story 1, 2, 3 from 002-direct-db-access spec
This commit is contained in:
2026-01-08 12:54:56 -05:00
parent 3e7ad39eb8
commit d6a79bf24a
11 changed files with 1064 additions and 40 deletions

View File

@@ -50,6 +50,12 @@ async def list_tools() -> list[Tool]:
"type": "string",
"description": "Project ID to select (required for multi-project files)",
},
"db_path": {
"type": "string",
"description": "Path for persistent SQLite database file. "
"If omitted, uses in-memory database. "
"If empty string, auto-generates path from XER filename (same directory, .sqlite extension).",
},
},
"required": ["file_path"],
},
@@ -183,6 +189,15 @@ async def list_tools() -> list[Tool]:
"properties": {},
},
),
Tool(
name="get_database_info",
description="Get information about the currently loaded database including file path and schema. "
"Use this to get connection details for direct SQL access.",
inputSchema={
"type": "object",
"properties": {},
},
),
]
@@ -197,6 +212,7 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
result = await load_xer(
file_path=arguments["file_path"],
project_id=arguments.get("project_id"),
db_path=arguments.get("db_path"),
)
return [TextContent(type="text", text=json.dumps(result, indent=2))]
@@ -258,6 +274,12 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
result = await get_critical_path()
return [TextContent(type="text", text=json.dumps(result, indent=2))]
if name == "get_database_info":
from xer_mcp.tools.get_database_info import get_database_info
result = await get_database_info()
return [TextContent(type="text", text=json.dumps(result, indent=2))]
raise ValueError(f"Unknown tool: {name}")