storage-map-mcp-server 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/build/clients/MySqlClient.d.ts +14 -0
- package/build/clients/MySqlClient.d.ts.map +1 -0
- package/build/clients/MySqlClient.js +141 -0
- package/build/clients/MySqlClient.js.map +1 -0
- package/build/clients/athena-client/AthenaClient.d.ts +17 -0
- package/build/clients/athena-client/AthenaClient.d.ts.map +1 -0
- package/build/clients/athena-client/AthenaClient.js +199 -0
- package/build/clients/athena-client/AthenaClient.js.map +1 -0
- package/build/clients/athena-client/index.d.ts +2 -0
- package/build/clients/athena-client/index.d.ts.map +1 -0
- package/build/clients/athena-client/index.js +2 -0
- package/build/clients/athena-client/index.js.map +1 -0
- package/build/clients/index.d.ts +3 -0
- package/build/clients/index.d.ts.map +1 -0
- package/build/clients/index.js +3 -0
- package/build/clients/index.js.map +1 -0
- package/build/clients/mysql-client/MySqlClient.d.ts +14 -0
- package/build/clients/mysql-client/MySqlClient.d.ts.map +1 -0
- package/build/clients/mysql-client/MySqlClient.js +141 -0
- package/build/clients/mysql-client/MySqlClient.js.map +1 -0
- package/build/clients/mysql-client/index.d.ts +2 -0
- package/build/clients/mysql-client/index.d.ts.map +1 -0
- package/build/clients/mysql-client/index.js +2 -0
- package/build/clients/mysql-client/index.js.map +1 -0
- package/build/clients/mysql.d.ts +14 -0
- package/build/clients/mysql.d.ts.map +1 -0
- package/build/clients/mysql.js +68 -0
- package/build/clients/mysql.js.map +1 -0
- package/build/config.d.ts +20 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +80 -0
- package/build/config.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +10 -0
- package/build/index.js.map +1 -0
- package/build/mcp-connectors/McpConnector.d.ts +14 -0
- package/build/mcp-connectors/McpConnector.d.ts.map +1 -0
- package/build/mcp-connectors/McpConnector.js +2 -0
- package/build/mcp-connectors/McpConnector.js.map +1 -0
- package/build/mcp-connectors/McpConnectorRegistry.d.ts +17 -0
- package/build/mcp-connectors/McpConnectorRegistry.d.ts.map +1 -0
- package/build/mcp-connectors/McpConnectorRegistry.js +46 -0
- package/build/mcp-connectors/McpConnectorRegistry.js.map +1 -0
- package/build/mcp-connectors/index.d.ts +3 -0
- package/build/mcp-connectors/index.d.ts.map +1 -0
- package/build/mcp-connectors/index.js +2 -0
- package/build/mcp-connectors/index.js.map +1 -0
- package/build/server.d.ts +2 -0
- package/build/server.d.ts.map +1 -0
- package/build/server.js +172 -0
- package/build/server.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hoon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# MCP Storage Map
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
Model Context Protocol (MCP) server for unified database and storage access. Query multiple databases (MySQL, AWS Athena, and more) through a single, consistent interface.
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
### 1. Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add -g @storage-map/mcp-server
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or run directly with pnpm dlx:
|
|
17
|
+
```bash
|
|
18
|
+
pnpm dlx @storage-map/mcp-server
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Configure Your IDE
|
|
22
|
+
|
|
23
|
+
Add to your Claude Desktop, Claude Code, or Cursor MCP configuration.
|
|
24
|
+
|
|
25
|
+
**Single Database Example:**
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"storage-map": {
|
|
31
|
+
"command": "pnpm",
|
|
32
|
+
"args": ["dlx", "@storage-map/mcp-server"],
|
|
33
|
+
"env": {
|
|
34
|
+
"STORAGE_MYSQL_TYPE": "mysql",
|
|
35
|
+
"STORAGE_MYSQL_HOST": "localhost",
|
|
36
|
+
"STORAGE_MYSQL_PORT": "3306",
|
|
37
|
+
"STORAGE_MYSQL_USER": "root",
|
|
38
|
+
"STORAGE_MYSQL_PASSWORD": "password",
|
|
39
|
+
"STORAGE_MYSQL_DATABASE": "myapp",
|
|
40
|
+
"STORAGE_MYSQL_WRITE_MODE": "false"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Multiple Databases Example:**
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"storage-map": {
|
|
53
|
+
"command": "pnpm",
|
|
54
|
+
"args": ["dlx", "@storage-map/mcp-server"],
|
|
55
|
+
"env": {
|
|
56
|
+
"STORAGE_PROD_TYPE": "mysql",
|
|
57
|
+
"STORAGE_PROD_HOST": "prod-db.example.com",
|
|
58
|
+
"STORAGE_PROD_PORT": "3306",
|
|
59
|
+
"STORAGE_PROD_USER": "readonly",
|
|
60
|
+
"STORAGE_PROD_PASSWORD": "secret",
|
|
61
|
+
"STORAGE_PROD_DATABASE": "production",
|
|
62
|
+
|
|
63
|
+
"STORAGE_STAGING_TYPE": "mysql",
|
|
64
|
+
"STORAGE_STAGING_HOST": "staging-db.example.com",
|
|
65
|
+
"STORAGE_STAGING_PORT": "3306",
|
|
66
|
+
"STORAGE_STAGING_USER": "admin",
|
|
67
|
+
"STORAGE_STAGING_PASSWORD": "secret",
|
|
68
|
+
"STORAGE_STAGING_DATABASE": "staging",
|
|
69
|
+
"STORAGE_STAGING_WRITE_MODE": "true",
|
|
70
|
+
|
|
71
|
+
"STORAGE_ANALYTICS_TYPE": "athena",
|
|
72
|
+
"STORAGE_ANALYTICS_REGION": "us-west-2",
|
|
73
|
+
"STORAGE_ANALYTICS_DATABASE": "analytics",
|
|
74
|
+
"STORAGE_ANALYTICS_S3_OUTPUT_LOCATION": "s3://analytics-results/"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **Tip**: Each database connection needs a unique ID (the part after `STORAGE_` and before the property name). In the examples above: `MYSQL`, `PROD`, `STAGING`, and `ANALYTICS` are the IDs.
|
|
82
|
+
|
|
83
|
+
### 3. Start Using
|
|
84
|
+
|
|
85
|
+
Ask your AI assistant to:
|
|
86
|
+
- **"List all my configured databases"**
|
|
87
|
+
- **"Query the users table from my MySQL database"**
|
|
88
|
+
- **"Show me tables in my Athena database"**
|
|
89
|
+
- **"Get the schema for the orders table"**
|
|
90
|
+
- **"Execute SELECT * FROM products LIMIT 10 on mysql storage"**
|
|
91
|
+
|
|
92
|
+
## Compatibility
|
|
93
|
+
|
|
94
|
+
| Database | Status | Version |
|
|
95
|
+
|----------|--------|---------|
|
|
96
|
+
| MySQL | ✅ Fully supported | 5.7+ |
|
|
97
|
+
| AWS Athena | ✅ Fully supported | All versions |
|
|
98
|
+
|
|
99
|
+
## Key Tools
|
|
100
|
+
|
|
101
|
+
| Tool | Description |
|
|
102
|
+
|------|-------------|
|
|
103
|
+
| `list_storages` | List all configured storage connections |
|
|
104
|
+
| `query` | Execute read queries (SELECT, etc.) |
|
|
105
|
+
| `execute` | Execute write operations (INSERT, UPDATE, DELETE) |
|
|
106
|
+
| `list_collections` | List tables/collections in a storage |
|
|
107
|
+
| `describe_collection` | Get schema information for a table/collection |
|
|
108
|
+
| `get_storage_info` | Get detailed connection information |
|
|
109
|
+
|
|
110
|
+
## Configuration
|
|
111
|
+
|
|
112
|
+
### Environment Variables
|
|
113
|
+
|
|
114
|
+
Storage Map uses a flexible configuration system with environment variables:
|
|
115
|
+
|
|
116
|
+
**Pattern**: `STORAGE_<ID>_<PROPERTY>`
|
|
117
|
+
|
|
118
|
+
- `<ID>`: Unique identifier for your storage connection (e.g., `MYSQL`, `PROD`, `STAGING`, `ANALYTICS`)
|
|
119
|
+
- Use descriptive names to identify each connection
|
|
120
|
+
- Same database type can have multiple IDs (e.g., `PROD_DB` and `STAGING_DB` for different MySQL servers)
|
|
121
|
+
- `<PROPERTY>`: Configuration property (e.g., `TYPE`, `HOST`, `PORT`)
|
|
122
|
+
|
|
123
|
+
**Common Properties**:
|
|
124
|
+
- `TYPE`: Database type (`mysql`, `athena`) - **Required**
|
|
125
|
+
- `WRITE_MODE`: Enable write operations (`true` or `false`, default: `false`)
|
|
126
|
+
|
|
127
|
+
**MySQL**:
|
|
128
|
+
- `HOST`: Database host - **Required**
|
|
129
|
+
- `PORT`: Database port (default: `3306`)
|
|
130
|
+
- `USER`: Username - **Required**
|
|
131
|
+
- `PASSWORD`: Password - **Required**
|
|
132
|
+
- `DATABASE`: Database name - **Required**
|
|
133
|
+
|
|
134
|
+
**AWS Athena**:
|
|
135
|
+
- `REGION`: AWS region (default: `us-east-1`)
|
|
136
|
+
- `DATABASE`: Athena database/catalog (default: `default`)
|
|
137
|
+
- `S3_OUTPUT_LOCATION`: S3 path for query results - **Required**
|
|
138
|
+
- `WORKGROUP`: Athena workgroup (default: `primary`)
|
|
139
|
+
|
|
140
|
+
### Configuration Examples
|
|
141
|
+
|
|
142
|
+
**Example 1: Single MySQL Database**
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
STORAGE_MYSQL_TYPE=mysql
|
|
146
|
+
STORAGE_MYSQL_HOST=localhost
|
|
147
|
+
STORAGE_MYSQL_PORT=3306
|
|
148
|
+
STORAGE_MYSQL_USER=root
|
|
149
|
+
STORAGE_MYSQL_PASSWORD=password
|
|
150
|
+
STORAGE_MYSQL_DATABASE=myapp
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Example 2: Multiple MySQL Databases (Prod + Staging)**
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Production (read-only)
|
|
157
|
+
STORAGE_PROD_TYPE=mysql
|
|
158
|
+
STORAGE_PROD_HOST=prod-db.example.com
|
|
159
|
+
STORAGE_PROD_PORT=3306
|
|
160
|
+
STORAGE_PROD_USER=readonly
|
|
161
|
+
STORAGE_PROD_PASSWORD=secret
|
|
162
|
+
STORAGE_PROD_DATABASE=production
|
|
163
|
+
|
|
164
|
+
# Staging (read-write)
|
|
165
|
+
STORAGE_STAGING_TYPE=mysql
|
|
166
|
+
STORAGE_STAGING_HOST=staging-db.example.com
|
|
167
|
+
STORAGE_STAGING_PORT=3306
|
|
168
|
+
STORAGE_STAGING_USER=admin
|
|
169
|
+
STORAGE_STAGING_PASSWORD=secret
|
|
170
|
+
STORAGE_STAGING_DATABASE=staging
|
|
171
|
+
STORAGE_STAGING_WRITE_MODE=true
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Example 3: MySQL + Athena**
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# MySQL for operational data
|
|
178
|
+
STORAGE_DB_TYPE=mysql
|
|
179
|
+
STORAGE_DB_HOST=localhost
|
|
180
|
+
STORAGE_DB_PORT=3306
|
|
181
|
+
STORAGE_DB_USER=admin
|
|
182
|
+
STORAGE_DB_PASSWORD=password
|
|
183
|
+
STORAGE_DB_DATABASE=myapp
|
|
184
|
+
|
|
185
|
+
# Athena for analytics
|
|
186
|
+
STORAGE_ANALYTICS_TYPE=athena
|
|
187
|
+
STORAGE_ANALYTICS_REGION=us-west-2
|
|
188
|
+
STORAGE_ANALYTICS_DATABASE=analytics
|
|
189
|
+
STORAGE_ANALYTICS_S3_OUTPUT_LOCATION=s3://my-bucket/athena-results/
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Features
|
|
193
|
+
|
|
194
|
+
### Unified Interface
|
|
195
|
+
Query different databases using the same MCP tools, regardless of the underlying technology.
|
|
196
|
+
|
|
197
|
+
### Read-Only by Default
|
|
198
|
+
All connections are read-only by default. Explicitly enable `WRITE_MODE` for write operations.
|
|
199
|
+
|
|
200
|
+
### Multiple Connections
|
|
201
|
+
Configure multiple databases of the same or different types simultaneously.
|
|
202
|
+
|
|
203
|
+
### Extensible Architecture
|
|
204
|
+
Easy to add new database connectors with the `McpConnector` interface.
|
|
205
|
+
|
|
206
|
+
## Architecture
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
┌─────────────────────────────────────────┐
|
|
210
|
+
│ MCP Server (server.ts) │
|
|
211
|
+
├─────────────────────────────────────────┤
|
|
212
|
+
│ MCP Tools: query, execute, list, etc. │
|
|
213
|
+
└─────────────┬───────────────────────────┘
|
|
214
|
+
│
|
|
215
|
+
┌───────┴────────┐
|
|
216
|
+
│ Registry │
|
|
217
|
+
└───────┬────────┘
|
|
218
|
+
│
|
|
219
|
+
┌─────────┴──────────┐
|
|
220
|
+
│ │
|
|
221
|
+
┌───▼──────┐ ┌─────▼─────┐
|
|
222
|
+
│ MySQL │ │ Athena │
|
|
223
|
+
│Connector │ │ Connector │
|
|
224
|
+
└──────────┘ └───────────┘
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Each connector implements the `McpConnector` interface:
|
|
228
|
+
- `query()` - Execute read queries
|
|
229
|
+
- `execute()` - Execute write operations
|
|
230
|
+
- `listCollections()` - List tables/collections
|
|
231
|
+
- `describeCollection()` - Get schema information
|
|
232
|
+
|
|
233
|
+
## Security
|
|
234
|
+
|
|
235
|
+
- Never commit credentials or API tokens to version control
|
|
236
|
+
- Use environment variables for sensitive configuration
|
|
237
|
+
- Keep `WRITE_MODE` disabled unless absolutely necessary
|
|
238
|
+
- Review queries before execution, especially with write access enabled
|
|
239
|
+
|
|
240
|
+
## Development
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Clone the repository
|
|
244
|
+
git clone https://github.com/cyhoon/mcp-storage-map.git
|
|
245
|
+
cd mcp-storage-map
|
|
246
|
+
|
|
247
|
+
# Install dependencies
|
|
248
|
+
pnpm install
|
|
249
|
+
|
|
250
|
+
# Run in development mode
|
|
251
|
+
pnpm dev
|
|
252
|
+
|
|
253
|
+
# Build
|
|
254
|
+
pnpm build
|
|
255
|
+
|
|
256
|
+
# Run tests
|
|
257
|
+
pnpm test
|
|
258
|
+
|
|
259
|
+
# Lint
|
|
260
|
+
pnpm lint
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
MIT - See [LICENSE](LICENSE)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StorageConfig } from '../config.js';
|
|
2
|
+
import { McpConnector, McpConnectorResult } from '../mcp-connectors/index.js';
|
|
3
|
+
export declare class MySQLConnector implements McpConnector {
|
|
4
|
+
private pool;
|
|
5
|
+
private config;
|
|
6
|
+
constructor(config: StorageConfig);
|
|
7
|
+
private connect;
|
|
8
|
+
query(query: string, params?: any): Promise<McpConnectorResult>;
|
|
9
|
+
execute(operation: string, params?: any): Promise<McpConnectorResult>;
|
|
10
|
+
listCollections(schema?: string): Promise<McpConnectorResult>;
|
|
11
|
+
describeCollection(collection: string, schema?: string): Promise<McpConnectorResult>;
|
|
12
|
+
disconnect(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=MySqlClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MySqlClient.d.ts","sourceRoot":"","sources":["../../src/clients/MySqlClient.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAE9E,qBAAa,cAAe,YAAW,YAAY;IACjD,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,aAAa;IAIjC,OAAO,CAAC,OAAO;IAiBT,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAyB/D,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA8BrE,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiC7D,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA+BpF,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAQlC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
export class MySQLConnector {
|
|
3
|
+
pool = null;
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
connect() {
|
|
9
|
+
if (!this.config.connection) {
|
|
10
|
+
throw new Error('MySQL connection config is missing');
|
|
11
|
+
}
|
|
12
|
+
this.pool = mysql.createPool({
|
|
13
|
+
host: this.config.connection.host,
|
|
14
|
+
port: this.config.connection.port || 3306,
|
|
15
|
+
user: this.config.connection.user,
|
|
16
|
+
password: this.config.connection.password,
|
|
17
|
+
database: this.config.connection.database,
|
|
18
|
+
waitForConnections: true,
|
|
19
|
+
connectionLimit: 10,
|
|
20
|
+
queueLimit: 0
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async query(query, params) {
|
|
24
|
+
try {
|
|
25
|
+
if (!this.pool) {
|
|
26
|
+
this.connect();
|
|
27
|
+
}
|
|
28
|
+
const [rows] = await this.pool.execute(query, params);
|
|
29
|
+
return {
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: JSON.stringify({
|
|
32
|
+
rows,
|
|
33
|
+
rowCount: Array.isArray(rows) ? rows.length : 0
|
|
34
|
+
}, null, 2)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return {
|
|
39
|
+
type: 'error',
|
|
40
|
+
text: JSON.stringify({
|
|
41
|
+
error: error.message || 'Unknown error occurred',
|
|
42
|
+
query
|
|
43
|
+
}, null, 2)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async execute(operation, params) {
|
|
48
|
+
try {
|
|
49
|
+
if (!this.config.writeMode) {
|
|
50
|
+
throw new Error('Write operations are not allowed for this connection');
|
|
51
|
+
}
|
|
52
|
+
if (!this.pool) {
|
|
53
|
+
this.connect();
|
|
54
|
+
}
|
|
55
|
+
const [result] = await this.pool.execute(operation, params);
|
|
56
|
+
return {
|
|
57
|
+
type: 'text',
|
|
58
|
+
text: JSON.stringify({
|
|
59
|
+
affectedRows: result.affectedRows || 0,
|
|
60
|
+
insertId: result.insertId,
|
|
61
|
+
message: result.message
|
|
62
|
+
}, null, 2)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return {
|
|
67
|
+
type: 'error',
|
|
68
|
+
text: JSON.stringify({
|
|
69
|
+
error: error.message || 'Unknown error occurred',
|
|
70
|
+
operation
|
|
71
|
+
}, null, 2)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async listCollections(schema) {
|
|
76
|
+
try {
|
|
77
|
+
if (!this.pool) {
|
|
78
|
+
this.connect();
|
|
79
|
+
}
|
|
80
|
+
const database = schema || this.config.connection?.database;
|
|
81
|
+
const sqlQuery = database
|
|
82
|
+
? 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?'
|
|
83
|
+
: 'SHOW TABLES';
|
|
84
|
+
const [rows] = await this.pool.execute(sqlQuery, database ? [database] : undefined);
|
|
85
|
+
const tables = rows.map((row) => Object.values(row)[0]);
|
|
86
|
+
return {
|
|
87
|
+
type: 'text',
|
|
88
|
+
text: JSON.stringify({
|
|
89
|
+
tables,
|
|
90
|
+
count: tables.length,
|
|
91
|
+
database: database
|
|
92
|
+
}, null, 2)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return {
|
|
97
|
+
type: 'error',
|
|
98
|
+
text: JSON.stringify({
|
|
99
|
+
error: error.message || 'Unknown error occurred',
|
|
100
|
+
schema
|
|
101
|
+
}, null, 2)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async describeCollection(collection, schema) {
|
|
106
|
+
try {
|
|
107
|
+
if (!this.pool) {
|
|
108
|
+
this.connect();
|
|
109
|
+
}
|
|
110
|
+
const database = schema || this.config.connection?.database;
|
|
111
|
+
const fullTableName = database ? `${database}.${collection}` : collection;
|
|
112
|
+
const [columns] = await this.pool.execute(`DESCRIBE ${fullTableName}`);
|
|
113
|
+
return {
|
|
114
|
+
type: 'text',
|
|
115
|
+
text: JSON.stringify({
|
|
116
|
+
table: collection,
|
|
117
|
+
columns,
|
|
118
|
+
database: database
|
|
119
|
+
}, null, 2)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
type: 'error',
|
|
125
|
+
text: JSON.stringify({
|
|
126
|
+
error: error.message || 'Unknown error occurred',
|
|
127
|
+
table: collection,
|
|
128
|
+
schema
|
|
129
|
+
}, null, 2)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async disconnect() {
|
|
134
|
+
if (!this.pool) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
await this.pool.end();
|
|
138
|
+
this.pool = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=MySqlClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MySqlClient.js","sourceRoot":"","sources":["../../src/clients/MySqlClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StorageConfig } from '../../config.js';
|
|
2
|
+
import { McpConnector, McpConnectorResult } from '../../mcp-connectors/index.js';
|
|
3
|
+
export declare class AthenaConnector implements McpConnector {
|
|
4
|
+
private client;
|
|
5
|
+
private config;
|
|
6
|
+
private outputLocation;
|
|
7
|
+
private database;
|
|
8
|
+
private workGroup;
|
|
9
|
+
constructor(config: StorageConfig);
|
|
10
|
+
query(query: string, _params?: any): Promise<McpConnectorResult>;
|
|
11
|
+
execute(operation: string, params?: any): Promise<McpConnectorResult>;
|
|
12
|
+
listCollections(schema?: string): Promise<McpConnectorResult>;
|
|
13
|
+
describeCollection(collection: string, schema?: string): Promise<McpConnectorResult>;
|
|
14
|
+
private waitForQueryCompletion;
|
|
15
|
+
private formatResults;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=AthenaClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AthenaClient.d.ts","sourceRoot":"","sources":["../../../src/clients/athena-client/AthenaClient.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEjF,qBAAa,eAAgB,YAAW,YAAY;IAClD,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,aAAa;IAiB3B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAoDhE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAoBrE,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAgC7D,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;YA8C5E,sBAAsB;IA4BpC,OAAO,CAAC,aAAa;CAkCtB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { AthenaClient, StartQueryExecutionCommand, GetQueryExecutionCommand, GetQueryResultsCommand, QueryExecutionState, ListTableMetadataCommand, GetTableMetadataCommand, } from '@aws-sdk/client-athena';
|
|
2
|
+
export class AthenaConnector {
|
|
3
|
+
client;
|
|
4
|
+
config;
|
|
5
|
+
outputLocation;
|
|
6
|
+
database;
|
|
7
|
+
workGroup;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
const region = config.connection?.region || process.env.AWS_REGION || 'us-east-1';
|
|
11
|
+
this.outputLocation = config.connection?.s3OutputLocation || process.env.ATHENA_S3_OUTPUT || '';
|
|
12
|
+
this.database = config.connection?.database || process.env.ATHENA_DATABASE || 'default';
|
|
13
|
+
this.workGroup = config.connection?.workgroup || process.env.ATHENA_WORKGROUP || 'primary';
|
|
14
|
+
if (!this.outputLocation) {
|
|
15
|
+
throw new Error('Athena S3 output location is required (s3OutputLocation or ATHENA_S3_OUTPUT)');
|
|
16
|
+
}
|
|
17
|
+
this.client = new AthenaClient({
|
|
18
|
+
region,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async query(query, _params) {
|
|
22
|
+
try {
|
|
23
|
+
const startCommand = new StartQueryExecutionCommand({
|
|
24
|
+
QueryString: query,
|
|
25
|
+
QueryExecutionContext: {
|
|
26
|
+
Database: this.database,
|
|
27
|
+
},
|
|
28
|
+
ResultConfiguration: {
|
|
29
|
+
OutputLocation: this.outputLocation,
|
|
30
|
+
},
|
|
31
|
+
WorkGroup: this.workGroup,
|
|
32
|
+
});
|
|
33
|
+
const { QueryExecutionId } = await this.client.send(startCommand);
|
|
34
|
+
if (!QueryExecutionId) {
|
|
35
|
+
throw new Error('Failed to start query execution');
|
|
36
|
+
}
|
|
37
|
+
await this.waitForQueryCompletion(QueryExecutionId);
|
|
38
|
+
const resultsCommand = new GetQueryResultsCommand({
|
|
39
|
+
QueryExecutionId,
|
|
40
|
+
});
|
|
41
|
+
const results = await this.client.send(resultsCommand);
|
|
42
|
+
const rows = this.formatResults(results.ResultSet);
|
|
43
|
+
return {
|
|
44
|
+
type: 'text',
|
|
45
|
+
text: JSON.stringify({
|
|
46
|
+
rows,
|
|
47
|
+
rowCount: rows.length,
|
|
48
|
+
queryExecutionId: QueryExecutionId,
|
|
49
|
+
}, null, 2)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return {
|
|
54
|
+
type: 'error',
|
|
55
|
+
text: JSON.stringify({
|
|
56
|
+
error: error.message || 'Unknown error occurred',
|
|
57
|
+
query,
|
|
58
|
+
database: this.database,
|
|
59
|
+
}, null, 2)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async execute(operation, params) {
|
|
64
|
+
try {
|
|
65
|
+
if (!this.config.writeMode) {
|
|
66
|
+
throw new Error('Write operations are not allowed for this connection');
|
|
67
|
+
}
|
|
68
|
+
return await this.query(operation, params);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return {
|
|
72
|
+
type: 'error',
|
|
73
|
+
text: JSON.stringify({
|
|
74
|
+
error: error.message || 'Unknown error occurred',
|
|
75
|
+
operation,
|
|
76
|
+
}, null, 2)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async listCollections(schema) {
|
|
81
|
+
try {
|
|
82
|
+
const targetDatabase = schema || this.database;
|
|
83
|
+
const command = new ListTableMetadataCommand({
|
|
84
|
+
CatalogName: 'AwsDataCatalog',
|
|
85
|
+
DatabaseName: targetDatabase,
|
|
86
|
+
});
|
|
87
|
+
const response = await this.client.send(command);
|
|
88
|
+
const tables = response.TableMetadataList?.map(table => table.Name) || [];
|
|
89
|
+
return {
|
|
90
|
+
type: 'text',
|
|
91
|
+
text: JSON.stringify({
|
|
92
|
+
tables,
|
|
93
|
+
count: tables.length,
|
|
94
|
+
database: targetDatabase,
|
|
95
|
+
}, null, 2)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
type: 'error',
|
|
101
|
+
text: JSON.stringify({
|
|
102
|
+
error: error.message || 'Unknown error occurred',
|
|
103
|
+
database: schema || this.database,
|
|
104
|
+
}, null, 2)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async describeCollection(collection, schema) {
|
|
109
|
+
try {
|
|
110
|
+
const targetDatabase = schema || this.database;
|
|
111
|
+
const command = new GetTableMetadataCommand({
|
|
112
|
+
CatalogName: 'AwsDataCatalog',
|
|
113
|
+
DatabaseName: targetDatabase,
|
|
114
|
+
TableName: collection,
|
|
115
|
+
});
|
|
116
|
+
const response = await this.client.send(command);
|
|
117
|
+
const columns = response.TableMetadata?.Columns?.map(col => ({
|
|
118
|
+
name: col.Name,
|
|
119
|
+
type: col.Type,
|
|
120
|
+
comment: col.Comment,
|
|
121
|
+
})) || [];
|
|
122
|
+
const partitionKeys = response.TableMetadata?.PartitionKeys?.map(key => ({
|
|
123
|
+
name: key.Name,
|
|
124
|
+
type: key.Type,
|
|
125
|
+
})) || [];
|
|
126
|
+
return {
|
|
127
|
+
type: 'text',
|
|
128
|
+
text: JSON.stringify({
|
|
129
|
+
table: collection,
|
|
130
|
+
database: targetDatabase,
|
|
131
|
+
columns,
|
|
132
|
+
partitionKeys,
|
|
133
|
+
tableType: response.TableMetadata?.TableType,
|
|
134
|
+
location: response.TableMetadata?.Parameters?.['location'],
|
|
135
|
+
}, null, 2)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
type: 'error',
|
|
141
|
+
text: JSON.stringify({
|
|
142
|
+
error: error.message || 'Unknown error occurred',
|
|
143
|
+
table: collection,
|
|
144
|
+
database: schema || this.database,
|
|
145
|
+
}, null, 2)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async waitForQueryCompletion(queryExecutionId) {
|
|
150
|
+
const maxAttempts = 100;
|
|
151
|
+
const delayMs = 1000;
|
|
152
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
153
|
+
const command = new GetQueryExecutionCommand({
|
|
154
|
+
QueryExecutionId: queryExecutionId,
|
|
155
|
+
});
|
|
156
|
+
const response = await this.client.send(command);
|
|
157
|
+
const state = response.QueryExecution?.Status?.State;
|
|
158
|
+
switch (state) {
|
|
159
|
+
case QueryExecutionState.SUCCEEDED:
|
|
160
|
+
return;
|
|
161
|
+
case QueryExecutionState.FAILED:
|
|
162
|
+
throw new Error(`Query failed: ${response.QueryExecution?.Status?.StateChangeReason}`);
|
|
163
|
+
case QueryExecutionState.CANCELLED:
|
|
164
|
+
throw new Error('Query was cancelled');
|
|
165
|
+
default:
|
|
166
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
throw new Error('Query timeout - exceeded maximum wait time');
|
|
170
|
+
}
|
|
171
|
+
formatResults(resultSet) {
|
|
172
|
+
if (!resultSet?.Rows || resultSet.Rows.length === 0) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const firstRow = resultSet.Rows[0];
|
|
176
|
+
const headers = [];
|
|
177
|
+
if (firstRow?.Data && Array.isArray(firstRow.Data)) {
|
|
178
|
+
firstRow.Data.forEach((col) => {
|
|
179
|
+
headers.push(col.VarCharValue);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const rows = [];
|
|
183
|
+
for (let i = 1; i < resultSet.Rows.length; i++) {
|
|
184
|
+
const row = resultSet.Rows[i];
|
|
185
|
+
const obj = {};
|
|
186
|
+
if (row.Data && Array.isArray(row.Data)) {
|
|
187
|
+
row.Data.forEach((cell, index) => {
|
|
188
|
+
const header = headers[index];
|
|
189
|
+
if (header) {
|
|
190
|
+
obj[header] = cell.VarCharValue || null;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
rows.push(obj);
|
|
195
|
+
}
|
|
196
|
+
return rows;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=AthenaClient.js.map
|