notebookvb
This commit is contained in:
+154
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MCP server pro Firebird/Medicus — používá oficiální MCP SDK (FastMCP)
|
||||
Spustit: python mcp_firebird.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import fdb
|
||||
import traceback
|
||||
from typing import Optional
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
FB_CONFIG = {
|
||||
'dsn': r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
'user': 'SYSDBA',
|
||||
'password': 'masterkey',
|
||||
'charset': 'win1250',
|
||||
'port': 3070,
|
||||
}
|
||||
|
||||
# Všechny logy MUSÍ jít na stderr — stdout je rezervován pro JSON-RPC
|
||||
def log(msg: str):
|
||||
print(msg, file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
# Připojení k Firebirdu
|
||||
try:
|
||||
conn = fdb.connect(**FB_CONFIG)
|
||||
log("✓ Připojeno k Firebirdu (Medicus)")
|
||||
except Exception as e:
|
||||
log(f"✗ Chyba připojení k Firebirdu: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def rows_to_json(rows, description):
|
||||
"""Převede fdb rows na JSON-serializovatelný formát"""
|
||||
import datetime
|
||||
import decimal
|
||||
|
||||
def convert(val):
|
||||
if isinstance(val, (datetime.date, datetime.datetime)):
|
||||
return val.isoformat()
|
||||
if isinstance(val, decimal.Decimal):
|
||||
return float(val)
|
||||
if isinstance(val, bytes):
|
||||
return val.decode('win1250', errors='replace')
|
||||
return val
|
||||
|
||||
cols = [d[0].strip() for d in description]
|
||||
return [dict(zip(cols, [convert(v) for v in row])) for row in rows]
|
||||
|
||||
|
||||
# MCP server
|
||||
mcp = FastMCP("medicus-firebird")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def execute_query(sql: str, params: Optional[list] = None) -> dict:
|
||||
"""Spusť SQL dotaz na Medicus databázi.
|
||||
Pro SELECT vrátí columns + rows. Pro INSERT/UPDATE/DELETE vrátí rowcount.
|
||||
"""
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
if params:
|
||||
cur.execute(sql, params)
|
||||
else:
|
||||
cur.execute(sql)
|
||||
|
||||
if sql.strip().upper().startswith('SELECT'):
|
||||
rows = rows_to_json(cur.fetchall(), cur.description or [])
|
||||
return {
|
||||
'rowcount': len(rows),
|
||||
'rows': rows
|
||||
}
|
||||
else:
|
||||
conn.commit()
|
||||
return {
|
||||
'rowcount': cur.rowcount,
|
||||
'message': f'Dotaz proveden: {cur.rowcount} řádků ovlivněno'
|
||||
}
|
||||
except Exception as e:
|
||||
log(f"execute_query chyba: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def list_tables() -> list[str]:
|
||||
"""Vrátí seznam všech uživatelských tabulek v Medicus databázi."""
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT TRIM(RDB$RELATION_NAME)
|
||||
FROM RDB$RELATIONS
|
||||
WHERE RDB$SYSTEM_FLAG = 0
|
||||
ORDER BY RDB$RELATION_NAME
|
||||
""")
|
||||
return [row[0] for row in cur.fetchall()]
|
||||
except Exception as e:
|
||||
log(f"list_tables chyba: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_table_columns(table_name: str) -> list[str]:
|
||||
"""Vrátí seznam sloupců dané tabulky."""
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT TRIM(RDB$FIELD_NAME)
|
||||
FROM RDB$RELATION_FIELDS
|
||||
WHERE TRIM(RDB$RELATION_NAME) = ?
|
||||
ORDER BY RDB$FIELD_POSITION
|
||||
""", [table_name.upper()])
|
||||
return [row[0] for row in cur.fetchall()]
|
||||
except Exception as e:
|
||||
log(f"get_table_columns chyba: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_schema() -> dict:
|
||||
"""Vrátí kompletní schéma DB — všechny tabulky a jejich sloupce."""
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT r.RDB$RELATION_NAME, f.RDB$FIELD_NAME
|
||||
FROM RDB$RELATIONS r
|
||||
LEFT JOIN RDB$RELATION_FIELDS f ON r.RDB$RELATION_NAME = f.RDB$RELATION_NAME
|
||||
WHERE r.RDB$SYSTEM_FLAG = 0
|
||||
ORDER BY r.RDB$RELATION_NAME, f.RDB$FIELD_POSITION
|
||||
""")
|
||||
|
||||
schema = {}
|
||||
for tbl_name, col_name in cur.fetchall():
|
||||
tbl = tbl_name.strip() if tbl_name else 'unknown'
|
||||
col = col_name.strip() if col_name else ''
|
||||
if tbl not in schema:
|
||||
schema[tbl] = []
|
||||
if col:
|
||||
schema[tbl].append(col)
|
||||
|
||||
return {
|
||||
'table_count': len(schema),
|
||||
'tables': list(schema.keys()),
|
||||
'schema': schema
|
||||
}
|
||||
except Exception as e:
|
||||
log(f"get_schema chyba: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
log("MCP Firebird server spuštěn (FastMCP)")
|
||||
mcp.run()
|
||||
Reference in New Issue
Block a user