MindsDB 25.8.3.0__py3-none-any.whl → 25.9.1.1__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.

Files changed (109) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +3 -45
  3. mindsdb/api/a2a/__init__.py +52 -0
  4. mindsdb/api/a2a/agent.py +11 -12
  5. mindsdb/api/a2a/common/server/server.py +17 -36
  6. mindsdb/api/a2a/common/server/task_manager.py +14 -28
  7. mindsdb/api/a2a/task_manager.py +20 -21
  8. mindsdb/api/a2a/utils.py +1 -1
  9. mindsdb/api/common/middleware.py +106 -0
  10. mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +466 -18
  11. mindsdb/api/executor/utilities/sql.py +9 -31
  12. mindsdb/api/http/initialize.py +34 -43
  13. mindsdb/api/http/namespaces/auth.py +6 -14
  14. mindsdb/api/http/namespaces/config.py +0 -2
  15. mindsdb/api/http/namespaces/default.py +74 -106
  16. mindsdb/api/http/namespaces/file.py +9 -3
  17. mindsdb/api/http/namespaces/handlers.py +77 -87
  18. mindsdb/api/http/start.py +29 -47
  19. mindsdb/api/litellm/start.py +11 -10
  20. mindsdb/api/mcp/__init__.py +165 -0
  21. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +33 -64
  22. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +86 -85
  23. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  24. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  25. mindsdb/integrations/handlers/crate_handler/crate_handler.py +3 -7
  26. mindsdb/integrations/handlers/derby_handler/derby_handler.py +32 -34
  27. mindsdb/integrations/handlers/documentdb_handler/requirements.txt +1 -0
  28. mindsdb/integrations/handlers/dummy_data_handler/dummy_data_handler.py +12 -13
  29. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  30. mindsdb/integrations/handlers/google_books_handler/google_books_handler.py +45 -44
  31. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +101 -95
  32. mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_handler.py +129 -129
  33. mindsdb/integrations/handlers/google_fit_handler/google_fit_handler.py +59 -43
  34. mindsdb/integrations/handlers/google_search_handler/google_search_handler.py +38 -39
  35. mindsdb/integrations/handlers/informix_handler/informix_handler.py +5 -18
  36. mindsdb/integrations/handlers/lightfm_handler/requirements.txt +1 -1
  37. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  38. mindsdb/integrations/handlers/maxdb_handler/maxdb_handler.py +22 -28
  39. mindsdb/integrations/handlers/monetdb_handler/monetdb_handler.py +3 -7
  40. mindsdb/integrations/handlers/mongodb_handler/mongodb_handler.py +53 -67
  41. mindsdb/integrations/handlers/mongodb_handler/requirements.txt +1 -0
  42. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_ast.py +43 -68
  43. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_parser.py +17 -25
  44. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_query.py +10 -16
  45. mindsdb/integrations/handlers/mongodb_handler/utils/mongodb_render.py +43 -69
  46. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  47. mindsdb/integrations/libs/base.py +1 -1
  48. mindsdb/integrations/libs/llm/config.py +15 -0
  49. mindsdb/integrations/libs/llm/utils.py +15 -0
  50. mindsdb/interfaces/agents/constants.py +1 -0
  51. mindsdb/interfaces/agents/langchain_agent.py +4 -0
  52. mindsdb/interfaces/agents/providers.py +20 -0
  53. mindsdb/interfaces/knowledge_base/controller.py +25 -7
  54. mindsdb/utilities/config.py +15 -158
  55. mindsdb/utilities/log.py +0 -25
  56. mindsdb/utilities/render/sqlalchemy_render.py +7 -1
  57. mindsdb/utilities/starters.py +0 -39
  58. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/METADATA +269 -267
  59. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/RECORD +62 -105
  60. mindsdb/api/a2a/__main__.py +0 -144
  61. mindsdb/api/a2a/run_a2a.py +0 -86
  62. mindsdb/api/common/check_auth.py +0 -42
  63. mindsdb/api/http/gunicorn_wrapper.py +0 -17
  64. mindsdb/api/mcp/start.py +0 -205
  65. mindsdb/api/mongo/__init__.py +0 -0
  66. mindsdb/api/mongo/classes/__init__.py +0 -5
  67. mindsdb/api/mongo/classes/query_sql.py +0 -19
  68. mindsdb/api/mongo/classes/responder.py +0 -45
  69. mindsdb/api/mongo/classes/responder_collection.py +0 -34
  70. mindsdb/api/mongo/classes/scram.py +0 -86
  71. mindsdb/api/mongo/classes/session.py +0 -23
  72. mindsdb/api/mongo/functions/__init__.py +0 -19
  73. mindsdb/api/mongo/responders/__init__.py +0 -73
  74. mindsdb/api/mongo/responders/add_shard.py +0 -13
  75. mindsdb/api/mongo/responders/aggregate.py +0 -90
  76. mindsdb/api/mongo/responders/buildinfo.py +0 -17
  77. mindsdb/api/mongo/responders/coll_stats.py +0 -63
  78. mindsdb/api/mongo/responders/company_id.py +0 -25
  79. mindsdb/api/mongo/responders/connection_status.py +0 -22
  80. mindsdb/api/mongo/responders/count.py +0 -21
  81. mindsdb/api/mongo/responders/db_stats.py +0 -32
  82. mindsdb/api/mongo/responders/delete.py +0 -105
  83. mindsdb/api/mongo/responders/describe.py +0 -23
  84. mindsdb/api/mongo/responders/end_sessions.py +0 -13
  85. mindsdb/api/mongo/responders/find.py +0 -175
  86. mindsdb/api/mongo/responders/get_cmd_line_opts.py +0 -18
  87. mindsdb/api/mongo/responders/get_free_monitoring_status.py +0 -14
  88. mindsdb/api/mongo/responders/get_parameter.py +0 -23
  89. mindsdb/api/mongo/responders/getlog.py +0 -14
  90. mindsdb/api/mongo/responders/host_info.py +0 -28
  91. mindsdb/api/mongo/responders/insert.py +0 -270
  92. mindsdb/api/mongo/responders/is_master.py +0 -20
  93. mindsdb/api/mongo/responders/is_master_lower.py +0 -13
  94. mindsdb/api/mongo/responders/list_collections.py +0 -55
  95. mindsdb/api/mongo/responders/list_databases.py +0 -37
  96. mindsdb/api/mongo/responders/list_indexes.py +0 -22
  97. mindsdb/api/mongo/responders/ping.py +0 -13
  98. mindsdb/api/mongo/responders/recv_chunk_start.py +0 -13
  99. mindsdb/api/mongo/responders/replsetgetstatus.py +0 -13
  100. mindsdb/api/mongo/responders/sasl_continue.py +0 -34
  101. mindsdb/api/mongo/responders/sasl_start.py +0 -33
  102. mindsdb/api/mongo/responders/update_range_deletions.py +0 -12
  103. mindsdb/api/mongo/responders/whatsmyuri.py +0 -18
  104. mindsdb/api/mongo/server.py +0 -388
  105. mindsdb/api/mongo/start.py +0 -15
  106. mindsdb/api/mongo/utilities/__init__.py +0 -0
  107. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/WHEEL +0 -0
  108. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/licenses/LICENSE +0 -0
  109. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/top_level.txt +0 -0
@@ -19,96 +19,94 @@ from mindsdb.api.http.utils import http_error
19
19
  from mindsdb.api.http.namespaces.configs.handlers import ns_conf
20
20
  from mindsdb.api.executor.controllers.session_controller import SessionController
21
21
  from mindsdb.api.executor.command_executor import ExecuteCommands
22
- from mindsdb.utilities.config import Config
23
22
 
24
23
 
25
- @ns_conf.route('/')
24
+ @ns_conf.route("/")
26
25
  class HandlersList(Resource):
27
- @ns_conf.doc('handlers_list')
28
- @api_endpoint_metrics('GET', '/handlers')
26
+ @ns_conf.doc("handlers_list")
27
+ @api_endpoint_metrics("GET", "/handlers")
29
28
  def get(self):
30
- '''List all db handlers'''
29
+ """List all db handlers"""
31
30
 
32
- if request.args.get('lazy') == '1':
31
+ if request.args.get("lazy") == "1":
33
32
  handlers = ca.integration_controller.get_handlers_metadata()
34
33
  else:
35
34
  handlers = ca.integration_controller.get_handlers_import_status()
36
35
  result = []
37
36
  for handler_type, handler_meta in handlers.items():
38
37
  # remove non-integration handlers
39
- if handler_type not in ['utilities', 'dummy_data']:
40
- row = {'name': handler_type}
38
+ if handler_type not in ["utilities", "dummy_data"]:
39
+ row = {"name": handler_type}
41
40
  row.update(handler_meta)
42
- del row['path']
41
+ del row["path"]
43
42
  result.append(row)
44
43
  return result
45
44
 
46
45
 
47
- @ns_conf.route('/<handler_name>/icon')
46
+ @ns_conf.route("/<handler_name>/icon")
48
47
  class HandlerIcon(Resource):
49
- @ns_conf.param('handler_name', 'Handler name')
50
- @api_endpoint_metrics('GET', '/handlers/handler/icon')
48
+ @ns_conf.param("handler_name", "Handler name")
49
+ @api_endpoint_metrics("GET", "/handlers/handler/icon")
51
50
  def get(self, handler_name):
52
51
  try:
53
52
  handler_meta = ca.integration_controller.get_handlers_metadata().get(handler_name)
54
53
  if handler_meta is None:
55
- return http_error(HTTPStatus.NOT_FOUND, 'Icon not found', f'Icon for {handler_name} not found')
56
- icon_name = handler_meta['icon']['name']
57
- handler_folder = handler_meta['import']['folder']
58
- mindsdb_path = Path(importlib.util.find_spec('mindsdb').origin).parent
59
- icon_path = mindsdb_path.joinpath('integrations/handlers').joinpath(handler_folder).joinpath(icon_name)
54
+ return http_error(HTTPStatus.NOT_FOUND, "Icon not found", f"Icon for {handler_name} not found")
55
+ icon_name = handler_meta["icon"]["name"]
56
+ handler_folder = handler_meta["import"]["folder"]
57
+ mindsdb_path = Path(importlib.util.find_spec("mindsdb").origin).parent
58
+ icon_path = mindsdb_path.joinpath("integrations/handlers").joinpath(handler_folder).joinpath(icon_name)
60
59
  if icon_path.is_absolute() is False:
61
60
  icon_path = Path(os.getcwd()).joinpath(icon_path)
62
61
  except Exception:
63
- return http_error(HTTPStatus.NOT_FOUND, 'Icon not found', f'Icon for {handler_name} not found')
62
+ return http_error(HTTPStatus.NOT_FOUND, "Icon not found", f"Icon for {handler_name} not found")
64
63
  else:
65
64
  return send_file(icon_path)
66
65
 
67
66
 
68
- @ns_conf.route('/<handler_name>')
67
+ @ns_conf.route("/<handler_name>")
69
68
  class HandlerInfo(Resource):
70
- @ns_conf.param('handler_name', 'Handler name')
71
- @api_endpoint_metrics('GET', '/handlers/handler')
69
+ @ns_conf.param("handler_name", "Handler name")
70
+ @api_endpoint_metrics("GET", "/handlers/handler")
72
71
  def get(self, handler_name):
73
-
74
72
  handler_meta = ca.integration_controller.get_handler_meta(handler_name)
75
- row = {'name': handler_name}
73
+ row = {"name": handler_name}
76
74
  row.update(handler_meta)
77
- del row['path']
78
- del row['icon']
75
+ del row["path"]
76
+ del row["icon"]
79
77
  return row
80
78
 
81
79
 
82
- @ns_conf.route('/<handler_name>/install')
80
+ @ns_conf.route("/<handler_name>/install")
83
81
  class InstallDependencies(Resource):
84
- @ns_conf.param('handler_name', 'Handler name')
85
- @api_endpoint_metrics('POST', '/handlers/handler/install')
82
+ @ns_conf.param("handler_name", "Handler name")
83
+ @api_endpoint_metrics("POST", "/handlers/handler/install")
86
84
  def post(self, handler_name):
87
85
  handler_meta = ca.integration_controller.get_handler_meta(handler_name)
88
86
 
89
87
  if handler_meta is None:
90
- return f'Unknown handler: {handler_name}', 400
88
+ return f"Unknown handler: {handler_name}", 400
91
89
 
92
- if handler_meta.get('import', {}).get('success', False) is True:
93
- return 'Installed', 200
90
+ if handler_meta.get("import", {}).get("success", False) is True:
91
+ return "Installed", 200
94
92
 
95
- dependencies = handler_meta['import']['dependencies']
93
+ dependencies = handler_meta["import"]["dependencies"]
96
94
  if len(dependencies) == 0:
97
- return 'Installed', 200
95
+ return "Installed", 200
98
96
 
99
97
  result = install_dependencies(dependencies)
100
98
 
101
99
  # reload it if any result, so we can get new error message
102
100
  ca.integration_controller.reload_handler_module(handler_name)
103
- if result.get('success') is True:
101
+ if result.get("success") is True:
104
102
  # If warm processes are available in the cache, remove them.
105
103
  # This will force a new process to be created with the installed dependencies.
106
104
  process_cache.remove_processes_for_handler(handler_name)
107
- return '', 200
105
+ return "", 200
108
106
  return http_error(
109
107
  500,
110
- f'Failed to install dependencies for {handler_meta.get("title", handler_name)}',
111
- result.get('error_message', 'unknown error')
108
+ f"Failed to install dependencies for {handler_meta.get('title', handler_name)}",
109
+ result.get("error_message", "unknown error"),
112
110
  )
113
111
 
114
112
 
@@ -122,24 +120,24 @@ def prepare_formdata():
122
120
  params[name] = value
123
121
 
124
122
  def on_file(file):
125
- params[file.field_name.decode()] = file.file_object
126
- file_names.append(file.field_name.decode())
123
+ file_name = file.field_name.decode()
124
+ if file_name not in ("code", "modules"):
125
+ raise ValueError(f"Wrong field name: {file_name}")
126
+ params[file_name] = file.file_object
127
+ file_names.append(file_name)
127
128
 
128
- temp_dir_path = temp_dir_path = tempfile.mkdtemp(
129
- prefix='mindsdb_byom_file_',
130
- dir=Config().paths['tmp']
131
- )
129
+ temp_dir_path = tempfile.mkdtemp(prefix="mindsdb_file_")
132
130
 
133
131
  parser = multipart.create_form_parser(
134
132
  headers=request.headers,
135
133
  on_field=on_field,
136
134
  on_file=on_file,
137
135
  config={
138
- 'UPLOAD_DIR': temp_dir_path.encode(), # bytes required
139
- 'UPLOAD_KEEP_FILENAME': True,
140
- 'UPLOAD_KEEP_EXTENSIONS': True,
141
- 'MAX_MEMORY_FILE_SIZE': 0
142
- }
136
+ "UPLOAD_DIR": temp_dir_path.encode(), # bytes required
137
+ "UPLOAD_KEEP_FILENAME": True,
138
+ "UPLOAD_KEEP_EXTENSIONS": True,
139
+ "MAX_MEMORY_FILE_SIZE": float("inf"),
140
+ },
143
141
  )
144
142
 
145
143
  while True:
@@ -151,32 +149,33 @@ def prepare_formdata():
151
149
  parser.close()
152
150
 
153
151
  for file_name in file_names:
152
+ file_path = os.path.join(temp_dir_path, file_name)
153
+ with open(file_path, "wb") as f:
154
+ params[file_name].seek(0)
155
+ f.write(params[file_name].read())
154
156
  params[file_name].close()
157
+ params[file_name] = file_path
155
158
 
156
159
  return params
157
160
 
158
161
 
159
- @ns_conf.route('/byom/<name>')
160
- @ns_conf.param('name', "Name of the model")
162
+ @ns_conf.route("/byom/<name>")
163
+ @ns_conf.param("name", "Name of the model")
161
164
  class BYOMUpload(Resource):
162
- @ns_conf.doc('post_file')
163
- @api_endpoint_metrics('POST', '/handlers/byom/handler')
165
+ @ns_conf.doc("post_file")
166
+ @api_endpoint_metrics("POST", "/handlers/byom/handler")
164
167
  def post(self, name):
165
168
  params = prepare_formdata()
166
169
 
167
- code_file_path = params['code'].name.decode()
170
+ code_file_path = params["code"]
168
171
  try:
169
- module_file_path = params['modules'].name.decode()
172
+ module_file_path = params["modules"]
170
173
  except AttributeError:
171
- module_file_path = Path(code_file_path).parent / 'requirements.txt'
174
+ module_file_path = Path(code_file_path).parent / "requirements.txt"
172
175
  module_file_path.touch()
173
176
  module_file_path = str(module_file_path)
174
177
 
175
- connection_args = {
176
- 'code': code_file_path,
177
- 'modules': module_file_path,
178
- 'type': params.get('type')
179
- }
178
+ connection_args = {"code": code_file_path, "modules": module_file_path, "type": params.get("type")}
180
179
 
181
180
  session = SessionController()
182
181
 
@@ -185,48 +184,39 @@ class BYOMUpload(Resource):
185
184
 
186
185
  engine_storage = HandlerStorage(base_ml_handler.integration_id)
187
186
 
188
- engine_versions = [
189
- int(x) for x in engine_storage.get_connection_args()['versions'].keys()
190
- ]
187
+ engine_versions = [int(x) for x in engine_storage.get_connection_args()["versions"].keys()]
191
188
 
192
- return {
193
- 'last_engine_version': max(engine_versions),
194
- 'engine_versions': engine_versions
195
- }
189
+ return {"last_engine_version": max(engine_versions), "engine_versions": engine_versions}
196
190
 
197
- @ns_conf.doc('put_file')
198
- @api_endpoint_metrics('PUT', '/handlers/byom/handler')
191
+ @ns_conf.doc("put_file")
192
+ @api_endpoint_metrics("PUT", "/handlers/byom/handler")
199
193
  def put(self, name):
200
- ''' upload new model
201
- params in FormData:
202
- - code
203
- - modules
204
- '''
194
+ """upload new model
195
+ params in FormData:
196
+ - code
197
+ - modules
198
+ """
205
199
 
206
200
  params = prepare_formdata()
207
201
 
208
- code_file_path = params['code'].name.decode()
202
+ code_file_path = params["code"]
209
203
  try:
210
- module_file_path = params['modules'].name.decode()
204
+ module_file_path = params["modules"]
211
205
  except KeyError:
212
- module_file_path = Path(code_file_path).parent / 'requirements.txt'
206
+ module_file_path = Path(code_file_path).parent / "requirements.txt"
213
207
  module_file_path.touch()
214
208
  module_file_path = str(module_file_path)
215
209
 
216
210
  connection_args = {
217
- 'code': code_file_path,
218
- 'modules': module_file_path,
219
- 'mode': params.get('mode'),
220
- 'type': params.get('type')
211
+ "code": code_file_path,
212
+ "modules": module_file_path,
213
+ "mode": params.get("mode"),
214
+ "type": params.get("type"),
221
215
  }
222
216
 
223
- ast_query = CreateMLEngine(
224
- name=Identifier(name),
225
- handler='byom',
226
- params=connection_args
227
- )
217
+ ast_query = CreateMLEngine(name=Identifier(name), handler="byom", params=connection_args)
228
218
  sql_session = SessionController()
229
219
  command_executor = ExecuteCommands(sql_session)
230
220
  command_executor.execute_command(ast_query)
231
221
 
232
- return '', 200
222
+ return "", 200
mindsdb/api/http/start.py CHANGED
@@ -1,8 +1,8 @@
1
1
  import gc
2
+
2
3
  gc.disable()
3
4
 
4
5
  from flask import Flask
5
- from waitress import serve
6
6
 
7
7
  from mindsdb.api.http.initialize import initialize_app
8
8
  from mindsdb.interfaces.storage import db
@@ -10,63 +10,45 @@ from mindsdb.utilities import log
10
10
  from mindsdb.utilities.config import config
11
11
  from mindsdb.utilities.functions import init_lexer_parsers
12
12
  from mindsdb.integrations.libs.ml_exec_base import process_cache
13
+ from mindsdb.api.common.middleware import PATAuthMiddleware
14
+
15
+
16
+ from starlette.applications import Starlette
17
+ from starlette.routing import Mount
18
+ from starlette.middleware.wsgi import WSGIMiddleware
19
+ import uvicorn
20
+
21
+ from mindsdb.api.a2a import get_a2a_app
22
+ from mindsdb.api.mcp import get_mcp_app
13
23
 
14
24
  gc.enable()
15
25
 
16
26
  logger = log.getLogger(__name__)
17
27
 
18
28
 
19
- def start(verbose, no_studio, app: Flask = None):
29
+ def start(verbose, app: Flask = None):
20
30
  db.init()
21
31
  init_lexer_parsers()
22
32
 
23
33
  if app is None:
24
- app = initialize_app(config, no_studio)
34
+ app = initialize_app()
25
35
 
26
- port = config['api']['http']['port']
27
- host = config['api']['http']['host']
28
- server_type = config['api']['http']['server']['type']
29
- server_config = config['api']['http']['server']['config']
36
+ port = config["api"]["http"]["port"]
37
+ host = config["api"]["http"]["host"]
30
38
 
31
39
  process_cache.init()
32
40
 
33
- if server_type == "waitress":
34
- logger.debug("Serving HTTP app with waitress...")
35
- serve(
36
- app,
37
- host='*' if host in ('', '0.0.0.0') else host,
38
- port=port,
39
- **server_config
40
- )
41
- elif server_type == "flask":
42
- logger.debug("Serving HTTP app with flask...")
43
- # that will 'disable access' log in console
44
-
45
- app.run(debug=False, port=port, host=host, **server_config)
46
- elif server_type == 'gunicorn':
47
- try:
48
- from mindsdb.api.http.gunicorn_wrapper import StandaloneApplication
49
- except ImportError:
50
- logger.error(
51
- "Gunicorn server is not available by default. If you wish to use it, please install 'gunicorn'"
52
- )
53
- return
54
-
55
- def post_fork(arbiter, worker):
56
- db.engine.dispose()
57
-
58
- def before_worker_exit(arbiter, worker):
59
- """Latest version of gunicorn (23.0.0) calls 'join' for each child process before exiting. However this does
60
- not work for processes created by ProcessPoolExecutor, because they execute forever. We need to explicitly
61
- call 'shutdown' for such processes before exiting.
62
- """
63
- from mindsdb.integrations.libs.process_cache import process_cache
64
- process_cache.shutdown(wait=True)
65
-
66
- options = {
67
- 'bind': f'{host}:{port}',
68
- 'post_fork': post_fork,
69
- 'worker_exit': before_worker_exit,
70
- **server_config
71
- }
72
- StandaloneApplication(app, options).run()
41
+ routes = []
42
+ # Specific mounts FIRST
43
+ a2a = get_a2a_app()
44
+ a2a.add_middleware(PATAuthMiddleware)
45
+ mcp = get_mcp_app()
46
+ mcp.add_middleware(PATAuthMiddleware)
47
+ routes.append(Mount("/a2a", app=a2a))
48
+ routes.append(Mount("/mcp", app=mcp))
49
+
50
+ # Root app LAST so it won't shadow the others
51
+ routes.append(Mount("/", app=WSGIMiddleware(app)))
52
+
53
+ # Setting logging to None makes uvicorn use the existing logging configuration
54
+ uvicorn.run(Starlette(routes=routes, debug=verbose), host=host, port=int(port), log_level=None, log_config=None)
@@ -24,12 +24,12 @@ async def start_async(verbose=False):
24
24
  project_name = config.cmd_args.project or "mindsdb"
25
25
 
26
26
  # Get MCP server connection details
27
- mcp_host = config.get('api', {}).get('mcp', {}).get('host', '127.0.0.1')
28
- mcp_port = int(config.get('api', {}).get('mcp', {}).get('port', 47337))
27
+ mcp_host = "127.0.0.1"
28
+ mcp_port = int(config.get("api", {}).get("http", {}).get("port", 47334))
29
29
 
30
30
  # Get LiteLLM server settings
31
- litellm_host = config.get('api', {}).get('litellm', {}).get('host', '0.0.0.0')
32
- litellm_port = int(config.get('api', {}).get('litellm', {}).get('port', 8000))
31
+ litellm_host = config.get("api", {}).get("litellm", {}).get("host", "0.0.0.0")
32
+ litellm_port = int(config.get("api", {}).get("litellm", {}).get("port", 8000))
33
33
 
34
34
  logger.info(f"Starting LiteLLM server for agent '{agent_name}' in project '{project_name}'")
35
35
  logger.info(f"Connecting to MCP server at {mcp_host}:{mcp_port}")
@@ -41,7 +41,7 @@ async def start_async(verbose=False):
41
41
  mcp_host=mcp_host,
42
42
  mcp_port=mcp_port,
43
43
  host=litellm_host,
44
- port=litellm_port
44
+ port=litellm_port,
45
45
  )
46
46
 
47
47
 
@@ -52,6 +52,7 @@ def start(verbose=False):
52
52
  verbose (bool): Whether to enable verbose logging
53
53
  """
54
54
  from mindsdb.interfaces.storage import db
55
+
55
56
  db.init()
56
57
 
57
58
  # Run the async function in the event loop
@@ -64,10 +65,10 @@ def start(verbose=False):
64
65
  config = Config()
65
66
  agent_name = config.cmd_args.agent
66
67
  project_name = config.cmd_args.project or "mindsdb"
67
- mcp_host = config.get('api', {}).get('mcp', {}).get('host', '127.0.0.1')
68
- mcp_port = int(config.get('api', {}).get('mcp', {}).get('port', 47337))
69
- litellm_host = config.get('api', {}).get('litellm', {}).get('host', '0.0.0.0')
70
- litellm_port = int(config.get('api', {}).get('litellm', {}).get('port', 8000))
68
+ mcp_host = config.get("api", {}).get("mcp", {}).get("host", "127.0.0.1")
69
+ mcp_port = int(config.get("api", {}).get("mcp", {}).get("port", 47337))
70
+ litellm_host = config.get("api", {}).get("litellm", {}).get("host", "0.0.0.0")
71
+ litellm_port = int(config.get("api", {}).get("litellm", {}).get("port", 8000))
71
72
 
72
73
  return run_server(
73
74
  agent_name=agent_name,
@@ -75,7 +76,7 @@ def start(verbose=False):
75
76
  mcp_host=mcp_host,
76
77
  mcp_port=mcp_port,
77
78
  host=litellm_host,
78
- port=litellm_port
79
+ port=litellm_port,
79
80
  )
80
81
  else:
81
82
  logger.error("LiteLLM server initialization failed")
@@ -0,0 +1,165 @@
1
+ from textwrap import dedent
2
+ from typing import Any
3
+ from contextlib import asynccontextmanager
4
+ from collections.abc import AsyncIterator
5
+ from dataclasses import dataclass
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+ from starlette.requests import Request
9
+ from starlette.responses import JSONResponse
10
+
11
+ from mindsdb.api.mysql.mysql_proxy.classes.fake_mysql_proxy import FakeMysqlProxy
12
+ from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE as SQL_RESPONSE_TYPE
13
+ from mindsdb.interfaces.storage import db
14
+ from mindsdb.utilities import log
15
+
16
+ logger = log.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class AppContext:
21
+ db: Any
22
+
23
+
24
+ @asynccontextmanager
25
+ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
26
+ """Manage application lifecycle with type-safe context"""
27
+ # Initialize on startup
28
+ db.init()
29
+ try:
30
+ yield AppContext(db=db)
31
+ finally:
32
+ # TODO: We need better way to handle this in storage/db.py
33
+ pass
34
+
35
+
36
+ # Configure server with lifespan
37
+ mcp = FastMCP(
38
+ "MindsDB",
39
+ lifespan=app_lifespan,
40
+ dependencies=["mindsdb"], # Add any additional dependencies
41
+ )
42
+
43
+
44
+ # MCP Queries
45
+ LISTING_QUERY = "SHOW DATABASES"
46
+
47
+
48
+ query_tool_description = dedent("""\
49
+ Executes a SQL query against MindsDB.
50
+
51
+ A database must be specified either in the `context` parameter or directly in the query string (e.g., `SELECT * FROM my_database.my_table`). Queries like `SELECT * FROM my_table` will fail without a `context`.
52
+
53
+ Args:
54
+ query (str): The SQL query to execute.
55
+ context (dict, optional): The default database context. For example, `{"db": "my_postgres"}`.
56
+
57
+ Returns:
58
+ A dictionary describing the result.
59
+ - For a successful query with no data to return (e.g., an `UPDATE` statement), the response is `{"type": "ok"}`.
60
+ - If the query returns tabular data, the response is a dictionary containing `data` (a list of rows) and `column_names` (a list of column names). For example: `{"type": "table", "data": [[1, "a"], [2, "b"]], "column_names": ["column_a", "column_b"]}`.
61
+ - In case of an error, a response is `{"type": "error", "error_message": "the error message"}`.
62
+ """)
63
+
64
+
65
+ @mcp.tool(name="query", description=query_tool_description)
66
+ def query(query: str, context: dict | None = None) -> dict[str, Any]:
67
+ """Execute a SQL query against MindsDB
68
+
69
+ Args:
70
+ query: The SQL query to execute
71
+ context: Optional context parameters for the query
72
+
73
+ Returns:
74
+ Dict containing the query results or error information
75
+ """
76
+
77
+ if context is None:
78
+ context = {}
79
+
80
+ logger.debug(f"Incoming MCP query: {query}")
81
+
82
+ mysql_proxy = FakeMysqlProxy()
83
+ mysql_proxy.set_context(context)
84
+
85
+ try:
86
+ result = mysql_proxy.process_query(query)
87
+
88
+ if result.type == SQL_RESPONSE_TYPE.OK:
89
+ return {"type": SQL_RESPONSE_TYPE.OK}
90
+
91
+ if result.type == SQL_RESPONSE_TYPE.TABLE:
92
+ return {
93
+ "type": SQL_RESPONSE_TYPE.TABLE,
94
+ "data": result.result_set.to_lists(json_types=True),
95
+ "column_names": [column.alias or column.name for column in result.result_set.columns],
96
+ }
97
+ else:
98
+ return {"type": SQL_RESPONSE_TYPE.ERROR, "error_code": 0, "error_message": "Unknown response type"}
99
+
100
+ except Exception as e:
101
+ logger.error(f"Error processing query: {str(e)}")
102
+ return {"type": SQL_RESPONSE_TYPE.ERROR, "error_code": 0, "error_message": str(e)}
103
+
104
+
105
+ list_databases_tool_description = (
106
+ "Returns a list of all database connections currently available in MindsDB. "
107
+ + "The tool takes no parameters and responds with a list of database names, "
108
+ + 'for example: ["my_postgres", "my_mysql", "test_db"].'
109
+ )
110
+
111
+
112
+ @mcp.tool(name="list_databases", description=list_databases_tool_description)
113
+ def list_databases() -> list[str]:
114
+ """
115
+ List all databases in MindsDB
116
+
117
+ Returns:
118
+ list[str]: list of databases
119
+ """
120
+
121
+ mysql_proxy = FakeMysqlProxy()
122
+
123
+ try:
124
+ result = mysql_proxy.process_query(LISTING_QUERY)
125
+ if result.type == SQL_RESPONSE_TYPE.ERROR:
126
+ return {
127
+ "type": "error",
128
+ "error_code": result.error_code,
129
+ "error_message": result.error_message,
130
+ }
131
+
132
+ elif result.type == SQL_RESPONSE_TYPE.OK:
133
+ return {"type": "ok"}
134
+
135
+ elif result.type == SQL_RESPONSE_TYPE.TABLE:
136
+ data = result.result_set.to_lists(json_types=True)
137
+ data = [val[0] for val in data]
138
+ return data
139
+
140
+ except Exception as e:
141
+ return {
142
+ "type": "error",
143
+ "error_code": 0,
144
+ "error_message": str(e),
145
+ }
146
+
147
+
148
+ def _get_status(request: Request) -> JSONResponse:
149
+ """
150
+ Status endpoint that returns basic server information.
151
+ This endpoint can be used by the frontend to check if the MCP server is running.
152
+ """
153
+
154
+ status_info = {
155
+ "status": "ok",
156
+ "service": "mindsdb-mcp",
157
+ }
158
+
159
+ return JSONResponse(status_info)
160
+
161
+
162
+ def get_mcp_app():
163
+ app = mcp.sse_app()
164
+ app.add_route("/status", _get_status, methods=["GET"])
165
+ return app