MindsDB 25.3.4.1__py3-none-any.whl → 25.4.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MindsDB might be problematic. Click here for more details.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +21 -4
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +5 -2
- mindsdb/api/executor/datahub/datanodes/system_tables.py +131 -138
- mindsdb/api/mcp/__init__.py +0 -0
- mindsdb/api/mcp/start.py +152 -0
- mindsdb/api/mysql/mysql_proxy/libs/constants/mysql.py +74 -0
- mindsdb/integrations/handlers/confluence_handler/confluence_api_client.py +14 -2
- mindsdb/integrations/handlers/ms_teams_handler/ms_graph_api_teams_client.py +278 -55
- mindsdb/integrations/handlers/ms_teams_handler/ms_teams_handler.py +52 -21
- mindsdb/integrations/handlers/ms_teams_handler/ms_teams_tables.py +6 -29
- mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +37 -1
- mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +28 -1
- mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +53 -5
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +37 -1
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +42 -1
- mindsdb/integrations/libs/vectordatabase_handler.py +20 -20
- mindsdb/integrations/utilities/handlers/auth_utilities/__init__.py +1 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/__init__.py +1 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/ms_graph_api_auth_utilities.py +97 -18
- mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +121 -11
- mindsdb/interfaces/database/projects.py +15 -0
- mindsdb/interfaces/knowledge_base/controller.py +78 -2
- mindsdb/utilities/config.py +8 -0
- mindsdb/utilities/render/sqlalchemy_render.py +30 -6
- mindsdb/utilities/starters.py +7 -0
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/METADATA +233 -234
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/RECORD +31 -29
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.4.1.0.dist-info}/top_level.txt +0 -0
mindsdb/__about__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
__title__ = 'MindsDB'
|
|
2
2
|
__package_name__ = 'mindsdb'
|
|
3
|
-
__version__ = '25.
|
|
3
|
+
__version__ = '25.4.1.0'
|
|
4
4
|
__description__ = "MindsDB's AI SQL Server enables developers to build AI tools that need access to real-time data to perform their tasks"
|
|
5
5
|
__email__ = "jorge@mindsdb.com"
|
|
6
6
|
__author__ = 'MindsDB Inc'
|
mindsdb/__main__.py
CHANGED
|
@@ -24,7 +24,8 @@ from mindsdb.__about__ import __version__ as mindsdb_version
|
|
|
24
24
|
from mindsdb.utilities.config import config
|
|
25
25
|
from mindsdb.utilities.exception import EntityNotExistsError
|
|
26
26
|
from mindsdb.utilities.starters import (
|
|
27
|
-
start_http, start_mysql, start_mongo, start_postgres, start_ml_task_queue, start_scheduler, start_tasks
|
|
27
|
+
start_http, start_mysql, start_mongo, start_postgres, start_ml_task_queue, start_scheduler, start_tasks,
|
|
28
|
+
start_mcp
|
|
28
29
|
)
|
|
29
30
|
from mindsdb.utilities.ps import is_pid_listen_port, get_child_pids
|
|
30
31
|
from mindsdb.utilities.functions import get_versions_where_predictors_become_obsolete
|
|
@@ -57,6 +58,7 @@ class TrunkProcessEnum(Enum):
|
|
|
57
58
|
JOBS = 'jobs'
|
|
58
59
|
TASKS = 'tasks'
|
|
59
60
|
ML_TASK_QUEUE = 'ml_task_queue'
|
|
61
|
+
MCP = 'mcp'
|
|
60
62
|
|
|
61
63
|
@classmethod
|
|
62
64
|
def _missing_(cls, value):
|
|
@@ -221,9 +223,9 @@ if __name__ == '__main__':
|
|
|
221
223
|
ctx.set_default()
|
|
222
224
|
|
|
223
225
|
# ---- CHECK SYSTEM ----
|
|
224
|
-
if not (sys.version_info[0] >= 3 and sys.version_info[1] >=
|
|
226
|
+
if not (sys.version_info[0] >= 3 and sys.version_info[1] >= 10):
|
|
225
227
|
print("""
|
|
226
|
-
MindsDB requires Python >= 3.
|
|
228
|
+
MindsDB requires Python >= 3.10 to run
|
|
227
229
|
|
|
228
230
|
Once you have supported Python version installed you can start mindsdb as follows:
|
|
229
231
|
|
|
@@ -385,6 +387,7 @@ if __name__ == '__main__':
|
|
|
385
387
|
|
|
386
388
|
http_api_config = config['api']['http']
|
|
387
389
|
mysql_api_config = config['api']['mysql']
|
|
390
|
+
mcp_api_config = config['api']['mcp']
|
|
388
391
|
trunc_processes_struct = {
|
|
389
392
|
TrunkProcessEnum.HTTP: TrunkProcessData(
|
|
390
393
|
name=TrunkProcessEnum.HTTP.value,
|
|
@@ -434,11 +437,25 @@ if __name__ == '__main__':
|
|
|
434
437
|
name=TrunkProcessEnum.ML_TASK_QUEUE.value,
|
|
435
438
|
entrypoint=start_ml_task_queue,
|
|
436
439
|
args=(config.cmd_args.verbose,)
|
|
440
|
+
),
|
|
441
|
+
TrunkProcessEnum.MCP: TrunkProcessData(
|
|
442
|
+
name=TrunkProcessEnum.MCP.value,
|
|
443
|
+
entrypoint=start_mcp,
|
|
444
|
+
port=mcp_api_config.get('port', 47337),
|
|
445
|
+
args=(config.cmd_args.verbose,),
|
|
446
|
+
restart_on_failure=mcp_api_config.get('restart_on_failure', False),
|
|
447
|
+
max_restart_count=mcp_api_config.get('max_restart_count', TrunkProcessData.max_restart_count),
|
|
448
|
+
max_restart_interval_seconds=mcp_api_config.get(
|
|
449
|
+
'max_restart_interval_seconds', TrunkProcessData.max_restart_interval_seconds
|
|
450
|
+
)
|
|
437
451
|
)
|
|
438
452
|
}
|
|
439
453
|
|
|
440
454
|
for api_enum in api_arr:
|
|
441
|
-
|
|
455
|
+
if api_enum in trunc_processes_struct:
|
|
456
|
+
trunc_processes_struct[api_enum].need_to_run = True
|
|
457
|
+
else:
|
|
458
|
+
logger.error(f"ERROR: {api_enum} API is not a valid api in config")
|
|
442
459
|
|
|
443
460
|
if config['jobs']['disable'] is False:
|
|
444
461
|
trunc_processes_struct[TrunkProcessEnum.JOBS].need_to_run = True
|
|
@@ -64,6 +64,7 @@ class IntegrationDataNode(DataNode):
|
|
|
64
64
|
df = response.data_frame
|
|
65
65
|
# case independent
|
|
66
66
|
columns = [str(c).lower() for c in df.columns]
|
|
67
|
+
df.columns = columns
|
|
67
68
|
|
|
68
69
|
col_name = None
|
|
69
70
|
# looking for specific column names
|
|
@@ -78,8 +79,10 @@ class IntegrationDataNode(DataNode):
|
|
|
78
79
|
names = df[df.columns[col_name]]
|
|
79
80
|
|
|
80
81
|
# type
|
|
81
|
-
if '
|
|
82
|
-
types = df[
|
|
82
|
+
if 'mysql_data_type' in columns:
|
|
83
|
+
types = df['mysql_data_type']
|
|
84
|
+
elif 'type' in columns:
|
|
85
|
+
types = df['type']
|
|
83
86
|
else:
|
|
84
87
|
types = [None] * len(names)
|
|
85
88
|
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
from typing import Optional, Literal
|
|
2
|
+
from dataclasses import dataclass, astuple, fields
|
|
1
3
|
|
|
2
4
|
import pandas as pd
|
|
3
5
|
from mindsdb_sql_parser.ast.base import ASTNode
|
|
4
|
-
from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions
|
|
5
6
|
|
|
7
|
+
from mindsdb.utilities import log
|
|
8
|
+
from mindsdb.utilities.config import config
|
|
9
|
+
from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions
|
|
10
|
+
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
6
11
|
from mindsdb.api.executor.datahub.classes.tables_row import (
|
|
7
12
|
TABLES_ROW_TYPE,
|
|
8
13
|
TablesRow,
|
|
9
14
|
)
|
|
10
|
-
from mindsdb.utilities import log
|
|
11
|
-
from mindsdb.utilities.config import config
|
|
12
15
|
|
|
13
16
|
logger = log.getLogger(__name__)
|
|
14
17
|
|
|
@@ -162,132 +165,108 @@ class TablesTable(Table):
|
|
|
162
165
|
return df
|
|
163
166
|
|
|
164
167
|
|
|
165
|
-
|
|
168
|
+
@dataclass
|
|
169
|
+
class ColumnsTableRow:
|
|
170
|
+
"""Represents a row in the COLUMNS table.
|
|
171
|
+
Fields description: https://dev.mysql.com/doc/refman/8.4/en/information-schema-columns-table.html
|
|
172
|
+
NOTE: attrs order matter, don't change it.
|
|
173
|
+
"""
|
|
174
|
+
TABLE_CATALOG: Literal['def'] = 'def'
|
|
175
|
+
TABLE_SCHEMA: Optional[str] = None
|
|
176
|
+
TABLE_NAME: Optional[str] = None
|
|
177
|
+
COLUMN_NAME: Optional[str] = None
|
|
178
|
+
ORDINAL_POSITION: int = 0
|
|
179
|
+
COLUMN_DEFAULT: Optional[str] = None
|
|
180
|
+
IS_NULLABLE: Literal['YES', 'NO'] = 'YES'
|
|
181
|
+
DATA_TYPE: str = MYSQL_DATA_TYPE.VARCHAR.value
|
|
182
|
+
CHARACTER_MAXIMUM_LENGTH: Optional[int] = None
|
|
183
|
+
CHARACTER_OCTET_LENGTH: Optional[int] = None
|
|
184
|
+
NUMERIC_PRECISION: Optional[int] = None
|
|
185
|
+
NUMERIC_SCALE: Optional[int] = None
|
|
186
|
+
DATETIME_PRECISION: Optional[int] = None
|
|
187
|
+
CHARACTER_SET_NAME: Optional[str] = None
|
|
188
|
+
COLLATION_NAME: Optional[str] = None
|
|
189
|
+
COLUMN_TYPE: Optional[str] = None
|
|
190
|
+
COLUMN_KEY: Optional[str] = None
|
|
191
|
+
EXTRA: Optional[str] = None
|
|
192
|
+
PRIVILEGES: str = 'select'
|
|
193
|
+
COLUMN_COMMENT: Optional[str] = None
|
|
194
|
+
GENERATION_EXPRESSION: Optional[str] = None
|
|
195
|
+
|
|
196
|
+
def __post_init__(self):
|
|
197
|
+
# region check mandatory fields
|
|
198
|
+
mandatory_fields = ['TABLE_SCHEMA', 'TABLE_NAME', 'COLUMN_NAME']
|
|
199
|
+
if any(getattr(self, field_name) is None for field_name in mandatory_fields):
|
|
200
|
+
raise ValueError('One of mandatory fields is missed when creating ColumnsTableRow')
|
|
201
|
+
# endregion
|
|
202
|
+
|
|
203
|
+
# region set default values depend on type
|
|
204
|
+
defaults = {
|
|
205
|
+
'COLUMN_TYPE': self.DATA_TYPE
|
|
206
|
+
}
|
|
207
|
+
if MYSQL_DATA_TYPE(self.DATA_TYPE) in (
|
|
208
|
+
MYSQL_DATA_TYPE.TIMESTAMP,
|
|
209
|
+
MYSQL_DATA_TYPE.DATETIME,
|
|
210
|
+
MYSQL_DATA_TYPE.DATE
|
|
211
|
+
):
|
|
212
|
+
defaults = {
|
|
213
|
+
'DATETIME_PRECISION': 0,
|
|
214
|
+
'COLUMN_TYPE': self.DATA_TYPE
|
|
215
|
+
}
|
|
216
|
+
elif MYSQL_DATA_TYPE(self.DATA_TYPE) in (
|
|
217
|
+
MYSQL_DATA_TYPE.FLOAT,
|
|
218
|
+
MYSQL_DATA_TYPE.DOUBLE,
|
|
219
|
+
MYSQL_DATA_TYPE.DECIMAL
|
|
220
|
+
):
|
|
221
|
+
defaults = {
|
|
222
|
+
'NUMERIC_PRECISION': 12,
|
|
223
|
+
'NUMERIC_SCALE': 0,
|
|
224
|
+
'COLUMN_TYPE': self.DATA_TYPE
|
|
225
|
+
}
|
|
226
|
+
elif MYSQL_DATA_TYPE(self.DATA_TYPE) in (
|
|
227
|
+
MYSQL_DATA_TYPE.TINYINT,
|
|
228
|
+
MYSQL_DATA_TYPE.SMALLINT,
|
|
229
|
+
MYSQL_DATA_TYPE.MEDIUMINT,
|
|
230
|
+
MYSQL_DATA_TYPE.INT,
|
|
231
|
+
MYSQL_DATA_TYPE.BIGINT
|
|
232
|
+
):
|
|
233
|
+
defaults = {
|
|
234
|
+
'NUMERIC_PRECISION': 20,
|
|
235
|
+
'NUMERIC_SCALE': 0,
|
|
236
|
+
'COLUMN_TYPE': self.DATA_TYPE
|
|
237
|
+
}
|
|
238
|
+
elif MYSQL_DATA_TYPE(self.DATA_TYPE) is MYSQL_DATA_TYPE.VARCHAR:
|
|
239
|
+
defaults = {
|
|
240
|
+
'CHARACTER_MAXIMUM_LENGTH': 1024,
|
|
241
|
+
'CHARACTER_OCTET_LENGTH': 3072,
|
|
242
|
+
'CHARACTER_SET_NAME': 'utf8',
|
|
243
|
+
'COLLATION_NAME': 'utf8_bin',
|
|
244
|
+
'COLUMN_TYPE': 'varchar(1024)'
|
|
245
|
+
}
|
|
246
|
+
else:
|
|
247
|
+
# show as MYSQL_DATA_TYPE.TEXT:
|
|
248
|
+
defaults = {
|
|
249
|
+
'CHARACTER_MAXIMUM_LENGTH': 65535, # from https://bugs.mysql.com/bug.php?id=90685
|
|
250
|
+
'CHARACTER_OCTET_LENGTH': 65535, #
|
|
251
|
+
'CHARACTER_SET_NAME': 'utf8',
|
|
252
|
+
'COLLATION_NAME': 'utf8_bin',
|
|
253
|
+
'COLUMN_TYPE': 'text'
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
for key, value in defaults.items():
|
|
257
|
+
setattr(self, key, value)
|
|
166
258
|
|
|
259
|
+
self.DATA_TYPE = self.DATA_TYPE.lower()
|
|
260
|
+
self.COLUMN_TYPE = self.COLUMN_TYPE.lower()
|
|
261
|
+
# endregion
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class ColumnsTable(Table):
|
|
167
265
|
name = 'COLUMNS'
|
|
168
|
-
columns = [
|
|
169
|
-
"TABLE_CATALOG",
|
|
170
|
-
"TABLE_SCHEMA",
|
|
171
|
-
"TABLE_NAME",
|
|
172
|
-
"COLUMN_NAME",
|
|
173
|
-
"ORDINAL_POSITION",
|
|
174
|
-
"COLUMN_DEFAULT",
|
|
175
|
-
"IS_NULLABLE",
|
|
176
|
-
"DATA_TYPE",
|
|
177
|
-
"CHARACTER_MAXIMUM_LENGTH",
|
|
178
|
-
"CHARACTER_OCTET_LENGTH",
|
|
179
|
-
"NUMERIC_PRECISION",
|
|
180
|
-
"NUMERIC_SCALE",
|
|
181
|
-
"DATETIME_PRECISION",
|
|
182
|
-
"CHARACTER_SET_NAME",
|
|
183
|
-
"COLLATION_NAME",
|
|
184
|
-
"COLUMN_TYPE",
|
|
185
|
-
"COLUMN_KEY",
|
|
186
|
-
"EXTRA",
|
|
187
|
-
"PRIVILEGES",
|
|
188
|
-
"COLUMN_COMMENT",
|
|
189
|
-
"GENERATION_EXPRESSION",
|
|
190
|
-
]
|
|
266
|
+
columns = [field.name for field in fields(ColumnsTableRow)]
|
|
191
267
|
|
|
192
268
|
@classmethod
|
|
193
269
|
def get_data(cls, inf_schema=None, query: ASTNode = None, **kwargs):
|
|
194
|
-
|
|
195
|
-
# NOTE there is a lot of types in mysql, but listed below should be enough for our purposes
|
|
196
|
-
row_templates = {
|
|
197
|
-
"text": [
|
|
198
|
-
"def",
|
|
199
|
-
"SCHEMA_NAME",
|
|
200
|
-
"TABLE_NAME",
|
|
201
|
-
"COLUMN_NAME",
|
|
202
|
-
"COL_INDEX",
|
|
203
|
-
None,
|
|
204
|
-
"YES",
|
|
205
|
-
"varchar",
|
|
206
|
-
1024,
|
|
207
|
-
3072,
|
|
208
|
-
None,
|
|
209
|
-
None,
|
|
210
|
-
None,
|
|
211
|
-
"utf8",
|
|
212
|
-
"utf8_bin",
|
|
213
|
-
"varchar(1024)",
|
|
214
|
-
None,
|
|
215
|
-
None,
|
|
216
|
-
"select",
|
|
217
|
-
None,
|
|
218
|
-
None,
|
|
219
|
-
],
|
|
220
|
-
"timestamp": [
|
|
221
|
-
"def",
|
|
222
|
-
"SCHEMA_NAME",
|
|
223
|
-
"TABLE_NAME",
|
|
224
|
-
"COLUMN_NAME",
|
|
225
|
-
"COL_INDEX",
|
|
226
|
-
"CURRENT_TIMESTAMP",
|
|
227
|
-
"YES",
|
|
228
|
-
"timestamp",
|
|
229
|
-
None,
|
|
230
|
-
None,
|
|
231
|
-
None,
|
|
232
|
-
None,
|
|
233
|
-
0,
|
|
234
|
-
None,
|
|
235
|
-
None,
|
|
236
|
-
"timestamp",
|
|
237
|
-
None,
|
|
238
|
-
None,
|
|
239
|
-
"select",
|
|
240
|
-
None,
|
|
241
|
-
None,
|
|
242
|
-
],
|
|
243
|
-
"bigint": [
|
|
244
|
-
"def",
|
|
245
|
-
"SCHEMA_NAME",
|
|
246
|
-
"TABLE_NAME",
|
|
247
|
-
"COLUMN_NAME",
|
|
248
|
-
"COL_INDEX",
|
|
249
|
-
None,
|
|
250
|
-
"YES",
|
|
251
|
-
"bigint",
|
|
252
|
-
None,
|
|
253
|
-
None,
|
|
254
|
-
20,
|
|
255
|
-
0,
|
|
256
|
-
None,
|
|
257
|
-
None,
|
|
258
|
-
None,
|
|
259
|
-
"bigint unsigned",
|
|
260
|
-
None,
|
|
261
|
-
None,
|
|
262
|
-
"select",
|
|
263
|
-
None,
|
|
264
|
-
None,
|
|
265
|
-
],
|
|
266
|
-
"float": [
|
|
267
|
-
"def",
|
|
268
|
-
"SCHEMA_NAME",
|
|
269
|
-
"TABLE_NAME",
|
|
270
|
-
"COLUMN_NAME",
|
|
271
|
-
"COL_INDEX",
|
|
272
|
-
None,
|
|
273
|
-
"YES",
|
|
274
|
-
"float",
|
|
275
|
-
None,
|
|
276
|
-
None,
|
|
277
|
-
12,
|
|
278
|
-
0,
|
|
279
|
-
None,
|
|
280
|
-
None,
|
|
281
|
-
None,
|
|
282
|
-
"float",
|
|
283
|
-
None,
|
|
284
|
-
None,
|
|
285
|
-
"select",
|
|
286
|
-
None,
|
|
287
|
-
None,
|
|
288
|
-
],
|
|
289
|
-
}
|
|
290
|
-
|
|
291
270
|
result = []
|
|
292
271
|
|
|
293
272
|
databases, tables_names = _get_scope(query)
|
|
@@ -320,20 +299,34 @@ class ColumnsTable(Table):
|
|
|
320
299
|
for i, column in enumerate(table_columns):
|
|
321
300
|
column_name = column['name']
|
|
322
301
|
column_type = column.get('type', 'text')
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
column_type
|
|
327
|
-
|
|
328
|
-
column_type
|
|
329
|
-
|
|
330
|
-
column_type
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
302
|
+
|
|
303
|
+
# region infer type
|
|
304
|
+
if isinstance(column_type, MYSQL_DATA_TYPE) is False:
|
|
305
|
+
if column_type in ('double precision', 'real', 'numeric', 'float'):
|
|
306
|
+
column_type = MYSQL_DATA_TYPE.FLOAT
|
|
307
|
+
elif column_type in ('integer', 'smallint', 'int', 'bigint'):
|
|
308
|
+
column_type = MYSQL_DATA_TYPE.BIGINT
|
|
309
|
+
elif column_type in (
|
|
310
|
+
'timestamp without time zone',
|
|
311
|
+
'timestamp with time zone',
|
|
312
|
+
'date', 'timestamp'
|
|
313
|
+
):
|
|
314
|
+
column_type = MYSQL_DATA_TYPE.DATETIME
|
|
315
|
+
else:
|
|
316
|
+
column_type = MYSQL_DATA_TYPE.VARCHAR
|
|
317
|
+
# endregion
|
|
318
|
+
|
|
319
|
+
column_row = astuple(
|
|
320
|
+
ColumnsTableRow(
|
|
321
|
+
TABLE_SCHEMA=db_name,
|
|
322
|
+
TABLE_NAME=table_name,
|
|
323
|
+
COLUMN_NAME=column_name,
|
|
324
|
+
DATA_TYPE=column_type.value,
|
|
325
|
+
ORDINAL_POSITION=i
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
result.append(column_row)
|
|
337
330
|
|
|
338
331
|
df = pd.DataFrame(result, columns=cls.columns)
|
|
339
332
|
return df
|
|
File without changes
|
mindsdb/api/mcp/start.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from collections.abc import AsyncIterator
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
from mindsdb.api.mysql.mysql_proxy.classes.fake_mysql_proxy import FakeMysqlProxy
|
|
8
|
+
from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE as SQL_RESPONSE_TYPE
|
|
9
|
+
from mindsdb.utilities import log
|
|
10
|
+
from mindsdb.utilities.config import Config
|
|
11
|
+
from mindsdb.interfaces.storage import db
|
|
12
|
+
|
|
13
|
+
logger = log.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AppContext:
|
|
18
|
+
db: Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@asynccontextmanager
|
|
22
|
+
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
|
23
|
+
"""Manage application lifecycle with type-safe context"""
|
|
24
|
+
# Initialize on startup
|
|
25
|
+
db.init()
|
|
26
|
+
try:
|
|
27
|
+
yield AppContext(db=db)
|
|
28
|
+
finally:
|
|
29
|
+
# TODO: We need better way to handle this in storage/db.py
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Configure server with lifespan
|
|
34
|
+
mcp = FastMCP(
|
|
35
|
+
"MindsDB",
|
|
36
|
+
lifespan=app_lifespan,
|
|
37
|
+
dependencies=["mindsdb"] # Add any additional dependencies
|
|
38
|
+
)
|
|
39
|
+
# MCP Queries
|
|
40
|
+
LISTING_QUERY = "SHOW DATABASES"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@mcp.tool()
|
|
44
|
+
def query(query: str, context: Optional[Dict] = None) -> Dict[str, Any]:
|
|
45
|
+
"""
|
|
46
|
+
Execute a SQL query against MindsDB
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
query: The SQL query to execute
|
|
50
|
+
context: Optional context parameters for the query
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Dict containing the query results or error information
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
if context is None:
|
|
57
|
+
context = {}
|
|
58
|
+
|
|
59
|
+
logger.debug(f'Incoming MCP query: {query}')
|
|
60
|
+
|
|
61
|
+
mysql_proxy = FakeMysqlProxy()
|
|
62
|
+
mysql_proxy.set_context(context)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
result = mysql_proxy.process_query(query)
|
|
66
|
+
|
|
67
|
+
if result.type == SQL_RESPONSE_TYPE.OK:
|
|
68
|
+
return {"type": SQL_RESPONSE_TYPE.OK}
|
|
69
|
+
|
|
70
|
+
if result.type == SQL_RESPONSE_TYPE.TABLE:
|
|
71
|
+
return {
|
|
72
|
+
"type": SQL_RESPONSE_TYPE.TABLE,
|
|
73
|
+
"data": result.data.to_lists(json_types=True),
|
|
74
|
+
"column_names": [
|
|
75
|
+
x["alias"] or x["name"] if "alias" in x else x["name"]
|
|
76
|
+
for x in result.columns
|
|
77
|
+
],
|
|
78
|
+
}
|
|
79
|
+
else:
|
|
80
|
+
return {
|
|
81
|
+
"type": SQL_RESPONSE_TYPE.ERROR,
|
|
82
|
+
"error_code": 0,
|
|
83
|
+
"error_message": "Unknown response type"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Error processing query: {str(e)}")
|
|
88
|
+
return {
|
|
89
|
+
"type": SQL_RESPONSE_TYPE.ERROR,
|
|
90
|
+
"error_code": 0,
|
|
91
|
+
"error_message": str(e)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@mcp.tool()
|
|
96
|
+
def list_databases() -> Dict[str, Any]:
|
|
97
|
+
"""
|
|
98
|
+
List all databases in MindsDB along with their tables
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Dict containing the list of databases and their tables
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
mysql_proxy = FakeMysqlProxy()
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
result = mysql_proxy.process_query(LISTING_QUERY)
|
|
108
|
+
if result.type == SQL_RESPONSE_TYPE.ERROR:
|
|
109
|
+
return {
|
|
110
|
+
"type": "error",
|
|
111
|
+
"error_code": result.error_code,
|
|
112
|
+
"error_message": result.error_message,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
elif result.type == SQL_RESPONSE_TYPE.OK:
|
|
116
|
+
return {"type": "ok"}
|
|
117
|
+
|
|
118
|
+
elif result.type == SQL_RESPONSE_TYPE.TABLE:
|
|
119
|
+
data = result.data.to_lists(json_types=True)
|
|
120
|
+
return data
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
return {
|
|
124
|
+
"type": "error",
|
|
125
|
+
"error_code": 0,
|
|
126
|
+
"error_message": str(e),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def start(*args, **kwargs):
|
|
131
|
+
"""Start the MCP server
|
|
132
|
+
Args:
|
|
133
|
+
host (str): Host to bind to
|
|
134
|
+
port (int): Port to listen on
|
|
135
|
+
"""
|
|
136
|
+
config = Config()
|
|
137
|
+
port = int(config['api'].get('mcp', {}).get('port', 47337))
|
|
138
|
+
host = config['api'].get('mcp', {}).get('host', '127.0.0.1')
|
|
139
|
+
|
|
140
|
+
logger.info(f"Starting MCP server on {host}:{port}")
|
|
141
|
+
mcp.settings.host = host
|
|
142
|
+
mcp.settings.port = port
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
mcp.run(transport="sse") # Use SSE transport instead of stdio
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"Error starting MCP server: {str(e)}")
|
|
148
|
+
raise
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
start()
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* permission of MindsDB Inc
|
|
9
9
|
*******************************************************
|
|
10
10
|
"""
|
|
11
|
+
import enum
|
|
11
12
|
|
|
12
13
|
# CAPABILITIES
|
|
13
14
|
# As defined in : https://dev.mysql.com/doc/dev/mysql-server/8.0.0/group__group__cs__capabilities__flags.html
|
|
@@ -101,6 +102,8 @@ COMMANDS = COMMANDS()
|
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
# FIELD TYPES
|
|
105
|
+
# https://dev.mysql.com/doc/dev/mysql-server/latest/field__types_8h_source.html
|
|
106
|
+
# https://mariadb.com/kb/en/result-set-packets/
|
|
104
107
|
class TYPES(object):
|
|
105
108
|
__slots__ = ()
|
|
106
109
|
MYSQL_TYPE_DECIMAL = 0
|
|
@@ -123,6 +126,11 @@ class TYPES(object):
|
|
|
123
126
|
MYSQL_TYPE_TIMESTAMP2 = 17
|
|
124
127
|
MYSQL_TYPE_DATETIME2 = 18
|
|
125
128
|
MYSQL_TYPE_TIME2 = 19
|
|
129
|
+
MYSQL_TYPE_TYPED_ARRAY = 20
|
|
130
|
+
MYSQL_TYPE_VECTOR = 242,
|
|
131
|
+
MYSQL_TYPE_INVALID = 243,
|
|
132
|
+
MYSQL_TYPE_BOOL = 244,
|
|
133
|
+
MYSQL_TYPE_JSON = 245,
|
|
126
134
|
MYSQL_TYPE_NEWDECIMAL = 246
|
|
127
135
|
MYSQL_TYPE_ENUM = 247
|
|
128
136
|
MYSQL_TYPE_SET = 248
|
|
@@ -135,9 +143,75 @@ class TYPES(object):
|
|
|
135
143
|
MYSQL_TYPE_GEOMETRY = 255
|
|
136
144
|
|
|
137
145
|
|
|
146
|
+
C_TYPES = TYPES()
|
|
138
147
|
TYPES = TYPES()
|
|
139
148
|
|
|
140
149
|
|
|
150
|
+
class MYSQL_DATA_TYPE(enum.Enum):
|
|
151
|
+
TINYINT = 'TINYINT'
|
|
152
|
+
SMALLINT = 'SMALLINT'
|
|
153
|
+
MEDIUMINT = 'MEDIUMINT'
|
|
154
|
+
INT = 'INT'
|
|
155
|
+
BIGINT = 'BIGINT'
|
|
156
|
+
FLOAT = 'FLOAT'
|
|
157
|
+
DOUBLE = 'DOUBLE'
|
|
158
|
+
DECIMAL = 'DECIMAL'
|
|
159
|
+
YEAR = 'YEAR'
|
|
160
|
+
TIME = 'TIME'
|
|
161
|
+
DATE = 'DATE'
|
|
162
|
+
DATETIME = 'DATETIME'
|
|
163
|
+
TIMESTAMP = 'TIMESTAMP'
|
|
164
|
+
CHAR = 'CHAR'
|
|
165
|
+
BINARY = 'BINARY'
|
|
166
|
+
VARCHAR = 'VARCHAR'
|
|
167
|
+
VARBINARY = 'VARBINARY'
|
|
168
|
+
TINYBLOB = 'TINYBLOB'
|
|
169
|
+
TINYTEXT = 'TINYTEXT'
|
|
170
|
+
BLOB = 'BLOB'
|
|
171
|
+
TEXT = 'TEXT'
|
|
172
|
+
MEDIUMBLOB = 'MEDIUMBLOB'
|
|
173
|
+
MEDIUMTEXT = 'MEDIUMTEXT'
|
|
174
|
+
LONGBLOB = 'LONGBLOB'
|
|
175
|
+
LONGTEXT = 'LONGTEXT'
|
|
176
|
+
BIT = 'BIT'
|
|
177
|
+
BOOL = 'BOOL'
|
|
178
|
+
BOOLEAN = 'BOOLEAN'
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# Map between data types and C types
|
|
182
|
+
# https://dev.mysql.com/doc/c-api/8.0/en/c-api-prepared-statement-type-codes.html
|
|
183
|
+
DATA_C_TYPE_MAP = {
|
|
184
|
+
MYSQL_DATA_TYPE.TINYINT: C_TYPES.MYSQL_TYPE_TINY,
|
|
185
|
+
MYSQL_DATA_TYPE.SMALLINT: C_TYPES.MYSQL_TYPE_SHORT,
|
|
186
|
+
MYSQL_DATA_TYPE.MEDIUMINT: C_TYPES.MYSQL_TYPE_INT24,
|
|
187
|
+
MYSQL_DATA_TYPE.INT: C_TYPES.MYSQL_TYPE_LONG,
|
|
188
|
+
MYSQL_DATA_TYPE.BIGINT: C_TYPES.MYSQL_TYPE_LONGLONG,
|
|
189
|
+
MYSQL_DATA_TYPE.FLOAT: C_TYPES.MYSQL_TYPE_FLOAT,
|
|
190
|
+
MYSQL_DATA_TYPE.DOUBLE: C_TYPES.MYSQL_TYPE_DOUBLE,
|
|
191
|
+
MYSQL_DATA_TYPE.DECIMAL: C_TYPES.MYSQL_TYPE_NEWDECIMAL,
|
|
192
|
+
MYSQL_DATA_TYPE.YEAR: C_TYPES.MYSQL_TYPE_SHORT,
|
|
193
|
+
MYSQL_DATA_TYPE.TIME: C_TYPES.MYSQL_TYPE_TIME,
|
|
194
|
+
MYSQL_DATA_TYPE.DATE: C_TYPES.MYSQL_TYPE_DATE,
|
|
195
|
+
MYSQL_DATA_TYPE.DATETIME: C_TYPES.MYSQL_TYPE_DATETIME,
|
|
196
|
+
MYSQL_DATA_TYPE.TIMESTAMP: C_TYPES.MYSQL_TYPE_TIMESTAMP,
|
|
197
|
+
MYSQL_DATA_TYPE.CHAR: C_TYPES.MYSQL_TYPE_STRING,
|
|
198
|
+
MYSQL_DATA_TYPE.BINARY: C_TYPES.MYSQL_TYPE_STRING,
|
|
199
|
+
MYSQL_DATA_TYPE.VARCHAR: C_TYPES.MYSQL_TYPE_VAR_STRING,
|
|
200
|
+
MYSQL_DATA_TYPE.VARBINARY: C_TYPES.MYSQL_TYPE_VAR_STRING,
|
|
201
|
+
MYSQL_DATA_TYPE.TINYBLOB: C_TYPES.MYSQL_TYPE_TINY_BLOB,
|
|
202
|
+
MYSQL_DATA_TYPE.TINYTEXT: C_TYPES.MYSQL_TYPE_TINY_BLOB,
|
|
203
|
+
MYSQL_DATA_TYPE.BLOB: C_TYPES.MYSQL_TYPE_BLOB,
|
|
204
|
+
MYSQL_DATA_TYPE.TEXT: C_TYPES.MYSQL_TYPE_BLOB,
|
|
205
|
+
MYSQL_DATA_TYPE.MEDIUMBLOB: C_TYPES.MYSQL_TYPE_MEDIUM_BLOB,
|
|
206
|
+
MYSQL_DATA_TYPE.MEDIUMTEXT: C_TYPES.MYSQL_TYPE_MEDIUM_BLOB,
|
|
207
|
+
MYSQL_DATA_TYPE.LONGBLOB: C_TYPES.MYSQL_TYPE_LONG_BLOB,
|
|
208
|
+
MYSQL_DATA_TYPE.LONGTEXT: C_TYPES.MYSQL_TYPE_LONG_BLOB,
|
|
209
|
+
MYSQL_DATA_TYPE.BIT: C_TYPES.MYSQL_TYPE_BIT,
|
|
210
|
+
MYSQL_DATA_TYPE.BOOL: C_TYPES.MYSQL_TYPE_TINY,
|
|
211
|
+
MYSQL_DATA_TYPE.BOOLEAN: C_TYPES.MYSQL_TYPE_TINY
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
141
215
|
class FIELD_FLAG(object):
|
|
142
216
|
__slots__ = ()
|
|
143
217
|
NOT_NULL = 1 # field cannot be null
|
|
@@ -149,8 +149,20 @@ class ConfluenceAPIClient:
|
|
|
149
149
|
results.extend(response["results"])
|
|
150
150
|
|
|
151
151
|
while response["_links"].get("next"):
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
next_url = response["_links"].get("next")
|
|
153
|
+
next_params = {}
|
|
154
|
+
if params:
|
|
155
|
+
next_params.update(params)
|
|
156
|
+
if "cursor=" in next_url:
|
|
157
|
+
# cursor= is 7 characters long
|
|
158
|
+
cursor_start = next_url.find("cursor=") + 7
|
|
159
|
+
cursor_value = next_url[cursor_start:]
|
|
160
|
+
if "&" in cursor_value:
|
|
161
|
+
cursor_value = cursor_value.split("&")[0]
|
|
162
|
+
next_params["cursor"] = cursor_value
|
|
163
|
+
response = self._make_request("GET", url, next_params)
|
|
164
|
+
else:
|
|
165
|
+
response = self._make_request("GET", next_url)
|
|
154
166
|
results.extend(response["results"])
|
|
155
167
|
|
|
156
168
|
return results
|