193 lines
6.9 KiB
Python
193 lines
6.9 KiB
Python
"""HTTP proxy handler for session token access."""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from grist_mcp.auth import Authenticator
|
|
from grist_mcp.grist_client import GristClient
|
|
from grist_mcp.session import SessionToken
|
|
|
|
|
|
class ProxyError(Exception):
|
|
"""Error during proxy request processing."""
|
|
|
|
def __init__(self, message: str, code: str):
|
|
self.message = message
|
|
self.code = code
|
|
super().__init__(message)
|
|
|
|
|
|
@dataclass
|
|
class ProxyRequest:
|
|
"""Parsed proxy request."""
|
|
method: str
|
|
table: str | None = None
|
|
records: list[dict] | None = None
|
|
record_ids: list[int] | None = None
|
|
filter: dict | None = None
|
|
sort: str | None = None
|
|
limit: int | None = None
|
|
query: str | None = None
|
|
table_id: str | None = None
|
|
columns: list[dict] | None = None
|
|
column_id: str | None = None
|
|
column_type: str | None = None
|
|
formula: str | None = None
|
|
type: str | None = None
|
|
|
|
|
|
METHODS_REQUIRING_TABLE = {
|
|
"get_records", "describe_table", "add_records", "update_records",
|
|
"delete_records", "add_column", "modify_column", "delete_column",
|
|
}
|
|
|
|
|
|
def parse_proxy_request(body: dict[str, Any]) -> ProxyRequest:
|
|
"""Parse and validate a proxy request body."""
|
|
if "method" not in body:
|
|
raise ProxyError("Missing required field: method", "INVALID_REQUEST")
|
|
|
|
method = body["method"]
|
|
|
|
if method in METHODS_REQUIRING_TABLE and "table" not in body:
|
|
raise ProxyError(f"Missing required field 'table' for method '{method}'", "INVALID_REQUEST")
|
|
|
|
return ProxyRequest(
|
|
method=method,
|
|
table=body.get("table"),
|
|
records=body.get("records"),
|
|
record_ids=body.get("record_ids"),
|
|
filter=body.get("filter"),
|
|
sort=body.get("sort"),
|
|
limit=body.get("limit"),
|
|
query=body.get("query"),
|
|
table_id=body.get("table_id"),
|
|
columns=body.get("columns"),
|
|
column_id=body.get("column_id"),
|
|
column_type=body.get("column_type"),
|
|
formula=body.get("formula"),
|
|
type=body.get("type"),
|
|
)
|
|
|
|
|
|
# Map methods to required permissions
|
|
METHOD_PERMISSIONS = {
|
|
"list_tables": "read",
|
|
"describe_table": "read",
|
|
"get_records": "read",
|
|
"sql_query": "read",
|
|
"add_records": "write",
|
|
"update_records": "write",
|
|
"delete_records": "write",
|
|
"create_table": "schema",
|
|
"add_column": "schema",
|
|
"modify_column": "schema",
|
|
"delete_column": "schema",
|
|
}
|
|
|
|
|
|
async def dispatch_proxy_request(
|
|
request: ProxyRequest,
|
|
session: SessionToken,
|
|
auth: Authenticator,
|
|
client: GristClient | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Dispatch a proxy request to the appropriate handler."""
|
|
# Check permission
|
|
required_perm = METHOD_PERMISSIONS.get(request.method)
|
|
if required_perm is None:
|
|
raise ProxyError(f"Unknown method: {request.method}", "INVALID_REQUEST")
|
|
|
|
if required_perm not in session.permissions:
|
|
raise ProxyError(
|
|
f"Permission '{required_perm}' required for {request.method}",
|
|
"UNAUTHORIZED",
|
|
)
|
|
|
|
# Create client if not provided
|
|
if client is None:
|
|
doc = auth.get_document(session.document)
|
|
client = GristClient(doc)
|
|
|
|
# Dispatch to appropriate method
|
|
try:
|
|
if request.method == "list_tables":
|
|
data = await client.list_tables()
|
|
return {"success": True, "data": {"tables": data}}
|
|
|
|
elif request.method == "describe_table":
|
|
data = await client.describe_table(request.table)
|
|
return {"success": True, "data": {"table": request.table, "columns": data}}
|
|
|
|
elif request.method == "get_records":
|
|
data = await client.get_records(
|
|
request.table,
|
|
filter=request.filter,
|
|
sort=request.sort,
|
|
limit=request.limit,
|
|
)
|
|
return {"success": True, "data": {"records": data}}
|
|
|
|
elif request.method == "sql_query":
|
|
if request.query is None:
|
|
raise ProxyError("Missing required field: query", "INVALID_REQUEST")
|
|
data = await client.sql_query(request.query)
|
|
return {"success": True, "data": {"records": data}}
|
|
|
|
elif request.method == "add_records":
|
|
if request.records is None:
|
|
raise ProxyError("Missing required field: records", "INVALID_REQUEST")
|
|
data = await client.add_records(request.table, request.records)
|
|
return {"success": True, "data": {"record_ids": data}}
|
|
|
|
elif request.method == "update_records":
|
|
if request.records is None:
|
|
raise ProxyError("Missing required field: records", "INVALID_REQUEST")
|
|
await client.update_records(request.table, request.records)
|
|
return {"success": True, "data": {"updated": len(request.records)}}
|
|
|
|
elif request.method == "delete_records":
|
|
if request.record_ids is None:
|
|
raise ProxyError("Missing required field: record_ids", "INVALID_REQUEST")
|
|
await client.delete_records(request.table, request.record_ids)
|
|
return {"success": True, "data": {"deleted": len(request.record_ids)}}
|
|
|
|
elif request.method == "create_table":
|
|
if request.table_id is None or request.columns is None:
|
|
raise ProxyError("Missing required fields: table_id, columns", "INVALID_REQUEST")
|
|
data = await client.create_table(request.table_id, request.columns)
|
|
return {"success": True, "data": {"table_id": data}}
|
|
|
|
elif request.method == "add_column":
|
|
if request.column_id is None or request.column_type is None:
|
|
raise ProxyError("Missing required fields: column_id, column_type", "INVALID_REQUEST")
|
|
await client.add_column(
|
|
request.table, request.column_id, request.column_type,
|
|
formula=request.formula,
|
|
)
|
|
return {"success": True, "data": {"column_id": request.column_id}}
|
|
|
|
elif request.method == "modify_column":
|
|
if request.column_id is None:
|
|
raise ProxyError("Missing required field: column_id", "INVALID_REQUEST")
|
|
await client.modify_column(
|
|
request.table, request.column_id,
|
|
type=request.type,
|
|
formula=request.formula,
|
|
)
|
|
return {"success": True, "data": {"column_id": request.column_id}}
|
|
|
|
elif request.method == "delete_column":
|
|
if request.column_id is None:
|
|
raise ProxyError("Missing required field: column_id", "INVALID_REQUEST")
|
|
await client.delete_column(request.table, request.column_id)
|
|
return {"success": True, "data": {"deleted": request.column_id}}
|
|
|
|
else:
|
|
raise ProxyError(f"Unknown method: {request.method}", "INVALID_REQUEST")
|
|
|
|
except ProxyError:
|
|
raise
|
|
except Exception as e:
|
|
raise ProxyError(str(e), "GRIST_ERROR")
|