This directory contains tool modules registered by src/iotdb_mcp_server/server.py.
Current registration order:
register_target_toolsregister_runtime_policy_toolsregister_result_store_toolsregister_query_toolsregister_sql_driver_toolsregister_metadata_toolsregister_explain_toolsregister_database_toolsregister_timeseries_toolsregister_ttl_toolsregister_table_toolsregister_udf_toolsregister_write_toolsregister_model_toolsquery.pyselect_query(query_sql, max_inline_rows=None, page_size_rows=None)export_query(query_sql, format="csv", filename=None)read_query(query_sql, max_inline_rows=None, page_size_rows=None)export_table_query(query_sql, format="csv", filename=None)Read-query tools stream rows into ResultStore and return only a preview by default. Use the returned result_id and next_cursor with read_result_page.
results.pyread_result_page(result_id, cursor=None, offset=None, limit=None, owner_session_id=None, target_id=None)read_result_pages(pages, owner_session_id=None, default_limit=None, max_pages=None, max_total_rows=None, continue_on_error=True, target_id=None)list_result_store(owner_session_id=None, all_sessions=False, limit=50, target_id=None)cleanup_result_store(owner_session_id=None, all_sessions=False, ttl_seconds=None, target_id=None)delete_result(result_id, owner_session_id=None, all_sessions=False, target_id=None)Loads a bounded page from a file-backed ResultStore result. Pass the same target_id as the original query when multiple IoTDB targets are registered. When the TimeSeek Codex plugin hook is active, owner_session_id is injected from the Codex session. Standalone MCP usage can pass owner_session_id explicitly or set TIMESEEK_MCP_SESSION_ID; otherwise the ResultStore owner is standalone.
ResultStore cleanup is finite and session-aware. Each write runs lightweight cleanup for the current owner and then enforces global limits. Entries expire by last_accessed_at, and quota pressure evicts least-recently-accessed results. If a single new result is larger than the byte quota, it is retained so the caller can still page through the result that was just returned.
read_result_pages batches multiple page requests in one MCP call. Each item accepts result_id plus optional cursor, offset, limit, and owner_session_id. The default per-call limits are 32 pages and 10000 returned rows; override them with tool arguments or TIMESEEK_RESULT_MAX_BATCH_PAGES and TIMESEEK_RESULT_MAX_BATCH_READ_ROWS.
sql_driver.pysql_driver_policy()inspect_sql_permission(sql)sql_executor_batch(sqls=None, sql_template=None, param_sets=None, max_concurrency=4, worker_pool_size=None, per_item_timeout_ms=None, batch_timeout_ms=None, max_result_rows_per_item=None, max_result_bytes_per_item=None, max_batch_result_rows=None, max_batch_result_bytes=None, continue_on_error=True, max_inline_rows=5, page_size_rows=None)sql_execute(sql, confirm_destructive=False, max_inline_rows=None, page_size_rows=None)Features:
readonly / ddl / fullIOTDB_SQL_DRIVER_MODErequired_permission, destructive, risk_level, approval_required, and the confirmation parameter without executing SQLsql_executor_batch executes only readonly single statements. It accepts either explicit sqls=[...] or a repeated sql_template plus param_sets. Template placeholders are {{name}} for SQL literals, {{name:path}} for IoTDB paths, and {{name:identifier}} for SQL identifiers.result_id, row counts, a small preview, and paging metadata; use read_result_page to fetch full result pages.SHOW CONTINUOUS QUERIES, SHOW CQSCREATE CONTINUOUS QUERY / CREATE CQ, DROP CONTINUOUS QUERY / DROP CQSELECT ... GROUP BY ([start, end), interval[, slidingStep])INTO + optional RESAMPLE clauseruntime_policy.pyget_iotdb_session_policy()set_iotdb_session_policy(policy=None, preset=None, replace=False)reset_iotdb_session_policy(keys=None)full: advisory signal for read, DDL, and DML-capable workflowsddl: advisory signal for read and DDL-capable workflowsreadonly: advisory signal for read-only workflowsstrict mode only, these presets become hard execution gates.mcp.jsonmetadata.pymetadata_query(query_sql)list_timeseries(path="root.**")list_devices(path="root.**")list_child_paths(path)list_child_nodes(path)count_timeseries(path="root.**")count_devices(path="root.**")count_nodes(path="root")metadata_query(query_sql)list_tables()describe_table(table_name, details=True)Metadata result sets also use ResultStore, which keeps large SHOW TIMESERIES or SHOW DEVICES responses bounded.
explain.pyexplain_query(query_sql, analyze=False)database.pylist_databases(details=False)create_database(database)drop_database(database, confirm=False)list_databases(details=False)create_database(database, if_not_exists=True)drop_database(database, if_exists=True, confirm=False)use_database(database)timeseries.py (tree only)create_timeseries_ddl(ddl_sql)create_timeseries_batch_ddl(ddl_list, continue_on_error=True, skip_if_exists=True, max_statements=1000)alter_timeseries_ddl(ddl_sql)drop_timeseries_ddl(ddl_sql, confirm=False)ttl.py (tree only)ttl_command(ttl_sql, confirm_unset=False)ttl_query(ttl_sql)table.py (table only)create_table_ddl(ddl_sql)alter_table_ddl(ddl_sql)drop_table_ddl(ddl_sql, confirm=False)udf.pylist_udf_functions()prepare_udf_query(function_name, expressions, table_name=None, from_path=None, attributes=None, where_clause=None, limit=None, alias=None, align_by_device=False)execute_udf_query(function_name, expressions, table_name=None, from_path=None, attributes=None, where_clause=None, limit=None, alias=None, align_by_device=False, max_inline_rows=None, page_size_rows=None)export_udf_query(function_name, expressions, table_name=None, from_path=None, attributes=None, where_clause=None, limit=None, alias=None, align_by_device=False, format="csv", filename=None)Features:
SHOW FUNCTIONS discovery through list_udf_functionsSELECT UDF(column, "k"="v") FROM table ...SELECT UDF(measurement, "k"="v") FROM root.sg.d1 ...where_clausewrite.pywrite_query(write_sql, confirm_delete=False) (INSERT/DELETE)write_query(write_sql, confirm_delete=False) (INSERT/UPDATE/DELETE)model.pymodel_query(model_sql)model_command(model_sql, confirm_destructive=False)prepare_model_inference_request(model_id, input_sql, output_length=96, generate_time=False)model_inference(inference_sql)json_response.pyJsonParser.check_format(obj)JsonParser.parse(text)payload_response(tool, payload, message=None)text_payload_response(tool, text, message=None)csv_payload_response(tool, columns, rows, message=None)sql_success_response(tool, sql)All tools now return TextContent.text as a JSON envelope instead of plain text.
Envelope format:
{ "version": "1.0", "tool": "tool_name", "ok": true, "timestamp": "2026-02-26T00:00:00+00:00", "payload": {}, "message": "optional" }
Payload conventions:
payload.format="csv" with columns, preview rows, preview text, row_count, inline_row_count, inline_truncated, and ResultStore fields when persistedpayload.format="text" with textpayload.sql + payload.result="success"For ResultStore-backed responses:
{ "format": "csv", "columns": ["Time", "root.sg.d.s0"], "rows": ["0,1.0"], "text": "Time,root.sg.d.s0\n0,1.0", "row_count": 100000, "inline_row_count": 1, "inline_truncated": true, "result_id": "res_20260704T000000Z_0123456789abcdef", "owner_session_id": "ses_abc123", "next_cursor": "1", "result_store": { "type": "file", "page_tool": "read_result_page", "batch_page_tool": "read_result_pages", "owner_session_id": "ses_abc123", "page_size_rows": 500, "shard_count": 10 } }
Environment knobs:
TIMESEEK_RESULT_PREVIEW_ROWS default 50TIMESEEK_RESULT_PAGE_SIZE_ROWS default 500TIMESEEK_RESULT_MAX_PAGE_ROWS default 5000TIMESEEK_RESULT_SHARD_ROWS default 10000TIMESEEK_RESULT_SHARD_BYTES default 8388608TIMESEEK_RESULT_TTL_SECONDS default 86400; set 0 to disable TTL expiryTIMESEEK_RESULT_MAX_SESSION_RESULTS default 100; set 0 to disableTIMESEEK_RESULT_MAX_SESSION_BYTES default 536870912; set 0 to disableTIMESEEK_RESULT_MAX_GLOBAL_RESULTS default 1000; set 0 to disableTIMESEEK_RESULT_MAX_GLOBAL_BYTES default 2147483648; set 0 to disableTIMESEEK_MCP_SESSION_ID optional standalone ResultStore owner fallbackIOTDB_SQL_EXECUTOR_BATCH_MAX_STATEMENTS default 64IOTDB_SQL_EXECUTOR_BATCH_MAX_CONCURRENCY default 16Parser check:
JsonParser.check_format(...)JsonParser.parse(...) before returningValueErrorIoTDB MCP permissions default to host-agent approval flow. The MCP server reports required permission and risk; Codex, Claude Code, or OpenCode owns user approval through its normal question/approval mechanism. This keeps MCP policy aligned with the host agent system instead of turning .mcp.json into a hard blocker.
Default mode is advisory. In advisory mode:
IOTDB_SQL_DRIVER_MODE is a prompt-layer signal, not an execution blocker.IOTDB_ENABLE_* and *_ALLOWED_USERS are prompt-layer/session settings, not hard gates.confirm_destructive=true, after the host agent has obtained approval.Strict hard gating is available only when explicitly enabled:
TIMESEEK_MCP_PERMISSION_ENFORCEMENT=strict # or IOTDB_STRICT_PERMISSION_ENFORCEMENT=true
Policy can be changed at runtime without restarting the MCP server:
{ "preset": "readonly", "replace": true }
or:
{ "policy": { "IOTDB_SQL_DRIVER_MODE": "full", "IOTDB_ENABLE_WRITE_DML": "true" } }
database.pyIOTDB_ENABLE_DATABASE_DDLIOTDB_DATABASE_DDL_ALLOWED_USERSIOTDB_REQUIRE_DROP_CONFIRMtimeseries.pyIOTDB_ENABLE_TIMESERIES_DDLIOTDB_TIMESERIES_DDL_ALLOWED_USERSIOTDB_REQUIRE_TIMESERIES_DROP_CONFIRMtable.pyIOTDB_ENABLE_TABLE_DDLIOTDB_TABLE_DDL_ALLOWED_USERSIOTDB_REQUIRE_TABLE_DROP_CONFIRMttl.pyIOTDB_ENABLE_TTL_SQLIOTDB_TTL_ALLOWED_USERSIOTDB_REQUIRE_TTL_UNSET_CONFIRMwrite.pyIOTDB_ENABLE_WRITE_DMLIOTDB_WRITE_ALLOWED_USERSIOTDB_REQUIRE_DELETE_CONFIRMmodel.pyIOTDB_ENABLE_MODEL_MANAGEMENTIOTDB_MODEL_ALLOWED_USERSIOTDB_REQUIRE_MODEL_DESTRUCTIVE_CONFIRMmodel_inference is registered only for tree dialect and accepts CALL INFERENCE(...) SQL.metadata.pyIOTDB_ENABLE_METADATA_QUERYIOTDB_METADATA_ALLOWED_USERSsql_driver.pyIOTDB_ENABLE_SQL_DRIVERIOTDB_SQL_DRIVER_ALLOWED_USERSIOTDB_SQL_DRIVER_MODE (readonly / ddl / full)IOTDB_SQL_DRIVER_REQUIRE_DESTRUCTIVE_CONFIRMIOTDB_SQL_DRIVER_EXTRA_READONLY_PREFIXESIOTDB_SQL_DRIVER_EXTRA_DDL_PREFIXESIOTDB_SQL_DRIVER_EXTRA_FULL_PREFIXES# Dialect IOTDB_SQL_DIALECT=tree # Keep generic driver available in full mode IOTDB_ENABLE_SQL_DRIVER=true IOTDB_SQL_DRIVER_MODE=full IOTDB_SQL_DRIVER_ALLOWED_USERS=* # Enable specialized tools IOTDB_ENABLE_METADATA_QUERY=true IOTDB_ENABLE_DATABASE_DDL=true IOTDB_ENABLE_TIMESERIES_DDL=true IOTDB_ENABLE_TTL_SQL=true IOTDB_ENABLE_WRITE_DML=true IOTDB_ENABLE_MODEL_MANAGEMENT=true
; separated multi-statement is rejected).sql_dialect (tree or table).TIMESEEK_MCP_PERMISSION_ENFORCEMENT=strict.