From 93c7b3e262c95948caad1d84d1aaa0d557dee735 Mon Sep 17 00:00:00 2001 From: xiangmy21 <2248278431@qq.com> Date: Mon, 14 Apr 2025 11:40:05 +0800 Subject: [PATCH 1/5] feat: server for tree model --- .gitignore | 2 + src/iotdb_mcp_server/config.py | 13 --- src/iotdb_mcp_server/server.py | 150 +++++++++++++++++++++++---------- 3 files changed, 109 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index d53d616..ec15ad1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ uv.lock # IDE settings (optional) .vscode/ .idea/ +.specstory/ +.cursor/ # Distribution directories *.dist-info/ diff --git a/src/iotdb_mcp_server/config.py b/src/iotdb_mcp_server/config.py index db6875a..0aad1e4 100644 --- a/src/iotdb_mcp_server/config.py +++ b/src/iotdb_mcp_server/config.py @@ -47,11 +47,6 @@ class Config: IoTDB password """ - database: str - """ - IoTDB database name - """ - @staticmethod def from_env_arguments() -> "Config": """ @@ -73,13 +68,6 @@ def from_env_arguments() -> "Config": default=os.getenv("IOTDB_PORT", 6667), ) - parser.add_argument( - "--database", - type=str, - help="IoTDB connect database name", - default=os.getenv("IOTDB_DATABASE", "test"), - ) - parser.add_argument( "--user", type=str, @@ -98,7 +86,6 @@ def from_env_arguments() -> "Config": return Config( host=args.host, port=args.port, - database=args.database, user=args.user, password=args.password, ) diff --git a/src/iotdb_mcp_server/server.py b/src/iotdb_mcp_server/server.py index 0527bc2..d91285c 100644 --- a/src/iotdb_mcp_server/server.py +++ b/src/iotdb_mcp_server/server.py @@ -17,9 +17,10 @@ # import logging +import sys -from iotdb.table_session import TableSession -from iotdb.table_session_pool import TableSessionPool, TableSessionPoolConfig +from iotdb.Session import Session +from iotdb.SessionPool import SessionPool, PoolConfig from iotdb.utils.SessionDataSet import SessionDataSet from mcp.server.fastmcp import FastMCP from mcp.types import ( @@ -45,78 +46,141 @@ "port": config.port, "user": config.user, "password": config.password, - "database": config.database, } logger.info(f"IoTDB Config: {db_config}") -session_pool_config = TableSessionPoolConfig( +pool_config = PoolConfig( node_urls=[str(config.host) + ":" + str(config.port)], - username=config.user, + user_name=config.user, password=config.password, - database=None if len(config.database) == 0 else config.database, + fetch_size=1024, + time_zone="UTC+8", + max_retry=3 ) +max_pool_size = 5 +wait_timeout_in_ms = 3000 +print('@@@@@@@@@@@@@', file=sys.stderr) +session_pool = SessionPool(pool_config, max_pool_size, wait_timeout_in_ms) +print('############', file=sys.stderr) -session_pool = TableSessionPool(session_pool_config) +@mcp.tool() +async def metadata_query(query_sql: str) -> list[TextContent]: + """Execute metadata queries on IoTDB to explore database structure and statistics. + Args: + query_sql: The metadata query to execute. Supported queries: + - SHOW DATABASES [path]: List all databases or databases under a specific path + - SHOW TIMESERIES [path]: List all time series or time series under a specific path + - SHOW CHILD PATHS [path]: List child paths under a specific path + - SHOW CHILD NODES [path]: List child nodes under a specific path + - SHOW DEVICES [path]: List all devices or devices under a specific path + - COUNT TIMESERIES [path]: Count time series under a specific path + - COUNT NODES [path]: Count nodes under a specific path + - COUNT DEVICES [path]: Count devices under a specific path + - if path is not provided, the query will be applied to root.** + + Examples: + SHOW DATABASES root.** + SHOW TIMESERIES root.ln.** + SHOW CHILD PATHS root.ln + SHOW CHILD PATHS root.ln.*.* + SHOW CHILD NODES root.ln + SHOW DEVICES root.ln.** + COUNT TIMESERIES root.ln.** + COUNT NODES root.ln + COUNT DEVICES root.ln + """ + session = session_pool.get_session() + try: + stmt = query_sql.strip().upper() + + # 处理SHOW DATABASES + if ( + stmt.startswith("SHOW DATABASES") + or stmt.startswith("SHOW TIMESERIES") + or stmt.startswith("SHOW CHILD PATHS") + or stmt.startswith("SHOW CHILD NODES") + or stmt.startswith("SHOW DEVICES") + or stmt.startswith("COUNT TIMESERIES") + or stmt.startswith("COUNT NODES") + or stmt.startswith("COUNT DEVICES") + ): + res = session.execute_query_statement(query_sql) + return prepare_res(res, session) + else: + raise ValueError("Unsupported metadata query. Please use one of the supported query types.") + + except Exception as e: + session.close() + raise e @mcp.tool() -async def read_query(query_sql: str) -> list[TextContent]: - """Execute a SELECT query on the IoTDB. Please use table sql_dialect when generating SQL queries. +async def select_query(query_sql: str) -> list[TextContent]: + """Execute a SELECT query on the IoTDB tree SQL dialect. Args: - query_sql: The SQL query to execute (using TABLE dialect) + query_sql: The SQL query to execute (using TREE dialect) + + SQL Syntax: + SELECT [LAST] selectExpr [, selectExpr] ... + [INTO intoItem [, intoItem] ...] + FROM prefixPath [, prefixPath] ... + [WHERE whereCondition] + [GROUP BY { + ([startTime, endTime), interval [, slidingStep]) | + LEVEL = levelNum [, levelNum] ... | + TAGS(tagKey [, tagKey] ... | + VARIATION(expression[,delta][,ignoreNull=true/false]) | + CONDITION(expression,[keep>/>=/=/ 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000) or (time >= 2017-11-01T16:35:00.000 and time <= 2017-11-01T16:37:00.000) + select * from root.ln.** where time > 1 order by time desc limit 10; + + Supported Aggregate Functions: + SUM + COUNT + MAX_VALUE + MIN_VALUE + AVG + VARIANCE + MAX_TIME + MIN_TIME + ... """ - table_session = session_pool.get_session() - res = table_session.execute_query_statement(query_sql) + session = session_pool.get_session() + res = session.execute_query_statement(query_sql) stmt = query_sql.strip().upper() # Regular SELECT queries if ( stmt.startswith("SELECT") - or stmt.startswith("DESCRIBE") - or stmt.startswith("SHOW") ): - return prepare_res(res, table_session) + return prepare_res(res, session) # Non-SELECT queries else: raise ValueError("Only SELECT queries are allowed for read_query") - -@mcp.tool() -async def list_tables() -> list[TextContent]: - """List all tables in the IoTDB database.""" - table_session = session_pool.get_session() - res = table_session.execute_query_statement("SHOW TABLES") - - result = ["Tables_in_" + db_config["database"]] # Header - while res.has_next(): - result.append(str(res.next().get_fields()[0])) - table_session.close() - return [TextContent(type="text", text="\n".join(result))] - - -@mcp.tool() -async def describe_table(table_name: str) -> list[TextContent]: - """Get the schema information for a specific table - Args: - table_name: name of the table to describe - """ - table_session = session_pool.get_session() - res = table_session.execute_query_statement("DESC " + table_name) - - return prepare_res(res, table_session) - - def prepare_res( - _res: SessionDataSet, _table_session: TableSession + _res: SessionDataSet, _session: Session ) -> list[TextContent]: columns = _res.get_column_names() result = [] while _res.has_next(): row = _res.next().get_fields() result.append(",".join(map(str, row))) - _table_session.close() + _session.close() return [ TextContent( type="text", From ba60010cccbc7573841252f51558a84d8be576c7 Mon Sep 17 00:00:00 2001 From: xiangmy21 <2248278431@qq.com> Date: Mon, 14 Apr 2025 12:09:00 +0800 Subject: [PATCH 2/5] fix: timestamp --- README.md | 24 ++++++++---------------- src/iotdb_mcp_server/server.py | 13 +++++++++++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 0033771..e5cf9dc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# IoTDB MCP Server +# IoTDB MCP Server for Tree Model [![smithery badge](https://smithery.ai/badge/@apache/iotdb-mcp-server)](https://smithery.ai/server/@apache/iotdb-mcp-server) @@ -17,24 +17,16 @@ The server doesn't provide any prompts. The server offers three core tools: #### Query Tools -- `read_query` - - Execute SELECT queries to read data from the database +- `metadata_query` + - Execute SHOW/COUNT queries to read metadata from the database - Input: - - `query` (string): The SELECT SQL query to execute + - `query_sql` (string): The SHOW/COUNT SQL query to execute - Returns: Query results as array of objects - - -#### Schema Tools -- `list_tables` - - Get a list of all tables in the database - - No input required - - Returns: Array of table names - -- `describe-table` - - View schema information for a specific table +- `select_query` + - Execute SELECT queries to read data from the database - Input: - - `table_name` (string): Name of table to describe - - Returns: Array of column definitions with names and types + - `query_sql` (string): The SELECT SQL query to execute + - Returns: Query results as array of objects diff --git a/src/iotdb_mcp_server/server.py b/src/iotdb_mcp_server/server.py index d91285c..3fb2462 100644 --- a/src/iotdb_mcp_server/server.py +++ b/src/iotdb_mcp_server/server.py @@ -18,6 +18,7 @@ import logging import sys +import datetime from iotdb.Session import Session from iotdb.SessionPool import SessionPool, PoolConfig @@ -178,8 +179,16 @@ def prepare_res( columns = _res.get_column_names() result = [] while _res.has_next(): - row = _res.next().get_fields() - result.append(",".join(map(str, row))) + record = _res.next() + if columns[0] == "Time": + timestamp = record.get_timestamp() + # 将时间戳格式化为易读的时间格式 + formatted_time = datetime.datetime.fromtimestamp(timestamp/1000).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + row = record.get_fields() + result.append(formatted_time + "," + ",".join(map(str, row))) + else: + row = record.get_fields() + result.append(",".join(map(str, row))) _session.close() return [ TextContent( From 70b7cc6804fe69f2c6561d36bb03ac1f4e922768 Mon Sep 17 00:00:00 2001 From: xiangmy21 <2248278431@qq.com> Date: Mon, 14 Apr 2025 12:41:37 +0800 Subject: [PATCH 3/5] feat: support for tree model --- README.md | 29 ++- src/iotdb_mcp_server/config.py | 26 +++ src/iotdb_mcp_server/server.py | 356 ++++++++++++++++++++------------- 3 files changed, 267 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index e5cf9dc..8baa85b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# IoTDB MCP Server for Tree Model +# IoTDB MCP Server [![smithery badge](https://smithery.ai/badge/@apache/iotdb-mcp-server)](https://smithery.ai/server/@apache/iotdb-mcp-server) @@ -14,9 +14,9 @@ The server doesn't expose any resources. The server doesn't provide any prompts. ### Tools -The server offers three core tools: +The server offers different tools for IoTDB Tree Model and Table Model. You can choose between them by setting the "IOTDB_SQL_DIALECT" configuration to either "tree" or "table". -#### Query Tools +#### Tree Model - `metadata_query` - Execute SHOW/COUNT queries to read metadata from the database - Input: @@ -28,6 +28,26 @@ The server offers three core tools: - `query_sql` (string): The SELECT SQL query to execute - Returns: Query results as array of objects +#### Table Model + +##### Query Tools +- `read_query` + - Execute SELECT queries to read data from the database + - Input: + - `query` (string): The SELECT SQL query to execute + - Returns: Query results as array of objects + +##### Schema Tools +- `list_tables` + - Get a list of all tables in the database + - No input required + - Returns: Array of table names + +- `describe-table` + - View schema information for a specific table + - Input: + - `table_name` (string): Name of table to describe + - Returns: Array of column definitions with names and types ## Claude Desktop Integration @@ -82,7 +102,8 @@ Location: `%APPDATA%/Claude/claude_desktop_config.json` "IOTDB_PORT": "6667", "IOTDB_USER": "root", "IOTDB_PASSWORD": "root", - "IOTDB_DATABASE": "test" + "IOTDB_DATABASE": "test", + "IOTDB_SQL_DIALECT": "table" } } } diff --git a/src/iotdb_mcp_server/config.py b/src/iotdb_mcp_server/config.py index 0aad1e4..c571e2b 100644 --- a/src/iotdb_mcp_server/config.py +++ b/src/iotdb_mcp_server/config.py @@ -47,6 +47,16 @@ class Config: IoTDB password """ + database: str + """ + IoTDB database + """ + + sql_dialect: str + """ + SQL dialect: tree or table + """ + @staticmethod def from_env_arguments() -> "Config": """ @@ -82,10 +92,26 @@ def from_env_arguments() -> "Config": default=os.getenv("IOTDB_PASSWORD", "root"), ) + parser.add_argument( + "--database", + type=str, + help="IoTDB connect database name", + default=os.getenv("IOTDB_DATABASE", "test"), + ) + + parser.add_argument( + "--sql-dialect", + type=str, + help="SQL dialect: tree or table", + default=os.getenv("IOTDB_SQL_DIALECT", "table"), + ) + args = parser.parse_args() return Config( host=args.host, port=args.port, user=args.user, password=args.password, + database=args.database, + sql_dialect=args.sql_dialect, ) diff --git a/src/iotdb_mcp_server/server.py b/src/iotdb_mcp_server/server.py index 3fb2462..1d21801 100644 --- a/src/iotdb_mcp_server/server.py +++ b/src/iotdb_mcp_server/server.py @@ -17,12 +17,13 @@ # import logging -import sys import datetime from iotdb.Session import Session from iotdb.SessionPool import SessionPool, PoolConfig from iotdb.utils.SessionDataSet import SessionDataSet +from iotdb.table_session import TableSession +from iotdb.table_session_pool import TableSessionPool, TableSessionPoolConfig from mcp.server.fastmcp import FastMCP from mcp.types import ( TextContent, @@ -47,156 +48,231 @@ "port": config.port, "user": config.user, "password": config.password, + "database": config.database, + "sql_dialect": config.sql_dialect, } logger.info(f"IoTDB Config: {db_config}") -pool_config = PoolConfig( - node_urls=[str(config.host) + ":" + str(config.port)], - user_name=config.user, - password=config.password, - fetch_size=1024, - time_zone="UTC+8", - max_retry=3 -) -max_pool_size = 5 -wait_timeout_in_ms = 3000 -print('@@@@@@@@@@@@@', file=sys.stderr) -session_pool = SessionPool(pool_config, max_pool_size, wait_timeout_in_ms) -print('############', file=sys.stderr) - -@mcp.tool() -async def metadata_query(query_sql: str) -> list[TextContent]: - """Execute metadata queries on IoTDB to explore database structure and statistics. - - Args: - query_sql: The metadata query to execute. Supported queries: - - SHOW DATABASES [path]: List all databases or databases under a specific path - - SHOW TIMESERIES [path]: List all time series or time series under a specific path - - SHOW CHILD PATHS [path]: List child paths under a specific path - - SHOW CHILD NODES [path]: List child nodes under a specific path - - SHOW DEVICES [path]: List all devices or devices under a specific path - - COUNT TIMESERIES [path]: Count time series under a specific path - - COUNT NODES [path]: Count nodes under a specific path - - COUNT DEVICES [path]: Count devices under a specific path - - if path is not provided, the query will be applied to root.** - - Examples: - SHOW DATABASES root.** - SHOW TIMESERIES root.ln.** - SHOW CHILD PATHS root.ln - SHOW CHILD PATHS root.ln.*.* - SHOW CHILD NODES root.ln - SHOW DEVICES root.ln.** - COUNT TIMESERIES root.ln.** - COUNT NODES root.ln - COUNT DEVICES root.ln - """ - session = session_pool.get_session() - try: +if config.sql_dialect == "tree": + + pool_config = PoolConfig( + node_urls=[str(config.host) + ":" + str(config.port)], + user_name=config.user, + password=config.password, + fetch_size=1024, + time_zone="UTC+8", + max_retry=3 + ) + max_pool_size = 5 + wait_timeout_in_ms = 3000 + session_pool = SessionPool(pool_config, max_pool_size, wait_timeout_in_ms) + + @mcp.tool() + async def metadata_query(query_sql: str) -> list[TextContent]: + """Execute metadata queries on IoTDB to explore database structure and statistics. + + Args: + query_sql: The metadata query to execute. Supported queries: + - SHOW DATABASES [path]: List all databases or databases under a specific path + - SHOW TIMESERIES [path]: List all time series or time series under a specific path + - SHOW CHILD PATHS [path]: List child paths under a specific path + - SHOW CHILD NODES [path]: List child nodes under a specific path + - SHOW DEVICES [path]: List all devices or devices under a specific path + - COUNT TIMESERIES [path]: Count time series under a specific path + - COUNT NODES [path]: Count nodes under a specific path + - COUNT DEVICES [path]: Count devices under a specific path + - if path is not provided, the query will be applied to root.** + + Examples: + SHOW DATABASES root.** + SHOW TIMESERIES root.ln.** + SHOW CHILD PATHS root.ln + SHOW CHILD PATHS root.ln.*.* + SHOW CHILD NODES root.ln + SHOW DEVICES root.ln.** + COUNT TIMESERIES root.ln.** + COUNT NODES root.ln + COUNT DEVICES root.ln + """ + session = session_pool.get_session() + try: + stmt = query_sql.strip().upper() + + # 处理SHOW DATABASES + if ( + stmt.startswith("SHOW DATABASES") + or stmt.startswith("SHOW TIMESERIES") + or stmt.startswith("SHOW CHILD PATHS") + or stmt.startswith("SHOW CHILD NODES") + or stmt.startswith("SHOW DEVICES") + or stmt.startswith("COUNT TIMESERIES") + or stmt.startswith("COUNT NODES") + or stmt.startswith("COUNT DEVICES") + ): + res = session.execute_query_statement(query_sql) + return prepare_res(res, session) + else: + raise ValueError("Unsupported metadata query. Please use one of the supported query types.") + + except Exception as e: + session.close() + raise e + + @mcp.tool() + async def select_query(query_sql: str) -> list[TextContent]: + """Execute a SELECT query on the IoTDB tree SQL dialect. + + Args: + query_sql: The SQL query to execute (using TREE dialect) + + SQL Syntax: + SELECT [LAST] selectExpr [, selectExpr] ... + [INTO intoItem [, intoItem] ...] + FROM prefixPath [, prefixPath] ... + [WHERE whereCondition] + [GROUP BY { + ([startTime, endTime), interval [, slidingStep]) | + LEVEL = levelNum [, levelNum] ... | + TAGS(tagKey [, tagKey] ... | + VARIATION(expression[,delta][,ignoreNull=true/false]) | + CONDITION(expression,[keep>/>=/=/ 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000) or (time >= 2017-11-01T16:35:00.000 and time <= 2017-11-01T16:37:00.000) + select * from root.ln.** where time > 1 order by time desc limit 10; + + Supported Aggregate Functions: + SUM + COUNT + MAX_VALUE + MIN_VALUE + AVG + VARIANCE + MAX_TIME + MIN_TIME + ... + """ + session = session_pool.get_session() + res = session.execute_query_statement(query_sql) + stmt = query_sql.strip().upper() - - # 处理SHOW DATABASES + # Regular SELECT queries if ( - stmt.startswith("SHOW DATABASES") - or stmt.startswith("SHOW TIMESERIES") - or stmt.startswith("SHOW CHILD PATHS") - or stmt.startswith("SHOW CHILD NODES") - or stmt.startswith("SHOW DEVICES") - or stmt.startswith("COUNT TIMESERIES") - or stmt.startswith("COUNT NODES") - or stmt.startswith("COUNT DEVICES") + stmt.startswith("SELECT") ): - res = session.execute_query_statement(query_sql) return prepare_res(res, session) + # Non-SELECT queries else: - raise ValueError("Unsupported metadata query. Please use one of the supported query types.") - - except Exception as e: - session.close() - raise e - -@mcp.tool() -async def select_query(query_sql: str) -> list[TextContent]: - """Execute a SELECT query on the IoTDB tree SQL dialect. - - Args: - query_sql: The SQL query to execute (using TREE dialect) - - SQL Syntax: - SELECT [LAST] selectExpr [, selectExpr] ... - [INTO intoItem [, intoItem] ...] - FROM prefixPath [, prefixPath] ... - [WHERE whereCondition] - [GROUP BY { - ([startTime, endTime), interval [, slidingStep]) | - LEVEL = levelNum [, levelNum] ... | - TAGS(tagKey [, tagKey] ... | - VARIATION(expression[,delta][,ignoreNull=true/false]) | - CONDITION(expression,[keep>/>=/=/ 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000) or (time >= 2017-11-01T16:35:00.000 and time <= 2017-11-01T16:37:00.000) - select * from root.ln.** where time > 1 order by time desc limit 10; - - Supported Aggregate Functions: - SUM - COUNT - MAX_VALUE - MIN_VALUE - AVG - VARIANCE - MAX_TIME - MIN_TIME - ... - """ - session = session_pool.get_session() - res = session.execute_query_statement(query_sql) - - stmt = query_sql.strip().upper() - # Regular SELECT queries - if ( - stmt.startswith("SELECT") - ): - return prepare_res(res, session) - # Non-SELECT queries - else: - raise ValueError("Only SELECT queries are allowed for read_query") - -def prepare_res( - _res: SessionDataSet, _session: Session -) -> list[TextContent]: - columns = _res.get_column_names() - result = [] - while _res.has_next(): - record = _res.next() - if columns[0] == "Time": - timestamp = record.get_timestamp() - # 将时间戳格式化为易读的时间格式 - formatted_time = datetime.datetime.fromtimestamp(timestamp/1000).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] - row = record.get_fields() - result.append(formatted_time + "," + ",".join(map(str, row))) + raise ValueError("Only SELECT queries are allowed for read_query") + + def prepare_res( + _res: SessionDataSet, _session: Session + ) -> list[TextContent]: + columns = _res.get_column_names() + result = [] + while _res.has_next(): + record = _res.next() + if columns[0] == "Time": + timestamp = record.get_timestamp() + # # 将时间戳格式化为易读的时间格式 + # formatted_time = datetime.datetime.fromtimestamp(timestamp/1000).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + row = record.get_fields() + result.append(timestamp + "," + ",".join(map(str, row))) + else: + row = record.get_fields() + result.append(",".join(map(str, row))) + _session.close() + return [ + TextContent( + type="text", + text="\n".join([",".join(columns)] + result), + ) + ] + +elif config.sql_dialect == "table": + + session_pool_config = TableSessionPoolConfig( + node_urls=[str(config.host) + ":" + str(config.port)], + username=config.user, + password=config.password, + database=None if len(config.database) == 0 else config.database, + ) + session_pool = TableSessionPool(session_pool_config) + + @mcp.tool() + async def read_query(query_sql: str) -> list[TextContent]: + """Execute a SELECT query on the IoTDB. Please use table sql_dialect when generating SQL queries. + + Args: + query_sql: The SQL query to execute (using TABLE dialect) + """ + table_session = session_pool.get_session() + res = table_session.execute_query_statement(query_sql) + + stmt = query_sql.strip().upper() + # Regular SELECT queries + if ( + stmt.startswith("SELECT") + or stmt.startswith("DESCRIBE") + or stmt.startswith("SHOW") + ): + return prepare_res(res, table_session) + # Non-SELECT queries else: - row = record.get_fields() - result.append(",".join(map(str, row))) - _session.close() - return [ - TextContent( - type="text", - text="\n".join([",".join(columns)] + result), - ) - ] + raise ValueError("Only SELECT queries are allowed for read_query") + + @mcp.tool() + async def list_tables() -> list[TextContent]: + """List all tables in the IoTDB database.""" + table_session = session_pool.get_session() + res = table_session.execute_query_statement("SHOW TABLES") + + result = ["Tables_in_" + db_config["database"]] # Header + while res.has_next(): + result.append(str(res.next().get_fields()[0])) + table_session.close() + return [TextContent(type="text", text="\n".join(result))] + + + @mcp.tool() + async def describe_table(table_name: str) -> list[TextContent]: + """Get the schema information for a specific table + Args: + table_name: name of the table to describe + """ + table_session = session_pool.get_session() + res = table_session.execute_query_statement("DESC " + table_name) + + return prepare_res(res, table_session) + + + def prepare_res( + _res: SessionDataSet, _table_session: TableSession + ) -> list[TextContent]: + columns = _res.get_column_names() + result = [] + while _res.has_next(): + row = _res.next().get_fields() + result.append(",".join(map(str, row))) + _table_session.close() + return [ + TextContent( + type="text", + text="\n".join([",".join(columns)] + result), + ) + ] if __name__ == "__main__": logger.info("iotdb_mcp_server running with stdio transport") From 6d60313b7d017a74cc340a806668efa35843e262 Mon Sep 17 00:00:00 2001 From: xiangmy21 <2248278431@qq.com> Date: Mon, 14 Apr 2025 12:46:03 +0800 Subject: [PATCH 4/5] fix: readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8baa85b..457021f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The server offers different tools for IoTDB Tree Model and Table Model. You can - `metadata_query` - Execute SHOW/COUNT queries to read metadata from the database - Input: - - `query_sql` (string): The SHOW/COUNT SQL query to execute + - `query_sql` (string): The SHOW/COUNT SQL query to execute - Returns: Query results as array of objects - `select_query` - Execute SELECT queries to read data from the database From fa2540ea7b4819f1483090ab0d8e2208504a5188 Mon Sep 17 00:00:00 2001 From: xiangmy21 <2248278431@qq.com> Date: Tue, 15 Apr 2025 11:28:37 +0800 Subject: [PATCH 5/5] chore --- src/iotdb_mcp_server/server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/iotdb_mcp_server/server.py b/src/iotdb_mcp_server/server.py index 1d21801..7e4b9ff 100644 --- a/src/iotdb_mcp_server/server.py +++ b/src/iotdb_mcp_server/server.py @@ -185,7 +185,6 @@ def prepare_res( record = _res.next() if columns[0] == "Time": timestamp = record.get_timestamp() - # # 将时间戳格式化为易读的时间格式 # formatted_time = datetime.datetime.fromtimestamp(timestamp/1000).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] row = record.get_fields() result.append(timestamp + "," + ",".join(map(str, row)))