sql-query-mcp 0.1.3__tar.gz → 0.2.0__tar.gz

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.
Files changed (37) hide show
  1. {sql_query_mcp-0.1.3/sql_query_mcp.egg-info → sql_query_mcp-0.2.0}/PKG-INFO +55 -37
  2. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/README.md +53 -36
  3. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/pyproject.toml +2 -1
  4. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/__init__.py +1 -1
  5. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/adapters/mysql.py +8 -0
  6. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/adapters/postgres.py +10 -0
  7. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/app.py +24 -0
  8. sql_query_mcp-0.2.0/sql_query_mcp/importer.py +223 -0
  9. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0/sql_query_mcp.egg-info}/PKG-INFO +55 -37
  10. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp.egg-info/SOURCES.txt +3 -0
  11. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp.egg-info/requires.txt +1 -0
  12. sql_query_mcp-0.2.0/tests/test_app.py +19 -0
  13. sql_query_mcp-0.2.0/tests/test_importer.py +330 -0
  14. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/tests/test_validator.py +20 -0
  15. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/LICENSE +0 -0
  16. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/setup.cfg +0 -0
  17. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/__main__.py +0 -0
  18. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/adapters/__init__.py +0 -0
  19. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/audit.py +0 -0
  20. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/config.py +0 -0
  21. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/errors.py +0 -0
  22. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/executor.py +0 -0
  23. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/introspection.py +0 -0
  24. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/namespace.py +0 -0
  25. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/registry.py +0 -0
  26. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/release_metadata.py +0 -0
  27. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp/validator.py +0 -0
  28. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp.egg-info/dependency_links.txt +0 -0
  29. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp.egg-info/entry_points.txt +0 -0
  30. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/sql_query_mcp.egg-info/top_level.txt +0 -0
  31. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/tests/test_audit.py +0 -0
  32. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/tests/test_config.py +0 -0
  33. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/tests/test_executor.py +0 -0
  34. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/tests/test_metadata.py +0 -0
  35. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/tests/test_namespace.py +0 -0
  36. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/tests/test_registry.py +0 -0
  37. {sql_query_mcp-0.1.3 → sql_query_mcp-0.2.0}/tests/test_release_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-query-mcp
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: Read-only SQL MCP server for PostgreSQL and MySQL.
5
5
  Author: Andy Wang
6
6
  License-Expression: MIT
@@ -21,6 +21,7 @@ Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Requires-Dist: mcp>=1.12.4
24
+ Requires-Dist: openpyxl>=3.1
24
25
  Requires-Dist: PyMySQL>=1.1
25
26
  Requires-Dist: psycopg[binary]>=3.2
26
27
  Requires-Dist: psycopg-pool>=3.2
@@ -35,6 +36,8 @@ Dynamic: license-file
35
36
  A general-purpose MCP server that lets AI work with multiple databases within
36
37
  clear boundaries.
37
38
 
39
+ [![sql-query-mcp MCP server](https://glama.ai/mcp/servers/andyWang1688/sql-query-mcp/badges/card.svg)](https://glama.ai/mcp/servers/andyWang1688/sql-query-mcp)
40
+
38
41
  ## Current database support
39
42
 
40
43
  | Database | Status | Current availability |
@@ -56,9 +59,10 @@ without exposing raw connection strings or flattening engine-specific concepts.
56
59
 
57
60
  ## What AI can do with it
58
61
 
59
- The current tool set focuses on database discovery and controlled query
60
- workflows. You can use it to help an AI assistant understand structure before
61
- it generates or refines SQL.
62
+ The current tool set focuses on database discovery, controlled query workflows,
63
+ and one narrow local file import path. You can use it to help an AI assistant
64
+ understand structure before it generates SQL or imports a prepared CSV/XLSX file
65
+ into an existing table.
62
66
 
63
67
  MySQL supports `explain_query`, but not `explain_query(..., analyze=True)` in
64
68
  the current implementation.
@@ -73,16 +77,18 @@ the current implementation.
73
77
  | `run_select(connection_id, sql, limit?)` | Yes | Yes | Run read-only queries |
74
78
  | `explain_query(connection_id, sql, analyze?)` | Yes | Yes | Inspect query plans |
75
79
  | `get_table_sample(connection_id, table_name, schema?, database?, limit?)` | Yes | Yes | Fetch small table samples |
80
+ | `import_table_file(connection_id, table_name, file_path, schema?, database?, sheet_name?)` | Yes | Yes | Import local CSV/XLSX files |
76
81
 
77
82
  These tools are useful for tasks such as listing namespaces, inspecting table
78
- definitions, reviewing indexes, sampling records, and analyzing read-only
79
- queries with `EXPLAIN`. For full request and response details, see
80
- `docs/api-reference.md` (Chinese).
83
+ definitions, reviewing indexes, sampling records, analyzing read-only queries
84
+ with `EXPLAIN`, and importing prepared local files. For full request and
85
+ response details, see `docs/api-reference.md` (Chinese).
81
86
 
82
87
  ## How boundaries are constrained
83
88
 
84
89
  The product boundary is intentionally narrow today. Only PostgreSQL and MySQL
85
- are available today, and the current tool set is fully read-only.
90
+ are available today. Query tools remain read-only, and the only write path is a
91
+ controlled local CSV/XLSX import into existing tables.
86
92
 
87
93
  The service keeps those boundaries explicit in a few ways.
88
94
 
@@ -96,57 +102,69 @@ The service keeps those boundaries explicit in a few ways.
96
102
  database.
97
103
  - The server accepts only `SELECT` and `WITH ... SELECT`, rejects comments and
98
104
  multi-statement input, and records audit logs for each call.
105
+ - `import_table_file` doesn't accept raw SQL. It inserts only file columns whose
106
+ headers exactly match existing table columns.
99
107
 
100
108
  For MySQL, `explain_query(..., analyze=True)` is not available in the current
101
109
  implementation.
102
110
 
103
111
  ## Quick start
104
112
 
105
- If you want to get the server running first and explore the rest later, follow
106
- these steps.
113
+ `sql-query-mcp` supports two official PyPI-based setup modes. Both are intended
114
+ for real usage, not just local testing.
115
+
116
+ 1. Choose how you want your MCP client to start the server.
107
117
 
108
- 1. Create a virtual environment and install the project.
118
+ Use installed command mode if you want a simple local command after one
119
+ install.
109
120
 
110
121
  ```bash
111
- python3.10 -m venv .venv
112
- source .venv/bin/activate
113
- python -m pip install --upgrade pip
114
- pip install sql-query-mcp
122
+ pipx install sql-query-mcp
115
123
  ```
116
124
 
117
- Install a specific release with `pip install sql-query-mcp==X.Y.Z` if you want
118
- to pin a version. Published release artifacts are also attached to each GitHub
119
- Release.
120
-
121
- 2. Copy the example connection config.
125
+ Use managed launch mode if you want the package source declared directly in
126
+ your MCP client config.
122
127
 
123
128
  ```bash
124
- cp config/connections.example.json config/connections.json
129
+ pipx run --spec sql-query-mcp sql-query-mcp
125
130
  ```
126
131
 
127
- 3. Export your database DSNs as environment variables.
132
+ Pin a version with `pipx install 'sql-query-mcp==X.Y.Z'` or
133
+ `pipx run --spec 'sql-query-mcp==X.Y.Z' sql-query-mcp`. Upgrade installed
134
+ command mode with `pipx upgrade sql-query-mcp`.
135
+
136
+ 2. Create a config file.
128
137
 
129
- These example names match the copied `config/connections.example.json` file.
138
+ The server configuration should live outside the repository so the same file
139
+ works with either startup mode.
130
140
 
131
141
  ```bash
132
- export PG_CONN_CRM_PROD_MUQIAO_RO='postgresql://user:password@host:5432/dbname'
133
- export MYSQL_CONN_CRM_PROD_MUQIAO_RO='mysql://user:password@host:3306/crm'
134
- export SQL_QUERY_MCP_CONFIG='/absolute/path/to/sql-query-mcp/config/connections.json'
142
+ mkdir -p ~/.config/sql-query-mcp
135
143
  ```
136
144
 
137
- 4. Register the server in your MCP client.
145
+ Then save the example JSON later in this section as
146
+ `~/.config/sql-query-mcp/connections.json`.
147
+
148
+ 3. Register the server in your MCP client.
138
149
 
139
150
  - Codex: `docs/codex-setup.md` (Chinese)
140
151
  - OpenCode: `docs/opencode-setup.md` (Chinese)
141
152
 
153
+ Installed command mode means your client runs `sql-query-mcp` directly.
154
+ Managed launch mode means your client starts the server through `pipx run`.
155
+
156
+ In both modes, put `SQL_QUERY_MCP_CONFIG` and your real database DSNs in the
157
+ MCP client's environment block instead of exporting them in your shell.
158
+
142
159
  The console entry point is `sql-query-mcp`, which maps to
143
160
  `sql_query_mcp.app:main`.
144
161
 
145
162
  The PyPI install name is `sql-query-mcp`, and the Python package import path is
146
163
  `sql_query_mcp`.
147
164
 
148
- The default config path is `config/connections.json`. If you need a different
149
- location, set `SQL_QUERY_MCP_CONFIG`.
165
+ For `pipx install` and `pipx run`, set `SQL_QUERY_MCP_CONFIG` explicitly to
166
+ your config file path. The default `config/connections.json` path is mainly for
167
+ source checkouts and local development.
150
168
 
151
169
  The example config looks like this.
152
170
 
@@ -159,24 +177,24 @@ The example config looks like this.
159
177
  },
160
178
  "connections": [
161
179
  {
162
- "connection_id": "crm_prod_muqiao_ro",
180
+ "connection_id": "crm_prod_main_ro",
163
181
  "engine": "postgres",
164
- "label": "CRM PostgreSQL production / Muqiao / read-only",
182
+ "label": "CRM PostgreSQL production / Main / read-only",
165
183
  "env": "prod",
166
- "tenant": "muqiao",
184
+ "tenant": "main",
167
185
  "role": "ro",
168
- "dsn_env": "PG_CONN_CRM_PROD_MUQIAO_RO",
186
+ "dsn_env": "PG_CONN_CRM_PROD_MAIN_RO",
169
187
  "enabled": true,
170
188
  "default_schema": "public"
171
189
  },
172
190
  {
173
- "connection_id": "crm_mysql_prod_muqiao_ro",
191
+ "connection_id": "crm_mysql_prod_main_ro",
174
192
  "engine": "mysql",
175
- "label": "CRM MySQL production / Muqiao / read-only",
193
+ "label": "CRM MySQL production / Main / read-only",
176
194
  "env": "prod",
177
- "tenant": "muqiao",
195
+ "tenant": "main",
178
196
  "role": "ro",
179
- "dsn_env": "MYSQL_CONN_CRM_PROD_MUQIAO_RO",
197
+ "dsn_env": "MYSQL_CONN_CRM_PROD_MAIN_RO",
180
198
  "enabled": true,
181
199
  "default_database": "crm"
182
200
  }
@@ -5,6 +5,8 @@
5
5
  A general-purpose MCP server that lets AI work with multiple databases within
6
6
  clear boundaries.
7
7
 
8
+ [![sql-query-mcp MCP server](https://glama.ai/mcp/servers/andyWang1688/sql-query-mcp/badges/card.svg)](https://glama.ai/mcp/servers/andyWang1688/sql-query-mcp)
9
+
8
10
  ## Current database support
9
11
 
10
12
  | Database | Status | Current availability |
@@ -26,9 +28,10 @@ without exposing raw connection strings or flattening engine-specific concepts.
26
28
 
27
29
  ## What AI can do with it
28
30
 
29
- The current tool set focuses on database discovery and controlled query
30
- workflows. You can use it to help an AI assistant understand structure before
31
- it generates or refines SQL.
31
+ The current tool set focuses on database discovery, controlled query workflows,
32
+ and one narrow local file import path. You can use it to help an AI assistant
33
+ understand structure before it generates SQL or imports a prepared CSV/XLSX file
34
+ into an existing table.
32
35
 
33
36
  MySQL supports `explain_query`, but not `explain_query(..., analyze=True)` in
34
37
  the current implementation.
@@ -43,16 +46,18 @@ the current implementation.
43
46
  | `run_select(connection_id, sql, limit?)` | Yes | Yes | Run read-only queries |
44
47
  | `explain_query(connection_id, sql, analyze?)` | Yes | Yes | Inspect query plans |
45
48
  | `get_table_sample(connection_id, table_name, schema?, database?, limit?)` | Yes | Yes | Fetch small table samples |
49
+ | `import_table_file(connection_id, table_name, file_path, schema?, database?, sheet_name?)` | Yes | Yes | Import local CSV/XLSX files |
46
50
 
47
51
  These tools are useful for tasks such as listing namespaces, inspecting table
48
- definitions, reviewing indexes, sampling records, and analyzing read-only
49
- queries with `EXPLAIN`. For full request and response details, see
50
- `docs/api-reference.md` (Chinese).
52
+ definitions, reviewing indexes, sampling records, analyzing read-only queries
53
+ with `EXPLAIN`, and importing prepared local files. For full request and
54
+ response details, see `docs/api-reference.md` (Chinese).
51
55
 
52
56
  ## How boundaries are constrained
53
57
 
54
58
  The product boundary is intentionally narrow today. Only PostgreSQL and MySQL
55
- are available today, and the current tool set is fully read-only.
59
+ are available today. Query tools remain read-only, and the only write path is a
60
+ controlled local CSV/XLSX import into existing tables.
56
61
 
57
62
  The service keeps those boundaries explicit in a few ways.
58
63
 
@@ -66,57 +71,69 @@ The service keeps those boundaries explicit in a few ways.
66
71
  database.
67
72
  - The server accepts only `SELECT` and `WITH ... SELECT`, rejects comments and
68
73
  multi-statement input, and records audit logs for each call.
74
+ - `import_table_file` doesn't accept raw SQL. It inserts only file columns whose
75
+ headers exactly match existing table columns.
69
76
 
70
77
  For MySQL, `explain_query(..., analyze=True)` is not available in the current
71
78
  implementation.
72
79
 
73
80
  ## Quick start
74
81
 
75
- If you want to get the server running first and explore the rest later, follow
76
- these steps.
82
+ `sql-query-mcp` supports two official PyPI-based setup modes. Both are intended
83
+ for real usage, not just local testing.
84
+
85
+ 1. Choose how you want your MCP client to start the server.
77
86
 
78
- 1. Create a virtual environment and install the project.
87
+ Use installed command mode if you want a simple local command after one
88
+ install.
79
89
 
80
90
  ```bash
81
- python3.10 -m venv .venv
82
- source .venv/bin/activate
83
- python -m pip install --upgrade pip
84
- pip install sql-query-mcp
91
+ pipx install sql-query-mcp
85
92
  ```
86
93
 
87
- Install a specific release with `pip install sql-query-mcp==X.Y.Z` if you want
88
- to pin a version. Published release artifacts are also attached to each GitHub
89
- Release.
90
-
91
- 2. Copy the example connection config.
94
+ Use managed launch mode if you want the package source declared directly in
95
+ your MCP client config.
92
96
 
93
97
  ```bash
94
- cp config/connections.example.json config/connections.json
98
+ pipx run --spec sql-query-mcp sql-query-mcp
95
99
  ```
96
100
 
97
- 3. Export your database DSNs as environment variables.
101
+ Pin a version with `pipx install 'sql-query-mcp==X.Y.Z'` or
102
+ `pipx run --spec 'sql-query-mcp==X.Y.Z' sql-query-mcp`. Upgrade installed
103
+ command mode with `pipx upgrade sql-query-mcp`.
104
+
105
+ 2. Create a config file.
98
106
 
99
- These example names match the copied `config/connections.example.json` file.
107
+ The server configuration should live outside the repository so the same file
108
+ works with either startup mode.
100
109
 
101
110
  ```bash
102
- export PG_CONN_CRM_PROD_MUQIAO_RO='postgresql://user:password@host:5432/dbname'
103
- export MYSQL_CONN_CRM_PROD_MUQIAO_RO='mysql://user:password@host:3306/crm'
104
- export SQL_QUERY_MCP_CONFIG='/absolute/path/to/sql-query-mcp/config/connections.json'
111
+ mkdir -p ~/.config/sql-query-mcp
105
112
  ```
106
113
 
107
- 4. Register the server in your MCP client.
114
+ Then save the example JSON later in this section as
115
+ `~/.config/sql-query-mcp/connections.json`.
116
+
117
+ 3. Register the server in your MCP client.
108
118
 
109
119
  - Codex: `docs/codex-setup.md` (Chinese)
110
120
  - OpenCode: `docs/opencode-setup.md` (Chinese)
111
121
 
122
+ Installed command mode means your client runs `sql-query-mcp` directly.
123
+ Managed launch mode means your client starts the server through `pipx run`.
124
+
125
+ In both modes, put `SQL_QUERY_MCP_CONFIG` and your real database DSNs in the
126
+ MCP client's environment block instead of exporting them in your shell.
127
+
112
128
  The console entry point is `sql-query-mcp`, which maps to
113
129
  `sql_query_mcp.app:main`.
114
130
 
115
131
  The PyPI install name is `sql-query-mcp`, and the Python package import path is
116
132
  `sql_query_mcp`.
117
133
 
118
- The default config path is `config/connections.json`. If you need a different
119
- location, set `SQL_QUERY_MCP_CONFIG`.
134
+ For `pipx install` and `pipx run`, set `SQL_QUERY_MCP_CONFIG` explicitly to
135
+ your config file path. The default `config/connections.json` path is mainly for
136
+ source checkouts and local development.
120
137
 
121
138
  The example config looks like this.
122
139
 
@@ -129,24 +146,24 @@ The example config looks like this.
129
146
  },
130
147
  "connections": [
131
148
  {
132
- "connection_id": "crm_prod_muqiao_ro",
149
+ "connection_id": "crm_prod_main_ro",
133
150
  "engine": "postgres",
134
- "label": "CRM PostgreSQL production / Muqiao / read-only",
151
+ "label": "CRM PostgreSQL production / Main / read-only",
135
152
  "env": "prod",
136
- "tenant": "muqiao",
153
+ "tenant": "main",
137
154
  "role": "ro",
138
- "dsn_env": "PG_CONN_CRM_PROD_MUQIAO_RO",
155
+ "dsn_env": "PG_CONN_CRM_PROD_MAIN_RO",
139
156
  "enabled": true,
140
157
  "default_schema": "public"
141
158
  },
142
159
  {
143
- "connection_id": "crm_mysql_prod_muqiao_ro",
160
+ "connection_id": "crm_mysql_prod_main_ro",
144
161
  "engine": "mysql",
145
- "label": "CRM MySQL production / Muqiao / read-only",
162
+ "label": "CRM MySQL production / Main / read-only",
146
163
  "env": "prod",
147
- "tenant": "muqiao",
164
+ "tenant": "main",
148
165
  "role": "ro",
149
- "dsn_env": "MYSQL_CONN_CRM_PROD_MUQIAO_RO",
166
+ "dsn_env": "MYSQL_CONN_CRM_PROD_MAIN_RO",
150
167
  "enabled": true,
151
168
  "default_database": "crm"
152
169
  }
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sql-query-mcp"
7
- version = "0.1.3"
7
+ version = "0.2.0"
8
8
  description = "Read-only SQL MCP server for PostgreSQL and MySQL."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -24,6 +24,7 @@ classifiers = [
24
24
  ]
25
25
  dependencies = [
26
26
  "mcp>=1.12.4",
27
+ "openpyxl>=3.1",
27
28
  "PyMySQL>=1.1",
28
29
  "psycopg[binary]>=3.2",
29
30
  "psycopg-pool>=3.2",
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.1.3"
5
+ __version__ = "0.1.4"
@@ -115,6 +115,14 @@ class MySQLAdapter:
115
115
  f"{self._quote_identifier(table_name)} LIMIT {int(sentinel_limit)}"
116
116
  )
117
117
 
118
+ def build_insert_query(self, database: str, table_name: str, columns: List[str]) -> str:
119
+ quoted_columns = ", ".join(self._quote_identifier(column) for column in columns)
120
+ placeholders = ", ".join(["%s"] * len(columns))
121
+ return (
122
+ f"INSERT INTO {self._quote_identifier(database)}."
123
+ f"{self._quote_identifier(table_name)} ({quoted_columns}) VALUES ({placeholders})"
124
+ )
125
+
118
126
  def build_explain_query(self, sql_text: str, analyze: bool = False) -> str:
119
127
  if analyze:
120
128
  raise SecurityError("MySQL 首版不支持 analyze=True。")
@@ -155,6 +155,16 @@ class PostgresAdapter:
155
155
  sql.Literal(sentinel_limit),
156
156
  )
157
157
 
158
+ def build_insert_query(self, schema: str, table_name: str, columns: List[str]):
159
+ if sql is None:
160
+ raise ConfigurationError("缺少 psycopg 依赖,请先安装项目依赖。")
161
+ return sql.SQL("INSERT INTO {}.{} ({}) VALUES ({})").format(
162
+ sql.Identifier(schema),
163
+ sql.Identifier(table_name),
164
+ sql.SQL(", ").join(sql.Identifier(column) for column in columns),
165
+ sql.SQL(", ").join(sql.Placeholder() for _ in columns),
166
+ )
167
+
158
168
  def build_explain_query(self, sql_text: str, analyze: bool = False) -> str:
159
169
  return f"EXPLAIN (FORMAT JSON, ANALYZE {'TRUE' if analyze else 'FALSE'}) {sql_text}"
160
170
 
@@ -10,6 +10,7 @@ from .audit import AuditLogger
10
10
  from .config import load_config
11
11
  from .errors import SqlQueryMCPError
12
12
  from .executor import QueryExecutor
13
+ from .importer import TableFileImporter
13
14
  from .introspection import MetadataService
14
15
  from .registry import ConnectionRegistry
15
16
 
@@ -20,6 +21,7 @@ def create_app() -> FastMCP:
20
21
  audit_logger = AuditLogger(app_config.settings.audit_log_path)
21
22
  metadata = MetadataService(registry, app_config.settings, audit_logger)
22
23
  executor = QueryExecutor(registry, app_config.settings, audit_logger)
24
+ importer = TableFileImporter(registry, app_config.settings, audit_logger)
23
25
 
24
26
  mcp = FastMCP("sql-query-mcp", json_response=True)
25
27
 
@@ -86,6 +88,28 @@ def create_app() -> FastMCP:
86
88
 
87
89
  return _run_tool(lambda: executor.get_table_sample(connection_id, table_name, schema, database, limit))
88
90
 
91
+ @mcp.tool()
92
+ def import_table_file(
93
+ connection_id: str,
94
+ table_name: str,
95
+ file_path: str,
96
+ schema: Optional[str] = None,
97
+ database: Optional[str] = None,
98
+ sheet_name: Optional[str] = None,
99
+ ) -> dict:
100
+ """Import a local CSV or XLSX file into an existing table."""
101
+
102
+ return _run_tool(
103
+ lambda: importer.import_table_file(
104
+ connection_id,
105
+ table_name,
106
+ file_path,
107
+ schema,
108
+ database,
109
+ sheet_name,
110
+ )
111
+ )
112
+
89
113
  return mcp
90
114
 
91
115