@berthojoris/mcp-mysql-server 1.13.0 β†’ 1.14.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable changes to the MySQL MCP Server will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.14.1] - 2025-12-08
9
+
10
+ ### Added
11
+ - **Workflow Macros** - New `safe_export_table` tool that combines data export with mandatory masking.
12
+ - Allows safe export of sensitive data by enforcing masking before the data leaves the database.
13
+ - Supports configurable masking profiles (strict (default), partial, soft).
14
+
15
+ ## [1.14.0] - 2025-12-08
16
+
17
+ ### Added
18
+ - **Data Masking Profiles** - New security feature to mask sensitive data in query responses.
19
+ - Configurable via `MCP_MASKING_PROFILE` environment variable.
20
+ - Profiles: `none` (default), `soft` (secrets), `partial` (secrets + PII), `strict` (all sensitive).
21
+ - Automatically applies to `run_query` and `read_records`.
22
+
8
23
  ## [1.13.0] - 2025-12-07
9
24
 
10
25
  ### Added
package/DOCUMENTATIONS.md CHANGED
@@ -7,7 +7,7 @@ This file contains detailed documentation for all features of the MySQL MCP Serv
7
7
  ## Table of Contents
8
8
 
9
9
  1. [Category Filtering System](#πŸ†•-category-filtering-system) - NEW!
10
- 2. [πŸ”§ Complete Tools Reference](#πŸ”§-complete-tools-reference) - All 120 tools organized by category
10
+ 2. [πŸ”§ Complete Tools Reference](#πŸ”§-complete-tools-reference) - All 124 tools organized by category
11
11
  3. [DDL Operations](#πŸ—οΈ-ddl-operations)
12
12
  4. [Data Export Tools](#πŸ“€-data-export-tools)
13
13
  5. [Data Import Tools](#πŸ“₯-data-import-tools)
@@ -26,19 +26,6 @@ This file contains detailed documentation for all features of the MySQL MCP Serv
26
26
  18. [Performance Monitoring](#πŸ“ˆ-performance-monitoring)
27
27
  19. [Usage Examples](#πŸ“‹-usage-examples)
28
28
  20. [Query Logging & Automatic SQL Display](#πŸ“-query-logging--automatic-sql-display)
29
- 21. [Security Features](#πŸ”’-security-features)
30
- 22. [Query Result Caching](#πŸ’Ύ-query-result-caching)
31
- 23. [Query Optimization Hints](#🎯-query-optimization-hints)
32
- 24. [Guided Query Builder/Fixer](#πŸ€–-guided-query-builderfixer)
33
- 25. [Bulk Operations](#πŸš€-bulk-operations)
34
- 26. [OpenAI Codex Integration](#πŸ€–-openai-codex-integration)
35
- 27. [Troubleshooting](#πŸ› οΈ-troubleshooting)
36
- 28. [License](#πŸ“„-license)
37
- 29. [Roadmap](#πŸ—ΊοΈ-roadmap)
38
-
39
- ---
40
-
41
- ## Dual-Layer Filtering System
42
29
 
43
30
  Control which database operations are available to AI using a **dual-layer filtering system**:
44
31
 
@@ -754,6 +741,47 @@ Both tools support:
754
741
 
755
742
  ---
756
743
 
744
+ ## πŸ”„ Workflow Macros
745
+
746
+ Workflow Macros are composite tools designed to execute complex, multi-step operations safely and efficiently. They encapsulate best practices and security policies (like data masking) into single, atomic tool calls.
747
+
748
+ ### Available Macros
749
+
750
+ | Tool | Description |
751
+ |------|-------------|
752
+ | `safe_export_table` | Exports table data to CSV with mandated data masking (redaction/hashing) |
753
+
754
+ ### safe_export_table
755
+
756
+ Exports table data to CSV format *with enforced data masking*. This is safer than standard export tools because it ensures sensitive data is masked before leaving the database layer, regardless of the global masking configuration.
757
+
758
+ **Parameters:**
759
+ - `table_name` (required): Name of the table to export.
760
+ - `masking_profile` (optional): "strict" (default), "partial", or "soft".
761
+ - `limit` (optional): Maximum rows to export (default 1000, max 10000).
762
+ - `include_headers` (optional): Whether to include CSV headers (default true).
763
+
764
+ **Example:**
765
+ *User prompt: "Safely export the users table to a CSV file"*
766
+
767
+ ```json
768
+ {
769
+ "tool": "safe_export_table",
770
+ "arguments": {
771
+ "table_name": "users",
772
+ "masking_profile": "strict"
773
+ }
774
+ }
775
+ ```
776
+
777
+ **Result:**
778
+ CSV content where:
779
+ - Emails are masked (e.g., `j***@domain.com`)
780
+ - Passwords/secrets are `[REDACTED]`
781
+ - Phone numbers are partially hidden
782
+
783
+ ---
784
+
757
785
  ## πŸ“₯ Data Import Tools
758
786
 
759
787
  The MySQL MCP Server provides tools to import data from various formats into your database tables.
@@ -3759,7 +3787,44 @@ Each bulk operation returns performance metrics:
3759
3787
 
3760
3788
  ---
3761
3789
 
3762
- ## πŸ€– OpenAI Codex Integration
3790
+ ## 🎭 Data Masking
3791
+
3792
+ The MySQL MCP server includes a robust data masking layer to protect sensitive information (PII, credentials) in query results. This is useful when sharing database access with AI agents or third parties.
3793
+
3794
+ ### Features
3795
+
3796
+ - **Profile-Based Masking**: Easy configuration profiles (`none`, `soft`, `partial`, `strict`)
3797
+ - **Automatic Detection**: Automatically identifies sensitive columns by name (e.g., `email`, `password`, `ssn`)
3798
+ - **Multiple Strategies**:
3799
+ - **REDACT**: Replaces value with `[REDACTED]`
3800
+ - **PARTIAL**: Partially masks email (`j***@d.com`) and phone/CC (`***1234`)
3801
+ - **HASH**: (Internal placeholder)
3802
+
3803
+ ### Configuration
3804
+
3805
+ Configure the masking profile via the `MCP_MASKING_PROFILE` environment variable:
3806
+
3807
+ ```bash
3808
+ MCP_MASKING_PROFILE=partial
3809
+ ```
3810
+
3811
+ ### Profiles Reference
3812
+
3813
+ | Profile | Description | Credentials (password, key) | PII (email, phone, ssn) |
3814
+ |---------|-------------|-----------------------------|-------------------------|
3815
+ | `none` | No masking (default) | Show | Show |
3816
+ | `soft` | Protect secrets only | **REDACT** | Show |
3817
+ | `partial` | Balanced security | **REDACT** | **PARTIAL** (j***@...) |
3818
+ | `strict` | Maximum security | **REDACT** | **REDACT** |
3819
+
3820
+ ### Behavior
3821
+
3822
+ - Masking applies automatically to `run_query` and `read_records` results.
3823
+ - It filters output **after** the query is run, so WHERE clauses still work on the real data (e.g., you can search by email, but the result will be masked).
3824
+
3825
+ ---
3826
+
3827
+ ## πŸ€– OpenAI Codex Integration
3763
3828
 
3764
3829
  OpenAI Codex CLI and VS Code Extension support MCP servers through a shared TOML configuration file. This section provides detailed setup instructions for integrating the MySQL MCP Server with Codex.
3765
3830
 
@@ -4144,7 +4209,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
4144
4209
  | Drift & Migration Assistant (Schema diff + risk summary) | High | High | 4 | βœ… Completed |
4145
4210
  | Safety Sandbox Mode (runQuery dry-run/EXPLAIN-only) | Medium | Low | 5 | βœ… Completed |
4146
4211
  | Anomaly & Slow-Query Watcher | Medium | Medium | 6 | βœ… Completed |
4147
- | Data Masking Profiles for Responses | Medium | Medium | 7 | Planned |
4212
+ | Data Masking Profiles for Responses | Medium | Medium | 7 | βœ… Completed |
4148
4213
  | Workflow Macros (e.g., safe_export_table) | Medium | Low | 8 | Planned |
4149
4214
  | Agent-Facing Changelog Feed | Medium | Low | 9 | Planned |
4150
4215
  | Connection Profiles (dev/stage/prod with allow/deny) | High | Low | 10 | Planned |
package/README.md CHANGED
@@ -50,26 +50,13 @@ Add to your AI agent config (`.mcp.json`, `.cursor/mcp.json`, etc.):
50
50
  - [Environment Variables](#environment-variables-configuration)
51
51
  - [Local Development](#local-path-configuration)
52
52
  - [Permission System](#-permission-system)
53
- - [Available Tools (120 total)](#-available-tools)
53
+ - [Available Tools (124 total)](#-available-tools)
54
54
  - [Documentation](#-detailed-documentation)
55
55
  - [Comparison: MCP vs Manual Access](#-mysql-mcp-vs-manual-database-access)
56
56
  - [License](#-license)
57
57
 
58
58
  ---
59
-
60
- ## Features
61
-
62
- | Category | Description |
63
- |----------|-------------|
64
- | **Full MCP Support** | Works with Claude Code, Cursor, Windsurf, Zed, Cline, Kilo Code, Roo Code, Gemini CLI, OpenAI Codex, and any MCP-compatible AI agent |
65
- | **Security First** | Parameterized queries, SQL injection protection, permission-based access control |
66
- | **121 Powerful Tools** | Complete database operations including CRUD, DDL, transactions, stored procedures, backup/restore, migrations |
67
- | **Adaptive Presets** | Built-in ReadOnly/Analyst/DBA Lite permission bundles with override merging |
68
- | **Schema-Aware RAG Pack** | Compact schema snapshots (tables, PK/FK, row estimates) tailored for embeddings-friendly prompts |
69
- | **Category Filtering** | 22 documentation categories for intuitive, fine-grained access control (backward compatible with 10 legacy categories) |
70
- | **Transaction Support** | Full ACID transaction management (BEGIN, COMMIT, ROLLBACK) |
71
- | **Schema Migrations** | Version control for database schema with up/down migrations |
72
- | **Dual Mode** | Run as MCP server OR as REST API |
59
+ | **Data Masking** | Protect PII/Secrets in responses with configurable profiles (soft/partial/strict) |
73
60
  | **TypeScript** | Fully typed with TypeScript definitions |
74
61
 
75
62
  ---
@@ -106,6 +93,7 @@ DB_USER=root
106
93
  DB_PASSWORD=yourpassword
107
94
  DB_NAME=yourdatabase
108
95
  MCP_CONFIG=list,read,utility
96
+ MCP_MASKING_PROFILE=partial
109
97
  ```
110
98
 
111
99
  ### 2. Build Project (If Cloned Locally)
@@ -367,7 +355,8 @@ Alternative approach using environment variables instead of connection string:
367
355
  "DB_PASSWORD": "your_password",
368
356
  "DB_NAME": "your_database",
369
357
  "MCP_PERMISSIONS": "list,read,utility",
370
- "MCP_CATEGORIES": "database_discovery,performance_monitoring"
358
+ "MCP_CATEGORIES": "database_discovery,performance_monitoring",
359
+ "MCP_MASKING_PROFILE": "partial"
371
360
  }
372
361
  }
373
362
  }
@@ -496,12 +485,12 @@ Use these categories for fine-grained control that matches the tool organization
496
485
  | `server_management` | 9 | Process list, explain queries, server info |
497
486
  | `performance_monitoring` | 10 | Metrics, slow queries, health checks |
498
487
  | `cache_management` | 5 | Query cache stats and configuration |
499
- | `query_optimization` | 2 | Analyze queries, get optimization hints |
488
+ | `query_optimization` | 3 | Analyze queries, get optimization hints |
500
489
  | `backup_restore` | 5 | Backup/restore database and tables |
501
490
  | `import_export` | 5 | Import/export JSON, CSV, SQL |
502
491
  | `data_migration` | 5 | Copy, move, clone, sync table data |
503
492
  | `schema_migrations` | 9 | Version control for database schema |
504
- | `analysis` | 3 | AI context optimization and data analysis |
493
+ | `analysis` | 4 | AI context optimization and data analysis |
505
494
 
506
495
  ### Legacy Categories (Backward Compatible)
507
496
 
@@ -531,7 +520,7 @@ Use these categories for fine-grained control that matches the tool organization
531
520
  | **Application Backend** | `database_discovery,crud_operations,bulk_operations,custom_queries,transaction_management` | Full app support |
532
521
  | **Development & Testing** | `database_discovery,crud_operations,bulk_operations,custom_queries,schema_management,utilities,transaction_management` | Development access |
533
522
  | **DBA & DevOps** | `database_discovery,schema_management,table_maintenance,backup_restore,schema_migrations,performance_monitoring` | Admin tasks |
534
- | **Full Access** | *(leave empty)* | All 120 tools enabled |
523
+ | **Full Access** | *(leave empty)* | All 124 tools enabled |
535
524
 
536
525
  #### Using Legacy Categories (Backward Compatible)
537
526
 
@@ -652,11 +641,11 @@ Use both 2nd argument (permissions) and 3rd argument (categories):
652
641
 
653
642
  ## Available Tools
654
643
 
655
- The MCP server provides **120 powerful tools** organized into categories:
644
+ The MCP server provides **124 powerful tools** organized into categories:
656
645
 
657
646
  ### Quick Reference
658
647
 
659
- **121 Tools Available** - Organized into 22 categories
648
+ **124 Tools Available** - Organized into 22 categories
660
649
 
661
650
  | Category | Count | Key Tools |
662
651
  |----------|-------|-----------|
@@ -230,19 +230,6 @@ exports.toolCategoryMap = {
230
230
  showReplicationStatus: ToolCategory.LIST,
231
231
  // Backup and restore tools
232
232
  backupTable: ToolCategory.UTILITY,
233
- backupDatabase: ToolCategory.UTILITY,
234
- restoreFromSql: ToolCategory.DDL,
235
- getCreateTableStatement: ToolCategory.LIST,
236
- getDatabaseSchema: ToolCategory.LIST,
237
- // Extended data export/import tools
238
- exportTableToJSON: ToolCategory.UTILITY,
239
- exportQueryToJSON: ToolCategory.UTILITY,
240
- exportTableToSql: ToolCategory.UTILITY,
241
- importFromCSV: ToolCategory.CREATE,
242
- importFromJSON: ToolCategory.CREATE,
243
- // Data migration tools
244
- copyTableData: ToolCategory.CREATE,
245
- moveTableData: ToolCategory.DELETE,
246
233
  cloneTable: ToolCategory.DDL,
247
234
  compareTableStructure: ToolCategory.LIST,
248
235
  syncTableData: ToolCategory.UPDATE,
@@ -397,6 +384,7 @@ exports.toolDocCategoryMap = {
397
384
  exportTableToJSON: DocCategory.IMPORT_EXPORT,
398
385
  exportQueryToJSON: DocCategory.IMPORT_EXPORT,
399
386
  exportTableToSql: DocCategory.IMPORT_EXPORT,
387
+ safe_export_table: DocCategory.IMPORT_EXPORT,
400
388
  importFromCSV: DocCategory.IMPORT_EXPORT,
401
389
  importFromJSON: DocCategory.IMPORT_EXPORT,
402
390
  // Data Migration
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ export declare class MySQLMCP {
24
24
  private performanceTools;
25
25
  private analysisTools;
26
26
  private aiTools;
27
+ private macroTools;
27
28
  private security;
28
29
  private featureConfig;
29
30
  constructor(permissionsConfig?: string, categoriesConfig?: string, presetName?: string);
@@ -590,6 +591,16 @@ export declare class MySQLMCP {
590
591
  suggestions?: string[];
591
592
  error?: string;
592
593
  }>;
594
+ safeExportTable(params: {
595
+ table_name: string;
596
+ masking_profile?: string;
597
+ limit?: number;
598
+ include_headers?: boolean;
599
+ }): Promise<{
600
+ status: string;
601
+ data?: any;
602
+ error?: string;
603
+ }>;
593
604
  getFeatureStatus(): {
594
605
  status: string;
595
606
  data: {
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ const schemaVersioningTools_1 = require("./tools/schemaVersioningTools");
25
25
  const performanceTools_1 = require("./tools/performanceTools");
26
26
  const analysisTools_1 = require("./tools/analysisTools");
27
27
  const aiTools_1 = require("./tools/aiTools");
28
+ const macroTools_1 = require("./tools/macroTools");
28
29
  const securityLayer_1 = __importDefault(require("./security/securityLayer"));
29
30
  const connection_1 = __importDefault(require("./db/connection"));
30
31
  const featureConfig_1 = require("./config/featureConfig");
@@ -56,7 +57,9 @@ class MySQLMCP {
56
57
  this.schemaVersioningTools = new schemaVersioningTools_1.SchemaVersioningTools(this.security);
57
58
  this.performanceTools = new performanceTools_1.PerformanceTools(this.security);
58
59
  this.analysisTools = new analysisTools_1.AnalysisTools(this.security);
60
+ this.analysisTools = new analysisTools_1.AnalysisTools(this.security);
59
61
  this.aiTools = new aiTools_1.AiTools(this.security);
62
+ this.macroTools = new macroTools_1.MacroTools(this.security);
60
63
  }
61
64
  // Helper method to check if tool is enabled
62
65
  checkToolEnabled(toolName) {
@@ -535,6 +538,14 @@ class MySQLMCP {
535
538
  }
536
539
  return await this.aiTools.repairQuery(params);
537
540
  }
541
+ // Workflow Macros
542
+ async safeExportTable(params) {
543
+ const check = this.checkToolEnabled("safe_export_table");
544
+ if (!check.enabled) {
545
+ return { status: "error", error: check.error };
546
+ }
547
+ return await this.macroTools.safeExportTable(params);
548
+ }
538
549
  // Get feature configuration status
539
550
  getFeatureStatus() {
540
551
  const snapshot = this.featureConfig.getConfigSnapshot();
@@ -476,6 +476,33 @@ const TOOLS = [
476
476
  required: ["query"],
477
477
  },
478
478
  },
479
+ {
480
+ name: "safe_export_table",
481
+ description: "Exports table data to CSV with enforced data masking rules to protect sensitive information.",
482
+ inputSchema: {
483
+ type: "object",
484
+ properties: {
485
+ table_name: {
486
+ type: "string",
487
+ description: "Name of the table to export",
488
+ },
489
+ masking_profile: {
490
+ type: "string",
491
+ enum: ["soft", "partial", "strict"],
492
+ description: "Masking profile to apply (default: strict). strict=redact all PII/secrets, partial=partial mask PII, soft=mask secrets only.",
493
+ },
494
+ limit: {
495
+ type: "number",
496
+ description: "Maximum number of rows to export (default: 1000, max: 10000)",
497
+ },
498
+ include_headers: {
499
+ type: "boolean",
500
+ description: "Whether to include CSV headers (default: true)",
501
+ },
502
+ },
503
+ required: ["table_name"],
504
+ },
505
+ },
479
506
  {
480
507
  name: "repair_query",
481
508
  description: "Analyzes a SQL query (and optional error) to suggest repairs or optimizations using EXPLAIN and heuristics.",
@@ -3083,6 +3110,12 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
3083
3110
  case "export_query_to_json":
3084
3111
  result = await mysqlMCP.exportQueryToJSON((args || {}));
3085
3112
  break;
3113
+ case "export_query_to_csv":
3114
+ result = await mysqlMCP.exportQueryToCSV((args || {}));
3115
+ break;
3116
+ case "safe_export_table":
3117
+ result = await mysqlMCP.safeExportTable((args || {}));
3118
+ break;
3086
3119
  case "export_table_to_sql":
3087
3120
  result = await mysqlMCP.exportTableToSql((args || {}));
3088
3121
  break;
@@ -0,0 +1,37 @@
1
+ export declare enum MaskingStrategy {
2
+ REDACT = "redact",// Replace with [REDACTED]
3
+ PARTIAL = "partial",// Show first/last chars: a***@b.com
4
+ HASH = "hash",// SHA256 hash (simulated for simplicity or real)
5
+ NONE = "none"
6
+ }
7
+ export interface MaskingRule {
8
+ columnPattern: RegExp;
9
+ strategy: MaskingStrategy;
10
+ }
11
+ export declare enum MaskingProfile {
12
+ NONE = "none",
13
+ SOFT = "soft",// Mask only credentials (passwords, secrets)
14
+ PARTIAL = "partial",// Mask credentials + partial mask PII (email, phone)
15
+ STRICT = "strict"
16
+ }
17
+ /**
18
+ * Data Masking Layer
19
+ * Handles identifying and masking sensitive data in query results
20
+ */
21
+ export declare class MaskingLayer {
22
+ private profile;
23
+ private rules;
24
+ constructor(profile?: string);
25
+ private parseProfile;
26
+ private getRulesForProfile;
27
+ /**
28
+ * Check if filtering is active
29
+ */
30
+ isEnabled(): boolean;
31
+ getProfile(): MaskingProfile;
32
+ /**
33
+ * Apply masking to a dataset (array of objects)
34
+ */
35
+ processResults(data: any[]): any[];
36
+ private applyStrategy;
37
+ }
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MaskingLayer = exports.MaskingProfile = exports.MaskingStrategy = void 0;
4
+ var MaskingStrategy;
5
+ (function (MaskingStrategy) {
6
+ MaskingStrategy["REDACT"] = "redact";
7
+ MaskingStrategy["PARTIAL"] = "partial";
8
+ MaskingStrategy["HASH"] = "hash";
9
+ MaskingStrategy["NONE"] = "none"; // No masking
10
+ })(MaskingStrategy || (exports.MaskingStrategy = MaskingStrategy = {}));
11
+ var MaskingProfile;
12
+ (function (MaskingProfile) {
13
+ MaskingProfile["NONE"] = "none";
14
+ MaskingProfile["SOFT"] = "soft";
15
+ MaskingProfile["PARTIAL"] = "partial";
16
+ MaskingProfile["STRICT"] = "strict"; // Redact all PII and credentials
17
+ })(MaskingProfile || (exports.MaskingProfile = MaskingProfile = {}));
18
+ /**
19
+ * Data Masking Layer
20
+ * Handles identifying and masking sensitive data in query results
21
+ */
22
+ class MaskingLayer {
23
+ constructor(profile = "none") {
24
+ this.profile = this.parseProfile(profile);
25
+ this.rules = this.getRulesForProfile(this.profile);
26
+ }
27
+ parseProfile(input) {
28
+ const normalized = input.toLowerCase().trim();
29
+ if (Object.values(MaskingProfile).includes(normalized)) {
30
+ return normalized;
31
+ }
32
+ return MaskingProfile.NONE;
33
+ }
34
+ getRulesForProfile(profile) {
35
+ const credentialsPattern = /^(password|passwd|pwd|secret|token|api_key|auth_key|access_token|refresh_token)$/i;
36
+ const piiPattern = /^(email|phone|mobile|cell|ssn|social_security|credit_card|cc_number|card_number|iban|dob|date_of_birth)$/i;
37
+ switch (profile) {
38
+ case MaskingProfile.NONE:
39
+ return [];
40
+ case MaskingProfile.SOFT:
41
+ return [
42
+ { columnPattern: credentialsPattern, strategy: MaskingStrategy.REDACT }
43
+ ];
44
+ case MaskingProfile.PARTIAL:
45
+ return [
46
+ { columnPattern: credentialsPattern, strategy: MaskingStrategy.REDACT },
47
+ { columnPattern: piiPattern, strategy: MaskingStrategy.PARTIAL }
48
+ ];
49
+ case MaskingProfile.STRICT:
50
+ return [
51
+ { columnPattern: credentialsPattern, strategy: MaskingStrategy.REDACT },
52
+ { columnPattern: piiPattern, strategy: MaskingStrategy.REDACT }
53
+ ];
54
+ default:
55
+ return [];
56
+ }
57
+ }
58
+ /**
59
+ * Check if filtering is active
60
+ */
61
+ isEnabled() {
62
+ return this.profile !== MaskingProfile.NONE;
63
+ }
64
+ getProfile() {
65
+ return this.profile;
66
+ }
67
+ /**
68
+ * Apply masking to a dataset (array of objects)
69
+ */
70
+ processResults(data) {
71
+ if (!this.isEnabled() || !data || data.length === 0) {
72
+ return data;
73
+ }
74
+ // Identify columns to mask based on the first record (optimization)
75
+ const firstRecord = data[0];
76
+ const columns = Object.keys(firstRecord);
77
+ const columnsToMask = [];
78
+ for (const col of columns) {
79
+ for (const rule of this.rules) {
80
+ if (rule.columnPattern.test(col)) {
81
+ columnsToMask.push({ col, strategy: rule.strategy });
82
+ break; // Apply first matching rule
83
+ }
84
+ }
85
+ }
86
+ if (columnsToMask.length === 0) {
87
+ // No sensitive columns found
88
+ return data;
89
+ }
90
+ // Apply masking to all records
91
+ return data.map(record => {
92
+ const maskedRecord = { ...record };
93
+ for (const { col, strategy } of columnsToMask) {
94
+ if (maskedRecord[col] !== null && maskedRecord[col] !== undefined) {
95
+ maskedRecord[col] = this.applyStrategy(maskedRecord[col], strategy);
96
+ }
97
+ }
98
+ return maskedRecord;
99
+ });
100
+ }
101
+ applyStrategy(value, strategy) {
102
+ const strVal = String(value);
103
+ switch (strategy) {
104
+ case MaskingStrategy.REDACT:
105
+ return "[REDACTED]";
106
+ case MaskingStrategy.PARTIAL:
107
+ if (strVal.includes('@')) {
108
+ // Email masking: j***@domain.com
109
+ const [local, domain] = strVal.split('@');
110
+ const maskedLocal = local.length > 2 ? local[0] + '***' + local[local.length - 1] : '***';
111
+ return `${maskedLocal}@${domain}`;
112
+ }
113
+ else if (strVal.length > 4) {
114
+ // Generic partial: show last 4 chars (e.g. phone/cc)
115
+ // or first 1 + last 4
116
+ return '***' + strVal.slice(-4);
117
+ }
118
+ else {
119
+ return "***";
120
+ }
121
+ case MaskingStrategy.HASH:
122
+ // Simple placeholder for hash to avoid crypto dependency if not needed,
123
+ // or use a simple consistent hash if strictly required.
124
+ // For now, let's use a redaction-like placeholder to indicate hashing intent.
125
+ return "[HASHED]";
126
+ default:
127
+ return value;
128
+ }
129
+ }
130
+ }
131
+ exports.MaskingLayer = MaskingLayer;
@@ -1,10 +1,12 @@
1
1
  import { FeatureConfig } from "../config/featureConfig.js";
2
+ import { MaskingLayer } from "./maskingLayer.js";
2
3
  export declare class SecurityLayer {
3
4
  private ajv;
4
5
  private readonly dangerousKeywords;
5
6
  private readonly allowedOperations;
6
7
  private readonly ddlOperations;
7
8
  private featureConfig;
9
+ masking: MaskingLayer;
8
10
  constructor(featureConfig?: FeatureConfig);
9
11
  /**
10
12
  * Check if a query is a read-only information query (SHOW, DESCRIBE, EXPLAIN, etc.)
@@ -6,10 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SecurityLayer = void 0;
7
7
  const ajv_1 = __importDefault(require("ajv"));
8
8
  const featureConfig_js_1 = require("../config/featureConfig.js");
9
+ const maskingLayer_js_1 = require("./maskingLayer.js");
9
10
  class SecurityLayer {
10
11
  constructor(featureConfig) {
11
12
  this.ajv = new ajv_1.default();
12
13
  this.featureConfig = featureConfig || new featureConfig_js_1.FeatureConfig();
14
+ // Initialize masking layer from environment variable
15
+ const maskingProfile = process.env.MCP_MASKING_PROFILE || "none";
16
+ this.masking = new maskingLayer_js_1.MaskingLayer(maskingProfile);
13
17
  // Define dangerous SQL keywords that should ALWAYS be blocked (critical security threats)
14
18
  // These are blocked even with 'execute' permission
15
19
  // Note: Avoid blocking common table/column names like "user" or "password"
@@ -187,7 +187,7 @@ class CrudTools {
187
187
  const results = await this.db.query(query, paramValidation.sanitizedParams);
188
188
  return {
189
189
  status: "success",
190
- data: results,
190
+ data: this.security.masking.processResults(results),
191
191
  total,
192
192
  };
193
193
  }
@@ -197,7 +197,7 @@ class CrudTools {
197
197
  const results = await this.db.query(query, paramValidation.sanitizedParams);
198
198
  return {
199
199
  status: "success",
200
- data: results,
200
+ data: this.security.masking.processResults(results),
201
201
  total: results.length,
202
202
  };
203
203
  }
@@ -0,0 +1,20 @@
1
+ import SecurityLayer from "../security/securityLayer";
2
+ export declare class MacroTools {
3
+ private db;
4
+ private security;
5
+ constructor(security: SecurityLayer);
6
+ /**
7
+ * Safe Export Table: Exports table data to CSV with enforced data masking
8
+ * This macro prioritizes data safety by applying masking rules before export.
9
+ */
10
+ safeExportTable(params: {
11
+ table_name: string;
12
+ masking_profile?: string;
13
+ limit?: number;
14
+ include_headers?: boolean;
15
+ }): Promise<{
16
+ status: string;
17
+ data?: any;
18
+ error?: string;
19
+ }>;
20
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MacroTools = void 0;
7
+ const connection_1 = __importDefault(require("../db/connection"));
8
+ const maskingLayer_1 = require("../security/maskingLayer");
9
+ class MacroTools {
10
+ constructor(security) {
11
+ this.db = connection_1.default.getInstance();
12
+ this.security = security;
13
+ }
14
+ /**
15
+ * Safe Export Table: Exports table data to CSV with enforced data masking
16
+ * This macro prioritizes data safety by applying masking rules before export.
17
+ */
18
+ async safeExportTable(params) {
19
+ try {
20
+ const { table_name, masking_profile = "strict", limit = 1000, include_headers = true, } = params;
21
+ // 1. Validate table name
22
+ const tableValidation = this.security.validateIdentifier(table_name);
23
+ if (!tableValidation.valid) {
24
+ return {
25
+ status: "error",
26
+ error: tableValidation.error,
27
+ };
28
+ }
29
+ // 2. Fetch data (with hard limit to prevent OOM on large safe exports)
30
+ const maxLimit = 10000;
31
+ const actualLimit = Math.min(limit, maxLimit);
32
+ const escapedTableName = this.security.escapeIdentifier(table_name);
33
+ const query = `SELECT * FROM ${escapedTableName} LIMIT ?`;
34
+ const results = (await this.db.query(query, [actualLimit]));
35
+ if (results.length === 0) {
36
+ return {
37
+ status: "success",
38
+ data: {
39
+ csv: include_headers ? "" : "",
40
+ row_count: 0,
41
+ applied_profile: masking_profile,
42
+ },
43
+ };
44
+ }
45
+ // 3. Apply masking explicitly using a new temporary layer to ensure strictness
46
+ // We don't rely on the global masking profile here; we use the one requested (default strict)
47
+ const tempMaskingLayer = new maskingLayer_1.MaskingLayer(masking_profile);
48
+ const maskedResults = tempMaskingLayer.processResults(results);
49
+ // 4. Convert to CSV
50
+ let csv = "";
51
+ if (include_headers) {
52
+ const headers = Object.keys(maskedResults[0]).join(",");
53
+ csv += headers + "\n";
54
+ }
55
+ for (const row of maskedResults) {
56
+ const values = Object.values(row)
57
+ .map((value) => {
58
+ if (value === null)
59
+ return "";
60
+ if (value === undefined)
61
+ return "";
62
+ let str = String(value);
63
+ // Escape quotes and wrap in quotes if contains comma, newline or quotes
64
+ if (str.includes(",") ||
65
+ str.includes("\n") ||
66
+ str.includes('"')) {
67
+ return `"${str.replace(/"/g, '""')}"`;
68
+ }
69
+ return str;
70
+ })
71
+ .join(",");
72
+ csv += values + "\n";
73
+ }
74
+ return {
75
+ status: "success",
76
+ data: {
77
+ csv: csv,
78
+ row_count: maskedResults.length,
79
+ applied_profile: tempMaskingLayer.getProfile(),
80
+ },
81
+ };
82
+ }
83
+ catch (error) {
84
+ return {
85
+ status: "error",
86
+ error: error.message,
87
+ };
88
+ }
89
+ }
90
+ }
91
+ exports.MacroTools = MacroTools;
@@ -90,9 +90,10 @@ class QueryTools {
90
90
  }
91
91
  // Execute the query with sanitized parameters
92
92
  const results = await this.db.query(finalQuery, paramValidation.sanitizedParams, useCache);
93
+ const maskedResults = this.security.masking.processResults(results);
93
94
  return {
94
95
  status: "success",
95
- data: results,
96
+ data: maskedResults,
96
97
  optimizedQuery,
97
98
  };
98
99
  }
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mysql-mcp",
3
3
  "description": "A Model Context Protocol for MySQL database interaction",
4
- "version": "1.12.0",
4
+ "version": "1.13.0",
5
5
  "tools": [
6
6
  {
7
7
  "name": "list_databases",
@@ -9,7 +9,9 @@
9
9
  "input_schema": {},
10
10
  "output_schema": {
11
11
  "type": "array",
12
- "items": { "type": "string" }
12
+ "items": {
13
+ "type": "string"
14
+ }
13
15
  }
14
16
  },
15
17
  {
@@ -18,7 +20,9 @@
18
20
  "input_schema": {},
19
21
  "output_schema": {
20
22
  "type": "array",
21
- "items": { "type": "string" }
23
+ "items": {
24
+ "type": "string"
25
+ }
22
26
  }
23
27
  },
24
28
  {
@@ -27,9 +31,13 @@
27
31
  "input_schema": {
28
32
  "type": "object",
29
33
  "properties": {
30
- "table_name": { "type": "string" }
34
+ "table_name": {
35
+ "type": "string"
36
+ }
31
37
  },
32
- "required": ["table_name"]
38
+ "required": [
39
+ "table_name"
40
+ ]
33
41
  },
34
42
  "output_schema": {
35
43
  "type": "object",
@@ -39,18 +47,38 @@
39
47
  "items": {
40
48
  "type": "object",
41
49
  "properties": {
42
- "name": { "type": "string" },
43
- "type": { "type": "string" },
44
- "nullable": { "type": "boolean" },
45
- "default": { "type": ["string", "null"] },
46
- "primary_key": { "type": "boolean" }
50
+ "name": {
51
+ "type": "string"
52
+ },
53
+ "type": {
54
+ "type": "string"
55
+ },
56
+ "nullable": {
57
+ "type": "boolean"
58
+ },
59
+ "default": {
60
+ "type": [
61
+ "string",
62
+ "null"
63
+ ]
64
+ },
65
+ "primary_key": {
66
+ "type": "boolean"
67
+ }
47
68
  }
48
69
  }
49
70
  },
50
- "primary_key": { "type": ["string", "null"] },
71
+ "primary_key": {
72
+ "type": [
73
+ "string",
74
+ "null"
75
+ ]
76
+ },
51
77
  "indexes": {
52
78
  "type": "array",
53
- "items": { "type": "string" }
79
+ "items": {
80
+ "type": "string"
81
+ }
54
82
  }
55
83
  }
56
84
  }
@@ -61,17 +89,33 @@
61
89
  "input_schema": {
62
90
  "type": "object",
63
91
  "properties": {
64
- "table_name": { "type": "string" },
65
- "data": { "type": "object" }
92
+ "table_name": {
93
+ "type": "string"
94
+ },
95
+ "data": {
96
+ "type": "object"
97
+ }
66
98
  },
67
- "required": ["table_name", "data"]
99
+ "required": [
100
+ "table_name",
101
+ "data"
102
+ ]
68
103
  },
69
104
  "output_schema": {
70
105
  "type": "object",
71
106
  "properties": {
72
- "success": { "type": "boolean" },
73
- "id": { "type": ["string", "number"] },
74
- "affected_rows": { "type": "number" }
107
+ "success": {
108
+ "type": "boolean"
109
+ },
110
+ "id": {
111
+ "type": [
112
+ "string",
113
+ "number"
114
+ ]
115
+ },
116
+ "affected_rows": {
117
+ "type": "number"
118
+ }
75
119
  }
76
120
  }
77
121
  },
@@ -81,20 +125,42 @@
81
125
  "input_schema": {
82
126
  "type": "object",
83
127
  "properties": {
84
- "table_name": { "type": "string" },
85
- "filters": { "type": "object" },
86
- "limit": { "type": "number" },
87
- "offset": { "type": "number" },
88
- "sort_by": { "type": "string" },
89
- "sort_direction": { "type": "string", "enum": ["ASC", "DESC"] }
128
+ "table_name": {
129
+ "type": "string"
130
+ },
131
+ "filters": {
132
+ "type": "object"
133
+ },
134
+ "limit": {
135
+ "type": "number"
136
+ },
137
+ "offset": {
138
+ "type": "number"
139
+ },
140
+ "sort_by": {
141
+ "type": "string"
142
+ },
143
+ "sort_direction": {
144
+ "type": "string",
145
+ "enum": [
146
+ "ASC",
147
+ "DESC"
148
+ ]
149
+ }
90
150
  },
91
- "required": ["table_name"]
151
+ "required": [
152
+ "table_name"
153
+ ]
92
154
  },
93
155
  "output_schema": {
94
156
  "type": "object",
95
157
  "properties": {
96
- "records": { "type": "array" },
97
- "total": { "type": "number" }
158
+ "records": {
159
+ "type": "array"
160
+ },
161
+ "total": {
162
+ "type": "number"
163
+ }
98
164
  }
99
165
  }
100
166
  },
@@ -104,18 +170,37 @@
104
170
  "input_schema": {
105
171
  "type": "object",
106
172
  "properties": {
107
- "table_name": { "type": "string" },
108
- "id_field": { "type": "string" },
109
- "id": { "type": ["string", "number"] },
110
- "data": { "type": "object" }
173
+ "table_name": {
174
+ "type": "string"
175
+ },
176
+ "id_field": {
177
+ "type": "string"
178
+ },
179
+ "id": {
180
+ "type": [
181
+ "string",
182
+ "number"
183
+ ]
184
+ },
185
+ "data": {
186
+ "type": "object"
187
+ }
111
188
  },
112
- "required": ["table_name", "id", "data"]
189
+ "required": [
190
+ "table_name",
191
+ "id",
192
+ "data"
193
+ ]
113
194
  },
114
195
  "output_schema": {
115
196
  "type": "object",
116
197
  "properties": {
117
- "success": { "type": "boolean" },
118
- "affected_rows": { "type": "number" }
198
+ "success": {
199
+ "type": "boolean"
200
+ },
201
+ "affected_rows": {
202
+ "type": "number"
203
+ }
119
204
  }
120
205
  }
121
206
  },
@@ -125,17 +210,33 @@
125
210
  "input_schema": {
126
211
  "type": "object",
127
212
  "properties": {
128
- "table_name": { "type": "string" },
129
- "id_field": { "type": "string" },
130
- "id": { "type": ["string", "number"] }
213
+ "table_name": {
214
+ "type": "string"
215
+ },
216
+ "id_field": {
217
+ "type": "string"
218
+ },
219
+ "id": {
220
+ "type": [
221
+ "string",
222
+ "number"
223
+ ]
224
+ }
131
225
  },
132
- "required": ["table_name", "id"]
226
+ "required": [
227
+ "table_name",
228
+ "id"
229
+ ]
133
230
  },
134
231
  "output_schema": {
135
232
  "type": "object",
136
233
  "properties": {
137
- "success": { "type": "boolean" },
138
- "affected_rows": { "type": "number" }
234
+ "success": {
235
+ "type": "boolean"
236
+ },
237
+ "affected_rows": {
238
+ "type": "number"
239
+ }
139
240
  }
140
241
  }
141
242
  },
@@ -145,16 +246,26 @@
145
246
  "input_schema": {
146
247
  "type": "object",
147
248
  "properties": {
148
- "query": { "type": "string" },
149
- "params": { "type": "array" }
249
+ "query": {
250
+ "type": "string"
251
+ },
252
+ "params": {
253
+ "type": "array"
254
+ }
150
255
  },
151
- "required": ["query"]
256
+ "required": [
257
+ "query"
258
+ ]
152
259
  },
153
260
  "output_schema": {
154
261
  "type": "object",
155
262
  "properties": {
156
- "results": { "type": "array" },
157
- "fields": { "type": "array" }
263
+ "results": {
264
+ "type": "array"
265
+ },
266
+ "fields": {
267
+ "type": "array"
268
+ }
158
269
  }
159
270
  }
160
271
  },
@@ -164,17 +275,32 @@
164
275
  "input_schema": {
165
276
  "type": "object",
166
277
  "properties": {
167
- "query": { "type": "string" },
168
- "params": { "type": "array" }
278
+ "query": {
279
+ "type": "string"
280
+ },
281
+ "params": {
282
+ "type": "array"
283
+ }
169
284
  },
170
- "required": ["query"]
285
+ "required": [
286
+ "query"
287
+ ]
171
288
  },
172
289
  "output_schema": {
173
290
  "type": "object",
174
291
  "properties": {
175
- "success": { "type": "boolean" },
176
- "affected_rows": { "type": "number" },
177
- "insert_id": { "type": ["number", "null"] }
292
+ "success": {
293
+ "type": "boolean"
294
+ },
295
+ "affected_rows": {
296
+ "type": "number"
297
+ },
298
+ "insert_id": {
299
+ "type": [
300
+ "number",
301
+ "null"
302
+ ]
303
+ }
178
304
  }
179
305
  }
180
306
  },
@@ -185,11 +311,21 @@
185
311
  "output_schema": {
186
312
  "type": "object",
187
313
  "properties": {
188
- "host": { "type": "string" },
189
- "port": { "type": "number" },
190
- "database": { "type": "string" },
191
- "user": { "type": "string" },
192
- "connected": { "type": "boolean" }
314
+ "host": {
315
+ "type": "string"
316
+ },
317
+ "port": {
318
+ "type": "number"
319
+ },
320
+ "database": {
321
+ "type": "string"
322
+ },
323
+ "user": {
324
+ "type": "string"
325
+ },
326
+ "connected": {
327
+ "type": "boolean"
328
+ }
193
329
  }
194
330
  }
195
331
  },
@@ -200,9 +336,15 @@
200
336
  "output_schema": {
201
337
  "type": "object",
202
338
  "properties": {
203
- "success": { "type": "boolean" },
204
- "latency_ms": { "type": "number" },
205
- "message": { "type": "string" }
339
+ "success": {
340
+ "type": "boolean"
341
+ },
342
+ "latency_ms": {
343
+ "type": "number"
344
+ },
345
+ "message": {
346
+ "type": "string"
347
+ }
206
348
  }
207
349
  }
208
350
  },
@@ -212,9 +354,13 @@
212
354
  "input_schema": {
213
355
  "type": "object",
214
356
  "properties": {
215
- "table_name": { "type": "string" }
357
+ "table_name": {
358
+ "type": "string"
359
+ }
216
360
  },
217
- "required": ["table_name"]
361
+ "required": [
362
+ "table_name"
363
+ ]
218
364
  },
219
365
  "output_schema": {
220
366
  "type": "object",
@@ -224,9 +370,15 @@
224
370
  "items": {
225
371
  "type": "object",
226
372
  "properties": {
227
- "table": { "type": "string" },
228
- "column": { "type": "string" },
229
- "referenced_column": { "type": "string" }
373
+ "table": {
374
+ "type": "string"
375
+ },
376
+ "column": {
377
+ "type": "string"
378
+ },
379
+ "referenced_column": {
380
+ "type": "string"
381
+ }
230
382
  }
231
383
  }
232
384
  },
@@ -235,9 +387,15 @@
235
387
  "items": {
236
388
  "type": "object",
237
389
  "properties": {
238
- "table": { "type": "string" },
239
- "column": { "type": "string" },
240
- "referenced_column": { "type": "string" }
390
+ "table": {
391
+ "type": "string"
392
+ },
393
+ "column": {
394
+ "type": "string"
395
+ },
396
+ "referenced_column": {
397
+ "type": "string"
398
+ }
241
399
  }
242
400
  }
243
401
  }
@@ -250,18 +408,36 @@
250
408
  "input_schema": {
251
409
  "type": "object",
252
410
  "properties": {
253
- "database": { "type": "string", "description": "Optional specific database name" },
254
- "max_tables": { "type": "number", "description": "Max tables to include (default 50, max 200)" },
255
- "max_columns": { "type": "number", "description": "Max columns per table (default 12, max 200)" },
256
- "include_relationships": { "type": "boolean", "description": "Whether to include FK relationships (default true)" }
411
+ "database": {
412
+ "type": "string",
413
+ "description": "Optional specific database name"
414
+ },
415
+ "max_tables": {
416
+ "type": "number",
417
+ "description": "Max tables to include (default 50, max 200)"
418
+ },
419
+ "max_columns": {
420
+ "type": "number",
421
+ "description": "Max columns per table (default 12, max 200)"
422
+ },
423
+ "include_relationships": {
424
+ "type": "boolean",
425
+ "description": "Whether to include FK relationships (default true)"
426
+ }
257
427
  }
258
428
  },
259
429
  "output_schema": {
260
430
  "type": "object",
261
431
  "properties": {
262
- "database": { "type": "string" },
263
- "total_tables": { "type": "number" },
264
- "context_text": { "type": "string" }
432
+ "database": {
433
+ "type": "string"
434
+ },
435
+ "total_tables": {
436
+ "type": "number"
437
+ },
438
+ "context_text": {
439
+ "type": "string"
440
+ }
265
441
  }
266
442
  }
267
443
  },
@@ -289,23 +465,77 @@
289
465
  "items": {
290
466
  "type": "object",
291
467
  "properties": {
292
- "table_name": { "type": "string" },
293
- "row_count": { "type": "number" },
294
- "data_size_bytes": { "type": "number" },
295
- "index_size_bytes": { "type": "number" },
296
- "total_size_mb": { "type": "string" }
468
+ "table_name": {
469
+ "type": "string"
470
+ },
471
+ "row_count": {
472
+ "type": "number"
473
+ },
474
+ "data_size_bytes": {
475
+ "type": "number"
476
+ },
477
+ "index_size_bytes": {
478
+ "type": "number"
479
+ },
480
+ "total_size_mb": {
481
+ "type": "string"
482
+ }
297
483
  }
298
484
  }
299
485
  },
300
486
  "summary": {
301
487
  "type": "object",
302
488
  "properties": {
303
- "total_tables": { "type": "number" },
304
- "total_size_mb": { "type": "string" }
489
+ "total_tables": {
490
+ "type": "number"
491
+ },
492
+ "total_size_mb": {
493
+ "type": "string"
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+ },
500
+ {
501
+ "name": "repair_query",
502
+ "description": "Analyzes a SQL query (and optional error) to suggest repairs or optimizations using EXPLAIN and heuristics.",
503
+ "input_schema": {
504
+ "type": "object",
505
+ "properties": {
506
+ "query": {
507
+ "type": "string",
508
+ "description": "The SQL query to analyze or repair"
509
+ },
510
+ "error_message": {
511
+ "type": "string",
512
+ "description": "Optional error message received when executing the query"
513
+ }
514
+ },
515
+ "required": [
516
+ "query"
517
+ ]
518
+ },
519
+ "output_schema": {
520
+ "type": "object",
521
+ "properties": {
522
+ "status": {
523
+ "type": "string"
524
+ },
525
+ "analysis": {
526
+ "type": "object"
527
+ },
528
+ "fixed_query": {
529
+ "type": "string"
530
+ },
531
+ "suggestions": {
532
+ "type": "array",
533
+ "items": {
534
+ "type": "string"
305
535
  }
306
536
  }
307
537
  }
308
538
  }
309
539
  }
310
540
  ]
311
- }
541
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@berthojoris/mcp-mysql-server",
3
- "version": "1.13.0",
3
+ "version": "1.14.1",
4
4
  "description": "Model Context Protocol server for MySQL database integration with dynamic per-project permissions, backup/restore, data import/export, and data migration capabilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -86,4 +86,4 @@
86
86
  "ts-node": "^10.9.1",
87
87
  "typescript": "^5.2.2"
88
88
  }
89
- }
89
+ }