blob: b22786ecc1c098f8be4cc06f56e8094700c95306 [file]
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
import logging
from iotdb.Session import Session
from iotdb.utils.SessionDataSet import SessionDataSet
from mcp.types import TextContent
from iotdb_mcp_server.config import Config
from iotdb_mcp_server.runtime_policy import dynamic_env_bool
from iotdb_mcp_server.runtime_policy import dynamic_getenv
from iotdb_mcp_server.runtime_policy import strict_permission_enforcement
from iotdb_mcp_server.services.json_response import (
csv_payload_response,
sql_success_response,
)
from iotdb_mcp_server.services.target_selection import (
iotdb_target_response_context,
tree_session_pool,
)
def _env_bool(name: str, default: bool) -> bool:
return dynamic_env_bool(name, default)
def _csv_set(value: str) -> set[str]:
return {item.strip() for item in value.split(",") if item.strip()}
def _assert_ttl_permission(
config: Config, action: str, confirm_unset: bool = False
) -> None:
if strict_permission_enforcement():
if not _env_bool("IOTDB_ENABLE_TTL_SQL", False):
raise PermissionError(
"TTL tools are disabled by strict MCP policy. "
"Set IOTDB_ENABLE_TTL_SQL=true to enable."
)
allowed_users = _csv_set(dynamic_getenv("IOTDB_TTL_ALLOWED_USERS", "root") or "root")
if "*" not in allowed_users and config.user not in allowed_users:
raise PermissionError(
f"Current MCP user '{config.user}' is not allowed by IOTDB_TTL_ALLOWED_USERS."
)
if (
action == "UNSET"
and _env_bool("IOTDB_REQUIRE_TTL_UNSET_CONFIRM", True)
and not confirm_unset
):
raise PermissionError(
"UNSET TTL requires confirm_unset=True by server policy."
)
def _normalize_sql(sql: str) -> str:
cleaned = sql.strip()
if not cleaned:
raise ValueError("TTL SQL cannot be empty.")
if cleaned.endswith(";"):
cleaned = cleaned[:-1].strip()
if ";" in cleaned:
raise ValueError("Only a single SQL statement is allowed.")
return cleaned
def _format_result(res: SessionDataSet, session: Session) -> list[TextContent]:
columns = res.get_column_names()
rows: list[str] = []
while res.has_next():
row = res.next().get_fields()
rows.append(",".join(map(str, row)))
session.close()
return csv_payload_response("ttl_query", columns, rows)
def register_ttl_tools(mcp, config: Config, logger: logging.Logger) -> None:
"""Register TTL SQL tools.
Note: TTL grammar is tree-model SQL.
"""
@mcp.tool()
async def ttl_command(
ttl_sql: str,
confirm_unset: bool = False,
target_id: str | None = None,
target: dict[str, object] | None = None,
) -> list[TextContent]:
"""Execute TTL command SQL.
Supported prefixes:
- SET TTL
- UNSET TTL
"""
selected_config, session_pool = tree_session_pool(
config,
target_id=target_id,
target=target,
max_pool_size=100,
wait_timeout_in_ms=5000,
tool_name="ttl_command",
)
with iotdb_target_response_context(selected_config):
session = None
try:
sql = _normalize_sql(ttl_sql)
upper = sql.upper()
if upper.startswith("SET TTL"):
_assert_ttl_permission(selected_config, action="SET")
elif upper.startswith("UNSET TTL"):
_assert_ttl_permission(
selected_config, action="UNSET", confirm_unset=confirm_unset
)
else:
raise ValueError(
"ttl_command only supports SQL starting with: SET TTL, UNSET TTL"
)
session = session_pool.get_session()
session.execute_non_query_statement(sql)
session.close()
return sql_success_response("ttl_command", sql)
except Exception as e:
if session:
session.close()
logger.error(f"Failed to execute ttl_command: {str(e)}")
raise
@mcp.tool()
async def ttl_query(
ttl_sql: str,
target_id: str | None = None,
target: dict[str, object] | None = None,
) -> list[TextContent]:
"""Execute TTL query SQL.
Supported prefixes:
- SHOW TTL ON
- SHOW ALL TTL
"""
selected_config, session_pool = tree_session_pool(
config,
target_id=target_id,
target=target,
max_pool_size=100,
wait_timeout_in_ms=5000,
tool_name="ttl_query",
)
with iotdb_target_response_context(selected_config):
session = None
try:
_assert_ttl_permission(selected_config, action="SHOW")
sql = _normalize_sql(ttl_sql)
upper = sql.upper()
if not upper.startswith(("SHOW TTL ON", "SHOW ALL TTL")):
raise ValueError(
"ttl_query only supports SQL starting with: SHOW TTL ON, SHOW ALL TTL"
)
session = session_pool.get_session()
res = session.execute_query_statement(sql)
return _format_result(res, session)
except Exception as e:
if session:
session.close()
logger.error(f"Failed to execute ttl_query: {str(e)}")
raise