Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 111 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ npm run build

## Usage Options

There are two ways to use this MCP server with Claude:
There are three ways to use this MCP server with Claude:

1. **Direct usage**: Install the package globally and use it directly
2. **Local development**: Run from your local development environment
3. **Multi-database mode**: Connect to multiple databases simultaneously using a config file

### Direct Usage with NPM Package

Expand Down Expand Up @@ -138,6 +139,66 @@ Required parameters:

Note: SSL is automatically enabled for AWS IAM authentication

### Multi-Database Mode

To connect to multiple databases simultaneously, use one of the following:

**From a config file:**
```
node dist/src/index.js --config /path/to/databases.json
```

**From an inline JSON string (useful for Kubernetes/Docker):**
```
node dist/src/index.js --config '{"databases":[...]}'
```

**From an environment variable (ideal for Kubernetes Secrets):**
```
node dist/src/index.js --config-env DB_CONFIG
```

The `--config` flag auto-detects whether the value is a JSON string (starts with `{`) or a file path. The `--config-env` flag reads the JSON config from the named environment variable.

The config JSON format:

```json
{
"databases": [
{
"name": "users-service",
"type": "postgresql",
"host": "localhost",
"port": 5432,
"database": "users_db",
"user": "admin",
"password": "secret"
},
{
"name": "orders-service",
"type": "mysql",
"host": "localhost",
"port": 3306,
"database": "orders_db",
"user": "admin",
"password": "secret"
},
{
"name": "analytics",
"type": "sqlite",
"path": "/data/analytics.db"
}
]
}
```

Each database entry requires:
- `name`: A unique alias for the database
- `type`: One of `sqlite`, `sqlserver`, `postgresql`, `mysql`
- Connection fields depending on type (same as the CLI parameters above)

When multiple databases are configured, pass the `database` parameter in tool calls to specify which database to target. Use the `list_databases` tool to see all available connections.

## Configuring Claude Desktop

### Direct Usage Configuration
Expand Down Expand Up @@ -209,6 +270,52 @@ If you installed the package globally, configure Claude Desktop with:
}
```

### Multi-Database Configuration

To connect to multiple databases from a single MCP server (ideal for microservices environments):

**Using a config file:**
```json
{
"mcpServers": {
"company-databases": {
"command": "npx",
"args": [
"-y",
"@executeautomation/database-server",
"--config", "/path/to/databases.json"
]
}
}
}
```

**Using an environment variable (Kubernetes / Docker):**
```json
{
"mcpServers": {
"company-databases": {
"command": "npx",
"args": [
"-y",
"@executeautomation/database-server",
"--config-env", "DB_CONFIG"
]
}
}
}
```

In Kubernetes, set `DB_CONFIG` from a Secret:
```yaml
env:
- name: DB_CONFIG
valueFrom:
secretKeyRef:
name: mcp-db-config
key: config.json
```

### Local Development Configuration

For local development, configure Claude Desktop to use your locally built version:
Expand Down Expand Up @@ -284,6 +391,7 @@ The MCP Database Server provides the following tools that Claude can use:

| Tool | Description | Required Parameters |
|------|-------------|---------------------|
| `list_databases` | List all configured database connections | None |
| `read_query` | Execute SELECT queries to read data | `query`: SQL SELECT statement |
| `write_query` | Execute INSERT, UPDATE, or DELETE queries | `query`: SQL modification statement |
| `create_table` | Create new tables in the database | `query`: CREATE TABLE statement |
Expand All @@ -295,6 +403,8 @@ The MCP Database Server provides the following tools that Claude can use:
| `append_insight` | Add a business insight to memo | `insight`: Text of insight |
| `list_insights` | List all business insights | None |

All database tools (except `append_insight`, `list_insights`, `list_databases`) accept an optional `database` parameter to specify which database to target. This parameter is required when multiple databases are configured.

For practical examples of how to use these tools with Claude, see [Usage Examples](docs/usage-examples.md).

## Additional Documentation
Expand Down
173 changes: 173 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { readFileSync } from 'fs';

export interface DatabaseConfig {
name: string;
type: string;
path?: string;
host?: string;
server?: string;
database?: string;
user?: string;
password?: string;
port?: number;
ssl?: boolean | string;
connectionTimeout?: number;
awsIamAuth?: boolean;
awsRegion?: string;
}

export interface MultiDbConfig {
databases: DatabaseConfig[];
}

const VALID_TYPES = new Set(['sqlite', 'sqlserver', 'postgresql', 'postgres', 'mysql']);

export function validateDatabaseConfig(db: DatabaseConfig, index: number): void {
if (!db.name || typeof db.name !== 'string') {
throw new Error(`databases[${index}]: "name" is required and must be a string`);
}

if (!db.type || !VALID_TYPES.has(db.type.toLowerCase())) {
throw new Error(`databases[${index}] ("${db.name}"): "type" must be one of: sqlite, sqlserver, postgresql, mysql`);
}

const type = db.type.toLowerCase();

if (type === 'sqlite') {
if (!db.path) {
throw new Error(`databases[${index}] ("${db.name}"): sqlite requires "path"`);
}
} else if (type === 'sqlserver') {
if (!db.server || !db.database) {
throw new Error(`databases[${index}] ("${db.name}"): sqlserver requires "server" and "database"`);
}
} else if (type === 'postgresql' || type === 'postgres') {
if (!db.host || !db.database) {
throw new Error(`databases[${index}] ("${db.name}"): postgresql requires "host" and "database"`);
}
} else if (type === 'mysql') {
if (!db.host || !db.database) {
throw new Error(`databases[${index}] ("${db.name}"): mysql requires "host" and "database"`);
}
if (db.awsIamAuth) {
if (!db.user) {
throw new Error(`databases[${index}] ("${db.name}"): AWS IAM authentication requires "user"`);
}
if (!db.awsRegion) {
throw new Error(`databases[${index}] ("${db.name}"): AWS IAM authentication requires "awsRegion"`);
}
}
}
}

export function configToConnectionInfo(db: DatabaseConfig): { dbType: string; connectionInfo: any } {
const type = db.type.toLowerCase();

if (type === 'sqlite') {
return { dbType: 'sqlite', connectionInfo: db.path };
}

if (type === 'sqlserver') {
return {
dbType: 'sqlserver',
connectionInfo: {
server: db.server,
database: db.database,
user: db.user,
password: db.password,
port: db.port,
},
};
}

if (type === 'postgresql' || type === 'postgres') {
return {
dbType: 'postgresql',
connectionInfo: {
host: db.host,
database: db.database,
user: db.user,
password: db.password,
port: db.port,
ssl: db.ssl,
connectionTimeout: db.connectionTimeout,
},
};
}

// mysql
const connectionInfo: any = {
host: db.host,
database: db.database,
user: db.user,
password: db.password,
port: db.port,
ssl: db.awsIamAuth ? true : db.ssl,
connectionTimeout: db.connectionTimeout,
awsIamAuth: db.awsIamAuth || false,
awsRegion: db.awsRegion,
};
return { dbType: 'mysql', connectionInfo };
}

/**
* Parse and validate a JSON string as MultiDbConfig
*/
export function parseConfig(raw: string, source: string = 'config'): MultiDbConfig {
let parsed: any;
try {
parsed = JSON.parse(raw);
} catch (err) {
throw new Error(`Failed to parse ${source}: ${(err as Error).message}`);
}

if (!parsed.databases || !Array.isArray(parsed.databases)) {
throw new Error(`${source} must contain a "databases" array`);
}

if (parsed.databases.length === 0) {
throw new Error(`${source}: "databases" array must not be empty`);
}

const names = new Set<string>();
for (let i = 0; i < parsed.databases.length; i++) {
validateDatabaseConfig(parsed.databases[i], i);
const name = parsed.databases[i].name;
if (names.has(name)) {
throw new Error(`Duplicate database name: "${name}"`);
}
names.add(name);
}

return parsed as MultiDbConfig;
}

/**
* Load config from a file path or inline JSON string.
* Auto-detects: if value starts with '{', treats as JSON; otherwise reads as file.
*/
export function loadConfig(filePathOrJson: string): MultiDbConfig {
if (filePathOrJson.trimStart().startsWith('{')) {
return parseConfig(filePathOrJson, 'inline JSON config');
}

let raw: string;
try {
raw = readFileSync(filePathOrJson, 'utf-8');
} catch (err) {
throw new Error(`Failed to read config file "${filePathOrJson}": ${(err as Error).message}`);
}

return parseConfig(raw, `config file "${filePathOrJson}"`);
}

/**
* Load config from an environment variable by name.
*/
export function loadConfigFromEnv(envVarName: string): MultiDbConfig {
const value = process.env[envVarName];
if (!value) {
throw new Error(`Environment variable "${envVarName}" is not set or empty`);
}
return parseConfig(value, `environment variable "${envVarName}"`);
}
Loading