Files
nextcloud-mcp-server/scripts/sqlitequery.py
T
Chris Coutinho 7b75304c9f feat(scripts): add database query helpers for development
Add dbquery.py for MariaDB and sqlitequery.py for SQLite databases
in MCP service containers. Both scripts wrap docker compose exec to
simplify database inspection during development.

- dbquery.py: Query Nextcloud MariaDB with vertical/JSON output
- sqlitequery.py: Query MCP service SQLite DBs with service aliases
  (mcp, oauth, keycloak, basic) and column/JSON output modes
- Document both scripts in CLAUDE.md Database Inspection section

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 09:26:30 +01:00

178 lines
4.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
SQLite database query helper for MCP service development.
Wraps `docker compose exec <service> sqlite3` to execute SQL statements
against the token storage database in any MCP service container.
Usage:
./scripts/sqlitequery.py ".tables"
./scripts/sqlitequery.py -s oauth "SELECT * FROM refresh_tokens"
./scripts/sqlitequery.py -s keycloak --headers "SELECT * FROM oauth_clients"
./scripts/sqlitequery.py --json "SELECT * FROM audit_logs LIMIT 5"
"""
import argparse
import subprocess
import sys
from pathlib import Path
# Service name aliases for convenience
SERVICE_ALIASES = {
"mcp": "mcp",
"oauth": "mcp-oauth",
"mcp-oauth": "mcp-oauth",
"keycloak": "mcp-keycloak",
"mcp-keycloak": "mcp-keycloak",
"basic": "mcp-multi-user-basic",
"multi-user-basic": "mcp-multi-user-basic",
"mcp-multi-user-basic": "mcp-multi-user-basic",
}
def find_compose_dir() -> Path:
"""Find the directory containing docker-compose.yml."""
current = Path(__file__).resolve().parent
while current != current.parent:
if (current / "docker-compose.yml").exists():
return current
if (current / "compose.yml").exists():
return current
current = current.parent
# Default to script's parent directory
return Path(__file__).resolve().parent.parent
def resolve_service(service: str) -> str:
"""Resolve service alias to container name."""
resolved = SERVICE_ALIASES.get(service.lower())
if resolved is None:
# Not a known alias, use as-is (might be a custom service)
return service
return resolved
def run_query(
sql: str,
service: str = "mcp",
database: str = "/app/data/tokens.db",
headers: bool = False,
json_output: bool = False,
column_mode: bool = False,
) -> tuple[int, str, str]:
"""
Execute SQL via docker compose exec.
Returns:
Tuple of (return_code, stdout, stderr)
"""
compose_dir = find_compose_dir()
container = resolve_service(service)
# Build sqlite3 command with options
sqlite_args = []
# Set output mode
if json_output:
sqlite_args.extend(["-json"])
elif column_mode:
sqlite_args.extend(["-column"])
# Enable headers
if headers or column_mode:
sqlite_args.extend(["-header"])
cmd = [
"docker",
"compose",
"exec",
"-T", # Disable pseudo-TTY allocation
container,
"sqlite3",
*sqlite_args,
database,
sql,
]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=compose_dir,
)
return result.returncode, result.stdout, result.stderr
def main() -> int:
parser = argparse.ArgumentParser(
description="Execute SQL queries against SQLite databases in MCP service containers",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Services:
mcp Single-user BasicAuth mode (default)
oauth Nextcloud OAuth mode (mcp-oauth)
keycloak Keycloak OAuth mode (mcp-keycloak)
basic Multi-user BasicAuth mode (mcp-multi-user-basic)
Examples:
%(prog)s ".tables"
%(prog)s -s oauth "SELECT user_id FROM refresh_tokens"
%(prog)s -s keycloak ".schema oauth_clients"
%(prog)s --headers "SELECT * FROM audit_logs LIMIT 5"
%(prog)s --json "SELECT * FROM oauth_sessions"
""",
)
parser.add_argument("sql", help="SQL statement or SQLite command to execute")
parser.add_argument(
"-s",
"--service",
default="mcp",
help="Target service (mcp, oauth, keycloak, basic) (default: mcp)",
)
parser.add_argument(
"-d",
"--database",
default="/app/data/tokens.db",
help="Database path inside container (default: /app/data/tokens.db)",
)
parser.add_argument(
"--headers",
action="store_true",
help="Show column headers",
)
parser.add_argument(
"--json",
action="store_true",
dest="json_output",
help="Output in JSON format",
)
parser.add_argument(
"--column",
action="store_true",
dest="column_mode",
help="Output in column format with headers",
)
args = parser.parse_args()
returncode, stdout, stderr = run_query(
sql=args.sql,
service=args.service,
database=args.database,
headers=args.headers,
json_output=args.json_output,
column_mode=args.column_mode,
)
if stdout:
print(stdout, end="")
if stderr:
print(stderr, file=sys.stderr)
return returncode
if __name__ == "__main__":
sys.exit(main())